From 3bafdee45d9c9ced8ad0de545f965359344737d3 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Wed, 11 Feb 2026 01:57:34 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): Implement DV::TmpImpl and use. --- CMakeLists.txt | 1 + MobileGL/Includes.h | 1 + .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 13 +- .../DirectVulkan/Managers/ProgramManager.cpp | 201 +- .../DirectVulkan/Managers/ProgramManager.h | 41 +- .../DirectVulkan/Renderer/FrameContext.cpp | 35 +- .../DirectVulkan/Renderer/FrameContext.h | 44 +- .../DirectVulkan/Renderer/PipelineManager.cpp | 134 +- .../DirectVulkan/Renderer/PipelineManager.h | 35 - .../Renderer/SwapchainManager.cpp | 205 +- .../DirectVulkan/Renderer/SwapchainManager.h | 53 +- .../DirectVulkan/Renderer/VkCommon.h | 22 + .../DirectVulkan/Renderer/VulkanContext.cpp | 203 +- .../DirectVulkan/Renderer/VulkanContext.h | 53 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 424 +- .../DirectVulkan/Renderer/VulkanRenderer.h | 85 +- MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp | 3448 +++++++++++++++++ MobileGL/MG_Backend/DirectVulkan/TmpImpl.h | 53 + .../EGLImpl/EGLForVulkan/EGLForVulkan.cpp | 5 + .../GLState/ProgramState/ProgramObject.h | 3 +- 20 files changed, 4267 insertions(+), 792 deletions(-) delete mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/VkCommon.h create mode 100644 MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/TmpImpl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e7de16e3..2a59dd5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,6 +208,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectGLES/Managers.cpp MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp + MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp diff --git a/MobileGL/Includes.h b/MobileGL/Includes.h index 929440e1..bce8a13b 100644 --- a/MobileGL/Includes.h +++ b/MobileGL/Includes.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 3de2fe4c..235729c0 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -7,13 +7,24 @@ // End of Source File Header #include "DirectVulkan.h" +#include "TmpImpl.h" +constexpr bool USE_TMP_IMPL = true; namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr pVulkanRenderer = nullptr; - void Clear(GLbitfield mask) {} + void Clear(GLbitfield mask) { + if (USE_TMP_IMPL) { + MobileGL::Backend::DirectVulkan::TmpImpl::Clear(mask); + return; + } + } void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { + if (USE_TMP_IMPL) { + MobileGL::Backend::DirectVulkan::TmpImpl::DrawElements(mode, count, type, indices); + return; + } pVulkanRenderer->RenderFrame(); } } // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp index c73a1b30..ee690109 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp @@ -7,128 +7,95 @@ // End of Source File Header #include "ProgramManager.h" -#include "MG_Backend/DirectVulkan/Renderer/VulkanContext.h" - -namespace MobileGL::MG_Backend::DirectVulkan { - ProgramManager::ProgramManager(VulkanContext &ctx): m_ctx(ctx) {} +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { ProgramManager::~ProgramManager() { - Cleanup(); - } - - void ProgramManager::Cleanup() { - auto device = m_ctx.GetDevice(); - if (device != VK_NULL_HANDLE) { - for (auto& [_, stages] : m_shaderStageCreateInfo) { - for (auto& stageInfo : stages) { - if (stageInfo.module != VK_NULL_HANDLE) { - vkDestroyShaderModule(device, stageInfo.module, nullptr); - stageInfo.module = VK_NULL_HANDLE; - } - } - } + for (auto& [_, stages] : m_cache) { + DestroyStages(stages); } - m_shaderStageCreateInfo.clear(); - XXH64_freeState(m_hashState); + m_cache.clear(); } - Vector& ProgramManager::CreatePipelineShaderStages(ProgramObject* programObject) { - auto hash = GetHash(programObject); - MOBILEGL_ASSERT(m_shaderStageCreateInfo.find(hash) == m_shaderStageCreateInfo.end(), - "A program with the same hash has already been created"); - Vector info; - auto& shaders = programObject->GetAttachedShaders(); - auto& spirvBinaries = programObject->GetGeneratedSpirv(); - MOBILEGL_ASSERT(shaders.size() == spirvBinaries.size(), - "Shader and SPIR-V binary count mismatch"); - - info.reserve(shaders.size()); - - auto device = m_ctx.GetDevice(); - MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "Vulkan device is null"); - - for (SizeT i = 0; i < shaders.size(); ++i) { - auto shaderStage = shaders[i]->GetShaderStage(); - VkShaderStageFlagBits vkStage = VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM; - switch (shaderStage) { - case ShaderStage::Vertex: - vkStage = VK_SHADER_STAGE_VERTEX_BIT; - break; - case ShaderStage::TessControl: - vkStage = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; - break; - case ShaderStage::TessEval: - vkStage = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; - break; - case ShaderStage::Geometry: - vkStage = VK_SHADER_STAGE_GEOMETRY_BIT; - break; - case ShaderStage::Fragment: - vkStage = VK_SHADER_STAGE_FRAGMENT_BIT; - break; - case ShaderStage::Compute: - vkStage = VK_SHADER_STAGE_COMPUTE_BIT; - break; - default: - MOBILEGL_ASSERT(false, "Unsupported shader stage"); - break; - } - - auto& spirv = spirvBinaries[i]; - MOBILEGL_ASSERT(!spirv.empty(), "SPIR-V binary is empty"); - VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - smci.codeSize = spirv.size() * sizeof(Uint); - smci.pCode = reinterpret_cast(spirv.data()); - - VkShaderModule shaderModule = VK_NULL_HANDLE; - VK_VERIFY(vkCreateShaderModule(device, &smci, nullptr, &shaderModule), "vkCreateShaderModule"); - MOBILEGL_ASSERT(shaderModule != VK_NULL_HANDLE, "Failed to create shader module"); - - VkPipelineShaderStageCreateInfo stageInfo{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; - stageInfo.stage = vkStage; - stageInfo.module = shaderModule; - stageInfo.pName = "main"; - info.emplace_back(stageInfo); + ProgramManager::HashType ProgramManager::ComputeSpvHash(MG_State::GLState::ProgramObject* program) const { + if (!program) return 0; + XXH64_state_t* state = XXH64_createState(); + XXH64_reset(state, 0xC0FFEEu); + auto& spirvs = program->GetGeneratedSpirv(); + for (const auto& spv : spirvs) { + if (spv.empty()) continue; + XXH64_update(state, spv.data(), spv.size() * sizeof(Uint)); } - - auto [it, inserted] = m_shaderStageCreateInfo.emplace(hash, Move(info)); - MOBILEGL_ASSERT(inserted, "Failed to cache shader stage create info"); - return it->second; - } - - Vector* ProgramManager::GetPipelineShaderStages(HashType hash) { - auto it = m_shaderStageCreateInfo.find(hash); - if (it == m_shaderStageCreateInfo.end()) return nullptr; - return &it->second; - } - - Vector* ProgramManager::GetPipelineShaderStages(ProgramObject* programObject) { - return GetPipelineShaderStages(GetHash(programObject)); - } - - ProgramManager::HashType ProgramManager::GetHash(ProgramObject *programObject) { - MOBILEGL_ASSERT(programObject && programObject->GetLinkStatus(), "program object is null or not successfully linked!"); - MOBILEGL_ASSERT(m_hashState != nullptr, "Hash state should already be initialized"); - - XXH64_hash_t const seed = MG_Config::CacheVersion; - auto errc = XXH64_reset(m_hashState, seed); - MOBILEGL_ASSERT(errc == XXH_OK, "Hash state reset failed"); - - auto& shaders = programObject->GetAttachedShaders(); - auto& spirvBinaries = programObject->GetGeneratedSpirv(); - auto count = shaders.size(); - - for (auto i = 0; i < count; i++) { - auto& shader = shaders[i]; - auto stage = shader->GetShaderStage(); - errc = XXH64_update(m_hashState, &stage, sizeof(stage)); - MOBILEGL_ASSERT(errc == XXH_OK, "Hash state update failed"); - errc = XXH64_update(m_hashState, spirvBinaries[i].data(), spirvBinaries[i].size() * sizeof(unsigned)); - MOBILEGL_ASSERT(errc == XXH_OK, "Hash state update failed"); - } - - HashType hash = XXH64_digest(m_hashState); - + HashType hash = XXH64_digest(state); + XXH64_freeState(state); return hash; } -} // namespace MobileGL::MG_Backend::DirectVulkan + + VkShaderStageFlagBits ProgramManager::ToVkStage(ShaderStage stage) const { + switch (stage) { + case ShaderStage::Vertex: + return VK_SHADER_STAGE_VERTEX_BIT; + case ShaderStage::Fragment: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case ShaderStage::Geometry: + return VK_SHADER_STAGE_GEOMETRY_BIT; + case ShaderStage::TessControl: + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case ShaderStage::TessEval: + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + case ShaderStage::Compute: + return VK_SHADER_STAGE_COMPUTE_BIT; + default: + return VK_SHADER_STAGE_ALL_GRAPHICS; + } + } + + void ProgramManager::DestroyStages(ProgramStages& stages) { + for (auto module : stages.modules) { + if (module != VK_NULL_HANDLE) { + vkDestroyShaderModule(m_ctx.GetDevice(), module, nullptr); + } + } + stages.modules.clear(); + stages.stages.clear(); + stages.hash = 0; + } + + Vector& ProgramManager::CreatePipelineShaderStages( + MG_State::GLState::ProgramObject* program) { + auto& entry = m_cache[program]; + HashType newHash = ComputeSpvHash(program); + if (!entry.stages.empty() && entry.hash == newHash) return entry.stages; + + DestroyStages(entry); + entry.hash = newHash; + + if (!program) return entry.stages; + + auto& spirvs = program->GetGeneratedSpirv(); + auto& shaders = program->GetAttachedShaders(); + + for (SizeT i = 0; i < spirvs.size(); ++i) { + auto& spv = spirvs[i]; + if (spv.empty()) continue; + + VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + smci.codeSize = spv.size() * sizeof(Uint); + smci.pCode = spv.data(); + + VkShaderModule module = VK_NULL_HANDLE; + VK_VERIFY(vkCreateShaderModule(m_ctx.GetDevice(), &smci, nullptr, &module), "vkCreateShaderModule"); + + VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + ShaderStage shaderStage = ShaderStage::Unknown; + if (i < shaders.size()) shaderStage = shaders[i]->GetShaderStage(); + stage.stage = ToVkStage(shaderStage); + stage.module = module; + stage.pName = "main"; + + entry.modules.push_back(module); + entry.stages.push_back(stage); + } + + return entry.stages; + } +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.h b/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.h index 542dfd20..35b4f151 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.h @@ -7,30 +7,37 @@ // End of Source File Header #pragma once -#include "Includes.h" +#include +#include "../Renderer/VulkanContext.h" +#include "../Renderer/VkCommon.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" -#include "Config.h" -#include "xxhash.h" - -namespace MobileGL::MG_Backend::DirectVulkan { - class VulkanContext; +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { class ProgramManager { public: - using ProgramObject = MobileGL::MG_State::GLState::ProgramObject; - using HashType = XXH64_hash_t; - ProgramManager(VulkanContext& ctx); + using HashType = Uint64; + + explicit ProgramManager(VulkanContext& ctx) : m_ctx(ctx) {} ~ProgramManager(); - Vector& CreatePipelineShaderStages(ProgramObject* programObject); - Vector* GetPipelineShaderStages(HashType hash); - Vector* GetPipelineShaderStages(ProgramObject* programObject); + ProgramManager(const ProgramManager&) = delete; + ProgramManager& operator=(const ProgramManager&) = delete; + + Vector& CreatePipelineShaderStages( + MG_State::GLState::ProgramObject* program); + private: + struct ProgramStages { + HashType hash = 0; + Vector stages; + Vector modules; + }; + + void DestroyStages(ProgramStages& stages); + HashType ComputeSpvHash(MG_State::GLState::ProgramObject* program) const; + VkShaderStageFlagBits ToVkStage(ShaderStage stage) const; - void Cleanup(); - HashType GetHash(ProgramObject* programObject); VulkanContext& m_ctx; - UnorderedMap> m_shaderStageCreateInfo; - XXH64_state_t* const m_hashState = XXH64_createState(); + UnorderedMap m_cache; }; -} // namespace MobileGL::MG_Backend::DirectVulkan +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index 802c5ddf..0c1dece4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -7,46 +7,43 @@ // End of Source File Header #include "FrameContext.h" -#include "VulkanContext.h" - -namespace MobileGL::MG_Backend::DirectVulkan { - FrameContext::~FrameContext() = default; +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { void FrameContext::Initialize(VulkanContext& ctx, VkCommandPool pool) { - // allocate cmd + CommandPool = pool; VkCommandBufferAllocateInfo abci{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; abci.commandPool = pool; abci.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; abci.commandBufferCount = 1; VK_VERIFY(vkAllocateCommandBuffers(ctx.GetDevice(), &abci, &CommandBuffer), "vkAllocateCommandBuffers"); - // semaphores VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; - VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &ImageAvailable), - "vkCreateSemaphore ImageAvailable"); - VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &RenderFinished), - "vkCreateSemaphore RenderFinished"); + VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &ImageAvailable), "vkCreateSemaphore"); + VK_VERIFY(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &RenderFinished), "vkCreateSemaphore"); - // fence (start signaled) VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; VK_VERIFY(vkCreateFence(ctx.GetDevice(), &fci, nullptr, &InFlightFence), "vkCreateFence"); } void FrameContext::Cleanup(VulkanContext& ctx) { + auto device = ctx.GetDevice(); + if (InFlightFence != VK_NULL_HANDLE) { + vkDestroyFence(device, InFlightFence, nullptr); + InFlightFence = VK_NULL_HANDLE; + } if (ImageAvailable != VK_NULL_HANDLE) { - vkDestroySemaphore(ctx.GetDevice(), ImageAvailable, nullptr); + vkDestroySemaphore(device, ImageAvailable, nullptr); ImageAvailable = VK_NULL_HANDLE; } if (RenderFinished != VK_NULL_HANDLE) { - vkDestroySemaphore(ctx.GetDevice(), RenderFinished, nullptr); + vkDestroySemaphore(device, RenderFinished, nullptr); RenderFinished = VK_NULL_HANDLE; } - if (InFlightFence != VK_NULL_HANDLE) { - vkDestroyFence(ctx.GetDevice(), InFlightFence, nullptr); - InFlightFence = VK_NULL_HANDLE; + if (CommandBuffer != VK_NULL_HANDLE) { + vkFreeCommandBuffers(device, CommandPool, 1, &CommandBuffer); + CommandBuffer = VK_NULL_HANDLE; } - - CommandBuffer = VK_NULL_HANDLE; + CommandPool = VK_NULL_HANDLE; } -} // namespace MobileGL::MG_Backend::DirectVulkan +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h index 39a12136..69b7813c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -8,21 +8,35 @@ #pragma once #include +#include "VulkanContext.h" +#include "VkCommon.h" namespace MobileGL::MG_Backend::DirectVulkan { - class VulkanContext; - - struct FrameContext { - FrameContext() = default; - ~FrameContext(); - - void Initialize(VulkanContext& ctx, VkCommandPool pool); - void Cleanup(VulkanContext& ctx); - - VkCommandBuffer CommandBuffer = VK_NULL_HANDLE; - VkSemaphore ImageAvailable = VK_NULL_HANDLE; - VkSemaphore RenderFinished = VK_NULL_HANDLE; - VkFence InFlightFence = VK_NULL_HANDLE; - Uint32 CurrentImageIndex = 0; + struct TrashBuffer { + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + Bool mapped = false; }; -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file + struct TrashImage { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + Vector mipViews; + }; + namespace VkManager { + struct FrameContext { + VkCommandBuffer CommandBuffer = VK_NULL_HANDLE; + VkSemaphore ImageAvailable = VK_NULL_HANDLE; + VkSemaphore RenderFinished = VK_NULL_HANDLE; + VkFence InFlightFence = VK_NULL_HANDLE; + Uint32 CurrentImageIndex = 0; + VkCommandPool CommandPool = VK_NULL_HANDLE; + Vector TrashBuffers; + Vector TrashImages; + // VkCommandPool CommandPool = VK_NULL_HANDLE; + + void Initialize(VulkanContext& ctx, VkCommandPool pool); + void Cleanup(VulkanContext& ctx); + }; + } // namespace VkManager +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp index 13c784b8..5fd77cc6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp @@ -6,136 +6,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header -#include "PipelineManager.h" -#include "VulkanContext.h" +#include -namespace MobileGL::MG_Backend::DirectVulkan { - PipelineManager::PipelineManager(VulkanContext& ctx) : Ctx(ctx) {} - PipelineManager::~PipelineManager() { - Cleanup(); - } - - void PipelineManager::EnsurePipelineLayout() { - if (PipelineLayout != VK_NULL_HANDLE) return; - VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - VK_VERIFY(vkCreatePipelineLayout(Ctx.GetDevice(), &plci, nullptr, &PipelineLayout), "vkCreatePipelineLayout"); - } - - VkPipeline PipelineManager::CreateGraphicsPipelineFromSpv(const std::string& key, - const std::vector& vsSpv, - const std::vector& fsSpv, - VkRenderPass renderPass, VkExtent2D extent) { - if (Pipelines.find(key) != Pipelines.end()) { - MGLOG_W("Pipeline key '%s' already exists - returning existing", key.c_str()); - return Pipelines[key]; - } - EnsurePipelineLayout(); - - VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - smci.codeSize = vsSpv.size() * sizeof(uint32_t); - smci.pCode = vsSpv.data(); - VkShaderModule vs; - VK_VERIFY(vkCreateShaderModule(Ctx.GetDevice(), &smci, nullptr, &vs), "vkCreateShaderModule VS"); - - smci.codeSize = fsSpv.size() * sizeof(uint32_t); - smci.pCode = fsSpv.data(); - VkShaderModule fs; - VK_VERIFY(vkCreateShaderModule(Ctx.GetDevice(), &smci, nullptr, &fs), "vkCreateShaderModule FS"); - - VkPipelineShaderStageCreateInfo stages[2]{}; - stages[0] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; - stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - stages[0].module = vs; - stages[0].pName = "main"; - stages[1] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; - stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - stages[1].module = fs; - stages[1].pName = "main"; - - VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; - vertexInput.vertexBindingDescriptionCount = 0; - vertexInput.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; - ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - - VkViewport vp{}; - vp.x = 0; - vp.y = 0; - vp.width = (float)extent.width; - vp.height = (float)extent.height; - vp.minDepth = 0; - vp.maxDepth = 1; - VkRect2D scissor{{0, 0}, extent}; - VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; - vpci.viewportCount = 1; - vpci.pViewports = &vp; - vpci.scissorCount = 1; - vpci.pScissors = &scissor; - - VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; - raster.polygonMode = VK_POLYGON_MODE_FILL; - raster.cullMode = VK_CULL_MODE_NONE; - raster.frontFace = VK_FRONT_FACE_CLOCKWISE; - raster.lineWidth = 1.0f; - - VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; - ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorAttach{}; - colorAttach.colorWriteMask = - VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorAttach.blendEnable = VK_FALSE; - VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; - blend.attachmentCount = 1; - blend.pAttachments = &colorAttach; - - VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; - gpi.stageCount = 2; - gpi.pStages = stages; - gpi.pVertexInputState = &vertexInput; - gpi.pInputAssemblyState = &ia; - gpi.pViewportState = &vpci; - gpi.pRasterizationState = &raster; - gpi.pMultisampleState = &ms; - gpi.pColorBlendState = &blend; - gpi.layout = PipelineLayout; - gpi.renderPass = renderPass; - gpi.subpass = 0; - - VkPipeline pipeline; - VK_VERIFY(vkCreateGraphicsPipelines(Ctx.GetDevice(), VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline), - "vkCreateGraphicsPipelines"); - - vkDestroyShaderModule(Ctx.GetDevice(), vs, nullptr); - vkDestroyShaderModule(Ctx.GetDevice(), fs, nullptr); - - Pipelines.emplace(key, pipeline); - MGLOG_D("Pipeline '%s' created", key.c_str()); - return pipeline; - } - - VkPipeline PipelineManager::GetPipeline(const std::string& key) const { - auto it = Pipelines.find(key); - if (it == Pipelines.end()) return VK_NULL_HANDLE; - return it->second; - } - - void PipelineManager::DestroyPipeline(const std::string& key) { - auto it = Pipelines.find(key); - if (it != Pipelines.end()) { - vkDestroyPipeline(Ctx.GetDevice(), it->second, nullptr); - Pipelines.erase(it); - } - } - - void PipelineManager::Cleanup() { - for (auto& kv : Pipelines) - vkDestroyPipeline(Ctx.GetDevice(), kv.second, nullptr); - Pipelines.clear(); - if (PipelineLayout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(Ctx.GetDevice(), PipelineLayout, nullptr); - PipelineLayout = VK_NULL_HANDLE; - } - } -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file +namespace MobileGL::MG_Backend::DirectVulkan::VkManager {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h deleted file mode 100644 index dad940be..00000000 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h +++ /dev/null @@ -1,35 +0,0 @@ -// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h -// Copyright (c) 2025-2026 MobileGL-Dev -// Licensed under the GNU Lesser General Public License v3.0: -// https://www.gnu.org/licenses/gpl-3.0.txt -// https://www.gnu.org/licenses/lgpl-3.0.txt -// SPDX-License-Identifier: LGPL-3.0-only -// End of Source File Header - -#pragma once -#include - -namespace MobileGL::MG_Backend::DirectVulkan { - class VulkanContext; - - class PipelineManager { - public: - PipelineManager(VulkanContext& ctx); - ~PipelineManager(); - - VkPipeline CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, - const std::vector& fsSpv, VkRenderPass renderPass, - VkExtent2D extent); - - VkPipeline GetPipeline(const std::string& key) const; - void DestroyPipeline(const std::string& key); - void Cleanup(); - - private: - VulkanContext& Ctx; - VkPipelineLayout PipelineLayout = VK_NULL_HANDLE; // TODO: per-pipeline layouts? - std::unordered_map Pipelines; - - void EnsurePipelineLayout(); - }; -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp index ede0e280..64393d16 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp @@ -7,127 +7,148 @@ // End of Source File Header #include "SwapchainManager.h" -#include "VulkanContext.h" -namespace MobileGL::MG_Backend::DirectVulkan { - SwapchainManager::SwapchainManager(VulkanContext& ctx) : Ctx(ctx) {} - SwapchainManager::~SwapchainManager() { - Cleanup(); - } +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { + namespace { + VkSurfaceFormatKHR ChooseSurfaceFormat(const Vector& formats) { + for (const auto& f : formats) { + if ((f.format == VK_FORMAT_R8G8B8A8_UNORM || f.format == VK_FORMAT_B8G8R8A8_UNORM) && + f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return f; + } + } + return formats.empty() ? VkSurfaceFormatKHR{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR} + : formats[0]; + } - void SwapchainManager::Initialize() { - CreateSwapchainInternal(); - CreateImageViews(); - ImagesInFlight.resize(Images.size(), VK_NULL_HANDLE); - } + VkPresentModeKHR ChoosePresentMode(const Vector& modes) { + for (const auto& m : modes) { + if (m == VK_PRESENT_MODE_MAILBOX_KHR) return m; + } + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D ChooseExtent(const VkSurfaceCapabilitiesKHR& caps, ANativeWindow* window) { + if (caps.currentExtent.width != UINT32_MAX) return caps.currentExtent; + VkExtent2D extent{640, 480}; +#ifdef VK_USE_PLATFORM_ANDROID_KHR + if (window) { + extent.width = static_cast(ANativeWindow_getWidth(window)); + extent.height = static_cast(ANativeWindow_getHeight(window)); + } +#else + (void)window; +#endif + extent.width = std::max(caps.minImageExtent.width, std::min(caps.maxImageExtent.width, extent.width)); + extent.height = std::max(caps.minImageExtent.height, std::min(caps.maxImageExtent.height, extent.height)); + return extent; + } + } // namespace + + SwapchainManager::~SwapchainManager() { DestroySwapchain(); } + + void SwapchainManager::Initialize() { CreateSwapchain(VK_NULL_HANDLE); } void SwapchainManager::Recreate() { - DestroyImageViews(); - if (Swapchain != VK_NULL_HANDLE) { - vkDestroySwapchainKHR(Ctx.GetDevice(), Swapchain, nullptr); - Swapchain = VK_NULL_HANDLE; + DestroySwapchain(); + CreateSwapchain(VK_NULL_HANDLE); + } + + void SwapchainManager::SetFramebuffers(Vector&& framebuffers) { m_framebuffers = Move(framebuffers); } + + void SwapchainManager::DestroySwapchain() { + auto device = m_ctx.GetDevice(); + if (device == VK_NULL_HANDLE) return; + for (auto fb : m_framebuffers) { + if (fb != VK_NULL_HANDLE) vkDestroyFramebuffer(device, fb, nullptr); } - CreateSwapchainInternal(); - CreateImageViews(); - ImagesInFlight.resize(Images.size(), VK_NULL_HANDLE); + m_framebuffers.clear(); - Framebuffers.clear(); - MGLOG_D("Swapchain recreated"); - } - - void SwapchainManager::Cleanup() { - for (auto fb : Framebuffers) - if (fb != VK_NULL_HANDLE) vkDestroyFramebuffer(Ctx.GetDevice(), fb, nullptr); - Framebuffers.clear(); - DestroyImageViews(); - if (Swapchain != VK_NULL_HANDLE) { - vkDestroySwapchainKHR(Ctx.GetDevice(), Swapchain, nullptr); - Swapchain = VK_NULL_HANDLE; + for (auto view : m_imageViews) { + if (view != VK_NULL_HANDLE) vkDestroyImageView(device, view, nullptr); } - } + m_imageViews.clear(); + m_images.clear(); + m_imagesInFlight.clear(); - void SwapchainManager::SetFramebuffers(std::vector&& fbs) { - Framebuffers = std::move(fbs); - } - - VkPresentModeKHR SwapchainManager::QueryPossiblePresentMode() { - const auto& surface = Ctx.GetSurface(); - const auto& phys = Ctx.GetPhysicalDevice(); - Uint32 modeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(phys, surface, &modeCount, NULL); - - Vector modes(modeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(phys, surface, &modeCount, modes.data()); - VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; - - for (auto m : modes) { - if (m == VK_PRESENT_MODE_MAILBOX_KHR) { - presentMode = m; - break; - } - if (m == VK_PRESENT_MODE_IMMEDIATE_KHR) { - presentMode = m; - } + if (m_swapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(device, m_swapchain, nullptr); + m_swapchain = VK_NULL_HANDLE; } - - return presentMode; + m_format = VK_FORMAT_UNDEFINED; + m_extent = {0, 0}; } - void SwapchainManager::CreateSwapchainInternal() { - VkSurfaceCapabilitiesKHR caps; - VK_VERIFY(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(Ctx.GetPhysicalDevice(), Ctx.GetSurface(), &caps), + void SwapchainManager::CreateSwapchain(VkSwapchainKHR oldSwapchain) { + VkSurfaceCapabilitiesKHR caps{}; + VK_VERIFY(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &caps), "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); - Extent = caps.currentExtent; - ImageFormat = VK_FORMAT_R8G8B8A8_UNORM; + Uint32 fmtCount = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &fmtCount, nullptr); + Vector formats(fmtCount); + if (fmtCount > 0) { + vkGetPhysicalDeviceSurfaceFormatsKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &fmtCount, + formats.data()); + } + + Uint32 modeCount = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &modeCount, nullptr); + Vector modes(modeCount); + if (modeCount > 0) { + vkGetPhysicalDeviceSurfacePresentModesKHR(m_ctx.GetPhysicalDevice(), m_ctx.GetSurface(), &modeCount, + modes.data()); + } + + VkSurfaceFormatKHR surfaceFormat = ChooseSurfaceFormat(formats); + VkPresentModeKHR presentMode = ChoosePresentMode(modes); + VkExtent2D extent = ChooseExtent(caps, m_ctx.GetWindow()); + + Uint32 imageCount = caps.minImageCount + 1; + if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) imageCount = caps.maxImageCount; VkSwapchainCreateInfoKHR sci{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; - sci.surface = Ctx.GetSurface(); - sci.minImageCount = std::max(2, caps.minImageCount); - sci.imageFormat = ImageFormat; - sci.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - sci.imageExtent = Extent; + sci.surface = m_ctx.GetSurface(); + sci.minImageCount = imageCount; + sci.imageFormat = surfaceFormat.format; + sci.imageColorSpace = surfaceFormat.colorSpace; + sci.imageExtent = extent; sci.imageArrayLayers = 1; - sci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + sci.imageUsage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; sci.preTransform = caps.currentTransform; sci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - sci.presentMode = QueryPossiblePresentMode(); + sci.presentMode = presentMode; + sci.clipped = VK_TRUE; + sci.oldSwapchain = oldSwapchain; - VK_VERIFY(vkCreateSwapchainKHR(Ctx.GetDevice(), &sci, nullptr, &Swapchain), "vkCreateSwapchainKHR"); + VK_VERIFY(vkCreateSwapchainKHR(m_ctx.GetDevice(), &sci, nullptr, &m_swapchain), "vkCreateSwapchainKHR"); - uint32_t count = 0; - VK_VERIFY(vkGetSwapchainImagesKHR(Ctx.GetDevice(), Swapchain, &count, nullptr), - "vkGetSwapchainImagesKHR count"); - Images.resize(count); - VK_VERIFY(vkGetSwapchainImagesKHR(Ctx.GetDevice(), Swapchain, &count, Images.data()), - "vkGetSwapchainImagesKHR images"); - MGLOG_D("Swapchain created (%u images)", count); - } + Uint32 actualCount = 0; + vkGetSwapchainImagesKHR(m_ctx.GetDevice(), m_swapchain, &actualCount, nullptr); + m_images.resize(actualCount); + vkGetSwapchainImagesKHR(m_ctx.GetDevice(), m_swapchain, &actualCount, m_images.data()); - void SwapchainManager::CreateImageViews() { - DestroyImageViews(); - ImageViews.resize(Images.size()); - for (size_t i = 0; i < Images.size(); ++i) { + m_imageViews.clear(); + m_imageViews.reserve(actualCount); + for (auto image : m_images) { VkImageViewCreateInfo ivci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - ivci.image = Images[i]; + ivci.image = image; ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; - ivci.format = ImageFormat; - ivci.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, - VK_COMPONENT_SWIZZLE_A}; + ivci.format = surfaceFormat.format; ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; ivci.subresourceRange.baseMipLevel = 0; ivci.subresourceRange.levelCount = 1; ivci.subresourceRange.baseArrayLayer = 0; ivci.subresourceRange.layerCount = 1; - VK_VERIFY(vkCreateImageView(Ctx.GetDevice(), &ivci, nullptr, &ImageViews[i]), "vkCreateImageView"); + VkImageView view = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(m_ctx.GetDevice(), &ivci, nullptr, &view), "vkCreateImageView swapchain"); + m_imageViews.push_back(view); } - MGLOG_D("ImageViews created"); - } - void SwapchainManager::DestroyImageViews() { - for (auto iv : ImageViews) - if (iv != VK_NULL_HANDLE) vkDestroyImageView(Ctx.GetDevice(), iv, nullptr); - ImageViews.clear(); + m_imagesInFlight.assign(actualCount, VK_NULL_HANDLE); + m_format = surfaceFormat.format; + m_extent = extent; } -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h index 9a46b7f1..c32bd3ca 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h @@ -8,42 +8,41 @@ #pragma once #include +#include "VulkanContext.h" +#include "VkCommon.h" -namespace MobileGL::MG_Backend::DirectVulkan { - class VulkanContext; - +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { class SwapchainManager { public: - SwapchainManager(VulkanContext& ctx); + explicit SwapchainManager(VulkanContext& ctx) : m_ctx(ctx) {} ~SwapchainManager(); + SwapchainManager(const SwapchainManager&) = delete; + SwapchainManager& operator=(const SwapchainManager&) = delete; + void Initialize(); void Recreate(); - void Cleanup(); - VkSwapchainKHR GetSwapchain() const { return Swapchain; } - VkFormat GetFormat() const { return ImageFormat; } - VkExtent2D GetExtent() const { return Extent; } - const std::vector& GetImageViews() const { return ImageViews; } - const std::vector& GetFramebuffers() const { return Framebuffers; } - const std::vector& GetImages() const { return Images; } - std::vector& GetImagesInFlight() { return ImagesInFlight; } - - void SetFramebuffers(std::vector&& fbs); + VkSwapchainKHR GetSwapchain() const { return m_swapchain; } + VkFormat GetFormat() const { return m_format; } + VkExtent2D GetExtent() const { return m_extent; } + const Vector& GetImages() const { return m_images; } + const Vector& GetImageViews() const { return m_imageViews; } + const Vector& GetFramebuffers() const { return m_framebuffers; } + void SetFramebuffers(Vector&& framebuffers); + Vector& GetImagesInFlight() { return m_imagesInFlight; } private: - VulkanContext& Ctx; - VkSwapchainKHR Swapchain = VK_NULL_HANDLE; - VkFormat ImageFormat = VK_FORMAT_UNDEFINED; - VkExtent2D Extent{0, 0}; - std::vector Images; - std::vector ImageViews; - std::vector ImagesInFlight; - std::vector Framebuffers; + void CreateSwapchain(VkSwapchainKHR oldSwapchain); + void DestroySwapchain(); - void CreateSwapchainInternal(); - void CreateImageViews(); - void DestroyImageViews(); - VkPresentModeKHR QueryPossiblePresentMode(); + VulkanContext& m_ctx; + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + VkFormat m_format = VK_FORMAT_UNDEFINED; + VkExtent2D m_extent{0, 0}; + Vector m_images; + Vector m_imageViews; + Vector m_framebuffers; + Vector m_imagesInFlight; }; -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkCommon.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkCommon.h new file mode 100644 index 00000000..c2b431e2 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkCommon.h @@ -0,0 +1,22 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkCommon.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { + inline void VkCheck(VkResult result, const char* msg) { + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) return; + MGLOG_E("Vulkan error %d at %s", static_cast(result), msg ? msg : "(unknown)"); + throw RuntimeError(msg ? msg : "Vulkan error"); + } +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager + +#ifndef VK_VERIFY +#define VK_VERIFY(res, msg) ::MobileGL::MG_Backend::DirectVulkan::VkManager::VkCheck((res), (msg)) +#endif diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp index a80bc516..11d1cd59 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp @@ -7,120 +7,159 @@ // End of Source File Header #include "VulkanContext.h" -#include "MG_Util/Types.h" -namespace MobileGL::MG_Backend::DirectVulkan { +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { + namespace { + Bool CheckDeviceExtensionSupport(VkPhysicalDevice device, const Vector& required) { + Uint32 count = 0; + vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr); + Vector props(count); + if (count > 0) vkEnumerateDeviceExtensionProperties(device, nullptr, &count, props.data()); + + for (auto* ext : required) { + Bool found = false; + for (const auto& p : props) { + if (std::strcmp(p.extensionName, ext) == 0) { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } + + Bool FindGraphicsQueueFamily(VkPhysicalDevice device, VkSurfaceKHR surface, Uint32& outFamily) { + Uint32 count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr); + if (count == 0) return false; + Vector props(count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &count, props.data()); + + for (Uint32 i = 0; i < count; ++i) { + if (!(props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) continue; + if (surface != VK_NULL_HANDLE) { + VkBool32 presentSupport = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (!presentSupport) continue; + } + outFamily = i; + return true; + } + return false; + } + } // namespace + VulkanContext::~VulkanContext() { - Shutdown(); + Cleanup(); } - void VulkanContext::Initialize(NativeWindowType window, const std::string& appName) { - if (Initialized) return; - CreateInstance(appName); + void VulkanContext::Initialize(ANativeWindow* window, const char* appName) { + if (m_instance != VK_NULL_HANDLE) return; + m_window = window; + CreateInstance(appName ? appName : "MobileGL-Vulkan"); CreateSurface(window); PickPhysicalDevice(); - CreateLogicalDevice(); - Initialized = true; - MGLOG_D("VulkanContext initialized"); + CreateDevice(); } - void VulkanContext::Shutdown() { - if (!Initialized && Instance == VK_NULL_HANDLE) return; - if (Device != VK_NULL_HANDLE) { - vkDeviceWaitIdle(Device); - vkDestroyDevice(Device, nullptr); - Device = VK_NULL_HANDLE; + void VulkanContext::Cleanup() { + if (m_device != VK_NULL_HANDLE) { + vkDeviceWaitIdle(m_device); + vkDestroyDevice(m_device, nullptr); + m_device = VK_NULL_HANDLE; } - if (Surface != VK_NULL_HANDLE) { - vkDestroySurfaceKHR(Instance, Surface, nullptr); - Surface = VK_NULL_HANDLE; + if (m_surface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); + m_surface = VK_NULL_HANDLE; } - if (Instance != VK_NULL_HANDLE) { - vkDestroyInstance(Instance, nullptr); - Instance = VK_NULL_HANDLE; + if (m_instance != VK_NULL_HANDLE) { + vkDestroyInstance(m_instance, nullptr); + m_instance = VK_NULL_HANDLE; } - Initialized = false; - MGLOG_D("VulkanContext shutdown"); + m_physicalDevice = VK_NULL_HANDLE; + m_graphicsQueue = VK_NULL_HANDLE; + m_graphicsQueueFamily = ~0u; + m_window = nullptr; } - void VulkanContext::CreateInstance(const std::string& appName) { - VkApplicationInfo appInfo{VK_STRUCTURE_TYPE_APPLICATION_INFO}; - appInfo.pApplicationName = appName.c_str(); - appInfo.apiVersion = VK_API_VERSION_1_1; + void VulkanContext::CreateInstance(const char* appName) { + VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO}; + app.pApplicationName = appName; + app.applicationVersion = VK_MAKE_VERSION(1, 1, 0); + app.pEngineName = "MobileGL"; + app.engineVersion = VK_MAKE_VERSION(1, 1, 0); + app.apiVersion = VK_API_VERSION_1_1; - const char* exts[] = {VK_KHR_SURFACE_EXTENSION_NAME, -#if __ANDROID__ - VK_KHR_ANDROID_SURFACE_EXTENSION_NAME + Vector extensions; + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); +#ifdef VK_USE_PLATFORM_ANDROID_KHR + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #endif - }; // TODO: support more platforms - VkInstanceCreateInfo ci{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; - ci.pApplicationInfo = &appInfo; - ci.enabledExtensionCount = 2; - ci.ppEnabledExtensionNames = exts; + VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; + ici.pApplicationInfo = &app; + ici.enabledExtensionCount = static_cast(extensions.size()); + ici.ppEnabledExtensionNames = extensions.data(); - VK_VERIFY(vkCreateInstance(&ci, nullptr, &Instance), "vkCreateInstance failed"); + VK_VERIFY(vkCreateInstance(&ici, nullptr, &m_instance), "vkCreateInstance"); } - void VulkanContext::CreateSurface(NativeWindowType window) { -#if __ANDROID__ - if (!Instance) throw RuntimeError("Instance not created"); - - auto* nativeWindow = static_cast(window); - if (!nativeWindow) throw RuntimeError("ANativeWindowType is null"); - + void VulkanContext::CreateSurface(ANativeWindow* window) { + if (!window) return; +#ifdef VK_USE_PLATFORM_ANDROID_KHR VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR}; - sci.window = nativeWindow; - VK_VERIFY(vkCreateAndroidSurfaceKHR(Instance, &sci, nullptr, &Surface), "vkCreateAndroidSurfaceKHR failed"); + sci.window = window; + VK_VERIFY(vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateAndroidSurfaceKHR"); #else - MGLOG_W("VulkanRenderer::Initialize called on a platform which is not supported yet"); // TODO: support more - // platforms + (void)window; #endif } void VulkanContext::PickPhysicalDevice() { - uint32_t count = 0; - VK_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, nullptr), "vkEnumeratePhysicalDevices count"); - if (count == 0) throw RuntimeError("No physical devices"); - std::vector devs(count); - VK_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, devs.data()), "vkEnumeratePhysicalDevices"); + Uint32 count = 0; + VK_VERIFY(vkEnumeratePhysicalDevices(m_instance, &count, nullptr), "vkEnumeratePhysicalDevices"); + if (count == 0) throw RuntimeError("No Vulkan physical devices found"); - for (auto d : devs) { - uint32_t qcount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(d, &qcount, nullptr); - std::vector qprops(qcount); - vkGetPhysicalDeviceQueueFamilyProperties(d, &qcount, qprops.data()); - for (uint32_t i = 0; i < qcount; ++i) { - VkBool32 present = VK_FALSE; - vkGetPhysicalDeviceSurfaceSupportKHR(d, i, Surface, &present); - if ((qprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) { - PhysicalDevice = d; - GraphicsQueueFamily = i; - MGLOG_D("Picked physical device, queue family %u", i); - return; - } - } + Vector devices(count); + VK_VERIFY(vkEnumeratePhysicalDevices(m_instance, &count, devices.data()), "vkEnumeratePhysicalDevices list"); + + Vector requiredExts = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + for (auto device : devices) { + Uint32 family = ~0u; + if (!FindGraphicsQueueFamily(device, m_surface, family)) continue; + if (!CheckDeviceExtensionSupport(device, requiredExts)) continue; + + m_physicalDevice = device; + m_graphicsQueueFamily = family; + break; + } + + if (m_physicalDevice == VK_NULL_HANDLE) { + throw RuntimeError("No suitable Vulkan physical device found"); } - throw RuntimeError("No suitable physical device"); } - void VulkanContext::CreateLogicalDevice() { - float prio = 1.0f; - VkDeviceQueueCreateInfo dqci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; - dqci.queueFamilyIndex = GraphicsQueueFamily; - dqci.queueCount = 1; - dqci.pQueuePriorities = &prio; + void VulkanContext::CreateDevice() { + float priority = 1.0f; + VkDeviceQueueCreateInfo qci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + qci.queueFamilyIndex = m_graphicsQueueFamily; + qci.queueCount = 1; + qci.pQueuePriorities = &priority; - const char* devExts[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + Vector deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + VkPhysicalDeviceFeatures features{}; VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; dci.queueCreateInfoCount = 1; - dci.pQueueCreateInfos = &dqci; - dci.enabledExtensionCount = 1; - dci.ppEnabledExtensionNames = devExts; + dci.pQueueCreateInfos = &qci; + dci.enabledExtensionCount = static_cast(deviceExtensions.size()); + dci.ppEnabledExtensionNames = deviceExtensions.data(); + dci.pEnabledFeatures = &features; - VK_VERIFY(vkCreateDevice(PhysicalDevice, &dci, nullptr, &Device), "vkCreateDevice failed"); - vkGetDeviceQueue(Device, GraphicsQueueFamily, 0, &GraphicsQueue); - MGLOG_D("Logical device created"); + VK_VERIFY(vkCreateDevice(m_physicalDevice, &dci, nullptr, &m_device), "vkCreateDevice"); + vkGetDeviceQueue(m_device, m_graphicsQueueFamily, 0, &m_graphicsQueue); } -} // namespace MobileGL::MG_Backend::DirectVulkan +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h index fc9283d2..74c68b4a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h @@ -8,43 +8,40 @@ #pragma once #include +#include "VkCommon.h" -#define VK_VERIFY(expr, ...) \ - do { \ - VkResult _vk_verify_result = (expr); \ - if (_vk_verify_result != VK_SUCCESS) { \ - MGLOG_E("Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \ - } \ - } while (0) - -namespace MobileGL::MG_Backend::DirectVulkan { +namespace MobileGL::MG_Backend::DirectVulkan::VkManager { class VulkanContext { public: VulkanContext() = default; ~VulkanContext(); - void Initialize(NativeWindowType window, const std::string& appName = "MobileGL-VulkanRenderer"); - void Shutdown(); + VulkanContext(const VulkanContext&) = delete; + VulkanContext& operator=(const VulkanContext&) = delete; - VkInstance GetInstance() const { return Instance; } - VkPhysicalDevice GetPhysicalDevice() const { return PhysicalDevice; } - VkDevice GetDevice() const { return Device; } - VkQueue GetGraphicsQueue() const { return GraphicsQueue; } - uint32_t GetGraphicsQueueFamily() const { return GraphicsQueueFamily; } - VkSurfaceKHR GetSurface() const { return Surface; } + void Initialize(ANativeWindow* window, const char* appName); + void Cleanup(); + + VkInstance GetInstance() const { return m_instance; } + VkPhysicalDevice GetPhysicalDevice() const { return m_physicalDevice; } + VkDevice GetDevice() const { return m_device; } + VkQueue GetGraphicsQueue() const { return m_graphicsQueue; } + Uint32 GetGraphicsQueueFamily() const { return m_graphicsQueueFamily; } + VkSurfaceKHR GetSurface() const { return m_surface; } + ANativeWindow* GetWindow() const { return m_window; } private: - void CreateInstance(const std::string& appName); - void CreateSurface(NativeWindowType window); + void CreateInstance(const char* appName); + void CreateSurface(ANativeWindow* window); void PickPhysicalDevice(); - void CreateLogicalDevice(); + void CreateDevice(); - VkInstance Instance = VK_NULL_HANDLE; - VkPhysicalDevice PhysicalDevice = VK_NULL_HANDLE; - VkDevice Device = VK_NULL_HANDLE; - VkQueue GraphicsQueue = VK_NULL_HANDLE; - uint32_t GraphicsQueueFamily = UINT32_MAX; - VkSurfaceKHR Surface = VK_NULL_HANDLE; - bool Initialized = false; + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + Uint32 m_graphicsQueueFamily = ~0u; + ANativeWindow* m_window = nullptr; }; -} // namespace MobileGL::MG_Backend::DirectVulkan +} // namespace MobileGL::MG_Backend::DirectVulkan::VkManager diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 31c5a9c7..619f9527 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7,64 +7,68 @@ // End of Source File Header #include "VulkanRenderer.h" -#include "VulkanContext.h" -#include "SwapchainManager.h" -#include "PipelineManager.h" -#include "FrameContext.h" namespace MobileGL::MG_Backend::DirectVulkan { - VulkanRenderer::VulkanRenderer(NativeWindowType window, const RendererConfig& cfg) : Window(window), Config(cfg) { - Ctx = std::make_unique(); - } + namespace { + VkShaderModule CreateShaderModule(VkDevice device, const Vector& spv) { + VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + smci.codeSize = spv.size() * sizeof(Uint); + smci.pCode = spv.data(); + VkShaderModule module = VK_NULL_HANDLE; + VK_VERIFY(vkCreateShaderModule(device, &smci, nullptr, &module), "vkCreateShaderModule"); + return module; + } + } // namespace + + VulkanRenderer::VulkanRenderer(ANativeWindow* window) : m_window(window) {} VulkanRenderer::~VulkanRenderer() { - Shutdown(); + if (!m_initialized) return; + vkDeviceWaitIdle(m_ctx.GetDevice()); + DestroyPipelines(); + DestroyFrameResources(); + DestroyRenderPass(); + if (m_commandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(m_ctx.GetDevice(), m_commandPool, nullptr); + m_commandPool = VK_NULL_HANDLE; + } + if (m_swapchain) { + m_swapchain.reset(); + } + m_ctx.Cleanup(); + m_initialized = false; } void VulkanRenderer::Initialize() { - Ctx->Initialize(Window, Config.AppName); - - Swapchain = std::make_unique(*Ctx); - Swapchain->Initialize(); - - CreateRenderPass(); - - PipelineMgr = std::make_unique(*Ctx); - + if (m_initialized) return; + m_ctx.Initialize(m_window, "MobileGL-VulkanRenderer"); + m_swapchain = MakeUnique(m_ctx); + m_swapchain->Initialize(); CreateCommandPool(); + CreateRenderPass(); + CreateFramebuffers(); CreateFrameResources(); - - FrameBegin(); - MGLOG_D("VulkanRenderer initialized"); + m_initialized = true; } - void VulkanRenderer::Shutdown() { - if (!Ctx) return; - - vkDeviceWaitIdle(Ctx->GetDevice()); - - DestroyFrameResources(); - DestroyCommandPool(); - - if (PipelineMgr) { - PipelineMgr->Cleanup(); - PipelineMgr.reset(); + void VulkanRenderer::EnsureInitialized() { + if (!m_initialized) { + throw RuntimeError("VulkanRenderer not initialized"); } - DestroyRenderPass(); - if (Swapchain) { - Swapchain->Cleanup(); - Swapchain.reset(); - } - if (Ctx) { - Ctx->Shutdown(); - Ctx.reset(); - } - MGLOG_D("VulkanRenderer shutdown"); + } + + void VulkanRenderer::CreateCommandPool() { + if (m_commandPool != VK_NULL_HANDLE) return; + VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + cpci.queueFamilyIndex = m_ctx.GetGraphicsQueueFamily(); + cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VK_VERIFY(vkCreateCommandPool(m_ctx.GetDevice(), &cpci, nullptr, &m_commandPool), "vkCreateCommandPool"); } void VulkanRenderer::CreateRenderPass() { + DestroyRenderPass(); VkAttachmentDescription color{}; - color.format = Swapchain->GetFormat(); + color.format = m_swapchain->GetFormat(); color.samples = VK_SAMPLE_COUNT_1_BIT; color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -72,220 +76,282 @@ namespace MobileGL::MG_Backend::DirectVulkan { color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub{}; sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; sub.colorAttachmentCount = 1; sub.pColorAttachments = &colorRef; + VkSubpassDependency dep{}; + dep.srcSubpass = VK_SUBPASS_EXTERNAL; + dep.dstSubpass = 0; + dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; rpci.attachmentCount = 1; rpci.pAttachments = &color; rpci.subpassCount = 1; rpci.pSubpasses = ⊂ + rpci.dependencyCount = 1; + rpci.pDependencies = &dep; - VK_VERIFY(vkCreateRenderPass(Ctx->GetDevice(), &rpci, nullptr, &RenderPass), "vkCreateRenderPass"); - - // Create framebuffers now (use swapchain imageviews) - const auto& imageViews = Swapchain->GetImageViews(); - std::vector fbs; - fbs.reserve(imageViews.size()); - for (auto iv : imageViews) { - VkImageView attachments[] = {iv}; - VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - fbci.renderPass = RenderPass; - fbci.attachmentCount = 1; - fbci.pAttachments = attachments; - fbci.width = Swapchain->GetExtent().width; - fbci.height = Swapchain->GetExtent().height; - fbci.layers = 1; - VkFramebuffer fb; - VK_VERIFY(vkCreateFramebuffer(Ctx->GetDevice(), &fbci, nullptr, &fb), "vkCreateFramebuffer"); - fbs.push_back(fb); - } - Swapchain->SetFramebuffers(std::move(fbs)); - MGLOG_D("RenderPass created and framebuffers set"); + VK_VERIFY(vkCreateRenderPass(m_ctx.GetDevice(), &rpci, nullptr, &m_renderPass), "vkCreateRenderPass"); } void VulkanRenderer::DestroyRenderPass() { - if (RenderPass != VK_NULL_HANDLE) { - vkDestroyRenderPass(Ctx->GetDevice(), RenderPass, nullptr); - RenderPass = VK_NULL_HANDLE; + if (m_renderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_ctx.GetDevice(), m_renderPass, nullptr); + m_renderPass = VK_NULL_HANDLE; } } - void VulkanRenderer::CreateCommandPool() { - VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; - cpci.queueFamilyIndex = Ctx->GetGraphicsQueueFamily(); - cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - VK_VERIFY(vkCreateCommandPool(Ctx->GetDevice(), &cpci, nullptr, &CommandPool), "vkCreateCommandPool"); - } - - void VulkanRenderer::DestroyCommandPool() { - if (CommandPool != VK_NULL_HANDLE) { - vkDestroyCommandPool(Ctx->GetDevice(), CommandPool, nullptr); - CommandPool = VK_NULL_HANDLE; + void VulkanRenderer::CreateFramebuffers() { + const auto& views = m_swapchain->GetImageViews(); + Vector fbs; + fbs.reserve(views.size()); + for (auto view : views) { + VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = m_renderPass; + fbci.attachmentCount = 1; + fbci.pAttachments = &view; + fbci.width = m_swapchain->GetExtent().width; + fbci.height = m_swapchain->GetExtent().height; + fbci.layers = 1; + VkFramebuffer fb = VK_NULL_HANDLE; + VK_VERIFY(vkCreateFramebuffer(m_ctx.GetDevice(), &fbci, nullptr, &fb), "vkCreateFramebuffer"); + fbs.push_back(fb); } + m_swapchain->SetFramebuffers(Move(fbs)); } void VulkanRenderer::CreateFrameResources() { - uint32_t imageCount = static_cast(Swapchain->GetImageViews().size()); + DestroyFrameResources(); + Uint32 imageCount = static_cast(m_swapchain->GetImageViews().size()); if (imageCount == 0) throw RuntimeError("Swapchain has zero images"); - uint32_t frames = std::min(Config.MaxFramesInFlight, imageCount); - Frames.clear(); - for (uint32_t i = 0; i < frames; ++i) { - auto fr = std::make_unique(); - fr->Initialize(*Ctx, CommandPool); - Frames.push_back(std::move(fr)); + Uint32 frames = std::min(2, imageCount); + for (Uint32 i = 0; i < frames; ++i) { + auto frame = MakeUnique(); + frame->Initialize(m_ctx, m_commandPool); + m_frames.push_back(Move(frame)); } - CurrentFrame = 0; - MGLOG_D("FrameResources created: %u", (uint32_t)Frames.size()); + m_currentFrame = 0; } void VulkanRenderer::DestroyFrameResources() { - for (auto& f : Frames) { - if (f) f->Cleanup(*Ctx); + for (auto& frame : m_frames) { + if (frame) frame->Cleanup(m_ctx); } - Frames.clear(); + m_frames.clear(); } - void VulkanRenderer::RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex) { - // Begin + void VulkanRenderer::DestroyPipelines() { + auto device = m_ctx.GetDevice(); + for (auto& [_, info] : m_pipelines) { + if (info.pipeline != VK_NULL_HANDLE) vkDestroyPipeline(device, info.pipeline, nullptr); + if (info.layout != VK_NULL_HANDLE) vkDestroyPipelineLayout(device, info.layout, nullptr); + } + m_pipelines.clear(); + } + + VkPipeline VulkanRenderer::CreateGraphicsPipelineFromSpv(const String& name, const Vector& vertexSpv, + const Vector& fragmentSpv) { + EnsureInitialized(); + auto it = m_pipelines.find(name); + if (it != m_pipelines.end()) return it->second.pipeline; + + VkShaderModule vs = CreateShaderModule(m_ctx.GetDevice(), vertexSpv); + VkShaderModule fs = CreateShaderModule(m_ctx.GetDevice(), fragmentSpv); + + VkPipelineShaderStageCreateInfo stages[2] = {}; + stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = vs; + stages[0].pName = "main"; + stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = fs; + stages[1].pName = "main"; + + VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + ia.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vpci.viewportCount = 1; + vpci.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + raster.polygonMode = VK_POLYGON_MODE_FILL; + raster.cullMode = VK_CULL_MODE_NONE; + raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + raster.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorAttach{}; + colorAttach.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorAttach.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + blend.attachmentCount = 1; + blend.pAttachments = &colorAttach; + + VkDynamicState dynamics[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dyn{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + dyn.dynamicStateCount = 2; + dyn.pDynamicStates = dynamics; + + VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + VkPipelineLayout layout = VK_NULL_HANDLE; + VK_VERIFY(vkCreatePipelineLayout(m_ctx.GetDevice(), &plci, nullptr, &layout), "vkCreatePipelineLayout"); + + VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + gpi.stageCount = 2; + gpi.pStages = stages; + gpi.pVertexInputState = &vertexInput; + gpi.pInputAssemblyState = &ia; + gpi.pViewportState = &vpci; + gpi.pRasterizationState = &raster; + gpi.pMultisampleState = &ms; + gpi.pColorBlendState = &blend; + gpi.pDynamicState = &dyn; + gpi.layout = layout; + gpi.renderPass = m_renderPass; + gpi.subpass = 0; + + VkPipeline pipeline = VK_NULL_HANDLE; + VK_VERIFY(vkCreateGraphicsPipelines(m_ctx.GetDevice(), VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline), + "vkCreateGraphicsPipelines"); + + vkDestroyShaderModule(m_ctx.GetDevice(), vs, nullptr); + vkDestroyShaderModule(m_ctx.GetDevice(), fs, nullptr); + + m_pipelines[name] = {pipeline, layout}; + return pipeline; + } + + void VulkanRenderer::RegisterRenderCallback(const String& name, RenderCallback callback) { + m_callbacks[name] = Move(callback); + } + + void VulkanRenderer::RecordAndSubmit(Uint32 imageIndex) { + auto& frame = *m_frames[m_currentFrame]; + VK_VERIFY(vkWaitForFences(m_ctx.GetDevice(), 1, &frame.InFlightFence, VK_TRUE, UINT64_MAX), + "vkWaitForFences"); + VK_VERIFY(vkResetFences(m_ctx.GetDevice(), 1, &frame.InFlightFence), "vkResetFences"); + VK_VERIFY(vkResetCommandBuffer(frame.CommandBuffer, 0), "vkResetCommandBuffer"); + VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; VK_VERIFY(vkBeginCommandBuffer(frame.CommandBuffer, &bi), "vkBeginCommandBuffer"); VkClearValue clear{}; clear.color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + VkRenderPassBeginInfo rpbi{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - rpbi.renderPass = RenderPass; - rpbi.framebuffer = Swapchain->GetFramebuffers()[imageIndex]; + rpbi.renderPass = m_renderPass; + rpbi.framebuffer = m_swapchain->GetFramebuffers()[imageIndex]; rpbi.renderArea.offset = {0, 0}; - rpbi.renderArea.extent = Swapchain->GetExtent(); + rpbi.renderArea.extent = m_swapchain->GetExtent(); rpbi.clearValueCount = 1; rpbi.pClearValues = &clear; vkCmdBeginRenderPass(frame.CommandBuffer, &rpbi, VK_SUBPASS_CONTENTS_INLINE); - for (auto& kv : RenderCallbacks) { - if (kv.second) { - kv.second(frame.CommandBuffer, imageIndex, Swapchain->GetExtent()); - } + VkViewport vp{}; + vp.x = 0.0f; + vp.y = 0.0f; + vp.width = static_cast(m_swapchain->GetExtent().width); + vp.height = static_cast(m_swapchain->GetExtent().height); + vp.minDepth = 0.0f; + vp.maxDepth = 1.0f; + vkCmdSetViewport(frame.CommandBuffer, 0, 1, &vp); + + VkRect2D scissor{{0, 0}, m_swapchain->GetExtent()}; + vkCmdSetScissor(frame.CommandBuffer, 0, 1, &scissor); + + for (auto& [_, cb] : m_callbacks) { + cb(frame.CommandBuffer, imageIndex, m_swapchain->GetExtent()); } vkCmdEndRenderPass(frame.CommandBuffer); VK_VERIFY(vkEndCommandBuffer(frame.CommandBuffer), "vkEndCommandBuffer"); - } - void VulkanRenderer::RecreateSwapchainIfNeeded() { - vkDeviceWaitIdle(Ctx->GetDevice()); - DestroyFrameResources(); - DestroyRenderPass(); - Swapchain->Recreate(); - CreateRenderPass(); - CreateFrameResources(); - } - - // Wait fence & Acquire image & Record commands & Submit - void VulkanRenderer::RenderFrame() { - if (!Ctx) throw RuntimeError("Renderer not initialized"); - FrameContext& frame = *Frames[CurrentFrame]; - - // Wait fence and reset - VK_VERIFY(vkWaitForFences(Ctx->GetDevice(), 1, &frame.InFlightFence, VK_TRUE, UINT64_MAX), "vkWaitForFences"); - VK_VERIFY(vkResetFences(Ctx->GetDevice(), 1, &frame.InFlightFence), "vkResetFences"); - - // Record commands - VK_VERIFY(vkResetCommandBuffer(frame.CommandBuffer, 0), "vkResetCommandBuffer"); - RecordFrameCommandBuffer(frame, frame.CurrentImageIndex); - - // Submit - VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; - VkSemaphore waitSemaphores[] = {frame.ImageAvailable}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + VkSemaphore waitSemaphores[] = {frame.ImageAvailable}; + VkSemaphore signalSemaphores[] = {frame.RenderFinished}; + + VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; si.waitSemaphoreCount = 1; si.pWaitSemaphores = waitSemaphores; si.pWaitDstStageMask = waitStages; si.commandBufferCount = 1; si.pCommandBuffers = &frame.CommandBuffer; - VkSemaphore signalSemaphores[] = {frame.RenderFinished}; si.signalSemaphoreCount = 1; si.pSignalSemaphores = signalSemaphores; - VK_VERIFY(vkQueueSubmit(Ctx->GetGraphicsQueue(), 1, &si, frame.InFlightFence), "vkQueueSubmit"); + VK_VERIFY(vkQueueSubmit(m_ctx.GetGraphicsQueue(), 1, &si, frame.InFlightFence), "vkQueueSubmit"); } - void VulkanRenderer::FrameBegin() { - FrameContext& frame = *Frames[CurrentFrame]; + void VulkanRenderer::DrawAndPresent() { + EnsureInitialized(); + auto& frame = *m_frames[m_currentFrame]; + auto& imagesInFlight = m_swapchain->GetImagesInFlight(); - // Acquire image - auto& imagesInFlight = Swapchain->GetImagesInFlight(); Uint32 imageIndex = 0; - VkResult res = vkAcquireNextImageKHR(Ctx->GetDevice(), Swapchain->GetSwapchain(), UINT64_MAX, + VkResult res = vkAcquireNextImageKHR(m_ctx.GetDevice(), m_swapchain->GetSwapchain(), UINT64_MAX, frame.ImageAvailable, VK_NULL_HANDLE, &imageIndex); - if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { - vkWaitForFences(Ctx->GetDevice(), 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); - } - imagesInFlight[imageIndex] = frame.InFlightFence; - frame.CurrentImageIndex = imageIndex; if (res == VK_ERROR_OUT_OF_DATE_KHR) { - MGLOG_D("vkAcquireNextImageKHR: OUT_OF_DATE -> recreate"); - RecreateSwapchainIfNeeded(); + RecreateSwapchain(); return; } VK_VERIFY(res, "vkAcquireNextImageKHR"); - } - void VulkanRenderer::Present() { - if (!Ctx) throw RuntimeError("Renderer not initialized"); - FrameContext& frame = *Frames[CurrentFrame]; + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(m_ctx.GetDevice(), 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); + } + imagesInFlight[imageIndex] = frame.InFlightFence; + + RecordAndSubmit(imageIndex); - // Present VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; - VkSemaphore signalSemaphores[] = {frame.RenderFinished}; + VkSemaphore waitSemaphores[] = {frame.RenderFinished}; pi.waitSemaphoreCount = 1; - pi.pWaitSemaphores = signalSemaphores; - VkSwapchainKHR scs[] = {Swapchain->GetSwapchain()}; + pi.pWaitSemaphores = waitSemaphores; + VkSwapchainKHR swapchains[] = {m_swapchain->GetSwapchain()}; pi.swapchainCount = 1; - pi.pSwapchains = scs; - pi.pImageIndices = &frame.CurrentImageIndex; - VkResult pres = vkQueuePresentKHR(Ctx->GetGraphicsQueue(), &pi); + pi.pSwapchains = swapchains; + pi.pImageIndices = &imageIndex; + + VkResult pres = vkQueuePresentKHR(m_ctx.GetGraphicsQueue(), &pi); if (pres == VK_ERROR_OUT_OF_DATE_KHR || pres == VK_SUBOPTIMAL_KHR) { - MGLOG_D("vkQueuePresentKHR: out_of_date/suboptimal -> recreate"); - RecreateSwapchainIfNeeded(); + RecreateSwapchain(); } else { VK_VERIFY(pres, "vkQueuePresentKHR"); } - CurrentFrame = (CurrentFrame + 1) % Frames.size(); - - FrameBegin(); + m_currentFrame = (m_currentFrame + 1) % m_frames.size(); } - void VulkanRenderer::RegisterRenderCallback(const std::string& name, RenderCallback cb) { - auto it = std::find_if(RenderCallbacks.begin(), RenderCallbacks.end(), - [&](const auto& kv) { return kv.first == name; }); - if (it != RenderCallbacks.end()) { - MGLOG_W("Render callback '%s' already registered", name.c_str()); - return; - } - RenderCallbacks.emplace_back(name, std::move(cb)); + void VulkanRenderer::RecreateSwapchain() { + vkDeviceWaitIdle(m_ctx.GetDevice()); + DestroyFrameResources(); + DestroyRenderPass(); + + m_swapchain->Recreate(); + CreateRenderPass(); + CreateFramebuffers(); + CreateFrameResources(); + + DestroyPipelines(); } - void VulkanRenderer::UnregisterRenderCallback(const std::string& name) { - RenderCallbacks.erase(std::remove_if(RenderCallbacks.begin(), RenderCallbacks.end(), - [&](const auto& kv) { return kv.first == name; }), - RenderCallbacks.end()); - } + void VulkanRenderer::RenderFrame() { DrawAndPresent(); } - VkPipeline VulkanRenderer::CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, - const std::vector& fsSpv) { - return PipelineMgr->CreateGraphicsPipelineFromSpv(key, vsSpv, fsSpv, RenderPass, Swapchain->GetExtent()); - } - - VkExtent2D VulkanRenderer::GetExtent() const { - return Swapchain ? Swapchain->GetExtent() : VkExtent2D{0, 0}; - } - - void VulkanRenderer::WaitIdle() { - if (Ctx && Ctx->GetDevice() != VK_NULL_HANDLE) vkDeviceWaitIdle(Ctx->GetDevice()); - } + void VulkanRenderer::Present() { DrawAndPresent(); } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 99dbba45..242e94b9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -8,67 +8,58 @@ #pragma once #include +#include "VulkanContext.h" +#include "SwapchainManager.h" +#include "FrameContext.h" +#include "VkCommon.h" namespace MobileGL::MG_Backend::DirectVulkan { - class VulkanContext; - class SwapchainManager; - class PipelineManager; - struct FrameContext; - - using RenderCallback = std::function; - - struct RendererConfig { - Uint32 MaxFramesInFlight = 2; - String AppName = "MobileGL-VulkanRenderer"; - }; - class VulkanRenderer { public: - VulkanRenderer(NativeWindowType window, const RendererConfig& cfg = {}); + using RenderCallback = std::function; + + explicit VulkanRenderer(ANativeWindow* window); ~VulkanRenderer(); - void Initialize(); - void Shutdown(); + VulkanRenderer(const VulkanRenderer&) = delete; + VulkanRenderer& operator=(const VulkanRenderer&) = delete; + void Initialize(); void RenderFrame(); void Present(); - void RegisterRenderCallback(const std::string& name, RenderCallback cb); - void UnregisterRenderCallback(const std::string& name); - - VkPipeline CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, - const std::vector& fsSpv); - - VkExtent2D GetExtent() const; - - void WaitIdle(); + VkPipeline CreateGraphicsPipelineFromSpv(const String& name, const Vector& vertexSpv, + const Vector& fragmentSpv); + void RegisterRenderCallback(const String& name, RenderCallback callback); private: - NativeWindowType Window = 0; - RendererConfig Config; - - std::unique_ptr Ctx; - std::unique_ptr Swapchain; - std::unique_ptr PipelineMgr; - - VkRenderPass RenderPass = VK_NULL_HANDLE; - VkCommandPool CommandPool = VK_NULL_HANDLE; - - std::vector> Frames; - Uint32 CurrentFrame = 0; - - // Render callbacks map - std::vector> RenderCallbacks; - - // Internals + void EnsureInitialized(); + void CreateCommandPool(); void CreateRenderPass(); void DestroyRenderPass(); - void CreateCommandPool(); - void DestroyCommandPool(); + void CreateFramebuffers(); void CreateFrameResources(); void DestroyFrameResources(); - void RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex); - void RecreateSwapchainIfNeeded(); - void FrameBegin(); + void RecreateSwapchain(); + void RecordAndSubmit(Uint32 imageIndex); + void DrawAndPresent(); + void DestroyPipelines(); + + struct PipelineInfo { + VkPipeline pipeline = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + }; + + ANativeWindow* m_window = nullptr; + VkManager::VulkanContext m_ctx; + UniquePtr m_swapchain; + VkRenderPass m_renderPass = VK_NULL_HANDLE; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + Vector> m_frames; + Uint32 m_currentFrame = 0; + Bool m_initialized = false; + + UnorderedMap m_callbacks; + UnorderedMap m_pipelines; }; -} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp b/MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp new file mode 100644 index 00000000..dc7b1ae7 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp @@ -0,0 +1,3448 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/TmpImpl.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "TmpImpl.h" +#include "MG_Backend/DirectVulkan/DirectVulkan.h" +#include "Renderer/VkCommon.h" +#include "Renderer/VulkanContext.h" +#include "Renderer/SwapchainManager.h" +#include "Renderer/FrameContext.h" +#include "Managers/ProgramManager.h" +#include +#include +#include +#include "MG_Util/Debug/Log.h" +#include + +#define DEBUG_TRACE_POINT() MGLOG_D("DV TmpImpl Trace: %s:%d", __func__, __LINE__) +namespace MobileGL::Backend::DirectVulkan::TmpImpl { + namespace { + class PendingClearInfo; + void BeginRenderPass(GLbitfield clearMask, const PendingClearInfo* clearInfo); + namespace DV = MobileGL::MG_Backend::DirectVulkan::VkManager; + using BufferObject = MobileGL::MG_State::GLState::BufferObject; + using ProgramObject = MobileGL::MG_State::GLState::ProgramObject; + using VertexArrayObject = MobileGL::MG_State::GLState::VertexArrayObject; + using FramebufferObject = MobileGL::MG_State::GLState::FramebufferObject; + using FramebufferAttachmentObject = MobileGL::MG_State::GLState::FramebufferAttachmentObject; + using ITextureObject = MobileGL::MG_State::GLState::ITextureObject; + using RenderbufferObject = MobileGL::MG_State::GLState::RenderbufferObject; + using SamplerObject = MobileGL::MG_State::GLState::SamplerObject; + using TextureObjectMipmap = MobileGL::MG_State::GLState::TextureObjectMipmap; + using TrashImage = MobileGL::MG_Backend::DirectVulkan::TrashImage; + + struct BufferResource { + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkDeviceSize size = 0; + VkBufferUsageFlags usage = 0; + VkMemoryPropertyFlags props = 0; + void* mapped = nullptr; + Uint32 lastUsedFrame = ~0u; + }; + + struct TextureResource { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + UnorderedMap mipViews; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent3D extent{0, 0, 1}; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + Uint32 mipLevels = 1; + Uint16 paramsVersion = 0; + bool valid = false; + }; + + struct RenderbufferResource { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent2D extent{0, 0}; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + bool valid = false; + }; + + struct SamplerResource { + VkSampler sampler = VK_NULL_HANDLE; + Uint16 version = 0; + SamplerParameters params{}; + }; + + struct PendingClearInfo { + GLbitfield mask = 0; + FloatVec4 clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; + Float clearDepth = 1.0f; + }; + + struct UniformBindingInfo { + String name; + Uint32 binding = 0; + Uint32 set = 0; + Uint32 blockIndex = 0xFFFFFFFFu; + VkShaderStageFlags stages = 0; + }; + + struct SamplerBindingInfo { + String name; + Uint32 binding = 0; + Uint32 set = 0; + VkShaderStageFlags stages = 0; + }; + + struct ProgramResource { + ProgramObject* program = nullptr; + Uint64 spvHash = 0; + DV::ProgramManager::HashType programHash = 0; + Vector* shaderStages = nullptr; + + VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + + struct UboPool { + Vector buffers; + Uint32 cursor = 0; + }; + Vector uboPools; + SizeT uboSize = 0; + Int32 uboBinding = -1; + Uint32 uniformDescriptorCount = 0; + Uint32 samplerDescriptorCount = 0; + + Vector uniformBindings; + Vector samplerBindings; + UnorderedMap samplerBindingByName; + + UnorderedMap pipelines; + }; + + struct DescriptorPoolConfig { + Uint32 maxSets = 0; + Uint32 uniformCount = 0; + Uint32 samplerCount = 0; + }; + + struct FrameDescriptorPools { + Vector pools; + DescriptorPoolConfig config; + Uint32 activePool = 0; + }; + + struct VertexAttribKey { + Uint8 enabled = 0; + Uint8 size = 0; + Uint8 type = 0; + Uint8 normalized = 0; + Uint8 isInteger = 0; + Uint8 divisor = 0; + Uint16 pad = 0; + Uint32 stride = 0; + Uint64 offset = 0; + }; + + struct AttachmentInfo { + FramebufferAttachmentType type = FramebufferAttachmentType::None; + VkImageView view = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageAspectFlags aspect = 0; + VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; + ITextureObject* texture = nullptr; + RenderbufferObject* renderbuffer = nullptr; + }; + + struct BackendFramebufferObject { + Array renderPasses = {}; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VkExtent2D extent{0, 0}; + Uint32 colorAttachmentCount = 0; + Bool hasDepth = false; + VkFormat depthFormat = VK_FORMAT_UNDEFINED; + Uint64 renderPassCompatHash = 0; + + Vector attachments; + Array(FramebufferAttachmentType::FramebufferAttachmentTypeCount)> + attachmentIndex = {}; + + FramebufferObject::FramebufferAttachmentVersionArray syncedAttachmentVersions = {0}; + FramebufferObject::FramebufferAttachmentArray frontendDrawBuffers = {FramebufferAttachmentType::None}; + Array drawBufferAttachmentIndices = {}; + FramebufferAttachmentType frontendReadBuffer = FramebufferAttachmentType::Color0; + Uint64 configHash = 0; + Uint16 objectVersion = 0; + + Bool IsValid() const { return renderPasses[0] != VK_NULL_HANDLE && framebuffer != VK_NULL_HANDLE; } + }; + + struct BackendState { + UniquePtr ctx; + UniquePtr swapchain; + UniquePtr programMgr; + Array renderPasses = {}; + VkCommandPool commandPool = VK_NULL_HANDLE; + Vector> frames; + Vector frameDescriptorPools; + Uint32 currentFrame = 0; + Uint32 maxFramesInFlight = 2; + Bool initialized = false; + + VkImage depthImage = VK_NULL_HANDLE; + VkDeviceMemory depthMemory = VK_NULL_HANDLE; + VkImageView depthView = VK_NULL_HANDLE; + VkFormat depthFormat = VK_FORMAT_UNDEFINED; + Uint64 defaultRenderPassCompatHash = 0; + + BufferResource nullUbo; + + TextureResource defaultTexture; + SamplerResource defaultSampler; + + UnorderedMap buffers; + UnorderedMap textures; + UnorderedMap renderbuffers; + UnorderedMap samplers; + UnorderedMap programs; + UnorderedMap, SharedPtr> + framebuffers; + + VkRenderPass activeRenderPass = VK_NULL_HANDLE; + VkFramebuffer activeFramebuffer = VK_NULL_HANDLE; + VkExtent2D activeExtent{0, 0}; + Uint32 activeColorAttachmentCount = 1; + Bool activeHasDepth = false; + VkFormat activeDepthFormat = VK_FORMAT_UNDEFINED; + SharedPtr activeStateFBO = nullptr; + SharedPtr activeBackendFBO = nullptr; + Bool activeIsDefault = true; + Bool activeDefaultColorWrites = true; + + VkRenderPass recordingRenderPass = VK_NULL_HANDLE; + VkFramebuffer recordingFramebuffer = VK_NULL_HANDLE; + Bool cmdBufferBegun = false; + + Bool recording = false; + Bool frameSubmitted = false; + FloatVec4 clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; + Float clearDepth = 1.0f; + + UnorderedMap, PendingClearInfo> pendingClears; + + Vector swapchainImageLayouts; + }; + + BackendState g; + + void ClearFrameTrashBuffers(); + void ClearFrameTrashImages(); + + Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags props) { + VkPhysicalDeviceMemoryProperties memProps{}; + vkGetPhysicalDeviceMemoryProperties(g.ctx->GetPhysicalDevice(), &memProps); + for (Uint32 i = 0; i < memProps.memoryTypeCount; ++i) { + if ((typeFilter & (1u << i)) && (memProps.memoryTypes[i].propertyFlags & props) == props) { + return i; + } + } + throw RuntimeError("No suitable memory type"); + } + + void DestroyBuffer(BufferResource& res) { + if (res.mapped && res.memory != VK_NULL_HANDLE) { + vkUnmapMemory(g.ctx->GetDevice(), res.memory); + res.mapped = nullptr; + } + if (res.buffer != VK_NULL_HANDLE) { + vkDestroyBuffer(g.ctx->GetDevice(), res.buffer, nullptr); + res.buffer = VK_NULL_HANDLE; + } + if (res.memory != VK_NULL_HANDLE) { + vkFreeMemory(g.ctx->GetDevice(), res.memory, nullptr); + res.memory = VK_NULL_HANDLE; + } + res.size = 0; + } + + void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props, + BufferResource& out) { + VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bci.size = size; + bci.usage = usage; + bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateBuffer(g.ctx->GetDevice(), &bci, nullptr, &out.buffer), "vkCreateBuffer"); + + VkMemoryRequirements req{}; + vkGetBufferMemoryRequirements(g.ctx->GetDevice(), out.buffer, &req); + + VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + mai.allocationSize = req.size; + mai.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, props); + VK_VERIFY(vkAllocateMemory(g.ctx->GetDevice(), &mai, nullptr, &out.memory), "vkAllocateMemory"); + VK_VERIFY(vkBindBufferMemory(g.ctx->GetDevice(), out.buffer, out.memory, 0), "vkBindBufferMemory"); + + out.size = size; + out.usage = usage; + out.props = props; + if (props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { + VK_VERIFY(vkMapMemory(g.ctx->GetDevice(), out.memory, 0, req.size, 0, &out.mapped), "vkMapMemory"); + } + + MOBILEGL_ASSERT(out.buffer != VK_NULL_HANDLE || out.memory != VK_NULL_HANDLE, + "CreateBuffer: buffer or memory is null"); + } + + void UpdateBufferFromObject(BufferResource& res, BufferObject* obj) { + auto dataPtr = obj->GetDataReadOnly(); + if (!dataPtr || dataPtr->empty()) { + obj->ClearDirty(); + return; + } + auto changeBits = obj->GetChangeBits(); + if (!(changeBits & BufferChangeBits::DirtyBit)) return; + + SizeT copySize = obj->GetSize(); + if (copySize == 0) copySize = dataPtr->size(); + + const auto& dirtyRanges = obj->GetDirtyRanges(); + if (dirtyRanges.empty()) { + Memcpy(res.mapped, dataPtr->data(), copySize); + } else { + for (const auto& range : dirtyRanges) { + if (range.end <= range.start) continue; + SizeT end = range.end; + if (end > copySize) end = copySize; + if (end <= range.start) continue; + Memcpy(reinterpret_cast(res.mapped) + range.start, dataPtr->data() + range.start, + end - range.start); + } + } + obj->ClearDirty(); + } + + BufferResource& SyncBuffer(BufferObject* obj) { + auto& res = g.buffers[obj]; + VkDeviceSize size = obj->GetSize(); + if (size == 0) { + auto dataPtr = obj->GetDataReadOnly(); + if (dataPtr && !dataPtr->empty()) { + size = static_cast(dataPtr->size()); + } + return res; + } + + const auto changeBits = obj->GetChangeBits(); + Bool preferRealloc = changeBits & BufferChangeBits::PreferReallocationBit; + Bool needCreate = res.buffer == VK_NULL_HANDLE || res.size < size || preferRealloc; + if (needCreate) { + auto& frame = *g.frames[g.currentFrame]; + frame.TrashBuffers.push_back({res.buffer, res.memory, res.mapped != nullptr}); + res = BufferResource{}; + VkBufferUsageFlags usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VkMemoryPropertyFlags props = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + CreateBuffer(size, usage, props, res); + res.lastUsedFrame = ~0u; + } + + if (res.mapped) { + if ((changeBits & BufferChangeBits::DirtyBit) && res.lastUsedFrame != ~0u && + res.lastUsedFrame != g.currentFrame && res.lastUsedFrame < g.frames.size()) { + auto& prior = *g.frames[res.lastUsedFrame]; + VK_VERIFY(vkWaitForFences(g.ctx->GetDevice(), 1, &prior.InFlightFence, VK_TRUE, UINT64_MAX), + "vkWaitForFences"); + } + auto dataPtr = obj->GetDataReadOnly(); + if (needCreate && dataPtr && !dataPtr->empty()) { + Memcpy(res.mapped, dataPtr->data(), static_cast(size)); + obj->ClearDirty(); + } else { + UpdateBufferFromObject(res, obj); + } + } + if (g.cmdBufferBegun) res.lastUsedFrame = g.currentFrame; + return res; + } + + void DestroyImage(TextureResource& res) { + if (res.view != VK_NULL_HANDLE) { + vkDestroyImageView(g.ctx->GetDevice(), res.view, nullptr); + res.view = VK_NULL_HANDLE; + } + if (!res.mipViews.empty()) { + for (auto& [_, view] : res.mipViews) { + if (view != VK_NULL_HANDLE) vkDestroyImageView(g.ctx->GetDevice(), view, nullptr); + } + res.mipViews.clear(); + } + if (res.image != VK_NULL_HANDLE) { + vkDestroyImage(g.ctx->GetDevice(), res.image, nullptr); + res.image = VK_NULL_HANDLE; + } + if (res.memory != VK_NULL_HANDLE) { + vkFreeMemory(g.ctx->GetDevice(), res.memory, nullptr); + res.memory = VK_NULL_HANDLE; + } + res.format = VK_FORMAT_UNDEFINED; + res.layout = VK_IMAGE_LAYOUT_UNDEFINED; + res.extent = {0, 0, 1}; + res.mipLevels = 1; + res.valid = false; + } + + void TrashImageResource(TextureResource& res) { + if (res.view == VK_NULL_HANDLE && res.image == VK_NULL_HANDLE && res.memory == VK_NULL_HANDLE && + res.mipViews.empty()) { + return; + } + auto& frame = *g.frames[g.currentFrame]; + TrashImage trash{}; + trash.view = res.view; + trash.image = res.image; + trash.memory = res.memory; + if (!res.mipViews.empty()) { + trash.mipViews.reserve(res.mipViews.size()); + for (auto& [_, view] : res.mipViews) { + if (view != VK_NULL_HANDLE) trash.mipViews.push_back(view); + } + } + frame.TrashImages.push_back(std::move(trash)); + + res.view = VK_NULL_HANDLE; + res.image = VK_NULL_HANDLE; + res.memory = VK_NULL_HANDLE; + res.mipViews.clear(); + res.format = VK_FORMAT_UNDEFINED; + res.layout = VK_IMAGE_LAYOUT_UNDEFINED; + res.extent = {0, 0, 1}; + res.mipLevels = 1; + res.valid = false; + } + + void DestroyRenderbuffer(RenderbufferResource& res) { + if (res.view != VK_NULL_HANDLE) { + vkDestroyImageView(g.ctx->GetDevice(), res.view, nullptr); + res.view = VK_NULL_HANDLE; + } + if (res.image != VK_NULL_HANDLE) { + vkDestroyImage(g.ctx->GetDevice(), res.image, nullptr); + res.image = VK_NULL_HANDLE; + } + if (res.memory != VK_NULL_HANDLE) { + vkFreeMemory(g.ctx->GetDevice(), res.memory, nullptr); + res.memory = VK_NULL_HANDLE; + } + res.format = VK_FORMAT_UNDEFINED; + res.layout = VK_IMAGE_LAYOUT_UNDEFINED; + res.extent = {0, 0}; + res.valid = false; + } + + VkFormat ToVkFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::R8: + return VK_FORMAT_R8_UNORM; + case TextureInternalFormat::R8Snorm: + return VK_FORMAT_R8_SNORM; + case TextureInternalFormat::R8I: + return VK_FORMAT_R8_SINT; + case TextureInternalFormat::R8UI: + return VK_FORMAT_R8_UINT; + case TextureInternalFormat::RG8: + return VK_FORMAT_R8G8_UNORM; + case TextureInternalFormat::RG8Snorm: + return VK_FORMAT_R8G8_SNORM; + case TextureInternalFormat::RG8I: + return VK_FORMAT_R8G8_SINT; + case TextureInternalFormat::RG8UI: + return VK_FORMAT_R8G8_UINT; + case TextureInternalFormat::RGB8: + return VK_FORMAT_R8G8B8_UNORM; + case TextureInternalFormat::RGB8Snorm: + return VK_FORMAT_R8G8B8_SNORM; + case TextureInternalFormat::RGBA8: + return VK_FORMAT_R8G8B8A8_UNORM; + case TextureInternalFormat::RGBA8Snorm: + return VK_FORMAT_R8G8B8A8_SNORM; + case TextureInternalFormat::SRGB8: + return VK_FORMAT_R8G8B8_SRGB; + case TextureInternalFormat::SRGB8Alpha8: + return VK_FORMAT_R8G8B8A8_SRGB; + case TextureInternalFormat::R16F: + return VK_FORMAT_R16_SFLOAT; + case TextureInternalFormat::RG16F: + return VK_FORMAT_R16G16_SFLOAT; + case TextureInternalFormat::RGB16F: + return VK_FORMAT_R16G16B16_SFLOAT; + case TextureInternalFormat::RGBA16F: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case TextureInternalFormat::R32F: + return VK_FORMAT_R32_SFLOAT; + case TextureInternalFormat::RG32F: + return VK_FORMAT_R32G32_SFLOAT; + case TextureInternalFormat::RGBA32F: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case TextureInternalFormat::DepthComponent16: + return VK_FORMAT_D16_UNORM; + case TextureInternalFormat::DepthComponent24: + return VK_FORMAT_D24_UNORM_S8_UINT; + case TextureInternalFormat::DepthComponent32F: + return VK_FORMAT_D32_SFLOAT; + case TextureInternalFormat::Depth24Stencil8: + return VK_FORMAT_D24_UNORM_S8_UINT; + case TextureInternalFormat::Depth32FStencil8: + return VK_FORMAT_D32_SFLOAT_S8_UINT; + default: + return VK_FORMAT_R8G8B8A8_UNORM; + } + } + + VkImageAspectFlags AspectForFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_D32_SFLOAT: + return VK_IMAGE_ASPECT_DEPTH_BIT; + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + default: + return VK_IMAGE_ASPECT_COLOR_BIT; + } + } + + Bool IsDepthFormat(VkFormat format) { + return (AspectForFormat(format) & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + } + + VkImageAspectFlags SampleAspectForFormat(VkFormat format) { + if (IsDepthFormat(format)) return VK_IMAGE_ASPECT_DEPTH_BIT; + return VK_IMAGE_ASPECT_COLOR_BIT; + } + + VkImageLayout SampledLayoutForFormat(VkFormat format) { + if (IsDepthFormat(format)) return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + VkImageUsageFlags UsageForTextureFormat(VkFormat format) { + VkImageUsageFlags usage = + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (IsDepthFormat(format)) { + usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + return usage; + } + + VkImageUsageFlags UsageForRenderbufferFormat(VkFormat format) { + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (IsDepthFormat(format)) { + usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + return usage; + } + + VkImageLayout LayoutForFramebufferAttachment(FramebufferAttachmentType type, VkFormat format) { + if (IsDepthFormat(format) || type == FramebufferAttachmentType::Depth || + type == FramebufferAttachmentType::Stencil) { + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + + Uint8 ClearMaskKey(GLbitfield mask) { + Uint8 key = 0; + if (mask & GL_COLOR_BUFFER_BIT) key |= 0x1; + if (mask & GL_DEPTH_BUFFER_BIT) key |= 0x2; + if (mask & GL_STENCIL_BUFFER_BIT) key |= 0x4; + return key; + } + + void FillSubpassDependency(VkSubpassDependency& dep, Bool hasColor, Bool hasDepthStencil) { + dep = {}; + dep.srcSubpass = VK_SUBPASS_EXTERNAL; + dep.dstSubpass = 0; + if (hasColor) { + dep.srcStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + } + if (hasDepthStencil) { + dep.srcStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep.dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dep.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + } + if (dep.srcStageMask == 0) { + dep.srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dep.dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + } + } + + VkRenderPass CreateBackendRenderPass(const BackendFramebufferObject& backend, GLbitfield clearMask) { + Vector descs; + descs.reserve(backend.attachments.size()); + + Vector colorClearable; + colorClearable.resize(backend.attachments.size(), false); + if (clearMask & GL_COLOR_BUFFER_BIT) { + for (Uint32 i = 0; i < backend.colorAttachmentCount; ++i) { + Int32 idx = backend.drawBufferAttachmentIndices[i]; + if (idx >= 0 && static_cast(idx) < backend.attachments.size()) { + colorClearable[static_cast(idx)] = true; + } + } + } + + for (SizeT i = 0; i < backend.attachments.size(); ++i) { + const auto& info = backend.attachments[i]; + VkAttachmentDescription desc{}; + desc.format = info.format; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + Bool isDepth = (info.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + Bool isStencil = (info.aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + if (isDepth) { + desc.loadOp = + (clearMask & GL_DEPTH_BUFFER_BIT) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + } else { + desc.loadOp = colorClearable[i] ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + } + if (isStencil) { + desc.stencilLoadOp = + (clearMask & GL_STENCIL_BUFFER_BIT) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + } else { + desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + desc.initialLayout = info.finalLayout; + desc.finalLayout = info.finalLayout; + descs.push_back(desc); + } + + Vector colorRefs; + colorRefs.resize(backend.colorAttachmentCount); + for (Uint32 i = 0; i < backend.colorAttachmentCount; ++i) { + Int32 idx = backend.drawBufferAttachmentIndices[i]; + if (idx < 0 || static_cast(idx) >= backend.attachments.size()) { + colorRefs[i] = {VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + } else { + colorRefs[i] = {static_cast(idx), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + } + } + + Int32 depthIdx = backend.attachmentIndex[static_cast(FramebufferAttachmentType::Depth)]; + if (depthIdx < 0) { + depthIdx = backend.attachmentIndex[static_cast(FramebufferAttachmentType::Stencil)]; + } + Bool useDepth = depthIdx >= 0; + VkAttachmentReference depthRef{}; + if (useDepth) { + depthRef.attachment = static_cast(depthIdx); + depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + VkSubpassDescription sub{}; + sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sub.colorAttachmentCount = backend.colorAttachmentCount; + sub.pColorAttachments = backend.colorAttachmentCount > 0 ? colorRefs.data() : nullptr; + if (useDepth) sub.pDepthStencilAttachment = &depthRef; + + VkSubpassDependency dep{}; + FillSubpassDependency(dep, backend.colorAttachmentCount > 0, useDepth); + + VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rpci.attachmentCount = descs.size(); + rpci.pAttachments = descs.data(); + rpci.subpassCount = 1; + rpci.pSubpasses = ⊂ + rpci.dependencyCount = 1; + rpci.pDependencies = &dep; + + VkRenderPass rp = VK_NULL_HANDLE; + VK_VERIFY(vkCreateRenderPass(g.ctx->GetDevice(), &rpci, nullptr, &rp), "vkCreateRenderPass"); + return rp; + } + + VkRenderPass GetOrCreateBackendRenderPass(BackendFramebufferObject& backend, GLbitfield clearMask) { + Uint8 key = ClearMaskKey(clearMask); + if (key == 0) return backend.renderPasses[0]; + VkRenderPass& rp = backend.renderPasses[key]; + if (rp != VK_NULL_HANDLE) return rp; + rp = CreateBackendRenderPass(backend, clearMask); + return rp; + } + + VkRenderPass CreateDefaultRenderPass(GLbitfield clearMask) { + VkAttachmentDescription color{}; + color.format = g.swapchain->GetFormat(); + color.samples = VK_SAMPLE_COUNT_1_BIT; + color.loadOp = (clearMask & GL_COLOR_BUFFER_BIT) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + Vector attachments; + attachments.push_back(color); + + VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkAttachmentReference depthRef{}; + Bool hasDepth = g.depthFormat != VK_FORMAT_UNDEFINED; + if (hasDepth) { + VkAttachmentDescription depth{}; + depth.format = g.depthFormat; + depth.samples = VK_SAMPLE_COUNT_1_BIT; + depth.loadOp = + (clearMask & GL_DEPTH_BUFFER_BIT) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + depth.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + if (AspectForFormat(g.depthFormat) & VK_IMAGE_ASPECT_STENCIL_BIT) { + depth.stencilLoadOp = + (clearMask & GL_STENCIL_BUFFER_BIT) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + depth.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + } else { + depth.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + depth.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depth.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments.push_back(depth); + + depthRef = {1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; + } + + VkSubpassDescription sub{}; + sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sub.colorAttachmentCount = 1; + sub.pColorAttachments = &colorRef; + if (hasDepth) sub.pDepthStencilAttachment = &depthRef; + + VkSubpassDependency dep{}; + FillSubpassDependency(dep, true, hasDepth); + + VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rpci.attachmentCount = attachments.size(); + rpci.pAttachments = attachments.data(); + rpci.subpassCount = 1; + rpci.pSubpasses = ⊂ + rpci.dependencyCount = 1; + rpci.pDependencies = &dep; + + VkRenderPass rp = VK_NULL_HANDLE; + VK_VERIFY(vkCreateRenderPass(g.ctx->GetDevice(), &rpci, nullptr, &rp), "vkCreateRenderPass"); + return rp; + } + + Uint64 ComputeDefaultRenderPassCompatHash() { + Vector compatData; + Bool hasDepth = g.depthFormat != VK_FORMAT_UNDEFINED; + compatData.reserve(6); + compatData.push_back(static_cast(hasDepth ? 2 : 1)); + compatData.push_back(static_cast(g.swapchain->GetFormat())); + if (hasDepth) compatData.push_back(static_cast(g.depthFormat)); + compatData.push_back(1); // color attachment count + compatData.push_back(0); // color attachment index + compatData.push_back(static_cast(hasDepth ? 1 : -1)); + return XXH64(compatData.data(), compatData.size() * sizeof(Uint64), 0xA11CE5EDu); + } + + Uint64 GetActiveRenderPassCompatHash() { + if (g.activeIsDefault) return g.defaultRenderPassCompatHash; + if (g.activeBackendFBO) return g.activeBackendFBO->renderPassCompatHash; + return 0; + } + + Bool DefaultFboColorWritesEnabled(const SharedPtr& fbo) { + if (!fbo) return true; + const auto& drawBuffers = fbo->GetDrawBuffers(); + for (Uint32 i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) { + if (drawBuffers[i] != FramebufferAttachmentType::None) return true; + } + return false; + } + + VkRenderPass GetOrCreateDefaultRenderPass(GLbitfield clearMask) { + Uint8 key = ClearMaskKey(clearMask); + if (key == 0) return g.renderPasses[0]; + VkRenderPass& rp = g.renderPasses[key]; + if (rp != VK_NULL_HANDLE) return rp; + rp = CreateDefaultRenderPass(clearMask); + return rp; + } + + void CreateImage(VkExtent3D extent, VkFormat format, VkImageUsageFlags usage, VkImage& image, + VkDeviceMemory& memory, Uint32 mipLevels = 1) { + VkImageCreateInfo ici{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ici.imageType = VK_IMAGE_TYPE_2D; + ici.format = format; + ici.extent = extent; + ici.mipLevels = mipLevels; + ici.arrayLayers = 1; + ici.samples = VK_SAMPLE_COUNT_1_BIT; + ici.tiling = VK_IMAGE_TILING_OPTIMAL; + ici.usage = usage; + ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VK_VERIFY(vkCreateImage(g.ctx->GetDevice(), &ici, nullptr, &image), "vkCreateImage"); + + VkMemoryRequirements req{}; + vkGetImageMemoryRequirements(g.ctx->GetDevice(), image, &req); + VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + mai.allocationSize = req.size; + mai.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_VERIFY(vkAllocateMemory(g.ctx->GetDevice(), &mai, nullptr, &memory), "vkAllocateMemory image"); + VK_VERIFY(vkBindImageMemory(g.ctx->GetDevice(), image, memory, 0), "vkBindImageMemory"); + } + + VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, Uint32 baseLevel, + Uint32 levelCount) { + VkImageViewCreateInfo ivci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + ivci.image = image; + ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; + ivci.format = format; + ivci.subresourceRange.aspectMask = aspect; + ivci.subresourceRange.baseMipLevel = baseLevel; + ivci.subresourceRange.levelCount = levelCount; + ivci.subresourceRange.baseArrayLayer = 0; + ivci.subresourceRange.layerCount = 1; + VkImageView view = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(g.ctx->GetDevice(), &ivci, nullptr, &view), "vkCreateImageView"); + return view; + } + + VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect) { + return CreateImageView(image, format, aspect, 0, 1); + } + + VkImageView GetTextureViewForLevel(TextureResource& res, Uint32 level, VkImageAspectFlags aspect) { + if (level == 0) return res.view; + if (level >= res.mipLevels) { + MGLOG_W("Requested mip level %u out of range (mipLevels=%u), using level 0", level, res.mipLevels); + return res.view; + } + auto it = res.mipViews.find(level); + if (it != res.mipViews.end()) return it->second; + VkImageView view = CreateImageView(res.image, res.format, aspect, level, 1); + res.mipViews[level] = view; + return view; + } + + VkImageView GetTextureViewForSampling(TextureResource& res, Uint32 level, VkImageAspectFlags aspect) { + VkImageAspectFlags fullAspect = AspectForFormat(res.format); + if (level == 0 && aspect == fullAspect) return res.view; + if (level >= res.mipLevels) { + MGLOG_W("Requested mip level %u out of range (mipLevels=%u), using level 0", level, res.mipLevels); + return res.view; + } + constexpr Uint32 kSampleViewKeyFlag = 0x80000000u; + Uint32 key = kSampleViewKeyFlag | (level & 0xFFFFu) | ((static_cast(aspect) & 0xFFFFu) << 16); + auto it = res.mipViews.find(key); + if (it != res.mipViews.end()) return it->second; + VkImageView view = CreateImageView(res.image, res.format, aspect, level, 1); + res.mipViews[key] = view; + return view; + } + + void CmdTransitionImageLayout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, + VkImageLayout newLayout, VkImageAspectFlags aspect) { + VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = aspect; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = 0; + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = 0; + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } + + vkCmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + void SubmitImmediate(const std::function& record) { + VkCommandBufferAllocateInfo abci{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + abci.commandPool = g.commandPool; + abci.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + abci.commandBufferCount = 1; + VkCommandBuffer cmd = VK_NULL_HANDLE; + VK_VERIFY(vkAllocateCommandBuffers(g.ctx->GetDevice(), &abci, &cmd), "vkAllocateCommandBuffers"); + + VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VK_VERIFY(vkBeginCommandBuffer(cmd, &bi), "vkBeginCommandBuffer"); + record(cmd); + VK_VERIFY(vkEndCommandBuffer(cmd), "vkEndCommandBuffer"); + + VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.commandBufferCount = 1; + si.pCommandBuffers = &cmd; + VK_VERIFY(vkQueueSubmit(g.ctx->GetGraphicsQueue(), 1, &si, VK_NULL_HANDLE), "vkQueueSubmit"); + vkQueueWaitIdle(g.ctx->GetGraphicsQueue()); + + vkFreeCommandBuffers(g.ctx->GetDevice(), g.commandPool, 1, &cmd); + } + + void EnsureImageLayout(VkImage image, VkImageLayout& currentLayout, VkImageLayout desiredLayout, + VkImageAspectFlags aspect) { + if (currentLayout == desiredLayout) return; + SubmitImmediate([&](VkCommandBuffer cmd) { + CmdTransitionImageLayout(cmd, image, currentLayout, desiredLayout, aspect); + }); + currentLayout = desiredLayout; + } + + void EnsureImageLayoutForDescriptor(TextureResource& res, VkImageAspectFlags aspect, + VkImageLayout desiredLayout) { + if (res.image == VK_NULL_HANDLE) return; + if (res.layout == desiredLayout) return; + if (!g.cmdBufferBegun) { + EnsureImageLayout(res.image, res.layout, desiredLayout, aspect); + return; + } + + auto& frame = *g.frames[g.currentFrame]; + Bool wasRecording = g.recording; + if (g.recording) { + vkCmdEndRenderPass(frame.CommandBuffer); + g.recording = false; + g.recordingRenderPass = VK_NULL_HANDLE; + g.recordingFramebuffer = VK_NULL_HANDLE; + } + + CmdTransitionImageLayout(frame.CommandBuffer, res.image, res.layout, desiredLayout, aspect); + res.layout = desiredLayout; + + if (wasRecording) { + if (g.activeIsDefault) { + g.activeRenderPass = GetOrCreateDefaultRenderPass(0); + } else if (g.activeBackendFBO) { + g.activeRenderPass = GetOrCreateBackendRenderPass(*g.activeBackendFBO, 0); + } + if (g.activeRenderPass != VK_NULL_HANDLE) { + BeginRenderPass(0, nullptr); + } + } + } + + void UploadTexture(ITextureObject* tex, TextureResource& res, bool force) { + if (!tex) return; + if (tex->GetStorageType() != TextureStorageType::Mipmap) return; + auto* mip = dynamic_cast(tex); + if (!mip) return; + + TextureUploadTarget target = TextureUploadTarget::Texture2D; + if (tex->GetTarget() == TextureTarget::Texture2D) { + target = TextureUploadTarget::Texture2D; + } + + if (!force && !mip->IsStorageDirty(target, 0)) return; + auto size = mip->GetMipmapByteSize(target, 0); + if (size == 0) return; + + void* data = mip->MapMipmapData(target, 0); + if (!data) return; + + BufferResource staging{}; + CreateBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging); + Memcpy(staging.mapped, data, size); + + VkExtent3D extent = res.extent; + VkImageAspectFlags aspect = AspectForFormat(res.format); + VkImageLayout sampledLayout = SampledLayoutForFormat(res.format); + + SubmitImmediate([&](VkCommandBuffer cmd) { + CmdTransitionImageLayout(cmd, res.image, res.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, aspect); + + VkBufferImageCopy region{}; + region.imageSubresource.aspectMask = aspect; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageExtent = extent; + vkCmdCopyBufferToImage(cmd, staging.buffer, res.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ®ion); + + CmdTransitionImageLayout(cmd, res.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, sampledLayout, aspect); + }); + + res.layout = sampledLayout; + mip->MarkStorageDirty(target, 0, false); + + DestroyBuffer(staging); + } + + void CopyImageMipLevels(const TextureResource& src, TextureResource& dst) { + VkImageAspectFlags aspect = AspectForFormat(src.format); + VkImageLayout finalLayout = + (src.layout != VK_IMAGE_LAYOUT_UNDEFINED) ? src.layout : SampledLayoutForFormat(src.format); + + SubmitImmediate([&](VkCommandBuffer cmd) { + CmdTransitionImageLayout(cmd, src.image, src.layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, aspect); + CmdTransitionImageLayout(cmd, dst.image, dst.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, aspect); + + Uint32 copyLevels = std::min(src.mipLevels, dst.mipLevels); + for (Uint32 level = 0; level < copyLevels; ++level) { + VkImageCopy region{}; + region.srcSubresource.aspectMask = aspect; + region.srcSubresource.mipLevel = level; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.dstSubresource = region.srcSubresource; + region.extent = {std::max(1u, dst.extent.width >> level), std::max(1u, dst.extent.height >> level), + 1}; + vkCmdCopyImage(cmd, src.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + } + + CmdTransitionImageLayout(cmd, dst.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, finalLayout, aspect); + }); + + dst.layout = finalLayout; + } + + TextureResource& SyncTexture(ITextureObject* tex) { + if (!tex || !tex->IsComplete()) return g.defaultTexture; + auto& res = g.textures[tex]; + VkFormat fmt = ToVkFormat(tex->GetFormat()); + auto size = tex->GetBaseSize(); + VkExtent3D extent{static_cast(std::max(1, size.x())), static_cast(std::max(1, size.y())), + 1}; + Uint32 mipLevels = 1; + if (tex->GetStorageType() == TextureStorageType::Mipmap) { + auto* mip = dynamic_cast(tex); + if (mip) { + mipLevels = std::max(1, static_cast(mip->GetMipmapLevelCount())); + } + } + + Bool needCreate = !res.valid || res.format != fmt || res.extent.width != extent.width || + res.extent.height != extent.height || res.mipLevels != mipLevels; + if (needCreate) { + TextureResource oldRes = res; + Bool canCopy = oldRes.valid && oldRes.image != VK_NULL_HANDLE && oldRes.format == fmt && + oldRes.extent.width == extent.width && oldRes.extent.height == extent.height && + oldRes.layout != VK_IMAGE_LAYOUT_UNDEFINED; + res = TextureResource{}; + res.format = fmt; + res.extent = extent; + res.mipLevels = mipLevels; + CreateImage(extent, fmt, UsageForTextureFormat(fmt), res.image, res.memory, res.mipLevels); + res.view = CreateImageView(res.image, fmt, AspectForFormat(fmt)); + res.layout = VK_IMAGE_LAYOUT_UNDEFINED; + res.valid = true; + res.paramsVersion = tex->GetTextureParamsVersion(); + + if (canCopy) { + CopyImageMipLevels(oldRes, res); + } + if (oldRes.image != VK_NULL_HANDLE || oldRes.view != VK_NULL_HANDLE || + oldRes.memory != VK_NULL_HANDLE) { + TrashImageResource(oldRes); + } + } + + UploadTexture(tex, res, false); + + if (tex->GetTextureParamsVersion() != res.paramsVersion) { + res.paramsVersion = tex->GetTextureParamsVersion(); + } + return res; + } + + RenderbufferResource& SyncRenderbuffer(RenderbufferObject* rbo) { + auto& res = g.renderbuffers[rbo]; + if (!rbo || !rbo->IsAllocated()) return res; + VkFormat fmt = ToVkFormat(rbo->GetInternalFormat()); + VkExtent2D extent{static_cast(std::max(1, rbo->GetWidth())), + static_cast(std::max(1, rbo->GetHeight()))}; + + Bool needCreate = !res.valid || res.format != fmt || res.extent.width != extent.width || + res.extent.height != extent.height; + if (needCreate) { + DestroyRenderbuffer(res); + res.format = fmt; + res.extent = extent; + VkExtent3D imgExtent{extent.width, extent.height, 1}; + CreateImage(imgExtent, fmt, UsageForRenderbufferFormat(fmt), res.image, res.memory, 1); + res.view = CreateImageView(res.image, fmt, AspectForFormat(fmt)); + res.layout = VK_IMAGE_LAYOUT_UNDEFINED; + res.valid = true; + } + return res; + } + + Bool SameAttachmentObject(const FramebufferAttachmentObject& a, const FramebufferAttachmentObject& b) { + if (a.IsTexture() && b.IsTexture()) { + return a.GetTexture().get() == b.GetTexture().get(); + } + if (a.IsRenderbuffer() && b.IsRenderbuffer()) { + return a.GetRenderbuffer().get() == b.GetRenderbuffer().get(); + } + return false; + } + + void DestroyBackendFramebuffer(BackendFramebufferObject& backend) { + if (backend.framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(g.ctx->GetDevice(), backend.framebuffer, nullptr); + backend.framebuffer = VK_NULL_HANDLE; + } + for (auto& rp : backend.renderPasses) { + if (rp != VK_NULL_HANDLE) { + vkDestroyRenderPass(g.ctx->GetDevice(), rp, nullptr); + rp = VK_NULL_HANDLE; + } + } + backend.attachments.clear(); + backend.attachmentIndex.fill(-1); + backend.extent = {0, 0}; + backend.colorAttachmentCount = 0; + backend.hasDepth = false; + backend.depthFormat = VK_FORMAT_UNDEFINED; + backend.renderPassCompatHash = 0; + backend.configHash = 0; + backend.drawBufferAttachmentIndices.fill(-1); + } + + Bool SyncFramebufferObject(BackendFramebufferObject& backend, const SharedPtr& stateFBO) { + if (!stateFBO) return false; + + if (!stateFBO->CheckCompleteness()) { + MGLOG_E("Framebuffer %u is incomplete, skipping Vulkan sync", stateFBO->GetExternalIndex()); + return false; + } + + const auto& allAttachments = stateFBO->GetAllAttachmentObjects(); + const auto& drawBuffers = stateFBO->GetDrawBuffers(); + + const auto& depthAtt = allAttachments[static_cast(FramebufferAttachmentType::Depth)]; + const auto& stencilAtt = allAttachments[static_cast(FramebufferAttachmentType::Stencil)]; + Bool hasDepth = depthAtt.IsValid(); + Bool hasStencil = stencilAtt.IsValid(); + Bool mergeDepthStencil = hasDepth && hasStencil && SameAttachmentObject(depthAtt, stencilAtt); + if (hasDepth && hasStencil && !mergeDepthStencil) { + MGLOG_W("Framebuffer %u has separate depth/stencil attachments; using depth only", + stateFBO->GetExternalIndex()); + hasStencil = false; + } + + Vector newAttachments; + Array(FramebufferAttachmentType::FramebufferAttachmentTypeCount)> newIndex = {}; + newIndex.fill(-1); + VkExtent2D extent{0, 0}; + Bool extentSet = false; + Bool valid = true; + + auto addAttachment = [&](FramebufferAttachmentType type, const FramebufferAttachmentObject& att) { + if (!att.IsValid()) return; + IntVec3 size = att.GetSize(); + if (size.x() <= 0 || size.y() <= 0) { + MGLOG_E("Framebuffer %u attachment %d has invalid size", stateFBO->GetExternalIndex(), + static_cast(type)); + valid = false; + return; + } + if (!extentSet) { + extent = {static_cast(size.x()), static_cast(size.y())}; + extentSet = true; + } else if (extent.width != static_cast(size.x()) || + extent.height != static_cast(size.y())) { + MGLOG_E("Framebuffer %u attachment sizes mismatch", stateFBO->GetExternalIndex()); + valid = false; + return; + } + + if (att.IsTexture()) { + auto tex = att.GetTexture(); + if (!tex || !tex->IsComplete()) { + MGLOG_E("Framebuffer %u texture attachment incomplete", stateFBO->GetExternalIndex()); + valid = false; + return; + } + auto& texRes = SyncTexture(tex.get()); + VkFormat fmt = texRes.format; + VkImageAspectFlags aspect = AspectForFormat(fmt); + VkImageLayout finalLayout = LayoutForFramebufferAttachment(type, fmt); + EnsureImageLayout(texRes.image, texRes.layout, finalLayout, aspect); + VkImageView view = GetTextureViewForLevel(texRes, att.GetTextureLevel(), aspect); + + AttachmentInfo info{}; + info.type = type; + info.view = view; + info.format = fmt; + info.aspect = aspect; + info.finalLayout = finalLayout; + info.texture = tex.get(); + newIndex[static_cast(type)] = static_cast(newAttachments.size()); + newAttachments.push_back(info); + } else if (att.IsRenderbuffer()) { + auto rbo = att.GetRenderbuffer(); + if (!rbo || !rbo->IsAllocated()) { + MGLOG_E("Framebuffer %u renderbuffer attachment incomplete", stateFBO->GetExternalIndex()); + valid = false; + return; + } + auto& rbRes = SyncRenderbuffer(rbo.get()); + if (!rbRes.valid) { + MGLOG_E("Framebuffer %u renderbuffer backend invalid", stateFBO->GetExternalIndex()); + valid = false; + return; + } + VkFormat fmt = rbRes.format; + VkImageAspectFlags aspect = AspectForFormat(fmt); + VkImageLayout finalLayout = LayoutForFramebufferAttachment(type, fmt); + EnsureImageLayout(rbRes.image, rbRes.layout, finalLayout, aspect); + + AttachmentInfo info{}; + info.type = type; + info.view = rbRes.view; + info.format = fmt; + info.aspect = aspect; + info.finalLayout = finalLayout; + info.renderbuffer = rbo.get(); + newIndex[static_cast(type)] = static_cast(newAttachments.size()); + newAttachments.push_back(info); + } + }; + + for (int i = 0; i < 32; ++i) { + FramebufferAttachmentType type = + static_cast(static_cast(FramebufferAttachmentType::Color0) + i); + addAttachment(type, allAttachments[static_cast(type)]); + } + + if (hasDepth) { + addAttachment(FramebufferAttachmentType::Depth, depthAtt); + } else if (hasStencil) { + addAttachment(FramebufferAttachmentType::Stencil, stencilAtt); + } + + if (!valid || newAttachments.empty() || !extentSet) { + return false; + } + + Uint32 nEffectiveBuffers = 0; + for (Uint32 i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) { + if (drawBuffers[i] != FramebufferAttachmentType::None) nEffectiveBuffers = i + 1; + } + + Array drawAttachmentIndices = {}; + drawAttachmentIndices.fill(-1); + for (Uint32 i = 0; i < nEffectiveBuffers; ++i) { + auto buf = drawBuffers[i]; + if (buf == FramebufferAttachmentType::None) { + continue; + } + Int32 idx = newIndex[static_cast(buf)]; + if (idx < 0) { + MGLOG_W("Framebuffer %u draw buffer %u refers to missing attachment", stateFBO->GetExternalIndex(), + i); + continue; + } + drawAttachmentIndices[i] = idx; + } + + Bool useDepthAttachment = hasDepth || hasStencil; + if (useDepthAttachment) { + FramebufferAttachmentType depthType = + hasDepth ? FramebufferAttachmentType::Depth : FramebufferAttachmentType::Stencil; + Int32 idx = newIndex[static_cast(depthType)]; + if (idx < 0) { + useDepthAttachment = false; + } + } + + Vector compatData; + compatData.reserve(4 + newAttachments.size() * 2 + nEffectiveBuffers); + compatData.push_back(static_cast(newAttachments.size())); + for (const auto& info : newAttachments) { + compatData.push_back(static_cast(info.format)); + compatData.push_back(static_cast(info.aspect)); + } + compatData.push_back(static_cast(nEffectiveBuffers)); + for (Uint32 i = 0; i < nEffectiveBuffers; ++i) { + compatData.push_back(static_cast(drawAttachmentIndices[i])); + } + Int32 depthIdx = -1; + if (useDepthAttachment) { + FramebufferAttachmentType depthType = + hasDepth ? FramebufferAttachmentType::Depth : FramebufferAttachmentType::Stencil; + depthIdx = newIndex[static_cast(depthType)]; + } + compatData.push_back(static_cast(depthIdx)); + Uint64 compatHash = XXH64(compatData.data(), compatData.size() * sizeof(Uint64), 0xA11CE5EDu); + + Vector hashData; + hashData.reserve(8 + newAttachments.size() * 3 + FramebufferObject::MAX_DRAW_BUFFERS); + hashData.push_back(static_cast(extent.width) << 32 | extent.height); + hashData.push_back(static_cast(nEffectiveBuffers)); + hashData.push_back(static_cast(useDepthAttachment)); + for (Uint32 i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) { + hashData.push_back(static_cast(drawBuffers[i])); + } + for (const auto& info : newAttachments) { + hashData.push_back(static_cast(info.type)); + hashData.push_back(static_cast(info.format)); + hashData.push_back(reinterpret_cast(info.view)); + } + Uint64 newHash = XXH64(hashData.data(), hashData.size() * sizeof(Uint64), 0xF00DF00Du); + + if (backend.configHash == newHash && backend.IsValid()) { + backend.objectVersion = stateFBO->GetObjectVersion(); + backend.frontendDrawBuffers = drawBuffers; + backend.drawBufferAttachmentIndices = drawAttachmentIndices; + backend.frontendReadBuffer = stateFBO->GetReadBuffer(); + backend.syncedAttachmentVersions = stateFBO->GetAllFramebufferAttachmentVersions(); + return true; + } + + DestroyBackendFramebuffer(backend); + + backend.attachments = std::move(newAttachments); + backend.attachmentIndex = std::move(newIndex); + backend.extent = extent; + backend.colorAttachmentCount = nEffectiveBuffers; + backend.hasDepth = useDepthAttachment; + backend.depthFormat = VK_FORMAT_UNDEFINED; + if (useDepthAttachment) { + FramebufferAttachmentType depthType = + hasDepth ? FramebufferAttachmentType::Depth : FramebufferAttachmentType::Stencil; + Int32 idx = backend.attachmentIndex[static_cast(depthType)]; + if (idx >= 0) { + backend.depthFormat = backend.attachments[static_cast(idx)].format; + } + } + backend.configHash = newHash; + backend.renderPassCompatHash = compatHash; + backend.frontendDrawBuffers = drawBuffers; + backend.drawBufferAttachmentIndices = drawAttachmentIndices; + backend.frontendReadBuffer = stateFBO->GetReadBuffer(); + backend.syncedAttachmentVersions = stateFBO->GetAllFramebufferAttachmentVersions(); + backend.objectVersion = stateFBO->GetObjectVersion(); + + backend.renderPasses[0] = CreateBackendRenderPass(backend, 0); + + Vector views; + views.reserve(backend.attachments.size()); + for (const auto& info : backend.attachments) { + views.push_back(info.view); + } + VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = backend.renderPasses[0]; + fbci.attachmentCount = views.size(); + fbci.pAttachments = views.data(); + fbci.width = extent.width; + fbci.height = extent.height; + fbci.layers = 1; + VK_VERIFY(vkCreateFramebuffer(g.ctx->GetDevice(), &fbci, nullptr, &backend.framebuffer), + "vkCreateFramebuffer"); + + return true; + } + + Bool SyncDrawFramebuffer() { + auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); + auto currentFBO = slot.GetBoundObject(); + if (!currentFBO) { + MGLOG_E("No draw framebuffer bound"); + return false; + } + + if (currentFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { + g.activeIsDefault = true; + g.activeStateFBO = currentFBO; + g.activeBackendFBO = nullptr; + g.activeDefaultColorWrites = DefaultFboColorWritesEnabled(currentFBO); + g.activeRenderPass = g.renderPasses[0]; + if (!g.frames.empty()) { + auto& frame = *g.frames[g.currentFrame]; + g.activeFramebuffer = g.swapchain->GetFramebuffers()[frame.CurrentImageIndex]; + } else { + g.activeFramebuffer = VK_NULL_HANDLE; + } + g.activeExtent = g.swapchain->GetExtent(); + g.activeColorAttachmentCount = 1; + g.activeHasDepth = g.depthView != VK_NULL_HANDLE; + g.activeDepthFormat = g.depthFormat; + return g.activeFramebuffer != VK_NULL_HANDLE; + } + + auto it = g.framebuffers.find(currentFBO); + SharedPtr backend; + if (it == g.framebuffers.end()) { + backend = MakeShared(); + g.framebuffers[currentFBO] = backend; + } else { + backend = it->second; + } + + if (!SyncFramebufferObject(*backend, currentFBO)) return false; + + g.activeIsDefault = false; + g.activeStateFBO = currentFBO; + g.activeBackendFBO = backend; + g.activeDefaultColorWrites = true; + g.activeRenderPass = backend->renderPasses[0]; + g.activeFramebuffer = backend->framebuffer; + g.activeExtent = backend->extent; + g.activeColorAttachmentCount = backend->colorAttachmentCount; + g.activeHasDepth = backend->hasDepth; + g.activeDepthFormat = backend->depthFormat; + return backend->IsValid(); + } + + VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode) { + switch (mode) { + case SamplerWrapMode::ClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case SamplerWrapMode::MirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case SamplerWrapMode::Repeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case SamplerWrapMode::ClampToBorder: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + case SamplerWrapMode::MirrorClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + default: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } + } + + VkFilter ToVkFilter(SamplerFilterMode mode) { + switch (mode) { + case SamplerFilterMode::Nearest: + return VK_FILTER_NEAREST; + case SamplerFilterMode::Linear: + return VK_FILTER_LINEAR; + default: + return VK_FILTER_LINEAR; + } + } + + VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode) { + switch (mode) { + case SamplerMipmapMode::Nearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case SamplerMipmapMode::Linear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + case SamplerMipmapMode::None: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + default: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + } + } + + VkCompareOp ToVkCompareOp(SamplerCompareFunc func) { + switch (func) { + case SamplerCompareFunc::Never: + return VK_COMPARE_OP_NEVER; + case SamplerCompareFunc::Less: + return VK_COMPARE_OP_LESS; + case SamplerCompareFunc::Equal: + return VK_COMPARE_OP_EQUAL; + case SamplerCompareFunc::LessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + case SamplerCompareFunc::Greater: + return VK_COMPARE_OP_GREATER; + case SamplerCompareFunc::NotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + case SamplerCompareFunc::GreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + case SamplerCompareFunc::Always: + return VK_COMPARE_OP_ALWAYS; + default: + return VK_COMPARE_OP_ALWAYS; + } + } + + SamplerResource& SyncSampler(SamplerObject* sampler) { + if (!sampler) return g.defaultSampler; + auto& res = g.samplers[sampler]; + Uint16 version = sampler->GetVersion(); + if (res.sampler != VK_NULL_HANDLE && res.version == version) return res; + + if (res.sampler != VK_NULL_HANDLE) { + vkDestroySampler(g.ctx->GetDevice(), res.sampler, nullptr); + res.sampler = VK_NULL_HANDLE; + } + + auto params = sampler->GetAllSamplerParameters(); + VkSamplerCreateInfo sci{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + sci.magFilter = ToVkFilter(params.magFilter); + sci.minFilter = ToVkFilter(params.minFilter); + sci.mipmapMode = ToVkMipmapMode(params.mipmapMode); + sci.addressModeU = ToVkAddressMode(params.wrapS); + sci.addressModeV = ToVkAddressMode(params.wrapT); + sci.addressModeW = ToVkAddressMode(params.wrapR); + sci.mipLodBias = params.lodBias; + sci.minLod = params.minLod; + sci.maxLod = params.maxLod; + sci.anisotropyEnable = VK_FALSE; + sci.compareEnable = params.compareMode == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE; + sci.compareOp = ToVkCompareOp(params.compareFunc); + VK_VERIFY(vkCreateSampler(g.ctx->GetDevice(), &sci, nullptr, &res.sampler), "vkCreateSampler"); + + res.version = version; + res.params = params; + return res; + } + + VkPrimitiveTopology ToVkTopology(GLenum mode) { + switch (mode) { + case GL_TRIANGLES: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case GL_TRIANGLE_STRIP: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + case GL_TRIANGLE_FAN: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + case GL_LINES: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case GL_LINE_STRIP: + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case GL_POINTS: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + default: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + } + } + + VkCullModeFlags ToVkCullMode(CullFaceMode mode) { + switch (mode) { + case CullFaceMode::Front: + return VK_CULL_MODE_FRONT_BIT; + case CullFaceMode::Back: + return VK_CULL_MODE_BACK_BIT; + case CullFaceMode::FrontAndBack: + return VK_CULL_MODE_FRONT_AND_BACK; + default: + return VK_CULL_MODE_BACK_BIT; + } + } + + VkCompareOp ToVkCompareOp(DepthTestFunc func) { + switch (func) { + case DepthTestFunc::Never: + return VK_COMPARE_OP_NEVER; + case DepthTestFunc::Less: + return VK_COMPARE_OP_LESS; + case DepthTestFunc::Equal: + return VK_COMPARE_OP_EQUAL; + case DepthTestFunc::LessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + case DepthTestFunc::Greater: + return VK_COMPARE_OP_GREATER; + case DepthTestFunc::NotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + case DepthTestFunc::GreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + case DepthTestFunc::Always: + return VK_COMPARE_OP_ALWAYS; + default: + return VK_COMPARE_OP_LESS_OR_EQUAL; + } + } + + VkBlendFactor ToVkBlendFactor(BlendFactor factor) { + switch (factor) { + case BlendFactor::Zero: + return VK_BLEND_FACTOR_ZERO; + case BlendFactor::One: + return VK_BLEND_FACTOR_ONE; + case BlendFactor::SrcColor: + return VK_BLEND_FACTOR_SRC_COLOR; + case BlendFactor::OneMinusSrcColor: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case BlendFactor::DstColor: + return VK_BLEND_FACTOR_DST_COLOR; + case BlendFactor::OneMinusDstColor: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case BlendFactor::SrcAlpha: + return VK_BLEND_FACTOR_SRC_ALPHA; + case BlendFactor::OneMinusSrcAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case BlendFactor::DstAlpha: + return VK_BLEND_FACTOR_DST_ALPHA; + case BlendFactor::OneMinusDstAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case BlendFactor::ConstantColor: + return VK_BLEND_FACTOR_CONSTANT_COLOR; + case BlendFactor::OneMinusConstantColor: + return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; + case BlendFactor::ConstantAlpha: + return VK_BLEND_FACTOR_CONSTANT_ALPHA; + case BlendFactor::OneMinusConstantAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + default: + return VK_BLEND_FACTOR_ONE; + } + } + + Uint32 SizeOfDataType(DataType type) { + switch (type) { + case DataType::Int8: + case DataType::Uint8: + return 1; + case DataType::Int16: + case DataType::Uint16: + case DataType::Float16: + return 2; + case DataType::Int32: + case DataType::Uint32: + case DataType::Float32: + case DataType::Fixed32: + return 4; + case DataType::Float64: + return 8; + default: + return 4; + } + } + + VkFormat ToVkVertexFormat(DataType type, int size, Bool normalized, Bool isInteger) { + if (size < 1 || size > 4) return VK_FORMAT_UNDEFINED; + + auto pick = [&](VkFormat r, VkFormat rg, VkFormat rgb, VkFormat rgba) { + switch (size) { + case 1: + return r; + case 2: + return rg; + case 3: + return rgb; + case 4: + return rgba; + default: + return VK_FORMAT_UNDEFINED; + } + }; + + if (isInteger) { + switch (type) { + case DataType::Int8: + return pick(VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT); + case DataType::Uint8: + return pick(VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT); + case DataType::Int16: + return pick(VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, + VK_FORMAT_R16G16B16A16_SINT); + case DataType::Uint16: + return pick(VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, + VK_FORMAT_R16G16B16A16_UINT); + case DataType::Int32: + return pick(VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, + VK_FORMAT_R32G32B32A32_SINT); + case DataType::Uint32: + return pick(VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, + VK_FORMAT_R32G32B32A32_UINT); + default: + return VK_FORMAT_UNDEFINED; + } + } + + if (normalized) { + switch (type) { + case DataType::Int8: + return pick(VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, + VK_FORMAT_R8G8B8A8_SNORM); + case DataType::Uint8: + return pick(VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM); + case DataType::Int16: + return pick(VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, + VK_FORMAT_R16G16B16A16_SNORM); + case DataType::Uint16: + return pick(VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, + VK_FORMAT_R16G16B16A16_UNORM); + default: + break; + } + } + + switch (type) { + case DataType::Float16: + return pick(VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, + VK_FORMAT_R16G16B16A16_SFLOAT); + case DataType::Float32: + case DataType::Fixed32: + return pick(VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, + VK_FORMAT_R32G32B32A32_SFLOAT); + case DataType::Float64: + return pick(VK_FORMAT_R64_SFLOAT, VK_FORMAT_R64G64_SFLOAT, VK_FORMAT_R64G64B64_SFLOAT, + VK_FORMAT_R64G64B64A64_SFLOAT); + case DataType::Int8: + return pick(VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT); + case DataType::Uint8: + return pick(VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT); + case DataType::Int16: + return pick(VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, + VK_FORMAT_R16G16B16A16_SINT); + case DataType::Uint16: + return pick(VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, + VK_FORMAT_R16G16B16A16_UINT); + case DataType::Int32: + return pick(VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, + VK_FORMAT_R32G32B32A32_SINT); + case DataType::Uint32: + return pick(VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, + VK_FORMAT_R32G32B32A32_UINT); + default: + return VK_FORMAT_UNDEFINED; + } + } + + VkIndexType ToVkIndexType(GLenum type) { + switch (type) { + case GL_UNSIGNED_SHORT: + return VK_INDEX_TYPE_UINT16; + case GL_UNSIGNED_INT: + return VK_INDEX_TYPE_UINT32; + default: + return VK_INDEX_TYPE_UINT16; + } + } + + Uint32 IndexTypeSize(GLenum type) { + switch (type) { + case GL_UNSIGNED_SHORT: + return 2; + case GL_UNSIGNED_INT: + return 4; + case GL_UNSIGNED_BYTE: + return 1; + default: + return 2; + } + } + + Uint64 ComputeProgramSpvHash(ProgramObject* program) { + XXH64_state_t* state = XXH64_createState(); + XXH64_reset(state, 0xC0FFEEu); + auto& spirvs = program->GetGeneratedSpirv(); + for (const auto& spv : spirvs) { + if (spv.empty()) continue; + XXH64_update(state, spv.data(), spv.size() * sizeof(Uint)); + } + Uint64 hash = XXH64_digest(state); + XXH64_freeState(state); + return hash; + } + + void DestroyProgramResource(ProgramResource& res) { + auto device = g.ctx->GetDevice(); + for (auto& kv : res.pipelines) { + if (kv.second != VK_NULL_HANDLE) vkDestroyPipeline(device, kv.second, nullptr); + } + res.pipelines.clear(); + + for (auto& pool : res.uboPools) { + for (auto& buf : pool.buffers) { + DestroyBuffer(buf); + } + pool.buffers.clear(); + pool.cursor = 0; + } + res.uboPools.clear(); + + if (res.pipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(device, res.pipelineLayout, nullptr); + res.pipelineLayout = VK_NULL_HANDLE; + } + if (res.setLayout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(device, res.setLayout, nullptr); + res.setLayout = VK_NULL_HANDLE; + } + } + + VkShaderStageFlagBits ToVkShaderStage(ShaderStage stage) { + switch (stage) { + case ShaderStage::Vertex: + return VK_SHADER_STAGE_VERTEX_BIT; + case ShaderStage::Fragment: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case ShaderStage::Geometry: + return VK_SHADER_STAGE_GEOMETRY_BIT; + case ShaderStage::TessControl: + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case ShaderStage::TessEval: + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + case ShaderStage::Compute: + return VK_SHADER_STAGE_COMPUTE_BIT; + default: + return VK_SHADER_STAGE_ALL_GRAPHICS; + } + } + + String NormalizeUniformName(const String& name) { + auto pos = name.find('['); + if (pos == String::npos) return name; + return name.substr(0, pos); + } + + Int FindUniformLocationByName(ProgramObject* program, const String& name) { + if (!program) return -1; + Int loc = program->GetUniformLocation(name); + if (loc >= 0) return loc; + String norm = NormalizeUniformName(name); + if (norm != name) { + loc = program->GetUniformLocation(norm); + if (loc >= 0) return loc; + } + Uint maxLoc = program->GetMaxUniformLocation(); + for (Uint i = 0; i < maxLoc; ++i) { + const String& uName = program->GetUniformName(i); + if (NormalizeUniformName(uName) == norm) return static_cast(i); + } + return -1; + } + + void ReflectProgramResources(ProgramObject* program, ProgramResource& out) { + UnorderedMap uniformMap; + UnorderedMap samplerMap; + + auto& shaders = program->GetAttachedShaders(); + auto& spirvs = program->GetGeneratedSpirv(); + for (SizeT i = 0; i < spirvs.size(); ++i) { + auto& spv = spirvs[i]; + if (spv.empty()) continue; + + VkShaderStageFlags stageFlag = VK_SHADER_STAGE_ALL_GRAPHICS; + if (i < shaders.size()) stageFlag = ToVkShaderStage(shaders[i]->GetShaderStage()); + + spvc_context context = nullptr; + spvc_context_create(&context); + spvc_parsed_ir ir = nullptr; + spvc_context_parse_spirv(context, spv.data(), spv.size(), &ir); + spvc_compiler compiler = nullptr; + spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, + &compiler); + spvc_resources resources = nullptr; + spvc_compiler_create_shader_resources(compiler, &resources); + + const spvc_reflected_resource* list = nullptr; + size_t count = 0; + + spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count); + for (size_t r = 0; r < count; ++r) { + if (spvc_compiler_has_decoration(compiler, list[r].id, SpvDecorationBuiltIn)) continue; + String name = list[r].name ? list[r].name : ""; + Uint32 binding = spvc_compiler_get_decoration(compiler, list[r].id, SpvDecorationBinding); + Uint32 set = spvc_compiler_get_decoration(compiler, list[r].id, SpvDecorationDescriptorSet); + if (set != 0) continue; + + auto& info = uniformMap[name]; + info.name = name; + info.binding = binding; + info.set = set; + info.stages |= stageFlag; + } + + spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count); + for (size_t r = 0; r < count; ++r) { + if (spvc_compiler_has_decoration(compiler, list[r].id, SpvDecorationBuiltIn)) continue; + String name = list[r].name ? list[r].name : ""; + String normName = NormalizeUniformName(name); + Uint32 binding = spvc_compiler_get_decoration(compiler, list[r].id, SpvDecorationBinding); + Uint32 set = spvc_compiler_get_decoration(compiler, list[r].id, SpvDecorationDescriptorSet); + if (set != 0) continue; + + auto& info = samplerMap[normName]; + info.name = normName; + info.binding = binding; + info.set = set; + info.stages |= stageFlag; + } + + spvc_context_destroy(context); + } + + out.uniformBindings.clear(); + out.samplerBindings.clear(); + out.samplerBindingByName.clear(); + + for (auto& [name, info] : uniformMap) { + if (name == MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) { + out.uboBinding = static_cast(info.binding); + continue; + } + auto blockIndex = program->GetUniformBlockIndex(name.c_str()); + info.blockIndex = blockIndex; + out.uniformBindings.push_back(info); + } + + for (auto& [name, info] : samplerMap) { + out.samplerBindings.push_back(info); + out.samplerBindingByName[name] = info.binding; + } + } + + constexpr Uint32 kBaseDescriptorPoolSets = 256; + + DescriptorPoolConfig MakeDescriptorPoolConfig(Uint32 requiredUniform, Uint32 requiredSampler) { + DescriptorPoolConfig cfg{}; + cfg.maxSets = kBaseDescriptorPoolSets; + cfg.uniformCount = std::max(kBaseDescriptorPoolSets, requiredUniform); + cfg.samplerCount = std::max(kBaseDescriptorPoolSets, requiredSampler); + return cfg; + } + + VkDescriptorPool CreateDescriptorPool(const DescriptorPoolConfig& cfg) { + VkDescriptorPoolSize sizes[2] = {}; + sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + sizes[0].descriptorCount = cfg.uniformCount; + sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + sizes[1].descriptorCount = cfg.samplerCount; + + VkDescriptorPoolCreateInfo dpci{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + dpci.maxSets = cfg.maxSets; + dpci.poolSizeCount = 2; + dpci.pPoolSizes = sizes; + VkDescriptorPool pool = VK_NULL_HANDLE; + VK_VERIFY(vkCreateDescriptorPool(g.ctx->GetDevice(), &dpci, nullptr, &pool), "vkCreateDescriptorPool"); + return pool; + } + + void DestroyDescriptorPools() { + auto device = g.ctx->GetDevice(); + for (auto& framePools : g.frameDescriptorPools) { + for (auto pool : framePools.pools) { + if (pool != VK_NULL_HANDLE) vkDestroyDescriptorPool(device, pool, nullptr); + } + framePools.pools.clear(); + framePools.activePool = 0; + framePools.config = {}; + } + g.frameDescriptorPools.clear(); + } + + void ResetFrameDescriptorPools(Uint32 frameIndex) { + if (frameIndex >= g.frameDescriptorPools.size()) return; + auto& framePools = g.frameDescriptorPools[frameIndex]; + for (auto pool : framePools.pools) { + if (pool != VK_NULL_HANDLE) { + VK_VERIFY(vkResetDescriptorPool(g.ctx->GetDevice(), pool, 0), "vkResetDescriptorPool"); + } + } + framePools.activePool = 0; + } + + void ResetProgramUboPools(Uint32 frameIndex) { + for (auto& [_, prog] : g.programs) { + if (frameIndex < prog.uboPools.size()) { + prog.uboPools[frameIndex].cursor = 0; + } + } + } + + VkDescriptorSet AllocateDescriptorSetForDraw(ProgramResource& prog, Uint32 frameIndex) { + if (prog.setLayout == VK_NULL_HANDLE) return VK_NULL_HANDLE; + if (frameIndex >= g.frameDescriptorPools.size()) return VK_NULL_HANDLE; + + auto& framePools = g.frameDescriptorPools[frameIndex]; + if (framePools.config.maxSets == 0) { + framePools.config = MakeDescriptorPoolConfig(prog.uniformDescriptorCount, prog.samplerDescriptorCount); + } + + auto ensurePool = [&]() { + if (framePools.activePool < framePools.pools.size()) return; + DescriptorPoolConfig cfg = framePools.config; + cfg.uniformCount = std::max(cfg.uniformCount, prog.uniformDescriptorCount); + cfg.samplerCount = std::max(cfg.samplerCount, prog.samplerDescriptorCount); + framePools.pools.push_back(CreateDescriptorPool(cfg)); + }; + + for (;;) { + ensurePool(); + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorPool pool = framePools.pools[framePools.activePool]; + VkDescriptorSetAllocateInfo dsai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + dsai.descriptorPool = pool; + dsai.descriptorSetCount = 1; + dsai.pSetLayouts = &prog.setLayout; + VkResult res = vkAllocateDescriptorSets(g.ctx->GetDevice(), &dsai, &set); + if (res == VK_SUCCESS) return set; + if (res == VK_ERROR_OUT_OF_POOL_MEMORY || res == VK_ERROR_FRAGMENTED_POOL) { + framePools.activePool++; + if (framePools.activePool >= framePools.pools.size()) { + framePools.config.maxSets *= 2; + framePools.config.uniformCount = + std::max(framePools.config.uniformCount * 2, prog.uniformDescriptorCount); + framePools.config.samplerCount = + std::max(framePools.config.samplerCount * 2, prog.samplerDescriptorCount); + } + continue; + } + VK_VERIFY(res, "vkAllocateDescriptorSets"); + return VK_NULL_HANDLE; + } + } + + void CreateNullUbo() { + if (g.nullUbo.buffer != VK_NULL_HANDLE) return; + CreateBuffer(256, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, g.nullUbo); + Memset(g.nullUbo.mapped, 0, 256); + } + + ProgramResource& GetProgramResource(ProgramObject* program) { + auto& slot = g.programs[program]; + Uint64 newHash = ComputeProgramSpvHash(program); + if (slot.program && slot.spvHash == newHash) return slot; + + if (slot.program) { + DestroyProgramResource(slot); + slot = ProgramResource{}; + } + + slot.program = program; + slot.spvHash = newHash; + slot.programHash = newHash; + + if (!g.programMgr) g.programMgr = MakeUnique(*g.ctx); + slot.shaderStages = &g.programMgr->CreatePipelineShaderStages(program); + + ReflectProgramResources(program, slot); + + Vector bindings; + if (slot.uboBinding >= 0 && program->GetUBOSize() > 0) { + VkDescriptorSetLayoutBinding b{}; + b.binding = static_cast(slot.uboBinding); + b.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + b.descriptorCount = 1; + b.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + bindings.push_back(b); + slot.uboSize = program->GetUBOSize(); + } + + for (auto& ub : slot.uniformBindings) { + VkDescriptorSetLayoutBinding b{}; + b.binding = ub.binding; + b.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + b.descriptorCount = 1; + b.stageFlags = ub.stages ? ub.stages : VK_SHADER_STAGE_ALL_GRAPHICS; + bindings.push_back(b); + } + + for (auto& sb : slot.samplerBindings) { + VkDescriptorSetLayoutBinding b{}; + b.binding = sb.binding; + b.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + b.descriptorCount = 1; + b.stageFlags = sb.stages ? sb.stages : VK_SHADER_STAGE_FRAGMENT_BIT; + bindings.push_back(b); + } + + if (!bindings.empty()) { + std::sort(bindings.begin(), bindings.end(), [](const auto& a, const auto& b) { + if (a.binding != b.binding) return a.binding < b.binding; + return a.descriptorType < b.descriptorType; + }); + + bindings.erase(std::unique(bindings.begin(), bindings.end(), + [](const auto& a, const auto& b) { + return a.binding == b.binding && a.descriptorType == b.descriptorType; + }), + bindings.end()); + + VkDescriptorSetLayoutCreateInfo dlci{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + dlci.bindingCount = bindings.size(); + dlci.pBindings = bindings.data(); + VK_VERIFY(vkCreateDescriptorSetLayout(g.ctx->GetDevice(), &dlci, nullptr, &slot.setLayout), + "vkCreateDescriptorSetLayout"); + } + + VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + if (slot.setLayout != VK_NULL_HANDLE) { + plci.setLayoutCount = 1; + plci.pSetLayouts = &slot.setLayout; + } + VK_VERIFY(vkCreatePipelineLayout(g.ctx->GetDevice(), &plci, nullptr, &slot.pipelineLayout), + "vkCreatePipelineLayout"); + + CreateNullUbo(); + + slot.uniformDescriptorCount = + static_cast(slot.uniformBindings.size() + ((slot.uboSize > 0 && slot.uboBinding >= 0) ? 1 : 0)); + slot.samplerDescriptorCount = static_cast(slot.samplerBindings.size()); + + if (slot.uboSize > 0 && slot.uboBinding >= 0) { + slot.uboPools.resize(g.frames.size()); + for (SizeT i = 0; i < g.frames.size(); ++i) { + BufferResource ubo{}; + CreateBuffer(slot.uboSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, ubo); + slot.uboPools[i].buffers.push_back(ubo); + } + } + + return slot; + } + + Uint64 HashVertexLayout(const VertexArrayObject* vao) { + VertexAttribKey keys[VertexArrayObject::MAX_VERTEX_ATTRIBS]; + Memset(keys, 0, sizeof(keys)); + if (!vao) return XXH64(keys, sizeof(keys), 0xBADC0FFEEu); + + for (Uint32 i = 0; i < VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) { + const auto& attr = vao->GetAttribute(i); + auto& key = keys[i]; + key.enabled = attr.Enabled ? 1 : 0; + key.size = static_cast(attr.Size); + key.type = static_cast(attr.Type); + key.normalized = attr.Normalized ? 1 : 0; + key.isInteger = attr.IsInteger ? 1 : 0; + key.divisor = static_cast(attr.Divisor); + key.stride = static_cast(attr.Stride); + key.offset = static_cast(attr.Offset); + } + + return XXH64(keys, sizeof(keys), 0xBADC0FFEEu); + } + + VkPipeline GetOrCreatePipeline(ProgramResource& prog, const VertexArrayObject* vao, + const RenderStateParameters& rs, VkPrimitiveTopology topology, + VkRenderPass renderPass, Uint32 colorAttachmentCount, Bool hasDepth, + Uint64 renderPassKey) { + if (!g.programMgr) g.programMgr = MakeUnique(*g.ctx); + prog.shaderStages = &g.programMgr->CreatePipelineShaderStages(prog.program); + if (!prog.shaderStages || prog.shaderStages->empty()) { + MGLOG_E("Vulkan: shader stages are empty for program %u (hash=%llu)", prog.program->GetExternalIndex(), + static_cast(prog.programHash)); + return VK_NULL_HANDLE; + } + struct PipelineKey { + Uint64 programHash = 0; + Uint64 vertexHash = 0; + Uint32 topology = 0; + Uint32 blendEnable = 0; + Uint32 depthTest = 0; + Uint32 depthWrite = 0; + Uint32 depthFunc = 0; + Uint32 cullEnable = 0; + Uint32 cullMode = 0; + Uint32 colorMask = 0; + Uint32 srcRGB = 0; + Uint32 dstRGB = 0; + Uint32 srcA = 0; + Uint32 dstA = 0; + Uint64 blendHash = 0; + Uint64 renderPass = 0; + Uint32 colorAttachments = 0; + Uint32 hasDepth = 0; + } key{}; + + DEBUG_TRACE_POINT(); + key.programHash = prog.programHash; + key.vertexHash = HashVertexLayout(vao); + key.topology = topology; + key.blendEnable = rs.BlendStates[0].Enabled ? 1u : 0u; + key.depthTest = rs.DepthTestEnabled ? 1u : 0u; + key.depthWrite = rs.DepthMask ? 1u : 0u; + key.depthFunc = static_cast(rs.DepthFunc); + key.cullEnable = rs.CullFaceEnabled ? 1u : 0u; + key.cullMode = static_cast(rs.CullFaceModeSetting); + key.colorMask = (rs.ColorMask.x() ? 1u : 0u) | (rs.ColorMask.y() ? 2u : 0u) | (rs.ColorMask.z() ? 4u : 0u) | + (rs.ColorMask.w() ? 8u : 0u); + key.srcRGB = static_cast(rs.BlendStates[0].SrcFactorRGB); + key.dstRGB = static_cast(rs.BlendStates[0].DstFactorRGB); + key.srcA = static_cast(rs.BlendStates[0].SrcFactorAlpha); + key.dstA = static_cast(rs.BlendStates[0].DstFactorAlpha); + key.blendHash = XXH64(rs.BlendStates.data(), sizeof(rs.BlendStates), 0xBEEFCAFEu); + key.renderPass = renderPassKey; + key.colorAttachments = colorAttachmentCount; + key.hasDepth = hasDepth ? 1u : 0u; + + DEBUG_TRACE_POINT(); + Uint64 hash = XXH64(&key, sizeof(key), 0x12345678u); + auto it = prog.pipelines.find(hash); + if (it != prog.pipelines.end()) return it->second; + + DEBUG_TRACE_POINT(); + Vector bindings; + Vector attrs; + if (vao) { + for (Uint32 i = 0; i < VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) { + const auto& attr = vao->GetAttribute(i); + if (!attr.Enabled || !attr.Buffer) continue; + + VkVertexInputBindingDescription b{}; + b.binding = i; + b.stride = attr.Stride > 0 ? attr.Stride : (attr.Size * SizeOfDataType(attr.Type)); + b.inputRate = attr.Divisor > 0 ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; + bindings.push_back(b); + + VkVertexInputAttributeDescription a{}; + a.location = i; + a.binding = i; + a.format = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger); + a.offset = static_cast(attr.Offset); + if (a.format != VK_FORMAT_UNDEFINED) { + attrs.push_back(a); + } + } + } + + DEBUG_TRACE_POINT(); + VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + vertexInput.vertexBindingDescriptionCount = bindings.size(); + vertexInput.pVertexBindingDescriptions = bindings.data(); + vertexInput.vertexAttributeDescriptionCount = attrs.size(); + vertexInput.pVertexAttributeDescriptions = attrs.data(); + + DEBUG_TRACE_POINT(); + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = topology; + ia.primitiveRestartEnable = VK_FALSE; + + DEBUG_TRACE_POINT(); + VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vpci.viewportCount = 1; + vpci.scissorCount = 1; + + DEBUG_TRACE_POINT(); + VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + raster.polygonMode = VK_POLYGON_MODE_FILL; + raster.cullMode = rs.CullFaceEnabled ? ToVkCullMode(rs.CullFaceModeSetting) : VK_CULL_MODE_NONE; + raster.frontFace = VK_FRONT_FACE_CLOCKWISE; + raster.lineWidth = 1.0f; + + DEBUG_TRACE_POINT(); + VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + DEBUG_TRACE_POINT(); + VkPipelineDepthStencilStateCreateInfo depth{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; + depth.depthTestEnable = rs.DepthTestEnabled ? VK_TRUE : VK_FALSE; + depth.depthWriteEnable = rs.DepthMask ? VK_TRUE : VK_FALSE; + depth.depthCompareOp = ToVkCompareOp(rs.DepthFunc); + depth.stencilTestEnable = VK_FALSE; + + DEBUG_TRACE_POINT(); + Vector colorAtts; + if (colorAttachmentCount > 0) { + colorAtts.resize(colorAttachmentCount); + for (Uint32 i = 0; i < colorAttachmentCount; ++i) { + const auto& bs = rs.BlendStates[i]; + VkPipelineColorBlendAttachmentState ca{}; + ca.colorWriteMask = (rs.ColorMask.x() ? VK_COLOR_COMPONENT_R_BIT : 0) | + (rs.ColorMask.y() ? VK_COLOR_COMPONENT_G_BIT : 0) | + (rs.ColorMask.z() ? VK_COLOR_COMPONENT_B_BIT : 0) | + (rs.ColorMask.w() ? VK_COLOR_COMPONENT_A_BIT : 0); + ca.blendEnable = bs.Enabled ? VK_TRUE : VK_FALSE; + ca.srcColorBlendFactor = ToVkBlendFactor(bs.SrcFactorRGB); + ca.dstColorBlendFactor = ToVkBlendFactor(bs.DstFactorRGB); + ca.colorBlendOp = VK_BLEND_OP_ADD; + ca.srcAlphaBlendFactor = ToVkBlendFactor(bs.SrcFactorAlpha); + ca.dstAlphaBlendFactor = ToVkBlendFactor(bs.DstFactorAlpha); + ca.alphaBlendOp = VK_BLEND_OP_ADD; + colorAtts[i] = ca; + } + } + + DEBUG_TRACE_POINT(); + VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + blend.attachmentCount = colorAtts.size(); + blend.pAttachments = colorAtts.empty() ? nullptr : colorAtts.data(); + + DEBUG_TRACE_POINT(); + VkDynamicState dynamics[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dyn{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + dyn.dynamicStateCount = 2; + dyn.pDynamicStates = dynamics; + + DEBUG_TRACE_POINT(); + VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + gpi.stageCount = prog.shaderStages ? prog.shaderStages->size() : 0; + gpi.pStages = prog.shaderStages ? prog.shaderStages->data() : nullptr; + gpi.pVertexInputState = &vertexInput; + gpi.pInputAssemblyState = &ia; + gpi.pViewportState = &vpci; + gpi.pRasterizationState = &raster; + gpi.pMultisampleState = &ms; + gpi.pDepthStencilState = hasDepth ? &depth : nullptr; + gpi.pColorBlendState = &blend; + gpi.pDynamicState = &dyn; + gpi.layout = prog.pipelineLayout; + gpi.renderPass = renderPass; + gpi.subpass = 0; + + DEBUG_TRACE_POINT(); + VkPipeline pipeline = VK_NULL_HANDLE; + VK_VERIFY(vkCreateGraphicsPipelines(g.ctx->GetDevice(), VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline), + "vkCreateGraphicsPipelines"); + + DEBUG_TRACE_POINT(); + prog.pipelines.emplace(hash, pipeline); + return pipeline; + } + + VkDescriptorSet UpdateDescriptorSetsForDraw(ProgramResource& prog, Uint32 frameIndex) { + if (prog.setLayout == VK_NULL_HANDLE) return VK_NULL_HANDLE; + VkDescriptorSet activeSet = AllocateDescriptorSetForDraw(prog, frameIndex); + if (activeSet == VK_NULL_HANDLE) return VK_NULL_HANDLE; + + DEBUG_TRACE_POINT(); + Vector writes; + Vector bufferInfos; + Vector imageInfos; + + DEBUG_TRACE_POINT(); + bufferInfos.reserve(prog.uniformBindings.size() + (prog.uboSize > 0 ? 1 : 0)); + imageInfos.reserve(prog.samplerBindings.size()); + writes.reserve(bufferInfos.capacity() + imageInfos.capacity()); + + DEBUG_TRACE_POINT(); + if (prog.uboSize > 0 && prog.uboBinding >= 0) { + if (frameIndex >= prog.uboPools.size()) return VK_NULL_HANDLE; + DEBUG_TRACE_POINT(); + auto& pool = prog.uboPools[frameIndex]; + if (pool.cursor >= pool.buffers.size()) { + BufferResource ubo{}; + CreateBuffer(prog.uboSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, ubo); + pool.buffers.push_back(ubo); + } + BufferResource& ubo = pool.buffers[pool.cursor++]; + Memcpy(ubo.mapped, prog.program->MapUBO(), prog.uboSize); + + DEBUG_TRACE_POINT(); + VkDescriptorBufferInfo info{}; + info.buffer = ubo.buffer; + info.offset = 0; + info.range = prog.uboSize; + bufferInfos.push_back(info); + + DEBUG_TRACE_POINT(); + VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + w.dstSet = activeSet; + w.dstBinding = static_cast(prog.uboBinding); + w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + w.descriptorCount = 1; + w.pBufferInfo = &bufferInfos.back(); + writes.push_back(w); + } + DEBUG_TRACE_POINT(); + + for (const auto& ub : prog.uniformBindings) { + DEBUG_TRACE_POINT(); + if (ub.blockIndex == 0xFFFFFFFFu) continue; + DEBUG_TRACE_POINT(); + Int bindingPoint = prog.program->GetUniformBlockBinding(ub.blockIndex); + if (bindingPoint < 0) continue; + + DEBUG_TRACE_POINT(); + auto& slot = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, bindingPoint); + auto bufferObj = slot.GetBoundObject(); + BufferResource* res = &g.nullUbo; + VkDeviceSize offset = 0; + VkDeviceSize range = g.nullUbo.size; + + DEBUG_TRACE_POINT(); + if (bufferObj) { + DEBUG_TRACE_POINT(); + auto& synced = SyncBuffer(bufferObj.get()); + res = &synced; + auto r = slot.GetRange(); + offset = r.start; + if (r.end == ~0u || r.end <= r.start) { + range = synced.size > offset ? synced.size - offset : VK_WHOLE_SIZE; + } else { + range = r.end - r.start; + } + } + + DEBUG_TRACE_POINT(); + VkDescriptorBufferInfo info{}; + info.buffer = res->buffer; + info.offset = offset; + info.range = range; + bufferInfos.push_back(info); + + DEBUG_TRACE_POINT(); + VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + w.dstSet = activeSet; + w.dstBinding = ub.binding; + w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + w.descriptorCount = 1; + w.pBufferInfo = &bufferInfos.back(); + writes.push_back(w); + } + + DEBUG_TRACE_POINT(); + for (const auto& sb : prog.samplerBindings) { + DEBUG_TRACE_POINT(); + Int loc = FindUniformLocationByName(prog.program, sb.name); + Int unit = (loc >= 0) ? prog.program->GetUniformSamplerOrImageUnitIndex(loc) : -1; + if (unit < 0) unit = 0; + + DEBUG_TRACE_POINT(); + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); + SharedPtr texObj = nullptr; + auto& slot2d = textureUnit.GetBindingSlot(TextureTarget::Texture2D); + texObj = slot2d.GetBoundObject(); + if (!texObj) { + DEBUG_TRACE_POINT(); + for (auto& slot : textureUnit.GetAllBindingSlots()) { + DEBUG_TRACE_POINT(); + if (slot.GetBoundObject()) { + DEBUG_TRACE_POINT(); + texObj = slot.GetBoundObject(); + break; + } + } + } + + DEBUG_TRACE_POINT(); + TextureResource& texRes = texObj ? SyncTexture(texObj.get()) : g.defaultTexture; + VkImageView sampleView = texRes.view; + VkImageLayout sampleLayout = SampledLayoutForFormat(texRes.format); + if (texRes.image != VK_NULL_HANDLE) { + VkImageAspectFlags aspect = SampleAspectForFormat(texRes.format); + EnsureImageLayoutForDescriptor(texRes, aspect, sampleLayout); + sampleView = GetTextureViewForSampling(texRes, 0, aspect); + } + SamplerObject* samplerObj = nullptr; + if (auto s = textureUnit.GetSamplerObject()) samplerObj = s.get(); + if (!samplerObj && texObj) samplerObj = texObj->GetSamplerObject().get(); + SamplerResource& sampRes = SyncSampler(samplerObj); + + DEBUG_TRACE_POINT(); + VkDescriptorImageInfo info{}; + info.sampler = sampRes.sampler != VK_NULL_HANDLE ? sampRes.sampler : g.defaultSampler.sampler; + info.imageView = sampleView; + info.imageLayout = sampleLayout; + imageInfos.push_back(info); + + DEBUG_TRACE_POINT(); + VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + w.dstSet = activeSet; + w.dstBinding = sb.binding; + w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + w.descriptorCount = 1; + w.pImageInfo = &imageInfos.back(); + writes.push_back(w); + } + + DEBUG_TRACE_POINT(); + if (!writes.empty()) { + DEBUG_TRACE_POINT(); + vkUpdateDescriptorSets(g.ctx->GetDevice(), writes.size(), writes.data(), 0, nullptr); + } + return activeSet; + } + + void CreateDefaultResources() { + if (!g.defaultSampler.sampler) { + VkSamplerCreateInfo sci{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + sci.magFilter = VK_FILTER_LINEAR; + sci.minFilter = VK_FILTER_LINEAR; + sci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + sci.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sci.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sci.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + VK_VERIFY(vkCreateSampler(g.ctx->GetDevice(), &sci, nullptr, &g.defaultSampler.sampler), + "vkCreateSampler default"); + } + + if (!g.defaultTexture.valid) { + g.defaultTexture.format = VK_FORMAT_R8G8B8A8_UNORM; + g.defaultTexture.extent = {1, 1, 1}; + g.defaultTexture.mipLevels = 1; + CreateImage(g.defaultTexture.extent, g.defaultTexture.format, + UsageForTextureFormat(g.defaultTexture.format), g.defaultTexture.image, + g.defaultTexture.memory, g.defaultTexture.mipLevels); + g.defaultTexture.view = + CreateImageView(g.defaultTexture.image, g.defaultTexture.format, VK_IMAGE_ASPECT_COLOR_BIT); + g.defaultTexture.layout = VK_IMAGE_LAYOUT_UNDEFINED; + g.defaultTexture.valid = true; + + Uint32 pixel = 0xFFFFFFFFu; + BufferResource staging{}; + CreateBuffer(sizeof(pixel), VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging); + Memcpy(staging.mapped, &pixel, sizeof(pixel)); + + SubmitImmediate([&](VkCommandBuffer cmd) { + CmdTransitionImageLayout(cmd, g.defaultTexture.image, g.defaultTexture.layout, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + + VkBufferImageCopy region{}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageExtent = {1, 1, 1}; + vkCmdCopyBufferToImage(cmd, staging.buffer, g.defaultTexture.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + CmdTransitionImageLayout(cmd, g.defaultTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + }); + + g.defaultTexture.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + DestroyBuffer(staging); + } + } + + VkFormat FindDepthFormat() { + VkFormat candidates[] = {VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, + VK_FORMAT_D16_UNORM}; + for (auto fmt : candidates) { + VkFormatProperties props{}; + vkGetPhysicalDeviceFormatProperties(g.ctx->GetPhysicalDevice(), fmt, &props); + if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { + return fmt; + } + } + return VK_FORMAT_UNDEFINED; + } + + void DestroyDepthResources() { + if (g.depthView != VK_NULL_HANDLE) { + vkDestroyImageView(g.ctx->GetDevice(), g.depthView, nullptr); + g.depthView = VK_NULL_HANDLE; + } + if (g.depthImage != VK_NULL_HANDLE) { + vkDestroyImage(g.ctx->GetDevice(), g.depthImage, nullptr); + g.depthImage = VK_NULL_HANDLE; + } + if (g.depthMemory != VK_NULL_HANDLE) { + vkFreeMemory(g.ctx->GetDevice(), g.depthMemory, nullptr); + g.depthMemory = VK_NULL_HANDLE; + } + } + + void CreateDepthResources() { + DestroyDepthResources(); + g.depthFormat = FindDepthFormat(); + if (g.depthFormat == VK_FORMAT_UNDEFINED) return; + + VkExtent2D extent = g.swapchain->GetExtent(); + VkExtent3D depthExtent{extent.width, extent.height, 1}; + CreateImage(depthExtent, g.depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, g.depthImage, + g.depthMemory); + g.depthView = CreateImageView(g.depthImage, g.depthFormat, AspectForFormat(g.depthFormat)); + + SubmitImmediate([&](VkCommandBuffer cmd) { + CmdTransitionImageLayout(cmd, g.depthImage, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + AspectForFormat(g.depthFormat)); + }); + } + + void DestroyRenderPass() { + for (auto& rp : g.renderPasses) { + if (rp != VK_NULL_HANDLE) { + vkDestroyRenderPass(g.ctx->GetDevice(), rp, nullptr); + rp = VK_NULL_HANDLE; + } + } + g.defaultRenderPassCompatHash = 0; + } + + void CreateRenderPass() { + DestroyRenderPass(); + g.renderPasses[0] = CreateDefaultRenderPass(0); + g.defaultRenderPassCompatHash = ComputeDefaultRenderPassCompatHash(); + } + + void CreateFramebuffers() { + const auto& imageViews = g.swapchain->GetImageViews(); + g.swapchainImageLayouts.clear(); + g.swapchainImageLayouts.resize(imageViews.size(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + Vector fbs; + fbs.reserve(imageViews.size()); + for (auto iv : imageViews) { + Vector attachments; + attachments.push_back(iv); + if (g.depthView != VK_NULL_HANDLE) attachments.push_back(g.depthView); + + VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = g.renderPasses[0]; + fbci.attachmentCount = attachments.size(); + fbci.pAttachments = attachments.data(); + fbci.width = g.swapchain->GetExtent().width; + fbci.height = g.swapchain->GetExtent().height; + fbci.layers = 1; + VkFramebuffer fb = VK_NULL_HANDLE; + VK_VERIFY(vkCreateFramebuffer(g.ctx->GetDevice(), &fbci, nullptr, &fb), "vkCreateFramebuffer"); + fbs.push_back(fb); + } + g.swapchain->SetFramebuffers(std::move(fbs)); + } + + void DestroyFrameResources() { + DestroyDescriptorPools(); + for (auto& f : g.frames) { + if (f) f->Cleanup(*g.ctx); + } + g.frames.clear(); + } + + void CreateFrameResources() { + DestroyFrameResources(); + Uint32 imageCount = static_cast(g.swapchain->GetImageViews().size()); + if (imageCount == 0) throw RuntimeError("Swapchain has zero images"); + Uint32 frames = std::min(g.maxFramesInFlight, imageCount); + for (Uint32 i = 0; i < frames; ++i) { + auto fr = MakeUnique(); + fr->Initialize(*g.ctx, g.commandPool); + g.frames.push_back(std::move(fr)); + } + g.frameDescriptorPools.clear(); + g.frameDescriptorPools.resize(frames); + g.currentFrame = 0; + } + + void CreateCommandPool() { + if (g.commandPool != VK_NULL_HANDLE) return; + VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + cpci.queueFamilyIndex = g.ctx->GetGraphicsQueueFamily(); + cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VK_VERIFY(vkCreateCommandPool(g.ctx->GetDevice(), &cpci, nullptr, &g.commandPool), "vkCreateCommandPool"); + } + + void BeginCommandBuffer() { + if (g.cmdBufferBegun) return; + auto& frame = *g.frames[g.currentFrame]; + VK_VERIFY(vkWaitForFences(g.ctx->GetDevice(), 1, &frame.InFlightFence, VK_TRUE, UINT64_MAX), + "vkWaitForFences"); + VK_VERIFY(vkResetFences(g.ctx->GetDevice(), 1, &frame.InFlightFence), "vkResetFences"); + ClearFrameTrashBuffers(); + ClearFrameTrashImages(); + VK_VERIFY(vkResetCommandBuffer(frame.CommandBuffer, 0), "vkResetCommandBuffer"); + ResetFrameDescriptorPools(g.currentFrame); + ResetProgramUboPools(g.currentFrame); + + VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + VK_VERIFY(vkBeginCommandBuffer(frame.CommandBuffer, &bi), "vkBeginCommandBuffer"); + g.cmdBufferBegun = true; + } + + void BeginRenderPass(GLbitfield clearMask, const PendingClearInfo* clearInfo) { + if (g.recording) return; + auto& frame = *g.frames[g.currentFrame]; + VkRenderPassBeginInfo rpbi{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + rpbi.renderPass = g.activeRenderPass; + rpbi.framebuffer = g.activeFramebuffer; + rpbi.renderArea.offset = {0, 0}; + rpbi.renderArea.extent = g.activeExtent; + + Vector clearValues; + if (clearMask && clearInfo) { + SizeT attachmentCount = 0; + if (g.activeIsDefault) { + attachmentCount = (g.depthFormat != VK_FORMAT_UNDEFINED) ? 2 : 1; + } else if (g.activeBackendFBO) { + attachmentCount = g.activeBackendFBO->attachments.size(); + } + if (attachmentCount > 0) { + clearValues.resize(attachmentCount); + if (g.activeIsDefault) { + if (clearMask & GL_COLOR_BUFFER_BIT) { + clearValues[0].color = {clearInfo->clearColor.x(), clearInfo->clearColor.y(), + clearInfo->clearColor.z(), clearInfo->clearColor.w()}; + } + if (attachmentCount > 1 && (clearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT))) { + clearValues[1].depthStencil = {clearInfo->clearDepth, 0}; + } + } else if (g.activeBackendFBO) { + for (SizeT i = 0; i < attachmentCount; ++i) { + const auto& att = g.activeBackendFBO->attachments[i]; + if ((att.aspect & VK_IMAGE_ASPECT_COLOR_BIT) && (clearMask & GL_COLOR_BUFFER_BIT)) { + clearValues[i].color = {clearInfo->clearColor.x(), clearInfo->clearColor.y(), + clearInfo->clearColor.z(), clearInfo->clearColor.w()}; + } + if ((att.aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && + (clearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT))) { + clearValues[i].depthStencil = {clearInfo->clearDepth, 0}; + } + } + } + rpbi.clearValueCount = static_cast(clearValues.size()); + rpbi.pClearValues = clearValues.data(); + } + } + + vkCmdBeginRenderPass(frame.CommandBuffer, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + g.recording = true; + g.recordingRenderPass = g.activeRenderPass; + g.recordingFramebuffer = g.activeFramebuffer; + } + + Bool EnsureRenderPassBound() { + if (!SyncDrawFramebuffer()) return false; + if (g.activeFramebuffer == VK_NULL_HANDLE) return false; + + GLbitfield pendingMask = 0; + PendingClearInfo pendingInfo{}; + auto pendingIt = g.pendingClears.end(); + if (g.activeStateFBO) { + pendingIt = g.pendingClears.find(g.activeStateFBO); + if (pendingIt != g.pendingClears.end() && pendingIt->second.mask) { + pendingMask = pendingIt->second.mask; + pendingInfo = pendingIt->second; + } + } + + GLbitfield effectiveMask = 0; + if (pendingMask & GL_COLOR_BUFFER_BIT) { + Bool hasColor = false; + if (g.activeIsDefault) { + hasColor = g.activeDefaultColorWrites && g.activeColorAttachmentCount > 0; + } else if (g.activeBackendFBO) { + for (Uint32 i = 0; i < g.activeBackendFBO->colorAttachmentCount; ++i) { + if (g.activeBackendFBO->drawBufferAttachmentIndices[i] >= 0) { + hasColor = true; + break; + } + } + } + if (hasColor) effectiveMask |= GL_COLOR_BUFFER_BIT; + } + VkImageAspectFlags depthAspect = + g.activeDepthFormat != VK_FORMAT_UNDEFINED ? AspectForFormat(g.activeDepthFormat) : 0; + if ((pendingMask & GL_DEPTH_BUFFER_BIT) && g.activeHasDepth && (depthAspect & VK_IMAGE_ASPECT_DEPTH_BIT)) { + effectiveMask |= GL_DEPTH_BUFFER_BIT; + } + if ((pendingMask & GL_STENCIL_BUFFER_BIT) && g.activeHasDepth && + (depthAspect & VK_IMAGE_ASPECT_STENCIL_BIT)) { + effectiveMask |= GL_STENCIL_BUFFER_BIT; + } + + if (pendingMask && effectiveMask == 0 && pendingIt != g.pendingClears.end()) { + g.pendingClears.erase(pendingIt); + pendingIt = g.pendingClears.end(); + pendingMask = 0; + } + + if (g.recording && g.recordingFramebuffer == g.activeFramebuffer && pendingMask == 0) { + g.activeRenderPass = g.recordingRenderPass; + return true; + } + + if (g.activeIsDefault) { + g.activeRenderPass = GetOrCreateDefaultRenderPass(effectiveMask); + } else if (g.activeBackendFBO) { + g.activeRenderPass = GetOrCreateBackendRenderPass(*g.activeBackendFBO, effectiveMask); + } + + if (g.activeRenderPass == VK_NULL_HANDLE) return false; + BeginCommandBuffer(); + bool forceRestart = pendingMask && effectiveMask != 0; + if (!g.recording) { + BeginRenderPass(effectiveMask, effectiveMask ? &pendingInfo : nullptr); + if (pendingIt != g.pendingClears.end()) g.pendingClears.erase(pendingIt); + return true; + } + if (forceRestart || g.recordingRenderPass != g.activeRenderPass || + g.recordingFramebuffer != g.activeFramebuffer) { + auto& frame = *g.frames[g.currentFrame]; + vkCmdEndRenderPass(frame.CommandBuffer); + g.recording = false; + BeginRenderPass(effectiveMask, effectiveMask ? &pendingInfo : nullptr); + if (pendingIt != g.pendingClears.end()) g.pendingClears.erase(pendingIt); + } + return true; + } + + void CmdClearAttachments(VkCommandBuffer cmd, GLbitfield mask) { + Vector atts; + + if (mask & GL_COLOR_BUFFER_BIT) { + if (g.activeIsDefault || !g.activeBackendFBO) { + if (!g.activeDefaultColorWrites) { + // Default FBO draw buffer is NONE, skip color clears. + } else { + for (Uint32 i = 0; i < g.activeColorAttachmentCount; ++i) { + VkClearAttachment color{}; + color.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + color.colorAttachment = i; + color.clearValue.color = {g.clearColor.x(), g.clearColor.y(), g.clearColor.z(), + g.clearColor.w()}; + atts.push_back(color); + } + } + } else { + for (Uint32 i = 0; i < g.activeColorAttachmentCount; ++i) { + if (g.activeBackendFBO->drawBufferAttachmentIndices[i] < 0) continue; + VkClearAttachment color{}; + color.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + color.colorAttachment = i; + color.clearValue.color = {g.clearColor.x(), g.clearColor.y(), g.clearColor.z(), + g.clearColor.w()}; + atts.push_back(color); + } + } + } + if ((mask & GL_DEPTH_BUFFER_BIT) && g.activeHasDepth) { + VkClearAttachment depth{}; + depth.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depth.clearValue.depthStencil = {g.clearDepth, 0}; + atts.push_back(depth); + } + if ((mask & GL_STENCIL_BUFFER_BIT) && g.activeHasDepth) { + VkClearAttachment stencil{}; + stencil.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + stencil.clearValue.depthStencil = {g.clearDepth, 0}; + atts.push_back(stencil); + } + + if (atts.empty()) return; + VkClearRect rect{}; + rect.rect.offset = {0, 0}; + rect.rect.extent = g.activeExtent; + rect.baseArrayLayer = 0; + rect.layerCount = 1; + + vkCmdClearAttachments(cmd, atts.size(), atts.data(), 1, &rect); + } + + void EndRecordingAndSubmit() { + auto& frame = *g.frames[g.currentFrame]; + if (g.recording) { + vkCmdEndRenderPass(frame.CommandBuffer); + g.recording = false; + } + VK_VERIFY(vkEndCommandBuffer(frame.CommandBuffer), "vkEndCommandBuffer"); + + VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; + VkSemaphore waitSemaphores[] = {frame.ImageAvailable}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + si.waitSemaphoreCount = 1; + si.pWaitSemaphores = waitSemaphores; + si.pWaitDstStageMask = waitStages; + si.commandBufferCount = 1; + si.pCommandBuffers = &frame.CommandBuffer; + VkSemaphore signalSemaphores[] = {frame.RenderFinished}; + si.signalSemaphoreCount = 1; + si.pSignalSemaphores = signalSemaphores; + + VK_VERIFY(vkQueueSubmit(g.ctx->GetGraphicsQueue(), 1, &si, frame.InFlightFence), "vkQueueSubmit"); + g.frameSubmitted = true; + g.cmdBufferBegun = false; + g.recordingRenderPass = VK_NULL_HANDLE; + g.recordingFramebuffer = VK_NULL_HANDLE; + } + + void RecreateSwapchain() { + vkDeviceWaitIdle(g.ctx->GetDevice()); + g.recording = false; + g.cmdBufferBegun = false; + g.frameSubmitted = false; + g.recordingRenderPass = VK_NULL_HANDLE; + g.recordingFramebuffer = VK_NULL_HANDLE; + + DestroyFrameResources(); + DestroyRenderPass(); + DestroyDepthResources(); + + g.swapchain->Recreate(); + CreateDepthResources(); + CreateRenderPass(); + CreateFramebuffers(); + CreateFrameResources(); + + for (auto& [_, prog] : g.programs) { + DestroyProgramResource(prog); + prog = ProgramResource{}; + } + } + + void FrameBeginInternal() { + auto& frame = *g.frames[g.currentFrame]; + auto& imagesInFlight = g.swapchain->GetImagesInFlight(); + Uint32 imageIndex = 0; + VkResult res = vkAcquireNextImageKHR(g.ctx->GetDevice(), g.swapchain->GetSwapchain(), UINT64_MAX, + frame.ImageAvailable, VK_NULL_HANDLE, &imageIndex); + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + RecreateSwapchain(); + return; + } + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(g.ctx->GetDevice(), 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); + } + imagesInFlight[imageIndex] = frame.InFlightFence; + frame.CurrentImageIndex = imageIndex; + VK_VERIFY(res, "vkAcquireNextImageKHR"); + } + + void EnsureInitialized() { + if (g.initialized) return; + throw RuntimeError("Vulkan backend not initialized"); + } + + void ClearFrameTrashBuffers() { + auto& frame = *g.frames[g.currentFrame]; + for (auto& buf : frame.TrashBuffers) { + if (!buf.mapped) { + vkUnmapMemory(g.ctx->GetDevice(), buf.memory); + } + vkDestroyBuffer(g.ctx->GetDevice(), buf.buffer, nullptr); + vkFreeMemory(g.ctx->GetDevice(), buf.memory, nullptr); + } + frame.TrashBuffers.clear(); + } + + void ClearFrameTrashImages() { + auto& frame = *g.frames[g.currentFrame]; + for (auto& img : frame.TrashImages) { + if (img.view != VK_NULL_HANDLE) { + vkDestroyImageView(g.ctx->GetDevice(), img.view, nullptr); + } + for (auto view : img.mipViews) { + if (view != VK_NULL_HANDLE) vkDestroyImageView(g.ctx->GetDevice(), view, nullptr); + } + if (img.image != VK_NULL_HANDLE) { + vkDestroyImage(g.ctx->GetDevice(), img.image, nullptr); + } + if (img.memory != VK_NULL_HANDLE) { + vkFreeMemory(g.ctx->GetDevice(), img.memory, nullptr); + } + } + frame.TrashImages.clear(); + } + } // namespace + + void FrameBegin() { + EnsureInitialized(); + FrameBeginInternal(); + } + + void Present() { + EnsureInitialized(); + auto& frame = *g.frames[g.currentFrame]; + + { + auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); + auto currentFBO = slot.GetBoundObject(); + if (currentFBO) { + auto it = g.pendingClears.find(currentFBO); + if (it != g.pendingClears.end() && it->second.mask) { + EnsureRenderPassBound(); + } + } + } + + if (g.cmdBufferBegun) { + EndRecordingAndSubmit(); + } + + VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + VkSemaphore waitSemaphores[] = {g.frameSubmitted ? frame.RenderFinished : frame.ImageAvailable}; + pi.waitSemaphoreCount = 1; + pi.pWaitSemaphores = waitSemaphores; + VkSwapchainKHR scs[] = {g.swapchain->GetSwapchain()}; + pi.swapchainCount = 1; + pi.pSwapchains = scs; + pi.pImageIndices = &frame.CurrentImageIndex; + VkResult pres = vkQueuePresentKHR(g.ctx->GetGraphicsQueue(), &pi); + if (pres == VK_ERROR_OUT_OF_DATE_KHR || pres == VK_SUBOPTIMAL_KHR) { + RecreateSwapchain(); + } else { + VK_VERIFY(pres, "vkQueuePresentKHR"); + } + + g.frameSubmitted = false; + g.currentFrame = (g.currentFrame + 1) % g.frames.size(); + FrameBeginInternal(); + } + + void InitVulkan(ANativeWindow* window) { + if (g.initialized) return; + + g.ctx = MakeUnique(); + g.ctx->Initialize(window, "MobileGL-DirectVulkan-Tmp"); + + g.swapchain = MakeUnique(*g.ctx); + g.swapchain->Initialize(); + + CreateCommandPool(); + CreateDepthResources(); + CreateRenderPass(); + CreateFramebuffers(); + CreateFrameResources(); + + CreateNullUbo(); + CreateDefaultResources(); + + g.initialized = true; + FrameBeginInternal(); + } + + Bool PrepareForDraw(GLenum mode, VkPipeline& pipeline, VkDescriptorSet& drawSet, VertexArrayObject*& vao, + const RenderStateParameters*& rs, ProgramResource*& progRes, DV::FrameContext*& frame) { + EnsureInitialized(); + auto program = MG_State::pGLContext->GetCurrentProgram(); + if (!program || !program->GetLinkStatus()) return false; + auto& shaders = program->GetAttachedShaders(); + for (SizeT i = 0; i < shaders.size(); ++i) { + auto& sh = shaders[i]; + if (!sh) continue; + } + vao = MG_State::pGLContext->GetBoundVertexArray().get(); + rs = &MG_State::pGLContext->GetRenderStateParameters(); + + if (!EnsureRenderPassBound()) return false; + progRes = &GetProgramResource(program.get()); + RenderStateParameters rsForPipeline = *rs; + if (g.activeIsDefault && !g.activeDefaultColorWrites) { + rsForPipeline.ColorMask = {false, false, false, false}; + } + pipeline = GetOrCreatePipeline(*progRes, vao, rsForPipeline, ToVkTopology(mode), g.activeRenderPass, + g.activeColorAttachmentCount, g.activeHasDepth, GetActiveRenderPassCompatHash()); + if (pipeline == VK_NULL_HANDLE) { + MGLOG_E("Vulkan: failed to create pipeline (no shader stages)"); + return false; + } + + frame = g.frames.empty() ? nullptr : g.frames[g.currentFrame].get(); + if (!frame) return false; + + drawSet = UpdateDescriptorSetsForDraw(*progRes, g.currentFrame); + if (progRes->setLayout != VK_NULL_HANDLE && drawSet == VK_NULL_HANDLE) { + MGLOG_E("Vulkan: failed to allocate descriptor set for draw"); + return false; + } + + VkViewport vp{}; + if (rs->Viewport.z() > 0 && rs->Viewport.w() > 0) { + vp.x = static_cast(rs->Viewport.x()); + vp.y = static_cast(rs->Viewport.y()); + vp.width = static_cast(rs->Viewport.z()); + vp.height = static_cast(rs->Viewport.w()); + } else { + vp.x = 0.0f; + vp.y = 0.0f; + vp.width = static_cast(g.activeExtent.width); + vp.height = static_cast(g.activeExtent.height); + } + vp.minDepth = 0.0f; + vp.maxDepth = 1.0f; + vkCmdSetViewport(frame->CommandBuffer, 0, 1, &vp); + + VkRect2D scissor{}; + if (rs->ScissorTestEnabled) { + scissor.offset = {rs->ScissorBox.x(), rs->ScissorBox.y()}; + scissor.extent = {static_cast(rs->ScissorBox.z()), static_cast(rs->ScissorBox.w())}; + } else { + scissor.offset = {0, 0}; + scissor.extent = g.activeExtent; + } + vkCmdSetScissor(frame->CommandBuffer, 0, 1, &scissor); + + vkCmdBindPipeline(frame->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + if (drawSet != VK_NULL_HANDLE) { + vkCmdBindDescriptorSets(frame->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, progRes->pipelineLayout, 0, + 1, &drawSet, 0, nullptr); + } + + return true; + } + + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { + VkPipeline pipeline = VK_NULL_HANDLE; + VkDescriptorSet drawSet = VK_NULL_HANDLE; + VertexArrayObject* vao = nullptr; + const RenderStateParameters* rs = nullptr; + ProgramResource* prog = nullptr; + DV::FrameContext* frame = nullptr; + if (!PrepareForDraw(mode, pipeline, drawSet, vao, rs, prog, frame)) return; + + if (vao) { + for (Uint32 i = 0; i < VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) { + const auto& attr = vao->GetAttribute(i); + if (!attr.Enabled || !attr.Buffer) continue; + auto& vbo = SyncBuffer(attr.Buffer.get()); + VkDeviceSize offset = 0; + VkBuffer buf = vbo.buffer; + vkCmdBindVertexBuffers(frame->CommandBuffer, i, 1, &buf, &offset); + } + } + + VkIndexType indexType = ToVkIndexType(type); + Uint32 indexSize = IndexTypeSize(type); + VkDeviceSize indexOffset = 0; + + BufferResource* iboRes = nullptr; + if (vao) { + auto ibo = vao->GetIndexBufferBindingSlot().GetBoundObject(); + if (ibo) { + iboRes = &SyncBuffer(ibo.get()); + indexOffset = reinterpret_cast(indices); + } + } + + BufferResource tempIndex; + if (!iboRes && indices) { + VkDeviceSize sz = static_cast(count) * indexSize; + CreateBuffer(sz, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, tempIndex); + Memcpy(tempIndex.mapped, indices, sz); + iboRes = &tempIndex; + indexOffset = 0; + } + + if (iboRes && iboRes->buffer != VK_NULL_HANDLE) { + vkCmdBindIndexBuffer(frame->CommandBuffer, iboRes->buffer, 0, indexType); + Uint32 firstIndex = indexSize ? static_cast(indexOffset / indexSize) : 0; + vkCmdDrawIndexed(frame->CommandBuffer, count, 1, firstIndex, 0, 0); + } + + if (tempIndex.buffer != VK_NULL_HANDLE || tempIndex.memory != VK_NULL_HANDLE) { + frame->TrashBuffers.push_back({tempIndex.buffer, tempIndex.memory, tempIndex.mapped != nullptr}); + } + } + + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter) { + EnsureInitialized(); + if ((mask & GL_COLOR_BUFFER_BIT) == 0) { + MGLOG_W("BlitFramebuffer: only color blit is supported"); + return; + } + + auto& readSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read); + auto& drawSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); + auto readFBO = readSlot.GetBoundObject(); + auto drawFBO = drawSlot.GetBoundObject(); + if (!readFBO || !drawFBO) { + MGLOG_E("BlitFramebuffer: read/draw framebuffer not bound"); + return; + } + + auto getBackendFBO = [&](const SharedPtr& stateFBO) -> SharedPtr { + auto it = g.framebuffers.find(stateFBO); + SharedPtr backend; + if (it == g.framebuffers.end()) { + backend = MakeShared(); + g.framebuffers[stateFBO] = backend; + } else { + backend = it->second; + } + if (!SyncFramebufferObject(*backend, stateFBO)) return nullptr; + return backend; + }; + + auto readBackend = getBackendFBO(readFBO); + auto drawBackend = getBackendFBO(drawFBO); + bool readDefault = readFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO; + bool drawDefault = drawFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO; + if (!readDefault && !readBackend) return; + if (!drawDefault && !drawBackend) return; + + auto pickSrcType = [&](const BackendFramebufferObject& backend) -> FramebufferAttachmentType { + auto type = backend.frontendReadBuffer; + if (type == FramebufferAttachmentType::None) return FramebufferAttachmentType::Color0; + return type; + }; + auto pickDstType = [&](const BackendFramebufferObject& backend) -> FramebufferAttachmentType { + auto type = backend.frontendDrawBuffers[0]; + if (type == FramebufferAttachmentType::None) return FramebufferAttachmentType::Color0; + return type; + }; + + FramebufferAttachmentType srcType = FramebufferAttachmentType::Color0; + FramebufferAttachmentType dstType = FramebufferAttachmentType::Color0; + if (!readDefault && readBackend) srcType = pickSrcType(*readBackend); + if (!drawDefault && drawBackend) dstType = pickDstType(*drawBackend); + + auto resolveImage = [&](BackendFramebufferObject& backend, FramebufferAttachmentType type, VkImage& image, + VkImageLayout*& layout, VkExtent2D& extent, VkFormat& format, + bool& isRenderbuffer) -> bool { + Int32 idx = backend.attachmentIndex[static_cast(type)]; + if (idx < 0 || static_cast(idx) >= backend.attachments.size()) return false; + const auto& att = backend.attachments[static_cast(idx)]; + if (att.texture) { + auto& texRes = SyncTexture(att.texture); + image = texRes.image; + layout = &texRes.layout; + extent = {texRes.extent.width, texRes.extent.height}; + format = texRes.format; + isRenderbuffer = false; + return image != VK_NULL_HANDLE; + } + if (att.renderbuffer) { + auto& rbRes = SyncRenderbuffer(att.renderbuffer); + image = rbRes.image; + layout = &rbRes.layout; + extent = rbRes.extent; + format = rbRes.format; + isRenderbuffer = true; + return image != VK_NULL_HANDLE; + } + return false; + }; + + VkImage srcImage = VK_NULL_HANDLE; + VkImage dstImage = VK_NULL_HANDLE; + VkImageLayout* srcLayout = nullptr; + VkImageLayout* dstLayout = nullptr; + VkExtent2D srcExtent{0, 0}; + VkExtent2D dstExtent{0, 0}; + VkFormat srcFormat = VK_FORMAT_UNDEFINED; + VkFormat dstFormat = VK_FORMAT_UNDEFINED; + bool srcIsRb = false; + bool dstIsRb = false; + + if (readDefault) { + auto& frame = *g.frames[g.currentFrame]; + const auto& images = g.swapchain->GetImages(); + if (frame.CurrentImageIndex >= images.size()) return; + srcImage = images[frame.CurrentImageIndex]; + srcLayout = &g.swapchainImageLayouts[frame.CurrentImageIndex]; + srcExtent = g.swapchain->GetExtent(); + srcFormat = g.swapchain->GetFormat(); + } else { + if (!resolveImage(*readBackend, srcType, srcImage, srcLayout, srcExtent, srcFormat, srcIsRb)) { + MGLOG_E("BlitFramebuffer: failed to resolve source image"); + return; + } + } + if (drawDefault) { + auto& frame = *g.frames[g.currentFrame]; + const auto& images = g.swapchain->GetImages(); + if (frame.CurrentImageIndex >= images.size()) return; + dstImage = images[frame.CurrentImageIndex]; + dstLayout = &g.swapchainImageLayouts[frame.CurrentImageIndex]; + dstExtent = g.swapchain->GetExtent(); + dstFormat = g.swapchain->GetFormat(); + } else { + if (!resolveImage(*drawBackend, dstType, dstImage, dstLayout, dstExtent, dstFormat, dstIsRb)) { + MGLOG_E("BlitFramebuffer: failed to resolve destination image"); + return; + } + } + if (dstIsRb) { + MGLOG_W("BlitFramebuffer: renderbuffer destination is not supported yet"); + return; + } + + BeginCommandBuffer(); + auto& frame = *g.frames[g.currentFrame]; + if (g.recording) { + vkCmdEndRenderPass(frame.CommandBuffer); + g.recording = false; + g.recordingRenderPass = VK_NULL_HANDLE; + g.recordingFramebuffer = VK_NULL_HANDLE; + } + + VkImageLayout oldSrc = srcLayout ? *srcLayout : VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout oldDst = dstLayout ? *dstLayout : VK_IMAGE_LAYOUT_UNDEFINED; + + VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; + CmdTransitionImageLayout(frame.CommandBuffer, srcImage, oldSrc, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, aspect); + CmdTransitionImageLayout(frame.CommandBuffer, dstImage, oldDst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, aspect); + + VkImageBlit blit{}; + blit.srcSubresource.aspectMask = aspect; + blit.srcSubresource.mipLevel = 0; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstSubresource = blit.srcSubresource; + + blit.srcOffsets[0] = {srcX0, srcY0, 0}; + blit.srcOffsets[1] = {srcX1, srcY1, 1}; + blit.dstOffsets[0] = {dstX0, dstY0, 0}; + blit.dstOffsets[1] = {dstX1, dstY1, 1}; + + VkFilter vkFilter = (filter == GL_LINEAR) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + vkCmdBlitImage(frame.CommandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, vkFilter); + + CmdTransitionImageLayout(frame.CommandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, oldSrc, aspect); + CmdTransitionImageLayout(frame.CommandBuffer, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, oldDst, aspect); + + if (srcLayout) *srcLayout = oldSrc; + if (dstLayout) *dstLayout = oldDst; + } + + void DrawArrays(GLenum mode, GLint first, GLsizei count) { + VkPipeline pipeline = VK_NULL_HANDLE; + VkDescriptorSet drawSet = VK_NULL_HANDLE; + VertexArrayObject* vao = nullptr; + const RenderStateParameters* rs = nullptr; + ProgramResource* prog = nullptr; + DV::FrameContext* frame = nullptr; + if (!PrepareForDraw(mode, pipeline, drawSet, vao, rs, prog, frame)) return; + + if (vao) { + for (Uint32 i = 0; i < VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) { + const auto& attr = vao->GetAttribute(i); + if (!attr.Enabled || !attr.Buffer) continue; + auto& vbo = SyncBuffer(attr.Buffer.get()); + VkDeviceSize offset = 0; + VkBuffer buf = vbo.buffer; + vkCmdBindVertexBuffers(frame->CommandBuffer, i, 1, &buf, &offset); + } + } + + vkCmdDraw(frame->CommandBuffer, count, 1, first, 0); + } + + void Clear(GLbitfield mask) { + EnsureInitialized(); + GLbitfield validMask = mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + if (!validMask) return; + + auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); + auto currentFBO = slot.GetBoundObject(); + if (!currentFBO) { + MGLOG_E("No draw framebuffer bound"); + return; + } + + if (currentFBO == g.activeStateFBO && g.recording) { + auto& frame = *g.frames[g.currentFrame]; + vkCmdEndRenderPass(frame.CommandBuffer); + g.recording = false; + g.recordingRenderPass = VK_NULL_HANDLE; + g.recordingFramebuffer = VK_NULL_HANDLE; + } + + auto& clearInfo = g.pendingClears[currentFBO]; + if (validMask & GL_COLOR_BUFFER_BIT) { + clearInfo.clearColor = MG_State::pGLContext->GetClearColor(); + } + if (validMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) { + clearInfo.clearDepth = MG_State::pGLContext->GetClearDepth(); + } + clearInfo.mask |= validMask; + } +} // namespace MobileGL::Backend::DirectVulkan::TmpImpl diff --git a/MobileGL/MG_Backend/DirectVulkan/TmpImpl.h b/MobileGL/MG_Backend/DirectVulkan/TmpImpl.h new file mode 100644 index 00000000..c4c5f254 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/TmpImpl.h @@ -0,0 +1,53 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/TmpImpl.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +namespace MobileGL::Backend::DirectVulkan::TmpImpl { + void Present(); + void FrameBegin(); + void InitVulkan(ANativeWindow* window); + void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); + void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); + void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value); + void Clear(GLbitfield mask); + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); + void DrawArrays(GLenum mode, GLint first, GLsizei count); + void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount); + void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount, const GLint* basevertex); + void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); + void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); + void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, + const void* indices, GLint basevertex); + void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices); + void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLint basevertex, GLuint baseinstance); + void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLint basevertex); + void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLuint baseinstance); + void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount); + void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect); + void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, + GLuint baseinstance); + void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); + void DrawArraysIndirect(GLenum mode, const void* indirect); + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter); + void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, + GLsizei height, GLint border); + void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, + GLsizei height); + void GenerateMipmap(GLenum target); + void Present(); +} // namespace MobileGL::Backend::DirectVulkan::TmpImpl \ No newline at end of file diff --git a/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp index 17759b3e..db35de54 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp @@ -8,6 +8,7 @@ #include "EGLForVulkan.h" #include "MG_Backend/DirectVulkan/DirectVulkan.h" +#include "MG_Backend/DirectVulkan/TmpImpl.h" #include #include @@ -88,6 +89,8 @@ namespace MobileGL { } void CreateWindowSurfaceForVulkan(NativeWindowType window) { + MobileGL::Backend::DirectVulkan::TmpImpl::InitVulkan(window); + return; MG_Backend::DirectVulkan::pVulkanRenderer = MakeUnique(window); MG_Backend::DirectVulkan::pVulkanRenderer->Initialize(); @@ -102,6 +105,8 @@ namespace MobileGL { } EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) { + MobileGL::Backend::DirectVulkan::TmpImpl::Present(); + return EGL_TRUE; if (!MG_Backend::DirectVulkan::pVulkanRenderer) { MGLOG_E("EGLForVulkan::SwapBuffers called but VulkanRenderer is null"); return EGL_FALSE; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 5ad9860a..9a9e38ea 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -113,7 +113,8 @@ namespace MobileGL { Vector>& GetGeneratedSpirv() { return m_generatedSpirv; } Int GetShaderIndexByStage(ShaderStage stage) const { - auto it = std::find_if(m_shaders.begin(), m_shaders.end(), + auto it = std::find_if( + m_shaders.begin(), m_shaders.end(), [stage](const SharedPtr& shader) { return shader->GetShaderStage() == stage; }); return it == m_shaders.end() ? -1 : std::distance(m_shaders.begin(), it); }