diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp index 09b67e73e4..39a6323a30 100644 --- a/src/tests/gl_tests/ImageTest.cpp +++ b/src/tests/gl_tests/ImageTest.cpp @@ -4868,6 +4868,81 @@ TEST_P(ImageTestES3, RenderToYUVAHB) destroyAndroidHardwareBuffer(source); } +// Regression test: ValidateDrawStates reads the legacy non-indexed BlendState when enforcing +// the EXT_yuv_target spec restriction (full color mask + blend disabled). The indexed setters +// from GL_OES_draw_buffers_indexed update only BlendStateExt, so glEnableiOES / glColorMaskiOES +// could bypass the check and submit blend-enabled / partially-masked state to the backend +// against a YUV external-format attachment. +TEST_P(ImageTestES3, RenderToYUVAHBIndexedBlendValidationBypass) +{ + EGLWindow *window = getEGLWindow(); + + ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt() || !hasYUVTargetExt()); + ANGLE_SKIP_TEST_IF(!hasAndroidImageNativeBufferExt() || !hasAndroidHardwareBufferSupport()); + ANGLE_SKIP_TEST_IF(!hasAhbLockPlanesSupport()); + ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_draw_buffers_indexed")); + + // 3 planes of data, initialize to all zeroes + GLubyte dataY[4] = {0, 0, 0, 0}; + GLubyte dataCb[1] = {0}; + GLubyte dataCr[1] = {0}; + + AHardwareBuffer *source; + EGLImageKHR image; + createEGLImageAndroidHardwareBufferSource( + 2, 2, 1, AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420, kDefaultAHBUsage, kDefaultAttribs, + {{dataY, 1}, {dataCb, 1}, {dataCr, 1}}, &source, &image); + + GLTexture target; + createEGLImageTargetTextureExternal(image, target); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, target, + 0); + ASSERT_GL_NO_ERROR(); + EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + glUseProgram(mRenderYUVProgram); + glUniform4f(mRenderYUVUniformLocation, 0.5f, 0.5f, 0.5f, 1.0f); + + // Sanity: non-indexed blend enable IS caught. + glEnable(GL_BLEND); + drawQuad(mRenderYUVProgram, "position", 0.0f); + EXPECT_GL_ERROR(GL_INVALID_OPERATION); + glDisable(GL_BLEND); + + // Sanity: non-indexed partial color mask IS caught. + glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); + drawQuad(mRenderYUVProgram, "position", 0.0f); + EXPECT_GL_ERROR(GL_INVALID_OPERATION); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // BUG: indexed blend enable updates only mBlendStateExt; ValidateDrawStates reads the + // stale legacy mBlendState (blend == false) and lets the draw through. Per + // EXT_yuv_target this MUST be GL_INVALID_OPERATION. + glEnableiOES(GL_BLEND, 0); + EXPECT_GL_NO_ERROR(); + drawQuad(mRenderYUVProgram, "position", 0.0f); + // On an unpatched ANGLE this assertion FAILS (no error is raised) and the Vulkan backend + // builds a VkPipelineColorBlendAttachmentState with blendEnable=VK_TRUE against the + // external-format YUV resolve attachment, whose VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT + // was deliberately never verified (see vk_format_utils.cpp). + EXPECT_GL_ERROR(GL_INVALID_OPERATION); + glDisableiOES(GL_BLEND, 0); + + // BUG: indexed partial color mask likewise bypasses the check. + glColorMaskiOES(0, GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); + EXPECT_GL_NO_ERROR(); + drawQuad(mRenderYUVProgram, "position", 0.0f); + EXPECT_GL_ERROR(GL_INVALID_OPERATION); + glColorMaskiOES(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glFinish(); + eglDestroyImageKHR(window->getDisplay(), image); + destroyAndroidHardwareBuffer(source); +} + // Test rendering to a YUV AHB using EXT_yuv_target with a normal depth attachment TEST_P(ImageTestES3, RenderToYUVAHBWithDepth) {