From 05dfff910040ebd74eaa3a974ae9625186eb4acb Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sun, 1 Nov 2026 19:20:48 +0800 Subject: [PATCH] [Perf] (MG_Backend/DirectVulkan, MG_IntegrationTest/Scenarios): defer window clears while a window pass is open - A whole-window clear with a window pass open ends that pass and folds into the next one, instead of running a load-clear-store pass of its own - A present drops the depth/stencil half of a pending window clear --- .../Renderer/VulkanRendererPresent.cpp | 11 ++++--- .../DirectVulkan/Renderer/WireDraw.cpp | 15 ++++----- .../Scenarios/InPassClearScenario.cpp | 31 +++++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp index f835ef1f..293292f9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp @@ -16,12 +16,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Present has its own submit path. Tag its last wire pass before that // submission, rather than retaining it until a draw in the next frame. RetireWireDrawPass(); - // A window clear no pass drew after still has to reach the presented image; a frame that - // cannot be presented drops it with the rest of the frame. - if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) + // A window color clear no pass drew after still has to reach the presented image. Its + // depth/stencil half is dropped: a swap leaves those undefined (see the content flags + // reset below). A frame that cannot be presented drops the whole clear. + if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) { m_defaultWirePendingClear = {}; - else + } else { + m_defaultWirePendingClear.depthStencil = 0; MaterializeDefaultWirePendingClear(); + } #endif if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) { // No usable swapchain: the window was zero-area at initialization, or diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp index ad5d3439..26de5086 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp @@ -1407,18 +1407,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { return variant; } - // The default framebuffer's half of the deferred clears: a whole-window glClear with no pass - // open on the window waits for the next pass that draws into it, which loads it with - // LOAD_OP_CLEAR (BeginWireDrawPassFolding) instead of storing a cleared window and loading it - // back. Returns the buffers it did not take. + // The default framebuffer's half of the deferred clears: a whole-window glClear waits for the + // next pass that draws into the window, which loads it with LOAD_OP_CLEAR + // (BeginWireDrawPassFolding) instead of storing a cleared window and loading it back. A pass + // open on the window ends first, since the in-pass clear does not take the window + // (MagmaInPassClearDefault); a present drops the depth/stencil half (Present). Returns the + // buffers it did not take. GLbitfield VulkanRenderer::DeferDefaultWireClear(const MG_Pipe::MGPFramebufferState& fbo, const ClearAttachmentPayload& payload, GLint drawbuffer) { GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (!remaining || !fbo.IsDefault || drawbuffer >= 0 || m_wirePreparationDepth != 0) return remaining; if (GLScissorTestClips()) return remaining; - // A pass open on the window takes the clear itself (ClearWireAttachmentsOfOpenPass). - if (VkRenderPassManager::GetActiveRenderPass() != nullptr && m_wireDrawPass && m_wireDrawPassKey.isDefault) - return remaining; GLbitfield handled = 0; Bool color = false; VkImageAspectFlags depthStencil = 0; @@ -1452,6 +1451,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } if (!color && depthStencil == 0) return remaining & ~handled; + if (VkRenderPassManager::GetActiveRenderPass() != nullptr && m_wireDrawPass && m_wireDrawPassKey.isDefault) + RetireWireDrawPass(); // The index the next drawing pass on the window will address (WireDraw.cpp's pass key). const Uint32 index = DefaultFramebufferWriteIndex(true); auto& pending = m_defaultWirePendingClear; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp index 6b8c0e51..c0cfe373 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp @@ -357,4 +357,35 @@ void main() { o0 = texture(uPalette, vec2(0.5, 0.5)); } glDeleteProgram(paletteProgram); } + // A depth clear of the DEFAULT framebuffer while a pass is open on it (a game clearing depth + // before its hand or GUI): the in-pass clear does not take the window, so the clear ends the + // pass and becomes the load op of the next one, which the farther draw must see. + TEST_F(InPassClearScenario, ADefaultFramebufferDepthClearBetweenTestedDrawsDecidesTheSecondDraw) { + if (!Ready()) return; + HeadlessGL& gl = Gl(); + const int width = gl.Width(), height = gl.Height(); + Build(); + for (int frame = 0; frame < 2; ++frame) { + BindDefaultFramebuffer(); + glViewport(0, 0, width, height); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearDepth(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + Quad(1.0f, 0.0f, 0.0f, 1.0f, -0.5f); + glClear(GL_DEPTH_BUFFER_BIT); + Quad(0.0f, 1.0f, 0.0f, 1.0f, 0.0f); + glDisable(GL_DEPTH_TEST); + EXPECT_EQ(FirstGLError(), 0u); + const Image image = ReadPixels(width, height); + EXPECT_TRUE(Near(image.At(width / 2, height / 2), 0, 255, 0, 255)) + << "frame " << frame << ": the depth clear between the draws let the farther draw pass"; + gl.EndFrame(); + } + } + } // namespace MGITest