Commit 31641b8e by Shahbaz Youssefi Committed by Angle LUCI CQ

Declare specialization constants in the AST

A new qualifier (EvqSpecConst) is added so that specialization constants can be declared in tree. This enables TVariable references to specialization constants to be validated, which were in fact invalid as every reference declared a new variable. That is fixed. Bug: angleproject:4889 Change-Id: I1711e41cbc5a1260843d2d004d3568bdae11e963 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2941451Reviewed-by: 's avatarTim Van Patten <timvp@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
parent 82654e5d
......@@ -1030,6 +1030,11 @@ enum TQualifier
EvqTessEvaluationOut,
EvqTessCoord,
// A specialization constant, which is not valid GLSL ES, but is there to support Vulkan output
// generation. In that case, TLayoutQualifier::location will contain the somewhat equivalent
// constant_id.
EvqSpecConst,
// end of list
EvqLast
};
......@@ -1449,6 +1454,7 @@ inline const char *getQualifierString(TQualifier q)
case EvqTessEvaluationIn: return "in";
case EvqTessEvaluationOut: return "out";
case EvqTessCoord: return "TessCoord";
case EvqSpecConst: return "const";
default: UNREACHABLE(); return "unknown qualifier";
}
// clang-format on
......
......@@ -1603,8 +1603,12 @@ void TIntermBinary::promote()
TQualifier resultQualifier = EvqConst;
// Binary operations results in temporary variables unless both
// operands are const.
if (mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst)
// operands are const. If initializing a specialization constant, make the declarator also
// EvqSpecConst.
const bool isSpecConstInit = mOp == EOpInitialize && mLeft->getQualifier() == EvqSpecConst;
const bool isEitherNonConst =
mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst;
if (!isSpecConstInit && isEitherNonConst)
{
resultQualifier = EvqTemporary;
getTypePointer()->setQualifier(EvqTemporary);
......
......@@ -167,7 +167,7 @@ void TOutputGLSLBase::writeBuiltInFunctionTriplet(Visit visit,
// Outputs what goes inside layout(), except for location and binding qualifiers, as they are
// handled differently between GL GLSL and Vulkan GLSL.
std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermTyped *variable)
std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermSymbol *variable)
{
std::ostringstream out;
CommaSeparatedListItemPrefixGenerator listItemPrefix;
......@@ -245,7 +245,7 @@ std::string TOutputGLSLBase::getMemoryQualifiers(const TType &type)
return out.str();
}
void TOutputGLSLBase::writeLayoutQualifier(TIntermTyped *variable)
void TOutputGLSLBase::writeLayoutQualifier(TIntermSymbol *variable)
{
const TType &type = variable->getType();
......@@ -1033,16 +1033,27 @@ bool TOutputGLSLBase::visitDeclaration(Visit visit, TIntermDeclaration *node)
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *variable = sequence.front()->getAsTyped();
TIntermSymbol *symbolNode = variable->getAsSymbolNode();
if (!symbolNode || (symbolNode->getName() != "gl_ClipDistance" &&
symbolNode->getName() != "gl_CullDistance"))
if (symbolNode == nullptr)
{
ASSERT(variable->getAsBinaryNode() &&
variable->getAsBinaryNode()->getOp() == EOpInitialize);
symbolNode = variable->getAsBinaryNode()->getLeft()->getAsSymbolNode();
}
ASSERT(symbolNode);
if (symbolNode->getName() != "gl_ClipDistance" &&
symbolNode->getName() != "gl_CullDistance")
{
// gl_Clip/CullDistance re-declaration doesn't need layout.
writeLayoutQualifier(variable);
writeLayoutQualifier(symbolNode);
}
writeVariableType(variable->getType(), symbolNode ? &symbolNode->variable() : nullptr,
false);
if (variable->getAsSymbolNode() == nullptr ||
variable->getAsSymbolNode()->variable().symbolType() != SymbolType::Empty)
// Note: the TIntermDeclaration type is used for variable declaration instead of the
// TIntermSymbol one. The TIntermDeclaration type includes precision promotions from the
// right hand side that the symbol may be missing. This is an inconsistency in the tree
// that is too ingrained.
writeVariableType(variable->getType(), &symbolNode->variable(), false);
if (symbolNode->variable().symbolType() != SymbolType::Empty)
{
out << " ";
}
......
......@@ -41,9 +41,9 @@ class TOutputGLSLBase : public TIntermTraverser
TInfoSinkBase &objSink() { return mObjSink; }
void writeFloat(TInfoSinkBase &out, float f);
void writeTriplet(Visit visit, const char *preStr, const char *inStr, const char *postStr);
std::string getCommonLayoutQualifiers(TIntermTyped *variable);
std::string getCommonLayoutQualifiers(TIntermSymbol *variable);
std::string getMemoryQualifiers(const TType &type);
virtual void writeLayoutQualifier(TIntermTyped *variable);
virtual void writeLayoutQualifier(TIntermSymbol *variable);
void writeFieldLayoutQualifier(const TField *field);
void writeInvariantQualifier(const TType &type);
void writePreciseQualifier(const TType &type);
......
......@@ -44,9 +44,9 @@ TOutputVulkanGLSL::TOutputVulkanGLSL(TInfoSinkBase &objSink,
mEnablePrecision(enablePrecision)
{}
void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
void TOutputVulkanGLSL::writeLayoutQualifier(TIntermSymbol *symbol)
{
const TType &type = variable->getType();
const TType &type = symbol->getType();
bool needsSetBinding = IsSampler(type.getBasicType()) ||
(type.isInterfaceBlock() && (type.getQualifier() == EvqUniform ||
......@@ -56,9 +56,10 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
type.getQualifier() == EvqVertexIn ||
type.getQualifier() == EvqFragmentOut || IsVarying(type.getQualifier());
bool needsInputAttachmentIndex = IsSubpassInputType(type.getBasicType());
bool needsSpecConstId = type.getQualifier() == EvqSpecConst;
if (!NeedsToWriteLayoutQualifier(type) && !needsSetBinding && !needsLocation &&
!needsInputAttachmentIndex)
!needsInputAttachmentIndex && !needsSpecConstId)
{
return;
}
......@@ -68,9 +69,6 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
// This isn't super clean, but it gets the job done.
// See corresponding code in glslang_wrapper_utils.cpp.
TIntermSymbol *symbol = variable->getAsSymbolNode();
ASSERT(symbol);
const char *blockStorage = nullptr;
const char *matrixPacking = nullptr;
......@@ -112,6 +110,12 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
separator = kCommaSeparator;
}
// If it's a specialization constant, add that constant_id qualifier.
if (needsSpecConstId)
{
out << separator << "constant_id=" << layoutQualifier.location;
}
// If the resource declaration requires set & binding layout qualifiers, specify arbitrary
// ones.
if (needsSetBinding)
......@@ -134,7 +138,7 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
// Output the list of qualifiers already known at this stage, i.e. everything other than
// `location` and `set`/`binding`.
std::string otherQualifiers = getCommonLayoutQualifiers(variable);
std::string otherQualifiers = getCommonLayoutQualifiers(symbol);
if (blockStorage)
{
......
......@@ -46,7 +46,7 @@ class TOutputVulkanGLSL : public TOutputGLSL
}
protected:
void writeLayoutQualifier(TIntermTyped *variable) override;
void writeLayoutQualifier(TIntermSymbol *variable) override;
void writeVariableType(const TType &type,
const TSymbol *symbol,
bool isFunctionArgument) override;
......
......@@ -1293,7 +1293,8 @@ bool TranslatorVulkan::translateImpl(TInfoSinkBase &sink,
break;
}
specConst->outputLayoutString(sink);
specConst->declareSpecConsts(root);
mValidateASTOptions.validateSpecConstReferences = true;
// Gather specialization constant usage bits so that we can feedback to context.
mSpecConstUsageBits = specConst->getSpecConstUsageBits();
......
......@@ -279,12 +279,11 @@ bool ValidateAST::variableNeedsDeclaration(const TVariable *variable)
return false;
}
// Additionally, don't expect declaration for Vulkan specialization constants. There is no
// representation for them in the AST.
if (variable->symbolType() == SymbolType::AngleInternal &&
SpecConst::IsSpecConstName(variable->name()))
// Additionally, don't expect declaration for Vulkan specialization constants if not enabled.
// The declaration of these variables is deferred.
if (variable->getType().getQualifier() == EvqSpecConst)
{
return false;
return mOptions.validateSpecConstReferences;
}
return true;
......
......@@ -25,6 +25,9 @@ struct ValidateASTOptions
bool validateSingleParent = true;
// Check that all symbols reference TVariables that have been declared.
bool validateVariableReferences = true;
// Whether validateVariableReferences should also include specialization constants. Their
// declaration is output after their usage is discovered, so this is disabled until then.
bool validateSpecConstReferences = false;
// Check that all EOpCallFunctionInAST have their corresponding function definitions in the AST,
// with matching symbol ids. There should also be at least a prototype declaration before the
// function is called.
......
......@@ -231,10 +231,29 @@ TIntermTyped *CreateFloatArrayWithRotationIndex(const Vec2EnumMap &valuesEnumMap
return new TIntermBinary(EOpIndexDirect, array, rotation);
}
const TType *MakeSpecConst(const TType &type, vk::SpecializationConstantId id)
{
// Create a new type with the EvqSpecConst qualifier
TType *specConstType = new TType(type);
specConstType->setQualifier(EvqSpecConst);
// Set the constant_id of the spec const
TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
layoutQualifier.location = static_cast<int>(id);
specConstType->setLayoutQualifier(layoutQualifier);
return specConstType;
}
} // anonymous namespace
SpecConst::SpecConst(TSymbolTable *symbolTable, ShCompileOptions compileOptions, GLenum shaderType)
: mSymbolTable(symbolTable), mCompileOptions(compileOptions)
: mSymbolTable(symbolTable),
mCompileOptions(compileOptions),
mLineRasterEmulationVar(nullptr),
mSurfaceRotationVar(nullptr),
mDrawableWidthVar(nullptr),
mDrawableHeightVar(nullptr)
{
if (shaderType == GL_FRAGMENT_SHADER || shaderType == GL_COMPUTE_SHADER)
{
......@@ -251,33 +270,43 @@ SpecConst::SpecConst(TSymbolTable *symbolTable, ShCompileOptions compileOptions,
SpecConst::~SpecConst() {}
void SpecConst::outputLayoutString(TInfoSinkBase &sink) const
void SpecConst::declareSpecConsts(TIntermBlock *root)
{
// Add specialization constant declarations. The default value of the specialization
// constant is irrelevant, as it will be set when creating the pipeline.
// Only emit specialized const layout string if it has been referenced.
if (mUsageBits.test(vk::SpecConstUsage::LineRasterEmulation))
// Only emit specialized const declaration if it has been referenced.
if (mLineRasterEmulationVar != nullptr)
{
sink << "layout(constant_id="
<< static_cast<uint32_t>(vk::SpecializationConstantId::LineRasterEmulation)
<< ") const bool " << kLineRasterEmulationSpecConstVarName << " = false;\n\n";
TIntermDeclaration *decl = new TIntermDeclaration();
decl->appendDeclarator(
new TIntermBinary(EOpInitialize, getLineRasterEmulation(), CreateBoolNode(false)));
root->insertStatement(0, decl);
}
if (mUsageBits.test(vk::SpecConstUsage::YFlip) || mUsageBits.test(vk::SpecConstUsage::Rotation))
if (mSurfaceRotationVar != nullptr)
{
sink << "layout(constant_id="
<< static_cast<uint32_t>(vk::SpecializationConstantId::SurfaceRotation)
<< ") const uint " << kSurfaceRotationSpecConstVarName << " = 0;\n\n";
TIntermDeclaration *decl = new TIntermDeclaration();
decl->appendDeclarator(
new TIntermBinary(EOpInitialize, getFlipRotation(), CreateUIntNode(0)));
root->insertStatement(0, decl);
}
if (mUsageBits.test(vk::SpecConstUsage::DrawableSize))
if (mDrawableWidthVar != nullptr)
{
sink << "layout(constant_id="
<< static_cast<uint32_t>(vk::SpecializationConstantId::DrawableWidth)
<< ") const uint " << kDrawableWidthSpecConstVarName << " = 0;\n\n";
sink << "layout(constant_id="
<< static_cast<uint32_t>(vk::SpecializationConstantId::DrawableHeight)
<< ") const uint " << kDrawableHeightSpecConstVarName << " = 0;\n\n";
TIntermDeclaration *decl = new TIntermDeclaration();
decl->appendDeclarator(
new TIntermBinary(EOpInitialize, getDrawableWidth(), CreateFloatNode(0)));
root->insertStatement(0, decl);
}
if (mDrawableHeightVar != nullptr)
{
TIntermDeclaration *decl = new TIntermDeclaration();
decl->appendDeclarator(
new TIntermBinary(EOpInitialize, getDrawableHeight(), CreateFloatNode(0)));
root->insertStatement(1, decl);
}
}
......@@ -287,19 +316,29 @@ TIntermSymbol *SpecConst::getLineRasterEmulation()
{
return nullptr;
}
TVariable *specConstVar =
new TVariable(mSymbolTable, kLineRasterEmulationSpecConstVarName,
StaticType::GetBasic<EbtBool>(), SymbolType::AngleInternal);
mUsageBits.set(vk::SpecConstUsage::LineRasterEmulation);
return new TIntermSymbol(specConstVar);
if (mLineRasterEmulationVar == nullptr)
{
const TType *type = MakeSpecConst(*StaticType::GetBasic<EbtBool>(),
vk::SpecializationConstantId::LineRasterEmulation);
mLineRasterEmulationVar = new TVariable(mSymbolTable, kLineRasterEmulationSpecConstVarName,
type, SymbolType::AngleInternal);
mUsageBits.set(vk::SpecConstUsage::LineRasterEmulation);
}
return new TIntermSymbol(mLineRasterEmulationVar);
}
TIntermSymbol *SpecConst::getFlipRotation()
{
TVariable *specConstVar =
new TVariable(mSymbolTable, kSurfaceRotationSpecConstVarName,
StaticType::GetBasic<EbtUInt>(), SymbolType::AngleInternal);
return new TIntermSymbol(specConstVar);
if (mSurfaceRotationVar == nullptr)
{
const TType *type = MakeSpecConst(*StaticType::GetBasic<EbtUInt>(),
vk::SpecializationConstantId::SurfaceRotation);
mSurfaceRotationVar = new TVariable(mSymbolTable, kSurfaceRotationSpecConstVarName, type,
SymbolType::AngleInternal);
}
return new TIntermSymbol(mSurfaceRotationVar);
}
TIntermTyped *SpecConst::getMultiplierXForDFdx()
......@@ -448,20 +487,28 @@ TIntermTyped *SpecConst::getFragRotationMultiplyFlipXY()
TIntermSymbol *SpecConst::getDrawableWidth()
{
TVariable *widthSpecConstVar =
new TVariable(mSymbolTable, kDrawableWidthSpecConstVarName,
StaticType::GetBasic<EbtFloat>(), SymbolType::AngleInternal);
TIntermSymbol *drawableWidth = new TIntermSymbol(widthSpecConstVar);
return drawableWidth;
if (mDrawableWidthVar == nullptr)
{
const TType *type = MakeSpecConst(*StaticType::GetBasic<EbtFloat>(),
vk::SpecializationConstantId::DrawableWidth);
mDrawableWidthVar = new TVariable(mSymbolTable, kDrawableWidthSpecConstVarName, type,
SymbolType::AngleInternal);
}
return new TIntermSymbol(mDrawableWidthVar);
}
TIntermSymbol *SpecConst::getDrawableHeight()
{
TVariable *heightSpecConstVar =
new TVariable(mSymbolTable, kDrawableHeightSpecConstVarName,
StaticType::GetBasic<EbtFloat>(), SymbolType::AngleInternal);
TIntermSymbol *drawableHeight = new TIntermSymbol(heightSpecConstVar);
return drawableHeight;
if (mDrawableHeightVar == nullptr)
{
const TType *type = MakeSpecConst(*StaticType::GetBasic<EbtFloat>(),
vk::SpecializationConstantId::DrawableHeight);
mDrawableHeightVar = new TVariable(mSymbolTable, kDrawableHeightSpecConstVarName, type,
SymbolType::AngleInternal);
}
return new TIntermSymbol(mDrawableHeightVar);
}
TIntermBinary *SpecConst::getHalfRenderArea()
......@@ -491,11 +538,4 @@ TIntermBinary *SpecConst::getHalfRenderArea()
return rotatedHalfRenderArea;
}
bool SpecConst::IsSpecConstName(const ImmutableString &name)
{
return name == kLineRasterEmulationSpecConstVarName ||
name == kSurfaceRotationSpecConstVarName || name == kDrawableWidthSpecConstVarName ||
name == kDrawableHeightSpecConstVarName;
}
} // namespace sh
......@@ -13,6 +13,7 @@
#include "compiler/translator/Compiler.h"
#include "compiler/translator/SymbolTable.h"
class TIntermBlock;
class TIntermTyped;
class TIntermSymbol;
class TVariable;
......@@ -44,11 +45,9 @@ class SpecConst
// Half render area
TIntermBinary *getHalfRenderArea();
void outputLayoutString(TInfoSinkBase &sink) const;
void declareSpecConsts(TIntermBlock *root);
SpecConstUsageBits getSpecConstUsageBits() const { return mUsageBits; }
static bool IsSpecConstName(const ImmutableString &name);
private:
TIntermSymbol *getFlipRotation();
TIntermTyped *getNegFlipY();
......@@ -60,6 +59,11 @@ class SpecConst
TSymbolTable *mSymbolTable;
ShCompileOptions mCompileOptions;
TVariable *mLineRasterEmulationVar;
TVariable *mSurfaceRotationVar;
TVariable *mDrawableWidthVar;
TVariable *mDrawableHeightVar;
// Bit is set if YFlip or Rotation has been used
SpecConstUsageBits mUsageBits;
};
......
......@@ -7777,10 +7777,10 @@ void ShaderProgramHelper::setSpecializationConstant(sh::vk::SpecializationConsta
mSpecializationConstants.surfaceRotation = value;
break;
case sh::vk::SpecializationConstantId::DrawableWidth:
mSpecializationConstants.drawableWidth = value;
mSpecializationConstants.drawableWidth = static_cast<float>(value);
break;
case sh::vk::SpecializationConstantId::DrawableHeight:
mSpecializationConstants.drawableHeight = value;
mSpecializationConstants.drawableHeight = static_cast<float>(value);
break;
default:
UNREACHABLE();
......
......@@ -727,8 +727,8 @@ struct SpecializationConstants final
{
VkBool32 lineRasterEmulation;
uint32_t surfaceRotation;
uint32_t drawableWidth;
uint32_t drawableHeight;
float drawableWidth;
float drawableHeight;
};
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS
......
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