From e132e9edc1cc9fb46bfd31bdf933f47e9d0439e2 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Feb 2026 19:39:10 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): inital FBO implementation, and blit framebuffer --- CMakeLists.txt | 1 + .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 45 ++- .../DirectVulkan/Renderer/SwapchainObject.cpp | 15 +- .../Renderer/VkFramebufferManager.cpp | 258 ++++++++++++++++++ .../Renderer/VkFramebufferManager.h | 58 ++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 234 +++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 11 +- .../GLImpl/Framebuffer/GL_Framebuffer.cpp | 66 +++++ .../MG_Test/Backend/DirectVulkan/TestExec.cpp | 54 ++-- .../ShaderTranspiler/ShaderCompiler.cpp | 13 +- 10 files changed, 717 insertions(+), 38 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f86eabd..7692c6b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,6 +219,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp MobileGL/MG_State/GLState/Core.cpp MobileGL/MG_State/GLState/ErrorState/Error.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index e2255cf3..504d3ac9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -8,6 +8,7 @@ #include "DirectVulkan.h" #include "MG_State/GLState/Core.h" +#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr pVulkanRenderer = nullptr; @@ -16,10 +17,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context"); + const auto& drawFboSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw); + const auto drawFbo = drawFboSlot.GetBoundObject(); + const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; + const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr; + const Bool isDefaultFboTarget = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr); + Uint drawFboExternalIndex = 0; + if (drawFbo) { + drawFboExternalIndex = drawFbo->GetExternalIndex(); + } else if (defaultFbo) { + drawFboExternalIndex = defaultFbo->GetExternalIndex(); + } + const auto& clearColor = MG_State::pGLContext->GetClearColor(); const auto clearDepth = MG_State::pGLContext->GetClearDepth(); const auto clearStencil = static_cast(MG_State::pGLContext->GetClearStencil()); - pVulkanRenderer->RequestClear(mask, clearColor, clearDepth, clearStencil); + pVulkanRenderer->RequestClear(mask, clearColor, clearDepth, clearStencil, drawFboExternalIndex, + isDefaultFboTarget); } void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { @@ -177,5 +191,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->DrawArrays(payload); } + + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::BlitFramebuffer called with null GL context"); + + const auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; + const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr; + + const Bool readIsDefault = (readFbo == defaultFbo) || (readFbo == nullptr && defaultFbo != nullptr); + const Bool drawIsDefault = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr); + Uint readFboExternalIndex = 0; + Uint drawFboExternalIndex = 0; + if (readFbo) { + readFboExternalIndex = readFbo->GetExternalIndex(); + } else if (defaultFbo) { + readFboExternalIndex = defaultFbo->GetExternalIndex(); + } + if (drawFbo) { + drawFboExternalIndex = drawFbo->GetExternalIndex(); + } else if (defaultFbo) { + drawFboExternalIndex = defaultFbo->GetExternalIndex(); + } + + pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter, + readFboExternalIndex, drawFboExternalIndex, readIsDefault, drawIsDefault); + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp index b15b881d..7d8d4cd5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -128,7 +128,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace; createInfo.imageExtent = swapchainCaps.currentExtent; createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + const VkImageUsageFlags requiredImageUsage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + MOBILEGL_ASSERT((swapchainCaps.supportedUsageFlags & requiredImageUsage) == requiredImageUsage, + "Swapchain does not support required usage flags (COLOR_ATTACHMENT | TRANSFER_DST). " + "supportedUsageFlags=0x%x", + static_cast(swapchainCaps.supportedUsageFlags)); + + VkImageUsageFlags imageUsage = requiredImageUsage; + if ((swapchainCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0) { + imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + createInfo.imageUsage = imageUsage; + MGLOG_I("Swapchain imageUsage = 0x%x (supportedUsageFlags = 0x%x)", static_cast(createInfo.imageUsage), + static_cast(swapchainCaps.supportedUsageFlags)); Uint32 queueFamilyIndices[] = {graphicsQueueFamily, presentQueueFamily}; if (graphicsQueueFamily != presentQueueFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp new file mode 100644 index 00000000..633edc14 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp @@ -0,0 +1,258 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.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 "VkFramebufferManager.h" + +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + Bool VkFramebufferManager::Initialize(const InitInfo& initInfo) { + m_device = initInfo.device; + m_physicalDevice = initInfo.physicalDevice; + return m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE; + } + + void VkFramebufferManager::Shutdown() { + for (auto& [_, target] : m_offscreenColorTargets) { + DestroyOffscreenColorTarget(target); + } + m_offscreenColorTargets.clear(); + m_device = VK_NULL_HANDLE; + m_physicalDevice = VK_NULL_HANDLE; + } + + Bool VkFramebufferManager::EnsureOffscreenColorTarget(Uint glFboExternalIndex, + const MG_State::GLState::FramebufferObject& glFbo) { + const auto& colorAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Color0); + if (!colorAttachment.IsValid() || colorAttachment.IsEmpty()) { + MGLOG_W("VkFramebufferManager: FBO %u has no valid COLOR0 attachment", glFboExternalIndex); + return false; + } + + const auto objectVersion = glFbo.GetObjectVersion(); + auto& target = m_offscreenColorTargets[glFboExternalIndex]; + if (target.image != VK_NULL_HANDLE && target.glObjectVersion == objectVersion) { + return true; + } + + return RecreateOffscreenColorTarget(target, colorAttachment, objectVersion); + } + + Bool VkFramebufferManager::ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex, + const VkClearColorValue& clearColor) { + auto it = m_offscreenColorTargets.find(glFboExternalIndex); + if (it == m_offscreenColorTargets.end()) { + MGLOG_W("VkFramebufferManager::ClearColor skipped: no offscreen target for FBO %u", 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; + } + + Bool VkFramebufferManager::TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, + Uint glFboExternalIndex) { + auto it = m_offscreenColorTargets.find(glFboExternalIndex); + if (it == m_offscreenColorTargets.end()) { + MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferSrc skipped: FBO %u not found", + glFboExternalIndex); + return false; + } + return TransitionColorTargetForBlitSrc(commandBuffer, it->second); + } + + Bool VkFramebufferManager::GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, + VkExtent2D& outExtent) const { + auto it = m_offscreenColorTargets.find(glFboExternalIndex); + if (it == m_offscreenColorTargets.end() || it->second.image == VK_NULL_HANDLE) { + return false; + } + outImage = it->second.image; + outExtent = it->second.extent; + return true; + } + + Bool VkFramebufferManager::RecreateOffscreenColorTarget( + OffscreenColorTarget& target, const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, + Uint16 glObjectVersion) { + DestroyOffscreenColorTarget(target); + + const auto size = colorAttachment.GetSize(); + if (size.x() <= 0 || size.y() <= 0) { + MGLOG_W("VkFramebufferManager: COLOR0 attachment size is invalid (%d, %d)", size.x(), size.y()); + return false; + } + + const VkFormat format = ResolveColorFormat(colorAttachment); + if (format == VK_FORMAT_UNDEFINED) { + MGLOG_W("VkFramebufferManager: COLOR0 attachment format is unsupported for Vulkan clear"); + return false; + } + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = static_cast(size.x()); + imageInfo.extent.height = static_cast(size.y()); + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &target.image), "vkCreateImage(offscreen color)"); + + VkMemoryRequirements memoryRequirements{}; + vkGetImageMemoryRequirements(m_device, target.image, &memoryRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memoryRequirements.size; + allocInfo.memoryTypeIndex = FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &target.memory), "vkAllocateMemory(offscreen color)"); + VK_VERIFY(vkBindImageMemory(m_device, target.image, target.memory, 0), "vkBindImageMemory(offscreen color)"); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = target.image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &target.imageView), "vkCreateImageView(offscreen color)"); + + target.layout = VK_IMAGE_LAYOUT_UNDEFINED; + target.extent = {static_cast(size.x()), static_cast(size.y())}; + target.format = format; + target.glObjectVersion = glObjectVersion; + return true; + } + + void VkFramebufferManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) { + if (target.imageView != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, target.imageView, nullptr); + target.imageView = VK_NULL_HANDLE; + } + if (target.image != VK_NULL_HANDLE) { + vkDestroyImage(m_device, target.image, nullptr); + target.image = VK_NULL_HANDLE; + } + if (target.memory != VK_NULL_HANDLE) { + vkFreeMemory(m_device, target.memory, nullptr); + target.memory = VK_NULL_HANDLE; + } + target.layout = VK_IMAGE_LAYOUT_UNDEFINED; + target.extent = {0, 0}; + target.format = VK_FORMAT_UNDEFINED; + target.glObjectVersion = 0; + } + + Bool VkFramebufferManager::TransitionColorTargetForClear(VkCommandBuffer commandBuffer, OffscreenColorTarget& target) { + if (target.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + return true; + } + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.oldLayout = target.layout; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = target.image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); + target.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + return true; + } + + Bool VkFramebufferManager::TransitionColorTargetForBlitSrc(VkCommandBuffer commandBuffer, + OffscreenColorTarget& target) { + if (target.layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + return true; + } + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.oldLayout = target.layout; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = target.image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); + target.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + return true; + } + + Uint32 VkFramebufferManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const { + VkPhysicalDeviceMemoryProperties memoryProperties{}; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); + for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) { + if ((typeFilter & (1U << i)) && + (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + MOBILEGL_ASSERT(false, "VkFramebufferManager::FindMemoryType failed"); + return 0; + } + + VkFormat VkFramebufferManager::ResolveColorFormat( + const MG_State::GLState::FramebufferAttachmentObject& colorAttachment) { + TextureInternalFormat internalFormat = TextureInternalFormat::Unknown; + if (colorAttachment.IsTexture()) { + const auto texture = colorAttachment.GetTexture(); + internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown; + } else if (colorAttachment.IsRenderbuffer()) { + const auto renderbuffer = colorAttachment.GetRenderbuffer(); + internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown; + } + + switch (internalFormat) { + case TextureInternalFormat::RGBA: + case TextureInternalFormat::RGBA8: + case TextureInternalFormat::SRGB8Alpha8: + return VK_FORMAT_R8G8B8A8_UNORM; + default: + 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 new file mode 100644 index 00000000..08530e86 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h @@ -0,0 +1,58 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.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 "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + class VkFramebufferManager { + public: + struct InitInfo { + VkDevice device = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + }; + + VkFramebufferManager() = default; + ~VkFramebufferManager() = default; + + Bool Initialize(const InitInfo& initInfo); + void Shutdown(); + + Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo); + Bool ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex, const VkClearColorValue& clearColor); + Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex); + Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const; + + private: + struct OffscreenColorTarget { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkExtent2D extent = {0, 0}; + VkFormat format = VK_FORMAT_UNDEFINED; + Uint16 glObjectVersion = 0; + }; + + Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target, + const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, + Uint16 glObjectVersion); + void DestroyOffscreenColorTarget(OffscreenColorTarget& target); + Bool TransitionColorTargetForClear(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); + + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + UnorderedMap m_offscreenColorTargets; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index ef4241da..a80ad49d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -10,6 +10,7 @@ #include "VertexInputStateFactory.h" #include "VertexInputStateBuilder.h" +#include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" namespace MobileGL::MG_Backend::DirectVulkan { @@ -130,6 +131,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_uniformDescriptorBinder.reset(); } m_vertexInputStateFactory = MakeUnique(m_config); + m_framebufferManager = MakeUnique(); + if (!m_framebufferManager->Initialize({m_device, m_physicalDevice.handle})) { + MGLOG_E("VkFramebufferManager initialization failed. Offscreen FBO clear path is disabled."); + m_framebufferManager.reset(); + } PrepareDemoPipeline(); CreateFrameContexts(); @@ -147,6 +153,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_pipelineFactory.reset(); m_programFactory.reset(); m_vertexInputStateFactory.reset(); + if (m_framebufferManager) { + m_framebufferManager->Shutdown(); + m_framebufferManager.reset(); + } for (auto& vertexBuffer : m_vertexBuffers) { if (vertexBuffer) { vertexBuffer->Destroy(); @@ -197,13 +207,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("VulkanRenderer shut down completed"); } - void VulkanRenderer::RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil) { + void VulkanRenderer::RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil, + Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget) { const GLbitfield supportedMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; const GLbitfield requestMask = (mask & supportedMask); if (requestMask == 0) { 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; + } + if ((requestMask & GL_COLOR_BUFFER_BIT) != 0) { m_pendingClearColor.float32[0] = color.x(); m_pendingClearColor.float32[1] = color.y(); @@ -216,6 +235,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if ((requestMask & GL_STENCIL_BUFFER_BIT) != 0) { m_pendingClearStencil = stencil; } + m_pendingClearDrawFboExternalIndex = drawFboExternalIndex; + m_pendingClearTargetsDefaultFramebuffer = isDefaultFramebufferTarget; m_pendingClearMask |= requestMask; } @@ -330,6 +351,22 @@ 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) { @@ -345,6 +382,47 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_uniformDescriptorBinder) { m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } + + 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; + } + 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); + } + + // This frame touched only offscreen resources. Present still requires + // the acquired swapchain image to be in PRESENT layout. + 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); + } + return; + } + TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired); TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired); @@ -648,6 +726,124 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdDrawIndexed(commandBuffer, static_cast(payload.drawArray.count), 1, 0, 0, 0); } + Bool VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter, + Uint readFboExternalIndex, Uint drawFboExternalIndex, + Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer) { + if ((mask & GL_COLOR_BUFFER_BIT) == 0 || (mask & ~GL_COLOR_BUFFER_BIT) != 0) { + MGLOG_W("BlitFramebuffer skipped: only GL_COLOR_BUFFER_BIT is supported in Vulkan backend for now"); + return false; + } + if (readIsDefaultFramebuffer || !drawIsDefaultFramebuffer) { + MGLOG_W("BlitFramebuffer skipped: currently only read=offscreen FBO and draw=default FBO is supported"); + return false; + } + + auto& frame = m_frameContext.GetCurrent(); + if (frame.hasCommandBufferRecorded) { + MGLOG_W("BlitFramebuffer skipped: current frame command buffer is already finalized"); + return false; + } + + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + if (m_uniformDescriptorBinder) { + m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + } + + VkCommandBuffer commandBuffer = frame.commandBuffer; + if (m_isMainRenderPassActive) { + vkCmdEndRenderPass(commandBuffer); + m_isMainRenderPassActive = false; + } + + if (!m_framebufferManager || !MG_State::pGLContext) { + MGLOG_W("BlitFramebuffer skipped: framebuffer manager is unavailable"); + return false; + } + + (void)drawFboExternalIndex; + const auto readFbo = MG_State::pGLContext->GetFramebufferObject(readFboExternalIndex); + if (!readFbo) { + MGLOG_W("BlitFramebuffer skipped: read FBO %u not found", readFboExternalIndex); + return false; + } + if (!m_framebufferManager->EnsureOffscreenColorTarget(readFboExternalIndex, *readFbo)) { + return false; + } + if (!m_framebufferManager->TransitionOffscreenColorToTransferSrc(commandBuffer, readFboExternalIndex)) { + return false; + } + + VkImage srcImage = VK_NULL_HANDLE; + VkExtent2D srcExtent{}; + if (!m_framebufferManager->GetOffscreenColorImage(readFboExternalIndex, srcImage, srcExtent)) { + MGLOG_W("BlitFramebuffer skipped: offscreen image for FBO %u is unavailable", readFboExternalIndex); + return false; + } + + const auto swapchainOldLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired); + if (swapchainOldLayout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + VkImageMemoryBarrier toTransferDstBarrier{}; + toTransferDstBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toTransferDstBarrier.srcAccessMask = 0; + toTransferDstBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toTransferDstBarrier.oldLayout = swapchainOldLayout; + toTransferDstBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toTransferDstBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDstBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDstBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired); + toTransferDstBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toTransferDstBarrier.subresourceRange.baseMipLevel = 0; + toTransferDstBarrier.subresourceRange.levelCount = 1; + toTransferDstBarrier.subresourceRange.baseArrayLayer = 0; + toTransferDstBarrier.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &toTransferDstBarrier); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + } + + VkImageBlit blitRegion{}; + blitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blitRegion.srcSubresource.mipLevel = 0; + blitRegion.srcSubresource.baseArrayLayer = 0; + blitRegion.srcSubresource.layerCount = 1; + blitRegion.srcOffsets[0] = {srcX0, srcY0, 0}; + blitRegion.srcOffsets[1] = {srcX1, srcY1, 1}; + blitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blitRegion.dstSubresource.mipLevel = 0; + blitRegion.dstSubresource.baseArrayLayer = 0; + blitRegion.dstSubresource.layerCount = 1; + blitRegion.dstOffsets[0] = {dstX0, dstY0, 0}; + blitRegion.dstOffsets[1] = {dstX1, dstY1, 1}; + + vkCmdBlitImage(commandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + m_swapchainObject.GetImage(m_imageIndexAcquired), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blitRegion, (filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST)); + + VkImageMemoryBarrier toPresentBarrier{}; + toPresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toPresentBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toPresentBarrier.dstAccessMask = 0; + toPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toPresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + toPresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toPresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toPresentBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired); + toPresentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toPresentBarrier.subresourceRange.baseMipLevel = 0; + toPresentBarrier.subresourceRange.levelCount = 1; + toPresentBarrier.subresourceRange.baseArrayLayer = 0; + toPresentBarrier.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, nullptr, 0, nullptr, 1, &toPresentBarrier); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + (void)srcExtent; + return true; + } + void VulkanRenderer::Render() { // Route test rendering through the same frame-start logic used by draw calls, // so pending glClear() state can be consumed consistently. @@ -669,17 +865,35 @@ 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.isCommandRecording && m_isMainRenderPassActive) { - if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) { - RecordColorClear(frame.commandBuffer, m_pendingClearColor); - } - if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { - RecordDepthStencilClear(frame.commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil); - } - m_pendingClearMask = 0; - } else if (m_pendingClearMask != 0 && frame.hasCommandBufferRecorded) { + 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_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { + RecordDepthStencilClear(frame.commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil); + } + 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); + } + 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); + } + } 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(); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 26f6f998..1a9c0554 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -15,6 +15,7 @@ #include "UniformDescriptorBinder.h" #include "VertexInputStateFactory.h" #include "VkBufferObject.h" +#include "VkFramebufferManager.h" #include "MG_Util/Math/VectorTypes.h" #include #include @@ -58,11 +59,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Initialize(); void Shutdown(); - void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil); + void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil, + Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget); Bool ConsumePendingColorClear(VkClearColorValue& outClearColor); void EnsureFrameRecordingStarted(); void DrawArrays(const DrawArrayPayload& payload); void DrawElements(const DrawElementPayload& payload); + Bool BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter, Uint readFboExternalIndex, + Uint drawFboExternalIndex, Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer); void Render(); void Present(); @@ -128,12 +133,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; Bool m_isMainRenderPassActive = false; UniquePtr m_pipelineFactory; UniquePtr m_programFactory; UniquePtr m_uniformDescriptorBinder; UniquePtr m_vertexInputStateFactory; + UniquePtr m_framebufferManager; void CreateInstance(); VkResult SetupDebugMessenger(); @@ -159,6 +167,7 @@ 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, diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index d463fca3..4230c73d 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -19,6 +19,9 @@ #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES #include #endif +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN +#include +#endif namespace MobileGL { namespace MG_Impl::GLImpl { @@ -27,9 +30,69 @@ namespace MobileGL { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES MG_Backend::DirectGLES::BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + MG_Backend::DirectVulkan::BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, + filter); #endif } + Bool BlitFramebuffer_State(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { + (void)srcX0; + (void)srcY0; + (void)srcX1; + (void)srcY1; + (void)dstX0; + (void)dstY0; + (void)dstX1; + (void)dstY1; + + constexpr GLbitfield kSupportedMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + if ((mask & kSupportedMask) == 0 || (mask & ~kSupportedMask) != 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeShared("MG_Impl/GLImpl", __func__, + "`mask` must contain COLOR/DEPTH/STENCIL bits only.")); + return false; + } + + if (filter != GL_NEAREST && filter != GL_LINEAR) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeShared("MG_Impl/GLImpl", __func__, + "`filter` must be GL_NEAREST or GL_LINEAR.")); + return false; + } + + if ((mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0 && filter != GL_NEAREST) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", __func__, + "Depth/stencil blit requires GL_NEAREST filter.")); + return false; + } + + const auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + if (!readFbo || !drawFbo) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeShared("MG_Impl/GLImpl", __func__, + "Read/draw framebuffer binding is null.")); + return false; + } + + if (!readFbo->CheckCompleteness() || !drawFbo->CheckCompleteness()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidFramebufferOperation, + MakeShared("MG_Impl/GLImpl", __func__, + "Read/draw framebuffer is incomplete.")); + return false; + } + + return true; + } + void SampleMaski_State(GLuint maskNumber, GLbitfield mask) { // TODO: implement } @@ -748,6 +811,9 @@ namespace MobileGL { void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { + if (!BlitFramebuffer_State(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)) { + return; + } BlitFramebuffer_Backend(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } diff --git a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp index 52c88706..715ff0ad 100644 --- a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp +++ b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp @@ -132,6 +132,22 @@ int main() { MobileGL::MG_Initialize(); + GLuint offscreenTex = 0; + GLuint offscreenFbo = 0; + glGenTextures(1, &offscreenTex); + glBindTexture(GL_TEXTURE_2D, offscreenTex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &offscreenFbo); + glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, offscreenTex, 0); + const GLenum offscreenFboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); + std::cout << "Offscreen FBO status = 0x" << std::hex << offscreenFboStatus << std::dec << std::endl; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLuint vao = 0; glGenVertexArrays(1, &vao); glBindVertexArray(vao); @@ -246,30 +262,30 @@ void main() { glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUbo); glBindBuffer(GL_UNIFORM_BUFFER, 0); - const auto startTime = std::chrono::steady_clock::now(); - int i = 0; while(!glfwWindowShouldClose(window)) { glfwPollEvents(); - if (i % 1000 > 500) + const bool useOffscreenPath = ((i / 100) % 2) == 0; + if (useOffscreenPath) { + glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo); glClearColor(1.0f, 0.0f, 0.0f, 1.0f); - else - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - if (i % 500 > 250) { - glUseProgram(program); - const auto now = std::chrono::steady_clock::now(); - const float t = std::chrono::duration(now - startTime).count(); - glUniform1f(iTimeLocation, t); + glClear(GL_COLOR_BUFFER_BIT); - uboColorData[0] = 0.5f + 0.5f * std::sin(t * 0.7f); - uboColorData[1] = 0.5f + 0.5f * std::sin(t * 1.1f + 1.2f); - uboColorData[2] = 0.5f + 0.5f * std::sin(t * 1.5f + 2.4f); - glBindBuffer(GL_UNIFORM_BUFFER, colorUbo); - glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uboColorData), uboColorData); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr); + glBindFramebuffer(GL_READ_FRAMEBUFFER, offscreenFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBlitFramebuffer(0, 0, 256, 256, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } else { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + + if (i % 100 == 0) { + std::cout << "frame=" << i + << " path=" << (useOffscreenPath ? "offscreen-clear+blit" : "default-clear") + << std::endl; } eglSwapBuffers(display, surface); ++i; @@ -281,6 +297,8 @@ void main() { glDeleteBuffers(1, &colorVbo); glDeleteBuffers(1, &ebo); glDeleteVertexArrays(1, &vao); + glDeleteFramebuffers(1, &offscreenFbo); + glDeleteTextures(1, &offscreenTex); glfwDestroyWindow(window); diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 803a93f3..a17d68e3 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -147,14 +147,13 @@ namespace MobileGL { if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); - tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); + tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); } else { tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); - tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); - tshader->setEnvTarget(glslang::EShTargetSpv, - ((attrib.flags & ShaderCompileBits::EmitDiscardAsDemote) - ? glslang::EShTargetSpv_1_6 - : glslang::EShTargetSpv_1_5)); + // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path, + // so generated SPIR-V must not exceed SPIR-V 1.3. + tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); + tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and // gl_InstanceID? } @@ -233,7 +232,7 @@ namespace MobileGL { OptimizerOptions options; options.set_run_validator(false); - Optimizer optimizer(SPV_ENV_UNIVERSAL_1_5); + Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(EliminateFloatEqualsZeroPass::CreateEliminateFloatEqualsZeroPass());