ES31: Add atomic counter for GLSL parsing
This makes shader compiler support the new basic type 'atomic_uint'
and validate its layout qualifiers properly.
BUG=angleproject:1729
TEST=angle_unittests:AtomicCounterTest
angle_deqp_gles31_tests:dEQP-GLES31.functional.atomic_counter.layout.invalid*
angle_deqp_gles31_tests:dEQP-GLES31.functional.debug.negative_coverage.*.atomic*
Change-Id: Ia237eadf6ea72314f436a0abbb93a05598e71eba
Reviewed-on: https://chromium-review.googlesource.com/500088
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 9b84a3f..f3bd179 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -9,11 +9,12 @@
#include <stdarg.h>
#include <stdio.h>
+#include "common/mathutil.h"
#include "compiler/preprocessor/SourceLocation.h"
#include "compiler/translator/Cache.h"
-#include "compiler/translator/glslang.h"
-#include "compiler/translator/ValidateSwitch.h"
#include "compiler/translator/ValidateGlobalInitializer.h"
+#include "compiler/translator/ValidateSwitch.h"
+#include "compiler/translator/glslang.h"
#include "compiler/translator/util.h"
namespace sh
@@ -68,6 +69,38 @@
} // namespace
+// This tracks each binding point's current default offset for inheritance of subsequent
+// variables using the same binding, and keeps offsets unique and non overlapping.
+// See GLSL ES 3.1, section 4.4.6.
+class TParseContext::AtomicCounterBindingState
+{
+ public:
+ AtomicCounterBindingState() : mDefaultOffset(0) {}
+ // Inserts a new span and returns -1 if overlapping, else returns the starting offset of
+ // newly inserted span.
+ int insertSpan(int start, size_t length)
+ {
+ gl::RangeI newSpan(start, start + static_cast<int>(length));
+ for (const auto &span : mSpans)
+ {
+ if (newSpan.intersects(span))
+ {
+ return -1;
+ }
+ }
+ mSpans.push_back(newSpan);
+ mDefaultOffset = newSpan.high();
+ return start;
+ }
+ // Inserts a new span starting from the default offset.
+ int appendSpan(size_t length) { return insertSpan(mDefaultOffset, length); }
+ void setDefaultOffset(int offset) { mDefaultOffset = offset; }
+
+ private:
+ int mDefaultOffset;
+ std::vector<gl::RangeI> mSpans;
+};
+
TParseContext::TParseContext(TSymbolTable &symt,
TExtensionBehavior &ext,
sh::GLenum type,
@@ -114,11 +147,16 @@
mMaxCombinedTextureImageUnits(resources.MaxCombinedTextureImageUnits),
mMaxUniformLocations(resources.MaxUniformLocations),
mMaxUniformBufferBindings(resources.MaxUniformBufferBindings),
+ mMaxAtomicCounterBindings(resources.MaxAtomicCounterBindings),
mDeclaringFunction(false)
{
mComputeShaderLocalSize.fill(-1);
}
+TParseContext::~TParseContext()
+{
+}
+
//
// Look at a '.' field selector string and change it into offsets
// for a vector.
@@ -1107,6 +1145,24 @@
}
}
+void TParseContext::atomicCounterQualifierErrorCheck(const TPublicType &publicType,
+ const TSourceLoc &location)
+{
+ if (publicType.precision != EbpHigh)
+ {
+ error(location, "Can only be highp", "atomic counter");
+ }
+ // dEQP enforces compile error if location is specified. See uniform_location.test.
+ if (publicType.layoutQualifier.location != -1)
+ {
+ error(location, "location must not be set for atomic_uint", "layout");
+ }
+ if (publicType.layoutQualifier.binding == -1)
+ {
+ error(location, "no binding specified", "atomic counter");
+ }
+}
+
void TParseContext::emptyDeclarationErrorCheck(const TPublicType &publicType,
const TSourceLoc &location)
{
@@ -1244,9 +1300,17 @@
else
{
checkInternalFormatIsNotSpecified(identifierLocation, layoutQualifier.imageInternalFormat);
-
checkMemoryQualifierIsNotSpecified(publicType.memoryQualifier, identifierLocation);
}
+
+ if (IsAtomicCounter(publicType.getBasicType()))
+ {
+ atomicCounterQualifierErrorCheck(publicType, identifierLocation);
+ }
+ else
+ {
+ checkOffsetIsNotSpecified(identifierLocation, layoutQualifier.offset);
+ }
}
void TParseContext::checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type)
@@ -1261,6 +1325,10 @@
{
checkSamplerBindingIsValid(identifierLocation, layoutQualifier.binding, arraySize);
}
+ else if (IsAtomicCounter(type.getBasicType()))
+ {
+ checkAtomicCounterBindingIsValid(identifierLocation, layoutQualifier.binding);
+ }
else
{
ASSERT(!IsOpaqueType(type.getBasicType()));
@@ -1318,6 +1386,15 @@
}
}
+void TParseContext::checkOffsetIsNotSpecified(const TSourceLoc &location, int offset)
+{
+ if (offset != -1)
+ {
+ error(location, "invalid layout qualifier: only valid when used with atomic counters",
+ "offset");
+ }
+}
+
void TParseContext::checkImageBindingIsValid(const TSourceLoc &location, int binding, int arraySize)
{
// Expects arraySize to be 1 when setting binding for only a single variable.
@@ -1347,6 +1424,14 @@
"binding");
}
}
+void TParseContext::checkAtomicCounterBindingIsValid(const TSourceLoc &location, int binding)
+{
+ if (binding >= mMaxAtomicCounterBindings)
+ {
+ error(location, "atomic counter binding greater than gl_MaxAtomicCounterBindings",
+ "binding");
+ }
+}
void TParseContext::checkUniformLocationInRange(const TSourceLoc &location,
int objectLocationCount,
@@ -1909,6 +1994,34 @@
}
}
+// Make sure there is no offset overlapping, and store the newly assigned offset to "type" in
+// intermediate tree.
+void TParseContext::checkAtomicCounterOffsetIsNotOverlapped(TPublicType &publicType,
+ size_t size,
+ bool forceAppend,
+ const TSourceLoc &loc,
+ TType &type)
+{
+ auto &bindingState = mAtomicCounterBindingStates[publicType.layoutQualifier.binding];
+ int offset;
+ if (publicType.layoutQualifier.offset == -1 || forceAppend)
+ {
+ offset = bindingState.appendSpan(size);
+ }
+ else
+ {
+ offset = bindingState.insertSpan(publicType.layoutQualifier.offset, size);
+ }
+ if (offset == -1)
+ {
+ error(loc, "Offset overlapping", "atomic counter");
+ return;
+ }
+ TLayoutQualifier qualifier = type.getLayoutQualifier();
+ qualifier.offset = offset;
+ type.setLayoutQualifier(qualifier);
+}
+
TIntermDeclaration *TParseContext::parseSingleDeclaration(
TPublicType &publicType,
const TSourceLoc &identifierOrTypeLocation,
@@ -1958,6 +2071,10 @@
{
symbol = intermediate.addSymbol(0, "", type, identifierOrTypeLocation);
}
+ else if (IsAtomicCounter(publicType.getBasicType()))
+ {
+ setAtomicCounterBindingDefaultOffset(publicType, identifierOrTypeLocation);
+ }
}
else
{
@@ -1965,6 +2082,13 @@
checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &publicType);
+ if (IsAtomicCounter(publicType.getBasicType()))
+ {
+
+ checkAtomicCounterOffsetIsNotOverlapped(publicType, kAtomicCounterSize, false,
+ identifierOrTypeLocation, type);
+ }
+
TVariable *variable = nullptr;
declareVariable(identifierOrTypeLocation, identifier, type, &variable);
@@ -2008,6 +2132,12 @@
// This ensures useless error messages regarding the variable's non-arrayness won't follow.
arrayType.setArraySize(size);
+ if (IsAtomicCounter(publicType.getBasicType()))
+ {
+ checkAtomicCounterOffsetIsNotOverlapped(publicType, kAtomicCounterArrayStride * size, false,
+ identifierLocation, arrayType);
+ }
+
TVariable *variable = nullptr;
declareVariable(identifierLocation, identifier, arrayType, &variable);
@@ -2170,6 +2300,11 @@
TVariable *variable = nullptr;
TType type(publicType);
+ if (IsAtomicCounter(publicType.getBasicType()))
+ {
+ checkAtomicCounterOffsetIsNotOverlapped(publicType, kAtomicCounterSize, true,
+ identifierLocation, type);
+ }
declareVariable(identifierLocation, identifier, type, &variable);
TIntermSymbol *symbol = intermediate.addSymbol(0, identifier, type, identifierLocation);
@@ -2205,6 +2340,12 @@
unsigned int size = checkIsValidArraySize(arrayLocation, indexExpression);
arrayType.setArraySize(size);
+ if (IsAtomicCounter(publicType.getBasicType()))
+ {
+ checkAtomicCounterOffsetIsNotOverlapped(publicType, kAtomicCounterArrayStride * size,
+ true, identifierLocation, arrayType);
+ }
+
TVariable *variable = nullptr;
declareVariable(identifierLocation, identifier, arrayType, &variable);
@@ -2292,6 +2433,19 @@
}
}
+void TParseContext::setAtomicCounterBindingDefaultOffset(const TPublicType &publicType,
+ const TSourceLoc &location)
+{
+ const TLayoutQualifier &layoutQualifier = publicType.layoutQualifier;
+ checkAtomicCounterBindingIsValid(location, layoutQualifier.binding);
+ if (layoutQualifier.binding == -1 || layoutQualifier.offset == -1)
+ {
+ error(location, "Requires both binding and offset", "layout");
+ return;
+ }
+ mAtomicCounterBindingStates[layoutQualifier.binding].setDefaultOffset(layoutQualifier.offset);
+}
+
void TParseContext::parseGlobalLayoutQualifier(const TTypeQualifierBuilder &typeQualifierBuilder)
{
TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
@@ -2321,6 +2475,8 @@
checkYuvIsNotSpecified(typeQualifier.line, layoutQualifier.yuv);
+ checkOffsetIsNotSpecified(typeQualifier.line, layoutQualifier.offset);
+
if (typeQualifier.qualifier == EvqComputeIn)
{
if (mComputeShaderLocalSizeDeclared &&
@@ -3423,6 +3579,19 @@
qualifier.binding = intValue;
}
}
+ else if (qualifierType == "offset")
+ {
+ checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
+ if (intValue < 0)
+ {
+ error(intValueLine, "out of range: offset must be non-negative",
+ intValueString.c_str());
+ }
+ else
+ {
+ qualifier.offset = intValue;
+ }
+ }
else if (qualifierType == "local_size_x")
{
parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 0u,
@@ -3586,7 +3755,8 @@
{
error(field.line(), "invalid qualifier on struct member", "invariant");
}
- if (IsImage(field.type()->getBasicType()))
+ // ESSL 3.10 section 4.1.8 -- atomic_uint or images are not allowed as structure member.
+ if (IsImage(field.type()->getBasicType()) || IsAtomicCounter(field.type()->getBasicType()))
{
error(field.line(), "disallowed type in struct", field.type()->getBasicString());
}