//
// Copyright 2017 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.
//

// GeometryShaderTest.cpp : Tests of the implementation of geometry shader

#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

using namespace angle;

namespace
{

class GeometryShaderTest : public ANGLETest
{
  protected:
    static std::string CreateEmptyGeometryShader(const std::string &inputPrimitive,
                                                 const std::string &outputPrimitive,
                                                 int invocations,
                                                 int maxVertices)
    {
        std::ostringstream ostream;
        ostream << "#version 310 es\n"
                   "#extension GL_EXT_geometry_shader : require\n";
        if (!inputPrimitive.empty())
        {
            ostream << "layout (" << inputPrimitive << ") in;\n";
        }
        if (!outputPrimitive.empty())
        {
            ostream << "layout (" << outputPrimitive << ") out;\n";
        }
        if (invocations > 0)
        {
            ostream << "layout (invocations = " << invocations << ") in;\n";
        }
        if (maxVertices >= 0)
        {
            ostream << "layout (max_vertices = " << maxVertices << ") out;\n";
        }
        ostream << "void main()\n"
                   "{\n"
                   "}";
        return ostream.str();
    }
};

class GeometryShaderTestES3 : public ANGLETest
{};

// Verify that Geometry Shader cannot be created in an OpenGL ES 3.0 context.
TEST_P(GeometryShaderTestES3, CreateGeometryShaderInES3)
{
    EXPECT_TRUE(!extensionEnabled("GL_EXT_geometry_shader"));
    GLuint geometryShader = glCreateShader(GL_GEOMETRY_SHADER_EXT);
    EXPECT_EQ(0u, geometryShader);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);
}

// Verify that Geometry Shader can be created and attached to a program.
TEST_P(GeometryShaderTest, CreateAndAttachGeometryShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &geometryShaderSource =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        layout (invocations = 3, triangles) in;
        layout (triangle_strip, max_vertices = 3) out;
        in vec4 texcoord[];
        out vec4 o_texcoord;
        void main()
        {
            int n;
            for (n = 0; n < gl_in.length(); n++)
            {
                gl_Position = gl_in[n].gl_Position;
                gl_Layer   = gl_InvocationID;
                o_texcoord = texcoord[n];
                EmitVertex();
            }
            EndPrimitive();
        })";

    GLuint geometryShader = CompileShader(GL_GEOMETRY_SHADER_EXT, geometryShaderSource);

    EXPECT_NE(0u, geometryShader);

    GLuint programID = glCreateProgram();
    glAttachShader(programID, geometryShader);

    glDetachShader(programID, geometryShader);
    glDeleteShader(geometryShader);
    glDeleteProgram(programID);

    EXPECT_GL_NO_ERROR();
}

// Verify that all the implementation dependent geometry shader related resource limits meet the
// requirement of GL_EXT_geometry_shader SPEC.
TEST_P(GeometryShaderTest, GeometryShaderImplementationDependentLimits)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::map<GLenum, int> limits = {{GL_MAX_FRAMEBUFFER_LAYERS_EXT, 256},
                                          {GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT, 1024},
                                          {GL_MAX_GEOMETRY_UNIFORM_BLOCKS_EXT, 12},
                                          {GL_MAX_GEOMETRY_INPUT_COMPONENTS_EXT, 64},
                                          {GL_MAX_GEOMETRY_OUTPUT_COMPONENTS_EXT, 64},
                                          {GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT, 256},
                                          {GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT, 1024},
                                          {GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT, 16},
                                          {GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS_EXT, 0},
                                          {GL_MAX_GEOMETRY_ATOMIC_COUNTERS_EXT, 0},
                                          {GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT, 0},
                                          {GL_MAX_GEOMETRY_IMAGE_UNIFORMS_EXT, 0},
                                          {GL_MAX_GEOMETRY_SHADER_INVOCATIONS_EXT, 32}};

    GLint value;
    for (const auto &limit : limits)
    {
        value = 0;
        glGetIntegerv(limit.first, &value);
        EXPECT_GL_NO_ERROR();
        EXPECT_GE(value, limit.second);
    }

    value = 0;
    glGetIntegerv(GL_LAYER_PROVOKING_VERTEX_EXT, &value);
    EXPECT_GL_NO_ERROR();
    EXPECT_TRUE(value == GL_FIRST_VERTEX_CONVENTION_EXT || value == GL_LAST_VERTEX_CONVENTION_EXT ||
                value == GL_UNDEFINED_VERTEX_EXT);
}

