Commit a78092cb by Olli Etuaho Committed by Commit Bot

Support ESSL 3.00 EXT_blend_func_extended shaders

This adds support for the index layout qualifier that's used in EXT_blend_func_extended to set whether a fragment output should be bound to the primary or secondary blend source color. Output locations are now validated correctly so that two outputs can have the same location as long as they have a different index. Some tests are fixed to allow this. BUG=angleproject:1085 TEST=angle_unittests Change-Id: I1de3ad1406398952287791eca367562bed59d380 Reviewed-on: https://chromium-review.googlesource.com/1245982 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 79207e6e
......@@ -25,7 +25,7 @@
// Version number for shader translation API.
// It is incremented every time the API changes.
#define ANGLE_SH_VERSION 199
#define ANGLE_SH_VERSION 200
enum ShShaderSpec
{
......
......@@ -203,6 +203,9 @@ struct OutputVariable : public VariableWithLocation
OutputVariable &operator=(const OutputVariable &other);
bool operator==(const OutputVariable &other) const;
bool operator!=(const OutputVariable &other) const { return !operator==(other); }
// From EXT_blend_func_extended.
int index;
};
struct InterfaceBlockField : public ShaderVariable
......
......@@ -729,7 +729,8 @@ struct TLayoutQualifier
return location == -1 && binding == -1 && offset == -1 && numViews == -1 && yuv == false &&
matrixPacking == EmpUnspecified && blockStorage == EbsUnspecified &&
!localSize.isAnyValueSet() && imageInternalFormat == EiifUnspecified &&
primitiveType == EptUndefined && invocations == 0 && maxVertices == -1;
primitiveType == EptUndefined && invocations == 0 && maxVertices == -1 &&
index == -1;
}
bool isCombinationValid() const
......@@ -739,7 +740,7 @@ struct TLayoutQualifier
bool geometryShaderSpecified =
(primitiveType != EptUndefined) || (invocations != 0) || (maxVertices != -1);
bool otherLayoutQualifiersSpecified =
(location != -1 || binding != -1 || matrixPacking != EmpUnspecified ||
(location != -1 || binding != -1 || index != -1 || matrixPacking != EmpUnspecified ||
blockStorage != EbsUnspecified || imageInternalFormat != EiifUnspecified);
// we can have either the work group size specified, or number of views,
......@@ -779,6 +780,9 @@ struct TLayoutQualifier
int invocations;
int maxVertices;
// EXT_blend_func_extended fragment output layout qualifier
int index;
private:
explicit constexpr TLayoutQualifier(int /*placeholder*/)
: location(-1),
......@@ -793,7 +797,8 @@ struct TLayoutQualifier
yuv(false),
primitiveType(EptUndefined),
invocations(0),
maxVertices(-1)
maxVertices(-1),
index(-1)
{
}
};
......
......@@ -648,6 +648,7 @@ OutputVariable CollectVariablesTraverser::recordOutputVariable(const TIntermSymb
setCommonVariableProperties(type, variable.variable(), &outputVariable);
outputVariable.location = type.getLayoutQualifier().location;
outputVariable.index = type.getLayoutQualifier().index;
return outputVariable;
}
......
......@@ -1130,6 +1130,25 @@ bool TParseContext::declareVariable(const TSourceLoc &line,
(*variable) = new TVariable(&symbolTable, identifier, type, SymbolType::UserDefined);
ASSERT(type->getLayoutQualifier().index == -1 ||
(isExtensionEnabled(TExtension::EXT_blend_func_extended) &&
mShaderType == GL_FRAGMENT_SHADER && mShaderVersion >= 300));
if (type->getQualifier() == EvqFragmentOut)
{
if (type->getLayoutQualifier().index != -1 && type->getLayoutQualifier().location == -1)
{
error(line,
"If index layout qualifier is specified for a fragment output, location must "
"also be specified.",
"index");
return false;
}
}
else
{
checkIndexIsNotSpecified(line, type->getLayoutQualifier().index);
}
checkBindingIsValid(line, *type);
bool needsReservedCheck = true;
......@@ -1376,6 +1395,11 @@ void TParseContext::emptyDeclarationErrorCheck(const TType &type, const TSourceL
// error. It is assumed that this applies to empty declarations as well.
error(location, "empty array declaration needs to specify a size", "");
}
if (type.getQualifier() != EvqFragmentOut)
{
checkIndexIsNotSpecified(location, type.getLayoutQualifier().index);
}
}
// These checks are done for all declarations that are non-empty. They're done for non-empty
......@@ -1596,6 +1620,17 @@ void TParseContext::checkInternalFormatIsNotSpecified(const TSourceLoc &location
}
}
void TParseContext::checkIndexIsNotSpecified(const TSourceLoc &location, int index)
{
if (index != -1)
{
error(location,
"invalid layout qualifier: only valid when used with a fragment shader output in "
"ESSL version >= 3.00 and EXT_blend_func_extended is enabled",
"index");
}
}
void TParseContext::checkBindingIsNotSpecified(const TSourceLoc &location, int binding)
{
if (binding != -1)
......@@ -2981,6 +3016,8 @@ void TParseContext::parseGlobalLayoutQualifier(const TTypeQualifierBuilder &type
return;
}
checkIndexIsNotSpecified(typeQualifier.line, layoutQualifier.index);
checkBindingIsNotSpecified(typeQualifier.line, layoutQualifier.binding);
checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line);
......@@ -3615,6 +3652,8 @@ TIntermDeclaration *TParseContext::addInterfaceBlock(
arraySize = checkIsValidArraySize(arrayIndexLine, arrayIndex);
}
checkIndexIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.index);
if (mShaderVersion < 310)
{
checkBindingIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.binding);
......@@ -3706,6 +3745,7 @@ TIntermDeclaration *TParseContext::addInterfaceBlock(
// check layout qualifiers
TLayoutQualifier fieldLayoutQualifier = fieldType->getLayoutQualifier();
checkLocationIsNotSpecified(field->line(), fieldLayoutQualifier);
checkIndexIsNotSpecified(field->line(), fieldLayoutQualifier.index);
checkBindingIsNotSpecified(field->line(), fieldLayoutQualifier.binding);
if (fieldLayoutQualifier.blockStorage != EbsUnspecified)
......@@ -4411,6 +4451,26 @@ void TParseContext::parseMaxVertices(int intValue,
}
}
void TParseContext::parseIndexLayoutQualifier(int intValue,
const TSourceLoc &intValueLine,
const std::string &intValueString,
int *index)
{
// EXT_blend_func_extended specifies that most validation should happen at link time, but since
// we're validating output variable locations at compile time, it makes sense to validate that
// index is 0 or 1 also at compile time. Also since we use "-1" as a placeholder for unspecified
// index, we can't accept it here.
if (intValue < 0 || intValue > 1)
{
error(intValueLine, "out of range: index layout qualifier can only be 0 or 1",
intValueString.c_str());
}
else
{
*index = intValue;
}
}
TLayoutQualifier TParseContext::parseLayoutQualifier(const ImmutableString &qualifierType,
const TSourceLoc &qualifierTypeLine,
int intValue,
......@@ -4492,7 +4552,11 @@ TLayoutQualifier TParseContext::parseLayoutQualifier(const ImmutableString &qual
{
parseMaxVertices(intValue, intValueLine, intValueString, &qualifier.maxVertices);
}
else if (qualifierType == "index" && mShaderType == GL_FRAGMENT_SHADER &&
checkCanUseExtension(qualifierTypeLine, TExtension::EXT_blend_func_extended))
{
parseIndexLayoutQualifier(intValue, intValueLine, intValueString, &qualifier.index);
}
else
{
error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
......@@ -4781,6 +4845,8 @@ TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine,
checkMemoryQualifierIsNotSpecified(field.type()->getMemoryQualifier(), field.line());
checkIndexIsNotSpecified(field.line(), field.type()->getLayoutQualifier().index);
checkBindingIsNotSpecified(field.line(), field.type()->getLayoutQualifier().binding);
checkLocationIsNotSpecified(field.line(), field.type()->getLayoutQualifier());
......
......@@ -373,6 +373,10 @@ class TParseContext : angle::NonCopyable
const TSourceLoc &intValueLine,
const std::string &intValueString,
int *numMaxVertices);
void parseIndexLayoutQualifier(int intValue,
const TSourceLoc &intValueLine,
const std::string &intValueString,
int *index);
TLayoutQualifier parseLayoutQualifier(const ImmutableString &qualifierType,
const TSourceLoc &qualifierTypeLine);
TLayoutQualifier parseLayoutQualifier(const ImmutableString &qualifierType,
......@@ -511,6 +515,7 @@ class TParseContext : angle::NonCopyable
void checkAtomicCounterOffsetDoesNotOverlap(bool forceAppend,
const TSourceLoc &loc,
TType *type);
void checkIndexIsNotSpecified(const TSourceLoc &location, int index);
void checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type);
void checkBindingIsNotSpecified(const TSourceLoc &location, int binding);
void checkOffsetIsNotSpecified(const TSourceLoc &location, int offset);
......
......@@ -682,6 +682,17 @@ TLayoutQualifier JoinLayoutQualifiers(TLayoutQualifier leftQualifier,
joinedQualifier.maxVertices = rightQualifier.maxVertices;
}
if (rightQualifier.index != -1)
{
if (joinedQualifier.index != -1)
{
// EXT_blend_func_extended spec: "Each of these qualifiers may appear at most once"
diagnostics->error(rightQualifierLocation, "Cannot have multiple index specifiers",
"index");
}
joinedQualifier.index = rightQualifier.index;
}
return joinedQualifier;
}
......
......@@ -352,7 +352,7 @@ bool Attribute::operator==(const Attribute &other) const
return VariableWithLocation::operator==(other);
}
OutputVariable::OutputVariable()
OutputVariable::OutputVariable() : index(-1)
{
}
......@@ -360,19 +360,12 @@ OutputVariable::~OutputVariable()
{
}
OutputVariable::OutputVariable(const OutputVariable &other) : VariableWithLocation(other)
{
}
OutputVariable &OutputVariable::operator=(const OutputVariable &other)
{
VariableWithLocation::operator=(other);
return *this;
}
OutputVariable::OutputVariable(const OutputVariable &other) = default;
OutputVariable &OutputVariable::operator=(const OutputVariable &other) = default;
bool OutputVariable::operator==(const OutputVariable &other) const
{
return VariableWithLocation::operator==(other);
return VariableWithLocation::operator==(other) && index == other.index;
}
InterfaceBlockField::InterfaceBlockField() : isRowMajorLayout(false)
......
......@@ -92,7 +92,8 @@ void ValidateOutputsTraverser::visitSymbol(TIntermSymbol *symbol)
void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const
{
ASSERT(diagnostics);
OutputVector validOutputs(mMaxDrawBuffers);
OutputVector validOutputs(mMaxDrawBuffers, nullptr);
OutputVector validSecondaryOutputs(mMaxDrawBuffers, nullptr);
for (const auto &symbol : mOutputs)
{
......@@ -104,21 +105,29 @@ void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const
ASSERT(type.getLayoutQualifier().location != -1);
if (location + elementCount <= validOutputs.size())
OutputVector &validOutputsToUse = validOutputs;
// The default index is 0, so we only assign the output to secondary outputs in case the
// index is explicitly set to 1.
if (type.getLayoutQualifier().index == 1)
{
validOutputsToUse = validSecondaryOutputs;
}
if (location + elementCount <= validOutputsToUse.size())
{
for (size_t elementIndex = 0; elementIndex < elementCount; elementIndex++)
{
const size_t offsetLocation = location + elementIndex;
if (validOutputs[offsetLocation])
if (validOutputsToUse[offsetLocation])
{
std::stringstream strstr;
strstr << "conflicting output locations with previously defined output '"
<< validOutputs[offsetLocation]->getName() << "'";
<< validOutputsToUse[offsetLocation]->getName() << "'";
error(*symbol, strstr.str().c_str(), diagnostics);
}
else
{
validOutputs[offsetLocation] = symbol;
validOutputsToUse[offsetLocation] = symbol;
}
}
}
......
......@@ -363,6 +363,7 @@ LinkResult MemoryProgramCache::Deserialize(const Context *context,
sh::OutputVariable output;
LoadShaderVar(&stream, &output);
output.location = stream.readInt<int>();
output.index = stream.readInt<int>();
state->mOutputVariables.push_back(output);
}
......@@ -555,6 +556,7 @@ void MemoryProgramCache::Serialize(const Context *context,
{
WriteShaderVar(&stream, output);
stream.writeInt(output.location);
stream.writeInt(output.index);
}
stream.writeInt(state.getOutputLocations().size());
......
......@@ -40,6 +40,8 @@ class CollectVariablesTest : public testing::Test
ShBuiltInResources resources;
InitBuiltInResources(&resources);
resources.MaxDrawBuffers = 8;
resources.EXT_blend_func_extended = true;
resources.MaxDualSourceDrawBuffers = 1;
initTranslator(resources);
}
......@@ -2058,3 +2060,29 @@ TEST_F(CollectVertexVariablesTest, VaryingOnlyDeclaredInvariant)
EXPECT_FALSE(varying.staticUse);
EXPECT_FALSE(varying.active);
}
// Test an output variable that is declared with the index layout qualifier from
// EXT_blend_func_extended.
TEST_F(CollectFragmentVariablesTest, OutputVarESSL3EXTBlendFuncExtendedIndex)
{
const std::string &shaderString =
R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(location = 0, index = 1) out float outVar;
void main()
{
outVar = 0.0;
})";
compile(shaderString);
const auto &outputs = mTranslator->getOutputVariables();
ASSERT_EQ(1u, outputs.size());
const OutputVariable &output = outputs[0];
EXPECT_EQ("outVar", output.name);
EXPECT_TRUE(output.staticUse);
EXPECT_TRUE(output.active);
EXPECT_EQ(1, output.index);
}
......@@ -67,9 +67,8 @@ const char ESSL300_MaxDualSourceAccessShader[] =
" fragColor = vec4(gl_MaxDualSourceDrawBuffersEXT / 10);\n"
"}\n";
// In GLSL version 300 es, the only way to write a correct shader is to require the extension and
// then leave the locations unspecified. The caller will then bind the variables with the extension
// binding functions.
// In ES 3.0, the locations can be assigned through the API with glBindFragDataLocationIndexedEXT.
// It's fine to have a mix of specified and unspecified locations.
const char ESSL300_LocationAndUnspecifiedOutputShader[] =
"precision mediump float;\n"
"layout(location = 0) out mediump vec4 fragColor;"
......@@ -79,6 +78,7 @@ const char ESSL300_LocationAndUnspecifiedOutputShader[] =
" secondaryFragColor = vec4(1.0);\n"
"}\n";
// It's also fine to leave locations completely unspecified.
const char ESSL300_TwoUnspecifiedLocationOutputsShader[] =
"precision mediump float;\n"
"out mediump vec4 fragColor;"
......@@ -88,26 +88,71 @@ const char ESSL300_TwoUnspecifiedLocationOutputsShader[] =
" secondaryFragColor = vec4(1.0);\n"
"}\n";
// Shader that is correct in GLSL ES 3.10 fails when used in version 300 es.
const char ESSL310_LocationIndexShader[] =
"precision mediump float;\n"
"layout(location = 0) out mediump vec4 fragColor;"
"layout(location = 0, index = 1) out mediump vec4 secondaryFragColor;"
"void main() {\n"
" fragColor = vec4(1);\n"
" secondaryFragColor = vec4(1);\n"
"}\n";
// Shader that is specifies two outputs with the same location but different indexes is valid.
const char ESSL300_LocationIndexShader[] =
R"(precision mediump float;
layout(location = 0) out mediump vec4 fragColor;
layout(location = 0, index = 1) out mediump vec4 secondaryFragColor;
void main() {
fragColor = vec4(1);
secondaryFragColor = vec4(1);
})";
// Shader that specifies index layout qualifier but not location fails to compile. Currently fails
// to compile due to version 310 es not being supported.
const char ESSL310_LocationIndexFailureShader[] =
"precision mediump float;\n"
"layout(location = 0) out mediump vec4 fragColor;"
"layout(index = 1) out mediump vec4 secondaryFragColor;"
"void main() {\n"
" fragColor = vec4(1.0);\n"
" secondaryFragColor = vec4(1.0);\n"
"}\n";
// Shader that specifies index layout qualifier but not location fails to compile.
const char ESSL300_LocationIndexFailureShader[] =
R"(precision mediump float;
layout(index = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
})";
// Shader that specifies index layout qualifier multiple times fails to compile.
const char ESSL300_DoubleIndexFailureShader[] =
R"(precision mediump float;
layout(index = 0, location = 0, index = 1) out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
})";
// Global index layout qualifier fails.
const char ESSL300_GlobalIndexFailureShader[] =
R"(precision mediump float;
layout(index = 0);
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
})";
// Index layout qualifier on a non-output variable fails.
const char ESSL300_IndexOnUniformVariableFailureShader[] =
R"(precision mediump float;
layout(index = 0) uniform vec4 u;
out vec4 fragColor;
void main() {
fragColor = u;
})";
// Index layout qualifier on a struct fails.
const char ESSL300_IndexOnStructFailureShader[] =
R"(precision mediump float;
layout(index = 0) struct S {
vec4 field;
};
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
})";
// Index layout qualifier on a struct member fails.
const char ESSL300_IndexOnStructFieldFailureShader[] =
R"(precision mediump float;
struct S {
layout(index = 0) vec4 field;
};
out mediump vec4 fragColor;
void main() {
fragColor = vec4(1.0);
})";
class EXTBlendFuncExtendedTest : public sh::ShaderExtensionTest
{
......@@ -169,7 +214,8 @@ INSTANTIATE_TEST_CASE_P(CorrectESSL300Shaders,
Values(sh::ESSLVersion300),
Values(ESSL300_MaxDualSourceAccessShader,
ESSL300_LocationAndUnspecifiedOutputShader,
ESSL300_TwoUnspecifiedLocationOutputsShader)));
ESSL300_TwoUnspecifiedLocationOutputsShader,
ESSL300_LocationIndexShader)));
class EXTBlendFuncExtendedCompileFailureTest : public EXTBlendFuncExtendedTest
{
......@@ -208,23 +254,16 @@ INSTANTIATE_TEST_CASE_P(CorrectESSL100Shaders,
Values(sh::ESSLVersion300),
Values(ESSL100_SimpleShader1, ESSL100_FragDataShader)));
// Incorrect #version 310 es always fails.
INSTANTIATE_TEST_CASE_P(IncorrectESSL310Shaders,
// Incorrect #version 300 es shaders always fail.
INSTANTIATE_TEST_CASE_P(IncorrectESSL300Shaders,
EXTBlendFuncExtendedCompileFailureTest,
Combine(Values(SH_GLES3_SPEC),
Combine(Values(SH_GLES3_1_SPEC),
Values(sh::ESSLVersion300, sh::ESSLVersion310),
Values(ESSL310_LocationIndexFailureShader)));
// Correct #version 310 es fails in #version 300 es.
INSTANTIATE_TEST_CASE_P(
CorrectESSL310ShadersInESSL300,
EXTBlendFuncExtendedCompileFailureTest,
Values(make_tuple(SH_GLES3_SPEC, &sh::ESSLVersion300[0], &ESSL310_LocationIndexShader[0])));
// Correct #version 310 es fails in #version 310 es, due to 3.1 not being supported.
INSTANTIATE_TEST_CASE_P(
CorrectESSL310Shaders,
EXTBlendFuncExtendedCompileFailureTest,
Values(make_tuple(SH_GLES3_SPEC, &sh::ESSLVersion310[0], &ESSL310_LocationIndexShader[0])));
Values(ESSL300_LocationIndexFailureShader,
ESSL300_DoubleIndexFailureShader,
ESSL300_GlobalIndexFailureShader,
ESSL300_IndexOnUniformVariableFailureShader,
ESSL300_IndexOnStructFailureShader,
ESSL300_IndexOnStructFieldFailureShader)));
} // namespace
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