Commit 0297779c by Shahbaz Youssefi Committed by Angle LUCI CQ

Vulkan: SPIR-V Gen: Handle invariant variables

The Invariant decoration is applied to variable declarations and block members as necessary. When invariant is applied to a gl_PerVertex built-in, it's applied to the appropriate member in DeclarePerVertexBlocks. Bug: angleproject:4889 Change-Id: Idaa8d4ab92f975381a0237d372f0a4044ff983e0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2946116 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent f33a41be
...@@ -32,7 +32,7 @@ bool operator==(const SpirvType &a, const SpirvType &b) ...@@ -32,7 +32,7 @@ bool operator==(const SpirvType &a, const SpirvType &b)
// ValidateASTOptions::validateVariableReferences. // ValidateASTOptions::validateVariableReferences.
if (a.block != nullptr) if (a.block != nullptr)
{ {
return a.blockStorage == b.blockStorage; return a.blockStorage == b.blockStorage && a.isInvariant == b.isInvariant;
} }
// Otherwise, match by the type contents. The AST transformations sometimes recreate types that // Otherwise, match by the type contents. The AST transformations sometimes recreate types that
...@@ -68,7 +68,7 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc ...@@ -68,7 +68,7 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc
spirvType.imageInternalFormat = type.getLayoutQualifier().imageInternalFormat; spirvType.imageInternalFormat = type.getLayoutQualifier().imageInternalFormat;
spirvType.blockStorage = blockStorage; spirvType.blockStorage = blockStorage;
// Turn unspecifed matrix packing into column-major. // Turn unspecified matrix packing into column-major.
if (spirvType.matrixPacking == EmpUnspecified) if (spirvType.matrixPacking == EmpUnspecified)
{ {
spirvType.matrixPacking = EmpColumnMajor; spirvType.matrixPacking = EmpColumnMajor;
...@@ -76,7 +76,8 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc ...@@ -76,7 +76,8 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc
if (type.getStruct() != nullptr) if (type.getStruct() != nullptr)
{ {
spirvType.block = type.getStruct(); spirvType.block = type.getStruct();
spirvType.isInvariant = isInvariantOutput(type);
} }
else if (type.isInterfaceBlock()) else if (type.isInterfaceBlock())
{ {
...@@ -230,7 +231,17 @@ SpirvTypeData SPIRVBuilder::declareType(const SpirvType &type, const char *block ...@@ -230,7 +231,17 @@ SpirvTypeData SPIRVBuilder::declareType(const SpirvType &type, const char *block
spirv::IdRefList fieldTypeIds; spirv::IdRefList fieldTypeIds;
for (const TField *field : type.block->fields()) for (const TField *field : type.block->fields())
{ {
spirv::IdRef fieldTypeId = getTypeData(*field->type(), type.blockStorage).id; const TType &fieldType = *field->type();
SpirvType fieldSpirvType = getSpirvType(fieldType, type.blockStorage);
const char *structName = "";
// Propagate invariant to struct members.
if (fieldType.getStruct() != nullptr)
{
fieldSpirvType.isInvariant = type.isInvariant;
structName = fieldType.getStruct()->name().data();
}
spirv::IdRef fieldTypeId = getSpirvTypeData(fieldSpirvType, structName).id;
fieldTypeIds.push_back(fieldTypeId); fieldTypeIds.push_back(fieldTypeId);
} }
...@@ -987,6 +998,13 @@ uint32_t SPIRVBuilder::nextUnusedOutputLocation(uint32_t consumedCount) ...@@ -987,6 +998,13 @@ uint32_t SPIRVBuilder::nextUnusedOutputLocation(uint32_t consumedCount)
return nextUnused; return nextUnused;
} }
bool SPIRVBuilder::isInvariantOutput(const TType &type) const
{
// The Invariant decoration is applied to output variables if specified or if globally enabled.
return type.isInvariant() ||
(IsShaderOut(type.getQualifier()) && mCompiler->getPragma().stdgl.invariantAll);
}
void SPIRVBuilder::addCapability(spv::Capability capability) void SPIRVBuilder::addCapability(spv::Capability capability)
{ {
mCapabilities.insert(capability); mCapabilities.insert(capability);
...@@ -1034,9 +1052,6 @@ void SPIRVBuilder::writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId ...@@ -1034,9 +1052,6 @@ void SPIRVBuilder::writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId
spv::DecorationBuiltIn, spv::DecorationBuiltIn,
{spirv::LiteralInteger(decorationValue)}); {spirv::LiteralInteger(decorationValue)});
} }
SpirvType spirvType = getSpirvType(type, EbsUnspecified);
writeMemberDecorations(spirvType, typeId);
} }
void SPIRVBuilder::writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId) void SPIRVBuilder::writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId)
...@@ -1309,6 +1324,14 @@ void SPIRVBuilder::writeMemberDecorations(const SpirvType &type, spirv::IdRef ty ...@@ -1309,6 +1324,14 @@ void SPIRVBuilder::writeMemberDecorations(const SpirvType &type, spirv::IdRef ty
const SpirvTypeData &fieldTypeData = getTypeData(fieldType, type.blockStorage); const SpirvTypeData &fieldTypeData = getTypeData(fieldType, type.blockStorage);
// Add invariant decoration if any.
if (type.isInvariant || fieldType.isInvariant())
{
spirv::WriteMemberDecorate(&mSpirvDecorations, typeId,
spirv::LiteralInteger(fieldIndex), spv::DecorationInvariant,
{});
}
// Add matrix decorations if any. // Add matrix decorations if any.
if (fieldType.isMatrix()) if (fieldType.isMatrix())
{ {
......
...@@ -33,6 +33,11 @@ struct SpirvType ...@@ -33,6 +33,11 @@ struct SpirvType
// except for non-block non-array types. // except for non-block non-array types.
TLayoutBlockStorage blockStorage = EbsUnspecified; TLayoutBlockStorage blockStorage = EbsUnspecified;
// If a structure is used in two I/O blocks or output varyings with and without the invariant
// qualifier, it would also have to generate two SPIR-V types, as its fields' Invariant
// decorations would be different.
bool isInvariant = false;
// Otherwise, it's a basic type + column, row and array dimensions, or it's an image // Otherwise, it's a basic type + column, row and array dimensions, or it's an image
// declaration. // declaration.
// //
...@@ -92,6 +97,9 @@ struct SpirvTypeHash ...@@ -92,6 +97,9 @@ struct SpirvTypeHash
ASSERT(type.blockStorage == sh::EbsUnspecified || type.block != nullptr || ASSERT(type.blockStorage == sh::EbsUnspecified || type.block != nullptr ||
!type.arraySizes.empty()); !type.arraySizes.empty());
// Invariant must only affect the type if it's a block type.
ASSERT(!type.isInvariant || type.block != nullptr);
size_t result = 0; size_t result = 0;
if (!type.arraySizes.empty()) if (!type.arraySizes.empty())
...@@ -103,7 +111,7 @@ struct SpirvTypeHash ...@@ -103,7 +111,7 @@ struct SpirvTypeHash
if (type.block != nullptr) if (type.block != nullptr)
{ {
return result ^ angle::ComputeGenericHash(&type.block, sizeof(type.block)) ^ return result ^ angle::ComputeGenericHash(&type.block, sizeof(type.block)) ^
type.blockStorage; static_cast<size_t>(type.isInvariant) ^ (type.blockStorage << 1);
} }
static_assert(sh::EbtLast < 256, "Basic type doesn't fit in uint8_t"); static_assert(sh::EbtLast < 256, "Basic type doesn't fit in uint8_t");
...@@ -165,6 +173,11 @@ struct SpirvTypeData ...@@ -165,6 +173,11 @@ struct SpirvTypeData
// NoContraction: used to implement |precise|. TODO: support this. It requires the precise // NoContraction: used to implement |precise|. TODO: support this. It requires the precise
// property to be promoted through the nodes in the AST, which currently isn't. // property to be promoted through the nodes in the AST, which currently isn't.
// http://anglebug.com/4889 // http://anglebug.com/4889
// Invariant: used to implement |invariant|, which is applied to output variables.
//
// Note that Invariant applies to variables and NoContraction to arithmetic instructions, so they
// are mutually exclusive and a maximum of 2 decorations are possible. FixedVector::push_back will
// ASSERT if the given size is ever not enough.
using SpirvDecorations = angle::FixedVector<spv::Decoration, 2>; using SpirvDecorations = angle::FixedVector<spv::Decoration, 2>;
// A block of code. SPIR-V produces forward references to blocks, such as OpBranchConditional // A block of code. SPIR-V produces forward references to blocks, such as OpBranchConditional
...@@ -274,6 +287,8 @@ class SPIRVBuilder : angle::NonCopyable ...@@ -274,6 +287,8 @@ class SPIRVBuilder : angle::NonCopyable
} }
SpirvConditional *getCurrentConditional() { return &mConditionalStack.back(); } SpirvConditional *getCurrentConditional() { return &mConditionalStack.back(); }
bool isInvariantOutput(const TType &type) const;
void addCapability(spv::Capability capability); void addCapability(spv::Capability capability);
void setEntryPointId(spirv::IdRef id); void setEntryPointId(spirv::IdRef id);
void addEntryPointInterfaceVariableId(spirv::IdRef id); void addEntryPointInterfaceVariableId(spirv::IdRef id);
......
...@@ -2205,10 +2205,27 @@ bool OutputSPIRVTraverser::visitFunctionDefinition(Visit visit, TIntermFunctionD ...@@ -2205,10 +2205,27 @@ bool OutputSPIRVTraverser::visitFunctionDefinition(Visit visit, TIntermFunctionD
bool OutputSPIRVTraverser::visitGlobalQualifierDeclaration(Visit visit, bool OutputSPIRVTraverser::visitGlobalQualifierDeclaration(Visit visit,
TIntermGlobalQualifierDeclaration *node) TIntermGlobalQualifierDeclaration *node)
{ {
// TODO: http://anglebug.com/4889 if (node->isPrecise())
UNIMPLEMENTED(); {
// TODO: handle precise. http://anglebug.com/4889.
UNIMPLEMENTED();
return false;
}
return true; // Global qualifier declarations apply to variables that are already declared. Invariant simply
// adds a decoration to the variable declaration, which can be done right away. Note that
// invariant cannot be applied to block members like this, except for gl_PerVertex built-ins,
// which are applied to the members directly by DeclarePerVertexBlocks.
ASSERT(node->isInvariant());
const TVariable *variable = &node->getSymbol()->variable();
ASSERT(mSymbolIdMap.count(variable) > 0);
const spirv::IdRef variableId = mSymbolIdMap[variable];
spirv::WriteDecorate(mBuilder.getSpirvDecorations(), variableId, spv::DecorationInvariant, {});
return false;
} }
void OutputSPIRVTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node) void OutputSPIRVTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node)
...@@ -2420,9 +2437,16 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod ...@@ -2420,9 +2437,16 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod
spv::StorageClass storageClass = GetStorageClass(type); spv::StorageClass storageClass = GetStorageClass(type);
SpirvDecorations decorations = mBuilder.getDecorations(type);
if (mBuilder.isInvariantOutput(type))
{
// Apply the Invariant decoration to output variables if specified or if globally enabled.
decorations.push_back(spv::DecorationInvariant);
}
const spirv::IdRef variableId = mBuilder.declareVariable( const spirv::IdRef variableId = mBuilder.declareVariable(
typeId, storageClass, mBuilder.getDecorations(type), typeId, storageClass, decorations, initializeWithDeclaration ? &initializerId : nullptr,
initializeWithDeclaration ? &initializerId : nullptr, mBuilder.hashName(variable).data()); mBuilder.hashName(variable).data());
if (!initializeWithDeclaration && initializerId.valid()) if (!initializeWithDeclaration && initializerId.valid())
{ {
......
...@@ -20,6 +20,29 @@ namespace sh ...@@ -20,6 +20,29 @@ namespace sh
{ {
namespace namespace
{ {
using PerVertexMemberFlags = std::array<bool, 4>;
int GetPerVertexFieldIndex(const TQualifier qualifier, const ImmutableString &name)
{
switch (qualifier)
{
case EvqPosition:
ASSERT(name == "gl_Position");
return 0;
case EvqPointSize:
ASSERT(name == "gl_PointSize");
return 1;
case EvqClipDistance:
ASSERT(name == "gl_ClipDistance");
return 2;
case EvqCullDistance:
ASSERT(name == "gl_CullDistance");
return 3;
default:
return -1;
}
}
// Traverser that: // Traverser that:
// //
// 1. Declares the input and output gl_PerVertex types and variables if not already (based on shader // 1. Declares the input and output gl_PerVertex types and variables if not already (based on shader
...@@ -28,14 +51,19 @@ namespace ...@@ -28,14 +51,19 @@ namespace
class DeclarePerVertexBlocksTraverser : public TIntermTraverser class DeclarePerVertexBlocksTraverser : public TIntermTraverser
{ {
public: public:
DeclarePerVertexBlocksTraverser(TCompiler *compiler, TSymbolTable *symbolTable) DeclarePerVertexBlocksTraverser(TCompiler *compiler,
TSymbolTable *symbolTable,
const PerVertexMemberFlags &invariantFlags,
const PerVertexMemberFlags &preciseFlags)
: TIntermTraverser(true, false, false, symbolTable), : TIntermTraverser(true, false, false, symbolTable),
mShaderType(compiler->getShaderType()), mShaderType(compiler->getShaderType()),
mResources(compiler->getResources()), mResources(compiler->getResources()),
mPerVertexInVar(nullptr), mPerVertexInVar(nullptr),
mPerVertexOutVar(nullptr), mPerVertexOutVar(nullptr),
mPerVertexInVarRedeclared(false), mPerVertexInVarRedeclared(false),
mPerVertexOutVarRedeclared(false) mPerVertexOutVarRedeclared(false),
mPerVertexOutInvariantFlags(invariantFlags),
mPerVertexOutPreciseFlags(preciseFlags)
{} {}
void visitSymbol(TIntermSymbol *symbol) override void visitSymbol(TIntermSymbol *symbol) override
...@@ -51,6 +79,25 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser ...@@ -51,6 +79,25 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
// Declare gl_out if not already. // Declare gl_out if not already.
if (mPerVertexOutVar == nullptr) if (mPerVertexOutVar == nullptr)
{ {
// Record invariant and precise qualifiers used on the fields so they would be
// applied to the replacement gl_out.
for (const TField *field : type->getInterfaceBlock()->fields())
{
const TType &fieldType = *field->type();
const int fieldIndex =
GetPerVertexFieldIndex(fieldType.getQualifier(), field->name());
ASSERT(fieldIndex >= 0);
if (fieldType.isInvariant())
{
mPerVertexOutInvariantFlags[fieldIndex] = true;
}
if (fieldType.isPrecise())
{
mPerVertexOutPreciseFlags[fieldIndex] = true;
}
}
declareDefaultGlOut(); declareDefaultGlOut();
} }
...@@ -111,27 +158,7 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser ...@@ -111,27 +158,7 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
return; return;
} }
int fieldIndex = -1; const int fieldIndex = GetPerVertexFieldIndex(type->getQualifier(), variable->name());
if (type->getQualifier() == EvqPosition)
{
ASSERT(variable->name() == "gl_Position");
fieldIndex = 0;
}
if (type->getQualifier() == EvqPointSize)
{
ASSERT(variable->name() == "gl_PointSize");
fieldIndex = 1;
}
if (type->getQualifier() == EvqClipDistance)
{
ASSERT(variable->name() == "gl_ClipDistance");
fieldIndex = 2;
}
if (type->getQualifier() == EvqCullDistance)
{
ASSERT(variable->name() == "gl_CullDistance");
fieldIndex = 3;
}
// Not the built-in we are looking for. // Not the built-in we are looking for.
if (fieldIndex < 0) if (fieldIndex < 0)
...@@ -191,6 +218,19 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser ...@@ -191,6 +218,19 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
clipDistanceType->makeArray(mResources.MaxClipDistances); clipDistanceType->makeArray(mResources.MaxClipDistances);
cullDistanceType->makeArray(mResources.MaxCullDistances); cullDistanceType->makeArray(mResources.MaxCullDistances);
if (qualifier == EvqPerVertexOut)
{
positionType->setInvariant(mPerVertexOutInvariantFlags[0]);
pointSizeType->setInvariant(mPerVertexOutInvariantFlags[1]);
clipDistanceType->setInvariant(mPerVertexOutInvariantFlags[2]);
cullDistanceType->setInvariant(mPerVertexOutInvariantFlags[3]);
positionType->setPrecise(mPerVertexOutPreciseFlags[0]);
pointSizeType->setPrecise(mPerVertexOutPreciseFlags[1]);
clipDistanceType->setPrecise(mPerVertexOutPreciseFlags[2]);
cullDistanceType->setPrecise(mPerVertexOutPreciseFlags[3]);
}
fields->push_back(new TField(positionType, ImmutableString("gl_Position"), TSourceLoc(), fields->push_back(new TField(positionType, ImmutableString("gl_Position"), TSourceLoc(),
SymbolType::AngleInternal)); SymbolType::AngleInternal));
fields->push_back(new TField(pointSizeType, ImmutableString("gl_PointSize"), TSourceLoc(), fields->push_back(new TField(pointSizeType, ImmutableString("gl_PointSize"), TSourceLoc(),
...@@ -267,6 +307,10 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser ...@@ -267,6 +307,10 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
// A map of already replaced built-in variables. // A map of already replaced built-in variables.
VariableReplacementMap mVariableMap; VariableReplacementMap mVariableMap;
// Whether each field is invariant or precise.
PerVertexMemberFlags mPerVertexOutInvariantFlags;
PerVertexMemberFlags mPerVertexOutPreciseFlags;
}; };
void AddPerVertexDecl(TIntermBlock *root, const TVariable *variable) void AddPerVertexDecl(TIntermBlock *root, const TVariable *variable)
...@@ -294,7 +338,54 @@ bool DeclarePerVertexBlocks(TCompiler *compiler, TIntermBlock *root, TSymbolTabl ...@@ -294,7 +338,54 @@ bool DeclarePerVertexBlocks(TCompiler *compiler, TIntermBlock *root, TSymbolTabl
return true; return true;
} }
DeclarePerVertexBlocksTraverser traverser(compiler, symbolTable); // First, visit all global qualifier declarations and find which built-ins are invariant or
// precise.
PerVertexMemberFlags invariantFlags = {};
PerVertexMemberFlags preciseFlags = {};
TIntermSequence withoutPerVertexGlobalQualifierDeclarations;
for (TIntermNode *node : *root->getSequence())
{
TIntermGlobalQualifierDeclaration *asGlobalQualifierDecl =
node->getAsGlobalQualifierDeclarationNode();
if (asGlobalQualifierDecl == nullptr)
{
withoutPerVertexGlobalQualifierDeclarations.push_back(node);
continue;
}
TIntermSymbol *symbol = asGlobalQualifierDecl->getSymbol();
const int fieldIndex =
GetPerVertexFieldIndex(symbol->getType().getQualifier(), symbol->getName());
if (fieldIndex < 0)
{
withoutPerVertexGlobalQualifierDeclarations.push_back(node);
continue;
}
if (asGlobalQualifierDecl->isInvariant())
{
invariantFlags[fieldIndex] = true;
}
else if (asGlobalQualifierDecl->isPrecise())
{
preciseFlags[fieldIndex] = true;
}
}
// Remove the global qualifier declarations for the gl_PerVertex members.
root->replaceAllChildren(withoutPerVertexGlobalQualifierDeclarations);
// If #pragma STDGL invariant(all) is specified, make all outputs invariant.
if (compiler->getPragma().stdgl.invariantAll)
{
std::fill(invariantFlags.begin(), invariantFlags.end(), true);
}
// Then declare the in and out gl_PerVertex I/O blocks.
DeclarePerVertexBlocksTraverser traverser(compiler, symbolTable, invariantFlags, preciseFlags);
root->traverse(&traverser); root->traverse(&traverser);
if (!traverser.updateTree(compiler, root)) if (!traverser.updateTree(compiler, root))
{ {
......
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