[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
This commit is contained in:
BZLZHH
2026-11-01 19:20:48 +08:00
parent 6c59b0391c
commit 05dfff9100
3 changed files with 46 additions and 11 deletions
@@ -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
@@ -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;
@@ -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