Swapchain presentation and GLFW
- Render objects list - SDL2 replaced with GLFW - Swapchain synchronization, presentation and recreation on resize
This commit is contained in:
parent
389ed599c8
commit
1d65039e2f
@ -10,7 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
project (EtrediaEmpires)
|
project (EtrediaEmpires)
|
||||||
|
|
||||||
add_subdirectory(materials)
|
add_subdirectory(sdk/materials)
|
||||||
add_subdirectory(utils)
|
add_subdirectory(utils)
|
||||||
add_subdirectory (p7lib)
|
add_subdirectory (p7lib)
|
||||||
add_subdirectory (ECSAPI)
|
add_subdirectory (ECSAPI)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -23,7 +23,7 @@ list(TRANSFORM SOURCES PREPEND "src/")
|
|||||||
|
|
||||||
find_package(Boost REQUIRED COMPONENTS lockfree)
|
find_package(Boost REQUIRED COMPONENTS lockfree)
|
||||||
find_package(xxHash CONFIG REQUIRED)
|
find_package(xxHash CONFIG REQUIRED)
|
||||||
find_package(SDL2 CONFIG REQUIRED)
|
find_package(glfw3 CONFIG REQUIRED)
|
||||||
find_package(Vulkan REQUIRED)
|
find_package(Vulkan REQUIRED)
|
||||||
find_package(RmlUi CONFIG REQUIRED)
|
find_package(RmlUi CONFIG REQUIRED)
|
||||||
|
|
||||||
@ -36,12 +36,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC utils)
|
|||||||
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::lockfree)
|
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::lockfree)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
|
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE p7)
|
target_link_libraries(${PROJECT_NAME} PRIVATE p7)
|
||||||
target_link_libraries(
|
target_link_libraries(${PROJECT_NAME} PRIVATE glfw)
|
||||||
${PROJECT_NAME}
|
|
||||||
PRIVATE
|
|
||||||
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
|
|
||||||
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
|
|
||||||
)
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan)
|
target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi)
|
target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi)
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ struct AssetShader : AssetData
|
|||||||
|
|
||||||
struct AssetMaterial : AssetData
|
struct AssetMaterial : AssetData
|
||||||
{
|
{
|
||||||
uint32_t vertexShader;
|
uint32_t vertexShaderAssetID;
|
||||||
uint32_t fragmentShader;
|
uint32_t fragmentShaderAssetID;
|
||||||
|
uint32_t renderType;
|
||||||
|
uint32_t renderStage;
|
||||||
};
|
};
|
||||||
@ -85,9 +85,6 @@ public:
|
|||||||
void Start();
|
void Start();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void Request(const AssetStreamCommand &command);
|
|
||||||
void Request(const AssetStreamCommand::Type &type);
|
|
||||||
|
|
||||||
void LoadShaders();
|
void LoadShaders();
|
||||||
|
|
||||||
void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path);
|
void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path);
|
||||||
@ -115,6 +112,8 @@ private:
|
|||||||
std::vector<AssetMaterial*> materialsStorage;
|
std::vector<AssetMaterial*> materialsStorage;
|
||||||
|
|
||||||
void Threading();
|
void Threading();
|
||||||
|
void Request(const AssetStreamCommand &command);
|
||||||
|
void Request(const AssetStreamCommand::Type &type);
|
||||||
void Loop();
|
void Loop();
|
||||||
void ProcessCommand(AssetStreamCommand &command);
|
void ProcessCommand(AssetStreamCommand &command);
|
||||||
void Release();
|
void Release();
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "AssetData.hpp"
|
#include "AssetData.hpp"
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#define PIPELINE_CREATE_NEW 0
|
#define PIPELINE_CREATE_NEW 0
|
||||||
#define PIPELINE_CREATE_DERIVED (1 << 0)
|
#define PIPELINE_CREATE_DERIVED (1 << 0)
|
||||||
@ -25,11 +26,6 @@ struct PipelineConfig
|
|||||||
VkRect2D defaultScissors;
|
VkRect2D defaultScissors;
|
||||||
std::vector<VkDynamicState> dynamicStates;
|
std::vector<VkDynamicState> dynamicStates;
|
||||||
|
|
||||||
std::vector<VkAttachmentDescription> colorAttachmentConfigs;
|
|
||||||
std::vector<VkAttachmentReference> colorAttachmentRefs;
|
|
||||||
std::vector<VkSubpassDescription> subpassConfigs;
|
|
||||||
VkRenderPassCreateInfo renderPassConfig;
|
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipelineConfig;
|
VkGraphicsPipelineCreateInfo pipelineConfig;
|
||||||
|
|
||||||
PipelineConfig Copy() const;
|
PipelineConfig Copy() const;
|
||||||
@ -39,26 +35,26 @@ struct Pipeline
|
|||||||
{
|
{
|
||||||
VkPipeline instance;
|
VkPipeline instance;
|
||||||
VkPipelineLayout layout;
|
VkPipelineLayout layout;
|
||||||
VkRenderPass renderPass;
|
VkViewport viewport;
|
||||||
|
VkRect2D scissors;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PipelineFactory
|
class PipelineFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat);
|
PipelineFactory(const AssetMaterial &baseMaterial, const VkRenderPass &baseRenderPass, VkDevice &device, VkExtent2D &surfaceExtent);
|
||||||
~PipelineFactory();
|
~PipelineFactory();
|
||||||
|
|
||||||
PipelineConfig &Request(const AssetMaterial &material, uint32_t flags);
|
Pipeline& Get(const AssetMaterial &material);
|
||||||
|
void Create(const AssetMaterial &material, const VkRenderPass &renderPass, const uint32_t &flags);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PipelineConfig basePipelineConfig;
|
PipelineConfig basePipelineConfig;
|
||||||
|
|
||||||
Pipeline basePipeline;
|
Pipeline basePipeline;
|
||||||
std::vector<Pipeline> pipelines;
|
std::unordered_map<const AssetMaterial*, Pipeline> pipelines;
|
||||||
|
|
||||||
VkDevice &device;
|
VkDevice &device;
|
||||||
VkExtent2D &surfaceExtent;
|
VkExtent2D &surfaceExtent;
|
||||||
VkFormat &surfaceFormat;
|
|
||||||
|
|
||||||
void Create(const AssetMaterial &material, uint32_t flags);
|
|
||||||
};
|
};
|
||||||
@ -2,11 +2,12 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
#include <Boost/lockfree/spsc_queue.hpp>
|
#include <Boost/lockfree/spsc_queue.hpp>
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#define GLFW_INCLUDE_VULKAN
|
||||||
#include <SDL2/SDL_vulkan.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
|
||||||
#include <RmlUi/Core.h>
|
#include <RmlUi/Core.h>
|
||||||
|
|
||||||
#include "PipelineFactory.hpp"
|
#include "PipelineFactory.hpp"
|
||||||
@ -24,16 +25,46 @@
|
|||||||
|
|
||||||
#define QUEUE_FAMILY_NO_VALUE UINT32_MAX
|
#define QUEUE_FAMILY_NO_VALUE UINT32_MAX
|
||||||
|
|
||||||
|
#define RENDER_PASS_BASE 438461662
|
||||||
|
#define RENDER_PASS_GUI
|
||||||
|
|
||||||
|
struct Transform
|
||||||
|
{
|
||||||
|
float x = 0.0f, y = 0.0f;
|
||||||
|
float scaleX = 1.0f, scaleY = 1.0f, scaleZ = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mesh
|
||||||
|
{
|
||||||
|
uint32_t vertexCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderObject
|
||||||
|
{
|
||||||
|
// Multiple transforms for batching
|
||||||
|
std::vector<Transform> transforms;
|
||||||
|
uint8_t depth = 0;
|
||||||
|
Mesh mesh; //TODO: mesh (vertex/index buffer)
|
||||||
|
//TODO: texture
|
||||||
|
|
||||||
|
AssetMaterial &material;
|
||||||
|
//TODO: materialParams (uniforms)
|
||||||
|
};
|
||||||
|
bool operator < (const RenderObject& lhs, const RenderObject& rhs);
|
||||||
|
|
||||||
struct RenderCommand
|
struct RenderCommand
|
||||||
{
|
{
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
NONE,
|
NONE,
|
||||||
|
|
||||||
//STORE screenHeight and screenWidth BEFORE request
|
REGISTER_OBJECTS,
|
||||||
RESIZE_WINDOW,
|
RESIZE_WINDOW, //STORE screenHeight and screenWidth BEFORE request
|
||||||
SHUTDOWN
|
SHUTDOWN
|
||||||
} type = NONE;
|
} type = NONE;
|
||||||
|
|
||||||
|
RenderObject *objects;
|
||||||
|
size_t objectsSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QueueFamiliesBundle
|
struct QueueFamiliesBundle
|
||||||
@ -48,6 +79,44 @@ struct QueuesBundle
|
|||||||
VkQueue present;
|
VkQueue present;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DrawConfig
|
||||||
|
{
|
||||||
|
VkCommandBufferBeginInfo commandBeginConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.pInheritanceInfo = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
VkClearValue clearColor {{{1.0f, 1.0f, 1.0f, 1.0f}}};
|
||||||
|
|
||||||
|
VkRenderPassBeginInfo renderPassBeginConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.clearValueCount = 1,
|
||||||
|
.pClearValues = &clearColor
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineStageFlags waitImageStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
|
||||||
|
VkSubmitInfo submitConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitDstStageMask = &waitImageStage,
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
.signalSemaphoreCount = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPresentInfoKHR presentConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.swapchainCount = 1,
|
||||||
|
.pResults = nullptr
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
struct SurfaceInfo
|
struct SurfaceInfo
|
||||||
{
|
{
|
||||||
VkSurfaceCapabilitiesKHR capabilities;
|
VkSurfaceCapabilitiesKHR capabilities;
|
||||||
@ -65,44 +134,67 @@ public:
|
|||||||
std::atomic<bool> isRunning;
|
std::atomic<bool> isRunning;
|
||||||
std::atomic<int> screenWidth, screenHeight;
|
std::atomic<int> screenWidth, screenHeight;
|
||||||
|
|
||||||
Renderer(SDL_Window *window, const AssetMaterial& baseMaterial);
|
Renderer(GLFWwindow *window, const AssetMaterial& baseMaterial);
|
||||||
~Renderer();
|
~Renderer();
|
||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void Request(const RenderCommand &command);
|
void RegisterObjects(RenderObject* objects, size_t size);
|
||||||
void Request(const RenderCommand::Type &type);
|
void ResizeWindow(int width, int height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::stop_token *stoken;
|
std::stop_token *stoken;
|
||||||
std::jthread renderThread;
|
std::jthread renderThread;
|
||||||
boost::lockfree::spsc_queue<RenderCommand, boost::lockfree::capacity<512>> commands;
|
boost::lockfree::spsc_queue<RenderCommand, boost::lockfree::capacity<512>> commands;
|
||||||
|
|
||||||
SDL_Window *window;
|
GLFWwindow *window;
|
||||||
|
|
||||||
VkInstance vulkan;
|
VkInstance vulkan;
|
||||||
VkSurfaceKHR surface;
|
VkSurfaceKHR surface;
|
||||||
VkPhysicalDevice gpu;
|
VkPhysicalDevice gpu;
|
||||||
VkDevice device;
|
VkDevice device;
|
||||||
VkSwapchainKHR swapchain;
|
VkSwapchainKHR swapchain;
|
||||||
|
VkRenderPass baseRenderPass;
|
||||||
|
VkCommandPool commandPool = nullptr;
|
||||||
|
std::vector<VkCommandBuffer> commandBuffers;
|
||||||
std::vector<VkImage> frames;
|
std::vector<VkImage> frames;
|
||||||
std::vector<VkImageView> frameViews;
|
std::vector<VkImageView> frameViews;
|
||||||
|
std::vector<VkFramebuffer> baseFrameBuffers;
|
||||||
|
std::vector<VkSemaphore> imageAvailable;
|
||||||
|
std::vector<VkSemaphore> renderFinished;
|
||||||
|
std::vector<VkFence> inFlightFences;
|
||||||
|
std::vector<VkFence> imagesInFlight;
|
||||||
|
|
||||||
SurfaceInfo surfaceInfo;
|
SurfaceInfo surfaceInfo;
|
||||||
QueueFamiliesBundle queueFamilies;
|
QueueFamiliesBundle queueFamilies;
|
||||||
QueuesBundle queues;
|
QueuesBundle queues;
|
||||||
const float defaultQueuePriority = 1.0f;
|
DrawConfig drawConfig;
|
||||||
const AssetMaterial &baseMaterial;
|
|
||||||
PipelineFactory *pipelineFactory;
|
PipelineFactory *pipelineFactory;
|
||||||
|
size_t currentFrame = 0;
|
||||||
|
const float DEFAULT_QUEUE_PRIORITY = 1.0f;
|
||||||
|
const AssetMaterial &baseMaterial;
|
||||||
|
const size_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
|
std::set<RenderObject> objects;
|
||||||
|
|
||||||
Rml::Context *uiContext;
|
Rml::Context *uiContext;
|
||||||
|
|
||||||
void InitVulkan();
|
void InitVulkan();
|
||||||
void SetupDevice();
|
void SetupDevice();
|
||||||
void SetupSwapchain();
|
void SetupSwapchain();
|
||||||
|
void RecreateSwapchain();
|
||||||
|
void ReleaseSwapchain();
|
||||||
|
void SetupRenderPasses();
|
||||||
|
void SetupBuffers();
|
||||||
|
void SetupSync();
|
||||||
|
|
||||||
|
void Draw();
|
||||||
|
|
||||||
void Threading();
|
void Threading();
|
||||||
|
void Request(const RenderCommand &command);
|
||||||
|
void Request(const RenderCommand::Type &type);
|
||||||
|
void Init();
|
||||||
void Loop();
|
void Loop();
|
||||||
void ProcessCommand(const RenderCommand &command);
|
void ProcessCommand(const RenderCommand &command);
|
||||||
void Release();
|
void Release();
|
||||||
|
|||||||
@ -10,13 +10,6 @@ PipelineConfig PipelineConfig::Copy() const
|
|||||||
copy.viewportStateConfig.pScissors = ©.defaultScissors;
|
copy.viewportStateConfig.pScissors = ©.defaultScissors;
|
||||||
copy.dynamicStateConfig.pDynamicStates = copy.dynamicStates.data();
|
copy.dynamicStateConfig.pDynamicStates = copy.dynamicStates.data();
|
||||||
copy.colorBlendConfig.pAttachments = copy.colorBlendAttachmentConfigs.data();
|
copy.colorBlendConfig.pAttachments = copy.colorBlendAttachmentConfigs.data();
|
||||||
copy.renderPassConfig.pAttachments = copy.colorAttachmentConfigs.data();
|
|
||||||
copy.renderPassConfig.pSubpasses = copy.subpassConfigs.data();
|
|
||||||
for (VkSubpassDescription &config : copy.subpassConfigs)
|
|
||||||
{
|
|
||||||
config.pColorAttachments = copy.colorAttachmentRefs.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.pipelineConfig.pVertexInputState = ©.vertexInputConfig;
|
copy.pipelineConfig.pVertexInputState = ©.vertexInputConfig;
|
||||||
copy.pipelineConfig.pInputAssemblyState = ©.inputAssemblyConfig;
|
copy.pipelineConfig.pInputAssemblyState = ©.inputAssemblyConfig;
|
||||||
copy.pipelineConfig.pViewportState = ©.viewportStateConfig;
|
copy.pipelineConfig.pViewportState = ©.viewportStateConfig;
|
||||||
@ -29,10 +22,16 @@ PipelineConfig PipelineConfig::Copy() const
|
|||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat) :
|
PipelineFactory::PipelineFactory(
|
||||||
device(device), surfaceExtent(surfaceExtent), surfaceFormat(surfaceFormat)
|
const AssetMaterial &baseMaterial,
|
||||||
|
const VkRenderPass &baseRenderPass,
|
||||||
|
VkDevice &device,
|
||||||
|
VkExtent2D &surfaceExtent
|
||||||
|
) :
|
||||||
|
device(device), surfaceExtent(surfaceExtent)
|
||||||
{
|
{
|
||||||
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT);
|
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT);
|
||||||
|
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR);
|
||||||
|
|
||||||
basePipelineConfig.colorBlendAttachmentConfigs.push_back({
|
basePipelineConfig.colorBlendAttachmentConfigs.push_back({
|
||||||
.blendEnable = VK_TRUE,
|
.blendEnable = VK_TRUE,
|
||||||
@ -45,27 +44,6 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
|
|||||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
|
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
|
||||||
});
|
});
|
||||||
|
|
||||||
basePipelineConfig.colorAttachmentConfigs.push_back({
|
|
||||||
.format = surfaceFormat,
|
|
||||||
.samples = VK_SAMPLE_COUNT_1_BIT, // TODO: Multisampling
|
|
||||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
|
||||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
|
||||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
||||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
||||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
||||||
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
|
||||||
});
|
|
||||||
basePipelineConfig.colorAttachmentRefs.push_back({
|
|
||||||
.attachment = 0,
|
|
||||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
|
||||||
});
|
|
||||||
|
|
||||||
basePipelineConfig.subpassConfigs.push_back({
|
|
||||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
||||||
.colorAttachmentCount = static_cast<uint32_t>(basePipelineConfig.colorAttachmentRefs.size()),
|
|
||||||
.pColorAttachments = basePipelineConfig.colorAttachmentRefs.data()
|
|
||||||
});
|
|
||||||
|
|
||||||
basePipelineConfig.vertexInputConfig = {
|
basePipelineConfig.vertexInputConfig = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@ -152,15 +130,6 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
|
|||||||
.pAttachments = basePipelineConfig.colorBlendAttachmentConfigs.data(),
|
.pAttachments = basePipelineConfig.colorBlendAttachmentConfigs.data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
basePipelineConfig.renderPassConfig = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.attachmentCount = static_cast<uint32_t>(basePipelineConfig.colorAttachmentConfigs.size()),
|
|
||||||
.pAttachments = basePipelineConfig.colorAttachmentConfigs.data(),
|
|
||||||
.subpassCount = static_cast<uint32_t>(basePipelineConfig.subpassConfigs.size()),
|
|
||||||
.pSubpasses = basePipelineConfig.subpassConfigs.data()
|
|
||||||
};
|
|
||||||
|
|
||||||
basePipelineConfig.pipelineConfig = {
|
basePipelineConfig.pipelineConfig = {
|
||||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@ -172,17 +141,23 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
|
|||||||
.pDepthStencilState = nullptr,
|
.pDepthStencilState = nullptr,
|
||||||
.pColorBlendState = &basePipelineConfig.colorBlendConfig,
|
.pColorBlendState = &basePipelineConfig.colorBlendConfig,
|
||||||
.pDynamicState = &basePipelineConfig.dynamicStateConfig,
|
.pDynamicState = &basePipelineConfig.dynamicStateConfig,
|
||||||
.subpass = 0,
|
|
||||||
.basePipelineHandle = VK_NULL_HANDLE,
|
.basePipelineHandle = VK_NULL_HANDLE,
|
||||||
.basePipelineIndex = -1
|
.basePipelineIndex = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
LOG(INFO, "Creating base material pipeline...");
|
LOG(INFO, "Creating base material pipeline...");
|
||||||
Create(baseMaterial, PIPELINE_CREATE_NEW);
|
Create(baseMaterial, baseRenderPass, PIPELINE_CREATE_NEW);
|
||||||
LOG(INFO, "Base material pipeline created");
|
LOG(INFO, "Base material pipeline created");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
Pipeline& PipelineFactory::Get(const AssetMaterial &material)
|
||||||
|
{
|
||||||
|
if (pipelines.contains(&material))
|
||||||
|
return pipelines[&material];
|
||||||
|
else return basePipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineFactory::Create(const AssetMaterial &material, const VkRenderPass &renderPass, const uint32_t &flags)
|
||||||
{
|
{
|
||||||
Pipeline newPipeline {};
|
Pipeline newPipeline {};
|
||||||
Pipeline *result = &basePipeline;
|
Pipeline *result = &basePipeline;
|
||||||
@ -209,11 +184,6 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
|||||||
LOG(CRITICAL, "Failed to create pipeline layout");
|
LOG(CRITICAL, "Failed to create pipeline layout");
|
||||||
config->pipelineConfig.layout = result->layout;
|
config->pipelineConfig.layout = result->layout;
|
||||||
|
|
||||||
vkResult = vkCreateRenderPass(device, &config->renderPassConfig, nullptr, &result->renderPass);
|
|
||||||
if (vkResult != VK_SUCCESS)
|
|
||||||
LOG(CRITICAL, "Failed to create render pass");
|
|
||||||
config->pipelineConfig.renderPass = result->renderPass;
|
|
||||||
|
|
||||||
// Configure shaders
|
// Configure shaders
|
||||||
AssetHandle shaderHandle;
|
AssetHandle shaderHandle;
|
||||||
AssetShader *shader;
|
AssetShader *shader;
|
||||||
@ -224,7 +194,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
|||||||
|
|
||||||
// Vertex
|
// Vertex
|
||||||
VkShaderModule vertexModule;
|
VkShaderModule vertexModule;
|
||||||
shaderHandle = Engine::assetManager->Find(material.vertexShader);
|
shaderHandle = Engine::assetManager->Find(material.vertexShaderAssetID);
|
||||||
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
|
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
|
||||||
shaderModuleCreateConfig = {
|
shaderModuleCreateConfig = {
|
||||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||||
@ -246,7 +216,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
|||||||
|
|
||||||
// Fragment
|
// Fragment
|
||||||
VkShaderModule fragmentModule;
|
VkShaderModule fragmentModule;
|
||||||
shaderHandle = Engine::assetManager->Find(material.fragmentShader);
|
shaderHandle = Engine::assetManager->Find(material.fragmentShaderAssetID);
|
||||||
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
|
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
|
||||||
shaderModuleCreateConfig = {
|
shaderModuleCreateConfig = {
|
||||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||||
@ -268,6 +238,8 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
|||||||
|
|
||||||
config->pipelineConfig.stageCount = static_cast<uint32_t>(stagesConfigs.size());
|
config->pipelineConfig.stageCount = static_cast<uint32_t>(stagesConfigs.size());
|
||||||
config->pipelineConfig.pStages = stagesConfigs.data();
|
config->pipelineConfig.pStages = stagesConfigs.data();
|
||||||
|
config->pipelineConfig.renderPass = renderPass;
|
||||||
|
config->pipelineConfig.subpass = material.renderStage;
|
||||||
|
|
||||||
vkResult = vkCreateGraphicsPipelines(
|
vkResult = vkCreateGraphicsPipelines(
|
||||||
device,
|
device,
|
||||||
@ -281,7 +253,10 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
|
|||||||
LOG(CRITICAL, "Failed to create pipeline");
|
LOG(CRITICAL, "Failed to create pipeline");
|
||||||
|
|
||||||
if (flags & PIPELINE_CREATE_DERIVED)
|
if (flags & PIPELINE_CREATE_DERIVED)
|
||||||
pipelines.push_back(newPipeline);
|
pipelines[&material] = newPipeline;
|
||||||
|
|
||||||
|
result->viewport = config->defaultViewport;
|
||||||
|
result->scissors = config->defaultScissors;
|
||||||
|
|
||||||
vkDestroyShaderModule(device, vertexModule, nullptr);
|
vkDestroyShaderModule(device, vertexModule, nullptr);
|
||||||
vkDestroyShaderModule(device, fragmentModule, nullptr);
|
vkDestroyShaderModule(device, fragmentModule, nullptr);
|
||||||
@ -291,11 +266,9 @@ PipelineFactory::~PipelineFactory()
|
|||||||
{
|
{
|
||||||
vkDestroyPipeline(device, basePipeline.instance, nullptr);
|
vkDestroyPipeline(device, basePipeline.instance, nullptr);
|
||||||
vkDestroyPipelineLayout(device, basePipeline.layout, nullptr);
|
vkDestroyPipelineLayout(device, basePipeline.layout, nullptr);
|
||||||
vkDestroyRenderPass(device, basePipeline.renderPass, nullptr);
|
for (auto &item : pipelines)
|
||||||
for (Pipeline &pipeline : pipelines)
|
|
||||||
{
|
{
|
||||||
vkDestroyPipeline(device, pipeline.instance, nullptr);
|
vkDestroyPipeline(device, item.second.instance, nullptr);
|
||||||
vkDestroyPipelineLayout(device, pipeline.layout, nullptr);
|
vkDestroyPipelineLayout(device, item.second.layout, nullptr);
|
||||||
vkDestroyRenderPass(device, pipeline.renderPass, nullptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,11 +9,20 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
Renderer::Renderer(SDL_Window *window, const AssetMaterial &baseMaterial) :
|
bool operator < (const RenderObject& lhs, const RenderObject& rhs)
|
||||||
|
{
|
||||||
|
return &lhs.material < &rhs.material;
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer::Renderer(GLFWwindow *window, const AssetMaterial &baseMaterial) :
|
||||||
isRunning(false),
|
isRunning(false),
|
||||||
baseMaterial(baseMaterial)
|
baseMaterial(baseMaterial)
|
||||||
{
|
{
|
||||||
this->window = window;
|
this->window = window;
|
||||||
|
int width = -1, height = -1;
|
||||||
|
glfwGetFramebufferSize(window, &width, &height);
|
||||||
|
screenWidth.store(width);
|
||||||
|
screenHeight.store(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer()
|
Renderer::~Renderer()
|
||||||
@ -31,17 +40,10 @@ void Renderer::InitVulkan()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get Vulkan extensions list
|
// Get Vulkan extensions list
|
||||||
uint32_t extensionsSDLCount = 0;
|
uint32_t extensionsGLFWCount = 0;
|
||||||
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, nullptr) )
|
const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&extensionsGLFWCount);
|
||||||
LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError());
|
for (size_t i = 0; i < extensionsGLFWCount; i++)
|
||||||
|
vulkanExtensions.push_back(glfwExtensions[i]);
|
||||||
std::vector<const char*> vulkanExtensionsSDL(extensionsSDLCount);
|
|
||||||
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, vulkanExtensionsSDL.data()) )
|
|
||||||
{
|
|
||||||
LOG(CRITICAL, "Fail to register SDL Vulkan extensions! %hs", SDL_GetError());
|
|
||||||
vulkanExtensionsSDL.clear();
|
|
||||||
}
|
|
||||||
else vulkanExtensions.insert(vulkanExtensions.end(), vulkanExtensionsSDL.begin(), vulkanExtensionsSDL.end());
|
|
||||||
|
|
||||||
for (const char *extension : vulkanExtensions)
|
for (const char *extension : vulkanExtensions)
|
||||||
{
|
{
|
||||||
@ -80,9 +82,11 @@ void Renderer::InitVulkan()
|
|||||||
else LOG(INFO, "Vulkan is instantiated");
|
else LOG(INFO, "Vulkan is instantiated");
|
||||||
|
|
||||||
// Create surface
|
// Create surface
|
||||||
if ( !SDL_Vulkan_CreateSurface(window, vulkan, &surface) )
|
|
||||||
LOG(CRITICAL, "Failed to create SDL Vulkan surface. %hs", SDL_GetError());
|
vkResult = glfwCreateWindowSurface(vulkan, window, nullptr, &surface);
|
||||||
else LOG(INFO, "SDL Vulkan surface created");
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create GLFW Vulkan surface. Code: %d", vkResult);
|
||||||
|
else LOG(INFO, "GLFW Vulkan surface created");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::SetupDevice()
|
void Renderer::SetupDevice()
|
||||||
@ -230,10 +234,11 @@ void Renderer::SetupDevice()
|
|||||||
if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
|
if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
|
||||||
priority += GPU_DISCRETE_PRIORITY;
|
priority += GPU_DISCRETE_PRIORITY;
|
||||||
|
|
||||||
|
surfaceInfoBuffer.format.format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||||
for (const VkSurfaceFormatKHR &format : surfaceInfoBuffer.availableFormats)
|
for (const VkSurfaceFormatKHR &format : surfaceInfoBuffer.availableFormats)
|
||||||
{
|
{
|
||||||
surfaceInfoBuffer.format = format;
|
surfaceInfoBuffer.format = format;
|
||||||
if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
||||||
{
|
{
|
||||||
priority += GPU_B8G8R8_SRGB_PRIORITY;
|
priority += GPU_B8G8R8_SRGB_PRIORITY;
|
||||||
break;
|
break;
|
||||||
@ -279,7 +284,7 @@ void Renderer::SetupDevice()
|
|||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.queueFamilyIndex = queueFamily,
|
.queueFamilyIndex = queueFamily,
|
||||||
.queueCount = 1,
|
.queueCount = 1,
|
||||||
.pQueuePriorities = &defaultQueuePriority
|
.pQueuePriorities = &DEFAULT_QUEUE_PRIORITY
|
||||||
};
|
};
|
||||||
|
|
||||||
queuesCreateConfigs.push_back(queueCreateConfig);
|
queuesCreateConfigs.push_back(queueCreateConfig);
|
||||||
@ -321,7 +326,7 @@ void Renderer::SetupSwapchain()
|
|||||||
LOG(CRITICAL, "Failed to get surface capabilities. Code: %d", vkResult);
|
LOG(CRITICAL, "Failed to get surface capabilities. Code: %d", vkResult);
|
||||||
|
|
||||||
int width = 0, height = 0;
|
int width = 0, height = 0;
|
||||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
glfwGetFramebufferSize(window, &width, &height);
|
||||||
|
|
||||||
// Clamp extent
|
// Clamp extent
|
||||||
surfaceInfo.extent.width = (std::max)(
|
surfaceInfo.extent.width = (std::max)(
|
||||||
@ -410,6 +415,313 @@ void Renderer::SetupSwapchain()
|
|||||||
LOG(INFO, "Frame views are ready.");
|
LOG(INFO, "Frame views are ready.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::RecreateSwapchain()
|
||||||
|
{
|
||||||
|
vkDeviceWaitIdle(device);
|
||||||
|
ReleaseSwapchain();
|
||||||
|
|
||||||
|
SetupSwapchain();
|
||||||
|
SetupRenderPasses();
|
||||||
|
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, baseRenderPass, device, surfaceInfo.extent);
|
||||||
|
SetupBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::ReleaseSwapchain()
|
||||||
|
{
|
||||||
|
for (VkFramebuffer &frameBuffer : baseFrameBuffers)
|
||||||
|
{
|
||||||
|
vkDestroyFramebuffer(device, frameBuffer, nullptr);
|
||||||
|
}
|
||||||
|
vkFreeCommandBuffers(
|
||||||
|
device,
|
||||||
|
commandPool,
|
||||||
|
static_cast<uint32_t>(commandBuffers.size()),
|
||||||
|
commandBuffers.data()
|
||||||
|
);
|
||||||
|
|
||||||
|
vkDestroyRenderPass(device, baseRenderPass, nullptr);
|
||||||
|
Deallocate(pipelineFactory);
|
||||||
|
|
||||||
|
for (VkImageView &frameView : frameViews)
|
||||||
|
{
|
||||||
|
vkDestroyImageView(device, frameView, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDestroySwapchainKHR(device, swapchain, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::SetupRenderPasses()
|
||||||
|
{
|
||||||
|
VkAttachmentDescription colorAttachmentConfig {
|
||||||
|
.format = surfaceInfo.format.format,
|
||||||
|
.samples = VK_SAMPLE_COUNT_1_BIT, // TODO: Multisampling
|
||||||
|
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||||
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||||
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
||||||
|
};
|
||||||
|
|
||||||
|
VkAttachmentReference colorAttachmentRef {
|
||||||
|
.attachment = 0,
|
||||||
|
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
||||||
|
};
|
||||||
|
|
||||||
|
VkSubpassDescription subpass {
|
||||||
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachments = &colorAttachmentRef
|
||||||
|
};
|
||||||
|
|
||||||
|
VkSubpassDependency dependency
|
||||||
|
{
|
||||||
|
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||||
|
.dstSubpass = 0,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
|
.srcAccessMask = 0,
|
||||||
|
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo baseRenderPassConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &colorAttachmentConfig,
|
||||||
|
.subpassCount = 1,
|
||||||
|
.pSubpasses = &subpass,
|
||||||
|
.dependencyCount = 1,
|
||||||
|
.pDependencies = &dependency
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
VkResult vkResult = vkCreateRenderPass(device, &baseRenderPassConfig, nullptr, &baseRenderPass);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create base render pass. Code: %d", vkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::SetupBuffers()
|
||||||
|
{
|
||||||
|
VkResult vkResult;
|
||||||
|
|
||||||
|
baseFrameBuffers.resize(frameViews.size());
|
||||||
|
size_t i = 0;
|
||||||
|
for (const VkImageView &frameView : frameViews)
|
||||||
|
{
|
||||||
|
VkImageView attachments[] = {frameView};
|
||||||
|
|
||||||
|
VkFramebufferCreateInfo frameBufferConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.renderPass = baseRenderPass,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = attachments,
|
||||||
|
.width = surfaceInfo.extent.width,
|
||||||
|
.height = surfaceInfo.extent.height,
|
||||||
|
.layers = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
vkResult = vkCreateFramebuffer(device, &frameBufferConfig, nullptr, &baseFrameBuffers[i]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create framebuffer for base render pass. Code: %d", vkResult);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandPool == nullptr)
|
||||||
|
{
|
||||||
|
VkCommandPoolCreateInfo commandPoolConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||||
|
.queueFamilyIndex = queueFamilies.graphics,
|
||||||
|
};
|
||||||
|
|
||||||
|
vkResult = vkCreateCommandPool(device, &commandPoolConfig, nullptr, &commandPool);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create command pool. Code: %d", vkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
VkCommandBufferAllocateInfo allocateConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.commandPool = commandPool,
|
||||||
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, //TODO: reuse some commands using SECONDARY
|
||||||
|
.commandBufferCount = static_cast<uint32_t>(commandBuffers.size())
|
||||||
|
};
|
||||||
|
vkResult = vkAllocateCommandBuffers(device, &allocateConfig, commandBuffers.data());
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to allocate command buffers. Code: %d", vkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::Draw()
|
||||||
|
{
|
||||||
|
VkResult vkResult;
|
||||||
|
uint32_t imageIndex = 0;
|
||||||
|
|
||||||
|
vkResult = vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to wait for fence. Code: %d", vkResult);
|
||||||
|
|
||||||
|
vkResult = vkAcquireNextImageKHR(
|
||||||
|
device, swapchain,
|
||||||
|
UINT64_MAX,
|
||||||
|
imageAvailable[currentFrame],
|
||||||
|
VK_NULL_HANDLE,
|
||||||
|
&imageIndex
|
||||||
|
);
|
||||||
|
switch (vkResult)
|
||||||
|
{
|
||||||
|
case VK_SUCCESS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VK_ERROR_OUT_OF_DATE_KHR:
|
||||||
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
LOG(WARNING, "Swapchain needs to be recreated");
|
||||||
|
RecreateSwapchain();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG(ERROR, "Failed to get next image index. Code: %d", vkResult);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
|
||||||
|
}
|
||||||
|
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
|
||||||
|
|
||||||
|
vkResult = vkResetCommandBuffer(commandBuffers[currentFrame], 0);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to reset command buffer. Code: %d", vkResult);
|
||||||
|
vkResult = vkBeginCommandBuffer(commandBuffers[currentFrame], &drawConfig.commandBeginConfig);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to begin command buffer. Code: %d", vkResult);
|
||||||
|
|
||||||
|
drawConfig.renderPassBeginConfig.renderPass = baseRenderPass;
|
||||||
|
drawConfig.renderPassBeginConfig.framebuffer = baseFrameBuffers[imageIndex];
|
||||||
|
drawConfig.renderPassBeginConfig.renderArea = {
|
||||||
|
.offset = {.x = 0, .y = 0},
|
||||||
|
.extent = surfaceInfo.extent
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdBeginRenderPass(
|
||||||
|
commandBuffers[currentFrame],
|
||||||
|
&drawConfig.renderPassBeginConfig,
|
||||||
|
VK_SUBPASS_CONTENTS_INLINE
|
||||||
|
);
|
||||||
|
|
||||||
|
Pipeline* lastPipeline = nullptr;
|
||||||
|
Pipeline* pipeline = nullptr;
|
||||||
|
for (const RenderObject &object : objects)
|
||||||
|
{
|
||||||
|
pipeline = &pipelineFactory->Get(object.material);
|
||||||
|
if (pipeline != lastPipeline)
|
||||||
|
{
|
||||||
|
vkCmdBindPipeline(
|
||||||
|
commandBuffers[currentFrame],
|
||||||
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
pipeline->instance
|
||||||
|
);
|
||||||
|
vkCmdSetViewport(commandBuffers[currentFrame], 0, 1, &pipeline->viewport);
|
||||||
|
vkCmdSetScissor(commandBuffers[currentFrame], 0, 1, &pipeline->scissors);
|
||||||
|
lastPipeline = pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdDraw(
|
||||||
|
commandBuffers[currentFrame],
|
||||||
|
object.mesh.vertexCount,
|
||||||
|
static_cast<uint32_t>(object.transforms.size()),
|
||||||
|
0, // TODO: batching
|
||||||
|
0 // TODO: batching
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdEndRenderPass(commandBuffers[currentFrame]);
|
||||||
|
vkResult = vkEndCommandBuffer(commandBuffers[currentFrame]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to end command buffer. Code: %d", vkResult);
|
||||||
|
|
||||||
|
VkSemaphore waitSemaphores[] = {imageAvailable[currentFrame]};
|
||||||
|
VkSemaphore signalSemaphores[] = {renderFinished[currentFrame]};
|
||||||
|
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||||
|
//drawConfig.submitConfig.pWaitSemaphores = &imageAvailable[currentFrame];
|
||||||
|
drawConfig.submitConfig.pWaitSemaphores = waitSemaphores;
|
||||||
|
//drawConfig.submitConfig.pSignalSemaphores = &renderFinished[currentFrame];
|
||||||
|
drawConfig.submitConfig.pSignalSemaphores =signalSemaphores;
|
||||||
|
drawConfig.submitConfig.pCommandBuffers = &commandBuffers[currentFrame];
|
||||||
|
drawConfig.submitConfig.pWaitDstStageMask = waitStages;
|
||||||
|
|
||||||
|
vkResult = vkResetFences(device, 1, &inFlightFences[currentFrame]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to reset for fence. Code: %d", vkResult);
|
||||||
|
|
||||||
|
vkResult = vkQueueSubmit(queues.graphics, 1, &drawConfig.submitConfig, inFlightFences[currentFrame]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(ERROR, "Failed to submit render commands. Code: %d", vkResult);
|
||||||
|
|
||||||
|
//drawConfig.presentConfig.pWaitSemaphores = &renderFinished[currentFrame];
|
||||||
|
drawConfig.presentConfig.pWaitSemaphores = signalSemaphores;
|
||||||
|
VkSwapchainKHR swapChains[] = {swapchain};
|
||||||
|
//drawConfig.presentConfig.pSwapchains = &swapchain;
|
||||||
|
drawConfig.presentConfig.pSwapchains = swapChains;
|
||||||
|
drawConfig.presentConfig.pImageIndices = &imageIndex;
|
||||||
|
vkResult = vkQueuePresentKHR(queues.present, &drawConfig.presentConfig);
|
||||||
|
switch (vkResult)
|
||||||
|
{
|
||||||
|
case VK_SUCCESS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VK_ERROR_OUT_OF_DATE_KHR:
|
||||||
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
LOG(WARNING, "Swapchain needs to be recreated");
|
||||||
|
RecreateSwapchain();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG(ERROR, "Failed to present swapchain. Code: %d", vkResult);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::SetupSync()
|
||||||
|
{
|
||||||
|
VkResult vkResult;
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo semaphoreConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||||
|
.pNext = nullptr
|
||||||
|
};
|
||||||
|
VkFenceCreateInfo fenceConfig {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT
|
||||||
|
};
|
||||||
|
|
||||||
|
imageAvailable.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
renderFinished.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
imagesInFlight.resize(frameViews.size(), VK_NULL_HANDLE);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||||
|
{
|
||||||
|
vkResult = vkCreateSemaphore(device, &semaphoreConfig, nullptr, &imageAvailable[i]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create semaphore. Code: %d", vkResult);
|
||||||
|
vkResult = vkCreateSemaphore(device, &semaphoreConfig, nullptr, &renderFinished[i]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create semaphore. Code: %d", vkResult);
|
||||||
|
vkResult = vkCreateFence(device, &fenceConfig, nullptr, &inFlightFences[i]);
|
||||||
|
if (vkResult != VK_SUCCESS)
|
||||||
|
LOG(CRITICAL, "Failed to create fence. Code: %d", vkResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::Start()
|
void Renderer::Start()
|
||||||
{
|
{
|
||||||
if (isRunning)
|
if (isRunning)
|
||||||
@ -430,14 +742,22 @@ void Renderer::Threading()
|
|||||||
Logger::trace->Register_Thread(L"Renderer", 0);
|
Logger::trace->Register_Thread(L"Renderer", 0);
|
||||||
LOG(INFO, "Renderer thread started");
|
LOG(INFO, "Renderer thread started");
|
||||||
|
|
||||||
|
Init();
|
||||||
|
Loop();
|
||||||
|
Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::Init()
|
||||||
|
{
|
||||||
InitVulkan();
|
InitVulkan();
|
||||||
SetupDevice();
|
SetupDevice();
|
||||||
SetupSwapchain();
|
SetupSwapchain();
|
||||||
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, device, surfaceInfo.extent, surfaceInfo.format.format);
|
SetupRenderPasses();
|
||||||
|
|
||||||
Loop();
|
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, baseRenderPass, device, surfaceInfo.extent);
|
||||||
|
|
||||||
Release();
|
SetupBuffers();
|
||||||
|
SetupSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::Loop()
|
void Renderer::Loop()
|
||||||
@ -450,8 +770,9 @@ void Renderer::Loop()
|
|||||||
ProcessCommand(commandBuffer);
|
ProcessCommand(commandBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Draw();
|
||||||
}
|
}
|
||||||
|
vkDeviceWaitIdle(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::ProcessCommand(const RenderCommand &command)
|
void Renderer::ProcessCommand(const RenderCommand &command)
|
||||||
@ -465,6 +786,18 @@ void Renderer::ProcessCommand(const RenderCommand &command)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RenderCommand::REGISTER_OBJECTS:
|
||||||
|
{
|
||||||
|
LOG(INFO, "REGISTER_OBJECTS command");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < command.objectsSize; i++)
|
||||||
|
{
|
||||||
|
objects.insert(command.objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case RenderCommand::RESIZE_WINDOW:
|
case RenderCommand::RESIZE_WINDOW:
|
||||||
{
|
{
|
||||||
LOG(INFO, "RESIZE_WINDOW command");
|
LOG(INFO, "RESIZE_WINDOW command");
|
||||||
@ -494,16 +827,38 @@ void Renderer::Request(const RenderCommand::Type &type)
|
|||||||
commands.push(RenderCommand {.type = type});
|
commands.push(RenderCommand {.type = type});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::RegisterObjects(RenderObject* objects, size_t size)
|
||||||
|
{
|
||||||
|
RenderCommand command {
|
||||||
|
.type = RenderCommand::REGISTER_OBJECTS,
|
||||||
|
.objects = objects,
|
||||||
|
.objectsSize = size
|
||||||
|
};
|
||||||
|
Request(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::ResizeWindow(int width, int height)
|
||||||
|
{
|
||||||
|
screenWidth.store(width);
|
||||||
|
screenHeight.store(height);
|
||||||
|
Request(RenderCommand::RESIZE_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::Release()
|
void Renderer::Release()
|
||||||
{
|
{
|
||||||
Deallocate(pipelineFactory);
|
vkDeviceWaitIdle(device);
|
||||||
|
|
||||||
for (VkImageView &frameView : frameViews)
|
ReleaseSwapchain();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||||
{
|
{
|
||||||
vkDestroyImageView(device, frameView, nullptr);
|
vkDestroySemaphore(device, renderFinished[i], nullptr);
|
||||||
|
vkDestroySemaphore(device, imageAvailable[i], nullptr);
|
||||||
|
vkDestroyFence(device, inFlightFences[i], nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
vkDestroySwapchainKHR(device, swapchain, nullptr);
|
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||||
|
|
||||||
vkDestroyDevice(device, nullptr);
|
vkDestroyDevice(device, nullptr);
|
||||||
vkDestroySurfaceKHR(vulkan, surface, nullptr);
|
vkDestroySurfaceKHR(vulkan, surface, nullptr);
|
||||||
vkDestroyInstance(vulkan, nullptr);
|
vkDestroyInstance(vulkan, nullptr);
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
#include "compile_config.hpp"
|
#include "compile_config.hpp"
|
||||||
#include "Engine.hpp"
|
#include "Engine.hpp"
|
||||||
|
|
||||||
IP7_Trace* Logger::trace;
|
IP7_Trace* Logger::trace;
|
||||||
|
|
||||||
int SDL_main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// Setup P7 logger
|
// Setup P7 logger
|
||||||
IP7_Client *p7client = P7_Create_Client(P7_SINK);
|
IP7_Client *p7client = P7_Create_Client(P7_SINK);
|
||||||
@ -16,28 +16,15 @@ int SDL_main(int argc, char **argv)
|
|||||||
|
|
||||||
LOG(INFO, "Etredia Empires, v%d.%d.%d", MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
|
LOG(INFO, "Etredia Empires, v%d.%d.%d", MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
|
||||||
|
|
||||||
// Setup SDL
|
// Setup GLFW
|
||||||
bool quit = false;
|
glfwInit();
|
||||||
SDL_Event event;
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
|
||||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER);
|
GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr);
|
||||||
|
|
||||||
SDL_Window *window = SDL_CreateWindow(
|
|
||||||
"Etredia Empires",
|
|
||||||
SDL_WINDOWPOS_UNDEFINED,
|
|
||||||
SDL_WINDOWPOS_UNDEFINED,
|
|
||||||
800,
|
|
||||||
600,
|
|
||||||
SDL_WINDOW_VULKAN
|
|
||||||
);
|
|
||||||
SDL_StopTextInput();
|
|
||||||
|
|
||||||
if (!window)
|
if (!window)
|
||||||
{
|
LOG(CRITICAL, "GLFW Window failed");
|
||||||
quit = true;
|
else LOG(INFO, "GLFW Window created");
|
||||||
LOG(CRITICAL, "SDL Window fails: %hs", SDL_GetError());
|
|
||||||
}
|
|
||||||
else LOG(INFO, "SDL Window created");
|
|
||||||
|
|
||||||
// Setup assets
|
// Setup assets
|
||||||
AssetManager assetManager;
|
AssetManager assetManager;
|
||||||
@ -47,51 +34,35 @@ int SDL_main(int argc, char **argv)
|
|||||||
// Setup Renderer
|
// Setup Renderer
|
||||||
assetManager.LoadAsync(AssetInfo::MATERIAL, "assets/materials/helloworld.mat");
|
assetManager.LoadAsync(AssetInfo::MATERIAL, "assets/materials/helloworld.mat");
|
||||||
AssetHandle baseMaterialHandle = assetManager.Find("assets/materials/helloworld.mat");
|
AssetHandle baseMaterialHandle = assetManager.Find("assets/materials/helloworld.mat");
|
||||||
Renderer renderer(window, assetManager.Get<AssetMaterial>(baseMaterialHandle));
|
AssetMaterial baseMaterial = assetManager.Get<AssetMaterial>(baseMaterialHandle);
|
||||||
|
Renderer renderer(window, baseMaterial);
|
||||||
renderer.Start();
|
renderer.Start();
|
||||||
|
|
||||||
//Setup Engine
|
//Setup Engine
|
||||||
Engine::assetManager = &assetManager;
|
Engine::assetManager = &assetManager;
|
||||||
Engine::renderer = &renderer;
|
Engine::renderer = &renderer;
|
||||||
|
|
||||||
// Process SDL events
|
// Test render obj
|
||||||
while(!quit)
|
RenderObject triangle {
|
||||||
{
|
.transforms = {{.x = 0, .y = 0}},
|
||||||
SDL_PollEvent(&event);
|
.mesh = {.vertexCount = 3},
|
||||||
|
|
||||||
switch(event.type)
|
.material = baseMaterial,
|
||||||
{
|
};
|
||||||
case SDL_QUIT:
|
renderer.RegisterObjects(&triangle, 1);
|
||||||
{
|
|
||||||
SDL_RestoreWindow(window);
|
|
||||||
quit = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_WINDOWEVENT:
|
// Process GLFW events
|
||||||
|
while(!glfwWindowShouldClose(window))
|
||||||
{
|
{
|
||||||
switch (event.window.event)
|
glfwPollEvents();
|
||||||
{
|
|
||||||
case SDL_WINDOWEVENT_RESIZED:
|
|
||||||
{
|
|
||||||
renderer.screenWidth.store(event.window.data1);
|
|
||||||
renderer.screenHeight.store(event.window.data2);
|
|
||||||
renderer.Request(RenderCommand::RESIZE_WINDOW);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free memory
|
// Free memory
|
||||||
LOG(INFO, "Exit...");
|
LOG(INFO, "Exit...");
|
||||||
renderer.Shutdown(); while(renderer.isRunning.load());
|
renderer.Shutdown(); while(renderer.isRunning.load());
|
||||||
assetManager.Shutdown(); while(assetManager.isRunning.load());
|
assetManager.Shutdown(); while(assetManager.isRunning.load());
|
||||||
SDL_DestroyWindow(window);
|
glfwDestroyWindow(window);
|
||||||
SDL_Quit();
|
glfwTerminate();
|
||||||
Logger::trace->Unregister_Thread(0);
|
Logger::trace->Unregister_Thread(0);
|
||||||
Logger::trace->Release();
|
Logger::trace->Release();
|
||||||
p7client->Release();
|
p7client->Release();
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"vertexShader": "assets/shaders/helloworld.vert.spv",
|
|
||||||
"fragmentShader": "assets/shaders/helloworld.frag.spv"
|
|
||||||
}
|
|
||||||
@ -8,7 +8,13 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AssetMaterial, vertexShader, fragmentShader)
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
|
||||||
|
AssetMaterial,
|
||||||
|
vertexShaderAssetID,
|
||||||
|
fragmentShaderAssetID,
|
||||||
|
renderType,
|
||||||
|
renderStage
|
||||||
|
)
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@ -32,15 +38,17 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
json data = json::parse(fin);
|
json data = json::parse(fin);
|
||||||
|
|
||||||
for (const char *link : {
|
for (auto &[key, value] : data.items())
|
||||||
"vertexShader",
|
|
||||||
"fragmentShader"
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
std::filesystem::path linkPath = data[link];
|
if (
|
||||||
|
!key.ends_with("AssetID") &&
|
||||||
|
!key.ends_with("Type")
|
||||||
|
) continue;
|
||||||
|
|
||||||
|
std::filesystem::path linkPath = data[key];
|
||||||
auto linkPath8 = linkPath.generic_u8string();
|
auto linkPath8 = linkPath.generic_u8string();
|
||||||
uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
|
uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
|
||||||
data[link] = assetId;
|
data[key] = assetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetMaterial material = data.get<AssetMaterial>();
|
AssetMaterial material = data.get<AssetMaterial>();
|
||||||
6
sdk/materials/helloworld.mat.json
Normal file
6
sdk/materials/helloworld.mat.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"vertexShaderAssetID": "assets/shaders/helloworld.vert.spv",
|
||||||
|
"fragmentShaderAssetID": "assets/shaders/helloworld.frag.spv",
|
||||||
|
"renderType": "base",
|
||||||
|
"renderStage": 0
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#version 330
|
#version 450
|
||||||
#extension GL_ARB_separate_shader_objects : enable
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
layout(location = 0) in vec3 fragColor;
|
layout(location = 0) in vec3 fragColor;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#version 330
|
#version 450
|
||||||
#extension GL_ARB_separate_shader_objects : enable
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
layout(location = 0) out vec3 fragColor;
|
layout(location = 0) out vec3 fragColor;
|
||||||
@ -5,7 +5,7 @@
|
|||||||
"nlohmann-json",
|
"nlohmann-json",
|
||||||
"entt",
|
"entt",
|
||||||
"vulkan",
|
"vulkan",
|
||||||
{"name": "sdl2", "features": ["vulkan"]},
|
"glfw3",
|
||||||
"rmlui",
|
"rmlui",
|
||||||
"gtest"
|
"gtest"
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user