[Fix] (DirectVulkan): detach ended render passes before attachment layout reconciliation

This commit is contained in:
2026-10-05 05:12:42 -04:00
parent 946e63d810
commit f497ff9741
2 changed files with 52 additions and 4 deletions
@@ -1681,14 +1681,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) {
BumpWireImageWriteEpoch();
auto* activeRenderPass = GetActiveRenderPass();
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
if (activeRenderPass != nullptr) {
trackedAttachmentLayouts = Move(activeRenderPass->trackedAttachmentLayouts);
}
vkCmdEndRenderPass(commandBuffer);
// Layout reconciliation below may record barriers, whose guard closes an active
// pass. Detach the ended pass first so that guard cannot recursively end it again.
s_activeRenderPass.Get() = {};
s_hasActiveRenderPass.Get() = false;
// The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry
// over (the next span may be a different FBO resolved before its render pass is begun).
if (s_renderPassManager.Get() != nullptr) {
s_renderPassManager.Get()->m_rpFastValid = false;
}
if (activeRenderPass != nullptr && !activeRenderPass->trackedAttachmentLayouts.empty()) {
for (const auto& trackedAttachment : activeRenderPass->trackedAttachmentLayouts) {
if (!trackedAttachmentLayouts.empty()) {
for (const auto& trackedAttachment : trackedAttachmentLayouts) {
switch (trackedAttachment.target) {
case TrackedAttachmentTarget::Texture:
MOBILEGL_ASSERT(s_textureManager.Get() != nullptr, "EndRenderPass: texture manager is null");
@@ -1730,8 +1738,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
}
s_activeRenderPass.Get() = {};
s_hasActiveRenderPass.Get() = false;
return true;
}
@@ -374,5 +374,47 @@ void main() { o_color = vec4(0.1, 0.2, 0.8, 1.0); }
<< (std::fabs(blitted - 0.125f) < 1e-3f ? " - the destination kept its own clear" : "");
}
TEST_F(LayeredAttachmentBarrierScenario, EndingAMiddleMipDrawPreservesOtherLevels) {
if (!Ready()) return;
GLuint texture = 0;
glGenTextures(1, &texture);
m_textures.push_back(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, 16, 16);
for (int level = 0; level < 3; ++level) {
const int size = 16 >> level;
const std::vector<Rgba8> initial(size * size, LayerFill(level));
glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, size, size,
GL_RGBA, GL_UNSIGNED_BYTE, initial.data());
}
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
m_fbos.push_back(fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1);
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE);
glViewport(0, 0, 8, 8);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
DrawFullViewportQuad(m_program);
// Ending this pass reconciles both lower and upper mip layouts. Their barriers
// must not see the pass as active and recursively end it a second time.
glFinish();
for (int level = 0; level < 3; ++level) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
glReadBuffer(GL_COLOR_ATTACHMENT0);
Rgba8 actual{};
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &actual);
const Rgba8 expected = level == 1 ? kPaintedColor : LayerFill(level);
EXPECT_NEAR(actual.r, expected.r, 2) << "level " << level;
EXPECT_NEAR(actual.g, expected.g, 2);
EXPECT_NEAR(actual.b, expected.b, 2);
EXPECT_EQ(actual.a, expected.a);
}
EXPECT_EQ(FirstGLError(), 0u);
}
} // namespace
} // namespace MGITest