// Verify that all the combined resource limits meet the requirement of GL_EXT_geometry_shader SPEC.
TEST_P(GeometryShaderTest, CombinedResourceLimits)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    // See http://anglebug.com/2261.
    ANGLE_SKIP_TEST_IF(IsAndroid());

    const std::map<GLenum, int> limits = {{GL_MAX_UNIFORM_BUFFER_BINDINGS, 48},
                                          {GL_MAX_COMBINED_UNIFORM_BLOCKS, 36},
                                          {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 64}};

    GLint value;
    for (const auto &limit : limits)
    {
        value = 0;
        glGetIntegerv(limit.first, &value);
        EXPECT_GL_NO_ERROR();
        EXPECT_GE(value, limit.second);
    }
}

// Verify that linking a program with an uncompiled geometry shader causes a link failure.
TEST_P(GeometryShaderTest, LinkWithUncompiledGeometryShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLuint vertexShader   = CompileShader(GL_VERTEX_SHADER, essl31_shaders::vs::Simple());
    GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, essl31_shaders::fs::Red());
    ASSERT_NE(0u, vertexShader);
    ASSERT_NE(0u, fragmentShader);

    GLuint geometryShader = glCreateShader(GL_GEOMETRY_SHADER_EXT);

    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glAttachShader(program, geometryShader);
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(geometryShader);

    glLinkProgram(program);

    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_EQ(0, linkStatus);

    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
}

// Verify that linking a program with geometry shader whose version is different from other shaders
// in this program causes a link error.
TEST_P(GeometryShaderTest, LinkWhenShaderVersionMismatch)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &emptyGeometryShader = CreateEmptyGeometryShader("points", "points", 2, 1);

    GLuint program = CompileProgramWithGS(essl3_shaders::vs::Simple(), emptyGeometryShader,
                                          essl3_shaders::fs::Red());
    EXPECT_EQ(0u, program);
}

