// // Copyright 2018 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // BlendFuncExtendedTest // Test EXT_blend_func_extended #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" #include "util/shader_utils.h" #include #include #include using namespace angle; namespace { // Partial implementation of weight function for GLES 2 blend equation that // is dual-source aware. template float Weight(const float /*dst*/[4], const float src[4], const float src1[4]) { if (factor == GL_SRC_COLOR) return src[index]; if (factor == GL_SRC_ALPHA) return src[3]; if (factor == GL_SRC1_COLOR_EXT) return src1[index]; if (factor == GL_SRC1_ALPHA_EXT) return src1[3]; if (factor == GL_ONE_MINUS_SRC1_COLOR_EXT) return 1.0f - src1[index]; if (factor == GL_ONE_MINUS_SRC1_ALPHA_EXT) return 1.0f - src1[3]; return 0.0f; } GLubyte ScaleChannel(float weight) { return static_cast(std::floor(std::max(0.0f, std::min(1.0f, weight)) * 255.0f)); } // Implementation of GLES 2 blend equation that is dual-source aware. template void BlendEquationFuncAdd(const float dst[4], const float src[4], const float src1[4], angle::GLColor *result) { float r[4]; r[0] = src[0] * Weight(dst, src, src1) + dst[0] * Weight(dst, src, src1); r[1] = src[1] * Weight(dst, src, src1) + dst[1] * Weight(dst, src, src1); r[2] = src[2] * Weight(dst, src, src1) + dst[2] * Weight(dst, src, src1); r[3] = src[3] * Weight(dst, src, src1) + dst[3] * Weight(dst, src, src1); result->R = ScaleChannel(r[0]); result->G = ScaleChannel(r[1]); result->B = ScaleChannel(r[2]); result->A = ScaleChannel(r[3]); } void CheckPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLint tolerance, const angle::GLColor &color) { for (GLint yy = 0; yy < height; ++yy) { for (GLint xx = 0; xx < width; ++xx) { const auto px = x + xx; const auto py = y + yy; EXPECT_PIXEL_COLOR_NEAR(px, py, color, 2); } } } const GLuint kWidth = 100; const GLuint kHeight = 100; class EXTBlendFuncExtendedTest : public ANGLETest<> {}; class EXTBlendFuncExtendedTestES3 : public ANGLETest<> {}; class EXTBlendFuncExtendedDrawTest : public ANGLETest<> { protected: EXTBlendFuncExtendedDrawTest() : mProgram(0) { setWindowWidth(kWidth); setWindowHeight(kHeight); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } void testSetUp() override { glGenBuffers(1, &mVBO); glBindBuffer(GL_ARRAY_BUFFER, mVBO); static const float vertices[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, }; glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); ASSERT_GL_NO_ERROR(); } void testTearDown() override { glDeleteBuffers(1, &mVBO); if (mProgram) { glDeleteProgram(mProgram); } ASSERT_GL_NO_ERROR(); } void makeProgram(const char *vertSource, const char *fragSource) { mProgram = CompileProgram(vertSource, fragSource); ASSERT_NE(0u, mProgram); } virtual GLint getVertexAttribLocation(const char *name) { return glGetAttribLocation(mProgram, name); } virtual GLint getFragmentUniformLocation(const char *name) { return glGetUniformLocation(mProgram, name); } virtual void setUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { glUniform4f(location, v0, v1, v2, v3); } void drawTest() { glUseProgram(mProgram); GLint position = getVertexAttribLocation(essl1_shaders::PositionAttrib()); GLint src0 = getFragmentUniformLocation("src0"); GLint src1 = getFragmentUniformLocation("src1"); ASSERT_GL_NO_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, mVBO); glEnableVertexAttribArray(position); glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, 0); ASSERT_GL_NO_ERROR(); static const float kDst[4] = {0.5f, 0.5f, 0.5f, 0.5f}; static const float kSrc0[4] = {1.0f, 1.0f, 1.0f, 1.0f}; static const float kSrc1[4] = {0.3f, 0.6f, 0.9f, 0.7f}; setUniform4f(src0, kSrc0[0], kSrc0[1], kSrc0[2], kSrc0[3]); setUniform4f(src1, kSrc1[0], kSrc1[1], kSrc1[2], kSrc1[3]); ASSERT_GL_NO_ERROR(); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glViewport(0, 0, kWidth, kHeight); glClearColor(kDst[0], kDst[1], kDst[2], kDst[3]); ASSERT_GL_NO_ERROR(); { glBlendFuncSeparate(GL_SRC1_COLOR_EXT, GL_SRC_ALPHA, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_ALPHA_EXT); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 6); ASSERT_GL_NO_ERROR(); // verify angle::GLColor color; BlendEquationFuncAdd(kDst, kSrc0, kSrc1, &color); CheckPixels(kWidth / 4, (3 * kHeight) / 4, 1, 1, 1, color); CheckPixels(kWidth - 1, 0, 1, 1, 1, color); } { glBlendFuncSeparate(GL_ONE_MINUS_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR, GL_SRC1_ALPHA_EXT); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 6); ASSERT_GL_NO_ERROR(); // verify angle::GLColor color; BlendEquationFuncAdd(kDst, kSrc0, kSrc1, &color); CheckPixels(kWidth / 4, (3 * kHeight) / 4, 1, 1, 1, color); CheckPixels(kWidth - 1, 0, 1, 1, 1, color); } } GLuint mVBO; GLuint mProgram; }; class EXTBlendFuncExtendedDrawTestES3 : public EXTBlendFuncExtendedDrawTest { protected: EXTBlendFuncExtendedDrawTestES3() : EXTBlendFuncExtendedDrawTest(), mIsES31OrNewer(false) {} void testSetUp() override { EXTBlendFuncExtendedDrawTest::testSetUp(); if (getClientMajorVersion() > 3 || (getClientMajorVersion() == 3 && getClientMinorVersion() >= 1)) { mIsES31OrNewer = true; } } virtual void checkOutputIndexQuery(const char *name, GLint expectedIndex) { GLint index = glGetFragDataIndexEXT(mProgram, name); EXPECT_EQ(expectedIndex, index); if (mIsES31OrNewer) { index = glGetProgramResourceLocationIndexEXT(mProgram, GL_PROGRAM_OUTPUT, name); EXPECT_EQ(expectedIndex, index); } else { glGetProgramResourceLocationIndexEXT(mProgram, GL_PROGRAM_OUTPUT, name); EXPECT_GL_ERROR(GL_INVALID_OPERATION); } } void LinkProgram() { glLinkProgram(mProgram); GLint linked = 0; glGetProgramiv(mProgram, GL_LINK_STATUS, &linked); EXPECT_NE(0, linked); glUseProgram(mProgram); return; } private: bool mIsES31OrNewer; }; class EXTBlendFuncExtendedDrawTestES31 : public EXTBlendFuncExtendedDrawTestES3 { protected: EXTBlendFuncExtendedDrawTestES31() : EXTBlendFuncExtendedDrawTestES3(), mPipeline(0), mVertexProgram(0), mFragProgram(0) {} GLint getVertexAttribLocation(const char *name) override { return glGetAttribLocation(mVertexProgram, name); } GLint getFragmentUniformLocation(const char *name) override { return glGetUniformLocation(mFragProgram, name); } void setUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) override { glActiveShaderProgram(mPipeline, mFragProgram); EXTBlendFuncExtendedDrawTest::setUniform4f(location, v0, v1, v2, v3); } void checkOutputIndexQuery(const char *name, GLint expectedIndex) override { GLint index = glGetFragDataIndexEXT(mFragProgram, name); EXPECT_EQ(expectedIndex, index); index = glGetProgramResourceLocationIndexEXT(mFragProgram, GL_PROGRAM_OUTPUT, name); EXPECT_EQ(expectedIndex, index); } void setupProgramPipeline(const char *vertexSource, const char *fragmentSource) { mVertexProgram = createShaderProgram(GL_VERTEX_SHADER, vertexSource); ASSERT_NE(mVertexProgram, 0u); mFragProgram = createShaderProgram(GL_FRAGMENT_SHADER, fragmentSource); ASSERT_NE(mFragProgram, 0u); // Generate a program pipeline and attach the programs to their respective stages glGenProgramPipelines(1, &mPipeline); EXPECT_GL_NO_ERROR(); glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertexProgram); EXPECT_GL_NO_ERROR(); glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProgram); EXPECT_GL_NO_ERROR(); glBindProgramPipeline(mPipeline); EXPECT_GL_NO_ERROR(); } GLuint createShaderProgram(GLenum type, const GLchar *shaderString) { GLShader shader(type); if (!shader) { return 0; } glShaderSource(shader, 1, &shaderString, nullptr); glCompileShader(shader); GLuint program = glCreateProgram(); if (program) { GLint compiled; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE); if (compiled) { glAttachShader(program, shader); glLinkProgram(program); glDetachShader(program, shader); } } EXPECT_GL_NO_ERROR(); return program; } void testTearDown() override { EXTBlendFuncExtendedDrawTest::testTearDown(); if (mVertexProgram) { glDeleteProgram(mVertexProgram); } if (mFragProgram) { glDeleteProgram(mFragProgram); } if (mPipeline) { glDeleteProgramPipelines(1, &mPipeline); } ASSERT_GL_NO_ERROR(); } GLuint mPipeline; GLuint mVertexProgram; GLuint mFragProgram; }; } // namespace // Test EXT_blend_func_extended related gets. TEST_P(EXTBlendFuncExtendedTest, TestMaxDualSourceDrawBuffers) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); GLint maxDualSourceDrawBuffers = 0; glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers); EXPECT_GT(maxDualSourceDrawBuffers, 0); ASSERT_GL_NO_ERROR(); } // Test that SRC1 factors limit the number of allowed draw buffers. TEST_P(EXTBlendFuncExtendedTest, MaxDualSourceDrawBuffersError) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_rgb8_rgba8")); GLint maxDualSourceDrawBuffers = 0; glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers); ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers != 1); ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rb0; glBindRenderbuffer(GL_RENDERBUFFER, rb0); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, rb0); GLRenderbuffer rb1; glBindRenderbuffer(GL_RENDERBUFFER, rb1); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER, rb1); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); const GLenum bufs[] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT}; glDrawBuffersEXT(2, bufs); ASSERT_GL_NO_ERROR(); for (const GLenum func : {GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT, GL_ONE_MINUS_SRC1_ALPHA_EXT}) { for (size_t slot = 0; slot < 4; slot++) { switch (slot) { case 0: glBlendFuncSeparate(func, GL_ONE, GL_ONE, GL_ONE); break; case 1: glBlendFuncSeparate(GL_ONE, func, GL_ONE, GL_ONE); break; case 2: glBlendFuncSeparate(GL_ONE, GL_ONE, func, GL_ONE); break; case 3: glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, func); break; } // Limit must be applied even with blending disabled glDisable(GL_BLEND); drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0); EXPECT_GL_ERROR(GL_INVALID_OPERATION); glEnable(GL_BLEND); drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0); EXPECT_GL_ERROR(GL_INVALID_OPERATION); // Limit must be applied even when an attachment is missing glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER, 0); drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0); EXPECT_GL_ERROR(GL_INVALID_OPERATION); // Restore the attachment for the next iteration glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER, rb1); // Limit is not applied when non-SRC1 funcs are used glBlendFunc(GL_ONE, GL_ONE); drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0); EXPECT_GL_NO_ERROR(); } } } // Test a shader with EXT_blend_func_extended and gl_SecondaryFragColorEXT. // Outputs to primary color buffer using primary and secondary colors. TEST_P(EXTBlendFuncExtendedDrawTest, FragColor) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShader = "#extension GL_EXT_blend_func_extended : require\n" "precision mediump float;\n" "uniform vec4 src0;\n" "uniform vec4 src1;\n" "void main() {\n" " gl_FragColor = src0;\n" " gl_SecondaryFragColorEXT = src1;\n" "}\n"; makeProgram(essl1_shaders::vs::Simple(), kFragColorShader); drawTest(); } // Test a shader with EXT_blend_func_extended and EXT_draw_buffers enabled at the same time. TEST_P(EXTBlendFuncExtendedDrawTest, FragColorBroadcast) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers")); const char *kFragColorShader = "#extension GL_EXT_blend_func_extended : require\n" "#extension GL_EXT_draw_buffers : require\n" "precision mediump float;\n" "uniform vec4 src0;\n" "uniform vec4 src1;\n" "void main() {\n" " gl_FragColor = src0;\n" " gl_SecondaryFragColorEXT = src1;\n" "}\n"; makeProgram(essl1_shaders::vs::Simple(), kFragColorShader); drawTest(); } // Test a shader with EXT_blend_func_extended and gl_FragData. // Outputs to a color buffer using primary and secondary frag data. TEST_P(EXTBlendFuncExtendedDrawTest, FragData) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShader = "#extension GL_EXT_blend_func_extended : require\n" "precision mediump float;\n" "uniform vec4 src0;\n" "uniform vec4 src1;\n" "void main() {\n" " gl_FragData[0] = src0;\n" " gl_SecondaryFragDataEXT[0] = src1;\n" "}\n"; makeProgram(essl1_shaders::vs::Simple(), kFragColorShader); drawTest(); } // Test that min/max blending works correctly with SRC1 factors. TEST_P(EXTBlendFuncExtendedDrawTest, MinMax) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_minmax")); const char *kFragColorShader = R"(#extension GL_EXT_blend_func_extended : require precision mediump float; void main() { gl_FragColor = vec4(0.125, 0.25, 0.75, 0.875); gl_SecondaryFragColorEXT = vec4(0.0, 0.0, 0.0, 0.0); })"; makeProgram(essl1_shaders::vs::Simple(), kFragColorShader); glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT, GL_ONE_MINUS_SRC1_ALPHA_EXT); glClearColor(0.5, 0.5, 0.5, 0.5); auto test = [&](GLenum colorOp, GLenum alphaOp, GLColor color) { glBlendEquationSeparate(colorOp, alphaOp); glClear(GL_COLOR_BUFFER_BIT); drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0); EXPECT_PIXEL_COLOR_NEAR(0, 0, color, 2); }; test(GL_MIN_EXT, GL_MIN_EXT, GLColor(32, 64, 128, 128)); test(GL_MIN_EXT, GL_MAX_EXT, GLColor(32, 64, 128, 224)); test(GL_MAX_EXT, GL_MIN_EXT, GLColor(128, 128, 192, 128)); test(GL_MAX_EXT, GL_MAX_EXT, GLColor(128, 128, 192, 224)); } // Test an ESSL 3.00 shader that uses two fragment outputs with locations specified in the shader. TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsInShader) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShader = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; layout(location = 0, index = 1) out vec4 outSrc1; layout(location = 0, index = 0) out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; makeProgram(essl3_shaders::vs::Simple(), kFragColorShader); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); drawTest(); } // Test an ESSL 3.00 shader that uses two fragment outputs with locations specified through the API. TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsAPI) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; out vec4 outSrc1; out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) { glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0"); glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1"); }); ASSERT_NE(0u, mProgram); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); drawTest(); } // Test an ESSL 3.00 shader that uses two fragment outputs, with location for one specified through // the API and location for another being set automatically. TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsAPIAndAutomatic) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; out vec4 outSrc1; out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) { glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1"); }); ASSERT_NE(0u, mProgram); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); drawTest(); } // Test an ESSL 3.00 shader that uses two array fragment outputs with locations // specified in the shader. TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsInShader) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShader = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; layout(location = 0, index = 1) out vec4 outSrc1[1]; layout(location = 0, index = 0) out vec4 outSrc0[1]; void main() { outSrc0[0] = src0; outSrc1[0] = src1; })"; makeProgram(essl3_shaders::vs::Simple(), kFragColorShader); checkOutputIndexQuery("outSrc0[0]", 0); checkOutputIndexQuery("outSrc1[0]", 1); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); // These queries use an out of range array index so they should return -1. checkOutputIndexQuery("outSrc0[1]", -1); checkOutputIndexQuery("outSrc1[1]", -1); drawTest(); } // Test an ESSL 3.00 shader that uses two array fragment outputs with locations specified through // the API. TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsAPI) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; out vec4 outSrc1[1]; out vec4 outSrc0[1]; void main() { outSrc0[0] = src0; outSrc1[0] = src1; })"; mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) { // Specs aren't very clear on what kind of name should be used when binding location for // array variables. We only allow names that do include the "[0]" suffix. glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0[0]"); glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1[0]"); }); ASSERT_NE(0u, mProgram); // The extension spec is not very clear on what name can be used for the queries for array // variables. We're checking that the queries work in the same way as specified in OpenGL 4.4 // page 107. checkOutputIndexQuery("outSrc0[0]", 0); checkOutputIndexQuery("outSrc1[0]", 1); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); // These queries use an out of range array index so they should return -1. checkOutputIndexQuery("outSrc0[1]", -1); checkOutputIndexQuery("outSrc1[1]", -1); drawTest(); } // Ported from TranslatorVariants/EXTBlendFuncExtendedES3DrawTest // Test that tests glBindFragDataLocationEXT, glBindFragDataLocationIndexedEXT, // glGetFragDataLocation, glGetFragDataIndexEXT work correctly with // GLSL array output variables. The output variable can be bound by // referring to the variable name with or without the first element array // accessor. The getters can query location of the individual elements in // the array. The test does not actually use the base test drawing, // since the drivers at the time of writing do not support multiple // buffers and dual source blending. TEST_P(EXTBlendFuncExtendedDrawTestES3, ES3GettersArray) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const GLint kTestArraySize = 2; const GLint kFragData0Location = 2; const GLint kFragData1Location = 1; const GLint kUnusedLocation = 5; // The test binds kTestArraySize -sized array to location 1 for test purposes. // The GL_MAX_DRAW_BUFFERS must be > kTestArraySize, since an // array will be bound to continuous locations, starting from the first // location. GLint maxDrawBuffers = 0; glGetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &maxDrawBuffers); EXPECT_LT(kTestArraySize, maxDrawBuffers); constexpr char kFragColorShader[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src; uniform vec4 src1; out vec4 FragData[2]; void main() { FragData[0] = src; FragData[1] = src1; })"; struct testCase { std::string unusedLocationName; std::string fragData0LocationName; std::string fragData1LocationName; }; testCase testCases[4]{{"FragData[0]", "FragData", "FragData[1]"}, {"FragData", "FragData[0]", "FragData[1]"}, {"FragData[0]", "FragData", "FragData[1]"}, {"FragData", "FragData[0]", "FragData[1]"}}; for (const testCase &test : testCases) { mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFragColorShader, [&](GLuint program) { glBindFragDataLocationEXT(program, kUnusedLocation, test.unusedLocationName.c_str()); glBindFragDataLocationEXT(program, kFragData0Location, test.fragData0LocationName.c_str()); glBindFragDataLocationEXT(program, kFragData1Location, test.fragData1LocationName.c_str()); }); EXPECT_EQ(static_cast(GL_NO_ERROR), glGetError()); LinkProgram(); EXPECT_EQ(kFragData0Location, glGetFragDataLocation(mProgram, "FragData")); EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData")); EXPECT_EQ(kFragData0Location, glGetFragDataLocation(mProgram, "FragData[0]")); EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData[0]")); EXPECT_EQ(kFragData1Location, glGetFragDataLocation(mProgram, "FragData[1]")); EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData[1]")); // Index bigger than the GLSL variable array length does not find anything. EXPECT_EQ(-1, glGetFragDataLocation(mProgram, "FragData[3]")); } } // Ported from TranslatorVariants/EXTBlendFuncExtendedES3DrawTest TEST_P(EXTBlendFuncExtendedDrawTestES3, ESSL3BindSimpleVarAsArrayNoBind) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFragDataShader[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src; uniform vec4 src1; out vec4 FragData; out vec4 SecondaryFragData; void main() { FragData = src; SecondaryFragData = src1; })"; mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFragDataShader, [](GLuint program) { glBindFragDataLocationEXT(program, 0, "FragData[0]"); glBindFragDataLocationIndexedEXT(program, 0, 1, "SecondaryFragData[0]"); }); LinkProgram(); EXPECT_EQ(-1, glGetFragDataLocation(mProgram, "FragData[0]")); EXPECT_EQ(0, glGetFragDataLocation(mProgram, "FragData")); EXPECT_EQ(1, glGetFragDataLocation(mProgram, "SecondaryFragData")); // Did not bind index. EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "SecondaryFragData")); glBindFragDataLocationEXT(mProgram, 0, "FragData"); glBindFragDataLocationIndexedEXT(mProgram, 0, 1, "SecondaryFragData"); LinkProgram(); } // Test an ESSL 3.00 program with a link-time fragment output location conflict. TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationConflict) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; out vec4 out0; out vec4 out1; void main() { out0 = src0; out1 = src1; })"; 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); glBindFragDataLocationIndexedEXT(program, 0, 0, "out0"); glBindFragDataLocationIndexedEXT(program, 0, 0, "out1"); // The program should fail to link. glLinkProgram(program); GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); EXPECT_EQ(0, linkStatus); glDeleteProgram(program); } // Test an ESSL 3.00 program with some bindings set for nonexistent variables. These should not // create link-time conflicts. TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationForNonexistentOutput) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; out vec4 out0; void main() { out0 = src0; })"; 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); glBindFragDataLocationIndexedEXT(program, 0, 0, "out0"); glBindFragDataLocationIndexedEXT(program, 0, 0, "out1"); glBindFragDataLocationIndexedEXT(program, 0, 0, "out2[0]"); // The program should link successfully - conflicting location for nonexistent variables out1 or // out2 should not be an issue. glLinkProgram(program); GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); EXPECT_NE(0, linkStatus); glDeleteProgram(program); } // Test mixing shader-assigned and automatic output locations. TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationsPartiallyAutomatic) { 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; uniform vec4 src0; uniform vec4 src1; uniform vec4 src2; uniform vec4 src3; layout(location=0) out vec4 out0; layout(location=3) out vec4 out3; out vec4 out12[2]; void main() { out0 = src0; out12[0] = src1; out12[1] = src2; out3 = src3; })"; GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFS); ASSERT_NE(0u, program); GLint location = glGetFragDataLocation(program, "out0"); EXPECT_EQ(0, location); location = glGetFragDataLocation(program, "out12"); EXPECT_EQ(1, location); location = glGetFragDataLocation(program, "out3"); EXPECT_EQ(3, location); glDeleteProgram(program); } // Test a fragment output array that doesn't fit because contiguous locations are not available. TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputArrayDoesntFit) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); GLint maxDrawBuffers; glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); ANGLE_SKIP_TEST_IF(maxDrawBuffers < 4); std::stringstream fragShader; fragShader << R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; layout(location=2) out vec4 out0; out vec4 outArray[)" << (maxDrawBuffers - 1) << R"(]; void main() { out0 = vec4(1.0); 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); // The program should not link - there's no way to fit "outArray" into 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) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); GLint maxDualSourceDrawBuffers; glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers); ASSERT_GE(maxDualSourceDrawBuffers, 1); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; out vec4 outSrc0; out vec4 outSrc1; void main() { outSrc0 = vec4(0.5); outSrc1 = 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); glBindFragDataLocationIndexedEXT(program, maxDualSourceDrawBuffers, 0, "outSrc0"); glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1"); ASSERT_GL_NO_ERROR(); GLint linkStatus; glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); EXPECT_EQ(0, linkStatus); glDeleteProgram(program); } // Test that fragment outputs bound to the same location must have the same type. TEST_P(EXTBlendFuncExtendedTestES3, InconsistentTypesForLocationAPI) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; out vec4 outSrc0; out ivec4 outSrc1; void main() { outSrc0 = vec4(0.5); outSrc1 = ivec4(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); glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0"); glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1"); ASSERT_GL_NO_ERROR(); GLint linkStatus; glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); EXPECT_EQ(0, linkStatus); glDeleteProgram(program); } // Test that rendering to multiple fragment outputs bound via API works. TEST_P(EXTBlendFuncExtendedDrawTestES3, MultipleDrawBuffersAPI) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); constexpr char kFS[] = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; out vec4 outSrc0; out ivec4 outSrc1; void main() { outSrc0 = vec4(0.0, 1.0, 0.0, 1.0); outSrc1 = ivec4(1, 2, 3, 4); })"; mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) { glBindFragDataLocationEXT(program, 0, "outSrc0"); glBindFragDataLocationEXT(program, 1, "outSrc1"); }); ASSERT_NE(0u, mProgram); GLRenderbuffer rb0; glBindRenderbuffer(GL_RENDERBUFFER, rb0); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1); GLRenderbuffer rb1; glBindRenderbuffer(GL_RENDERBUFFER, rb1); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8I, 1, 1); GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); const GLenum bufs[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(2, bufs); GLfloat clearF[] = {0.0, 0.0, 0.0, 0.0}; GLint clearI[] = {0, 0, 0, 0}; // FBO: rb0 (rgba8), rb1 (rgba8i) glBindRenderbuffer(GL_RENDERBUFFER, rb0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0); glBindRenderbuffer(GL_RENDERBUFFER, rb1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rb1); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); glClearBufferfv(GL_COLOR, 0, clearF); glClearBufferiv(GL_COLOR, 1, clearI); ASSERT_GL_NO_ERROR(); glReadBuffer(GL_COLOR_ATTACHMENT0); EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0); glReadBuffer(GL_COLOR_ATTACHMENT1); EXPECT_PIXEL_8I(0, 0, 0, 0, 0, 0); drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glReadBuffer(GL_COLOR_ATTACHMENT0); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); glReadBuffer(GL_COLOR_ATTACHMENT1); EXPECT_PIXEL_8I(0, 0, 1, 2, 3, 4); // FBO: rb1 (rgba8i), rb0 (rgba8) glBindRenderbuffer(GL_RENDERBUFFER, rb0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rb0); glBindRenderbuffer(GL_RENDERBUFFER, rb1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb1); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Rebind fragment outputs glBindFragDataLocationEXT(mProgram, 0, "outSrc1"); glBindFragDataLocationEXT(mProgram, 1, "outSrc0"); glLinkProgram(mProgram); glClearBufferfv(GL_COLOR, 1, clearF); glClearBufferiv(GL_COLOR, 0, clearI); ASSERT_GL_NO_ERROR(); glReadBuffer(GL_COLOR_ATTACHMENT1); EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0); glReadBuffer(GL_COLOR_ATTACHMENT0); EXPECT_PIXEL_8I(0, 0, 0, 0, 0, 0); drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glReadBuffer(GL_COLOR_ATTACHMENT1); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); glReadBuffer(GL_COLOR_ATTACHMENT0); EXPECT_PIXEL_8I(0, 0, 1, 2, 3, 4); } // Use a program pipeline with EXT_blend_func_extended TEST_P(EXTBlendFuncExtendedDrawTestES31, UseProgramPipeline) { // Only the Vulkan backend supports PPO ANGLE_SKIP_TEST_IF(!IsVulkan()); // Create two separable program objects from a // single source string respectively (vertSrc and fragSrc) ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShader = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; layout(location = 0, index = 1) out vec4 outSrc1; layout(location = 0, index = 0) out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; setupProgramPipeline(essl3_shaders::vs::Simple(), kFragColorShader); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); ASSERT_EQ(mProgram, 0u); drawTest(); ASSERT_GL_NO_ERROR(); } // Use program pipeline where the fragment program is changed TEST_P(EXTBlendFuncExtendedDrawTestES31, UseTwoProgramStages) { // Only the Vulkan backend supports PPO ANGLE_SKIP_TEST_IF(!IsVulkan()); // Create two separable program objects from a // single source string respectively (vertSrc and fragSrc) ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); const char *kFragColorShaderFlipped = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; layout(location = 0, index = 0) out vec4 outSrc1; layout(location = 0, index = 1) out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; const char *kFragColorShader = R"(#version 300 es #extension GL_EXT_blend_func_extended : require precision mediump float; uniform vec4 src0; uniform vec4 src1; layout(location = 0, index = 1) out vec4 outSrc1; layout(location = 0, index = 0) out vec4 outSrc0; void main() { outSrc0 = src0; outSrc1 = src1; })"; setupProgramPipeline(essl3_shaders::vs::Simple(), kFragColorShaderFlipped); // Check index values frag shader with the "flipped" index values checkOutputIndexQuery("outSrc0", 1); checkOutputIndexQuery("outSrc1", 0); GLuint previousProgram = mFragProgram; mFragProgram = createShaderProgram(GL_FRAGMENT_SHADER, kFragColorShader); ASSERT_NE(mFragProgram, 0u); // Change the Fragment program of the pipeline glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProgram); EXPECT_GL_NO_ERROR(); checkOutputIndexQuery("outSrc0", 0); checkOutputIndexQuery("outSrc1", 1); ASSERT_EQ(mProgram, 0u); drawTest(); if (previousProgram) { glDeleteProgram(previousProgram); } ASSERT_GL_NO_ERROR(); } ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedTest); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedTestES3); ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedTestES3); ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedDrawTest); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedDrawTestES3); ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedDrawTestES3); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedDrawTestES31); ANGLE_INSTANTIATE_TEST_ES31(EXTBlendFuncExtendedDrawTestES31);