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)
// ValidateASTOptions::validateVariableReferences.
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
......@@ -68,7 +68,7 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc
spirvType.imageInternalFormat = type.getLayoutQualifier().imageInternalFormat;
spirvType.blockStorage = blockStorage;
// Turn unspecifed matrix packing into column-major.
// Turn unspecified matrix packing into column-major.
if (spirvType.matrixPacking == EmpUnspecified)
{
spirvType.matrixPacking = EmpColumnMajor;
......@@ -76,7 +76,8 @@ SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage bloc
if (type.getStruct() != nullptr)
{
spirvType.block = type.getStruct();
spirvType.block = type.getStruct();
spirvType.isInvariant = isInvariantOutput(type);
}
else if (type.isInterfaceBlock())
{
......@@ -230,7 +231,17 @@ SpirvTypeData SPIRVBuilder::declareType(const SpirvType &type, const char *block
spirv::IdRefList fieldTypeIds;
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);
}
......@@ -987,6 +998,13 @@ uint32_t SPIRVBuilder::nextUnusedOutputLocation(uint32_t consumedCount)
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)
{
mCapabilities.insert(capability);
......@@ -1034,9 +1052,6 @@ void SPIRVBuilder::writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId
spv::DecorationBuiltIn,
{spirv::LiteralInteger(decorationValue)});
}
SpirvType spirvType = getSpirvType(type, EbsUnspecified);
writeMemberDecorations(spirvType, typeId);
}
void SPIRVBuilder::writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId)
......@@ -1309,6 +1324,14 @@ void SPIRVBuilder::writeMemberDecorations(const SpirvType &type, spirv::IdRef ty
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.
if (fieldType.isMatrix())
{
......
......@@ -33,6 +33,11 @@ struct SpirvType
// except for non-block non-array types.
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
// declaration.
//
......@@ -92,6 +97,9 @@ struct SpirvTypeHash
ASSERT(type.blockStorage == sh::EbsUnspecified || type.block != nullptr ||
!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;
if (!type.arraySizes.empty())
......@@ -103,7 +111,7 @@ struct SpirvTypeHash
if (type.block != nullptr)
{
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");
......@@ -165,6 +173,11 @@ struct SpirvTypeData
// 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.
// 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>;
// A block of code. SPIR-V produces forward references to blocks, such as OpBranchConditional
......@@ -274,6 +287,8 @@ class SPIRVBuilder : angle::NonCopyable
}
SpirvConditional *getCurrentConditional() { return &mConditionalStack.back(); }
bool isInvariantOutput(const TType &type) const;
void addCapability(spv::Capability capability);
void setEntryPointId(spirv::IdRef id);
void addEntryPointInterfaceVariableId(spirv::IdRef id);
......
......@@ -2205,10 +2205,27 @@ bool OutputSPIRVTraverser::visitFunctionDefinition(Visit visit, TIntermFunctionD
bool OutputSPIRVTraverser::visitGlobalQualifierDeclaration(Visit visit,
TIntermGlobalQualifierDeclaration *node)
{
// TODO: http://anglebug.com/4889
UNIMPLEMENTED();
if (node->isPrecise())
{
// 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)
......@@ -2420,9 +2437,16 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod
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(
typeId, storageClass, mBuilder.getDecorations(type),
initializeWithDeclaration ? &initializerId : nullptr, mBuilder.hashName(variable).data());
typeId, storageClass, decorations, initializeWithDeclaration ? &initializerId : nullptr,
mBuilder.hashName(variable).data());
if (!initializeWithDeclaration && initializerId.valid())
{
......
......@@ -20,6 +20,29 @@ namespace sh
{
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:
//
// 1. Declares the input and output gl_PerVertex types and variables if not already (based on shader
......@@ -28,14 +51,19 @@ namespace
class DeclarePerVertexBlocksTraverser : public TIntermTraverser
{
public:
DeclarePerVertexBlocksTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
DeclarePerVertexBlocksTraverser(TCompiler *compiler,
TSymbolTable *symbolTable,
const PerVertexMemberFlags &invariantFlags,
const PerVertexMemberFlags &preciseFlags)
: TIntermTraverser(true, false, false, symbolTable),
mShaderType(compiler->getShaderType()),
mResources(compiler->getResources()),
mPerVertexInVar(nullptr),
mPerVertexOutVar(nullptr),
mPerVertexInVarRedeclared(false),
mPerVertexOutVarRedeclared(false)
mPerVertexOutVarRedeclared(false),
mPerVertexOutInvariantFlags(invariantFlags),
mPerVertexOutPreciseFlags(preciseFlags)
{}
void visitSymbol(TIntermSymbol *symbol) override
......@@ -51,6 +79,25 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
// Declare gl_out if not already.
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();
}
......@@ -111,27 +158,7 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
return;
}
int fieldIndex = -1;
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;
}
const int fieldIndex = GetPerVertexFieldIndex(type->getQualifier(), variable->name());
// Not the built-in we are looking for.
if (fieldIndex < 0)
......@@ -191,6 +218,19 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
clipDistanceType->makeArray(mResources.MaxClipDistances);
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(),
SymbolType::AngleInternal));
fields->push_back(new TField(pointSizeType, ImmutableString("gl_PointSize"), TSourceLoc(),
......@@ -267,6 +307,10 @@ class DeclarePerVertexBlocksTraverser : public TIntermTraverser
// A map of already replaced built-in variables.
VariableReplacementMap mVariableMap;
// Whether each field is invariant or precise.
PerVertexMemberFlags mPerVertexOutInvariantFlags;
PerVertexMemberFlags mPerVertexOutPreciseFlags;
};
void AddPerVertexDecl(TIntermBlock *root, const TVariable *variable)
......@@ -294,7 +338,54 @@ bool DeclarePerVertexBlocks(TCompiler *compiler, TIntermBlock *root, TSymbolTabl
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);
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