From 377b96799674cb87f0581ad841c1cf13dfb4e1cc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Feb 2026 20:36:49 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): per-fbo pending clear state --- .../Renderer/VkFramebufferManager.cpp | 307 ++++++++++-- .../Renderer/VkFramebufferManager.h | 18 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 435 +++++++++++++----- .../DirectVulkan/Renderer/VulkanRenderer.h | 23 +- 4 files changed, 641 insertions(+), 142 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp index 633edc14..3491a8e9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp @@ -41,30 +41,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - return RecreateOffscreenColorTarget(target, colorAttachment, objectVersion); + return RecreateOffscreenColorTarget(target, glFbo, colorAttachment, objectVersion); } - Bool VkFramebufferManager::ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex, - const VkClearColorValue& clearColor) { + Bool VkFramebufferManager::TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer, + Uint glFboExternalIndex) { auto it = m_offscreenColorTargets.find(glFboExternalIndex); if (it == m_offscreenColorTargets.end()) { - MGLOG_W("VkFramebufferManager::ClearColor skipped: no offscreen target for FBO %u", glFboExternalIndex); + MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToAttachment skipped: FBO %u not found", + glFboExternalIndex); return false; } - - auto& target = it->second; - if (!TransitionColorTargetForClear(commandBuffer, target)) { - return false; - } - - VkImageSubresourceRange subresourceRange{}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = 1; - subresourceRange.baseArrayLayer = 0; - subresourceRange.layerCount = 1; - vkCmdClearColorImage(commandBuffer, target.image, target.layout, &clearColor, 1, &subresourceRange); - return true; + return TransitionColorTargetForAttachment(commandBuffer, it->second) && + TransitionDepthStencilTargetForAttachment(commandBuffer, it->second); } Bool VkFramebufferManager::TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, @@ -89,8 +78,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool VkFramebufferManager::GetOffscreenRenderTarget(Uint glFboExternalIndex, VkRenderPass& outRenderPass, + VkFramebuffer& outFramebuffer, VkExtent2D& outExtent, + VkFormat& outDepthStencilFormat) const { + auto it = m_offscreenColorTargets.find(glFboExternalIndex); + if (it == m_offscreenColorTargets.end() || it->second.framebuffer == VK_NULL_HANDLE || + it->second.renderPassLoad == VK_NULL_HANDLE) { + return false; + } + outRenderPass = it->second.renderPassLoad; + outFramebuffer = it->second.framebuffer; + outExtent = it->second.extent; + outDepthStencilFormat = it->second.depthStencilFormat; + return true; + } + Bool VkFramebufferManager::RecreateOffscreenColorTarget( - OffscreenColorTarget& target, const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, + OffscreenColorTarget& target, const MG_State::GLState::FramebufferObject& glFbo, + const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, Uint16 glObjectVersion) { DestroyOffscreenColorTarget(target); @@ -144,43 +149,202 @@ namespace MobileGL::MG_Backend::DirectVulkan { viewInfo.subresourceRange.layerCount = 1; VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &target.imageView), "vkCreateImageView(offscreen color)"); + const auto& depthAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Depth); + const auto& stencilAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Stencil); + const Bool requestedDepthStencil = + (depthAttachment.IsValid() && !depthAttachment.IsEmpty()) || + (stencilAttachment.IsValid() && !stencilAttachment.IsEmpty()); + VkFormat depthStencilFormat = ResolveDepthStencilFormat(depthAttachment, stencilAttachment); + if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT) { + depthStencilFormat = FindSupportedDepthStencilFormat({VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT}); + } else if (depthStencilFormat == VK_FORMAT_D32_SFLOAT) { + depthStencilFormat = FindSupportedDepthStencilFormat({VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM}); + } + const Bool hasDepthStencil = (depthStencilFormat != VK_FORMAT_UNDEFINED); + if (requestedDepthStencil && !hasDepthStencil) { + MGLOG_W("VkFramebufferManager: FBO %u depth/stencil attachment exists but format is unsupported", + glFbo.GetExternalIndex()); + } + if (hasDepthStencil) { + VkImageCreateInfo depthImageInfo{}; + depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + depthImageInfo.imageType = VK_IMAGE_TYPE_2D; + depthImageInfo.extent.width = static_cast(size.x()); + depthImageInfo.extent.height = static_cast(size.y()); + depthImageInfo.extent.depth = 1; + depthImageInfo.mipLevels = 1; + depthImageInfo.arrayLayers = 1; + depthImageInfo.format = depthStencilFormat; + depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateImage(m_device, &depthImageInfo, nullptr, &target.depthStencilImage), + "vkCreateImage(offscreen depth/stencil)"); + + VkMemoryRequirements depthMemoryRequirements{}; + vkGetImageMemoryRequirements(m_device, target.depthStencilImage, &depthMemoryRequirements); + VkMemoryAllocateInfo depthAllocInfo{}; + depthAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + depthAllocInfo.allocationSize = depthMemoryRequirements.size; + depthAllocInfo.memoryTypeIndex = + FindMemoryType(depthMemoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &depthAllocInfo, nullptr, &target.depthStencilMemory), + "vkAllocateMemory(offscreen depth/stencil)"); + VK_VERIFY(vkBindImageMemory(m_device, target.depthStencilImage, target.depthStencilMemory, 0), + "vkBindImageMemory(offscreen depth/stencil)"); + + VkImageAspectFlags depthAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT || depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) { + depthAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + VkImageViewCreateInfo depthViewInfo{}; + depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthViewInfo.image = target.depthStencilImage; + depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthViewInfo.format = depthStencilFormat; + depthViewInfo.subresourceRange.aspectMask = depthAspectMask; + depthViewInfo.subresourceRange.baseMipLevel = 0; + depthViewInfo.subresourceRange.levelCount = 1; + depthViewInfo.subresourceRange.baseArrayLayer = 0; + depthViewInfo.subresourceRange.layerCount = 1; + VK_VERIFY(vkCreateImageView(m_device, &depthViewInfo, nullptr, &target.depthStencilImageView), + "vkCreateImageView(offscreen depth/stencil)"); + } + + VkAttachmentDescription colorAttachmentDesc{}; + colorAttachmentDesc.format = format; + colorAttachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkAttachmentDescription depthAttachmentDesc{}; + VkAttachmentReference depthAttachmentRef{}; + Array attachments{}; + attachments[0] = colorAttachmentDesc; + Uint32 attachmentCount = 1; + if (hasDepthStencil) { + depthAttachmentDesc.format = depthStencilFormat; + depthAttachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + depthAttachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + depthAttachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + attachments[1] = depthAttachmentDesc; + attachmentCount = 2; + } + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = attachmentCount; + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + VK_VERIFY(vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &target.renderPassLoad), + "vkCreateRenderPass(offscreen)"); + + Array framebufferAttachments{}; + framebufferAttachments[0] = target.imageView; + Uint32 framebufferAttachmentCount = 1; + if (hasDepthStencil) { + framebufferAttachments[1] = target.depthStencilImageView; + framebufferAttachmentCount = 2; + } + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = target.renderPassLoad; + framebufferInfo.attachmentCount = framebufferAttachmentCount; + framebufferInfo.pAttachments = framebufferAttachments.data(); + framebufferInfo.width = static_cast(size.x()); + framebufferInfo.height = static_cast(size.y()); + framebufferInfo.layers = 1; + VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &target.framebuffer), + "vkCreateFramebuffer(offscreen)"); + target.layout = VK_IMAGE_LAYOUT_UNDEFINED; target.extent = {static_cast(size.x()), static_cast(size.y())}; target.format = format; + target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED; + target.depthStencilFormat = depthStencilFormat; target.glObjectVersion = glObjectVersion; return true; } void VkFramebufferManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) { + if (target.framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(m_device, target.framebuffer, nullptr); + target.framebuffer = VK_NULL_HANDLE; + } + if (target.renderPassLoad != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_device, target.renderPassLoad, nullptr); + target.renderPassLoad = VK_NULL_HANDLE; + } if (target.imageView != VK_NULL_HANDLE) { vkDestroyImageView(m_device, target.imageView, nullptr); target.imageView = VK_NULL_HANDLE; } + if (target.depthStencilImageView != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, target.depthStencilImageView, nullptr); + target.depthStencilImageView = VK_NULL_HANDLE; + } if (target.image != VK_NULL_HANDLE) { vkDestroyImage(m_device, target.image, nullptr); target.image = VK_NULL_HANDLE; } + if (target.depthStencilImage != VK_NULL_HANDLE) { + vkDestroyImage(m_device, target.depthStencilImage, nullptr); + target.depthStencilImage = VK_NULL_HANDLE; + } if (target.memory != VK_NULL_HANDLE) { vkFreeMemory(m_device, target.memory, nullptr); target.memory = VK_NULL_HANDLE; } + if (target.depthStencilMemory != VK_NULL_HANDLE) { + vkFreeMemory(m_device, target.depthStencilMemory, nullptr); + target.depthStencilMemory = VK_NULL_HANDLE; + } target.layout = VK_IMAGE_LAYOUT_UNDEFINED; + target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED; target.extent = {0, 0}; target.format = VK_FORMAT_UNDEFINED; + target.depthStencilFormat = VK_FORMAT_UNDEFINED; target.glObjectVersion = 0; } - Bool VkFramebufferManager::TransitionColorTargetForClear(VkCommandBuffer commandBuffer, OffscreenColorTarget& target) { - if (target.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + Bool VkFramebufferManager::TransitionColorTargetForAttachment(VkCommandBuffer commandBuffer, + OffscreenColorTarget& target) { + if (target.layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { return true; } VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; barrier.oldLayout = target.layout; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = target.image; @@ -189,10 +353,46 @@ namespace MobileGL::MG_Backend::DirectVulkan { barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - target.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + target.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + return true; + } + + Bool VkFramebufferManager::TransitionDepthStencilTargetForAttachment(VkCommandBuffer commandBuffer, + OffscreenColorTarget& target) { + if (target.depthStencilImage == VK_NULL_HANDLE) { + return true; + } + if (target.depthStencilLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + return true; + } + + VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT || + target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) { + aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = target.depthStencilLayout; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = target.depthStencilImage; + barrier.subresourceRange.aspectMask = aspectMask; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); + target.depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; return true; } @@ -255,4 +455,59 @@ namespace MobileGL::MG_Backend::DirectVulkan { return VK_FORMAT_UNDEFINED; } } + + VkFormat VkFramebufferManager::ResolveDepthStencilFormat( + const MG_State::GLState::FramebufferAttachmentObject& depthAttachment, + const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment) { + const auto resolveAttachmentFormat = [](const MG_State::GLState::FramebufferAttachmentObject& attachment) { + TextureInternalFormat internalFormat = TextureInternalFormat::Unknown; + if (attachment.IsTexture()) { + const auto texture = attachment.GetTexture(); + internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown; + } else if (attachment.IsRenderbuffer()) { + const auto renderbuffer = attachment.GetRenderbuffer(); + internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown; + } + return internalFormat; + }; + + const auto depthFormat = resolveAttachmentFormat(depthAttachment); + const auto stencilFormat = resolveAttachmentFormat(stencilAttachment); + + switch (depthFormat) { + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: + case TextureInternalFormat::DepthStencil: + return VK_FORMAT_D24_UNORM_S8_UINT; + case TextureInternalFormat::DepthComponent16: + return VK_FORMAT_D16_UNORM; + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: + case TextureInternalFormat::DepthComponent: + return VK_FORMAT_D32_SFLOAT; + default: + break; + } + + switch (stencilFormat) { + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: + case TextureInternalFormat::DepthStencil: + return VK_FORMAT_D24_UNORM_S8_UINT; + default: + return VK_FORMAT_UNDEFINED; + } + } + + VkFormat VkFramebufferManager::FindSupportedDepthStencilFormat(const Vector& candidates) const { + for (auto format : candidates) { + VkFormatProperties properties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties); + if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) { + return format; + } + } + return VK_FORMAT_UNDEFINED; + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h index 08530e86..c6c27f77 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h @@ -27,9 +27,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Shutdown(); Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo); - Bool ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex, const VkClearColorValue& clearColor); + Bool TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer, Uint glFboExternalIndex); Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex); Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const; + Bool GetOffscreenRenderTarget(Uint glFboExternalIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer, + VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const; private: struct OffscreenColorTarget { @@ -39,17 +41,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; VkExtent2D extent = {0, 0}; VkFormat format = VK_FORMAT_UNDEFINED; + VkImage depthStencilImage = VK_NULL_HANDLE; + VkDeviceMemory depthStencilMemory = VK_NULL_HANDLE; + VkImageView depthStencilImageView = VK_NULL_HANDLE; + VkImageLayout depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED; + VkRenderPass renderPassLoad = VK_NULL_HANDLE; + VkFramebuffer framebuffer = VK_NULL_HANDLE; Uint16 glObjectVersion = 0; }; Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target, + const MG_State::GLState::FramebufferObject& glFbo, const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, Uint16 glObjectVersion); void DestroyOffscreenColorTarget(OffscreenColorTarget& target); - Bool TransitionColorTargetForClear(VkCommandBuffer commandBuffer, OffscreenColorTarget& target); + Bool TransitionColorTargetForAttachment(VkCommandBuffer commandBuffer, OffscreenColorTarget& target); + Bool TransitionDepthStencilTargetForAttachment(VkCommandBuffer commandBuffer, OffscreenColorTarget& target); Bool TransitionColorTargetForBlitSrc(VkCommandBuffer commandBuffer, OffscreenColorTarget& target); Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const; static VkFormat ResolveColorFormat(const MG_State::GLState::FramebufferAttachmentObject& colorAttachment); + static VkFormat ResolveDepthStencilFormat(const MG_State::GLState::FramebufferAttachmentObject& depthAttachment, + const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment); + VkFormat FindSupportedDepthStencilFormat(const Vector& candidates) const; VkDevice m_device = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index a80ad49d..4c4f4a93 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -12,6 +12,7 @@ #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" +#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" namespace MobileGL::MG_Backend::DirectVulkan { VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, @@ -76,7 +77,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.programHash = programHash; payload.vertexInputHash = vertexInputHash; payload.pipelineLayout = m_pipelineLayout; - payload.renderPass = m_renderPassLoad; + payload.renderPass = (m_activeRenderPass != VK_NULL_HANDLE) ? m_activeRenderPass : m_renderPassLoad; payload.subpass = 0; payload.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; payload.stages = &stages; @@ -215,39 +216,39 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } - if (m_pendingClearMask != 0 && - (m_pendingClearDrawFboExternalIndex != drawFboExternalIndex || - m_pendingClearTargetsDefaultFramebuffer != isDefaultFramebufferTarget)) { - MGLOG_W("Pending clear target changed from FBO %u to FBO %u before execution; dropping previous pending clear", - m_pendingClearDrawFboExternalIndex, drawFboExternalIndex); - m_pendingClearMask = 0; - } + const Uint64 pendingKey = BuildPendingClearKey(drawFboExternalIndex, isDefaultFramebufferTarget); + auto& pending = m_pendingClears[pendingKey]; if ((requestMask & GL_COLOR_BUFFER_BIT) != 0) { - m_pendingClearColor.float32[0] = color.x(); - m_pendingClearColor.float32[1] = color.y(); - m_pendingClearColor.float32[2] = color.z(); - m_pendingClearColor.float32[3] = color.w(); + pending.color.float32[0] = color.x(); + pending.color.float32[1] = color.y(); + pending.color.float32[2] = color.z(); + pending.color.float32[3] = color.w(); } if ((requestMask & GL_DEPTH_BUFFER_BIT) != 0) { - m_pendingClearDepth = depth; + pending.depth = depth; } if ((requestMask & GL_STENCIL_BUFFER_BIT) != 0) { - m_pendingClearStencil = stencil; + pending.stencil = stencil; } - m_pendingClearDrawFboExternalIndex = drawFboExternalIndex; - m_pendingClearTargetsDefaultFramebuffer = isDefaultFramebufferTarget; - m_pendingClearMask |= requestMask; + pending.drawFboExternalIndex = drawFboExternalIndex; + pending.targetsDefaultFramebuffer = isDefaultFramebufferTarget; + pending.mask |= requestMask; } Bool VulkanRenderer::ConsumePendingColorClear(VkClearColorValue& outClearColor) { - if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) == 0) { - return false; + for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { + if (!it->second.targetsDefaultFramebuffer || (it->second.mask & GL_COLOR_BUFFER_BIT) == 0) { + continue; + } + outClearColor = it->second.color; + it->second.mask &= ~GL_COLOR_BUFFER_BIT; + if (it->second.mask == 0) { + m_pendingClears.erase(it); + } + return true; } - - outClearColor = m_pendingClearColor; - m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT; - return true; + return false; } void VulkanRenderer::TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex) { @@ -284,7 +285,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkClearRect clearRect{}; clearRect.rect.offset = {0, 0}; - clearRect.rect.extent = m_swapchainObject.GetExtent(); + clearRect.rect.extent = m_activeRenderExtent; clearRect.baseArrayLayer = 0; clearRect.layerCount = 1; @@ -292,11 +293,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VulkanRenderer::RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil) { + if (m_activeDepthStencilFormat == VK_FORMAT_UNDEFINED) { + return; + } + VkImageAspectFlags aspectMask = 0; if ((mask & GL_DEPTH_BUFFER_BIT) != 0) { aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; } - if ((mask & GL_STENCIL_BUFFER_BIT) != 0 && HasStencilComponent(m_depthStencilFormat)) { + if ((mask & GL_STENCIL_BUFFER_BIT) != 0 && HasStencilComponent(m_activeDepthStencilFormat)) { aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; } if (aspectMask == 0) { @@ -310,7 +315,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkClearRect clearRect{}; clearRect.rect.offset = {0, 0}; - clearRect.rect.extent = m_swapchainObject.GetExtent(); + clearRect.rect.extent = m_activeRenderExtent; clearRect.baseArrayLayer = 0; clearRect.layerCount = 1; @@ -351,50 +356,59 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_depthStencilImageLayouts[imageIndex] = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - Bool VulkanRenderer::RecordOffscreenColorClear(VkCommandBuffer commandBuffer) { - if (!m_framebufferManager || !MG_State::pGLContext) { - return false; - } - - const auto fbo = MG_State::pGLContext->GetFramebufferObject(m_pendingClearDrawFboExternalIndex); - if (!fbo) { - MGLOG_W("RecordOffscreenColorClear skipped: draw FBO %u not found", m_pendingClearDrawFboExternalIndex); - return false; - } - if (!m_framebufferManager->EnsureOffscreenColorTarget(m_pendingClearDrawFboExternalIndex, *fbo)) { - return false; - } - return m_framebufferManager->ClearColor(commandBuffer, m_pendingClearDrawFboExternalIndex, m_pendingClearColor); - } - void VulkanRenderer::EnsureFrameRecordingStarted() { auto& frame = m_frameContext.GetCurrent(); - if (frame.isCommandRecording) { - return; - } - if (frame.hasCommandBufferRecorded) { MGLOG_W("EnsureFrameRecordingStarted skipped: current frame command buffer is already finalized"); return; } - VkCommandBuffer& commandBuffer = m_frameContext.BeginCommandRecording(); - if (m_uniformDescriptorBinder) { - m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + const auto drawFbo = MG_State::pGLContext + ? MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject() + : nullptr; + const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; + const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr; + const Bool drawTargetsDefault = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr); + const Uint drawFboExternalIndex = drawFbo ? drawFbo->GetExternalIndex() : (defaultFbo ? defaultFbo->GetExternalIndex() : 0U); + const Uint64 drawTargetKey = BuildPendingClearKey(drawFboExternalIndex, drawTargetsDefault); + + if (frame.isCommandRecording && m_isMainRenderPassActive) { + const Bool activeTargetMismatch = + (drawTargetsDefault != m_activeRenderTargetIsDefault) || + (!drawTargetsDefault && m_activeDrawFboExternalIndex != drawFboExternalIndex); + if (activeTargetMismatch) { + vkCmdEndRenderPass(frame.commandBuffer); + m_isMainRenderPassActive = false; + m_activeRenderPass = VK_NULL_HANDLE; + m_activeRenderExtent = {0, 0}; + m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; + } else { + return; + } } - if (m_pendingClearMask != 0 && !m_pendingClearTargetsDefaultFramebuffer) { - if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) { - if (!RecordOffscreenColorClear(commandBuffer)) { - MGLOG_W("Failed to clear non-default FBO %u color attachment", - m_pendingClearDrawFboExternalIndex); - } - m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT; + VkCommandBuffer* commandBufferPtr = nullptr; + if (frame.isCommandRecording) { + commandBufferPtr = &frame.commandBuffer; + } else { + commandBufferPtr = &m_frameContext.BeginCommandRecording(); + if (m_uniformDescriptorBinder) { + m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } - if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { - MGLOG_W("Depth/stencil clear on non-default FBO is not implemented yet (FBO %u)", - m_pendingClearDrawFboExternalIndex); - m_pendingClearMask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + VkCommandBuffer& commandBuffer = *commandBufferPtr; + + if (!drawTargetsDefault) { + if (!m_framebufferManager || !drawFbo) { + MGLOG_W("EnsureFrameRecordingStarted skipped: offscreen draw target is unavailable"); + return; + } + if (!m_framebufferManager->EnsureOffscreenColorTarget(drawFboExternalIndex, *drawFbo)) { + MGLOG_W("EnsureFrameRecordingStarted skipped: failed to materialize offscreen target for FBO %u", + drawFboExternalIndex); + return; } // This frame touched only offscreen resources. Present still requires @@ -420,33 +434,90 @@ namespace MobileGL::MG_Backend::DirectVulkan { 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } + + if (!m_framebufferManager->TransitionOffscreenColorToAttachment(commandBuffer, drawFboExternalIndex)) { + MGLOG_W("EnsureFrameRecordingStarted skipped: failed to transition offscreen FBO %u for attachment", + drawFboExternalIndex); + return; + } + + VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; + VkFramebuffer offscreenFramebuffer = VK_NULL_HANDLE; + VkExtent2D offscreenExtent{}; + VkFormat offscreenDepthStencilFormat = VK_FORMAT_UNDEFINED; + if (!m_framebufferManager->GetOffscreenRenderTarget(drawFboExternalIndex, offscreenRenderPass, + offscreenFramebuffer, offscreenExtent, + offscreenDepthStencilFormat)) { + MGLOG_W("EnsureFrameRecordingStarted skipped: offscreen render target for FBO %u is unavailable", + drawFboExternalIndex); + return; + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = offscreenRenderPass; + renderPassInfo.framebuffer = offscreenFramebuffer; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = offscreenExtent; + renderPassInfo.clearValueCount = 0; + renderPassInfo.pClearValues = nullptr; + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + m_isMainRenderPassActive = true; + m_activeRenderPass = offscreenRenderPass; + m_activeRenderExtent = offscreenExtent; + m_activeDepthStencilFormat = offscreenDepthStencilFormat; + m_activeRenderTargetIsDefault = false; + m_activeDrawFboExternalIndex = drawFboExternalIndex; + auto pendingIt = m_pendingClears.find(drawTargetKey); + if (pendingIt != m_pendingClears.end()) { + if ((pendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) { + RecordColorClear(commandBuffer, pendingIt->second.color); + pendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT; + } + if ((pendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { + RecordDepthStencilClear(commandBuffer, pendingIt->second.mask, pendingIt->second.depth, + pendingIt->second.stencil); + pendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + if (pendingIt->second.mask == 0) { + m_pendingClears.erase(pendingIt); + } + } return; } TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired); TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired); - const Bool useRenderPassClearValue = (m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0; - VkClearValue clearValues[1]{}; - if (useRenderPassClearValue) { - clearValues[0].color = m_pendingClearColor; - m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT; - } - VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = useRenderPassClearValue ? m_renderPassClear : m_renderPassLoad; + renderPassInfo.renderPass = m_renderPassLoad; renderPassInfo.framebuffer = m_framebuffers[m_imageIndexAcquired]; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = m_swapchainObject.GetExtent(); - renderPassInfo.clearValueCount = useRenderPassClearValue ? 1 : 0; - renderPassInfo.pClearValues = useRenderPassClearValue ? clearValues : nullptr; + renderPassInfo.clearValueCount = 0; + renderPassInfo.pClearValues = nullptr; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); m_isMainRenderPassActive = true; - - if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { - RecordDepthStencilClear(commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil); - m_pendingClearMask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + m_activeRenderPass = renderPassInfo.renderPass; + m_activeRenderExtent = renderPassInfo.renderArea.extent; + m_activeDepthStencilFormat = m_depthStencilFormat; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = drawFboExternalIndex; + auto pendingIt = m_pendingClears.find(drawTargetKey); + if (pendingIt != m_pendingClears.end()) { + if ((pendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) { + RecordColorClear(commandBuffer, pendingIt->second.color); + pendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT; + } + if ((pendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { + RecordDepthStencilClear(commandBuffer, pendingIt->second.mask, pendingIt->second.depth, + pendingIt->second.stencil); + pendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + if (pendingIt->second.mask == 0) { + m_pendingClears.erase(pendingIt); + } } } @@ -459,6 +530,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_isMainRenderPassActive) { vkCmdEndRenderPass(frame.commandBuffer); m_isMainRenderPassActive = false; + m_activeRenderPass = VK_NULL_HANDLE; + m_activeRenderExtent = {0, 0}; + m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; } m_frameContext.EndCommandRecording(); } @@ -559,7 +635,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkCommandBuffer& commandBuffer = frame.commandBuffer; - const auto swapchainExtent = m_swapchainObject.GetExtent(); + const auto activeExtent = m_activeRenderExtent; if (payload.program == nullptr) { MGLOG_W("DrawArrays skipped: no current program is bound"); @@ -601,15 +677,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = static_cast(swapchainExtent.width); - viewport.height = static_cast(swapchainExtent.height); + viewport.width = static_cast(activeExtent.width); + viewport.height = static_cast(activeExtent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(commandBuffer, 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = {0, 0}; - scissor.extent = swapchainExtent; + scissor.extent = activeExtent; vkCmdSetScissor(commandBuffer, 0, 1, &scissor); vkCmdDraw(commandBuffer, static_cast(payload.count), 1, static_cast(payload.first), 0); @@ -687,7 +763,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkCommandBuffer& commandBuffer = frame.commandBuffer; - const auto swapchainExtent = m_swapchainObject.GetExtent(); + const auto activeExtent = m_activeRenderExtent; vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); if (m_uniformDescriptorBinder && @@ -711,15 +787,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = static_cast(swapchainExtent.width); - viewport.height = static_cast(swapchainExtent.height); + viewport.width = static_cast(activeExtent.width); + viewport.height = static_cast(activeExtent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(commandBuffer, 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = {0, 0}; - scissor.extent = swapchainExtent; + scissor.extent = activeExtent; vkCmdSetScissor(commandBuffer, 0, 1, &scissor); vkCmdBindIndexBuffer(commandBuffer, m_indexBuffer.GetHandle(), 0, vkIndexType); @@ -756,6 +832,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_isMainRenderPassActive) { vkCmdEndRenderPass(commandBuffer); m_isMainRenderPassActive = false; + m_activeRenderPass = VK_NULL_HANDLE; + m_activeRenderExtent = {0, 0}; + m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; } if (!m_framebufferManager || !MG_State::pGLContext) { @@ -865,37 +946,166 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), "Present, acquired image index out of range"); auto& frame = m_frameContext.GetCurrent(); - if (m_pendingClearMask != 0 && frame.hasCommandBufferRecorded) { - MGLOG_W("Dropping pending clear for current frame because command buffer is already finalized"); - m_pendingClearMask = 0; - } else if (m_pendingClearMask != 0 && frame.isCommandRecording) { - if (m_pendingClearTargetsDefaultFramebuffer && m_isMainRenderPassActive) { - if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) { - RecordColorClear(frame.commandBuffer, m_pendingClearColor); + if (!m_pendingClears.empty() && frame.hasCommandBufferRecorded) { + MGLOG_W("Dropping pending clears for current frame because command buffer is already finalized"); + m_pendingClears.clear(); + } else if (!m_pendingClears.empty()) { + auto activateTarget = [&](Bool targetIsDefault, Uint targetFboExternalIndex) -> Bool { + const Bool alreadyMatched = + frame.isCommandRecording && + m_isMainRenderPassActive && + (targetIsDefault == m_activeRenderTargetIsDefault) && + (targetIsDefault || targetFboExternalIndex == m_activeDrawFboExternalIndex); + if (alreadyMatched) { + return true; } - if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { - RecordDepthStencilClear(frame.commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil); + if (frame.hasCommandBufferRecorded) { + return false; } - m_pendingClearMask = 0; - } else if (!m_pendingClearTargetsDefaultFramebuffer) { - if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) { - if (!RecordOffscreenColorClear(frame.commandBuffer)) { - MGLOG_W("Present: failed to clear non-default FBO %u color attachment", - m_pendingClearDrawFboExternalIndex); + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + if (m_uniformDescriptorBinder) { + m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } - m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT; } - if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { - MGLOG_W("Present: depth/stencil clear on non-default FBO is not implemented yet (FBO %u)", - m_pendingClearDrawFboExternalIndex); - m_pendingClearMask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + VkCommandBuffer commandBuffer = frame.commandBuffer; + + if (m_isMainRenderPassActive) { + vkCmdEndRenderPass(commandBuffer); + m_isMainRenderPassActive = false; + m_activeRenderPass = VK_NULL_HANDLE; + m_activeRenderExtent = {0, 0}; + m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; + } + + if (targetIsDefault) { + TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired); + TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired); + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = m_renderPassLoad; + renderPassInfo.framebuffer = m_framebuffers[m_imageIndexAcquired]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = m_swapchainObject.GetExtent(); + renderPassInfo.clearValueCount = 0; + renderPassInfo.pClearValues = nullptr; + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + m_isMainRenderPassActive = true; + m_activeRenderPass = renderPassInfo.renderPass; + m_activeRenderExtent = renderPassInfo.renderArea.extent; + m_activeDepthStencilFormat = m_depthStencilFormat; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; + return true; + } + + if (!m_framebufferManager || !MG_State::pGLContext) { + return false; + } + const auto pendingFbo = MG_State::pGLContext->GetFramebufferObject(targetFboExternalIndex); + if (!pendingFbo) { + return false; + } + if (!m_framebufferManager->EnsureOffscreenColorTarget(targetFboExternalIndex, *pendingFbo)) { + return false; + } + if (!m_framebufferManager->TransitionOffscreenColorToAttachment(commandBuffer, targetFboExternalIndex)) { + return false; + } + + VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; + VkFramebuffer offscreenFramebuffer = VK_NULL_HANDLE; + VkExtent2D offscreenExtent{}; + VkFormat offscreenDepthStencilFormat = VK_FORMAT_UNDEFINED; + if (!m_framebufferManager->GetOffscreenRenderTarget(targetFboExternalIndex, offscreenRenderPass, + offscreenFramebuffer, offscreenExtent, + offscreenDepthStencilFormat)) { + return false; + } + + const auto swapchainOldLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired); + if (swapchainOldLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && + swapchainOldLayout != VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) { + VkImageMemoryBarrier presentBarrier{}; + presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + presentBarrier.srcAccessMask = 0; + presentBarrier.dstAccessMask = 0; + presentBarrier.oldLayout = swapchainOldLayout; + presentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + presentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + presentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + presentBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired); + presentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + presentBarrier.subresourceRange.baseMipLevel = 0; + presentBarrier.subresourceRange.levelCount = 1; + presentBarrier.subresourceRange.baseArrayLayer = 0; + presentBarrier.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + } + + VkRenderPassBeginInfo offscreenPassInfo{}; + offscreenPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + offscreenPassInfo.renderPass = offscreenRenderPass; + offscreenPassInfo.framebuffer = offscreenFramebuffer; + offscreenPassInfo.renderArea.offset = {0, 0}; + offscreenPassInfo.renderArea.extent = offscreenExtent; + offscreenPassInfo.clearValueCount = 0; + offscreenPassInfo.pClearValues = nullptr; + vkCmdBeginRenderPass(commandBuffer, &offscreenPassInfo, VK_SUBPASS_CONTENTS_INLINE); + m_isMainRenderPassActive = true; + m_activeRenderPass = offscreenRenderPass; + m_activeRenderExtent = offscreenExtent; + m_activeDepthStencilFormat = offscreenDepthStencilFormat; + m_activeRenderTargetIsDefault = false; + m_activeDrawFboExternalIndex = targetFboExternalIndex; + return true; + }; + + while (!m_pendingClears.empty()) { + auto pendingIt = m_pendingClears.end(); + if (m_isMainRenderPassActive) { + const Uint64 activeKey = + BuildPendingClearKey(m_activeDrawFboExternalIndex, m_activeRenderTargetIsDefault); + pendingIt = m_pendingClears.find(activeKey); + } + if (pendingIt == m_pendingClears.end()) { + pendingIt = m_pendingClears.begin(); + } + + const auto pendingTarget = pendingIt->second; + if (!activateTarget(pendingTarget.targetsDefaultFramebuffer, pendingTarget.drawFboExternalIndex)) { + MGLOG_W("Present: dropping pending clear because target activation failed (FBO %u, default=%d)", + pendingTarget.drawFboExternalIndex, pendingTarget.targetsDefaultFramebuffer); + m_pendingClears.erase(pendingIt); + continue; + } + + const Uint64 activeKey = + BuildPendingClearKey(m_activeDrawFboExternalIndex, m_activeRenderTargetIsDefault); + auto activePendingIt = m_pendingClears.find(activeKey); + if (activePendingIt == m_pendingClears.end()) { + continue; + } + + if ((activePendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) { + RecordColorClear(frame.commandBuffer, activePendingIt->second.color); + activePendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT; + } + if ((activePendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { + RecordDepthStencilClear(frame.commandBuffer, activePendingIt->second.mask, + activePendingIt->second.depth, activePendingIt->second.stencil); + activePendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + if (activePendingIt->second.mask == 0) { + m_pendingClears.erase(activePendingIt); } - } else { - MGLOG_W("Present: pending default-FBO clear cannot execute because no render pass is active"); - m_pendingClearMask = 0; } - } else if (m_pendingClearMask != 0) { - EnsureFrameRecordingStarted(); } EndFrameRecordingIfNeeded(); @@ -1312,6 +1522,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { return 0; } + Uint64 VulkanRenderer::BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer) { + return (static_cast(targetsDefaultFramebuffer ? 1 : 0) << 63) | + static_cast(drawFboExternalIndex); + } + Bool VulkanRenderer::HasStencilComponent(VkFormat format) { return format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT; } @@ -1630,6 +1845,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_frameContext.GetCurrent().hasCommandBufferRecorded = false; } m_isMainRenderPassActive = false; + m_activeRenderPass = VK_NULL_HANDLE; + m_activeRenderExtent = {0, 0}; + m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + m_activeRenderTargetIsDefault = true; + m_activeDrawFboExternalIndex = 0; + m_pendingClears.clear(); } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 1a9c0554..42241e12 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -74,6 +74,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { void RecreateSwapchain(); private: + struct PendingClearState { + GLbitfield mask = 0; + VkClearColorValue color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + Float depth = 1.0f; + Uint32 stencil = 0; + Uint drawFboExternalIndex = 0; + Bool targetsDefaultFramebuffer = true; + }; + struct QueueFamilyIndices { Int32 graphicsFamily = -1; Int32 presentFamily = -1; @@ -129,13 +138,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; - GLbitfield m_pendingClearMask = 0; - VkClearColorValue m_pendingClearColor = {{0.0f, 0.0f, 0.0f, 1.0f}}; - Float m_pendingClearDepth = 1.0f; - Uint32 m_pendingClearStencil = 0; - Uint m_pendingClearDrawFboExternalIndex = 0; - Bool m_pendingClearTargetsDefaultFramebuffer = true; + UnorderedMap m_pendingClears; Bool m_isMainRenderPassActive = false; + VkRenderPass m_activeRenderPass = VK_NULL_HANDLE; + VkExtent2D m_activeRenderExtent = {0, 0}; + VkFormat m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED; + Bool m_activeRenderTargetIsDefault = true; + Uint m_activeDrawFboExternalIndex = 0; UniquePtr m_pipelineFactory; UniquePtr m_programFactory; @@ -167,7 +176,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex); void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor); void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil); - Bool RecordOffscreenColorClear(VkCommandBuffer commandBuffer); void EndFrameRecordingIfNeeded(); Bool UploadAndBindVertexStreams( const VertexInputStateFactory::BackendVertexInputState& vertexInputState, @@ -185,6 +193,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device); static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface, const PhysicalDevice& compareWithDevice, PhysicalDevice& outBetterDevice); Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const; + static Uint64 BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer); static Bool HasStencilComponent(VkFormat format); static VkFormat FindSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice); static constexpr VkDynamicState s_dynamicStates[] = {