Edit

kc3-lang/angle/src/tests/compiler_tests/CollectVariables_test.cpp

Branch :

  • Show log

    Commit

  • Author : Stuart Morgan
    Date : 2019-08-14 12:25:12
    Hash : 9d737966
    Message : Standardize copyright notices to project style For all "ANGLE Project" copyrights, standardize to the format specified by the style guide. Changes: - "Copyright (c)" and "Copyright(c)" changed to just "Copyright". - Removed the second half of date ranges ("Y1Y1-Y2Y2"->"Y1Y1"). - Fixed a small number of files that had no copyright date using the initial commit year from the version control history. - Fixed one instance of copyright being "The ANGLE Project" rather than "The ANGLE Project Authors" These changes are applied both to the copyright of source file, and where applicable to copyright statements that are generated by templates. BUG=angleproject:3811 Change-Id: I973dd65e4ef9deeba232d5be74c768256a0eb2e5 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1754397 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org>

  • src/tests/compiler_tests/CollectVariables_test.cpp
  • //
    // Copyright 2014 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.
    //
    // CollectVariables_test.cpp:
    //   Some tests for shader inspection
    //
    
    #include <memory>
    
    #include "GLSLANG/ShaderLang.h"
    #include "angle_gl.h"
    #include "compiler/translator/TranslatorGLSL.h"
    #include "gtest/gtest.h"
    
    using namespace sh;
    
    #define EXPECT_GLENUM_EQ(expected, actual) \
        EXPECT_EQ(static_cast<::GLenum>(expected), static_cast<::GLenum>(actual))
    
    namespace
    {
    
    std::string DecorateName(const char *name)
    {
        return std::string("_u") + name;
    }
    
    }  // anonymous namespace
    
    class CollectVariablesTest : public testing::Test
    {
      public:
        CollectVariablesTest(::GLenum shaderType) : mShaderType(shaderType) {}
    
      protected:
        void SetUp() override
        {
            ShBuiltInResources resources;
            InitBuiltInResources(&resources);
            resources.MaxDrawBuffers           = 8;
            resources.EXT_blend_func_extended  = true;
            resources.MaxDualSourceDrawBuffers = 1;
    
            initTranslator(resources);
        }
    
        virtual void initTranslator(const ShBuiltInResources &resources)
        {
            mTranslator.reset(
                new TranslatorGLSL(mShaderType, SH_GLES3_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
            ASSERT_TRUE(mTranslator->Init(resources));
        }
    
        // For use in the gl_DepthRange tests.
        void validateDepthRangeShader(const std::string &shaderString)
        {
            const char *shaderStrings[] = {shaderString.c_str()};
            ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));
    
            const std::vector<Uniform> &uniforms = mTranslator->getUniforms();
            ASSERT_EQ(1u, uniforms.size());
    
            const Uniform &uniform = uniforms[0];
            EXPECT_EQ("gl_DepthRange", uniform.name);
            ASSERT_TRUE(uniform.isStruct());
            ASSERT_EQ(3u, uniform.fields.size());
    
            bool foundNear = false;
            bool foundFar  = false;
            bool foundDiff = false;
    
            for (const auto &field : uniform.fields)
            {
                if (field.name == "near")
                {
                    EXPECT_FALSE(foundNear);
                    foundNear = true;
                }
                else if (field.name == "far")
                {
                    EXPECT_FALSE(foundFar);
                    foundFar = true;
                }
                else
                {
                    ASSERT_EQ("diff", field.name);
                    EXPECT_FALSE(foundDiff);
                    foundDiff = true;
                }
    
                EXPECT_FALSE(field.isArray());
                EXPECT_FALSE(field.isStruct());
                EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
                EXPECT_TRUE(field.staticUse);
                EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
            }
    
            EXPECT_TRUE(foundNear && foundFar && foundDiff);
        }
    
        // For use in tests for output varibles.
        void validateOutputVariableForShader(const std::string &shaderString,
                                             unsigned int varIndex,
                                             const char *varName,
                                             const OutputVariable **outResult)
        {
            const char *shaderStrings[] = {shaderString.c_str()};
            ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES))
                << mTranslator->getInfoSink().info.str();
    
            const auto &outputVariables = mTranslator->getOutputVariables();
            ASSERT_LT(varIndex, outputVariables.size());
            const OutputVariable &outputVariable = outputVariables[varIndex];
            EXPECT_EQ(-1, outputVariable.location);
            EXPECT_TRUE(outputVariable.staticUse);
            EXPECT_TRUE(outputVariable.active);
            EXPECT_EQ(varName, outputVariable.name);
            *outResult = &outputVariable;
        }
    
        void compile(const std::string &shaderString, ShCompileOptions compileOptions)
        {
            const char *shaderStrings[] = {shaderString.c_str()};
            ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES | compileOptions));
        }
    
        void compile(const std::string &shaderString) { compile(shaderString, 0u); }
    
        void checkUniformStaticallyUsedButNotActive(const char *name)
        {
            const auto &uniforms = mTranslator->getUniforms();
            ASSERT_EQ(1u, uniforms.size());
    
            const Uniform &uniform = uniforms[0];
            EXPECT_EQ(name, uniform.name);
            EXPECT_TRUE(uniform.staticUse);
            EXPECT_FALSE(uniform.active);
        }
    
        ::GLenum mShaderType;
        std::unique_ptr<TranslatorGLSL> mTranslator;
    };
    
    class CollectVertexVariablesTest : public CollectVariablesTest
    {
      public:
        CollectVertexVariablesTest() : CollectVariablesTest(GL_VERTEX_SHADER) {}
    };
    
    class CollectFragmentVariablesTest : public CollectVariablesTest
    {
      public:
        CollectFragmentVariablesTest() : CollectVariablesTest(GL_FRAGMENT_SHADER) {}
    };
    
    class CollectVariablesTestES31 : public CollectVariablesTest
    {
      public:
        CollectVariablesTestES31(sh::GLenum shaderType) : CollectVariablesTest(shaderType) {}
    
      protected:
        void initTranslator(const ShBuiltInResources &resources) override
        {
            mTranslator.reset(
                new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
            ASSERT_TRUE(mTranslator->Init(resources));
        }
    };
    
    class CollectVariablesEXTGeometryShaderTest : public CollectVariablesTestES31
    {
      public:
        CollectVariablesEXTGeometryShaderTest(sh::GLenum shaderType)
            : CollectVariablesTestES31(shaderType)
        {}
    
      protected:
        void SetUp() override
        {
            ShBuiltInResources resources;
            InitBuiltInResources(&resources);
            resources.EXT_geometry_shader = 1;
    
            initTranslator(resources);
        }
    };
    
    class CollectGeometryVariablesTest : public CollectVariablesEXTGeometryShaderTest
    {
      public:
        CollectGeometryVariablesTest() : CollectVariablesEXTGeometryShaderTest(GL_GEOMETRY_SHADER_EXT)
        {}
    
      protected:
        void compileGeometryShaderWithInputPrimitive(const std::string &inputPrimitive,
                                                     const std::string &inputVarying,
                                                     const std::string &functionBody)
        {
            std::ostringstream sstream;
            sstream << "#version 310 es\n"
                    << "#extension GL_EXT_geometry_shader : require\n"
                    << "layout (" << inputPrimitive << ") in;\n"
                    << "layout (points, max_vertices = 2) out;\n"
                    << inputVarying << functionBody;
    
            compile(sstream.str());
        }
    };
    
    class CollectFragmentVariablesEXTGeometryShaderTest : public CollectVariablesEXTGeometryShaderTest
    {
      public:
        CollectFragmentVariablesEXTGeometryShaderTest()
            : CollectVariablesEXTGeometryShaderTest(GL_FRAGMENT_SHADER)
        {}
    
      protected:
        void initTranslator(const ShBuiltInResources &resources)
        {
            mTranslator.reset(
                new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
            ASSERT_TRUE(mTranslator->Init(resources));
        }
    };
    
    class CollectVertexVariablesES31Test : public CollectVariablesTestES31
    {
      public:
        CollectVertexVariablesES31Test() : CollectVariablesTestES31(GL_VERTEX_SHADER) {}
    };
    
    class CollectFragmentVariablesES31Test : public CollectVariablesTestES31
    {
      public:
        CollectFragmentVariablesES31Test() : CollectVariablesTestES31(GL_FRAGMENT_SHADER) {}
    };
    
    TEST_F(CollectFragmentVariablesTest, SimpleOutputVar)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 out_fragColor;\n"
            "void main() {\n"
            "   out_fragColor = vec4(1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const auto &outputVariables = mTranslator->getOutputVariables();
        ASSERT_EQ(1u, outputVariables.size());
    
        const OutputVariable &outputVariable = outputVariables[0];
    
        EXPECT_FALSE(outputVariable.isArray());
        EXPECT_EQ(-1, outputVariable.location);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
        EXPECT_TRUE(outputVariable.staticUse);
        EXPECT_TRUE(outputVariable.active);
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable.type);
        EXPECT_EQ("out_fragColor", outputVariable.name);
    }
    
    TEST_F(CollectFragmentVariablesTest, LocationOutputVar)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "layout(location=5) out vec4 out_fragColor;\n"
            "void main() {\n"
            "   out_fragColor = vec4(1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const auto &outputVariables = mTranslator->getOutputVariables();
        ASSERT_EQ(1u, outputVariables.size());
    
        const OutputVariable &outputVariable = outputVariables[0];
    
        EXPECT_FALSE(outputVariable.isArray());
        EXPECT_EQ(5, outputVariable.location);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
        EXPECT_TRUE(outputVariable.staticUse);
        EXPECT_TRUE(outputVariable.active);
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable.type);
        EXPECT_EQ("out_fragColor", outputVariable.name);
    }
    
    TEST_F(CollectVertexVariablesTest, LocationAttribute)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "layout(location=5) in vec4 in_Position;\n"
            "void main() {\n"
            "   gl_Position = in_Position;\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<Attribute> &attributes = mTranslator->getAttributes();
        ASSERT_EQ(1u, attributes.size());
    
        const Attribute &attribute = attributes[0];
    
        EXPECT_FALSE(attribute.isArray());
        EXPECT_EQ(5, attribute.location);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, attribute.precision);
        EXPECT_TRUE(attribute.staticUse);
        EXPECT_TRUE(attribute.active);
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, attribute.type);
        EXPECT_EQ("in_Position", attribute.name);
    }
    
    TEST_F(CollectVertexVariablesTest, SimpleInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "uniform b {\n"
            "  float f;\n"
            "};"
            "void main() {\n"
            "   gl_Position = vec4(f, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &field = interfaceBlock.fields[0];
    
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
        EXPECT_TRUE(field.staticUse);
        EXPECT_TRUE(field.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
        EXPECT_EQ("f", field.name);
        EXPECT_FALSE(field.isRowMajorLayout);
        EXPECT_TRUE(field.fields.empty());
    }
    
    TEST_F(CollectVertexVariablesTest, SimpleInstancedInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "uniform b {\n"
            "  float f;\n"
            "} blockInstance;"
            "void main() {\n"
            "   gl_Position = vec4(blockInstance.f, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_EQ("blockInstance", interfaceBlock.instanceName);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &field = interfaceBlock.fields[0];
    
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
        EXPECT_TRUE(field.staticUse);
        EXPECT_TRUE(field.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
        EXPECT_EQ("f", field.name);
        EXPECT_FALSE(field.isRowMajorLayout);
        EXPECT_TRUE(field.fields.empty());
    }
    
    TEST_F(CollectVertexVariablesTest, StructInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "struct st { float f; };"
            "uniform b {\n"
            "  st s;\n"
            "};"
            "void main() {\n"
            "   gl_Position = vec4(s.f, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &blockField = interfaceBlock.fields[0];
    
        EXPECT_TRUE(blockField.isStruct());
        EXPECT_TRUE(blockField.staticUse);
        EXPECT_TRUE(blockField.active);
        EXPECT_EQ("s", blockField.name);
        EXPECT_EQ(DecorateName("s"), blockField.mappedName);
        EXPECT_FALSE(blockField.isRowMajorLayout);
    
        const ShaderVariable &structField = blockField.fields[0];
    
        // NOTE: we don't track static use or active at individual struct member granularity.
        EXPECT_FALSE(structField.isStruct());
        EXPECT_EQ("f", structField.name);
        EXPECT_EQ(DecorateName("f"), structField.mappedName);
        EXPECT_GLENUM_EQ(GL_FLOAT, structField.type);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
    }
    
    TEST_F(CollectVertexVariablesTest, StructInstancedInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "struct st { float f; };"
            "uniform b {\n"
            "  st s;\n"
            "} instanceName;"
            "void main() {\n"
            "   gl_Position = vec4(instanceName.s.f, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
        EXPECT_EQ("instanceName", interfaceBlock.instanceName);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &blockField = interfaceBlock.fields[0];
    
        EXPECT_TRUE(blockField.isStruct());
        EXPECT_TRUE(blockField.staticUse);
        EXPECT_TRUE(blockField.active);
        EXPECT_EQ("s", blockField.name);
        EXPECT_EQ(DecorateName("s"), blockField.mappedName);
        EXPECT_FALSE(blockField.isRowMajorLayout);
    
        const ShaderVariable &structField = blockField.fields[0];
    
        // NOTE: we don't track static use or active at individual struct member granularity.
        EXPECT_FALSE(structField.isStruct());
        EXPECT_EQ("f", structField.name);
        EXPECT_EQ(DecorateName("f"), structField.mappedName);
        EXPECT_GLENUM_EQ(GL_FLOAT, structField.type);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
    }
    
    TEST_F(CollectVertexVariablesTest, NestedStructRowMajorInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "struct st { mat2 m; };"
            "layout(row_major) uniform b {\n"
            "  st s;\n"
            "};"
            "void main() {\n"
            "   gl_Position = vec4(s.m);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_EQ(DecorateName("b"), interfaceBlock.mappedName);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &blockField = interfaceBlock.fields[0];
    
        EXPECT_TRUE(blockField.isStruct());
        EXPECT_TRUE(blockField.staticUse);
        EXPECT_TRUE(blockField.active);
        EXPECT_EQ("s", blockField.name);
        EXPECT_EQ(DecorateName("s"), blockField.mappedName);
        EXPECT_TRUE(blockField.isRowMajorLayout);
    
        const ShaderVariable &structField = blockField.fields[0];
    
        // NOTE: we don't track static use or active at individual struct member granularity.
        EXPECT_FALSE(structField.isStruct());
        EXPECT_EQ("m", structField.name);
        EXPECT_EQ(DecorateName("m"), structField.mappedName);
        EXPECT_GLENUM_EQ(GL_FLOAT_MAT2, structField.type);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, structField.precision);
    }
    
    TEST_F(CollectVertexVariablesTest, VaryingInterpolation)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "centroid out float vary;\n"
            "void main() {\n"
            "   gl_Position = vec4(1.0);\n"
            "   vary = 1.0;\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<Varying> &varyings = mTranslator->getOutputVaryings();
        ASSERT_EQ(2u, varyings.size());
    
        const Varying *varying = &varyings[0];
    
        if (varying->name == "gl_Position")
        {
            varying = &varyings[1];
        }
    
        EXPECT_FALSE(varying->isArray());
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, varying->precision);
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_GLENUM_EQ(GL_FLOAT, varying->type);
        EXPECT_EQ("vary", varying->name);
        EXPECT_EQ(DecorateName("vary"), varying->mappedName);
        EXPECT_EQ(INTERPOLATION_CENTROID, varying->interpolation);
    }
    
    // Test for builtin uniform "gl_DepthRange" (Vertex shader)
    TEST_F(CollectVertexVariablesTest, DepthRange)
    {
        const std::string &shaderString =
            "attribute vec4 position;\n"
            "void main() {\n"
            "   gl_Position = position + vec4(gl_DepthRange.near, gl_DepthRange.far, "
            "gl_DepthRange.diff, 1.0);\n"
            "}\n";
    
        validateDepthRangeShader(shaderString);
    }
    
    // Test for builtin uniform "gl_DepthRange" (Fragment shader)
    TEST_F(CollectFragmentVariablesTest, DepthRange)
    {
        const std::string &shaderString =
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragColor = vec4(gl_DepthRange.near, gl_DepthRange.far, gl_DepthRange.diff, 1.0);\n"
            "}\n";
    
        validateDepthRangeShader(shaderString);
    }
    
    // Test that gl_FragColor built-in usage in ESSL1 fragment shader is reflected in the output
    // variables list.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1FragColor)
    {
        const std::string &fragColorShader =
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragColor = vec4(1.0);\n"
            "}\n";
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(fragColorShader, 0u, "gl_FragColor", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_FragData built-in usage in ESSL1 fragment shader is reflected in the output
    // variables list.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1FragData)
    {
        const std::string &fragDataShader =
            "#extension GL_EXT_draw_buffers : require\n"
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragData[0] = vec4(1.0);\n"
            "   gl_FragData[1] = vec4(0.5);\n"
            "}\n";
    
        ShBuiltInResources resources       = mTranslator->getResources();
        resources.EXT_draw_buffers         = 1;
        const unsigned int kMaxDrawBuffers = 3u;
        resources.MaxDrawBuffers           = kMaxDrawBuffers;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(fragDataShader, 0u, "gl_FragData", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        ASSERT_EQ(1u, outputVariable->arraySizes.size());
        EXPECT_EQ(kMaxDrawBuffers, outputVariable->arraySizes.back());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_FragDataEXT built-in usage in ESSL1 fragment shader is reflected in the output
    // variables list. Also test that the precision is mediump.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1FragDepthMediump)
    {
        const std::string &fragDepthShader =
            "#extension GL_EXT_frag_depth : require\n"
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragDepthEXT = 0.7;"
            "}\n";
    
        ShBuiltInResources resources = mTranslator->getResources();
        resources.EXT_frag_depth     = 1;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(fragDepthShader, 0u, "gl_FragDepthEXT", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_FragDataEXT built-in usage in ESSL1 fragment shader is reflected in the output
    // variables list. Also test that the precision is highp if user requests it.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1FragDepthHighp)
    {
        const std::string &fragDepthHighShader =
            "#extension GL_EXT_frag_depth : require\n"
            "void main() {\n"
            "   gl_FragDepthEXT = 0.7;"
            "}\n";
    
        ShBuiltInResources resources    = mTranslator->getResources();
        resources.EXT_frag_depth        = 1;
        resources.FragmentPrecisionHigh = 1;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(fragDepthHighShader, 0u, "gl_FragDepthEXT", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_FragData built-in usage in ESSL3 fragment shader is reflected in the output
    // variables list. Also test that the precision is highp.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL3FragDepthHighp)
    {
        const std::string &fragDepthHighShader =
            "#version 300 es\n"
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragDepth = 0.7;"
            "}\n";
    
        ShBuiltInResources resources = mTranslator->getResources();
        resources.EXT_frag_depth     = 1;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(fragDepthHighShader, 0u, "gl_FragDepth", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_SecondaryFragColorEXT built-in usage in ESSL1 fragment shader is reflected in the
    // output variables list.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1EXTBlendFuncExtendedSecondaryFragColor)
    {
        const char *secondaryFragColorShader =
            "#extension GL_EXT_blend_func_extended : require\n"
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragColor = vec4(1.0);\n"
            "   gl_SecondaryFragColorEXT = vec4(1.0);\n"
            "}\n";
    
        const unsigned int kMaxDrawBuffers = 3u;
        ShBuiltInResources resources       = mTranslator->getResources();
        resources.EXT_blend_func_extended  = 1;
        resources.EXT_draw_buffers         = 1;
        resources.MaxDrawBuffers           = kMaxDrawBuffers;
        resources.MaxDualSourceDrawBuffers = resources.MaxDrawBuffers;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(secondaryFragColorShader, 0u, "gl_FragColor", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    
        outputVariable = nullptr;
        validateOutputVariableForShader(secondaryFragColorShader, 1u, "gl_SecondaryFragColorEXT",
                                        &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        EXPECT_FALSE(outputVariable->isArray());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    }
    
    // Test that gl_SecondaryFragDataEXT built-in usage in ESSL1 fragment shader is reflected in the
    // output variables list.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL1EXTBlendFuncExtendedSecondaryFragData)
    {
        const char *secondaryFragDataShader =
            "#extension GL_EXT_blend_func_extended : require\n"
            "#extension GL_EXT_draw_buffers : require\n"
            "precision mediump float;\n"
            "void main() {\n"
            "   gl_FragData[0] = vec4(1.0);\n"
            "   gl_FragData[1] = vec4(0.5);\n"
            "   gl_SecondaryFragDataEXT[0] = vec4(1.0);\n"
            "   gl_SecondaryFragDataEXT[1] = vec4(0.8);\n"
            "}\n";
        const unsigned int kMaxDrawBuffers = 3u;
        ShBuiltInResources resources       = mTranslator->getResources();
        resources.EXT_blend_func_extended  = 1;
        resources.EXT_draw_buffers         = 1;
        resources.MaxDrawBuffers           = kMaxDrawBuffers;
        resources.MaxDualSourceDrawBuffers = resources.MaxDrawBuffers;
        initTranslator(resources);
    
        const OutputVariable *outputVariable = nullptr;
        validateOutputVariableForShader(secondaryFragDataShader, 0u, "gl_FragData", &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        ASSERT_EQ(1u, outputVariable->arraySizes.size());
        EXPECT_EQ(kMaxDrawBuffers, outputVariable->arraySizes.back());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    
        outputVariable = nullptr;
        validateOutputVariableForShader(secondaryFragDataShader, 1u, "gl_SecondaryFragDataEXT",
                                        &outputVariable);
        ASSERT_NE(outputVariable, nullptr);
        ASSERT_EQ(1u, outputVariable->arraySizes.size());
        EXPECT_EQ(kMaxDrawBuffers, outputVariable->arraySizes.back());
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, outputVariable->type);
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable->precision);
    }
    
    static khronos_uint64_t SimpleTestHash(const char *str, size_t len)
    {
        return static_cast<uint64_t>(len);
    }
    
    class CollectHashedVertexVariablesTest : public CollectVertexVariablesTest
    {
      protected:
        void SetUp() override
        {
            // Initialize the translate with a hash function
            ShBuiltInResources resources;
            sh::InitBuiltInResources(&resources);
            resources.HashFunction = SimpleTestHash;
            initTranslator(resources);
        }
    };
    
    TEST_F(CollectHashedVertexVariablesTest, InstancedInterfaceBlock)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "uniform blockName {\n"
            "  float field;\n"
            "} blockInstance;"
            "void main() {\n"
            "   gl_Position = vec4(blockInstance.field, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
    
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ(0u, interfaceBlock.arraySize);
        EXPECT_EQ(BLOCKLAYOUT_SHARED, interfaceBlock.layout);
        EXPECT_EQ("blockName", interfaceBlock.name);
        EXPECT_EQ("blockInstance", interfaceBlock.instanceName);
        EXPECT_EQ("webgl_9", interfaceBlock.mappedName);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_TRUE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
    
        const InterfaceBlockField &field = interfaceBlock.fields[0];
    
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
        EXPECT_TRUE(field.staticUse);
        EXPECT_TRUE(field.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
        EXPECT_EQ("field", field.name);
        EXPECT_EQ("webgl_5", field.mappedName);
        EXPECT_FALSE(field.isRowMajorLayout);
        EXPECT_TRUE(field.fields.empty());
    }
    
    // Test a struct uniform where the struct does have a name.
    TEST_F(CollectHashedVertexVariablesTest, StructUniform)
    {
        const std::string &shaderString =
            R"(#version 300 es
            struct sType
            {
                float field;
            };
            uniform sType u;
    
            void main()
            {
                gl_Position = vec4(u.field, 0.0, 0.0, 1.0);
            })";
    
        compile(shaderString);
    
        const auto &uniforms = mTranslator->getUniforms();
        ASSERT_EQ(1u, uniforms.size());
    
        const Uniform &uniform = uniforms[0];
    
        EXPECT_FALSE(uniform.isArray());
        EXPECT_EQ("u", uniform.name);
        EXPECT_EQ("webgl_1", uniform.mappedName);
        EXPECT_EQ("sType", uniform.structName);
        EXPECT_TRUE(uniform.staticUse);
        EXPECT_TRUE(uniform.active);
    
        ASSERT_EQ(1u, uniform.fields.size());
    
        const ShaderVariable &field = uniform.fields[0];
    
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
        // We don't yet support tracking static use per field, but fields are marked statically used in
        // case the struct is.
        EXPECT_TRUE(field.staticUse);
        EXPECT_TRUE(field.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
        EXPECT_EQ("field", field.name);
        EXPECT_EQ("webgl_5", field.mappedName);
        EXPECT_TRUE(field.fields.empty());
    }
    
    // Test a struct uniform where the struct doesn't have a name.
    TEST_F(CollectHashedVertexVariablesTest, NamelessStructUniform)
    {
        const std::string &shaderString =
            R"(#version 300 es
            uniform struct
            {
                float field;
            } u;
    
            void main()
            {
                gl_Position = vec4(u.field, 0.0, 0.0, 1.0);
            })";
    
        compile(shaderString);
    
        const auto &uniforms = mTranslator->getUniforms();
        ASSERT_EQ(1u, uniforms.size());
    
        const Uniform &uniform = uniforms[0];
    
        EXPECT_FALSE(uniform.isArray());
        EXPECT_EQ("u", uniform.name);
        EXPECT_EQ("webgl_1", uniform.mappedName);
        EXPECT_EQ("", uniform.structName);
        EXPECT_TRUE(uniform.staticUse);
        EXPECT_TRUE(uniform.active);
    
        ASSERT_EQ(1u, uniform.fields.size());
    
        const ShaderVariable &field = uniform.fields[0];
    
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
        // We don't yet support tracking static use per field, but fields are marked statically used in
        // case the struct is.
        EXPECT_TRUE(field.staticUse);
        EXPECT_TRUE(field.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
        EXPECT_EQ("field", field.name);
        EXPECT_EQ("webgl_5", field.mappedName);
        EXPECT_TRUE(field.fields.empty());
    }
    
    // Test a uniform declaration with multiple declarators.
    TEST_F(CollectFragmentVariablesTest, MultiDeclaration)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 out_fragColor;\n"
            "uniform float uA, uB;\n"
            "void main()\n"
            "{\n"
            "    vec4 color = vec4(uA, uA, uA, uB);\n"
            "    out_fragColor = color;\n"
            "}\n";
    
        compile(shaderString);
    
        const auto &uniforms = mTranslator->getUniforms();
        ASSERT_EQ(2u, uniforms.size());
    
        const Uniform &uniform = uniforms[0];
        EXPECT_FALSE(uniform.isArray());
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniform.precision);
        EXPECT_TRUE(uniform.staticUse);
        EXPECT_TRUE(uniform.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, uniform.type);
        EXPECT_EQ("uA", uniform.name);
    
        const Uniform &uniformB = uniforms[1];
        EXPECT_FALSE(uniformB.isArray());
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniformB.precision);
        EXPECT_TRUE(uniformB.staticUse);
        EXPECT_TRUE(uniformB.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, uniformB.type);
        EXPECT_EQ("uB", uniformB.name);
    }
    
    // Test a uniform declaration starting with an empty declarator.
    TEST_F(CollectFragmentVariablesTest, EmptyDeclarator)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 out_fragColor;\n"
            "uniform float /* empty declarator */, uB;\n"
            "void main()\n"
            "{\n"
            "    out_fragColor = vec4(uB, uB, uB, uB);\n"
            "}\n";
    
        compile(shaderString);
    
        const auto &uniforms = mTranslator->getUniforms();
        ASSERT_EQ(1u, uniforms.size());
    
        const Uniform &uniformB = uniforms[0];
        EXPECT_FALSE(uniformB.isArray());
        EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, uniformB.precision);
        EXPECT_TRUE(uniformB.staticUse);
        EXPECT_TRUE(uniformB.active);
        EXPECT_GLENUM_EQ(GL_FLOAT, uniformB.type);
        EXPECT_EQ("uB", uniformB.name);
    }
    
    // Test collecting variables from an instanced multiview shader that has an internal ViewID_OVR
    // varying.
    TEST_F(CollectVertexVariablesTest, ViewID_OVR)
    {
        const std::string &shaderString =
            "#version 300 es\n"
            "#extension GL_OVR_multiview2 : require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(0.0);\n"
            "}\n";
    
        ShBuiltInResources resources = mTranslator->getResources();
        resources.OVR_multiview2     = 1;
        resources.MaxViewsOVR        = 4;
        initTranslator(resources);
    
        compile(shaderString, SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW |
                                  SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER);
    
        // The internal ViewID_OVR varying is not exposed through the ShaderVars interface.
        const auto &varyings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, varyings.size());
        const Varying *varying = &varyings[0];
        EXPECT_EQ("gl_Position", varying->name);
    }
    
    // Test all the fields of gl_in can be collected correctly in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectGLInFields)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
    
            layout (points) in;
            layout (points, max_vertices = 2) out;
    
            void main()
            {
                vec4 value = gl_in[0].gl_Position;
                vec4 value2 = gl_in[0].gl_Position;
                gl_Position = value + value2;
                EmitVertex();
            })";
    
        compile(shaderString);
    
        EXPECT_EQ(1u, mTranslator->getOutputVaryings().size());
        EXPECT_TRUE(mTranslator->getInputVaryings().empty());
    
        const auto &inBlocks = mTranslator->getInBlocks();
        ASSERT_EQ(1u, inBlocks.size());
    
        const InterfaceBlock *inBlock = &inBlocks[0];
        EXPECT_EQ("gl_PerVertex", inBlock->name);
        EXPECT_EQ("gl_in", inBlock->instanceName);
        EXPECT_TRUE(inBlock->staticUse);
        EXPECT_TRUE(inBlock->active);
        EXPECT_TRUE(inBlock->isBuiltIn());
    
        ASSERT_EQ(1u, inBlock->fields.size());
    
        const ShaderVariable &glPositionField = inBlock->fields[0];
        EXPECT_EQ("gl_Position", glPositionField.name);
        EXPECT_FALSE(glPositionField.isArray());
        EXPECT_FALSE(glPositionField.isStruct());
        EXPECT_TRUE(glPositionField.staticUse);
        EXPECT_TRUE(glPositionField.active);
        EXPECT_TRUE(glPositionField.isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, glPositionField.precision);
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, glPositionField.type);
    }
    
    // Test the collected array size of gl_in matches the input primitive declaration.
    TEST_F(CollectGeometryVariablesTest, GLInArraySize)
    {
        const std::array<std::string, 5> kInputPrimitives = {
            {"points", "lines", "lines_adjacency", "triangles", "triangles_adjacency"}};
    
        const GLuint kArraySizeForInputPrimitives[] = {1u, 2u, 4u, 3u, 6u};
    
        const std::string &functionBody =
            R"(void main()
            {
                gl_Position = gl_in[0].gl_Position;
            })";
    
        for (size_t i = 0; i < kInputPrimitives.size(); ++i)
        {
            compileGeometryShaderWithInputPrimitive(kInputPrimitives[i], "", functionBody);
    
            const auto &inBlocks = mTranslator->getInBlocks();
            ASSERT_EQ(1u, inBlocks.size());
    
            const InterfaceBlock *inBlock = &inBlocks[0];
            ASSERT_EQ("gl_in", inBlock->instanceName);
            EXPECT_EQ(kArraySizeForInputPrimitives[i], inBlock->arraySize);
        }
    }
    
    // Test collecting gl_PrimitiveIDIn in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectPrimitiveIDIn)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_Position = vec4(gl_PrimitiveIDIn);
                EmitVertex();
            })";
    
        compile(shaderString);
    
        EXPECT_EQ(1u, mTranslator->getOutputVaryings().size());
        ASSERT_TRUE(mTranslator->getInBlocks().empty());
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, inputVaryings.size());
    
        const Varying *varying = &inputVaryings[0];
        EXPECT_EQ("gl_PrimitiveIDIn", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting gl_InvocationID in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectInvocationID)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points, invocations = 2) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_Position = vec4(gl_InvocationID);
                EmitVertex();
            })";
    
        compile(shaderString);
    
        EXPECT_EQ(1u, mTranslator->getOutputVaryings().size());
        ASSERT_TRUE(mTranslator->getInBlocks().empty());
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, inputVaryings.size());
    
        const Varying *varying = &inputVaryings[0];
        EXPECT_EQ("gl_InvocationID", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting gl_in in a geometry shader when gl_in is indexed by an expression.
    TEST_F(CollectGeometryVariablesTest, CollectGLInIndexedByExpression)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (triangles, invocations = 2) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_Position = gl_in[gl_InvocationID + 1].gl_Position;
                EmitVertex();
            })";
    
        compile(shaderString);
    
        EXPECT_EQ(1u, mTranslator->getOutputVaryings().size());
    
        const auto &inBlocks = mTranslator->getInBlocks();
        ASSERT_EQ(1u, inBlocks.size());
        const InterfaceBlock *inBlock = &inBlocks[0];
        EXPECT_EQ("gl_PerVertex", inBlock->name);
        EXPECT_EQ("gl_in", inBlock->instanceName);
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, inputVaryings.size());
        const Varying *glInvocationID = &inputVaryings[0];
        EXPECT_EQ("gl_InvocationID", glInvocationID->name);
    }
    
    // Test collecting gl_Position in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectPosition)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_Position = vec4(0.1, 0.2, 0.3, 1);
            })";
    
        compile(shaderString);
    
        ASSERT_TRUE(mTranslator->getInputVaryings().empty());
        ASSERT_TRUE(mTranslator->getInBlocks().empty());
    
        const auto &outputVaryings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, outputVaryings.size());
    
        const Varying *varying = &outputVaryings[0];
        EXPECT_EQ("gl_Position", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying->precision);
        EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, varying->type);
    }
    
    // Test collecting gl_PrimitiveID in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectPrimitiveID)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_PrimitiveID = 100;
            })";
    
        compile(shaderString);
    
        ASSERT_TRUE(mTranslator->getInputVaryings().empty());
        ASSERT_TRUE(mTranslator->getInBlocks().empty());
    
        const auto &OutputVaryings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, OutputVaryings.size());
    
        const Varying *varying = &OutputVaryings[0];
        EXPECT_EQ("gl_PrimitiveID", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting gl_Layer in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectLayer)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            void main()
            {
                gl_Layer = 2;
            })";
    
        compile(shaderString);
    
        ASSERT_TRUE(mTranslator->getInputVaryings().empty());
        ASSERT_TRUE(mTranslator->getInBlocks().empty());
    
        const auto &OutputVaryings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, OutputVaryings.size());
    
        const Varying *varying = &OutputVaryings[0];
        EXPECT_EQ("gl_Layer", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting gl_PrimitiveID in a fragment shader.
    TEST_F(CollectFragmentVariablesEXTGeometryShaderTest, CollectPrimitiveID)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
    
            out int my_out;
    
            void main()
            {
                my_out = gl_PrimitiveID;
            })";
    
        compile(shaderString);
    
        ASSERT_TRUE(mTranslator->getOutputVaryings().empty());
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, inputVaryings.size());
    
        const Varying *varying = &inputVaryings[0];
        EXPECT_EQ("gl_PrimitiveID", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting gl_Layer in a fragment shader.
    TEST_F(CollectFragmentVariablesEXTGeometryShaderTest, CollectLayer)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
    
            out int my_out;
    
            void main()
            {
                my_out = gl_Layer;
            })";
    
        compile(shaderString);
    
        ASSERT_TRUE(mTranslator->getOutputVaryings().empty());
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, inputVaryings.size());
    
        const Varying *varying = &inputVaryings[0];
        EXPECT_EQ("gl_Layer", varying->name);
        EXPECT_FALSE(varying->isArray());
        EXPECT_FALSE(varying->isStruct());
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_TRUE(varying->isBuiltIn());
        EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
        EXPECT_GLENUM_EQ(GL_INT, varying->type);
    }
    
    // Test collecting the location of vertex shader outputs.
    TEST_F(CollectVertexVariablesES31Test, CollectOutputWithLocation)
    {
        const std::string &shaderString =
            R"(#version 310 es
            out vec4 v_output1;
            layout (location = 1) out vec4 v_output2;
            void main()
            {
            })";
    
        compile(shaderString);
    
        const auto &outputVaryings = mTranslator->getOutputVaryings();
        ASSERT_EQ(2u, outputVaryings.size());
    
        const Varying *varying1 = &outputVaryings[0];
        EXPECT_EQ("v_output1", varying1->name);
        EXPECT_EQ(-1, varying1->location);
    
        const Varying *varying2 = &outputVaryings[1];
        EXPECT_EQ("v_output2", varying2->name);
        EXPECT_EQ(1, varying2->location);
    }
    
    // Test collecting the location of fragment shader inputs.
    TEST_F(CollectFragmentVariablesES31Test, CollectInputWithLocation)
    {
        const std::string &shaderString =
            R"(#version 310 es
            precision mediump float;
            in vec4 f_input1;
            layout (location = 1) in vec4 f_input2;
            layout (location = 0) out vec4 o_color;
            void main()
            {
                o_color = f_input2;
            })";
    
        compile(shaderString);
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(2u, inputVaryings.size());
    
        const Varying *varying1 = &inputVaryings[0];
        EXPECT_EQ("f_input1", varying1->name);
        EXPECT_EQ(-1, varying1->location);
    
        const Varying *varying2 = &inputVaryings[1];
        EXPECT_EQ("f_input2", varying2->name);
        EXPECT_EQ(1, varying2->location);
    }
    
    // Test collecting the inputs of a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectInputs)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            in vec4 texcoord1[];
            in vec4 texcoord2[1];
            void main()
            {
                gl_Position = texcoord1[0];
                gl_Position += texcoord2[0];
                EmitVertex();
            })";
    
        compile(shaderString);
    
        EXPECT_EQ(1u, mTranslator->getOutputVaryings().size());
    
        const auto &inputVaryings = mTranslator->getInputVaryings();
        ASSERT_EQ(2u, inputVaryings.size());
    
        const std::string kVaryingName[] = {"texcoord1", "texcoord2"};
    
        for (size_t i = 0; i < inputVaryings.size(); ++i)
        {
            const Varying &varying = inputVaryings[i];
    
            EXPECT_EQ(kVaryingName[i], varying.name);
            EXPECT_TRUE(varying.isArray());
            EXPECT_FALSE(varying.isStruct());
            EXPECT_TRUE(varying.staticUse);
            EXPECT_TRUE(varying.active);
            EXPECT_FALSE(varying.isBuiltIn());
            EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying.precision);
            EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, varying.type);
            EXPECT_FALSE(varying.isInvariant);
            ASSERT_EQ(1u, varying.arraySizes.size());
            EXPECT_EQ(1u, varying.arraySizes.back());
        }
    }
    
    // Test that the unsized input of a geometry shader can be correctly collected.
    TEST_F(CollectGeometryVariablesTest, CollectInputArraySizeForUnsizedInput)
    {
        const std::array<std::string, 5> kInputPrimitives = {
            {"points", "lines", "lines_adjacency", "triangles", "triangles_adjacency"}};
    
        const GLuint kArraySizeForInputPrimitives[] = {1u, 2u, 4u, 3u, 6u};
    
        const std::string &kVariableDeclaration = "in vec4 texcoord[];\n";
        const std::string &kFunctionBody =
            R"(void main()
            {
                gl_Position = texcoord[0];
            })";
    
        for (size_t i = 0; i < kInputPrimitives.size(); ++i)
        {
            compileGeometryShaderWithInputPrimitive(kInputPrimitives[i], kVariableDeclaration,
                                                    kFunctionBody);
    
            const auto &inputVaryings = mTranslator->getInputVaryings();
            ASSERT_EQ(1u, inputVaryings.size());
    
            const Varying *varying = &inputVaryings[0];
            EXPECT_EQ("texcoord", varying->name);
            ASSERT_EQ(1u, varying->arraySizes.size());
            EXPECT_EQ(kArraySizeForInputPrimitives[i], varying->arraySizes.back());
        }
    }
    
    // Test collecting inputs using interpolation qualifiers in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectInputsWithInterpolationQualifiers)
    {
        const std::string &kHeader =
            "#version 310 es\n"
            "#extension GL_EXT_geometry_shader : require\n";
        const std::string &kLayout =
            "layout (points) in;\n"
            "layout (points, max_vertices = 2) out;\n";
    
        const std::array<std::string, 3> kInterpolationQualifiers = {{"flat", "smooth", "centroid"}};
    
        const std::array<InterpolationType, 3> kInterpolationType = {
            {INTERPOLATION_FLAT, INTERPOLATION_SMOOTH, INTERPOLATION_CENTROID}};
    
        const std::string &kFunctionBody =
            R"(void main()
            {
                gl_Position = texcoord[0];
                EmitVertex();
            })";
    
        for (size_t i = 0; i < kInterpolationQualifiers.size(); ++i)
        {
            const std::string &qualifier = kInterpolationQualifiers[i];
    
            std::ostringstream stream1;
            stream1 << kHeader << kLayout << qualifier << " in vec4 texcoord[];\n" << kFunctionBody;
            compile(stream1.str());
    
            const auto &inputVaryings = mTranslator->getInputVaryings();
            ASSERT_EQ(1u, inputVaryings.size());
            const Varying *varying = &inputVaryings[0];
            EXPECT_EQ("texcoord", varying->name);
            EXPECT_EQ(kInterpolationType[i], varying->interpolation);
        }
    }
    
    // Test collecting outputs using interpolation qualifiers in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectOutputsWithInterpolationQualifiers)
    {
        const std::string &kHeader =
            "#version 310 es\n"
            "#extension GL_EXT_geometry_shader : require\n"
            "layout (points) in;\n"
            "layout (points, max_vertices = 2) out;\n";
    
        const std::array<std::string, 4> kInterpolationQualifiers = {
            {"", "flat", "smooth", "centroid"}};
    
        const std::array<InterpolationType, 4> kInterpolationType = {
            {INTERPOLATION_SMOOTH, INTERPOLATION_FLAT, INTERPOLATION_SMOOTH, INTERPOLATION_CENTROID}};
    
        const std::string &kFunctionBody =
            "void main()\n"
            "{\n"
            "    texcoord = vec4(1.0, 0.0, 0.0, 1.0);\n"
            "}\n";
    
        for (size_t i = 0; i < kInterpolationQualifiers.size(); ++i)
        {
            const std::string &qualifier = kInterpolationQualifiers[i];
            std::ostringstream stream;
            stream << kHeader << qualifier << " out vec4 texcoord;\n" << kFunctionBody;
    
            compile(stream.str());
            const auto &outputVaryings = mTranslator->getOutputVaryings();
            ASSERT_EQ(1u, outputVaryings.size());
    
            const Varying *varying = &outputVaryings[0];
            EXPECT_EQ("texcoord", varying->name);
            EXPECT_EQ(kInterpolationType[i], varying->interpolation);
            EXPECT_FALSE(varying->isInvariant);
        }
    }
    
    // Test collecting outputs using 'invariant' qualifier in a geometry shader.
    TEST_F(CollectGeometryVariablesTest, CollectOutputsWithInvariant)
    {
        const std::string &shaderString =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (points) in;
            layout (points, max_vertices = 2) out;
            invariant out vec4 texcoord;
            void main()
            {
                texcoord = vec4(1.0, 0.0, 0.0, 1.0);
            })";
    
        compile(shaderString);
    
        const auto &outputVaryings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, outputVaryings.size());
    
        const Varying *varying = &outputVaryings[0];
        EXPECT_EQ("texcoord", varying->name);
        EXPECT_TRUE(varying->isInvariant);
    }
    
    // Test collecting a varying variable that is used inside a folded ternary operator. The result of
    // the folded ternary operator has a different qualifier from the original variable, which makes
    // this case tricky.
    TEST_F(CollectFragmentVariablesTest, VaryingUsedInsideFoldedTernary)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision highp float;
            centroid in float vary;
            out vec4 color;
            void main() {
               color = vec4(0.0, true ? vary : 0.0, 0.0, 1.0);
            })";
    
        compile(shaderString);
    
        const std::vector<Varying> &varyings = mTranslator->getInputVaryings();
        ASSERT_EQ(1u, varyings.size());
    
        const Varying *varying = &varyings[0];
    
        EXPECT_FALSE(varying->isArray());
        EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, varying->precision);
        EXPECT_TRUE(varying->staticUse);
        EXPECT_TRUE(varying->active);
        EXPECT_GLENUM_EQ(GL_FLOAT, varying->type);
        EXPECT_EQ("vary", varying->name);
        EXPECT_EQ(DecorateName("vary"), varying->mappedName);
        EXPECT_EQ(INTERPOLATION_CENTROID, varying->interpolation);
    }
    
    // Test a variable that is statically used but not active. The variable is used in a branch of a
    // ternary op that is not evaluated.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveInTernaryOp)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform float u;
            void main()
            {
                out_fragColor = vec4(true ? 0.0 : u);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a return value in an
    // unused function.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsReturnValue)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform float u;
            float f() {
                return u;
            }
            void main()
            {
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is an if statement condition
    // inside a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsIfCondition)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform bool u;
            void main()
            {
                if (false) {
                    if (u) {
                        out_fragColor = vec4(1.0);
                    }
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a constructor argument in
    // a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsConstructorArgument)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform float u;
            void main()
            {
                if (false) {
                    out_fragColor = vec4(u);
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a binary operator operand
    // in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsBinaryOpOperand)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform vec4 u;
            void main()
            {
                if (false) {
                    out_fragColor = u + 1.0;
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a comparison operator
    // operand in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsComparisonOpOperand)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform vec4 u;
            void main()
            {
                if (false) {
                    if (u == vec4(1.0))
                    {
                        out_fragColor = vec4(1.0);
                    }
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is an unary operator operand
    // in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsUnaryOpOperand)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform vec4 u;
            void main()
            {
                if (false) {
                    out_fragColor = -u;
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is an rvalue in an assigment
    // in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsAssignmentRValue)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform vec4 u;
            void main()
            {
                if (false) {
                    out_fragColor = u;
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a comma operator operand
    // in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsCommaOperand)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform vec4 u;
            void main()
            {
                if (false) {
                    out_fragColor = u, vec4(1.0);
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a switch init statement
    // in a block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsSwitchInitStatement)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform int u;
            void main()
            {
                if (false)
                {
                    switch (u)
                    {
                        case 1:
                            out_fragColor = vec4(2.0);
                        default:
                            out_fragColor = vec4(1.0);
                    }
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a loop condition in a
    // block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsLoopCondition)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform bool u;
            void main()
            {
                int counter = 0;
                if (false)
                {
                    while (u)
                    {
                        if (++counter > 2)
                        {
                            break;
                        }
                    }
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a loop expression in a
    // block that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsLoopExpression)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform bool u;
            void main()
            {
                if (false)
                {
                    for (int i = 0; i < 3; u)
                    {
                        ++i;
                    }
                }
                out_fragColor = vec4(0.0);
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is a vector index in a block
    // that is not executed.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveAsVectorIndex)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform int u;
            void main()
            {
                vec4 color = vec4(0.0);
                if (false)
                {
                    color[u] = 1.0;
                }
                out_fragColor = color;
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is referenced in a block
    // that's not executed. This is a bit of a corner case with some room for interpretation, but we
    // treat the variable as statically used.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveJustAReference)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform int u;
            void main()
            {
                vec4 color = vec4(0.0);
                if (false)
                {
                    u;
                }
                out_fragColor = color;
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is statically used but not active. The variable is referenced in a block
    // without braces that's not executed. This is a bit of a corner case with some room for
    // interpretation, but we treat the variable as statically used.
    TEST_F(CollectFragmentVariablesTest, StaticallyUsedButNotActiveJustAReferenceNoBracesIf)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform int u;
            void main()
            {
                vec4 color = vec4(0.0);
                if (false)
                    u;
                out_fragColor = color;
            })";
    
        compile(shaderString);
        checkUniformStaticallyUsedButNotActive("u");
    }
    
    // Test a variable that is referenced in a loop body without braces.
    TEST_F(CollectFragmentVariablesTest, JustAVariableReferenceInNoBracesLoop)
    {
        const std::string &shaderString =
            R"(#version 300 es
            precision mediump float;
            out vec4 out_fragColor;
            uniform int u;
            void main()
            {
                vec4 color = vec4(0.0);
                while (false)
                    u;
                out_fragColor = color;
            })";
    
        compile(shaderString);
    
        const auto &uniforms = mTranslator->getUniforms();
        ASSERT_EQ(1u, uniforms.size());
    
        const Uniform &uniform = uniforms[0];
        EXPECT_EQ("u", uniform.name);
        EXPECT_TRUE(uniform.staticUse);
        // Note that we don't check the active flag here - the usage of the uniform is not currently
        // being optimized away.
    }
    
    // Test an interface block member variable that is statically used but not active.
    TEST_F(CollectVertexVariablesTest, StaticallyUsedButNotActiveSimpleInterfaceBlock)
    {
        const std::string &shaderString =
            R"(#version 300 es
            uniform b
            {
                float f;
            };
            void main() {
                gl_Position = vec4(true ? 0.0 : f);
            })";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_FALSE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
        const InterfaceBlockField &field = interfaceBlock.fields[0];
    
        EXPECT_EQ("f", field.name);
        EXPECT_TRUE(field.staticUse);
        EXPECT_FALSE(field.active);
    }
    
    // Test an interface block instance variable that is statically used but not active.
    TEST_F(CollectVertexVariablesTest, StaticallyUsedButNotActiveInstancedInterfaceBlock)
    {
        const std::string &shaderString =
            R"(#version 300 es
            uniform b
            {
                float f;
            } blockInstance;
            void main() {
                gl_Position = vec4(true ? 0.0 : blockInstance.f);
            })";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_TRUE(interfaceBlock.staticUse);
        EXPECT_FALSE(interfaceBlock.active);
    
        ASSERT_EQ(1u, interfaceBlock.fields.size());
        const InterfaceBlockField &field = interfaceBlock.fields[0];
    
        EXPECT_EQ("f", field.name);
        // See TODO in CollectVariables.cpp about tracking instanced interface block field static use.
        // EXPECT_TRUE(field.staticUse);
        EXPECT_FALSE(field.active);
    }
    
    // Test an interface block member variable that is statically used. The variable is used to call
    // array length method.
    TEST_F(CollectVertexVariablesTest, StaticallyUsedInArrayLengthOp)
    {
        const std::string &shaderString =
            R"(#version 300 es
            uniform b
            {
                float f[3];
            };
            void main() {
                if (f.length() > 1)
                {
                    gl_Position = vec4(1.0);
                }
                else
                {
                    gl_Position = vec4(0.0);
                }
            })";
    
        compile(shaderString);
    
        const std::vector<InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
        ASSERT_EQ(1u, interfaceBlocks.size());
        const InterfaceBlock &interfaceBlock = interfaceBlocks[0];
    
        EXPECT_EQ("b", interfaceBlock.name);
        EXPECT_TRUE(interfaceBlock.staticUse);
    }
    
    // Test a varying that is declared invariant but not otherwise used.
    TEST_F(CollectVertexVariablesTest, VaryingOnlyDeclaredInvariant)
    {
        const std::string &shaderString =
            R"(precision mediump float;
            varying float vf;
            invariant vf;
            void main()
            {
            })";
    
        compile(shaderString);
    
        const auto &varyings = mTranslator->getOutputVaryings();
        ASSERT_EQ(1u, varyings.size());
    
        const Varying &varying = varyings[0];
        EXPECT_EQ("vf", varying.name);
        EXPECT_FALSE(varying.staticUse);
        EXPECT_FALSE(varying.active);
    }
    
    // Test an output variable that is declared with the index layout qualifier from
    // EXT_blend_func_extended.
    TEST_F(CollectFragmentVariablesTest, OutputVarESSL3EXTBlendFuncExtendedIndex)
    {
        const std::string &shaderString =
            R"(#version 300 es
    #extension GL_EXT_blend_func_extended : require
    precision mediump float;
    layout(location = 0, index = 1) out float outVar;
    void main()
    {
        outVar = 0.0;
    })";
    
        compile(shaderString);
    
        const auto &outputs = mTranslator->getOutputVariables();
        ASSERT_EQ(1u, outputs.size());
    
        const OutputVariable &output = outputs[0];
        EXPECT_EQ("outVar", output.name);
        EXPECT_TRUE(output.staticUse);
        EXPECT_TRUE(output.active);
        EXPECT_EQ(1, output.index);
    }