From f0f6d1e5faf4ece6c98505b75aee4e2a73b09c2b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 23 Jun 2026 20:00:43 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectVulkan): fix prerotated default framebuffer --- .../DirectVulkan/Renderer/SwapchainObject.cpp | 8 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 174 +++++++++++++++--- 2 files changed, 150 insertions(+), 32 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp index 17023aba..c39ace69 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -184,6 +184,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { swapchainCaps.minImageExtent.height, swapchainCaps.maxImageExtent.height); } + const VkExtent2D defaultFramebufferExtent = createInfo.imageExtent; if (swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) { std::swap(createInfo.imageExtent.width, createInfo.imageExtent.height); @@ -254,10 +255,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Properly initialize Default FBO here auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; - const Int extentWidth = static_cast(createInfo.imageExtent.width); - const Int extentHeight = static_cast(createInfo.imageExtent.height); + const Int extentWidth = static_cast(defaultFramebufferExtent.width); + const Int extentHeight = static_cast(defaultFramebufferExtent.height); const SizeT defaultAttachmentByteSize = - static_cast(createInfo.imageExtent.width) * static_cast(createInfo.imageExtent.height) * 4; + static_cast(defaultFramebufferExtent.width) * + static_cast(defaultFramebufferExtent.height) * 4; auto* colorTex = static_cast(defaultFBOInfo->colorAttachment.get()); colorTex->AllocateStorage( diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index cec34453..81649b38 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -90,17 +90,53 @@ namespace MobileGL::MG_Backend::DirectVulkan { return requestedAlpha; } - static void ApplyGLViewportState(VkCommandBuffer commandBuffer, const IntVec2& fallbackExtent) { + static Bool IsQuarterTurnPreTransform(VkSurfaceTransformFlagBitsKHR preTransform) { + return preTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || + preTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; + } + + static IntVec2 ResolveDefaultFramebufferLogicalExtent(VkSurfaceTransformFlagBitsKHR preTransform, + const IntVec2& rawExtent) { + if (IsQuarterTurnPreTransform(preTransform)) { + return {rawExtent.y(), rawExtent.x()}; + } + return rawExtent; + } + + static Int ScaleFramebufferCoordinate(Int value, Int fromExtent, Int toExtent) { + if (fromExtent <= 0 || toExtent <= 0) { + return value; + } + return static_cast((static_cast(value) * toExtent + fromExtent / 2) / fromExtent); + } + + static void ApplyGLViewportState(VkCommandBuffer commandBuffer, + const IntVec2& framebufferExtent, + VkSurfaceTransformFlagBitsKHR preTransform, + Bool isDefaultFramebuffer) { const IntVec4& viewportState = MG_State::pGLContext->GetViewport(); const FloatVec2& depthRange = MG_State::pGLContext->GetDepthRange(); + const IntVec2 logicalExtent = isDefaultFramebuffer + ? ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent) + : framebufferExtent; + + Int viewportX = viewportState.x(); + Int viewportY = viewportState.y(); + Int viewportWidth = viewportState.z() > 0 ? viewportState.z() : logicalExtent.x(); + Int viewportHeight = viewportState.w() > 0 ? viewportState.w() : logicalExtent.y(); + + if (isDefaultFramebuffer && IsQuarterTurnPreTransform(preTransform)) { + viewportX = ScaleFramebufferCoordinate(viewportX, logicalExtent.x(), framebufferExtent.x()); + viewportY = ScaleFramebufferCoordinate(viewportY, logicalExtent.y(), framebufferExtent.y()); + viewportWidth = ScaleFramebufferCoordinate(viewportWidth, logicalExtent.x(), framebufferExtent.x()); + viewportHeight = ScaleFramebufferCoordinate(viewportHeight, logicalExtent.y(), framebufferExtent.y()); + } VkViewport viewport{}; - viewport.x = static_cast(viewportState.x()); - viewport.y = static_cast(viewportState.y()); - viewport.width = - static_cast(viewportState.z() > 0 ? viewportState.z() : fallbackExtent.x()); - viewport.height = - static_cast(viewportState.w() > 0 ? viewportState.w() : fallbackExtent.y()); + viewport.x = static_cast(viewportX); + viewport.y = static_cast(viewportY); + viewport.width = static_cast(viewportWidth); + viewport.height = static_cast(viewportHeight); viewport.minDepth = depthRange.x(); viewport.maxDepth = depthRange.y(); vkCmdSetViewport(commandBuffer, 0, 1, &viewport); @@ -163,6 +199,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { return scissor; } + static VkRect2D MakeDefaultFramebufferScissorRect(const IntVec4& scissorBox, + const IntVec2& framebufferExtent, + VkSurfaceTransformFlagBitsKHR preTransform) { + if (!IsQuarterTurnPreTransform(preTransform)) { + return MakeClampedScissorRect(scissorBox, framebufferExtent); + } + + const IntVec2 logicalExtent = ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent); + const Int logicalX0 = std::max(0, scissorBox.x()); + const Int logicalY0 = std::max(0, scissorBox.y()); + const Int logicalX1 = std::min(logicalExtent.x(), scissorBox.x() + std::max(0, scissorBox.z())); + const Int logicalY1 = std::min(logicalExtent.y(), scissorBox.y() + std::max(0, scissorBox.w())); + + const Int rawX0 = ScaleFramebufferCoordinate(logicalX0, logicalExtent.x(), framebufferExtent.x()); + const Int rawY0 = ScaleFramebufferCoordinate(logicalY0, logicalExtent.y(), framebufferExtent.y()); + const Int rawX1 = ScaleFramebufferCoordinate(logicalX1, logicalExtent.x(), framebufferExtent.x()); + const Int rawY1 = ScaleFramebufferCoordinate(logicalY1, logicalExtent.y(), framebufferExtent.y()); + + VkRect2D scissor{}; + scissor.offset = {std::max(0, rawX0), std::max(0, rawY0)}; + scissor.extent = { + static_cast(std::max(0, rawX1 - rawX0)), + static_cast(std::max(0, rawY1 - rawY0)), + }; + return scissor; + } + static void ApplyStencilState(VkCommandBuffer commandBuffer) { const StencilFaceState& frontStencil = MG_State::pGLContext->GetStencilState(StencilFace::Front); const StencilFaceState& backStencil = MG_State::pGLContext->GetStencilState(StencilFace::Back); @@ -1410,12 +1473,76 @@ void main() { case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_B8G8R8A8_SNORM: case VK_FORMAT_B8G8R8A8_SRGB: + case VK_FORMAT_B8G8R8A8_USCALED: + case VK_FORMAT_B8G8R8A8_SSCALED: return true; default: return false; } } + static VkExtent2D GetPresentedDumpExtent(VkExtent2D rawExtent, + VkSurfaceTransformFlagBitsKHR preTransform) { + if (IsQuarterTurnPreTransform(preTransform)) { + return {rawExtent.height, rawExtent.width}; + } + return rawExtent; + } + + static const Uint8* GetPresentedDumpPixel(const Uint8* rawPixels, + VkExtent2D rawExtent, + VkSurfaceTransformFlagBitsKHR preTransform, + Uint32 presentedX, + Uint32 presentedY) { + Uint32 rawX = presentedX; + Uint32 rawY = presentedY; + switch (preTransform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: + rawX = rawExtent.width - 1 - presentedY; + rawY = presentedX; + break; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: + rawX = rawExtent.width - 1 - presentedX; + rawY = rawExtent.height - 1 - presentedY; + break; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: + rawX = presentedY; + rawY = rawExtent.height - 1 - presentedX; + break; + default: + break; + } + return rawPixels + (static_cast(rawY) * rawExtent.width + rawX) * 4; + } + + static Bool WritePresentedDumpPpm(const char* path, + const Uint8* rawPixels, + VkExtent2D rawExtent, + VkFormat rawFormat, + VkSurfaceTransformFlagBitsKHR preTransform) { + FILE* dump = std::fopen(path, "wb"); + if (dump == nullptr) { + return false; + } + + const VkExtent2D presentedExtent = GetPresentedDumpExtent(rawExtent, preTransform); + const Bool presentIsBgra = IsBgraVkFormat(rawFormat); + std::fprintf(dump, "P6\n%u %u\n255\n", presentedExtent.width, presentedExtent.height); + for (Uint32 y = 0; y < presentedExtent.height; ++y) { + for (Uint32 x = 0; x < presentedExtent.width; ++x) { + const Uint8* p = GetPresentedDumpPixel(rawPixels, rawExtent, preTransform, x, y); + const Uint8 rgb[3] = { + presentIsBgra ? p[2] : p[0], + p[1], + presentIsBgra ? p[0] : p[2], + }; + std::fwrite(rgb, 1, sizeof(rgb), dump); + } + } + std::fclose(dump); + return true; + } + static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) { const SizeT resolvedAlignment = static_cast(std::max(alignment, 1)); return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1); @@ -3045,7 +3172,8 @@ void main() { MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer"); } - ApplyGLViewportState(frame.commandBuffer, renderPassEntry->extent); + ApplyGLViewportState(frame.commandBuffer, renderPassEntry->extent, + m_swapchainObject.GetPreTransform(), drawFbo->IsDefaultFramebuffer()); ApplyBlendConstants(frame.commandBuffer); ApplyPolygonOffsetState(frame.commandBuffer); ApplyLineWidthState(frame.commandBuffer); @@ -3055,7 +3183,10 @@ void main() { VkRect2D scissor{}; if (scissorEnabled) { const auto& scissorBox = MG_State::pGLContext->GetScissorBox(); - scissor = MakeClampedScissorRect(scissorBox, renderPassEntry->extent); + scissor = drawFbo->IsDefaultFramebuffer() + ? MakeDefaultFramebufferScissorRect(scissorBox, renderPassEntry->extent, + m_swapchainObject.GetPreTransform()) + : MakeClampedScissorRect(scissorBox, renderPassEntry->extent); } else { scissor.offset = {0, 0}; scissor.extent = { (Uint)renderPassEntry->extent.x(), (Uint)renderPassEntry->extent.y() }; @@ -3523,7 +3654,8 @@ void main() { const Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry); MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); - ApplyGLViewportState(frame.commandBuffer, renderPassEntry.extent); + ApplyGLViewportState(frame.commandBuffer, renderPassEntry.extent, + m_swapchainObject.GetPreTransform(), drawIsDefaultFbo); VkRect2D scissor{}; scissor.offset = {0, 0}; @@ -5102,25 +5234,9 @@ void main() { presentStatsExtent.width, presentStatsExtent.height); } if (shouldDumpPresent) { - FILE* dump = std::fopen(presentDumpPath, "wb"); - if (dump != nullptr) { - const VkFormat presentFormat = m_swapchainObject.GetSurfaceFormat().format; - const Bool presentIsBgra = presentFormat == VK_FORMAT_B8G8R8A8_UNORM || - presentFormat == VK_FORMAT_B8G8R8A8_SRGB || - presentFormat == VK_FORMAT_B8G8R8A8_SNORM || - presentFormat == VK_FORMAT_B8G8R8A8_USCALED || - presentFormat == VK_FORMAT_B8G8R8A8_SSCALED; - std::fprintf(dump, "P6\n%u %u\n255\n", presentStatsExtent.width, presentStatsExtent.height); - for (SizeT i = 0; i < pixelCount; ++i) { - const Uint8* p = pixels + i * 4; - const Uint8 rgb[3] = { - presentIsBgra ? p[2] : p[0], - p[1], - presentIsBgra ? p[0] : p[2], - }; - std::fwrite(rgb, 1, sizeof(rgb), dump); - } - std::fclose(dump); + const VkFormat presentFormat = m_swapchainObject.GetSurfaceFormat().format; + if (WritePresentedDumpPpm(presentDumpPath, pixels, presentStatsExtent, presentFormat, + m_swapchainObject.GetPreTransform())) { if (PresentStatsEnabled()) { std::fprintf(stderr, "MOBILEGL_PRESENT_DUMP_WRITTEN path=%s bytes=%zu\n", presentDumpPath, static_cast(pixelCount * 3));