| // | |
| // 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 <algorithm> | |
| #include <cmath> | |
| #include <fstream> | |
| using namespace angle; | |
| namespace | |
| { | |
| // Partial implementation of weight function for GLES 2 blend equation that | |
| // is dual-source aware. | |
| template <int factor, int index> | |
| 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<GLubyte>(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 <int RGBs, int RGBd, int As, int Ad> | |
| 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<RGBs, 0>(dst, src, src1) + dst[0] * Weight<RGBd, 0>(dst, src, src1); | |
| r[1] = src[1] * Weight<RGBs, 1>(dst, src, src1) + dst[1] * Weight<RGBd, 1>(dst, src, src1); | |
| r[2] = src[2] * Weight<RGBs, 2>(dst, src, src1) + dst[2] * Weight<RGBd, 2>(dst, src, src1); | |
| r[3] = src[3] * Weight<As, 3>(dst, src, src1) + dst[3] * Weight<Ad, 3>(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, 1); | |
| } | |
| } | |
| } | |
| 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<GL_SRC1_COLOR_EXT, GL_SRC_ALPHA, GL_ONE_MINUS_SRC1_COLOR_EXT, | |
| GL_ONE_MINUS_SRC1_ALPHA_EXT>(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<GL_ONE_MINUS_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC_ALPHA, | |
| GL_ONE_MINUS_SRC_COLOR, GL_SRC1_ALPHA_EXT>(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.get()) | |
| { | |
| 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 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 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")); | |
| // Suspected VK driver bug http://anglebug.com/5523 | |
| ANGLE_SKIP_TEST_IF(IsVulkan() && (IsNVIDIA() || IsPixel2())); | |
| // Suspected AMD VK driver bug http://anglebug.com/5537 | |
| ANGLE_SKIP_TEST_IF(IsVulkan() && IsWindows() && IsAMD()); | |
| 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 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, FragmentOutputLocationAPI) | |
| { | |
| 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 through | |
| // the API. | |
| TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsAPI) | |
| { | |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended")); | |
| // TODO: Investigate this mac-only failure. http://angleproject.com/1085 | |
| ANGLE_SKIP_TEST_IF(IsOSX()); | |
| // Suspected VK driver bug http://anglebug.com/5523 | |
| ANGLE_SKIP_TEST_IF(IsVulkan() && (IsNVIDIA() || IsPixel2())); | |
| 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")); | |
| // TODO(zmo): Figure out why this fails on AMD. crbug.com/585132. | |
| // Also fails on the Intel Mesa driver, see | |
| // https://bugs.freedesktop.org/show_bug.cgi?id=96765 | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD()); | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel()); | |
| 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<GLenum>(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); | |
| } | |
| // 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); |