//
// Copyright (c) 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.
//
// BufferVariables_test.cpp:
//   Tests for buffer variables in GLSL ES 3.10 section 4.3.7.
//

#include "gtest/gtest.h"

#include "GLSLANG/ShaderLang.h"
#include "angle_gl.h"
#include "gtest/gtest.h"
#include "tests/test_utils/ShaderCompileTreeTest.h"

using namespace sh;

class BufferVariablesTest : public ShaderCompileTreeTest
{
  public:
    BufferVariablesTest() {}

  protected:
    ::GLenum getShaderType() const override { return GL_VERTEX_SHADER; }
    ShShaderSpec getShaderSpec() const override { return SH_GLES3_1_SPEC; }
    void initResources(ShBuiltInResources *resources) override
    {
        resources->MaxShaderStorageBufferBindings = 8;
    }
};

// Test that the buffer qualifier described in GLSL ES 3.10 section 4.3.7 can be successfully
// compiled.
TEST_F(BufferVariablesTest, BasicShaderStorageBlockDeclaration)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    int b1;\n"
        "    buffer int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that shader storage block layout qualifiers can be declared for global scope.
TEST_F(BufferVariablesTest, LayoutQualifiersDeclaredInGlobal)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(shared, column_major) buffer;\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that shader storage block can be used with one or more memory qualifiers.
TEST_F(BufferVariablesTest, ShaderStorageBlockWithMemoryQualifier)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) writeonly buffer buf {\n"
        "    int b1;\n"
        "    buffer int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that buffer variables can be used with one or more memory qualifiers.
TEST_F(BufferVariablesTest, BufferVariablesWithMemoryQualifier)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly int b1;\n"
        "    writeonly buffer int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that it is a compile-time error to declare buffer variables at global scope (outside a
// block).
TEST_F(BufferVariablesTest, DeclareBufferVariableAtGlobal)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer int a;\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that the buffer variable can't be opaque type.
TEST_F(BufferVariablesTest, BufferVariableWithOpaqueType)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    int b1;\n"
        "    atomic_uint b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that the uniform variable can't be in shader storage block.
TEST_F(BufferVariablesTest, UniformVariableInShaderStorageBlock)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    uniform int a;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that buffer qualifier is not supported in verson lower than GLSL ES 3.10.
TEST_F(BufferVariablesTest, BufferQualifierInESSL3)
{
    const std::string &source =
        "#version 300 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    int b1;\n"
        "    buffer int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that can't assign to a readonly buffer variable.
TEST_F(BufferVariablesTest, AssignToReadonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    readonly int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    b1 = 5;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that can't assign to a buffer variable declared within shader storage block with readonly.
TEST_F(BufferVariablesTest, AssignToBufferVariableWithinReadonlyBlock)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) readonly buffer buf {\n"
        "    int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    b1 = 5;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that can't assign to a readonly buffer variable through an instance name.
TEST_F(BufferVariablesTest, AssignToReadonlyBufferVariableByInstanceName)
{
    const std::string &source =
        R"(#version 310 es
        layout(binding = 3) buffer buf {
            readonly float f;
        } instanceBuffer;
        void main()
        {
            instanceBuffer.f += 0.2;
        })";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that can't assign to a readonly struct buffer variable.
TEST_F(BufferVariablesTest, AssignToReadonlyStructBufferVariable)
{
    const std::string &source =
        R"(#version 310 es
        struct S {
            float f;
        };
        layout(binding = 3) buffer buf {
            readonly S s;
        };
        void main()
        {
            s.f += 0.2;
        })";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that can't assign to a readonly struct buffer variable through an instance name.
TEST_F(BufferVariablesTest, AssignToReadonlyStructBufferVariableByInstanceName)
{
    const std::string &source =
        R"(#version 310 es
        struct S {
            float f;
        };
        layout(binding = 3) buffer buf {
            readonly S s;
        } instanceBuffer;
        void main()
        {
            instanceBuffer.s.f += 0.2;
        })";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that a readonly and writeonly buffer variable should neither read or write.
TEST_F(BufferVariablesTest, AccessReadonlyWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    readonly writeonly int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    b1 = 5;\n"
        "    int test = b1;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that accessing a writeonly buffer variable should be error.
TEST_F(BufferVariablesTest, AccessWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    int test = b1;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that accessing a buffer variable through an instance name is ok.
TEST_F(BufferVariablesTest, AccessReadonlyBufferVariableByInstanceName)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    readonly float f;\n"
        "} instanceBuffer;\n"
        "void main()\n"
        "{\n"
        "    gl_Position.x = instanceBuffer.f;\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that accessing a buffer variable through an instance name inherits the writeonly qualifier
// and generates errors.
TEST_F(BufferVariablesTest, AccessWriteonlyBufferVariableByInstanceName)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) writeonly buffer buf {\n"
        "    float f;\n"
        "} instanceBuffer;\n"
        "void main()\n"
        "{\n"
        "    float test = instanceBuffer.f;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as the argument of a unary operator should be error.
TEST_F(BufferVariablesTest, UnaryOperatorWithWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    ++b1;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable on the left-hand side of compound assignment should be error.
TEST_F(BufferVariablesTest, CompoundAssignmentToWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly int b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    b1 += 5;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as ternary op argument should be error.
TEST_F(BufferVariablesTest, TernarySelectionWithWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly bool b1;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    int test = b1 ? 1 : 0;\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as array constructor argument should be error.
TEST_F(BufferVariablesTest, ArrayConstructorWithWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly float f;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    float a[3] = float[3](f, f, f);\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as structure constructor argument should be error.
TEST_F(BufferVariablesTest, StructureConstructorWithWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "struct S {\n"
        "    int a;\n"
        "};\n"
        "struct T {\n"
        "    S b;\n"
        "};\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly S c;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    T t = T(c);\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as built-in function argument should be error.
TEST_F(BufferVariablesTest, BuildInFunctionWithWriteonlyBufferVariable)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly int a;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "    int test = min(a, 1);\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that readonly buffer variable as user-defined function in argument should be ok.
TEST_F(BufferVariablesTest, UserDefinedFunctionWithReadonlyBufferVariableInArgument)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    readonly float f;\n"
        "};\n"
        "void foo(float a) {}\n"
        "void main()\n"
        "{\n"
        "    foo(f);\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as user-defined function in argument should be error.
TEST_F(BufferVariablesTest, UserDefinedFunctionWithWriteonlyBufferVariableInArgument)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly float f;\n"
        "};\n"
        "void foo(float a) {}\n"
        "void main()\n"
        "{\n"
        "    foo(f);\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that writeonly buffer variable as user-defined function out argument should be ok.
TEST_F(BufferVariablesTest, UserDefinedFunctionWithWriteonlyBufferVariableOutArgument)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    writeonly float f;\n"
        "};\n"
        "void foo(out float a) {}\n"
        "void main()\n"
        "{\n"
        "    foo(f);\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that readonly buffer variable as user-defined function out argument should be error.
TEST_F(BufferVariablesTest, UserDefinedFunctionWithReadonlyBufferVariableOutArgument)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(binding = 3) buffer buf {\n"
        "    readonly float f;\n"
        "};\n"
        "void foo(out float a) {}\n"
        "void main()\n"
        "{\n"
        "    foo(f);\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that buffer qualifier can't modify a function parameter.
TEST_F(BufferVariablesTest, BufferQualifierOnFunctionParameter)
{
    const std::string &source =
        "#version 310 es\n"
        "void foo(buffer float a) {}\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that std430 qualifier is supported for shader storage blocks.
TEST_F(BufferVariablesTest, ShaderStorageBlockWithStd430)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(std430) buffer buf {\n"
        "    int b1;\n"
        "    int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that using std430 qualifier on a uniform block will fail to compile.
TEST_F(BufferVariablesTest, UniformBlockWithStd430)
{
    const std::string &source =
        "#version 310 es\n"
        "layout(std430) uniform buf {\n"
        "    int b1;\n"
        "    int b2;\n"
        "};\n"
        "void main()\n"
        "{\n"
        "}\n";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that indexing a runtime-sized array with a positive index compiles.
TEST_F(BufferVariablesTest, IndexRuntimeSizedArray)
{
    const std::string &source =
        R"(#version 310 es

        layout(std430) buffer buf
        {
            int arr[];
        };

        void main()
        {
            arr[100];
        })";
    if (!compile(source))
    {
        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
    }
}

// Test that indexing a runtime-sized array with a negative constant index does not compile.
TEST_F(BufferVariablesTest, IndexRuntimeSizedArrayWithNegativeIndex)
{
    const std::string &source =
        R"(#version 310 es

        layout(std430) buffer buf
        {
            int arr[];
        };

        void main()
        {
            arr[-1];
        })";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}

// Test that only the last member of a buffer can be runtime-sized.
TEST_F(BufferVariablesTest, RuntimeSizedVariableInNotLastInBuffer)
{
    const std::string &source =
        R"(#version 310 es

        layout(std430) buffer buf
        {
            int arr[];
            int i;
        };

        void main()
        {
        })";
    if (compile(source))
    {
        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
    }
}
