Edit

kc3-lang/angle/src/tests/gl_tests/BlendFuncExtendedTest.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2019-12-03 14:11:17
    Hash : af52f9c6
    Message : Fix tests checking for major version > 3 The tests meant to do getClientMajorVersion() >= 3, but in a few locations accidentally did > 3. Bug: angleproject:3569 Change-Id: Ie69898348982c6aae7dac265a18229c257dbfbda Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1947458 Reviewed-by: Jonah Ryan-Davis <jonahr@google.com> Reviewed-by: Geoff Lang <geofflang@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/tests/gl_tests/BlendFuncExtendedTest.cpp
  • //
    // 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 "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);
        }
    
        void drawTest()
        {
            glUseProgram(mProgram);
    
            GLint position = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
            GLint src0     = glGetUniformLocation(mProgram, "src0");
            GLint src1     = glGetUniformLocation(mProgram, "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};
    
            glUniform4f(src0, kSrc0[0], kSrc0[1], kSrc0[2], kSrc0[3]);
            glUniform4f(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;
            }
        }
        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;
    };
    
    }  // 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"));
    
        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());
    
        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);
    }
    
    ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedTest);
    ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedTestES3);
    
    ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedDrawTest);
    ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedDrawTestES3);