//
// Copyright (c) 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 "angle_gl.h"
#include "gtest/gtest.h"
#include "GLSLANG/ShaderLang.h"
#include "compiler/translator/TranslatorGLSL.h"

#define EXPECT_GLENUM_EQ(expected, actual) \
    EXPECT_EQ(static_cast<GLenum>(expected), static_cast<GLenum>(actual))

class CollectVariablesTest : public testing::Test
{
  public:
    CollectVariablesTest(GLenum shaderType)
        : mShaderType(shaderType)
    {}

  protected:
    virtual void SetUp()
    {
        ShBuiltInResources resources;
        ShInitBuiltInResources(&resources);
        resources.MaxDrawBuffers = 8;

        mTranslator = new TranslatorGLSL(mShaderType, SH_GLES2_SPEC);
        ASSERT_TRUE(mTranslator->Init(resources));
    }

    virtual void TearDown()
    {
        delete mTranslator;
    }

    GLenum mShaderType;
    TranslatorGLSL *mTranslator;
};

class CollectVertexVariablesTest : public CollectVariablesTest
{
  public:
    CollectVertexVariablesTest() : CollectVariablesTest(GL_VERTEX_SHADER) {}
};

class CollectFragmentVariablesTest : public CollectVariablesTest
{
  public:
      CollectFragmentVariablesTest() : CollectVariablesTest(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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::Attribute> &outputVariables = mTranslator->getOutputVariables();
    ASSERT_EQ(1u, outputVariables.size());

    const sh::Attribute &outputVariable = outputVariables[0];

    EXPECT_EQ(0u, outputVariable.arraySize);
    EXPECT_EQ(-1, outputVariable.location);
    EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
    EXPECT_TRUE(outputVariable.staticUse);
    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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::Attribute> &outputVariables = mTranslator->getOutputVariables();
    ASSERT_EQ(1u, outputVariables.size());

    const sh::Attribute &outputVariable = outputVariables[0];

    EXPECT_EQ(0u, outputVariable.arraySize);
    EXPECT_EQ(5, outputVariable.location);
    EXPECT_GLENUM_EQ(GL_MEDIUM_FLOAT, outputVariable.precision);
    EXPECT_TRUE(outputVariable.staticUse);
    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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::Attribute> &attributes = mTranslator->getAttributes();
    ASSERT_EQ(1u, attributes.size());

    const sh::Attribute &attribute = attributes[0];

    EXPECT_EQ(0u, attribute.arraySize);
    EXPECT_EQ(5, attribute.location);
    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, attribute.precision);
    EXPECT_TRUE(attribute.staticUse);
    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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
    ASSERT_EQ(1u, interfaceBlocks.size());

    const sh::InterfaceBlock &interfaceBlock = interfaceBlocks[0];

    EXPECT_EQ(0u, interfaceBlock.arraySize);
    EXPECT_FALSE(interfaceBlock.isRowMajorLayout);
    EXPECT_EQ(sh::BLOCKLAYOUT_SHARED, interfaceBlock.layout);
    EXPECT_EQ("b", interfaceBlock.name);
    EXPECT_TRUE(interfaceBlock.staticUse);

    ASSERT_EQ(1u, interfaceBlock.fields.size());

    const sh::InterfaceBlockField &field = interfaceBlock.fields[0];

    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
    EXPECT_TRUE(field.staticUse);
    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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
    ASSERT_EQ(1u, interfaceBlocks.size());

    const sh::InterfaceBlock &interfaceBlock = interfaceBlocks[0];

    EXPECT_EQ(0u, interfaceBlock.arraySize);
    EXPECT_FALSE(interfaceBlock.isRowMajorLayout);
    EXPECT_EQ(sh::BLOCKLAYOUT_SHARED, interfaceBlock.layout);
    EXPECT_EQ("b", interfaceBlock.name);
    EXPECT_TRUE(interfaceBlock.staticUse);

    ASSERT_EQ(1u, interfaceBlock.fields.size());

