mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user