diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp index 7ac75d9e05..e69eab1e22 100644 --- a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp +++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp @@ -4194,6 +4194,56 @@ void GraphicsPipelineDesc::initializePipelineFragmentOutputState( stateOut->blendState.attachmentCount = colorAttachmentIndex; } + // POC instrumentation: dump the per-attachment blend state that is about + // to be handed to vkCreateGraphicsPipelines. If any attachment carries + // an advanced blend op (>= VK_BLEND_OP_ZERO_EXT) while attachmentCount > 1 + // and the ops are non-uniform, ANGLE is violating + // VUID-VkPipelineColorBlendAttachmentState-advancedBlendIndependentBlend-01407/01408 + // on every device with advancedBlendIndependentBlend == VK_FALSE. + { + bool anyAdvanced = false; + bool nonUniform = false; + for (uint32_t i = 0; i < stateOut->blendState.attachmentCount; ++i) + { + const VkPipelineColorBlendAttachmentState &a = stateOut->blendAttachmentState[i]; + if (a.blendEnable && a.colorBlendOp >= VK_BLEND_OP_ZERO_EXT) + { + anyAdvanced = true; + } + if (i > 0 && (a.colorBlendOp != stateOut->blendAttachmentState[0].colorBlendOp || + a.alphaBlendOp != stateOut->blendAttachmentState[0].alphaBlendOp)) + { + nonUniform = true; + } + } + if (anyAdvanced && stateOut->blendState.attachmentCount > 1) + { + fprintf(stderr, + "[POC] initializePipelineFragmentOutputState: attachmentCount=%u " + "supportsBlendOperationAdvanced=%d nonUniformColorBlendOp=%d\n", + stateOut->blendState.attachmentCount, + context->getFeatures().supportsBlendOperationAdvanced.enabled ? 1 : 0, + nonUniform ? 1 : 0); + for (uint32_t i = 0; i < stateOut->blendState.attachmentCount; ++i) + { + const VkPipelineColorBlendAttachmentState &a = stateOut->blendAttachmentState[i]; + fprintf(stderr, + "[POC] pAttachments[%u]: blendEnable=%u colorBlendOp=0x%x " + "alphaBlendOp=0x%x\n", + i, a.blendEnable, static_cast(a.colorBlendOp), + static_cast(a.alphaBlendOp)); + } + if (nonUniform) + { + fprintf(stderr, + "[POC] *** ANGLE is emitting a VkPipelineColorBlendStateCreateInfo " + "that violates VUID-VkPipelineColorBlendAttachmentState-" + "advancedBlendIndependentBlend-01407/01408 on devices with " + "advancedBlendIndependentBlend == VK_FALSE ***\n"); + } + } + } + // Dynamic state dynamicStateListOut->push_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS); if (context->getFeatures().supportsLogicOpDynamicState.enabled) diff --git a/src/tests/gl_tests/AdvancedBlendTest.cpp b/src/tests/gl_tests/AdvancedBlendTest.cpp index 15a6c496b3..83eeef04db 100644 --- a/src/tests/gl_tests/AdvancedBlendTest.cpp +++ b/src/tests/gl_tests/AdvancedBlendTest.cpp @@ -435,6 +435,104 @@ TEST_P(AdvancedBlendTest, AdvancedBlendCoherentQueryFailsIfNotSupported) EXPECT_GL_ERROR(GL_INVALID_ENUM); } +// POC: Demonstrate that ANGLE's GL-frontend MRT check for advanced blend +// (validationES.cpp ValidateDrawStates) is incomplete and allows a Vulkan +// pipeline to be created with a mix of advanced (VK_BLEND_OP_*_EXT) and +// basic (VK_BLEND_OP_ADD) per-attachment blend ops over N>1 color +// attachments. On any device with advancedBlendIndependentBlend == VK_FALSE +// (SwiftShader, ARM Mali, Qualcomm Adreno, NVIDIA) this violates +// VUID-VkPipelineColorBlendAttachmentState-advancedBlendIndependentBlend-01407 +// and -01408; on devices with advancedBlendMaxColorAttachments == 1 (Mali, +// Adreno) it additionally violates VUID-...-colorBlendOp-01410. +// +// The frontend check only iterates getUsesAdvancedBlendEquationMask(), so a +// single advanced-equation buffer at index 1 with three FUNC_ADD buffers +// passes (advancedBlendRenderTargetCount == 1). No backend check exists: +// supportsBlendOperationAdvanced is enabled purely on extension presence and +// VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT is never queried. +TEST_P(AdvancedBlendTest, MixedAdvancedBlendWithMRTReachesVkDriver) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_blend_equation_advanced")); + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_OES_draw_buffers_indexed")); + + GLint maxDrawBuffers = 0; + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + ANGLE_SKIP_TEST_IF(maxDrawBuffers < 4); + + constexpr char kVS[] = R"(#version 300 es +void main() +{ + vec2 p = vec2((gl_VertexID & 1) == 0 ? -1.0 : 3.0, + (gl_VertexID & 2) == 0 ? -1.0 : 3.0); + gl_Position = vec4(p, 0.0, 1.0); +})"; + + constexpr char kFS[] = R"(#version 300 es +#extension GL_KHR_blend_equation_advanced : require +precision highp float; +layout(blend_support_multiply) out; +layout(location = 0) out vec4 c0; +layout(location = 1) out vec4 c1; +layout(location = 2) out vec4 c2; +layout(location = 3) out vec4 c3; +void main() +{ + c0 = vec4(0.5, 0.25, 0.75, 1.0); + c1 = vec4(0.25, 0.5, 0.5, 1.0); + c2 = vec4(1.0, 0.0, 0.0, 1.0); + c3 = vec4(0.0, 1.0, 0.0, 1.0); +})"; + + ANGLE_GL_PROGRAM(program, kVS, kFS); + ASSERT_NE(0u, program.get()); + glUseProgram(program); + ASSERT_GL_NO_ERROR(); + + GLTexture tex[4]; + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + for (int i = 0; i < 4; ++i) + { + glBindTexture(GL_TEXTURE_2D, tex[i]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, tex[i], 0); + } + ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); + ASSERT_GL_NO_ERROR(); + + const GLenum bufs[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3}; + glDrawBuffers(4, bufs); + glViewport(0, 0, 4, 4); + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Per-index blend state: enable blend on every attachment. + glEnable(GL_BLEND); + // Put the advanced equation on index 1; indices 0,2,3 stay GL_FUNC_ADD. + // This passes ANGLE's incomplete ValidateDrawStates MRT check + // (advancedBlendRenderTargetCount == 1) and produces a non-uniform + // colorBlendOp array {ADD, MULTIPLY_EXT, ADD, ADD}, which the Vulkan + // validation layer flags as VUID-01407 / VUID-01408 on any device with + // advancedBlendIndependentBlend == VK_FALSE. + glBlendEquationiOES(1, GL_MULTIPLY_KHR); + EXPECT_GL_NO_ERROR(); + + fprintf(stderr, "[POC] issuing glDrawArrays with mixed advanced/normal blend over 4 RTs\n"); + glDrawArrays(GL_TRIANGLES, 0, 3); + // The bug is precisely that this draw is *not* rejected by the GL + // frontend. Per GLES 3.2 / KHR_blend_equation_advanced this should be + // GL_INVALID_OPERATION (only draw buffer 0 may be non-NONE). Instead, + // it succeeds and the Vulkan backend emits an invalid pipeline. + EXPECT_GL_NO_ERROR(); + glFinish(); + + fprintf(stderr, + "[POC] glDrawArrays returned without GL error -> ValidateDrawStates " + "MRT check bypassed; vkCreateGraphicsPipelines was called with " + "non-uniform advanced blend ops over 4 attachments.\n"); +} + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AdvancedBlendTest); ANGLE_INSTANTIATE_TEST_ES3(AdvancedBlendTest); diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp index 63476f2a6a..ffda908ecb 100644 --- a/src/tests/test_utils/ANGLETest.cpp +++ b/src/tests/test_utils/ANGLETest.cpp @@ -544,7 +544,9 @@ ANGLETestBase::ANGLETestBase(const PlatformParameters ¶ms) #if defined(ANGLE_ENABLE_VULKAN_VALIDATION_LAYERS) withMethods.eglParameters.debugLayersEnabled = true; #else - withMethods.eglParameters.debugLayersEnabled = false; + // POC: force-enable validation layers so VUID violations are reported + // even though this is an is_asan build that normally disables them. + withMethods.eglParameters.debugLayersEnabled = true; #endif }