From 1d65039e2fd37b810842f5be8cebf266a0179913 Mon Sep 17 00:00:00 2001 From: legatt Date: Sat, 27 Sep 2025 12:30:00 +0300 Subject: [PATCH] Swapchain presentation and GLFW - Render objects list - SDL2 replaced with GLFW - Swapchain synchronization, presentation and recreation on resize --- CMakeLists.txt | 2 +- assets/materials/helloworld.mat | Bin 8 -> 16 bytes assets/shaders/helloworld.frag.spv | Bin 608 -> 608 bytes assets/shaders/helloworld.vert.spv | Bin 1488 -> 1540 bytes frontend/CMakeLists.txt | 9 +- frontend/include/AssetData.hpp | 6 +- frontend/include/AssetManager.hpp | 5 +- frontend/include/PipelineFactory.hpp | 18 +- frontend/include/Renderer.hpp | 114 ++++- frontend/src/PipelineFactory.cpp | 83 ++-- frontend/src/Renderer.cpp | 413 ++++++++++++++++-- frontend/src/main.cpp | 77 +--- materials/helloworld.mat.json | 4 - {materials => sdk/materials}/CMakeLists.txt | 0 {materials => sdk/materials}/_schema.json | 0 .../materials}/compile_materials.cpp | 22 +- sdk/materials/helloworld.mat.json | 6 + .../shaders}/helloworld/helloworld.frag | 2 +- .../shaders}/helloworld/helloworld.vert | 2 +- vcpkg.json | 2 +- 20 files changed, 579 insertions(+), 186 deletions(-) delete mode 100644 materials/helloworld.mat.json rename {materials => sdk/materials}/CMakeLists.txt (100%) rename {materials => sdk/materials}/_schema.json (100%) rename {materials => sdk/materials}/compile_materials.cpp (74%) create mode 100644 sdk/materials/helloworld.mat.json rename {shaders => sdk/shaders}/helloworld/helloworld.frag (93%) rename {shaders => sdk/shaders}/helloworld/helloworld.vert (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5906806..35384d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project (EtrediaEmpires) -add_subdirectory(materials) +add_subdirectory(sdk/materials) add_subdirectory(utils) add_subdirectory (p7lib) add_subdirectory (ECSAPI) diff --git a/assets/materials/helloworld.mat b/assets/materials/helloworld.mat index 28bae65343cbc2815888eb6e161d16f0b0f6a19f..b258151de8ba8aac3251d82fa77f7c861d3662c3 100644 GIT binary patch literal 16 Wcmd;mw6Q{8MKb(eijouq5C8x$rvzF6 literal 8 Pcmd;mw6Q{8MKT-!4$T7G diff --git a/assets/shaders/helloworld.frag.spv b/assets/shaders/helloworld.frag.spv index 81575e52e858ffe36d41a6de7453cde597a37d5b..a6765af36224cbef703715b364693cf4475d0403 100644 GIT binary patch delta 12 TcmaFB@_=PR0pp>Kh3^>wB=QBG delta 12 TcmaFB@_=PR0i)N(!uN~-BANvz diff --git a/assets/shaders/helloworld.vert.spv b/assets/shaders/helloworld.vert.spv index 3197881af2de907311cccce1695b0ff29e18f160..27cdb4fe118a4c87219604a1c0dbb71a8b1de338 100644 GIT binary patch delta 80 zcmcb>-NG{=gYnSDOm0SDHU@SE1qKENW*|<_iFYo|$(fwZ*eU42zzP@V2FkH){>aG5 U#Los6lm!Z~0kQmMR+iI@0C=0L6?5X#fBK diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index 054a023..7b51280 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -23,7 +23,7 @@ list(TRANSFORM SOURCES PREPEND "src/") find_package(Boost REQUIRED COMPONENTS lockfree) find_package(xxHash CONFIG REQUIRED) -find_package(SDL2 CONFIG REQUIRED) +find_package(glfw3 CONFIG REQUIRED) find_package(Vulkan 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 xxHash::xxhash) target_link_libraries(${PROJECT_NAME} PRIVATE p7) -target_link_libraries( - ${PROJECT_NAME} - PRIVATE - $ - $,SDL2::SDL2,SDL2::SDL2-static> -) +target_link_libraries(${PROJECT_NAME} PRIVATE glfw) target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan) target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi) diff --git a/frontend/include/AssetData.hpp b/frontend/include/AssetData.hpp index e353a4c..bf4f427 100644 --- a/frontend/include/AssetData.hpp +++ b/frontend/include/AssetData.hpp @@ -25,6 +25,8 @@ struct AssetShader : AssetData struct AssetMaterial : AssetData { - uint32_t vertexShader; - uint32_t fragmentShader; + uint32_t vertexShaderAssetID; + uint32_t fragmentShaderAssetID; + uint32_t renderType; + uint32_t renderStage; }; \ No newline at end of file diff --git a/frontend/include/AssetManager.hpp b/frontend/include/AssetManager.hpp index 95d0b5a..a6450b0 100644 --- a/frontend/include/AssetManager.hpp +++ b/frontend/include/AssetManager.hpp @@ -85,9 +85,6 @@ public: void Start(); void Shutdown(); - void Request(const AssetStreamCommand &command); - void Request(const AssetStreamCommand::Type &type); - void LoadShaders(); void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path); @@ -115,6 +112,8 @@ private: std::vector materialsStorage; void Threading(); + void Request(const AssetStreamCommand &command); + void Request(const AssetStreamCommand::Type &type); void Loop(); void ProcessCommand(AssetStreamCommand &command); void Release(); diff --git a/frontend/include/PipelineFactory.hpp b/frontend/include/PipelineFactory.hpp index 395257f..82f65ab 100644 --- a/frontend/include/PipelineFactory.hpp +++ b/frontend/include/PipelineFactory.hpp @@ -2,6 +2,7 @@ #include "AssetData.hpp" #include #include +#include #define PIPELINE_CREATE_NEW 0 #define PIPELINE_CREATE_DERIVED (1 << 0) @@ -25,11 +26,6 @@ struct PipelineConfig VkRect2D defaultScissors; std::vector dynamicStates; - std::vector colorAttachmentConfigs; - std::vector colorAttachmentRefs; - std::vector subpassConfigs; - VkRenderPassCreateInfo renderPassConfig; - VkGraphicsPipelineCreateInfo pipelineConfig; PipelineConfig Copy() const; @@ -39,26 +35,26 @@ struct Pipeline { VkPipeline instance; VkPipelineLayout layout; - VkRenderPass renderPass; + VkViewport viewport; + VkRect2D scissors; }; class PipelineFactory { public: - PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat); + PipelineFactory(const AssetMaterial &baseMaterial, const VkRenderPass &baseRenderPass, VkDevice &device, VkExtent2D &surfaceExtent); ~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: PipelineConfig basePipelineConfig; Pipeline basePipeline; - std::vector pipelines; + std::unordered_map pipelines; VkDevice &device; VkExtent2D &surfaceExtent; - VkFormat &surfaceFormat; - void Create(const AssetMaterial &material, uint32_t flags); }; \ No newline at end of file diff --git a/frontend/include/Renderer.hpp b/frontend/include/Renderer.hpp index d73f912..8b963c6 100644 --- a/frontend/include/Renderer.hpp +++ b/frontend/include/Renderer.hpp @@ -2,11 +2,12 @@ #include #include #include +#include +#include #include -#include -#include -#include +#define GLFW_INCLUDE_VULKAN +#include #include #include "PipelineFactory.hpp" @@ -24,16 +25,46 @@ #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 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 { enum Type { NONE, - //STORE screenHeight and screenWidth BEFORE request - RESIZE_WINDOW, + REGISTER_OBJECTS, + RESIZE_WINDOW, //STORE screenHeight and screenWidth BEFORE request SHUTDOWN } type = NONE; + + RenderObject *objects; + size_t objectsSize; }; struct QueueFamiliesBundle @@ -48,6 +79,44 @@ struct QueuesBundle 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 { VkSurfaceCapabilitiesKHR capabilities; @@ -65,44 +134,67 @@ public: std::atomic isRunning; std::atomic screenWidth, screenHeight; - Renderer(SDL_Window *window, const AssetMaterial& baseMaterial); + Renderer(GLFWwindow *window, const AssetMaterial& baseMaterial); ~Renderer(); void Start(); void Shutdown(); - void Request(const RenderCommand &command); - void Request(const RenderCommand::Type &type); + void RegisterObjects(RenderObject* objects, size_t size); + void ResizeWindow(int width, int height); private: std::stop_token *stoken; std::jthread renderThread; boost::lockfree::spsc_queue> commands; - SDL_Window *window; + GLFWwindow *window; VkInstance vulkan; VkSurfaceKHR surface; VkPhysicalDevice gpu; VkDevice device; VkSwapchainKHR swapchain; + VkRenderPass baseRenderPass; + VkCommandPool commandPool = nullptr; + std::vector commandBuffers; std::vector frames; std::vector frameViews; + std::vector baseFrameBuffers; + std::vector imageAvailable; + std::vector renderFinished; + std::vector inFlightFences; + std::vector imagesInFlight; SurfaceInfo surfaceInfo; QueueFamiliesBundle queueFamilies; QueuesBundle queues; - const float defaultQueuePriority = 1.0f; - const AssetMaterial &baseMaterial; + DrawConfig drawConfig; 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 objects; Rml::Context *uiContext; void InitVulkan(); void SetupDevice(); void SetupSwapchain(); + void RecreateSwapchain(); + void ReleaseSwapchain(); + void SetupRenderPasses(); + void SetupBuffers(); + void SetupSync(); + + void Draw(); void Threading(); + void Request(const RenderCommand &command); + void Request(const RenderCommand::Type &type); + void Init(); void Loop(); void ProcessCommand(const RenderCommand &command); void Release(); diff --git a/frontend/src/PipelineFactory.cpp b/frontend/src/PipelineFactory.cpp index 8399fba..77862f1 100644 --- a/frontend/src/PipelineFactory.cpp +++ b/frontend/src/PipelineFactory.cpp @@ -10,13 +10,6 @@ PipelineConfig PipelineConfig::Copy() const copy.viewportStateConfig.pScissors = ©.defaultScissors; copy.dynamicStateConfig.pDynamicStates = copy.dynamicStates.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.pInputAssemblyState = ©.inputAssemblyConfig; copy.pipelineConfig.pViewportState = ©.viewportStateConfig; @@ -29,10 +22,16 @@ PipelineConfig PipelineConfig::Copy() const return copy; } -PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat) : - device(device), surfaceExtent(surfaceExtent), surfaceFormat(surfaceFormat) +PipelineFactory::PipelineFactory( + 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_SCISSOR); basePipelineConfig.colorBlendAttachmentConfigs.push_back({ .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 }); - 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(basePipelineConfig.colorAttachmentRefs.size()), - .pColorAttachments = basePipelineConfig.colorAttachmentRefs.data() - }); - basePipelineConfig.vertexInputConfig = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .pNext = nullptr, @@ -152,15 +130,6 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de .pAttachments = basePipelineConfig.colorBlendAttachmentConfigs.data(), }; - basePipelineConfig.renderPassConfig = { - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, - .pNext = nullptr, - .attachmentCount = static_cast(basePipelineConfig.colorAttachmentConfigs.size()), - .pAttachments = basePipelineConfig.colorAttachmentConfigs.data(), - .subpassCount = static_cast(basePipelineConfig.subpassConfigs.size()), - .pSubpasses = basePipelineConfig.subpassConfigs.data() - }; - basePipelineConfig.pipelineConfig = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -172,17 +141,23 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de .pDepthStencilState = nullptr, .pColorBlendState = &basePipelineConfig.colorBlendConfig, .pDynamicState = &basePipelineConfig.dynamicStateConfig, - .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1 }; LOG(INFO, "Creating base material pipeline..."); - Create(baseMaterial, PIPELINE_CREATE_NEW); + Create(baseMaterial, baseRenderPass, PIPELINE_CREATE_NEW); 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 *result = &basePipeline; @@ -208,11 +183,6 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags) if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to create pipeline 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 AssetHandle shaderHandle; @@ -224,7 +194,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags) // Vertex VkShaderModule vertexModule; - shaderHandle = Engine::assetManager->Find(material.vertexShader); + shaderHandle = Engine::assetManager->Find(material.vertexShaderAssetID); shader = &Engine::assetManager->Get(shaderHandle); shaderModuleCreateConfig = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, @@ -246,7 +216,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags) // Fragment VkShaderModule fragmentModule; - shaderHandle = Engine::assetManager->Find(material.fragmentShader); + shaderHandle = Engine::assetManager->Find(material.fragmentShaderAssetID); shader = &Engine::assetManager->Get(shaderHandle); shaderModuleCreateConfig = { .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(stagesConfigs.size()); config->pipelineConfig.pStages = stagesConfigs.data(); + config->pipelineConfig.renderPass = renderPass; + config->pipelineConfig.subpass = material.renderStage; vkResult = vkCreateGraphicsPipelines( device, @@ -281,7 +253,10 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags) LOG(CRITICAL, "Failed to create pipeline"); 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, fragmentModule, nullptr); @@ -291,11 +266,9 @@ PipelineFactory::~PipelineFactory() { vkDestroyPipeline(device, basePipeline.instance, nullptr); vkDestroyPipelineLayout(device, basePipeline.layout, nullptr); - vkDestroyRenderPass(device, basePipeline.renderPass, nullptr); - for (Pipeline &pipeline : pipelines) + for (auto &item : pipelines) { - vkDestroyPipeline(device, pipeline.instance, nullptr); - vkDestroyPipelineLayout(device, pipeline.layout, nullptr); - vkDestroyRenderPass(device, pipeline.renderPass, nullptr); + vkDestroyPipeline(device, item.second.instance, nullptr); + vkDestroyPipelineLayout(device, item.second.layout, nullptr); } } \ No newline at end of file diff --git a/frontend/src/Renderer.cpp b/frontend/src/Renderer.cpp index b3d239c..6cab727 100644 --- a/frontend/src/Renderer.cpp +++ b/frontend/src/Renderer.cpp @@ -9,11 +9,20 @@ #include #include -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), baseMaterial(baseMaterial) { this->window = window; + int width = -1, height = -1; + glfwGetFramebufferSize(window, &width, &height); + screenWidth.store(width); + screenHeight.store(height); } Renderer::~Renderer() @@ -31,17 +40,10 @@ void Renderer::InitVulkan() }; // Get Vulkan extensions list - uint32_t extensionsSDLCount = 0; - if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, nullptr) ) - LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError()); - - std::vector 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()); + uint32_t extensionsGLFWCount = 0; + const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&extensionsGLFWCount); + for (size_t i = 0; i < extensionsGLFWCount; i++) + vulkanExtensions.push_back(glfwExtensions[i]); for (const char *extension : vulkanExtensions) { @@ -80,9 +82,11 @@ void Renderer::InitVulkan() else LOG(INFO, "Vulkan is instantiated"); // Create surface - if ( !SDL_Vulkan_CreateSurface(window, vulkan, &surface) ) - LOG(CRITICAL, "Failed to create SDL Vulkan surface. %hs", SDL_GetError()); - else LOG(INFO, "SDL Vulkan surface created"); + + vkResult = glfwCreateWindowSurface(vulkan, window, nullptr, &surface); + 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() @@ -230,10 +234,11 @@ void Renderer::SetupDevice() if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) priority += GPU_DISCRETE_PRIORITY; + surfaceInfoBuffer.format.format = VK_FORMAT_B8G8R8A8_UNORM; for (const VkSurfaceFormatKHR &format : surfaceInfoBuffer.availableFormats) { 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; break; @@ -279,7 +284,7 @@ void Renderer::SetupDevice() .pNext = nullptr, .queueFamilyIndex = queueFamily, .queueCount = 1, - .pQueuePriorities = &defaultQueuePriority + .pQueuePriorities = &DEFAULT_QUEUE_PRIORITY }; queuesCreateConfigs.push_back(queueCreateConfig); @@ -321,7 +326,7 @@ void Renderer::SetupSwapchain() LOG(CRITICAL, "Failed to get surface capabilities. Code: %d", vkResult); int width = 0, height = 0; - SDL_Vulkan_GetDrawableSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); // Clamp extent surfaceInfo.extent.width = (std::max)( @@ -410,6 +415,313 @@ void Renderer::SetupSwapchain() LOG(INFO, "Frame views are ready."); } +void Renderer::RecreateSwapchain() +{ + vkDeviceWaitIdle(device); + ReleaseSwapchain(); + + SetupSwapchain(); + SetupRenderPasses(); + pipelineFactory = Allocate(baseMaterial, baseRenderPass, device, surfaceInfo.extent); + SetupBuffers(); +} + +void Renderer::ReleaseSwapchain() +{ + for (VkFramebuffer &frameBuffer : baseFrameBuffers) + { + vkDestroyFramebuffer(device, frameBuffer, nullptr); + } + vkFreeCommandBuffers( + device, + commandPool, + static_cast(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(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(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() { if (isRunning) @@ -429,15 +741,23 @@ void Renderer::Threading() { Logger::trace->Register_Thread(L"Renderer", 0); LOG(INFO, "Renderer thread started"); - + + Init(); + Loop(); + Release(); +} + +void Renderer::Init() +{ InitVulkan(); SetupDevice(); SetupSwapchain(); - pipelineFactory = Allocate(baseMaterial, device, surfaceInfo.extent, surfaceInfo.format.format); - - Loop(); - - Release(); + SetupRenderPasses(); + + pipelineFactory = Allocate(baseMaterial, baseRenderPass, device, surfaceInfo.extent); + + SetupBuffers(); + SetupSync(); } void Renderer::Loop() @@ -450,8 +770,9 @@ void Renderer::Loop() ProcessCommand(commandBuffer); } - + Draw(); } + vkDeviceWaitIdle(device); } void Renderer::ProcessCommand(const RenderCommand &command) @@ -464,6 +785,18 @@ void Renderer::ProcessCommand(const RenderCommand &command) Shutdown(); 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: { @@ -494,16 +827,38 @@ void Renderer::Request(const 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() { - Deallocate(pipelineFactory); + vkDeviceWaitIdle(device); + + ReleaseSwapchain(); - for (VkImageView &frameView : frameViews) + 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); vkDestroySurfaceKHR(vulkan, surface, nullptr); vkDestroyInstance(vulkan, nullptr); diff --git a/frontend/src/main.cpp b/frontend/src/main.cpp index 534f787..c001fd4 100644 --- a/frontend/src/main.cpp +++ b/frontend/src/main.cpp @@ -1,12 +1,12 @@ #include "Logger.h" -#include +#include #include "compile_config.hpp" #include "Engine.hpp" IP7_Trace* Logger::trace; -int SDL_main(int argc, char **argv) +int main(int argc, char **argv) { // Setup P7 logger 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); - // Setup SDL - bool quit = false; - SDL_Event event; + // Setup GLFW + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER); - - SDL_Window *window = SDL_CreateWindow( - "Etredia Empires", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - 800, - 600, - SDL_WINDOW_VULKAN - ); - SDL_StopTextInput(); + GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr); if (!window) - { - quit = true; - LOG(CRITICAL, "SDL Window fails: %hs", SDL_GetError()); - } - else LOG(INFO, "SDL Window created"); + LOG(CRITICAL, "GLFW Window failed"); + else LOG(INFO, "GLFW Window created"); // Setup assets AssetManager assetManager; @@ -47,51 +34,35 @@ int SDL_main(int argc, char **argv) // Setup Renderer assetManager.LoadAsync(AssetInfo::MATERIAL, "assets/materials/helloworld.mat"); AssetHandle baseMaterialHandle = assetManager.Find("assets/materials/helloworld.mat"); - Renderer renderer(window, assetManager.Get(baseMaterialHandle)); + AssetMaterial baseMaterial = assetManager.Get(baseMaterialHandle); + Renderer renderer(window, baseMaterial); renderer.Start(); //Setup Engine Engine::assetManager = &assetManager; Engine::renderer = &renderer; - // Process SDL events - while(!quit) + // Test render obj + RenderObject triangle { + .transforms = {{.x = 0, .y = 0}}, + .mesh = {.vertexCount = 3}, + + .material = baseMaterial, + }; + renderer.RegisterObjects(&triangle, 1); + + // Process GLFW events + while(!glfwWindowShouldClose(window)) { - SDL_PollEvent(&event); - - switch(event.type) - { - case SDL_QUIT: - { - SDL_RestoreWindow(window); - quit = true; - break; - } - - case SDL_WINDOWEVENT: - { - switch (event.window.event) - { - case SDL_WINDOWEVENT_RESIZED: - { - renderer.screenWidth.store(event.window.data1); - renderer.screenHeight.store(event.window.data2); - renderer.Request(RenderCommand::RESIZE_WINDOW); - break; - } - } - - break; - } - } + glfwPollEvents(); } // Free memory LOG(INFO, "Exit..."); renderer.Shutdown(); while(renderer.isRunning.load()); assetManager.Shutdown(); while(assetManager.isRunning.load()); - SDL_DestroyWindow(window); - SDL_Quit(); + glfwDestroyWindow(window); + glfwTerminate(); Logger::trace->Unregister_Thread(0); Logger::trace->Release(); p7client->Release(); diff --git a/materials/helloworld.mat.json b/materials/helloworld.mat.json deleted file mode 100644 index 451a887..0000000 --- a/materials/helloworld.mat.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "vertexShader": "assets/shaders/helloworld.vert.spv", - "fragmentShader": "assets/shaders/helloworld.frag.spv" -} \ No newline at end of file diff --git a/materials/CMakeLists.txt b/sdk/materials/CMakeLists.txt similarity index 100% rename from materials/CMakeLists.txt rename to sdk/materials/CMakeLists.txt diff --git a/materials/_schema.json b/sdk/materials/_schema.json similarity index 100% rename from materials/_schema.json rename to sdk/materials/_schema.json diff --git a/materials/compile_materials.cpp b/sdk/materials/compile_materials.cpp similarity index 74% rename from materials/compile_materials.cpp rename to sdk/materials/compile_materials.cpp index b88eb0a..cea2936 100644 --- a/materials/compile_materials.cpp +++ b/sdk/materials/compile_materials.cpp @@ -8,7 +8,13 @@ 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) { @@ -32,15 +38,17 @@ int main(int argc, char **argv) json data = json::parse(fin); - for (const char *link : { - "vertexShader", - "fragmentShader" - }) + for (auto &[key, value] : data.items()) { - 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(); uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0); - data[link] = assetId; + data[key] = assetId; } AssetMaterial material = data.get(); diff --git a/sdk/materials/helloworld.mat.json b/sdk/materials/helloworld.mat.json new file mode 100644 index 0000000..be905ce --- /dev/null +++ b/sdk/materials/helloworld.mat.json @@ -0,0 +1,6 @@ +{ + "vertexShaderAssetID": "assets/shaders/helloworld.vert.spv", + "fragmentShaderAssetID": "assets/shaders/helloworld.frag.spv", + "renderType": "base", + "renderStage": 0 +} \ No newline at end of file diff --git a/shaders/helloworld/helloworld.frag b/sdk/shaders/helloworld/helloworld.frag similarity index 93% rename from shaders/helloworld/helloworld.frag rename to sdk/shaders/helloworld/helloworld.frag index ff8da04..1730b4f 100644 --- a/shaders/helloworld/helloworld.frag +++ b/sdk/shaders/helloworld/helloworld.frag @@ -1,4 +1,4 @@ -#version 330 +#version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; diff --git a/shaders/helloworld/helloworld.vert b/sdk/shaders/helloworld/helloworld.vert similarity index 96% rename from shaders/helloworld/helloworld.vert rename to sdk/shaders/helloworld/helloworld.vert index b2328fc..0439d32 100644 --- a/shaders/helloworld/helloworld.vert +++ b/sdk/shaders/helloworld/helloworld.vert @@ -1,4 +1,4 @@ -#version 330 +#version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) out vec3 fragColor; diff --git a/vcpkg.json b/vcpkg.json index 169dbb8..34dab61 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -5,7 +5,7 @@ "nlohmann-json", "entt", "vulkan", - {"name": "sdl2", "features": ["vulkan"]}, + "glfw3", "rmlui", "gtest" ]