| // | |
| // 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. | |
| // | |
| // 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" | |
| #include "tests/test_utils/compiler_test.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; | |
| } | |
| }; | |
| class BufferVariablesMatchTest : public MatchOutputCodeTest | |
| { | |
| public: | |
| BufferVariablesMatchTest() : MatchOutputCodeTest(GL_VERTEX_SHADER, SH_ESSL_OUTPUT) | |
| { | |
| getResources()->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; | |
| } | |
| } | |
| // Test that memory qualifiers are output. | |
| TEST_F(BufferVariablesMatchTest, MemoryQualifiers) | |
| { | |
| const std::string &source = | |
| R"(#version 310 es | |
| layout(std430) coherent buffer buf | |
| { | |
| int defaultCoherent; | |
| coherent ivec2 specifiedCoherent; | |
| volatile ivec3 specifiedVolatile; | |
| restrict ivec4 specifiedRestrict; | |
| readonly float specifiedReadOnly; | |
| writeonly vec2 specifiedWriteOnly; | |
| volatile readonly vec3 specifiedMultiple; | |
| }; | |
| void main() | |
| { | |
| })"; | |
| compile(source); | |
| ASSERT_TRUE(foundInESSLCode("coherent highp int")); | |
| ASSERT_TRUE(foundInESSLCode("coherent highp ivec2")); | |
| ASSERT_TRUE(foundInESSLCode("coherent volatile highp ivec3")); | |
| ASSERT_TRUE(foundInESSLCode("coherent restrict highp ivec4")); | |
| ASSERT_TRUE(foundInESSLCode("readonly coherent highp float")); | |
| ASSERT_TRUE(foundInESSLCode("writeonly coherent highp vec2")); | |
| ASSERT_TRUE(foundInESSLCode("readonly coherent volatile highp vec3")); | |
| } |