    const sh::InterfaceBlockField &field = interfaceBlock.fields[0];

    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, field.precision);
    EXPECT_TRUE(field.staticUse);
    EXPECT_GLENUM_EQ(GL_FLOAT, field.type);
    EXPECT_EQ("b.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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
    ASSERT_EQ(1u, interfaceBlocks.size());

    const sh::InterfaceBlock &interfaceBlock = interfaceBlocks[0];

    EXPECT_EQ(0u, interfaceBlock.arraySize);
    EXPECT_FALSE(interfaceBlock.isRowMajorLayout);
    EXPECT_EQ(sh::BLOCKLAYOUT_SHARED, interfaceBlock.layout);
    EXPECT_EQ("b", interfaceBlock.name);
    EXPECT_TRUE(interfaceBlock.staticUse);

    ASSERT_EQ(1u, interfaceBlock.fields.size());

    const sh::InterfaceBlockField &field = interfaceBlock.fields[0];

    EXPECT_TRUE(field.isStruct());
    EXPECT_TRUE(field.staticUse);
    EXPECT_EQ("s", field.name);
    EXPECT_FALSE(field.isRowMajorLayout);

    const sh::ShaderVariable &member = field.fields[0];

    // NOTE: we don't currently mark struct members as statically used or not
    EXPECT_FALSE(member.isStruct());
    EXPECT_EQ("f", member.name);
    EXPECT_GLENUM_EQ(GL_FLOAT, member.type);
    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
    ASSERT_EQ(1u, interfaceBlocks.size());

    const sh::InterfaceBlock &interfaceBlock = interfaceBlocks[0];

    EXPECT_EQ(0u, interfaceBlock.arraySize);
    EXPECT_FALSE(interfaceBlock.isRowMajorLayout);
    EXPECT_EQ(sh::BLOCKLAYOUT_SHARED, interfaceBlock.layout);
    EXPECT_EQ("b", interfaceBlock.name);
    EXPECT_TRUE(interfaceBlock.staticUse);

    ASSERT_EQ(1u, interfaceBlock.fields.size());

    const sh::InterfaceBlockField &field = interfaceBlock.fields[0];

    EXPECT_TRUE(field.isStruct());
    EXPECT_TRUE(field.staticUse);
    EXPECT_EQ("b.s", field.name);
    EXPECT_FALSE(field.isRowMajorLayout);

    const sh::ShaderVariable &member = field.fields[0];

    // NOTE: we don't currently mark struct members as statically used or not
    EXPECT_FALSE(member.isStruct());
    EXPECT_EQ("f", member.name);
    EXPECT_GLENUM_EQ(GL_FLOAT, member.type);
    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.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";

    const char *shaderStrings[] = { shaderString.c_str() };
    ASSERT_TRUE(mTranslator->compile(shaderStrings, 1, SH_VARIABLES));

    const std::vector<sh::InterfaceBlock> &interfaceBlocks = mTranslator->getInterfaceBlocks();
    ASSERT_EQ(1u, interfaceBlocks.size());

    const sh::InterfaceBlock &interfaceBlock = interfaceBlocks[0];

    EXPECT_EQ(0u, interfaceBlock.arraySize);
    EXPECT_TRUE(interfaceBlock.isRowMajorLayout);
    EXPECT_EQ(sh::BLOCKLAYOUT_SHARED, interfaceBlock.layout);
    EXPECT_EQ("b", interfaceBlock.name);
    EXPECT_TRUE(interfaceBlock.staticUse);

    ASSERT_EQ(1u, interfaceBlock.fields.size());

    const sh::InterfaceBlockField &field = interfaceBlock.fields[0];

    EXPECT_TRUE(field.isStruct());
    EXPECT_TRUE(field.staticUse);
    EXPECT_EQ("s", field.name);
    EXPECT_TRUE(field.isRowMajorLayout);

    const sh::ShaderVariable &member = field.fields[0];

    // NOTE: we don't currently mark struct members as statically used or not
    EXPECT_FALSE(member.isStruct());
    EXPECT_EQ("m", member.name);
    EXPECT_GLENUM_EQ(GL_FLOAT_MAT2, member.type);
    EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, member.precision);
}