// Verify that linking a program with geometry shader that lacks input primitive,
// output primitive, or declaration on 'max_vertices' causes a link failure.
TEST_P(GeometryShaderTest, LinkValidationOnGeometryShaderLayouts)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &gsWithoutInputPrimitive  = CreateEmptyGeometryShader("", "points", 2, 1);
    const std::string &gsWithoutOutputPrimitive = CreateEmptyGeometryShader("points", "", 2, 1);
    const std::string &gsWithoutInvocations = CreateEmptyGeometryShader("points", "points", -1, 1);
    const std::string &gsWithoutMaxVertices = CreateEmptyGeometryShader("points", "points", 2, -1);

    // Linking a program with a geometry shader that only lacks 'invocations' should not cause a
    // link failure.
    GLuint program = CompileProgramWithGS(essl31_shaders::vs::Simple(), gsWithoutInvocations,
                                          essl31_shaders::fs::Red());
    EXPECT_NE(0u, program);

    glDeleteProgram(program);

    // Linking a program with a geometry shader that lacks input primitive, output primitive or
    // 'max_vertices' causes a link failure.
    program = CompileProgramWithGS(essl31_shaders::vs::Simple(), gsWithoutInputPrimitive,
                                   essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    program = CompileProgramWithGS(essl31_shaders::vs::Simple(), gsWithoutOutputPrimitive,
                                   essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    program = CompileProgramWithGS(essl31_shaders::vs::Simple(), gsWithoutMaxVertices,
                                   essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    ASSERT_GL_NO_ERROR();
}

// Verify that an link error occurs when the vertex shader has an array output and there is a
// geometry shader in the program.
TEST_P(GeometryShaderTest, VertexShaderArrayOutput)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &vertexShader =
        R"(#version 310 es
        in vec4 vertex_in;
        out vec4 vertex_out[3];
        void main()
        {
            gl_Position = vertex_in;
            vertex_out[0] = vec4(1.0, 0.0, 0.0, 1.0);
            vertex_out[1] = vec4(0.0, 1.0, 0.0, 1.0);
            vertex_out[2] = vec4(0.0, 0.0, 1.0, 1.0);
        })";

    const std::string &geometryShader =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        layout (invocations = 3, triangles) in;
        layout (points, max_vertices = 3) out;
        in vec4 vertex_out[];
        out vec4 geometry_color;
        void main()
        {
            gl_Position = gl_in[0].gl_Position;
            geometry_color = vertex_out[0];
            EmitVertex();
        })";

    const std::string &fragmentShader =
        R"(#version 310 es
        precision mediump float;
        in vec4 geometry_color;
        layout (location = 0) out vec4 output_color;
        void main()
        {
            output_color = geometry_color;
        })";

    GLuint program = CompileProgramWithGS(vertexShader, geometryShader, fragmentShader);
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify that an link error occurs when the definition of a unform in fragment shader is different
// from those in a geometry shader.
TEST_P(GeometryShaderTest, UniformMismatchBetweenGeometryAndFragmentShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &vertexShader =
        R"(#version 310 es
        uniform highp vec4 uniform_value_vert;
        in vec4 vertex_in;
        out vec4 vertex_out;
        void main()
        {
            gl_Position = vertex_in;
            vertex_out = uniform_value_vert;
        })";

    const std::string &geometryShader =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        uniform vec4 uniform_value;
        layout (invocations = 3, triangles) in;
        layout (points, max_vertices = 3) out;
        in vec4 vertex_out[];
        out vec4 geometry_color;
        void main()
        {
            gl_Position = gl_in[0].gl_Position;
            geometry_color = vertex_out[0] + uniform_value;
            EmitVertex();
        })";

    const std::string &fragmentShader =
        R"(#version 310 es
        precision highp float;
        uniform float uniform_value;
        in vec4 geometry_color;
        layout (location = 0) out vec4 output_color;
        void main()
        {
            output_color = vec4(geometry_color.rgb, uniform_value);
        })";

    GLuint program = CompileProgramWithGS(vertexShader, geometryShader, fragmentShader);
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify that an link error occurs when the number of uniform blocks in a geometry shader exceeds
// MAX_GEOMETRY_UNIFORM_BLOCKS_EXT.
TEST_P(GeometryShaderTest, TooManyUniformBlocks)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLint maxGeometryUniformBlocks = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_UNIFORM_BLOCKS_EXT, &maxGeometryUniformBlocks);

    GLint numUniformBlocks = maxGeometryUniformBlocks + 1;
    std::ostringstream stream;
    stream << "#version 310 es\n"
              "#extension GL_EXT_geometry_shader : require\n"
              "uniform ubo\n"
              "{\n"
              "    vec4 value1;\n"
              "} block0["
           << numUniformBlocks
           << "];\n"
              "layout (triangles) in;\n"
              "layout (points, max_vertices = 1) out;\n"
              "void main()\n"
              "{\n"
              "    gl_Position = gl_in[0].gl_Position;\n";

    for (GLint i = 0; i < numUniformBlocks; ++i)
    {
        stream << "    gl_Position += block0[" << i << "].value1;\n";
    }
    stream << "    EmitVertex();\n"
              "}\n";

    GLuint program =
        CompileProgramWithGS(essl31_shaders::vs::Simple(), stream.str(), essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify that an link error occurs when the number of shader storage blocks in a geometry shader
// exceeds MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT.
TEST_P(GeometryShaderTest, TooManyShaderStorageBlocks)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLint maxGeometryShaderStorageBlocks = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT, &maxGeometryShaderStorageBlocks);

    GLint numSSBOs = maxGeometryShaderStorageBlocks + 1;
    std::ostringstream stream;
    stream << "#version 310 es\n"
              "#extension GL_EXT_geometry_shader : require\n"
              "buffer ssbo\n"
              "{\n"
              "    vec4 value1;\n"
              "} block0["
           << numSSBOs
           << "];\n"
              "layout (triangles) in;\n"
              "layout (points, max_vertices = 1) out;\n"
              "void main()\n"
              "{\n"
              "    gl_Position = gl_in[0].gl_Position;\n";

    for (GLint i = 0; i < numSSBOs; ++i)
    {
        stream << "    gl_Position += block0[" << i << "].value1;\n";
    }
    stream << "    EmitVertex();\n"
              "}\n";

    GLuint program =
        CompileProgramWithGS(essl31_shaders::vs::Simple(), stream.str(), essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify that an link error occurs when the definition of a unform block in the vertex shader is
// different from that in a geometry shader.
TEST_P(GeometryShaderTest, UniformBlockMismatchBetweenVertexAndGeometryShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &vertexShader =
        R"(#version 310 es
        uniform ubo
        {
            vec4 uniform_value_vert;
        } block0;
        in vec4 vertex_in;
        out vec4 vertex_out;
        void main()
        {
            gl_Position = vertex_in;
            vertex_out = block0.uniform_value_vert;
        })";

    const std::string &geometryShader =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        uniform ubo
        {
            vec4 uniform_value_geom;
        } block0;
        layout (triangles) in;
        layout (points, max_vertices = 1) out;
        in vec4 vertex_out[];
        void main()
        {
            gl_Position = gl_in[0].gl_Position + vertex_out[0];
            gl_Position += block0.uniform_value_geom;
            EmitVertex();
        })";

    GLuint program = CompileProgramWithGS(vertexShader, geometryShader, essl31_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify that an link error occurs when the definition of a shader storage block in the geometry
// shader is different from that in a fragment shader.
TEST_P(GeometryShaderTest, ShaderStorageBlockMismatchBetweenGeometryAndFragmentShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLint maxGeometryShaderStorageBlocks = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT, &maxGeometryShaderStorageBlocks);

    // The minimun value of MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT can be 0.
    // [EXT_geometry_shader] Table 20.43gs
    ANGLE_SKIP_TEST_IF(maxGeometryShaderStorageBlocks == 0);

    GLint maxFragmentShaderStorageBlocks = 0;
    glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);

    // The minimun value of MAX_FRAGMENT_SHADER_STORAGE_BLOCKS can be 0.
    // [OpenGL ES 3.1] Table 20.44
    ANGLE_SKIP_TEST_IF(maxFragmentShaderStorageBlocks == 0);

    const std::string &geometryShader =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        buffer ssbo
        {
            vec4 ssbo_value;
        } block0;
        layout (triangles) in;
        layout (points, max_vertices = 1) out;
        void main()
        {
            gl_Position = gl_in[0].gl_Position + block0.ssbo_value;
            EmitVertex();
        })";

    const std::string &fragmentShader =
        R"(#version 310 es
        precision highp float;
        buffer ssbo
        {
            vec3 ssbo_value;
        } block0;
        layout (location = 0) out vec4 output_color;
        void main()
        {
            output_color = vec4(block0.ssbo_value, 1);
        })";

    GLuint program =
        CompileProgramWithGS(essl31_shaders::vs::Simple(), geometryShader, fragmentShader);
    EXPECT_EQ(0u, program);

    EXPECT_GL_NO_ERROR();
}

