diff --git a/src/libANGLE/ProgramExecutable.cpp b/src/libANGLE/ProgramExecutable.cpp index c5a431ddc1..9c73a3590c 100644 --- a/src/libANGLE/ProgramExecutable.cpp +++ b/src/libANGLE/ProgramExecutable.cpp @@ -1742,12 +1742,11 @@ bool ProgramExecutable::linkValidateOutputVariables( outputVariableIndex, false, outputVariable); } - // Check for any elements assigned above the max location that are actually used. - if (baseLocation + elementCount > maxLocation && - (baseLocation >= maxLocation || - FindUsedOutputLocation(outputLocations, maxLocation, - baseLocation + elementCount - maxLocation, reservedLocations, - outputVariableIndex))) + // Fail linking if the contiguous range for this output reaches or exceeds the maximum + // location. Output arrays consume contiguous locations starting at the base, so the whole + // array must fit regardless of any per-element bindings that may have redirected + // individual indices. + if (baseLocation + elementCount > maxLocation) { // EXT_blend_func_extended: Linking can fail: // "if the explicit binding assignments do not leave enough space for the linker to diff --git a/src/tests/gl_tests/BlendFuncExtendedTest.cpp b/src/tests/gl_tests/BlendFuncExtendedTest.cpp index 8c9d8d306d..e1b76f2121 100644 --- a/src/tests/gl_tests/BlendFuncExtendedTest.cpp +++ b/src/tests/gl_tests/BlendFuncExtendedTest.cpp @@ -1030,6 +1030,102 @@ void main() { glDeleteProgram(program); } +// Test a fragment output array whose base is bound near the end of the available draw buffer +// range while individual elements are bound to lower locations. The contiguous range starting at +// the base location does not fit within MAX_DRAW_BUFFERS so linking must fail. +TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputArrayPerElementBindingDoesntFit) +{ + ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); + + GLint maxDrawBuffers; + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + ANGLE_SKIP_TEST_IF(maxDrawBuffers < 2); + + std::stringstream fragShader; + fragShader << R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +precision mediump float; +out vec4 outArray[)" + << maxDrawBuffers << R"(]; +void main() { + outArray[0] = vec4(1.0); +})"; + + GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple()); + GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragShader.str().c_str()); + ASSERT_NE(0u, vs); + ASSERT_NE(0u, fs); + + GLuint program = glCreateProgram(); + glAttachShader(program, vs); + glDeleteShader(vs); + glAttachShader(program, fs); + glDeleteShader(fs); + + glBindFragDataLocationEXT(program, maxDrawBuffers - 1, "outArray"); + for (GLint elementIndex = 1; elementIndex < maxDrawBuffers; ++elementIndex) + { + std::stringstream elementName; + elementName << "outArray[" << elementIndex << "]"; + glBindFragDataLocationEXT(program, elementIndex - 1, elementName.str().c_str()); + } + ASSERT_GL_NO_ERROR(); + + // The program should not link - there's no way to fit "outArray" starting at location + // MAX_DRAW_BUFFERS-1 into the available output locations. + glLinkProgram(program); + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + EXPECT_EQ(0, linkStatus); + + glDeleteProgram(program); +} + +// Test a fragment output array bound near the end of the available draw buffer range with one +// element redirected to a lower location. The remaining elements still extend past +// MAX_DRAW_BUFFERS so linking must fail. +TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputArraySingleElementBindingDoesntFit) +{ + ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); + + GLint maxDrawBuffers; + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + ANGLE_SKIP_TEST_IF(maxDrawBuffers < 4); + + constexpr char kFS[] = R"(#version 300 es +#extension GL_EXT_blend_func_extended : require +precision mediump float; +out vec4 outArray[4]; +void main() { + outArray[0] = vec4(1.0); +})"; + + GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple()); + GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS); + ASSERT_NE(0u, vs); + ASSERT_NE(0u, fs); + + GLuint program = glCreateProgram(); + glAttachShader(program, vs); + glDeleteShader(vs); + glAttachShader(program, fs); + glDeleteShader(fs); + + glBindFragDataLocationEXT(program, maxDrawBuffers - 2, "outArray"); + glBindFragDataLocationEXT(program, maxDrawBuffers - 1, "outArray[1]"); + glBindFragDataLocationEXT(program, 0, "outArray[2]"); + ASSERT_GL_NO_ERROR(); + + // The program should not link - the array starting at location MAX_DRAW_BUFFERS-2 with four + // elements extends past the available output locations. + glLinkProgram(program); + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + EXPECT_EQ(0, linkStatus); + + glDeleteProgram(program); +} + // Test that a secondary blending source limits the number of primary outputs. TEST_P(EXTBlendFuncExtendedTestES3, TooManyFragmentOutputsForDualSourceBlending) { @@ -1300,7 +1396,7 @@ void main() { ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedTest); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedTestES3); -ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedTestES3); +ANGLE_INSTANTIATE_TEST_ES3_AND_ES31_AND(EXTBlendFuncExtendedTestES3, ES3_NULL()); ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedDrawTest);