diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp index eb4d24910..1f3a4d52f 100644 --- a/src/compiler/translator/Compiler.cpp +++ b/src/compiler/translator/Compiler.cpp @@ -887,7 +887,7 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root, if (mShaderVersion >= 300 && mShaderType == GL_FRAGMENT_SHADER && !ValidateOutputs(root, getExtensionBehavior(), mResources, hasPixelLocalStorageUniforms(), - &mDiagnostics)) + IsWebGLBasedSpec(mShaderSpec), &mDiagnostics)) { return false; } diff --git a/src/compiler/translator/ValidateOutputs.cpp b/src/compiler/translator/ValidateOutputs.cpp index b42cca078..927783db7 100644 --- a/src/compiler/translator/ValidateOutputs.cpp +++ b/src/compiler/translator/ValidateOutputs.cpp @@ -31,7 +31,8 @@ class ValidateOutputsTraverser : public TIntermTraverser public: ValidateOutputsTraverser(const TExtensionBehavior &extBehavior, const ShBuiltInResources &resources, - bool usesPixelLocalStorage); + bool usesPixelLocalStorage, + bool isWebGL); void validate(TDiagnostics *diagnostics) const; @@ -43,6 +44,7 @@ class ValidateOutputsTraverser : public TIntermTraverser bool mEnablesBlendFuncExtended; bool mUsesIndex1; bool mUsesPixelLocalStorage; + bool mIsWebGL; bool mUsesFragDepth; typedef std::vector OutputVector; @@ -54,7 +56,8 @@ class ValidateOutputsTraverser : public TIntermTraverser ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &extBehavior, const ShBuiltInResources &resources, - bool usesPixelLocalStorage) + bool usesPixelLocalStorage, + bool isWebGL) : TIntermTraverser(true, false, false), mMaxDrawBuffers(resources.MaxDrawBuffers), mMaxDualSourceDrawBuffers(resources.MaxDualSourceDrawBuffers), @@ -62,6 +65,7 @@ ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &ext IsExtensionEnabled(extBehavior, TExtension::EXT_blend_func_extended)), mUsesIndex1(false), mUsesPixelLocalStorage(usesPixelLocalStorage), + mIsWebGL(isWebGL), mUsesFragDepth(false) {} @@ -187,6 +191,12 @@ void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const "must explicitly specify all locations when using multiple fragment outputs and " "pixel local storage, even if EXT_blend_func_extended is enabled"; } + else if (mIsWebGL) + { + unspecifiedLocationErrorMessage = + "must explicitly specify all locations when using multiple fragment outputs " + "in WebGL contexts, even if EXT_blend_func_extended is enabled"; + } if (unspecifiedLocationErrorMessage != nullptr) { for (const auto &symbol : mUnspecifiedLocationOutputs) @@ -215,9 +225,11 @@ bool ValidateOutputs(TIntermBlock *root, const TExtensionBehavior &extBehavior, const ShBuiltInResources &resources, bool usesPixelLocalStorage, + bool isWebGL, TDiagnostics *diagnostics) { - ValidateOutputsTraverser validateOutputs(extBehavior, resources, usesPixelLocalStorage); + ValidateOutputsTraverser validateOutputs(extBehavior, resources, usesPixelLocalStorage, + isWebGL); root->traverse(&validateOutputs); int numErrorsBefore = diagnostics->numErrors(); validateOutputs.validate(diagnostics); diff --git a/src/compiler/translator/ValidateOutputs.h b/src/compiler/translator/ValidateOutputs.h index 4201f7cb6..2d502985c 100644 --- a/src/compiler/translator/ValidateOutputs.h +++ b/src/compiler/translator/ValidateOutputs.h @@ -27,6 +27,7 @@ bool ValidateOutputs(TIntermBlock *root, const TExtensionBehavior &extBehavior, const ShBuiltInResources &resources, bool usesPixelLocalStorage, + bool isWebGL, TDiagnostics *diagnostics); } // namespace sh diff --git a/src/libANGLE/ProgramExecutable.cpp b/src/libANGLE/ProgramExecutable.cpp index ae1e40333..1fab2f77b 100644 --- a/src/libANGLE/ProgramExecutable.cpp +++ b/src/libANGLE/ProgramExecutable.cpp @@ -429,6 +429,7 @@ void ProgramExecutable::reset() mPODStruct.attributesMask.reset(); mPODStruct.maxActiveAttribLocation = 0; mPODStruct.activeOutputVariablesMask.reset(); + mPODStruct.activeSecondaryOutputVariablesMask.reset(); mPODStruct.defaultUniformRange = RangeUI(0, 0); mPODStruct.samplerUniformRange = RangeUI(0, 0); @@ -1207,6 +1208,7 @@ bool ProgramExecutable::linkValidateOutputVariables( const ProgramAliasedBindings &fragmentOutputIndices) { ASSERT(mPODStruct.activeOutputVariablesMask.none()); + ASSERT(mPODStruct.activeSecondaryOutputVariablesMask.none()); ASSERT(mPODStruct.drawBufferTypeMask.none()); ASSERT(!mPODStruct.hasYUVOutput); @@ -1425,7 +1427,9 @@ bool ProgramExecutable::gatherOutputTypes() for (const sh::ShaderVariable &outputVariable : mOutputVariables) { if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" && - outputVariable.name != "gl_FragData") + outputVariable.name != "gl_FragData" && + outputVariable.name != "gl_SecondaryFragColorEXT" && + outputVariable.name != "gl_SecondaryFragDataEXT") { continue; } @@ -1434,6 +1438,10 @@ bool ProgramExecutable::gatherOutputTypes() (outputVariable.location == -1 ? 0u : static_cast(outputVariable.location)); + const bool secondary = + outputVariable.index == 1 || (outputVariable.name == "gl_SecondaryFragColorEXT" || + outputVariable.name == "gl_SecondaryFragDataEXT"); + const ComponentType componentType = GLenumToComponentType(VariableComponentType(outputVariable.type)); @@ -1444,7 +1452,15 @@ bool ProgramExecutable::gatherOutputTypes() { const unsigned int location = baseLocation + elementIndex; ASSERT(location < mPODStruct.activeOutputVariablesMask.size()); - mPODStruct.activeOutputVariablesMask.set(location); + ASSERT(location < mPODStruct.activeSecondaryOutputVariablesMask.size()); + if (secondary) + { + mPODStruct.activeSecondaryOutputVariablesMask.set(location); + } + else + { + mPODStruct.activeOutputVariablesMask.set(location); + } const ComponentType storedComponentType = gl::GetComponentTypeMask(mPODStruct.drawBufferTypeMask, location); if (storedComponentType == ComponentType::InvalidEnum) diff --git a/src/libANGLE/ProgramExecutable.h b/src/libANGLE/ProgramExecutable.h index e7e598312..03c548ae5 100644 --- a/src/libANGLE/ProgramExecutable.h +++ b/src/libANGLE/ProgramExecutable.h @@ -490,6 +490,10 @@ class ProgramExecutable final : public angle::Subject { return mPODStruct.activeOutputVariablesMask; } + DrawBufferMask getActiveSecondaryOutputVariablesMask() const + { + return mPODStruct.activeSecondaryOutputVariablesMask; + } bool linkUniforms(const Caps &caps, const ShaderMap> &shaderUniforms, @@ -579,6 +583,7 @@ class ProgramExecutable final : public angle::Subject // removed. AttributesMask attributesMask; DrawBufferMask activeOutputVariablesMask; + DrawBufferMask activeSecondaryOutputVariablesMask; ComponentTypeMask drawBufferTypeMask; RangeUI defaultUniformRange; diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp index 4e8de25df..084625840 100644 --- a/src/libANGLE/validationES.cpp +++ b/src/libANGLE/validationES.cpp @@ -486,11 +486,16 @@ bool ValidateFragmentShaderColorBufferMaskMatch(const Context *context) const Program *program = context->getActiveLinkedProgram(); const Framebuffer *framebuffer = glState.getDrawFramebuffer(); - auto drawBufferMask = - framebuffer->getDrawBufferMask() & glState.getBlendStateExt().compareColorMask(0); + const auto &blendStateExt = glState.getBlendStateExt(); + auto drawBufferMask = framebuffer->getDrawBufferMask() & blendStateExt.compareColorMask(0); + auto dualSourceBlendingMask = drawBufferMask & blendStateExt.getEnabledMask() & + blendStateExt.getUsesExtendedBlendFactorMask(); auto fragmentOutputMask = program->getExecutable().getActiveOutputVariablesMask(); + auto fragmentSecondaryOutputMask = + program->getExecutable().getActiveSecondaryOutputVariablesMask(); - return drawBufferMask == (drawBufferMask & fragmentOutputMask); + return drawBufferMask == (drawBufferMask & fragmentOutputMask) && + dualSourceBlendingMask == (dualSourceBlendingMask & fragmentSecondaryOutputMask); } bool ValidateFragmentShaderColorBufferTypeMatch(const Context *context) diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 8338a7d39..04b1a80e4 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -1177,6 +1177,10 @@ b/273271471 WIN INTEL VULKAN : ShaderAlgorithmTest.rgb_to_hsl_vertex_shader/* = 8131 LINUX INTEL : SampleMultisampleInterpolationTest.SampleMaskInPerSample/* = SKIP 8131 LINUX INTEL : SampleMultisampleInterpolationTest.SampleMaskInPerSampleNoPerspective/* = SKIP +// Driver bugs related to handling secondary fragment output arrays +8336 ANDROID VULKAN : WebGLCompatibilityTest.EXTBlendFuncExtendedMissingOutputsArrays/* = SKIP +8336 ANDROID VULKAN : WebGL2CompatibilityTest.EXTBlendFuncExtendedMissingOutputsArrays/* = SKIP + // bits 24..31 from glClearValueuiv value don't work on Intel Metal. 7794 MAC INTEL METAL : PixelLocalStorageTest.ClearValues_r32/* = SKIP diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp index 5a3d3e23b..04ab7ef58 100644 --- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp +++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp @@ -6140,6 +6140,199 @@ void main() { EXPECT_FALSE(prg.valid()); } +// Test that EXT_blend_func_extended does not allow omitting locations in WebGL 2.0 contexts. +TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedNoLocations) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended")); + + constexpr char kFS[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +out highp vec4 color0; +out highp vec4 color1; +void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); + color1 = vec4(0.0, 1.0, 0.0, 1.0); +})"; + + GLProgram prg; + prg.makeRaster(essl3_shaders::vs::Simple(), kFS); + EXPECT_FALSE(prg.valid()); +} + +// Test that a secondary fragment output is declared +// when using EXT_blend_func_extended in WebGL 1.0 contexts. +TEST_P(WebGLCompatibilityTest, EXTBlendFuncExtendedMissingOutputs) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended")); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT); + ASSERT_GL_NO_ERROR(); + + { + constexpr char kFragColor[] = R"( +void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragColor); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kSecondaryFragColor[] = R"(#extension GL_EXT_blend_func_extended : require +void main() { + gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kSecondaryFragColor); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kFragColorAndSecondaryFragColor[] = + R"(#extension GL_EXT_blend_func_extended : require +void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragColorAndSecondaryFragColor); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_NO_ERROR(); + } +} + +// Test that a secondary fragment output is declared +// when using EXT_blend_func_extended in WebGL 1.0 contexts. +TEST_P(WebGLCompatibilityTest, EXTBlendFuncExtendedMissingOutputsArrays) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended")); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT); + ASSERT_GL_NO_ERROR(); + + { + constexpr char kFragData[] = R"( +void main() { + gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragData); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kSecondaryFragData[] = R"(#extension GL_EXT_blend_func_extended : require +void main() { + gl_SecondaryFragDataEXT[0] = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kSecondaryFragData); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kFragDataAndSecondaryFragData[] = + R"(#extension GL_EXT_blend_func_extended : require +void main() { + gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0); + gl_SecondaryFragDataEXT[0] = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragDataAndSecondaryFragData); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_NO_ERROR(); + } +} + +// Test that a secondary fragment output is declared +// when using EXT_blend_func_extended in WebGL 2.0 contexts. +TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedMissingOutputs) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended")); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT); + ASSERT_GL_NO_ERROR(); + + { + constexpr char kColor0[] = R"(#version 300 es +out mediump vec4 color0; +void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor0); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kColor1[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +layout(location = 0, index = 1) out mediump vec4 color1; +void main() { + color1 = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor1); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kColor0AndColor1[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +layout(location = 0, index = 0) out mediump vec4 color0; +layout(location = 0, index = 1) out mediump vec4 color1; +void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); + color1 = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor0AndColor1); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_NO_ERROR(); + } +} + +// Test that a secondary fragment output is declared +// when using EXT_blend_func_extended in WebGL 2.0 contexts. +TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedMissingOutputsArrays) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended")); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT); + ASSERT_GL_NO_ERROR(); + + { + constexpr char kArrayColor0[] = R"(#version 300 es +out mediump vec4 color0[1]; +void main() { + color0[0] = vec4(1.0, 0.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor0); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kArrayColor1[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +layout(location = 0, index = 1) out mediump vec4 color1[1]; +void main() { + color1[0] = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor1); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_ERROR(GL_INVALID_OPERATION); + } + { + constexpr char kArrayColor0AndColor0[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +layout(location = 0, index = 0) out mediump vec4 color0[1]; +layout(location = 0, index = 1) out mediump vec4 color1[1]; +void main() { + color0[0] = vec4(1.0, 0.0, 0.0, 1.0); + color1[0] = vec4(0.0, 1.0, 0.0, 1.0); +})"; + ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor0AndColor0); + drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true); + ASSERT_GL_NO_ERROR(); + } +} + // Test for a mishandling of instanced vertex attributes with zero-sized buffers bound on Apple // OpenGL drivers. TEST_P(WebGL2CompatibilityTest, DrawWithZeroSizedBuffer)