diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp index 693560394e..64e431751d 100644 --- a/src/tests/gl_tests/RobustResourceInitTest.cpp +++ b/src/tests/gl_tests/RobustResourceInitTest.cpp @@ -809,6 +809,88 @@ TEST_P(RobustResourceInitTestES3, DrawThenInvalidateThenReadBack) EXPECT_GL_NO_ERROR(); } +// A full clear marks the attachment as Initialized in the frontend before the +// backend has actually written texture memory (the clear may be implemented as +// a render-pass loadAction=Clear). Calling invalidate immediately after must +// not let allocation-time GPU heap contents leak through to readPixels. +TEST_P(RobustResourceInitTestES3, ClearThenInvalidateThenReadBack) +{ + ANGLE_SKIP_TEST_IF(!hasGLExtension()); + + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + // Full unmasked unscissored clear: partialClearNeedsInit() == false, so the + // frontend resets mResourceNeedsInit *before* the data lands in texture + // backing memory. + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + // Backend may set storeAction = DontCare on the live encoder, discarding + // the only write that would have initialised the texture. + std::array attachments = {GL_COLOR_ATTACHMENT0}; + glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, attachments.data()); + + // ensureReadAttachmentsInitialized early-exits because mResourceNeedsInit + // was already cleared by the clear() above. Robust init must still ensure + // the readback is the clear colour (or the robust init value). + EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::transparentBlack); + EXPECT_GL_NO_ERROR(); +} + +// Same as above but with a 1-pixel draw between invalidate and readPixels. +// On Metal the next render pass inherits loadAction=DontCare, storeAction=Store +// so stale on-chip tile memory is *stored* into the texture and read back. +TEST_P(RobustResourceInitTestES3, ClearThenInvalidateThenDrawThenReadBack) +{ + ANGLE_SKIP_TEST_IF(!hasGLExtension()); + + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + std::array attachments = {GL_COLOR_ATTACHMENT0}; + glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, attachments.data()); + + // 1-pixel point at the centre. Everywhere else must be transparentBlack. + constexpr char kVS[] = R"(#version 300 es +void main(){gl_Position=vec4(0.0,0.0,0.0,1.0);gl_PointSize=1.0;})"; + constexpr char kFS[] = R"(#version 300 es +precision highp float; out vec4 c; void main(){c=vec4(0.0);})"; + ANGLE_GL_PROGRAM(prog, kVS, kFS); + glUseProgram(prog); + glViewport(0, 0, kWidth, kHeight); + glDrawArrays(GL_POINTS, 0, 1); + + std::vector pixels(kWidth * kHeight); + glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + EXPECT_GL_NO_ERROR(); + for (const GLColor &p : pixels) + { + ASSERT_EQ(p, GLColor::transparentBlack) << "leaked: " << p; + } +} + // Calling invalidate after draw should either read back the result of the draw call (invalidate // didn't happen) or the init depth/stencil values. TEST_P(RobustResourceInitTestES3, DrawThenInvalidateThenReadBackDepthStencil)