// Verify GL_REFERENCED_BY_GEOMETRY_SHADER_EXT cannot be used on platforms that don't support
// EXT_geometry_shader, or we will get an INVALID_ENUM error.
TEST_P(GeometryShaderTest, ReferencedByGeometryShaderWithoutExtensionEnabled)
{
    ANGLE_SKIP_TEST_IF(extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &fragmentShader =
        R"(#version 310 es
        precision highp float;
        uniform vec4 color;
        layout(location = 0) out vec4 oColor;
        void main()
        {
            oColor = color;
        })";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fragmentShader);

    const GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "color");
    ASSERT_GL_NO_ERROR();
    ASSERT_NE(GL_INVALID_INDEX, index);

    constexpr GLenum kProps[]    = {GL_REFERENCED_BY_GEOMETRY_SHADER_EXT};
    constexpr GLsizei kPropCount = static_cast<GLsizei>(ArraySize(kProps));
    GLint params[ArraySize(kProps)];
    GLsizei length;

    glGetProgramResourceiv(program, GL_UNIFORM, index, kPropCount, kProps, kPropCount, &length,
                           params);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);
}

// Verify GL_REFERENCED_BY_GEOMETRY_SHADER_EXT can work correctly on platforms that support
// EXT_geometry_shader.
TEST_P(GeometryShaderTest, ReferencedByGeometryShader)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    const std::string &vertexShader =
        R"(#version 310 es
        precision highp float;
        layout(location = 0) in highp vec4 position;
        void main()
        {
            gl_Position = position;
        })";

    const std::string &geometryShader =
        R"(#version 310 es
        #extension GL_EXT_geometry_shader : require
        layout (binding = 3) uniform ubo0
        {
            vec4 ubo0_location;
        } block0;
        layout (binding = 4) uniform ubo1
        {
            vec4 ubo1_location;
        } block1;
        uniform vec4 u_color;
        layout (triangles) in;
        layout (points, max_vertices = 1) out;
        out vec4 gs_out;
        void main()
        {
            gl_Position = gl_in[0].gl_Position;
            gl_Position += block0.ubo0_location + block1.ubo1_location;
            gs_out = u_color;
            EmitVertex();
        })";

    const std::string &fragmentShader =
        R"(#version 310 es
        precision highp float;
        in vec4 gs_out;
        layout(location = 0) out vec4 oColor;
        void main()
        {
            oColor = gs_out;
        })";

    ANGLE_GL_PROGRAM_WITH_GS(program, vertexShader, geometryShader, fragmentShader);

    constexpr GLenum kProps[]    = {GL_REFERENCED_BY_GEOMETRY_SHADER_EXT};
    constexpr GLsizei kPropCount = static_cast<GLsizei>(ArraySize(kProps));
    std::array<GLint, ArraySize(kProps)> params;
    GLsizei length;

    params.fill(1);
    GLuint index = glGetProgramResourceIndex(program, GL_PROGRAM_INPUT, "position");
    glGetProgramResourceiv(program, GL_PROGRAM_INPUT, index, kPropCount, kProps, kPropCount,
                           &length, params.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(0, params[0]);

    params.fill(1);
    index = glGetProgramResourceIndex(program, GL_PROGRAM_OUTPUT, "oColor");
    glGetProgramResourceiv(program, GL_PROGRAM_OUTPUT, index, kPropCount, kProps, kPropCount,
                           &length, params.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(0, params[0]);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "u_color");
    glGetProgramResourceiv(program, GL_UNIFORM, index, kPropCount, kProps, kPropCount, &length,
                           params.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(1, params[0]);

    params.fill(0);
    index = glGetProgramResourceIndex(program, GL_UNIFORM_BLOCK, "ubo1");
    glGetProgramResourceiv(program, GL_UNIFORM_BLOCK, index, kPropCount, kProps, kPropCount,
                           &length, params.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(1, params[0]);

    GLint maxGeometryShaderStorageBlocks = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT, &maxGeometryShaderStorageBlocks);
    // The maximum number of shader storage blocks in a geometry shader can be 0.
    // [EXT_geometry_shader] Table 20.43gs
    if (maxGeometryShaderStorageBlocks > 0)
    {
        const std::string &geometryShaderWithSSBO =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout (binding = 2) buffer ssbo
            {
                vec4 ssbo_value;
            } block0;
            layout (triangles) in;
            layout (points, max_vertices = 1) out;
            out vec4 gs_out;
            void main()
            {
                gl_Position = gl_in[0].gl_Position + block0.ssbo_value;
                gs_out = block0.ssbo_value;
                EmitVertex();
            })";

        ANGLE_GL_PROGRAM_WITH_GS(programWithSSBO, vertexShader, geometryShaderWithSSBO,
                                 fragmentShader);

        params.fill(0);
        index = glGetProgramResourceIndex(programWithSSBO, GL_SHADER_STORAGE_BLOCK, "ssbo");
        glGetProgramResourceiv(programWithSSBO, GL_SHADER_STORAGE_BLOCK, index, kPropCount, kProps,
                               kPropCount, &length, params.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(1, params[0]);

        params.fill(0);
        index = glGetProgramResourceIndex(programWithSSBO, GL_BUFFER_VARIABLE, "ssbo.ssbo_value");
        glGetProgramResourceiv(programWithSSBO, GL_BUFFER_VARIABLE, index, kPropCount, kProps,
                               kPropCount, &length, params.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(1, params[0]);
    }

    GLint maxGeometryAtomicCounterBuffers = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS_EXT, &maxGeometryAtomicCounterBuffers);
    // The maximum number of atomic counter buffers in a geometry shader can be 0.
    // [EXT_geometry_shader] Table 20.43gs
    if (maxGeometryAtomicCounterBuffers > 0)
    {
        const std::string &geometryShaderWithAtomicCounters =
            R"(#version 310 es
            #extension GL_EXT_geometry_shader : require
            layout(binding = 1, offset = 0) uniform atomic_uint gs_counter;
            layout (triangles) in;
            layout (points, max_vertices = 1) out;
            out vec4 gs_out;
            void main()
            {
                atomicCounterIncrement(gs_counter);
                gl_Position = gl_in[0].gl_Position;
                gs_out = vec4(1.0, 0.0, 0.0, 1.0);
                EmitVertex();
            })";

        ANGLE_GL_PROGRAM_WITH_GS(programWithAtomicCounter, vertexShader,
                                 geometryShaderWithAtomicCounters, fragmentShader);

        params.fill(0);
        index = glGetProgramResourceIndex(programWithAtomicCounter, GL_UNIFORM, "gs_counter");
        EXPECT_GL_NO_ERROR();
        glGetProgramResourceiv(programWithAtomicCounter, GL_ATOMIC_COUNTER_BUFFER, index,
                               kPropCount, kProps, kPropCount, &length, params.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(1, params[0]);
    }
}

// Verify correct errors can be reported when we use illegal parameters on FramebufferTextureEXT.
TEST_P(GeometryShaderTest, NegativeFramebufferTextureEXT)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_3D, tex);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // [EXT_geometry_shader] Section 9.2.8, "Attaching Texture Images to a Framebuffer"
    // An INVALID_ENUM error is generated if <target> is not DRAW_FRAMEBUFFER, READ_FRAMEBUFFER, or
    // FRAMEBUFFER.
    glFramebufferTextureEXT(GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, tex, 0);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    // An INVALID_ENUM error is generated if <attachment> is not one of the attachments in Table
    // 9.1.
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_TEXTURE_2D, tex, 0);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    // An INVALID_OPERATION error is generated if zero is bound to <target>.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // An INVALID_VALUE error is generated if <texture> is not the name of a texture object, or if
    // <level> is not a supported texture level for <texture>.
    GLuint tex2;
    glGenTextures(1, &tex2);
    glDeleteTextures(1, &tex2);
    ASSERT_FALSE(glIsTexture(tex2));
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2, 0);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    GLint max3DSize;
    glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DSize);
    GLint max3DLevel = std::log2(max3DSize);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, max3DLevel + 1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Verify CheckFramebufferStatus can work correctly on layered depth and stencil attachments.
TEST_P(GeometryShaderTest, LayeredFramebufferCompletenessWithDepthAttachment)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLint maxFramebufferLayers;
    glGetIntegerv(GL_MAX_FRAMEBUFFER_LAYERS_EXT, &maxFramebufferLayers);

    constexpr GLint kTexLayers = 2;
    ASSERT_LT(kTexLayers, maxFramebufferLayers);

    GLTexture layeredColorTex;
    glBindTexture(GL_TEXTURE_3D, layeredColorTex);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 32, 32, kTexLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 nullptr);

    // [EXT_geometry_shader] section 9.4.1, "Framebuffer Completeness"
    // If any framebuffer attachment is layered, all populated attachments must be layered.
    // {FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT }
    GLTexture layeredDepthStencilTex;
    glBindTexture(GL_TEXTURE_2D_ARRAY, layeredDepthStencilTex);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH24_STENCIL8, 32, 32, kTexLayers, 0,
                 GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);

    // 1. Color attachment is layered, while depth attachment is not layered.
    GLFramebuffer fbo1;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, layeredColorTex, 0);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, layeredDepthStencilTex, 0, 0);
    GLenum status1 = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, status1);

    // 2. Color attachment is not layered, while depth attachment is layered.
    GLFramebuffer fbo2;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, layeredColorTex, 0, 0);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, layeredDepthStencilTex, 0);
    GLenum status2 = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, status2);

    // 3. Color attachment is not layered, while stencil attachment is layered.
    GLFramebuffer fbo3;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo3);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, layeredColorTex, 0, 0);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, layeredDepthStencilTex, 0);
    GLenum status3 = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, status3);

    // [EXT_geometry_shader] section 9.4.1, "Framebuffer Completeness"
    // If <image> is a three-dimensional texture or a two-dimensional array texture and the
    // attachment is layered, the depth or layer count, respectively, of the texture is less than or
    // equal to the value of MAX_FRAMEBUFFER_LAYERS_EXT.
    GLint maxArrayTextureLayers;
    glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrayTextureLayers);
    GLint depthTexLayer4 = maxFramebufferLayers + 1;
    ANGLE_SKIP_TEST_IF(maxArrayTextureLayers < depthTexLayer4);

    // Use a depth attachment whose layer count exceeds MAX_FRAMEBUFFER_LAYERS
    GLTexture depthTex4;
    glBindTexture(GL_TEXTURE_2D_ARRAY, depthTex4);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH24_STENCIL8, 32, 32, depthTexLayer4, 0,
                 GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
    GLFramebuffer fbo4;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo4);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTex4, 0);
    GLenum status4 = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, status4);
}

// Verify correct errors can be reported when we use layered cube map attachments on a framebuffer.
TEST_P(GeometryShaderTest, NegativeLayeredFramebufferCompletenessWithCubeMapTextures)
{
    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));

    GLTexture tex;
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, status);

    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 nullptr);
    glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
    ASSERT_GL_NO_ERROR();
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, status);
}

ANGLE_INSTANTIATE_TEST(GeometryShaderTestES3, ES3_OPENGL(), ES3_OPENGLES(), ES3_D3D11());
ANGLE_INSTANTIATE_TEST(GeometryShaderTest, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11());
}  // namespace
