ES31: Add atomic memory functions
BUG=angleproject:1442
TEST=angle_unittests, angle_end2end_tests
dEQP-GLES31.functional.compute.shared_var.atomic*
dEQP-GLES31.functional.compute.basic.shared_atomic_op*
dEQP-GLES31.functional.ssbo.atomic.*
Change-Id: I82b54fde3a852d3bd917b1e19680baa1c28fce4d
Reviewed-on: https://chromium-review.googlesource.com/765061
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/compiler/translator/Initialize.cpp b/src/compiler/translator/Initialize.cpp
index aeb8d58..7283bdb 100644
--- a/src/compiler/translator/Initialize.cpp
+++ b/src/compiler/translator/Initialize.cpp
@@ -635,6 +635,26 @@
symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCounterIncrement", atomicCounter);
symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCounterDecrement", atomicCounter);
+ // Insert all atomic memory functions
+ const TType *int1InOut = TCache::getType(EbtInt, EvqInOut);
+ const TType *uint1InOut = TCache::getType(EbtUInt, EvqInOut);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicAdd", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicAdd", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicMin", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicMin", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicMax", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicMax", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicAnd", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicAnd", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicOr", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicOr", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicXor", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicXor", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicExchange", uint1InOut, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicExchange", int1InOut, int1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCompSwap", uint1InOut, uint1, uint1);
+ symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicCompSwap", int1InOut, int1, int1);
+
const TType *gimage2D = TCache::getType(EbtGImage2D);
const TType *gimage3D = TCache::getType(EbtGImage3D);
const TType *gimage2DArray = TCache::getType(EbtGImage2DArray);
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index ab0ece3..523228a 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -32,6 +32,22 @@
const int kWebGLMaxStructNesting = 4;
+const std::array<const char *, 8> kAtomicBuiltin = {{"atomicAdd", "atomicMin", "atomicMax",
+ "atomicAnd", "atomicOr", "atomicXor",
+ "atomicExchange", "atomicCompSwap"}};
+
+bool IsAtomicBuiltin(const TString &name)
+{
+ for (size_t i = 0; i < kAtomicBuiltin.size(); ++i)
+ {
+ if (name.compare(kAtomicBuiltin[i]) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
bool ContainsSampler(const TStructure *structType);
bool ContainsSampler(const TType &type)
@@ -116,6 +132,16 @@
}
}
+bool IsBufferOrSharedVariable(TIntermTyped *var)
+{
+ if (var->isInterfaceBlock() || var->getQualifier() == EvqBuffer ||
+ var->getQualifier() == EvqShared)
+ {
+ return true;
+ }
+ return false;
+}
+
} // namespace
// This tracks each binding point's current default offset for inheritance of subsequent
@@ -5592,6 +5618,35 @@
}
}
+void TParseContext::checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall)
+{
+ const TString &name = functionCall->getFunctionSymbolInfo()->getName();
+ if (IsAtomicBuiltin(name))
+ {
+ TIntermSequence *arguments = functionCall->getSequence();
+ TIntermTyped *memNode = (*arguments)[0]->getAsTyped();
+
+ if (IsBufferOrSharedVariable(memNode))
+ {
+ return;
+ }
+
+ while (memNode->getAsBinaryNode())
+ {
+ memNode = memNode->getAsBinaryNode()->getLeft();
+ if (IsBufferOrSharedVariable(memNode))
+ {
+ return;
+ }
+ }
+
+ error(memNode->getLine(),
+ "The value passed to the mem argument of an atomic memory function does not "
+ "correspond to a buffer or shared variable.",
+ functionCall->getFunctionSymbolInfo()->getName().c_str());
+ }
+}
+
// GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers
void TParseContext::checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall)
{
@@ -5828,6 +5883,7 @@
checkTextureOffsetConst(callNode);
checkTextureGather(callNode);
checkImageMemoryAccessForBuiltinFunctions(callNode);
+ checkAtomicMemoryBuiltinFunctions(callNode);
}
else
{
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index c611b31..e917db7 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -417,6 +417,7 @@
void checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall);
void checkImageMemoryAccessForUserDefinedFunctions(const TFunction *functionDefinition,
const TIntermAggregate *functionCall);
+ void checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall);
TIntermSequence *createEmptyArgumentsList();
// fnCall is only storing the built-in op, and function name or constructor type. arguments
diff --git a/src/tests/compiler_tests/ShaderValidation_test.cpp b/src/tests/compiler_tests/ShaderValidation_test.cpp
index b581a40..e3f587e 100644
--- a/src/tests/compiler_tests/ShaderValidation_test.cpp
+++ b/src/tests/compiler_tests/ShaderValidation_test.cpp
@@ -5325,3 +5325,232 @@
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
+
+// Test that the value passed to the mem argument of an atomic memory function can be a shared
+// variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariable)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(local_size_x = 5) in;
+ shared uint myShared;
+
+ void main() {
+ atomicAdd(myShared, 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass an element of an array to the mem argument of an atomic memory
+// function, as long as the underlying array is a buffer or shared variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariableArray)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(local_size_x = 5) in;
+ shared uint myShared[2];
+
+ void main() {
+ atomicAdd(myShared[0], 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass a single component of a vector to the mem argument of an
+// atomic memory function, as long as the underlying vector is a buffer or shared variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariableVector)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(local_size_x = 5) in;
+ shared uvec4 myShared;
+
+ void main() {
+ atomicAdd(myShared[0], 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that the value passed to the mem argument of an atomic memory function can be a buffer
+// variable.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariable)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(std140) buffer bufferName1{
+ uint u1;
+ };
+
+ void main()
+ {
+ atomicAdd(u1, 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass an element of an array to the mem argument of an atomic memory
+// function, as long as the underlying array is a buffer or shared variable.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableArrayElement)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(std140) buffer bufferName1{
+ uint u1[2];
+ };
+
+ void main()
+ {
+ atomicAdd(u1[0], 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass a member of a shader storage block instance to the mem
+// argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableInBlockInstance)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(std140) buffer bufferName{
+ uint u1;
+ } instanceName;
+
+ void main()
+ {
+ atomicAdd(instanceName.u1, 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass a member of a shader storage block instance array to the mem
+// argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableInBlockInstanceArray)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(std140) buffer bufferName{
+ uint u1;
+ } instanceName[1];
+
+ void main()
+ {
+ atomicAdd(instanceName[0].u1, 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is acceptable to pass an element of an array of a shader storage block instance to
+// the mem argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithElementOfArrayInBlockInstance)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(std140) buffer blockName {
+ uint data[2];
+ } instanceName;
+
+ void main()
+ {
+ atomicAdd(instanceName.data[0], 2u);
+ })";
+
+ if (!compile(shaderString))
+ {
+ FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+ }
+}
+
+// Test that it is not allowed to pass an atomic counter variable to the mem argument of an atomic
+// memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithAtomicCounter)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(binding = 0, offset = 4) uniform atomic_uint ac;
+
+ void main()
+ {
+ atomicAdd(ac, 2u);
+ })";
+
+ if (compile(shaderString))
+ {
+ FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+ }
+}
+
+// Test that it is not allowed to pass an element of an atomic counter array to the mem argument of
+// an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithAtomicCounterArray)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ layout(binding = 0, offset = 4) uniform atomic_uint ac[2];
+
+ void main()
+ {
+ atomicAdd(ac[0], 2u);
+ })";
+
+ if (compile(shaderString))
+ {
+ FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+ }
+}
+
+// Test that it is not allowed to pass a local uint value to the mem argument of an atomic memory
+// function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithNonStorageVariable)
+{
+ const std::string &shaderString =
+ R"(#version 310 es
+
+ void main()
+ {
+ uint test = 1u;
+ atomicAdd(test, 2u);
+ })";
+
+ if (compile(shaderString))
+ {
+ FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+ }
+}
diff --git a/src/tests/deqp_support/deqp_gles31_test_expectations.txt b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
index e13e35c..1c50f3b 100644
--- a/src/tests/deqp_support/deqp_gles31_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
@@ -80,6 +80,11 @@
1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_single_group = FAIL
1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_multiple_invocations = FAIL
1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_multiple_groups = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_invocation = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_group = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_invocations = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_groups = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.atomic.* = FAIL
1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.basic_type.* = FAIL
1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.work_group_size.* = FAIL
1442 D3D11 : dEQP-GLES31.functional.atomic_counter.* = FAIL
@@ -1176,10 +1181,6 @@
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.write_multiple_unsized_arr_multiple_groups = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.ssbo_cmd_barrier_single = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.ssbo_cmd_barrier_multiple = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_invocation = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_group = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_invocations = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_groups = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.copy_image_to_ssbo_large = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.copy_ssbo_to_image_large = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.image_barrier_single = FAIL
@@ -1188,7 +1189,6 @@
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_single_group = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_multiple_invocations = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_multiple_groups = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.shared_var.atomic.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.indirect_dispatch.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.ssbo.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.ubo.2_level_array.* = FAIL
diff --git a/src/tests/gl_tests/ShaderStorageBufferTest.cpp b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
index 01317bb..1d0babd 100644
--- a/src/tests/gl_tests/ShaderStorageBufferTest.cpp
+++ b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
@@ -134,6 +134,59 @@
EXPECT_GL_NO_ERROR();
}
+// Test atomic memory functions.
+TEST_P(ShaderStorageBufferTest31, AtomicMemoryFunctions)
+{
+ ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL() && IsWindows());
+ ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());
+
+ const std::string &csSource =
+ R"(#version 310 es
+
+ layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
+ layout(binding = 1) buffer blockName {
+ uint data[2];
+ } instanceName;
+
+ void main()
+ {
+ instanceName.data[0] = 0u;
+ instanceName.data[1] = 0u;
+ atomicAdd(instanceName.data[0], 5u);
+ atomicMax(instanceName.data[1], 7u);
+
+ })";
+
+ ANGLE_GL_COMPUTE_PROGRAM(program, csSource);
+
+ glUseProgram(program.get());
+
+ unsigned int bufferData[2] = {0u};
+ // Create shader storage buffer
+ GLBuffer shaderStorageBuffer;
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
+ glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(bufferData), nullptr, GL_STATIC_DRAW);
+
+ // Bind shader storage buffer
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, shaderStorageBuffer);
+
+ // Dispath compute
+ glDispatchCompute(1, 1, 1);
+
+ glFinish();
+
+ // Read back shader storage buffer
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
+ void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(bufferData), GL_MAP_READ_BIT);
+ memcpy(bufferData, ptr, sizeof(bufferData));
+ glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+ EXPECT_EQ(5u, bufferData[0]);
+ EXPECT_EQ(7u, bufferData[1]);
+
+ EXPECT_GL_NO_ERROR();
+}
+
ANGLE_INSTANTIATE_TEST(ShaderStorageBufferTest31, ES31_OPENGL(), ES31_OPENGLES());
} // namespace