Commit ebee5b3b by Olli Etuaho Committed by Commit Bot

Add GLSL support for runtime-sized arrays in SSBOs

The GLSL parser now allows a runtime-sized array as the last member in a shader storage block. Clamping indexing against the memory bounds is done by determining the array length at runtime. Runtime-sized arrays are used in dEQP tests for many compute shader tests, so these now work on the OpenGL backend. BUG=angleproject:1951 TEST=angle_unittests, dEQP-GLES31.functional.shaders.linkage.shader_storage_block.* dEQP-GLES31.functional.shaders.builtin_functions.*compute* Change-Id: Ibecca24623ca8e4723af6f0e0421fe9711ea828d Reviewed-on: https://chromium-review.googlesource.com/787976 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent cddcb59e
...@@ -1390,7 +1390,8 @@ TIntermTyped *TIntermUnary::fold(TDiagnostics *diagnostics) ...@@ -1390,7 +1390,8 @@ TIntermTyped *TIntermUnary::fold(TDiagnostics *diagnostics)
if (mOp == EOpArrayLength) if (mOp == EOpArrayLength)
{ {
if (mOperand->hasSideEffects()) // The size of runtime-sized arrays may only be determined at runtime.
if (mOperand->hasSideEffects() || mOperand->getType().isUnsizedArray())
{ {
return this; return this;
} }
......
...@@ -526,24 +526,42 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary *node) ...@@ -526,24 +526,42 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary *node)
} }
else if (visit == PostVisit) else if (visit == PostVisit)
{ {
int maxSize;
TIntermTyped *left = node->getLeft(); TIntermTyped *left = node->getLeft();
TType leftType = left->getType(); TType leftType = left->getType();
if (left->isArray()) if (mClampingStrategy == SH_CLAMP_WITH_CLAMP_INTRINSIC)
out << "), 0.0, float(";
else
out << ", 0, ";
if (leftType.isUnsizedArray())
{ {
// The shader will fail validation if the array length is not > 0. // For runtime-sized arrays in ESSL 3.10 we need to call the length method
maxSize = static_cast<int>(leftType.getOutermostArraySize()) - 1; // to get the length to clamp against. See ESSL 3.10 section 4.1.9. Note
// that a runtime-sized array expression is guaranteed not to have side
// effects, so it's fine to add the expression to the output twice.
ASSERT(mShaderVersion >= 310);
ASSERT(!left->hasSideEffects());
left->traverse(this);
out << ".length() - 1";
} }
else else
{ {
maxSize = leftType.getNominalSize() - 1; int maxSize;
if (leftType.isArray())
{
maxSize = static_cast<int>(leftType.getOutermostArraySize()) - 1;
}
else
{
maxSize = leftType.getNominalSize() - 1;
}
out << maxSize;
} }
if (mClampingStrategy == SH_CLAMP_WITH_CLAMP_INTRINSIC) if (mClampingStrategy == SH_CLAMP_WITH_CLAMP_INTRINSIC)
out << "), 0.0, float(" << maxSize << ")))]"; out << ")))]";
else else
out << ", 0, " << maxSize << ")]"; out << ")]";
} }
} }
else else
......
...@@ -3785,6 +3785,15 @@ TIntermDeclaration *TParseContext::addInterfaceBlock( ...@@ -3785,6 +3785,15 @@ TIntermDeclaration *TParseContext::addInterfaceBlock(
fieldType->setLayoutQualifier(fieldLayoutQualifier); fieldType->setLayoutQualifier(fieldLayoutQualifier);
if (mShaderVersion < 310 || memberIndex != fieldList->size() - 1u ||
typeQualifier.qualifier != EvqBuffer)
{
// ESSL 3.10 spec section 4.1.9 allows for runtime-sized arrays.
checkIsNotUnsizedArray(field->line(),
"array members of interface blocks must specify a size",
field->name().c_str(), field->type());
}
if (typeQualifier.qualifier == EvqBuffer) if (typeQualifier.qualifier == EvqBuffer)
{ {
// set memory qualifiers // set memory qualifiers
...@@ -4007,85 +4016,88 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression, ...@@ -4007,85 +4016,88 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression,
int safeIndex = -1; int safeIndex = -1;
if (baseExpression->isArray()) if (index < 0)
{
outOfRangeError(outOfRangeIndexIsError, location, "index expression is negative", "[]");
safeIndex = 0;
}
if (!baseExpression->getType().isUnsizedArray())
{ {
if (baseExpression->getQualifier() == EvqFragData && index > 0) if (baseExpression->isArray())
{ {
if (!isExtensionEnabled(TExtension::EXT_draw_buffers)) if (baseExpression->getQualifier() == EvqFragData && index > 0)
{
if (!isExtensionEnabled(TExtension::EXT_draw_buffers))
{
outOfRangeError(outOfRangeIndexIsError, location,
"array index for gl_FragData must be zero when "
"GL_EXT_draw_buffers is disabled",
"[]");
safeIndex = 0;
}
}
// Only do generic out-of-range check if similar error hasn't already been reported.
if (safeIndex < 0)
{ {
outOfRangeError(outOfRangeIndexIsError, location, safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
"array index for gl_FragData must be zero when " baseExpression->getOutermostArraySize(),
"GL_EXT_draw_buffers is disabled", "array index out of range");
"[");
safeIndex = 0;
} }
} }
// Only do generic out-of-range check if similar error hasn't already been reported. else if (baseExpression->isMatrix())
if (safeIndex < 0)
{ {
safeIndex = checkIndexOutOfRange(outOfRangeIndexIsError, location, index, safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
baseExpression->getOutermostArraySize(), baseExpression->getType().getCols(),
"array index out of range"); "matrix field selection out of range");
}
else if (baseExpression->isVector())
{
safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
baseExpression->getType().getNominalSize(),
"vector field selection out of range");
} }
}
else if (baseExpression->isMatrix())
{
safeIndex = checkIndexOutOfRange(outOfRangeIndexIsError, location, index,
baseExpression->getType().getCols(),
"matrix field selection out of range");
}
else if (baseExpression->isVector())
{
safeIndex = checkIndexOutOfRange(outOfRangeIndexIsError, location, index,
baseExpression->getType().getNominalSize(),
"vector field selection out of range");
}
ASSERT(safeIndex >= 0); ASSERT(safeIndex >= 0);
// Data of constant unions can't be changed, because it may be shared with other // Data of constant unions can't be changed, because it may be shared with other
// constant unions or even builtins, like gl_MaxDrawBuffers. Instead use a new // constant unions or even builtins, like gl_MaxDrawBuffers. Instead use a new
// sanitized object. // sanitized object.
if (safeIndex != index || indexConstantUnion->getBasicType() != EbtInt) if (safeIndex != index || indexConstantUnion->getBasicType() != EbtInt)
{ {
TConstantUnion *safeConstantUnion = new TConstantUnion(); TConstantUnion *safeConstantUnion = new TConstantUnion();
safeConstantUnion->setIConst(safeIndex); safeConstantUnion->setIConst(safeIndex);
indexConstantUnion->replaceConstantUnion(safeConstantUnion); indexConstantUnion->replaceConstantUnion(safeConstantUnion);
indexConstantUnion->getTypePointer()->setBasicType(EbtInt); indexConstantUnion->getTypePointer()->setBasicType(EbtInt);
} }
TIntermBinary *node = new TIntermBinary(EOpIndexDirect, baseExpression, indexExpression); TIntermBinary *node =
node->setLine(location); new TIntermBinary(EOpIndexDirect, baseExpression, indexExpression);
return node->fold(mDiagnostics); node->setLine(location);
} return node->fold(mDiagnostics);
else }
{
TIntermBinary *node = new TIntermBinary(EOpIndexIndirect, baseExpression, indexExpression);
node->setLine(location);
// Indirect indexing can never be constant folded.
return node;
} }
TIntermBinary *node = new TIntermBinary(EOpIndexIndirect, baseExpression, indexExpression);
node->setLine(location);
// Indirect indexing can never be constant folded.
return node;
} }
int TParseContext::checkIndexOutOfRange(bool outOfRangeIndexIsError, int TParseContext::checkIndexLessThan(bool outOfRangeIndexIsError,
const TSourceLoc &location, const TSourceLoc &location,
int index, int index,
int arraySize, int arraySize,
const char *reason) const char *reason)
{ {
if (index >= arraySize || index < 0) // Should not reach here with an unsized / runtime-sized array.
ASSERT(arraySize > 0);
if (index >= arraySize)
{ {
std::stringstream reasonStream; std::stringstream reasonStream;
reasonStream << reason << " '" << index << "'"; reasonStream << reason << " '" << index << "'";
std::string token = reasonStream.str(); std::string token = reasonStream.str();
outOfRangeError(outOfRangeIndexIsError, location, reason, "[]"); outOfRangeError(outOfRangeIndexIsError, location, reason, "[]");
if (index < 0) return arraySize - 1;
{
return 0;
}
else
{
return arraySize - 1;
}
} }
return index; return index;
} }
...@@ -4759,9 +4771,6 @@ TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecif ...@@ -4759,9 +4771,6 @@ TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecif
{ {
type->makeArray(arraySize); type->makeArray(arraySize);
} }
checkIsNotUnsizedArray(typeSpecifier.getLine(),
"array members of structs must specify a size",
declarator->name().c_str(), type);
checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *declarator); checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *declarator);
} }
...@@ -4792,7 +4801,7 @@ TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine, ...@@ -4792,7 +4801,7 @@ TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine,
// ensure we do not specify any storage qualifiers on the struct members // ensure we do not specify any storage qualifiers on the struct members
for (unsigned int typeListIndex = 0; typeListIndex < fieldList->size(); typeListIndex++) for (unsigned int typeListIndex = 0; typeListIndex < fieldList->size(); typeListIndex++)
{ {
const TField &field = *(*fieldList)[typeListIndex]; TField &field = *(*fieldList)[typeListIndex];
const TQualifier qualifier = field.type()->getQualifier(); const TQualifier qualifier = field.type()->getQualifier();
switch (qualifier) switch (qualifier)
{ {
...@@ -4814,6 +4823,9 @@ TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine, ...@@ -4814,6 +4823,9 @@ TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine,
error(field.line(), "disallowed type in struct", field.type()->getBasicString()); error(field.line(), "disallowed type in struct", field.type()->getBasicString());
} }
checkIsNotUnsizedArray(field.line(), "array members of structs must specify a size",
field.name().c_str(), field.type());
checkMemoryQualifierIsNotSpecified(field.type()->getMemoryQualifier(), field.line()); checkMemoryQualifierIsNotSpecified(field.type()->getMemoryQualifier(), field.line());
checkBindingIsNotSpecified(field.line(), field.type()->getLayoutQualifier().binding); checkBindingIsNotSpecified(field.line(), field.type()->getLayoutQualifier().binding);
......
...@@ -462,11 +462,11 @@ class TParseContext : angle::NonCopyable ...@@ -462,11 +462,11 @@ class TParseContext : angle::NonCopyable
constexpr static size_t kAtomicCounterArrayStride = 4; constexpr static size_t kAtomicCounterArrayStride = 4;
// Returns a clamped index. If it prints out an error message, the token is "[]". // Returns a clamped index. If it prints out an error message, the token is "[]".
int checkIndexOutOfRange(bool outOfRangeIndexIsError, int checkIndexLessThan(bool outOfRangeIndexIsError,
const TSourceLoc &location, const TSourceLoc &location,
int index, int index,
int arraySize, int arraySize,
const char *reason); const char *reason);
bool declareVariable(const TSourceLoc &line, bool declareVariable(const TSourceLoc &line,
const TString &identifier, const TString &identifier,
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
// //
// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps // Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps
// have been done to expressions containing calls of the array length method. // have been done to expressions containing calls of the array length method.
//
// Does nothing to length method calls done on runtime-sized arrays.
#include "compiler/translator/RemoveArrayLengthMethod.h" #include "compiler/translator/RemoveArrayLengthMethod.h"
...@@ -45,7 +47,8 @@ class RemoveArrayLengthTraverser : public TIntermTraverser ...@@ -45,7 +47,8 @@ class RemoveArrayLengthTraverser : public TIntermTraverser
bool RemoveArrayLengthTraverser::visitUnary(Visit visit, TIntermUnary *node) bool RemoveArrayLengthTraverser::visitUnary(Visit visit, TIntermUnary *node)
{ {
if (node->getOp() == EOpArrayLength) // The only case where we leave array length() in place is for runtime-sized arrays.
if (node->getOp() == EOpArrayLength && !node->getOperand()->getType().isUnsizedArray())
{ {
mFoundArrayLength = true; mFoundArrayLength = true;
if (!node->getOperand()->hasSideEffects()) if (!node->getOperand()->hasSideEffects())
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
// //
// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps // Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps
// have been done to expressions containing calls of the array length method. // have been done to expressions containing calls of the array length method.
//
// Does nothing to length method calls done on runtime-sized arrays.
namespace sh namespace sh
{ {
......
...@@ -272,6 +272,7 @@ void InitBuiltInResources(ShBuiltInResources *resources) ...@@ -272,6 +272,7 @@ void InitBuiltInResources(ShBuiltInResources *resources)
resources->MaxAtomicCounterBufferSize = 32; resources->MaxAtomicCounterBufferSize = 32;
resources->MaxUniformBufferBindings = 32; resources->MaxUniformBufferBindings = 32;
resources->MaxShaderStorageBufferBindings = 4;
resources->MaxGeometryUniformComponents = 1024; resources->MaxGeometryUniformComponents = 1024;
resources->MaxGeometryUniformBlocks = 12; resources->MaxGeometryUniformBlocks = 12;
......
...@@ -519,3 +519,66 @@ TEST_F(BufferVariablesTest, UniformBlockWithStd430) ...@@ -519,3 +519,66 @@ TEST_F(BufferVariablesTest, UniformBlockWithStd430)
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog; 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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment