Edit

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

Branch :

  • Show log

    Commit

  • Author : Corentin Wallez
    Date : 2016-11-15 13:40:37
    Hash : 8b7d8144
    Message : Clamp "count" in glUniform* before passing to the backend The OpenGL spec allows "count" to overflow safely implemented but some drivers like the Intel Windows OpenGL driver don't handle this correctly and crash on overflow tests. BUG=661413 Change-Id: I10de9292c75daa375f002850900bb5e1cbfce3b6 Reviewed-on: https://chromium-review.googlesource.com/411387 Reviewed-by: Geoff Lang <geofflang@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Corentin Wallez <cwallez@chromium.org>

  • src/tests/gl_tests/UniformTest.cpp
  • //
    // Copyright 2015 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.
    //
    
    #include "test_utils/ANGLETest.h"
    
    #include <array>
    #include <cmath>
    
    using namespace angle;
    
    namespace
    {
    
    class UniformTest : public ANGLETest
    {
      protected:
        UniformTest() : mProgram(0), mUniformFLocation(-1), mUniformILocation(-1), mUniformBLocation(-1)
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        void SetUp() override
        {
            ANGLETest::SetUp();
    
            const std::string &vertexShader = "void main() { gl_Position = vec4(1); }";
            const std::string &fragShader =
                "precision mediump float;\n"
                "uniform float uniF;\n"
                "uniform int uniI;\n"
                "uniform bool uniB;\n"
                "uniform bool uniBArr[4];\n"
                "void main() {\n"
                "  gl_FragColor = vec4(uniF + float(uniI));\n"
                "  gl_FragColor += vec4(uniB ? 1.0 : 0.0);\n"
                "  gl_FragColor += vec4(uniBArr[0] ? 1.0 : 0.0);\n"
                "}";
    
            mProgram = CompileProgram(vertexShader, fragShader);
            ASSERT_NE(mProgram, 0u);
    
            mUniformFLocation = glGetUniformLocation(mProgram, "uniF");
            ASSERT_NE(mUniformFLocation, -1);
    
            mUniformILocation = glGetUniformLocation(mProgram, "uniI");
            ASSERT_NE(mUniformILocation, -1);
    
            mUniformBLocation = glGetUniformLocation(mProgram, "uniB");
            ASSERT_NE(mUniformBLocation, -1);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void TearDown() override
        {
            glDeleteProgram(mProgram);
            ANGLETest::TearDown();
        }
    
        GLuint mProgram;
        GLint mUniformFLocation;
        GLint mUniformILocation;
        GLint mUniformBLocation;
    };
    
    TEST_P(UniformTest, GetUniformNoCurrentProgram)
    {
        glUseProgram(mProgram);
        glUniform1f(mUniformFLocation, 1.0f);
        glUniform1i(mUniformILocation, 1);
        glUseProgram(0);
    
        GLfloat f;
        glGetnUniformfvEXT(mProgram, mUniformFLocation, 4, &f);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1.0f, f);
    
        glGetUniformfv(mProgram, mUniformFLocation, &f);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1.0f, f);
    
        GLint i;
        glGetnUniformivEXT(mProgram, mUniformILocation, 4, &i);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1, i);
    
        glGetUniformiv(mProgram, mUniformILocation, &i);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1, i);
    }
    
    TEST_P(UniformTest, UniformArrayLocations)
    {
        // TODO(geofflang): Figure out why this is broken on Intel OpenGL
        if (IsIntel() && getPlatformRenderer() == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE)
        {
            std::cout << "Test skipped on Intel OpenGL." << std::endl;
            return;
        }
    
        const std::string vertexShader = SHADER_SOURCE
        (
            precision mediump float;
            uniform float uPosition[4];
            void main(void)
            {
                gl_Position = vec4(uPosition[0], uPosition[1], uPosition[2], uPosition[3]);
            }
        );
    
        const std::string fragShader = SHADER_SOURCE
        (
            precision mediump float;
            uniform float uColor[4];
            void main(void)
            {
                gl_FragColor = vec4(uColor[0], uColor[1], uColor[2], uColor[3]);
            }
        );
    
        GLuint program = CompileProgram(vertexShader, fragShader);
        ASSERT_NE(program, 0u);
    
        // Array index zero should be equivalent to the un-indexed uniform
        EXPECT_NE(-1, glGetUniformLocation(program, "uPosition"));
        EXPECT_EQ(glGetUniformLocation(program, "uPosition"), glGetUniformLocation(program, "uPosition[0]"));
    
        EXPECT_NE(-1, glGetUniformLocation(program, "uColor"));
        EXPECT_EQ(glGetUniformLocation(program, "uColor"), glGetUniformLocation(program, "uColor[0]"));
    
        // All array uniform locations should be unique
        GLint positionLocations[4] =
        {
            glGetUniformLocation(program, "uPosition[0]"),
            glGetUniformLocation(program, "uPosition[1]"),
            glGetUniformLocation(program, "uPosition[2]"),
            glGetUniformLocation(program, "uPosition[3]"),
        };
    
        GLint colorLocations[4] =
        {
            glGetUniformLocation(program, "uColor[0]"),
            glGetUniformLocation(program, "uColor[1]"),
            glGetUniformLocation(program, "uColor[2]"),
            glGetUniformLocation(program, "uColor[3]"),
        };
    
        for (size_t i = 0; i < 4; i++)
        {
            EXPECT_NE(-1, positionLocations[i]);
            EXPECT_NE(-1, colorLocations[i]);
    
            for (size_t j = i + 1; j < 4; j++)
            {
                EXPECT_NE(positionLocations[i], positionLocations[j]);
                EXPECT_NE(colorLocations[i], colorLocations[j]);
            }
        }
    
        glDeleteProgram(program);
    }
    
    // Test that float to integer GetUniform rounds values correctly.
    TEST_P(UniformTest, FloatUniformStateQuery)
    {
        std::vector<double> inValues;
        std::vector<GLfloat> expectedFValues;
        std::vector<GLint> expectedIValues;
    
        double intMaxD = static_cast<double>(std::numeric_limits<GLint>::max());
        double intMinD = static_cast<double>(std::numeric_limits<GLint>::min());
    
        // TODO(jmadill): Investigate rounding of .5
        inValues.push_back(-1.0);
        inValues.push_back(-0.6);
        // inValues.push_back(-0.5); // undefined behaviour?
        inValues.push_back(-0.4);
        inValues.push_back(0.0);
        inValues.push_back(0.4);
        // inValues.push_back(0.5); // undefined behaviour?
        inValues.push_back(0.6);
        inValues.push_back(1.0);
        inValues.push_back(999999.2);
        inValues.push_back(intMaxD * 2.0);
        inValues.push_back(intMaxD + 1.0);
        inValues.push_back(intMinD * 2.0);
        inValues.push_back(intMinD - 1.0);
    
        for (double value : inValues)
        {
            expectedFValues.push_back(static_cast<GLfloat>(value));
    
            double clampedValue = std::max(intMinD, std::min(intMaxD, value));
            double rounded = round(clampedValue);
            expectedIValues.push_back(static_cast<GLint>(rounded));
        }
    
        glUseProgram(mProgram);
        ASSERT_GL_NO_ERROR();
    
        for (size_t index = 0; index < inValues.size(); ++index)
        {
            GLfloat inValue       = static_cast<GLfloat>(inValues[index]);
            GLfloat expectedValue = expectedFValues[index];
    
            glUniform1f(mUniformFLocation, inValue);
            GLfloat testValue;
            glGetUniformfv(mProgram, mUniformFLocation, &testValue);
            ASSERT_GL_NO_ERROR();
            EXPECT_EQ(expectedValue, testValue);
        }
    
        for (size_t index = 0; index < inValues.size(); ++index)
        {
            GLfloat inValue     = static_cast<GLfloat>(inValues[index]);
            GLint expectedValue = expectedIValues[index];
    
            glUniform1f(mUniformFLocation, inValue);
            GLint testValue;
            glGetUniformiv(mProgram, mUniformFLocation, &testValue);
            ASSERT_GL_NO_ERROR();
            EXPECT_EQ(expectedValue, testValue);
        }
    }
    
    // Test that integer to float GetUniform rounds values correctly.
    TEST_P(UniformTest, IntUniformStateQuery)
    {
        std::vector<GLint> inValues;
        std::vector<GLint> expectedIValues;
        std::vector<GLfloat> expectedFValues;
    
        GLint intMax = std::numeric_limits<GLint>::max();
        GLint intMin = std::numeric_limits<GLint>::min();
    
        inValues.push_back(-1);
        inValues.push_back(0);
        inValues.push_back(1);
        inValues.push_back(999999);
        inValues.push_back(intMax);
        inValues.push_back(intMax - 1);
        inValues.push_back(intMin);
        inValues.push_back(intMin + 1);
    
        for (GLint value : inValues)
        {
            expectedIValues.push_back(value);
            expectedFValues.push_back(static_cast<GLfloat>(value));
        }
    
        glUseProgram(mProgram);
        ASSERT_GL_NO_ERROR();
    
        for (size_t index = 0; index < inValues.size(); ++index)
        {
            GLint inValue       = inValues[index];
            GLint expectedValue = expectedIValues[index];
    
            glUniform1i(mUniformILocation, inValue);
            GLint testValue;
            glGetUniformiv(mProgram, mUniformILocation, &testValue);
            ASSERT_GL_NO_ERROR();
            EXPECT_EQ(expectedValue, testValue);
        }
    
        for (size_t index = 0; index < inValues.size(); ++index)
        {
            GLint inValue         = inValues[index];
            GLfloat expectedValue = expectedFValues[index];
    
            glUniform1i(mUniformILocation, inValue);
            GLfloat testValue;
            glGetUniformfv(mProgram, mUniformILocation, &testValue);
            ASSERT_GL_NO_ERROR();
            EXPECT_EQ(expectedValue, testValue);
        }
    }
    
    // Test that queries of boolean uniforms round correctly.
    TEST_P(UniformTest, BooleanUniformStateQuery)
    {
        glUseProgram(mProgram);
        GLint intValue     = 0;
        GLfloat floatValue = 0.0f;
    
        // Calling Uniform1i
        glUniform1i(mUniformBLocation, GL_FALSE);
    
        glGetUniformiv(mProgram, mUniformBLocation, &intValue);
        EXPECT_EQ(0, intValue);
    
        glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
        EXPECT_EQ(0.0f, floatValue);
    
        glUniform1i(mUniformBLocation, GL_TRUE);
    
        glGetUniformiv(mProgram, mUniformBLocation, &intValue);
        EXPECT_EQ(1, intValue);
    
        glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
        EXPECT_EQ(1.0f, floatValue);
    
        // Calling Uniform1f
        glUniform1f(mUniformBLocation, 0.0f);
    
        glGetUniformiv(mProgram, mUniformBLocation, &intValue);
        EXPECT_EQ(0, intValue);
    
        glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
        EXPECT_EQ(0.0f, floatValue);
    
        glUniform1f(mUniformBLocation, 1.0f);
    
        glGetUniformiv(mProgram, mUniformBLocation, &intValue);
        EXPECT_EQ(1, intValue);
    
        glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
        EXPECT_EQ(1.0f, floatValue);
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Test queries for arrays of boolean uniforms.
    TEST_P(UniformTest, BooleanArrayUniformStateQuery)
    {
        glUseProgram(mProgram);
        GLint boolValuesi[4]   = {0, 1, 0, 1};
        GLfloat boolValuesf[4] = {0, 1, 0, 1};
    
        GLint locations[4] = {
            glGetUniformLocation(mProgram, "uniBArr"),
            glGetUniformLocation(mProgram, "uniBArr[1]"),
            glGetUniformLocation(mProgram, "uniBArr[2]"),
            glGetUniformLocation(mProgram, "uniBArr[3]"),
        };
    
        // Calling Uniform1iv
        glUniform1iv(locations[0], 4, boolValuesi);
    
        for (unsigned int idx = 0; idx < 4; ++idx)
        {
            int value = -1;
            glGetUniformiv(mProgram, locations[idx], &value);
            EXPECT_EQ(boolValuesi[idx], value);
        }
    
        for (unsigned int idx = 0; idx < 4; ++idx)
        {
            float value = -1.0f;
            glGetUniformfv(mProgram, locations[idx], &value);
            EXPECT_EQ(boolValuesf[idx], value);
        }
    
        // Calling Uniform1fv
        glUniform1fv(locations[0], 4, boolValuesf);
    
        for (unsigned int idx = 0; idx < 4; ++idx)
        {
            int value = -1;
            glGetUniformiv(mProgram, locations[idx], &value);
            EXPECT_EQ(boolValuesi[idx], value);
        }
    
        for (unsigned int idx = 0; idx < 4; ++idx)
        {
            float value = -1.0f;
            glGetUniformfv(mProgram, locations[idx], &value);
            EXPECT_EQ(boolValuesf[idx], value);
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    class UniformTestES3 : public ANGLETest
    {
      protected:
        UniformTestES3() : mProgram(0) {}
    
        void SetUp() override
        {
            ANGLETest::SetUp();
        }
    
        void TearDown() override
        {
            if (mProgram != 0)
            {
                glDeleteProgram(mProgram);
                mProgram = 0;
            }
        }
    
        GLuint mProgram;
    };
    
    // Test queries for transposed arrays of non-square matrix uniforms.
    TEST_P(UniformTestES3, TranposedMatrixArrayUniformStateQuery)
    {
        const std::string &vertexShader =
            "#version 300 es\n"
            "void main() { gl_Position = vec4(1); }";
        const std::string &fragShader =
            "#version 300 es\n"
            "precision mediump float;\n"
            "uniform mat3x2 uniMat3x2[5];\n"
            "out vec4 color;\n"
            "void main() {\n"
            "  color = vec4(uniMat3x2[0][0][0]);\n"
            "}";
    
        mProgram = CompileProgram(vertexShader, fragShader);
        ASSERT_NE(mProgram, 0u);
    
        glUseProgram(mProgram);
    
        std::vector<GLfloat> transposedValues;
    
        for (size_t arrayElement = 0; arrayElement < 5; ++arrayElement)
        {
            transposedValues.push_back(1.0f + arrayElement);
            transposedValues.push_back(3.0f + arrayElement);
            transposedValues.push_back(5.0f + arrayElement);
            transposedValues.push_back(2.0f + arrayElement);
            transposedValues.push_back(4.0f + arrayElement);
            transposedValues.push_back(6.0f + arrayElement);
        }
    
        // Setting as a clump
        GLint baseLocation = glGetUniformLocation(mProgram, "uniMat3x2");
        ASSERT_NE(-1, baseLocation);
    
        glUniformMatrix3x2fv(baseLocation, 5, GL_TRUE, &transposedValues[0]);
    
        for (size_t arrayElement = 0; arrayElement < 5; ++arrayElement)
        {
            std::stringstream nameStr;
            nameStr << "uniMat3x2[" << arrayElement << "]";
            std::string name = nameStr.str();
            GLint location = glGetUniformLocation(mProgram, name.c_str());
            ASSERT_NE(-1, location);
    
            std::vector<GLfloat> sequentialValues(6, 0);
            glGetUniformfv(mProgram, location, &sequentialValues[0]);
    
            ASSERT_GL_NO_ERROR();
    
            for (size_t comp = 0; comp < 6; ++comp)
            {
                EXPECT_EQ(static_cast<GLfloat>(comp + 1 + arrayElement), sequentialValues[comp]);
            }
        }
    }
    
    // Check that trying setting too many elements of an array doesn't overflow
    TEST_P(UniformTestES3, OverflowArray)
    {
        const std::string &vertexShader =
            "#version 300 es\n"
            "void main() { gl_Position = vec4(1); }";
        const std::string &fragShader =
            "#version 300 es\n"
            "precision mediump float;\n"
            "uniform float uniF[5];\n"
            "uniform mat3x2 uniMat3x2[5];\n"
            "out vec4 color;\n"
            "void main() {\n"
            "  color = vec4(uniMat3x2[0][0][0] + uniF[0]);\n"
            "}";
    
        mProgram = CompileProgram(vertexShader, fragShader);
        ASSERT_NE(mProgram, 0u);
    
        glUseProgram(mProgram);
    
        const size_t kOverflowSize = 10000;
        std::vector<GLfloat> values(10000 * 6);
    
        // Setting as a clump
        GLint floatLocation = glGetUniformLocation(mProgram, "uniF");
        ASSERT_NE(-1, floatLocation);
        GLint matLocation = glGetUniformLocation(mProgram, "uniMat3x2");
        ASSERT_NE(-1, matLocation);
    
        // Set too many float uniforms
        glUniform1fv(floatLocation, kOverflowSize, &values[0]);
    
        // Set too many matrix uniforms, transposed or not
        glUniformMatrix3x2fv(matLocation, kOverflowSize, GL_FALSE, &values[0]);
        glUniformMatrix3x2fv(matLocation, kOverflowSize, GL_TRUE, &values[0]);
    
        // Same checks but with offsets
        GLint floatLocationOffset = glGetUniformLocation(mProgram, "uniF[3]");
        ASSERT_NE(-1, floatLocationOffset);
        GLint matLocationOffset = glGetUniformLocation(mProgram, "uniMat3x2[3]");
        ASSERT_NE(-1, matLocationOffset);
    
        glUniform1fv(floatLocationOffset, kOverflowSize, &values[0]);
        glUniformMatrix3x2fv(matLocationOffset, kOverflowSize, GL_FALSE, &values[0]);
        glUniformMatrix3x2fv(matLocationOffset, kOverflowSize, GL_TRUE, &values[0]);
    }
    
    // Check that sampler uniforms only show up one time in the list
    TEST_P(UniformTest, SamplerUniformsAppearOnce)
    {
        int maxVertexTextureImageUnits = 0;
        glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits);
    
        if (maxVertexTextureImageUnits == 0)
        {
            std::cout << "Renderer doesn't support vertex texture fetch, skipping test" << std::endl;
            return;
        }
    
        const std::string &vertShader =
            "attribute vec2 position;\n"
            "uniform sampler2D tex2D;\n"
            "varying vec4 color;\n"
            "void main() {\n"
            "  gl_Position = vec4(position, 0, 1);\n"
            "  color = texture2D(tex2D, vec2(0));\n"
            "}";
    
        const std::string &fragShader =
            "precision mediump float;\n"
            "varying vec4 color;\n"
            "uniform sampler2D tex2D;\n"
            "void main() {\n"
            "  gl_FragColor = texture2D(tex2D, vec2(0)) + color;\n"
            "}";
    
        GLuint program = CompileProgram(vertShader, fragShader);
        ASSERT_NE(0u, program);
    
        GLint activeUniformsCount = 0;
        glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniformsCount);
        ASSERT_EQ(1, activeUniformsCount);
    
        GLint size       = 0;
        GLenum type      = GL_NONE;
        GLchar name[120] = {0};
        glGetActiveUniform(program, 0, 100, nullptr, &size, &type, name);
        EXPECT_EQ(1, size);
        EXPECT_GLENUM_EQ(GL_SAMPLER_2D, type);
        EXPECT_STREQ("tex2D", name);
    
        EXPECT_GL_NO_ERROR();
    
        glDeleteProgram(program);
    }
    
    template <typename T, typename GetUniformV>
    void CheckOneElement(GetUniformV getUniformv,
                         GLuint program,
                         const std::string &name,
                         int components,
                         T canary)
    {
        // The buffer getting the results has three chunks
        //  - A chunk to see underflows
        //  - A chunk that will hold the result
        //  - A chunk to see overflows for when components = kChunkSize
        static const size_t kChunkSize = 4;
        std::array<T, 3 * kChunkSize> buffer;
        buffer.fill(canary);
    
        GLint location = glGetUniformLocation(program, name.c_str());
        ASSERT_NE(location, -1);
    
        getUniformv(program, location, &buffer[kChunkSize]);
        for (size_t i = 0; i < kChunkSize; i++)
        {
            ASSERT_EQ(canary, buffer[i]);
        }
        for (size_t i = kChunkSize + components; i < buffer.size(); i++)
        {
            ASSERT_EQ(canary, buffer[i]);
        }
    }
    
    // Check that getting an element array doesn't return the whole array.
    TEST_P(UniformTestES3, ReturnsOnlyOneArrayElement)
    {
        static const size_t kArraySize = 4;
        struct UniformArrayInfo
        {
            UniformArrayInfo(std::string type, std::string name, int components)
                : type(type), name(name), components(components)
            {
            }
            std::string type;
            std::string name;
            int components;
        };
    
        // Check for various number of components and types
        std::vector<UniformArrayInfo> uniformArrays;
        uniformArrays.emplace_back("bool", "uBool", 1);
        uniformArrays.emplace_back("vec2", "uFloat", 2);
        uniformArrays.emplace_back("ivec3", "uInt", 3);
        uniformArrays.emplace_back("uvec4", "uUint", 4);
    
        std::ostringstream uniformStream;
        std::ostringstream additionStream;
        for (const auto &array : uniformArrays)
        {
            uniformStream << "uniform " << array.type << " " << array.name << "["
                          << ToString(kArraySize) << "];\n";
    
            // We need to make use of the uniforms or they get compiled out.
            for (int i = 0; i < 4; i++)
            {
                if (array.components == 1)
                {
                    additionStream << " + float(" << array.name << "[" << i << "])";
                }
                else
                {
                    for (int component = 0; component < array.components; component++)
                    {
                        additionStream << " + float(" << array.name << "[" << i << "][" << component
                                       << "])";
                    }
                }
            }
        }
    
        const std::string &vertexShader =
            "#version 300 es\n" +
            uniformStream.str() +
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(1.0" + additionStream.str() + ");\n"
            "}";
    
        const std::string &fragmentShader =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 color;\n"
            "void main ()\n"
            "{\n"
            "    color = vec4(1, 0, 0, 1);\n"
            "}";
    
        mProgram = CompileProgram(vertexShader, fragmentShader);
        ASSERT_NE(0u, mProgram);
    
        glUseProgram(mProgram);
    
        for (const auto &uniformArray : uniformArrays)
        {
            for (size_t index = 0; index < kArraySize; index++)
            {
                std::string strIndex = "[" + ToString(index) + "]";
                // Check all the different glGetUniformv functions
                CheckOneElement<float>(glGetUniformfv, mProgram, uniformArray.name + strIndex,
                                       uniformArray.components, 42.4242f);
                CheckOneElement<int>(glGetUniformiv, mProgram, uniformArray.name + strIndex,
                                     uniformArray.components, 0x7BADBED5);
                CheckOneElement<unsigned int>(glGetUniformuiv, mProgram, uniformArray.name + strIndex,
                                              uniformArray.components, 0xDEADBEEF);
            }
        }
    }
    
    // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
    ANGLE_INSTANTIATE_TEST(UniformTest,
                           ES2_D3D9(),
                           ES2_D3D11(),
                           ES2_D3D11_FL9_3(),
                           ES2_OPENGL(),
                           ES2_OPENGLES());
    ANGLE_INSTANTIATE_TEST(UniformTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
    
    } // namespace