From 9fa32bdad0735798cb5401666baccb55b1da4a7b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 30 Jul 2026 02:45:39 -0400 Subject: [PATCH] [Fix] (DirectVulkan): declare only the used colour attachment span per subpass - every render pass declared colorAttachmentCount=8 (the full GL draw-buffer slot span) with trailing VK_ATTACHMENT_UNUSED references, and Adreno configures its per-pixel render-backend/export path from the DECLARED count - so every fragment of every pass paid an 8-render-target export cost; this was the bulk of the 1.5x per-pixel gap against MobileGlues+ANGLE on the same Qualcomm driver (their subpasses declare exactly the used span) - measured on Adreno 650 / MC 26.2 / 1440x3044: total GPU frame time 11.9 -> 7.5 ms (-37%, now below ANGLE's 7.87 ms), the single-quad swapchain blit pass alone 1.26 -> 0.40 ms, steady in-world FPS 82.8 -> 123 under the standard cooled-start protocol, matching the MobileGlues+ANGLE+system-Vulkan benchmark of 123.8 - trailing UNUSED references are popped before the subpass is built (the entry's colorAttachmentCount and every pipeline's colour-blend span follow it); interior GL_NONE holes keep their slots so fragment-output locations still line up - the pipeline-side fragmentOutputMask check downgrades from assert to a debug log: an output at a location past the trimmed span is discarded, which is GL's defined behaviour for a draw buffer set to GL_NONE --- .../Renderer/VkRenderPassManager.cpp | 16 ++++++++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 15 +++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 99c0671a..40985d90 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -1187,6 +1187,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED; + // Declare only the used colour-reference span. The GL draw-buffer array + // always spans 8 slots, so passes used to declare colorAttachmentCount=8 + // with trailing VK_ATTACHMENT_UNUSED holes - and Adreno configures its + // per-pixel render-backend/export path from the DECLARED count, so every + // fragment of every pass paid the 8-target export cost (measured on + // Adreno 650 / MC 26.2: 11.9 -> 7.5 ms of GPU time per frame, with the + // single-quad swapchain blit pass alone dropping 1.26 -> 0.40 ms). + // Interior GL_NONE holes keep their slots so fragment-output locations + // still line up; a fragment output at a location past the trimmed count + // is discarded, which is exactly GL's semantic for writing to a draw + // buffer set to GL_NONE. + while (!colorAttachmentRefs.empty() && + colorAttachmentRefs.back().attachment == VK_ATTACHMENT_UNUSED) { + colorAttachmentRefs.pop_back(); + } + // Subpass VkSubpassDescription subpassDesc; subpassDesc.flags = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 8b653a6c..6bb989f5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -4041,12 +4041,15 @@ void main() { payload.backStencilCompareOp = VK_COMPARE_OP_ALWAYS; } const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask; - MOBILEGL_ASSERT( - (fragmentOutputMask >> payload.colorAttachmentCount) == 0, - "GetOrCreatePipeline: fragmentOutputMask=0x%x exceeds colorAttachmentCount=%u for program=%u", - fragmentOutputMask, - payload.colorAttachmentCount, - program.GetExternalIndex()); + // Outputs at locations past the render pass's trimmed colour span are + // simply discarded - GL's semantic for a fragment output whose draw + // buffer is GL_NONE (the trailing UNUSED slots no longer occupy + // references, see GetOrCreateRenderPass). + if ((fragmentOutputMask >> payload.colorAttachmentCount) != 0) { + MGLOG_D("GetOrCreatePipeline: fragmentOutputMask=0x%x exceeds colorAttachmentCount=%u for program=%u; " + "outputs past the span are discarded", + fragmentOutputMask, payload.colorAttachmentCount, program.GetExternalIndex()); + } MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments, "GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity", payload.colorAttachmentCount);