glslang-zig/glslang/MachineIndependent/ParseHelper.cpp
Pankaj Mistry 5ff0c048b7
Clean the implementation of GL_EXT_texture_shadow_lod.
Move the parameter verifictation to a centralized place where all the builtins
are verified for correctness.

Add verification for the new builtins with version and extension check
These builtins are supported on GLSL since version 130 and GLES since
version 300.
2023-10-02 15:10:11 -04:00

9768 lines
454 KiB
C++

//
// Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
// Copyright (C) 2012-2015 LunarG, Inc.
// Copyright (C) 2015-2018 Google, Inc.
// Copyright (C) 2017, 2019 ARM Limited.
// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
#include "ParseHelper.h"
#include "Initialize.h"
#include "Scan.h"
#include "../OSDependent/osinclude.h"
#include <algorithm>
#include "preprocessor/PpContext.h"
extern int yyparse(glslang::TParseContext*);
namespace glslang {
TParseContext::TParseContext(TSymbolTable& symbolTable, TIntermediate& interm, bool parsingBuiltins,
int version, EProfile profile, const SpvVersion& spvVersion, EShLanguage language,
TInfoSink& infoSink, bool forwardCompatible, EShMessages messages,
const TString* entryPoint) :
TParseContextBase(symbolTable, interm, parsingBuiltins, version, profile, spvVersion, language,
infoSink, forwardCompatible, messages, entryPoint),
inMain(false),
blockName(nullptr),
limits(resources.limits),
atomicUintOffsets(nullptr), anyIndexLimits(false)
{
// decide whether precision qualifiers should be ignored or respected
if (isEsProfile() || spvVersion.vulkan > 0) {
precisionManager.respectPrecisionQualifiers();
if (! parsingBuiltins && language == EShLangFragment && !isEsProfile() && spvVersion.vulkan > 0)
precisionManager.warnAboutDefaults();
}
setPrecisionDefaults();
globalUniformDefaults.clear();
globalUniformDefaults.layoutMatrix = ElmColumnMajor;
globalUniformDefaults.layoutPacking = spvVersion.spv != 0 ? ElpStd140 : ElpShared;
globalBufferDefaults.clear();
globalBufferDefaults.layoutMatrix = ElmColumnMajor;
globalBufferDefaults.layoutPacking = spvVersion.spv != 0 ? ElpStd430 : ElpShared;
globalInputDefaults.clear();
globalOutputDefaults.clear();
globalSharedDefaults.clear();
globalSharedDefaults.layoutMatrix = ElmColumnMajor;
globalSharedDefaults.layoutPacking = ElpStd430;
// "Shaders in the transform
// feedback capturing mode have an initial global default of
// layout(xfb_buffer = 0) out;"
if (language == EShLangVertex ||
language == EShLangTessControl ||
language == EShLangTessEvaluation ||
language == EShLangGeometry)
globalOutputDefaults.layoutXfbBuffer = 0;
if (language == EShLangGeometry)
globalOutputDefaults.layoutStream = 0;
if (entryPoint != nullptr && entryPoint->size() > 0 && *entryPoint != "main")
infoSink.info.message(EPrefixError, "Source entry point must be \"main\"");
}
TParseContext::~TParseContext()
{
delete [] atomicUintOffsets;
}
// Set up all default precisions as needed by the current environment.
// Intended just as a TParseContext constructor helper.
void TParseContext::setPrecisionDefaults()
{
// Set all precision defaults to EpqNone, which is correct for all types
// when not obeying precision qualifiers, and correct for types that don't
// have defaults (thus getting an error on use) when obeying precision
// qualifiers.
for (int type = 0; type < EbtNumTypes; ++type)
defaultPrecision[type] = EpqNone;
for (int type = 0; type < maxSamplerIndex; ++type)
defaultSamplerPrecision[type] = EpqNone;
// replace with real precision defaults for those that have them
if (obeyPrecisionQualifiers()) {
if (isEsProfile()) {
// Most don't have defaults, a few default to lowp.
TSampler sampler;
sampler.set(EbtFloat, Esd2D);
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
sampler.set(EbtFloat, EsdCube);
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
sampler.set(EbtFloat, Esd2D);
sampler.setExternal(true);
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
}
// If we are parsing built-in computational variables/functions, it is meaningful to record
// whether the built-in has no precision qualifier, as that ambiguity
// is used to resolve the precision from the supplied arguments/operands instead.
// So, we don't actually want to replace EpqNone with a default precision for built-ins.
if (! parsingBuiltins) {
if (isEsProfile() && language == EShLangFragment) {
defaultPrecision[EbtInt] = EpqMedium;
defaultPrecision[EbtUint] = EpqMedium;
} else {
defaultPrecision[EbtInt] = EpqHigh;
defaultPrecision[EbtUint] = EpqHigh;
defaultPrecision[EbtFloat] = EpqHigh;
}
if (!isEsProfile()) {
// Non-ES profile
// All sampler precisions default to highp.
for (int type = 0; type < maxSamplerIndex; ++type)
defaultSamplerPrecision[type] = EpqHigh;
}
}
defaultPrecision[EbtSampler] = EpqLow;
defaultPrecision[EbtAtomicUint] = EpqHigh;
}
}
void TParseContext::setLimits(const TBuiltInResource& r)
{
resources = r;
intermediate.setLimits(r);
anyIndexLimits = ! limits.generalAttributeMatrixVectorIndexing ||
! limits.generalConstantMatrixVectorIndexing ||
! limits.generalSamplerIndexing ||
! limits.generalUniformIndexing ||
! limits.generalVariableIndexing ||
! limits.generalVaryingIndexing;
// "Each binding point tracks its own current default offset for
// inheritance of subsequent variables using the same binding. The initial state of compilation is that all
// binding points have an offset of 0."
atomicUintOffsets = new int[resources.maxAtomicCounterBindings];
for (int b = 0; b < resources.maxAtomicCounterBindings; ++b)
atomicUintOffsets[b] = 0;
}
//
// Parse an array of strings using yyparse, going through the
// preprocessor to tokenize the shader strings, then through
// the GLSL scanner.
//
// Returns true for successful acceptance of the shader, false if any errors.
//
bool TParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner& input, bool versionWillBeError)
{
currentScanner = &input;
ppContext.setInput(input, versionWillBeError);
yyparse(this);
finish();
return numErrors == 0;
}
// This is called from bison when it has a parse (syntax) error
// Note though that to stop cascading errors, we set EOF, which
// will usually cause a syntax error, so be more accurate that
// compilation is terminating.
void TParseContext::parserError(const char* s)
{
if (! getScanner()->atEndOfInput() || numErrors == 0)
error(getCurrentLoc(), "", "", s, "");
else
error(getCurrentLoc(), "compilation terminated", "", "");
}
void TParseContext::growGlobalUniformBlock(const TSourceLoc& loc, TType& memberType, const TString& memberName, TTypeList* typeList)
{
bool createBlock = globalUniformBlock == nullptr;
if (createBlock) {
globalUniformBinding = intermediate.getGlobalUniformBinding();
globalUniformSet = intermediate.getGlobalUniformSet();
}
// use base class function to create/expand block
TParseContextBase::growGlobalUniformBlock(loc, memberType, memberName, typeList);
if (spvVersion.vulkan > 0 && spvVersion.vulkanRelaxed) {
// check for a block storage override
TBlockStorageClass storageOverride = intermediate.getBlockStorageOverride(getGlobalUniformBlockName());
TQualifier& qualifier = globalUniformBlock->getWritableType().getQualifier();
qualifier.defaultBlock = true;
if (storageOverride != EbsNone) {
if (createBlock) {
// Remap block storage
qualifier.setBlockStorage(storageOverride);
// check that the change didn't create errors
blockQualifierCheck(loc, qualifier, false);
}
// remap meber storage as well
memberType.getQualifier().setBlockStorage(storageOverride);
}
}
}
void TParseContext::growAtomicCounterBlock(int binding, const TSourceLoc& loc, TType& memberType, const TString& memberName, TTypeList* typeList)
{
bool createBlock = atomicCounterBuffers.find(binding) == atomicCounterBuffers.end();
if (createBlock) {
atomicCounterBlockSet = intermediate.getAtomicCounterBlockSet();
}
// use base class function to create/expand block
TParseContextBase::growAtomicCounterBlock(binding, loc, memberType, memberName, typeList);
TQualifier& qualifier = atomicCounterBuffers[binding]->getWritableType().getQualifier();
qualifier.defaultBlock = true;
if (spvVersion.vulkan > 0 && spvVersion.vulkanRelaxed) {
// check for a Block storage override
TBlockStorageClass storageOverride = intermediate.getBlockStorageOverride(getAtomicCounterBlockName());
if (storageOverride != EbsNone) {
if (createBlock) {
// Remap block storage
qualifier.setBlockStorage(storageOverride);
// check that the change didn't create errors
blockQualifierCheck(loc, qualifier, false);
}
// remap meber storage as well
memberType.getQualifier().setBlockStorage(storageOverride);
}
}
}
const char* TParseContext::getGlobalUniformBlockName() const
{
const char* name = intermediate.getGlobalUniformBlockName();
if (std::string(name) == "")
return "gl_DefaultUniformBlock";
else
return name;
}
void TParseContext::finalizeGlobalUniformBlockLayout(TVariable&)
{
}
void TParseContext::setUniformBlockDefaults(TType& block) const
{
block.getQualifier().layoutPacking = ElpStd140;
block.getQualifier().layoutMatrix = ElmColumnMajor;
}
const char* TParseContext::getAtomicCounterBlockName() const
{
const char* name = intermediate.getAtomicCounterBlockName();
if (std::string(name) == "")
return "gl_AtomicCounterBlock";
else
return name;
}
void TParseContext::finalizeAtomicCounterBlockLayout(TVariable&)
{
}
void TParseContext::setAtomicCounterBlockDefaults(TType& block) const
{
block.getQualifier().layoutPacking = ElpStd430;
block.getQualifier().layoutMatrix = ElmRowMajor;
}
void TParseContext::setInvariant(const TSourceLoc& loc, const char* builtin) {
TSymbol* symbol = symbolTable.find(builtin);
if (symbol && symbol->getType().getQualifier().isPipeOutput()) {
if (intermediate.inIoAccessed(builtin))
warn(loc, "changing qualification after use", "invariant", builtin);
TSymbol* csymbol = symbolTable.copyUp(symbol);
csymbol->getWritableType().getQualifier().invariant = true;
}
}
void TParseContext::handlePragma(const TSourceLoc& loc, const TVector<TString>& tokens)
{
if (pragmaCallback)
pragmaCallback(loc.line, tokens);
if (tokens.size() == 0)
return;
if (tokens[0].compare("optimize") == 0) {
if (tokens.size() != 4) {
error(loc, "optimize pragma syntax is incorrect", "#pragma", "");
return;
}
if (tokens[1].compare("(") != 0) {
error(loc, "\"(\" expected after 'optimize' keyword", "#pragma", "");
return;
}
if (tokens[2].compare("on") == 0)
contextPragma.optimize = true;
else if (tokens[2].compare("off") == 0)
contextPragma.optimize = false;
else {
if(relaxedErrors())
// If an implementation does not recognize the tokens following #pragma, then it will ignore that pragma.
warn(loc, "\"on\" or \"off\" expected after '(' for 'optimize' pragma", "#pragma", "");
return;
}
if (tokens[3].compare(")") != 0) {
error(loc, "\")\" expected to end 'optimize' pragma", "#pragma", "");
return;
}
} else if (tokens[0].compare("debug") == 0) {
if (tokens.size() != 4) {
error(loc, "debug pragma syntax is incorrect", "#pragma", "");
return;
}
if (tokens[1].compare("(") != 0) {
error(loc, "\"(\" expected after 'debug' keyword", "#pragma", "");
return;
}
if (tokens[2].compare("on") == 0)
contextPragma.debug = true;
else if (tokens[2].compare("off") == 0)
contextPragma.debug = false;
else {
if(relaxedErrors())
// If an implementation does not recognize the tokens following #pragma, then it will ignore that pragma.
warn(loc, "\"on\" or \"off\" expected after '(' for 'debug' pragma", "#pragma", "");
return;
}
if (tokens[3].compare(")") != 0) {
error(loc, "\")\" expected to end 'debug' pragma", "#pragma", "");
return;
}
} else if (spvVersion.spv > 0 && tokens[0].compare("use_storage_buffer") == 0) {
if (tokens.size() != 1)
error(loc, "extra tokens", "#pragma", "");
intermediate.setUseStorageBuffer();
} else if (spvVersion.spv > 0 && tokens[0].compare("use_vulkan_memory_model") == 0) {
if (tokens.size() != 1)
error(loc, "extra tokens", "#pragma", "");
intermediate.setUseVulkanMemoryModel();
} else if (spvVersion.spv > 0 && tokens[0].compare("use_variable_pointers") == 0) {
if (tokens.size() != 1)
error(loc, "extra tokens", "#pragma", "");
if (spvVersion.spv < glslang::EShTargetSpv_1_3)
error(loc, "requires SPIR-V 1.3", "#pragma use_variable_pointers", "");
intermediate.setUseVariablePointers();
} else if (tokens[0].compare("once") == 0) {
warn(loc, "not implemented", "#pragma once", "");
} else if (tokens[0].compare("glslang_binary_double_output") == 0) {
intermediate.setBinaryDoubleOutput();
} else if (spvVersion.spv > 0 && tokens[0].compare("STDGL") == 0 &&
tokens[1].compare("invariant") == 0 && tokens[3].compare("all") == 0) {
intermediate.setInvariantAll();
// Set all builtin out variables invariant if declared
setInvariant(loc, "gl_Position");
setInvariant(loc, "gl_PointSize");
setInvariant(loc, "gl_ClipDistance");
setInvariant(loc, "gl_CullDistance");
setInvariant(loc, "gl_TessLevelOuter");
setInvariant(loc, "gl_TessLevelInner");
setInvariant(loc, "gl_PrimitiveID");
setInvariant(loc, "gl_Layer");
setInvariant(loc, "gl_ViewportIndex");
setInvariant(loc, "gl_FragDepth");
setInvariant(loc, "gl_SampleMask");
setInvariant(loc, "gl_ClipVertex");
setInvariant(loc, "gl_FrontColor");
setInvariant(loc, "gl_BackColor");
setInvariant(loc, "gl_FrontSecondaryColor");
setInvariant(loc, "gl_BackSecondaryColor");
setInvariant(loc, "gl_TexCoord");
setInvariant(loc, "gl_FogFragCoord");
setInvariant(loc, "gl_FragColor");
setInvariant(loc, "gl_FragData");
}
}
//
// Handle seeing a variable identifier in the grammar.
//
TIntermTyped* TParseContext::handleVariable(const TSourceLoc& loc, TSymbol* symbol, const TString* string)
{
TIntermTyped* node = nullptr;
// Error check for requiring specific extensions present.
if (symbol && symbol->getNumExtensions())
requireExtensions(loc, symbol->getNumExtensions(), symbol->getExtensions(), symbol->getName().c_str());
if (symbol && symbol->isReadOnly()) {
// All shared things containing an unsized array must be copied up
// on first use, so that all future references will share its array structure,
// so that editing the implicit size will effect all nodes consuming it,
// and so that editing the implicit size won't change the shared one.
//
// If this is a variable or a block, check it and all it contains, but if this
// is a member of an anonymous block, check the whole block, as the whole block
// will need to be copied up if it contains an unsized array.
//
// This check is being done before the block-name check further down, so guard
// for that too.
if (!symbol->getType().isUnusableName()) {
if (symbol->getType().containsUnsizedArray() ||
(symbol->getAsAnonMember() &&
symbol->getAsAnonMember()->getAnonContainer().getType().containsUnsizedArray()))
makeEditable(symbol);
}
}
const TVariable* variable;
const TAnonMember* anon = symbol ? symbol->getAsAnonMember() : nullptr;
if (anon) {
// It was a member of an anonymous container.
// Create a subtree for its dereference.
variable = anon->getAnonContainer().getAsVariable();
TIntermTyped* container = intermediate.addSymbol(*variable, loc);
TIntermTyped* constNode = intermediate.addConstantUnion(anon->getMemberNumber(), loc);
node = intermediate.addIndex(EOpIndexDirectStruct, container, constNode, loc);
node->setType(*(*variable->getType().getStruct())[anon->getMemberNumber()].type);
if (node->getType().hiddenMember())
error(loc, "member of nameless block was not redeclared", string->c_str(), "");
} else {
// Not a member of an anonymous container.
// The symbol table search was done in the lexical phase.
// See if it was a variable.
variable = symbol ? symbol->getAsVariable() : nullptr;
if (variable) {
if (variable->getType().isUnusableName()) {
error(loc, "cannot be used (maybe an instance name is needed)", string->c_str(), "");
variable = nullptr;
}
if (language == EShLangMesh && variable) {
TLayoutGeometry primitiveType = intermediate.getOutputPrimitive();
if ((variable->getMangledName() == "gl_PrimitiveTriangleIndicesEXT" && primitiveType != ElgTriangles) ||
(variable->getMangledName() == "gl_PrimitiveLineIndicesEXT" && primitiveType != ElgLines) ||
(variable->getMangledName() == "gl_PrimitivePointIndicesEXT" && primitiveType != ElgPoints)) {
error(loc, "cannot be used (ouput primitive type mismatch)", string->c_str(), "");
variable = nullptr;
}
}
} else {
if (symbol)
error(loc, "variable name expected", string->c_str(), "");
}
// Recovery, if it wasn't found or was not a variable.
if (! variable)
variable = new TVariable(string, TType(EbtVoid));
if (variable->getType().getQualifier().isFrontEndConstant())
node = intermediate.addConstantUnion(variable->getConstArray(), variable->getType(), loc);
else
node = intermediate.addSymbol(*variable, loc);
}
if (variable->getType().getQualifier().isIo())
intermediate.addIoAccessed(*string);
if (variable->getType().isReference() &&
variable->getType().getQualifier().bufferReferenceNeedsVulkanMemoryModel()) {
intermediate.setUseVulkanMemoryModel();
}
return node;
}
//
// Handle seeing a base[index] dereference in the grammar.
//
TIntermTyped* TParseContext::handleBracketDereference(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
{
int indexValue = 0;
if (index->getQualifier().isFrontEndConstant())
indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst();
// basic type checks...
variableCheck(base);
if (! base->isArray() && ! base->isMatrix() && ! base->isVector() && ! base->getType().isCoopMat() &&
! base->isReference()) {
if (base->getAsSymbolNode())
error(loc, " left of '[' is not of type array, matrix, or vector ", base->getAsSymbolNode()->getName().c_str(), "");
else
error(loc, " left of '[' is not of type array, matrix, or vector ", "expression", "");
// Insert dummy error-recovery result
return intermediate.addConstantUnion(0.0, EbtFloat, loc);
}
if (!base->isArray() && base->isVector()) {
if (base->getType().contains16BitFloat())
requireFloat16Arithmetic(loc, "[", "does not operate on types containing float16");
if (base->getType().contains16BitInt())
requireInt16Arithmetic(loc, "[", "does not operate on types containing (u)int16");
if (base->getType().contains8BitInt())
requireInt8Arithmetic(loc, "[", "does not operate on types containing (u)int8");
}
// check for constant folding
if (base->getType().getQualifier().isFrontEndConstant() && index->getQualifier().isFrontEndConstant()) {
// both base and index are front-end constants
checkIndex(loc, base->getType(), indexValue);
return intermediate.foldDereference(base, indexValue, loc);
}
// at least one of base and index is not a front-end constant variable...
TIntermTyped* result = nullptr;
if (base->isReference() && ! base->isArray()) {
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference2, "buffer reference indexing");
if (base->getType().getReferentType()->containsUnsizedArray()) {
error(loc, "cannot index reference to buffer containing an unsized array", "", "");
result = nullptr;
} else {
result = intermediate.addBinaryMath(EOpAdd, base, index, loc);
if (result != nullptr)
result->setType(base->getType());
}
if (result == nullptr) {
error(loc, "cannot index buffer reference", "", "");
result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
}
return result;
}
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
handleIoResizeArrayAccess(loc, base);
if (index->getQualifier().isFrontEndConstant())
checkIndex(loc, base->getType(), indexValue);
if (index->getQualifier().isFrontEndConstant()) {
if (base->getType().isUnsizedArray()) {
base->getWritableType().updateImplicitArraySize(indexValue + 1);
base->getWritableType().setImplicitlySized(true);
if (base->getQualifier().builtIn == EbvClipDistance &&
indexValue >= resources.maxClipDistances) {
error(loc, "gl_ClipDistance", "[", "array index out of range '%d'", indexValue);
}
else if (base->getQualifier().builtIn == EbvCullDistance &&
indexValue >= resources.maxCullDistances) {
error(loc, "gl_CullDistance", "[", "array index out of range '%d'", indexValue);
}
// For 2D per-view builtin arrays, update the inner dimension size in parent type
if (base->getQualifier().isPerView() && base->getQualifier().builtIn != EbvNone) {
TIntermBinary* binaryNode = base->getAsBinaryNode();
if (binaryNode) {
TType& leftType = binaryNode->getLeft()->getWritableType();
TArraySizes& arraySizes = *leftType.getArraySizes();
assert(arraySizes.getNumDims() == 2);
arraySizes.setDimSize(1, std::max(arraySizes.getDimSize(1), indexValue + 1));
}
}
} else
checkIndex(loc, base->getType(), indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
if (base->getType().isUnsizedArray()) {
// we have a variable index into an unsized array, which is okay,
// depending on the situation
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
error(loc, "", "[", "array must be sized by a redeclaration or layout qualifier before being indexed with a variable");
else {
// it is okay for a run-time sized array
checkRuntimeSizable(loc, *base);
}
base->getWritableType().setArrayVariablyIndexed();
}
if (base->getBasicType() == EbtBlock) {
if (base->getQualifier().storage == EvqBuffer)
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing buffer block array");
else if (base->getQualifier().storage == EvqUniform)
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5,
"variable indexing uniform block array");
else {
// input/output blocks either don't exist or can't be variably indexed
}
} else if (language == EShLangFragment && base->getQualifier().isPipeOutput())
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing fragment shader output array");
else if (base->getBasicType() == EbtSampler && version >= 130) {
const char* explanation = "variable indexing sampler array";
requireProfile(base->getLoc(), EEsProfile | ECoreProfile | ECompatibilityProfile, explanation);
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5, explanation);
profileRequires(base->getLoc(), ECoreProfile | ECompatibilityProfile, 400, nullptr, explanation);
}
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
// Insert valid dereferenced result type
TType newType(base->getType(), 0);
if (base->getType().getQualifier().isConstant() && index->getQualifier().isConstant()) {
newType.getQualifier().storage = EvqConst;
// If base or index is a specialization constant, the result should also be a specialization constant.
if (base->getType().getQualifier().isSpecConstant() || index->getQualifier().isSpecConstant()) {
newType.getQualifier().makeSpecConstant();
}
} else {
newType.getQualifier().storage = EvqTemporary;
newType.getQualifier().specConstant = false;
}
result->setType(newType);
inheritMemoryQualifiers(base->getQualifier(), result->getWritableType().getQualifier());
// Propagate nonuniform
if (base->getQualifier().isNonUniform() || index->getQualifier().isNonUniform())
result->getWritableType().getQualifier().nonUniform = true;
if (anyIndexLimits)
handleIndexLimits(loc, base, index);
return result;
}
// for ES 2.0 (version 100) limitations for almost all index operations except vertex-shader uniforms
void TParseContext::handleIndexLimits(const TSourceLoc& /*loc*/, TIntermTyped* base, TIntermTyped* index)
{
if ((! limits.generalSamplerIndexing && base->getBasicType() == EbtSampler) ||
(! limits.generalUniformIndexing && base->getQualifier().isUniformOrBuffer() && language != EShLangVertex) ||
(! limits.generalAttributeMatrixVectorIndexing && base->getQualifier().isPipeInput() && language == EShLangVertex && (base->getType().isMatrix() || base->getType().isVector())) ||
(! limits.generalConstantMatrixVectorIndexing && base->getAsConstantUnion()) ||
(! limits.generalVariableIndexing && ! base->getType().getQualifier().isUniformOrBuffer() &&
! base->getType().getQualifier().isPipeInput() &&
! base->getType().getQualifier().isPipeOutput() &&
! base->getType().getQualifier().isConstant()) ||
(! limits.generalVaryingIndexing && (base->getType().getQualifier().isPipeInput() ||
base->getType().getQualifier().isPipeOutput()))) {
// it's too early to know what the inductive variables are, save it for post processing
needsIndexLimitationChecking.push_back(index);
}
}
// Make a shared symbol have a non-shared version that can be edited by the current
// compile, such that editing its type will not change the shared version and will
// effect all nodes sharing it.
void TParseContext::makeEditable(TSymbol*& symbol)
{
TParseContextBase::makeEditable(symbol);
// See if it's tied to IO resizing
if (isIoResizeArray(symbol->getType()))
ioArraySymbolResizeList.push_back(symbol);
}
// Return true if this is a geometry shader input array or tessellation control output array
// or mesh shader output array.
bool TParseContext::isIoResizeArray(const TType& type) const
{
return type.isArray() &&
((language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn) ||
(language == EShLangTessControl && type.getQualifier().storage == EvqVaryingOut &&
! type.getQualifier().patch) ||
(language == EShLangFragment && type.getQualifier().storage == EvqVaryingIn &&
(type.getQualifier().pervertexNV || type.getQualifier().pervertexEXT)) ||
(language == EShLangMesh && type.getQualifier().storage == EvqVaryingOut &&
!type.getQualifier().perTaskNV));
}
// If an array is not isIoResizeArray() but is an io array, make sure it has the right size
void TParseContext::fixIoArraySize(const TSourceLoc& loc, TType& type)
{
if (! type.isArray() || type.getQualifier().patch || symbolTable.atBuiltInLevel())
return;
assert(! isIoResizeArray(type));
if (type.getQualifier().storage != EvqVaryingIn || type.getQualifier().patch)
return;
if (language == EShLangTessControl || language == EShLangTessEvaluation) {
if (type.getOuterArraySize() != resources.maxPatchVertices) {
if (type.isSizedArray())
error(loc, "tessellation input array size must be gl_MaxPatchVertices or implicitly sized", "[]", "");
type.changeOuterArraySize(resources.maxPatchVertices);
}
}
}
// Issue any errors if the non-array object is missing arrayness WRT
// shader I/O that has array requirements.
// All arrayness checking is handled in array paths, this is for
void TParseContext::ioArrayCheck(const TSourceLoc& loc, const TType& type, const TString& identifier)
{
if (! type.isArray() && ! symbolTable.atBuiltInLevel()) {
if (type.getQualifier().isArrayedIo(language) && !type.getQualifier().layoutPassthrough)
error(loc, "type must be an array:", type.getStorageQualifierString(), identifier.c_str());
}
}
// Handle a dereference of a geometry shader input array or tessellation control output array.
// See ioArraySymbolResizeList comment in ParseHelper.h.
//
void TParseContext::handleIoResizeArrayAccess(const TSourceLoc& /*loc*/, TIntermTyped* base)
{
TIntermSymbol* symbolNode = base->getAsSymbolNode();
assert(symbolNode);
if (! symbolNode)
return;
// fix array size, if it can be fixed and needs to be fixed (will allow variable indexing)
if (symbolNode->getType().isUnsizedArray()) {
int newSize = getIoArrayImplicitSize(symbolNode->getType().getQualifier());
if (newSize > 0)
symbolNode->getWritableType().changeOuterArraySize(newSize);
}
}
// If there has been an input primitive declaration (geometry shader) or an output
// number of vertices declaration(tessellation shader), make sure all input array types
// match it in size. Types come either from nodes in the AST or symbols in the
// symbol table.
//
// Types without an array size will be given one.
// Types already having a size that is wrong will get an error.
//
void TParseContext::checkIoArraysConsistency(const TSourceLoc &loc, bool tailOnly)
{
int requiredSize = 0;
TString featureString;
size_t listSize = ioArraySymbolResizeList.size();
size_t i = 0;
// If tailOnly = true, only check the last array symbol in the list.
if (tailOnly) {
i = listSize - 1;
}
for (bool firstIteration = true; i < listSize; ++i) {
TType &type = ioArraySymbolResizeList[i]->getWritableType();
// As I/O array sizes don't change, fetch requiredSize only once,
// except for mesh shaders which could have different I/O array sizes based on type qualifiers.
if (firstIteration || (language == EShLangMesh)) {
requiredSize = getIoArrayImplicitSize(type.getQualifier(), &featureString);
if (requiredSize == 0)
break;
firstIteration = false;
}
checkIoArrayConsistency(loc, requiredSize, featureString.c_str(), type,
ioArraySymbolResizeList[i]->getName());
}
}
int TParseContext::getIoArrayImplicitSize(const TQualifier &qualifier, TString *featureString) const
{
int expectedSize = 0;
TString str = "unknown";
unsigned int maxVertices = intermediate.getVertices() != TQualifier::layoutNotSet ? intermediate.getVertices() : 0;
if (language == EShLangGeometry) {
expectedSize = TQualifier::mapGeometryToSize(intermediate.getInputPrimitive());
str = TQualifier::getGeometryString(intermediate.getInputPrimitive());
}
else if (language == EShLangTessControl) {
expectedSize = maxVertices;
str = "vertices";
} else if (language == EShLangFragment) {
// Number of vertices for Fragment shader is always three.
expectedSize = 3;
str = "vertices";
} else if (language == EShLangMesh) {
unsigned int maxPrimitives =
intermediate.getPrimitives() != TQualifier::layoutNotSet ? intermediate.getPrimitives() : 0;
if (qualifier.builtIn == EbvPrimitiveIndicesNV) {
expectedSize = maxPrimitives * TQualifier::mapGeometryToSize(intermediate.getOutputPrimitive());
str = "max_primitives*";
str += TQualifier::getGeometryString(intermediate.getOutputPrimitive());
}
else if (qualifier.builtIn == EbvPrimitiveTriangleIndicesEXT || qualifier.builtIn == EbvPrimitiveLineIndicesEXT ||
qualifier.builtIn == EbvPrimitivePointIndicesEXT) {
expectedSize = maxPrimitives;
str = "max_primitives";
}
else if (qualifier.isPerPrimitive()) {
expectedSize = maxPrimitives;
str = "max_primitives";
}
else {
expectedSize = maxVertices;
str = "max_vertices";
}
}
if (featureString)
*featureString = str;
return expectedSize;
}
void TParseContext::checkIoArrayConsistency(const TSourceLoc& loc, int requiredSize, const char* feature, TType& type, const TString& name)
{
if (type.isUnsizedArray())
type.changeOuterArraySize(requiredSize);
else if (type.getOuterArraySize() != requiredSize) {
if (language == EShLangGeometry)
error(loc, "inconsistent input primitive for array size of", feature, name.c_str());
else if (language == EShLangTessControl)
error(loc, "inconsistent output number of vertices for array size of", feature, name.c_str());
else if (language == EShLangFragment) {
if (type.getOuterArraySize() > requiredSize)
error(loc, " cannot be greater than 3 for pervertexEXT", feature, name.c_str());
}
else if (language == EShLangMesh)
error(loc, "inconsistent output array size of", feature, name.c_str());
else
assert(0);
}
}
// Handle seeing a binary node with a math operation.
// Returns nullptr if not semantically allowed.
TIntermTyped* TParseContext::handleBinaryMath(const TSourceLoc& loc, const char* str, TOperator op, TIntermTyped* left, TIntermTyped* right)
{
rValueErrorCheck(loc, str, left->getAsTyped());
rValueErrorCheck(loc, str, right->getAsTyped());
bool allowed = true;
switch (op) {
// TODO: Bring more source language-specific checks up from intermediate.cpp
// to the specific parse helpers for that source language.
case EOpLessThan:
case EOpGreaterThan:
case EOpLessThanEqual:
case EOpGreaterThanEqual:
if (! left->isScalar() || ! right->isScalar())
allowed = false;
break;
default:
break;
}
if (((left->getType().contains16BitFloat() || right->getType().contains16BitFloat()) && !float16Arithmetic()) ||
((left->getType().contains16BitInt() || right->getType().contains16BitInt()) && !int16Arithmetic()) ||
((left->getType().contains8BitInt() || right->getType().contains8BitInt()) && !int8Arithmetic())) {
allowed = false;
}
TIntermTyped* result = nullptr;
if (allowed) {
if ((left->isReference() || right->isReference()))
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference2, "buffer reference math");
result = intermediate.addBinaryMath(op, left, right, loc);
}
if (result == nullptr) {
bool enhanced = intermediate.getEnhancedMsgs();
binaryOpError(loc, str, left->getCompleteString(enhanced), right->getCompleteString(enhanced));
}
return result;
}
// Handle seeing a unary node with a math operation.
TIntermTyped* TParseContext::handleUnaryMath(const TSourceLoc& loc, const char* str, TOperator op, TIntermTyped* childNode)
{
rValueErrorCheck(loc, str, childNode);
bool allowed = true;
if ((childNode->getType().contains16BitFloat() && !float16Arithmetic()) ||
(childNode->getType().contains16BitInt() && !int16Arithmetic()) ||
(childNode->getType().contains8BitInt() && !int8Arithmetic())) {
allowed = false;
}
TIntermTyped* result = nullptr;
if (allowed)
result = intermediate.addUnaryMath(op, childNode, loc);
if (result)
return result;
else {
bool enhanced = intermediate.getEnhancedMsgs();
unaryOpError(loc, str, childNode->getCompleteString(enhanced));
}
return childNode;
}
//
// Handle seeing a base.field dereference in the grammar.
//
TIntermTyped* TParseContext::handleDotDereference(const TSourceLoc& loc, TIntermTyped* base, const TString& field)
{
variableCheck(base);
//
// .length() can't be resolved until we later see the function-calling syntax.
// Save away the name in the AST for now. Processing is completed in
// handleLengthMethod().
//
if (field == "length") {
if (base->isArray()) {
profileRequires(loc, ENoProfile, 120, E_GL_3DL_array_objects, ".length");
profileRequires(loc, EEsProfile, 300, nullptr, ".length");
} else if (base->isVector() || base->isMatrix()) {
const char* feature = ".length() on vectors and matrices";
requireProfile(loc, ~EEsProfile, feature);
profileRequires(loc, ~EEsProfile, 420, E_GL_ARB_shading_language_420pack, feature);
} else if (!base->getType().isCoopMat()) {
bool enhanced = intermediate.getEnhancedMsgs();
error(loc, "does not operate on this type:", field.c_str(), base->getType().getCompleteString(enhanced).c_str());
return base;
}
return intermediate.addMethod(base, TType(EbtInt), &field, loc);
}
// It's not .length() if we get to here.
if (base->isArray()) {
error(loc, "cannot apply to an array:", ".", field.c_str());
return base;
}
if (base->getType().isCoopMat()) {
error(loc, "cannot apply to a cooperative matrix type:", ".", field.c_str());
return base;
}
// It's neither an array nor .length() if we get here,
// leaving swizzles and struct/block dereferences.
TIntermTyped* result = base;
if ((base->isVector() || base->isScalar()) &&
(base->isFloatingDomain() || base->isIntegerDomain() || base->getBasicType() == EbtBool)) {
result = handleDotSwizzle(loc, base, field);
} else if (base->isStruct() || base->isReference()) {
const TTypeList* fields = base->isReference() ?
base->getType().getReferentType()->getStruct() :
base->getType().getStruct();
bool fieldFound = false;
int member;
for (member = 0; member < (int)fields->size(); ++member) {
if ((*fields)[member].type->getFieldName() == field) {
fieldFound = true;
break;
}
}
if (fieldFound) {
if (base->getType().getQualifier().isFrontEndConstant())
result = intermediate.foldDereference(base, member, loc);
else {
blockMemberExtensionCheck(loc, base, member, field);
TIntermTyped* index = intermediate.addConstantUnion(member, loc);
result = intermediate.addIndex(EOpIndexDirectStruct, base, index, loc);
result->setType(*(*fields)[member].type);
if ((*fields)[member].type->getQualifier().isIo())
intermediate.addIoAccessed(field);
}
inheritMemoryQualifiers(base->getQualifier(), result->getWritableType().getQualifier());
} else {
auto baseSymbol = base;
while (baseSymbol->getAsSymbolNode() == nullptr) {
auto binaryNode = baseSymbol->getAsBinaryNode();
if (binaryNode == nullptr) break;
baseSymbol = binaryNode->getLeft();
}
if (baseSymbol->getAsSymbolNode() != nullptr) {
TString structName;
structName.append("\'").append(baseSymbol->getAsSymbolNode()->getName().c_str()).append("\'");
error(loc, "no such field in structure", field.c_str(), structName.c_str());
} else {
error(loc, "no such field in structure", field.c_str(), "");
}
}
} else
error(loc, "does not apply to this type:", field.c_str(),
base->getType().getCompleteString(intermediate.getEnhancedMsgs()).c_str());
// Propagate noContraction up the dereference chain
if (base->getQualifier().isNoContraction())
result->getWritableType().getQualifier().setNoContraction();
// Propagate nonuniform
if (base->getQualifier().isNonUniform())
result->getWritableType().getQualifier().nonUniform = true;
return result;
}
//
// Handle seeing a base.swizzle, a subset of base.identifier in the grammar.
//
TIntermTyped* TParseContext::handleDotSwizzle(const TSourceLoc& loc, TIntermTyped* base, const TString& field)
{
TIntermTyped* result = base;
if (base->isScalar()) {
const char* dotFeature = "scalar swizzle";
requireProfile(loc, ~EEsProfile, dotFeature);
profileRequires(loc, ~EEsProfile, 420, E_GL_ARB_shading_language_420pack, dotFeature);
}
TSwizzleSelectors<TVectorSelector> selectors;
parseSwizzleSelector(loc, field, base->getVectorSize(), selectors);
if (base->isVector() && selectors.size() != 1 && base->getType().contains16BitFloat())
requireFloat16Arithmetic(loc, ".", "can't swizzle types containing float16");
if (base->isVector() && selectors.size() != 1 && base->getType().contains16BitInt())
requireInt16Arithmetic(loc, ".", "can't swizzle types containing (u)int16");
if (base->isVector() && selectors.size() != 1 && base->getType().contains8BitInt())
requireInt8Arithmetic(loc, ".", "can't swizzle types containing (u)int8");
if (base->isScalar()) {
if (selectors.size() == 1)
return result;
else {
TType type(base->getBasicType(), EvqTemporary, selectors.size());
// Swizzle operations propagate specialization-constantness
if (base->getQualifier().isSpecConstant())
type.getQualifier().makeSpecConstant();
return addConstructor(loc, base, type);
}
}
if (base->getType().getQualifier().isFrontEndConstant())
result = intermediate.foldSwizzle(base, selectors, loc);
else {
if (selectors.size() == 1) {
TIntermTyped* index = intermediate.addConstantUnion(selectors[0], loc);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision));
} else {
TIntermTyped* index = intermediate.addSwizzle(selectors, loc);
result = intermediate.addIndex(EOpVectorSwizzle, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision, selectors.size()));
}
// Swizzle operations propagate specialization-constantness
if (base->getType().getQualifier().isSpecConstant())
result->getWritableType().getQualifier().makeSpecConstant();
}
return result;
}
void TParseContext::blockMemberExtensionCheck(const TSourceLoc& loc, const TIntermTyped* base, int member, const TString& memberName)
{
// a block that needs extension checking is either 'base', or if arrayed,
// one level removed to the left
const TIntermSymbol* baseSymbol = nullptr;
if (base->getAsBinaryNode() == nullptr)
baseSymbol = base->getAsSymbolNode();
else
baseSymbol = base->getAsBinaryNode()->getLeft()->getAsSymbolNode();
if (baseSymbol == nullptr)
return;
const TSymbol* symbol = symbolTable.find(baseSymbol->getName());
if (symbol == nullptr)
return;
const TVariable* variable = symbol->getAsVariable();
if (variable == nullptr)
return;
if (!variable->hasMemberExtensions())
return;
// We now have a variable that is the base of a dot reference
// with members that need extension checking.
if (variable->getNumMemberExtensions(member) > 0)
requireExtensions(loc, variable->getNumMemberExtensions(member), variable->getMemberExtensions(member), memberName.c_str());
}
//
// Handle seeing a function declarator in the grammar. This is the precursor
// to recognizing a function prototype or function definition.
//
TFunction* TParseContext::handleFunctionDeclarator(const TSourceLoc& loc, TFunction& function, bool prototype)
{
// ES can't declare prototypes inside functions
if (! symbolTable.atGlobalLevel())
requireProfile(loc, ~EEsProfile, "local function declaration");
//
// Multiple declarations of the same function name are allowed.
//
// If this is a definition, the definition production code will check for redefinitions
// (we don't know at this point if it's a definition or not).
//
// Redeclarations (full signature match) are allowed. But, return types and parameter qualifiers must also match.
// - except ES 100, which only allows a single prototype
//
// ES 100 does not allow redefining, but does allow overloading of built-in functions.
// ES 300 does not allow redefining or overloading of built-in functions.
//
bool builtIn;
TSymbol* symbol = symbolTable.find(function.getMangledName(), &builtIn);
if (symbol && symbol->getAsFunction() && builtIn)
requireProfile(loc, ~EEsProfile, "redefinition of built-in function");
// Check the validity of using spirv_literal qualifier
for (int i = 0; i < function.getParamCount(); ++i) {
if (function[i].type->getQualifier().isSpirvLiteral() && function.getBuiltInOp() != EOpSpirvInst)
error(loc, "'spirv_literal' can only be used on functions defined with 'spirv_instruction' for argument",
function.getName().c_str(), "%d", i + 1);
}
// For function declaration with SPIR-V instruction qualifier, always ignore the built-in function and
// respect this redeclared one.
if (symbol && builtIn && function.getBuiltInOp() == EOpSpirvInst)
symbol = nullptr;
const TFunction* prevDec = symbol ? symbol->getAsFunction() : nullptr;
if (prevDec) {
if (prevDec->isPrototyped() && prototype)
profileRequires(loc, EEsProfile, 300, nullptr, "multiple prototypes for same function");
if (prevDec->getType() != function.getType())
error(loc, "overloaded functions must have the same return type", function.getName().c_str(), "");
if (prevDec->getSpirvInstruction() != function.getSpirvInstruction()) {
error(loc, "overloaded functions must have the same qualifiers", function.getName().c_str(),
"spirv_instruction");
}
for (int i = 0; i < prevDec->getParamCount(); ++i) {
if ((*prevDec)[i].type->getQualifier().storage != function[i].type->getQualifier().storage)
error(loc, "overloaded functions must have the same parameter storage qualifiers for argument", function[i].type->getStorageQualifierString(), "%d", i+1);
if ((*prevDec)[i].type->getQualifier().precision != function[i].type->getQualifier().precision)
error(loc, "overloaded functions must have the same parameter precision qualifiers for argument", function[i].type->getPrecisionQualifierString(), "%d", i+1);
}
}
arrayObjectCheck(loc, function.getType(), "array in function return type");
if (prototype) {
// All built-in functions are defined, even though they don't have a body.
// Count their prototype as a definition instead.
if (symbolTable.atBuiltInLevel())
function.setDefined();
else {
if (prevDec && ! builtIn)
symbol->getAsFunction()->setPrototyped(); // need a writable one, but like having prevDec as a const
function.setPrototyped();
}
}
// This insert won't actually insert it if it's a duplicate signature, but it will still check for
// other forms of name collisions.
if (! symbolTable.insert(function))
error(loc, "function name is redeclaration of existing name", function.getName().c_str(), "");
//
// If this is a redeclaration, it could also be a definition,
// in which case, we need to use the parameter names from this one, and not the one that's
// being redeclared. So, pass back this declaration, not the one in the symbol table.
//
return &function;
}
//
// Handle seeing the function prototype in front of a function definition in the grammar.
// The body is handled after this function returns.
//
TIntermAggregate* TParseContext::handleFunctionDefinition(const TSourceLoc& loc, TFunction& function)
{
currentCaller = function.getMangledName();
TSymbol* symbol = symbolTable.find(function.getMangledName());
TFunction* prevDec = symbol ? symbol->getAsFunction() : nullptr;
if (! prevDec)
error(loc, "can't find function", function.getName().c_str(), "");
// Note: 'prevDec' could be 'function' if this is the first time we've seen function
// as it would have just been put in the symbol table. Otherwise, we're looking up
// an earlier occurrence.
if (prevDec && prevDec->isDefined()) {
// Then this function already has a body.
error(loc, "function already has a body", function.getName().c_str(), "");
}
if (prevDec && ! prevDec->isDefined()) {
prevDec->setDefined();
// Remember the return type for later checking for RETURN statements.
currentFunctionType = &(prevDec->getType());
} else
currentFunctionType = new TType(EbtVoid);
functionReturnsValue = false;
// Check for entry point
if (function.getName().compare(intermediate.getEntryPointName().c_str()) == 0) {
intermediate.setEntryPointMangledName(function.getMangledName().c_str());
intermediate.incrementEntryPointCount();
inMain = true;
} else
inMain = false;
//
// Raise error message if main function takes any parameters or returns anything other than void
//
if (inMain) {
if (function.getParamCount() > 0)
error(loc, "function cannot take any parameter(s)", function.getName().c_str(), "");
if (function.getType().getBasicType() != EbtVoid)
error(loc, "", function.getType().getBasicTypeString().c_str(), "entry point cannot return a value");
if (function.getLinkType() != ELinkNone)
error(loc, "main function cannot be exported", "", "");
}
//
// New symbol table scope for body of function plus its arguments
//
symbolTable.push();
//
// Insert parameters into the symbol table.
// If the parameter has no name, it's not an error, just don't insert it
// (could be used for unused args).
//
// Also, accumulate the list of parameters into the HIL, so lower level code
// knows where to find parameters.
//
TIntermAggregate* paramNodes = new TIntermAggregate;
for (int i = 0; i < function.getParamCount(); i++) {
TParameter& param = function[i];
if (param.name != nullptr) {
TVariable *variable = new TVariable(param.name, *param.type);
// Insert the parameters with name in the symbol table.
if (! symbolTable.insert(*variable))
error(loc, "redefinition", variable->getName().c_str(), "");
else {
// Transfer ownership of name pointer to symbol table.
param.name = nullptr;
// Add the parameter to the HIL
paramNodes = intermediate.growAggregate(paramNodes,
intermediate.addSymbol(*variable, loc),
loc);
}
} else
paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(*param.type, loc), loc);
}
paramNodes->setLinkType(function.getLinkType());
intermediate.setAggregateOperator(paramNodes, EOpParameters, TType(EbtVoid), loc);
loopNestingLevel = 0;
statementNestingLevel = 0;
controlFlowNestingLevel = 0;
postEntryPointReturn = false;
return paramNodes;
}
//
// Handle seeing function call syntax in the grammar, which could be any of
// - .length() method
// - constructor
// - a call to a built-in function mapped to an operator
// - a call to a built-in function that will remain a function call (e.g., texturing)
// - user function
// - subroutine call (not implemented yet)
//
TIntermTyped* TParseContext::handleFunctionCall(const TSourceLoc& loc, TFunction* function, TIntermNode* arguments)
{
TIntermTyped* result = nullptr;
if (spvVersion.vulkan != 0 && spvVersion.vulkanRelaxed) {
// allow calls that are invalid in Vulkan Semantics to be invisibily
// remapped to equivalent valid functions
result = vkRelaxedRemapFunctionCall(loc, function, arguments);
if (result)
return result;
}
if (function->getBuiltInOp() == EOpArrayLength)
result = handleLengthMethod(loc, function, arguments);
else if (function->getBuiltInOp() != EOpNull) {
//
// Then this should be a constructor.
// Don't go through the symbol table for constructors.
// Their parameters will be verified algorithmically.
//
TType type(EbtVoid); // use this to get the type back
if (! constructorError(loc, arguments, *function, function->getBuiltInOp(), type)) {
//
// It's a constructor, of type 'type'.
//
result = addConstructor(loc, arguments, type);
if (result == nullptr)
error(loc, "cannot construct with these arguments", type.getCompleteString(intermediate.getEnhancedMsgs()).c_str(), "");
}
} else {
//
// Find it in the symbol table.
//
const TFunction* fnCandidate;
bool builtIn {false};
fnCandidate = findFunction(loc, *function, builtIn);
if (fnCandidate) {
// This is a declared function that might map to
// - a built-in operator,
// - a built-in function not mapped to an operator, or
// - a user function.
// Error check for a function requiring specific extensions present.
if (builtIn && fnCandidate->getNumExtensions())
requireExtensions(loc, fnCandidate->getNumExtensions(), fnCandidate->getExtensions(), fnCandidate->getName().c_str());
if (builtIn && fnCandidate->getType().contains16BitFloat())
requireFloat16Arithmetic(loc, "built-in function", "float16 types can only be in uniform block or buffer storage");
if (builtIn && fnCandidate->getType().contains16BitInt())
requireInt16Arithmetic(loc, "built-in function", "(u)int16 types can only be in uniform block or buffer storage");
if (builtIn && fnCandidate->getType().contains8BitInt())
requireInt8Arithmetic(loc, "built-in function", "(u)int8 types can only be in uniform block or buffer storage");
if (arguments != nullptr) {
// Make sure qualifications work for these arguments.
TIntermAggregate* aggregate = arguments->getAsAggregate();
for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
// At this early point there is a slight ambiguity between whether an aggregate 'arguments'
// is the single argument itself or its children are the arguments. Only one argument
// means take 'arguments' itself as the one argument.
TIntermNode* arg = fnCandidate->getParamCount() == 1 ? arguments : (aggregate ? aggregate->getSequence()[i] : arguments);
TQualifier& formalQualifier = (*fnCandidate)[i].type->getQualifier();
if (formalQualifier.isParamOutput()) {
if (lValueErrorCheck(arguments->getLoc(), "assign", arg->getAsTyped()))
error(arguments->getLoc(), "Non-L-value cannot be passed for 'out' or 'inout' parameters.", "out", "");
}
if (formalQualifier.isSpirvLiteral()) {
if (!arg->getAsTyped()->getQualifier().isFrontEndConstant()) {
error(arguments->getLoc(),
"Non front-end constant expressions cannot be passed for 'spirv_literal' parameters.",
"spirv_literal", "");
}
}
const TType& argType = arg->getAsTyped()->getType();
const TQualifier& argQualifier = argType.getQualifier();
bool containsBindlessSampler = intermediate.getBindlessMode() && argType.containsSampler();
if (argQualifier.isMemory() && !containsBindlessSampler && (argType.containsOpaque() || argType.isReference())) {
const char* message = "argument cannot drop memory qualifier when passed to formal parameter";
if (argQualifier.volatil && ! formalQualifier.volatil)
error(arguments->getLoc(), message, "volatile", "");
if (argQualifier.coherent && ! (formalQualifier.devicecoherent || formalQualifier.coherent))
error(arguments->getLoc(), message, "coherent", "");
if (argQualifier.devicecoherent && ! (formalQualifier.devicecoherent || formalQualifier.coherent))
error(arguments->getLoc(), message, "devicecoherent", "");
if (argQualifier.queuefamilycoherent && ! (formalQualifier.queuefamilycoherent || formalQualifier.devicecoherent || formalQualifier.coherent))
error(arguments->getLoc(), message, "queuefamilycoherent", "");
if (argQualifier.workgroupcoherent && ! (formalQualifier.workgroupcoherent || formalQualifier.queuefamilycoherent || formalQualifier.devicecoherent || formalQualifier.coherent))
error(arguments->getLoc(), message, "workgroupcoherent", "");
if (argQualifier.subgroupcoherent && ! (formalQualifier.subgroupcoherent || formalQualifier.workgroupcoherent || formalQualifier.queuefamilycoherent || formalQualifier.devicecoherent || formalQualifier.coherent))
error(arguments->getLoc(), message, "subgroupcoherent", "");
if (argQualifier.readonly && ! formalQualifier.readonly)
error(arguments->getLoc(), message, "readonly", "");
if (argQualifier.writeonly && ! formalQualifier.writeonly)
error(arguments->getLoc(), message, "writeonly", "");
// Don't check 'restrict', it is different than the rest:
// "...but only restrict can be taken away from a calling argument, by a formal parameter that
// lacks the restrict qualifier..."
}
if (!builtIn && argQualifier.getFormat() != formalQualifier.getFormat()) {
// we have mismatched formats, which should only be allowed if writeonly
// and at least one format is unknown
if (!formalQualifier.isWriteOnly() || (formalQualifier.getFormat() != ElfNone &&
argQualifier.getFormat() != ElfNone))
error(arguments->getLoc(), "image formats must match", "format", "");
}
if (builtIn && arg->getAsTyped()->getType().contains16BitFloat())
requireFloat16Arithmetic(arguments->getLoc(), "built-in function", "float16 types can only be in uniform block or buffer storage");
if (builtIn && arg->getAsTyped()->getType().contains16BitInt())
requireInt16Arithmetic(arguments->getLoc(), "built-in function", "(u)int16 types can only be in uniform block or buffer storage");
if (builtIn && arg->getAsTyped()->getType().contains8BitInt())
requireInt8Arithmetic(arguments->getLoc(), "built-in function", "(u)int8 types can only be in uniform block or buffer storage");
// TODO 4.5 functionality: A shader will fail to compile
// if the value passed to the memargument of an atomic memory function does not correspond to a buffer or
// shared variable. It is acceptable to pass an element of an array or a single component of a vector to the
// memargument of an atomic memory function, as long as the underlying array or vector is a buffer or
// shared variable.
}
// Convert 'in' arguments
addInputArgumentConversions(*fnCandidate, arguments); // arguments may be modified if it's just a single argument node
}
if (builtIn && fnCandidate->getBuiltInOp() != EOpNull) {
// A function call mapped to a built-in operation.
result = handleBuiltInFunctionCall(loc, arguments, *fnCandidate);
} else if (fnCandidate->getBuiltInOp() == EOpSpirvInst) {
// When SPIR-V instruction qualifier is specified, the function call is still mapped to a built-in operation.
result = handleBuiltInFunctionCall(loc, arguments, *fnCandidate);
} else {
// This is a function call not mapped to built-in operator.
// It could still be a built-in function, but only if PureOperatorBuiltins == false.
result = intermediate.setAggregateOperator(arguments, EOpFunctionCall, fnCandidate->getType(), loc);
TIntermAggregate* call = result->getAsAggregate();
call->setName(fnCandidate->getMangledName());
// this is how we know whether the given function is a built-in function or a user-defined function
// if builtIn == false, it's a userDefined -> could be an overloaded built-in function also
// if builtIn == true, it's definitely a built-in function with EOpNull
if (! builtIn) {
call->setUserDefined();
if (symbolTable.atGlobalLevel()) {
requireProfile(loc, ~EEsProfile, "calling user function from global scope");
intermediate.addToCallGraph(infoSink, "main(", fnCandidate->getMangledName());
} else
intermediate.addToCallGraph(infoSink, currentCaller, fnCandidate->getMangledName());
}
if (builtIn)
nonOpBuiltInCheck(loc, *fnCandidate, *call);
else
userFunctionCallCheck(loc, *call);
}
// Convert 'out' arguments. If it was a constant folded built-in, it won't be an aggregate anymore.
// Built-ins with a single argument aren't called with an aggregate, but they also don't have an output.
// Also, build the qualifier list for user function calls, which are always called with an aggregate.
if (result->getAsAggregate()) {
TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
TStorageQualifier qual = (*fnCandidate)[i].type->getQualifier().storage;
qualifierList.push_back(qual);
}
result = addOutputArgumentConversions(*fnCandidate, *result->getAsAggregate());
}
if (result->getAsTyped()->getType().isCoopMat() &&
!result->getAsTyped()->getType().isParameterized()) {
assert(fnCandidate->getBuiltInOp() == EOpCooperativeMatrixMulAdd ||
fnCandidate->getBuiltInOp() == EOpCooperativeMatrixMulAddNV);
result->setType(result->getAsAggregate()->getSequence()[2]->getAsTyped()->getType());
}
}
}
// generic error recovery
// TODO: simplification: localize all the error recoveries that look like this, and taking type into account to reduce cascades
if (result == nullptr)
result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
return result;
}
TIntermTyped* TParseContext::handleBuiltInFunctionCall(TSourceLoc loc, TIntermNode* arguments,
const TFunction& function)
{
checkLocation(loc, function.getBuiltInOp());
TIntermTyped *result = intermediate.addBuiltInFunctionCall(loc, function.getBuiltInOp(),
function.getParamCount() == 1,
arguments, function.getType());
if (result != nullptr && obeyPrecisionQualifiers())
computeBuiltinPrecisions(*result, function);
if (result == nullptr) {
if (arguments == nullptr)
error(loc, " wrong operand type", "Internal Error",
"built in unary operator function. Type: %s", "");
else
error(arguments->getLoc(), " wrong operand type", "Internal Error",
"built in unary operator function. Type: %s",
static_cast<TIntermTyped*>(arguments)->getCompleteString(intermediate.getEnhancedMsgs()).c_str());
} else if (result->getAsOperator())
builtInOpCheck(loc, function, *result->getAsOperator());
// Special handling for function call with SPIR-V instruction qualifier specified
if (function.getBuiltInOp() == EOpSpirvInst) {
if (auto agg = result->getAsAggregate()) {
// Propogate spirv_by_reference/spirv_literal from parameters to arguments
auto& sequence = agg->getSequence();
for (unsigned i = 0; i < sequence.size(); ++i) {
if (function[i].type->getQualifier().isSpirvByReference())
sequence[i]->getAsTyped()->getQualifier().setSpirvByReference();
if (function[i].type->getQualifier().isSpirvLiteral())
sequence[i]->getAsTyped()->getQualifier().setSpirvLiteral();
}
// Attach the function call to SPIR-V intruction
agg->setSpirvInstruction(function.getSpirvInstruction());
} else if (auto unaryNode = result->getAsUnaryNode()) {
// Propogate spirv_by_reference/spirv_literal from parameters to arguments
if (function[0].type->getQualifier().isSpirvByReference())
unaryNode->getOperand()->getQualifier().setSpirvByReference();
if (function[0].type->getQualifier().isSpirvLiteral())
unaryNode->getOperand()->getQualifier().setSpirvLiteral();
// Attach the function call to SPIR-V intruction
unaryNode->setSpirvInstruction(function.getSpirvInstruction());
} else
assert(0);
}
return result;
}
// "The operation of a built-in function can have a different precision
// qualification than the precision qualification of the resulting value.
// These two precision qualifications are established as follows.
//
// The precision qualification of the operation of a built-in function is
// based on the precision qualification of its input arguments and formal
// parameters: When a formal parameter specifies a precision qualifier,
// that is used, otherwise, the precision qualification of the calling
// argument is used. The highest precision of these will be the precision
// qualification of the operation of the built-in function. Generally,
// this is applied across all arguments to a built-in function, with the
// exceptions being:
// - bitfieldExtract and bitfieldInsert ignore the 'offset' and 'bits'
// arguments.
// - interpolateAt* functions only look at the 'interpolant' argument.
//
// The precision qualification of the result of a built-in function is
// determined in one of the following ways:
//
// - For the texture sampling, image load, and image store functions,
// the precision of the return type matches the precision of the
// sampler type
//
// Otherwise:
//
// - For prototypes that do not specify a resulting precision qualifier,
// the precision will be the same as the precision of the operation.
//
// - For prototypes that do specify a resulting precision qualifier,
// the specified precision qualifier is the precision qualification of
// the result."
//
void TParseContext::computeBuiltinPrecisions(TIntermTyped& node, const TFunction& function)
{
TPrecisionQualifier operationPrecision = EpqNone;
TPrecisionQualifier resultPrecision = EpqNone;
TIntermOperator* opNode = node.getAsOperator();
if (opNode == nullptr)
return;
if (TIntermUnary* unaryNode = node.getAsUnaryNode()) {
operationPrecision = std::max(function[0].type->getQualifier().precision,
unaryNode->getOperand()->getType().getQualifier().precision);
if (function.getType().getBasicType() != EbtBool)
resultPrecision = function.getType().getQualifier().precision == EpqNone ?
operationPrecision :
function.getType().getQualifier().precision;
} else if (TIntermAggregate* agg = node.getAsAggregate()) {
TIntermSequence& sequence = agg->getSequence();
unsigned int numArgs = (unsigned int)sequence.size();
switch (agg->getOp()) {
case EOpBitfieldExtract:
numArgs = 1;
break;
case EOpBitfieldInsert:
numArgs = 2;
break;
case EOpInterpolateAtCentroid:
case EOpInterpolateAtOffset:
case EOpInterpolateAtSample:
numArgs = 1;
break;
case EOpDebugPrintf:
numArgs = 0;
break;
default:
break;
}
// find the maximum precision from the arguments and parameters
for (unsigned int arg = 0; arg < numArgs; ++arg) {
operationPrecision = std::max(operationPrecision, sequence[arg]->getAsTyped()->getQualifier().precision);
operationPrecision = std::max(operationPrecision, function[arg].type->getQualifier().precision);
}
// compute the result precision
if (agg->isSampling() ||
agg->getOp() == EOpImageLoad || agg->getOp() == EOpImageStore ||
agg->getOp() == EOpImageLoadLod || agg->getOp() == EOpImageStoreLod)
resultPrecision = sequence[0]->getAsTyped()->getQualifier().precision;
else if (function.getType().getBasicType() != EbtBool)
resultPrecision = function.getType().getQualifier().precision == EpqNone ?
operationPrecision :
function.getType().getQualifier().precision;
}
// Propagate precision through this node and its children. That algorithm stops
// when a precision is found, so start by clearing this subroot precision
opNode->getQualifier().precision = EpqNone;
if (operationPrecision != EpqNone) {
opNode->propagatePrecision(operationPrecision);
opNode->setOperationPrecision(operationPrecision);
}
// Now, set the result precision, which might not match
opNode->getQualifier().precision = resultPrecision;
}
TIntermNode* TParseContext::handleReturnValue(const TSourceLoc& loc, TIntermTyped* value)
{
storage16BitAssignmentCheck(loc, value->getType(), "return");
functionReturnsValue = true;
TIntermBranch* branch = nullptr;
if (currentFunctionType->getBasicType() == EbtVoid) {
error(loc, "void function cannot return a value", "return", "");
branch = intermediate.addBranch(EOpReturn, loc);
} else if (*currentFunctionType != value->getType()) {
TIntermTyped* converted = intermediate.addConversion(EOpReturn, *currentFunctionType, value);
if (converted) {
if (*currentFunctionType != converted->getType())
error(loc, "cannot convert return value to function return type", "return", "");
if (version < 420)
warn(loc, "type conversion on return values was not explicitly allowed until version 420",
"return", "");
branch = intermediate.addBranch(EOpReturn, converted, loc);
} else {
error(loc, "type does not match, or is not convertible to, the function's return type", "return", "");
branch = intermediate.addBranch(EOpReturn, value, loc);
}
} else {
if (value->getType().isTexture() || value->getType().isImage()) {
if (!extensionTurnedOn(E_GL_ARB_bindless_texture))
error(loc, "sampler or image can be used as return type only when the extension GL_ARB_bindless_texture enabled", "return", "");
}
branch = intermediate.addBranch(EOpReturn, value, loc);
}
branch->updatePrecision(currentFunctionType->getQualifier().precision);
return branch;
}
// See if the operation is being done in an illegal location.
void TParseContext::checkLocation(const TSourceLoc& loc, TOperator op)
{
switch (op) {
case EOpBarrier:
if (language == EShLangTessControl) {
if (controlFlowNestingLevel > 0)
error(loc, "tessellation control barrier() cannot be placed within flow control", "", "");
if (! inMain)
error(loc, "tessellation control barrier() must be in main()", "", "");
else if (postEntryPointReturn)
error(loc, "tessellation control barrier() cannot be placed after a return from main()", "", "");
}
break;
case EOpBeginInvocationInterlock:
if (language != EShLangFragment)
error(loc, "beginInvocationInterlockARB() must be in a fragment shader", "", "");
if (! inMain)
error(loc, "beginInvocationInterlockARB() must be in main()", "", "");
else if (postEntryPointReturn)
error(loc, "beginInvocationInterlockARB() cannot be placed after a return from main()", "", "");
if (controlFlowNestingLevel > 0)
error(loc, "beginInvocationInterlockARB() cannot be placed within flow control", "", "");
if (beginInvocationInterlockCount > 0)
error(loc, "beginInvocationInterlockARB() must only be called once", "", "");
if (endInvocationInterlockCount > 0)
error(loc, "beginInvocationInterlockARB() must be called before endInvocationInterlockARB()", "", "");
beginInvocationInterlockCount++;
// default to pixel_interlock_ordered
if (intermediate.getInterlockOrdering() == EioNone)
intermediate.setInterlockOrdering(EioPixelInterlockOrdered);
break;
case EOpEndInvocationInterlock:
if (language != EShLangFragment)
error(loc, "endInvocationInterlockARB() must be in a fragment shader", "", "");
if (! inMain)
error(loc, "endInvocationInterlockARB() must be in main()", "", "");
else if (postEntryPointReturn)
error(loc, "endInvocationInterlockARB() cannot be placed after a return from main()", "", "");
if (controlFlowNestingLevel > 0)
error(loc, "endInvocationInterlockARB() cannot be placed within flow control", "", "");
if (endInvocationInterlockCount > 0)
error(loc, "endInvocationInterlockARB() must only be called once", "", "");
if (beginInvocationInterlockCount == 0)
error(loc, "beginInvocationInterlockARB() must be called before endInvocationInterlockARB()", "", "");
endInvocationInterlockCount++;
break;
default:
break;
}
}
// Finish processing object.length(). This started earlier in handleDotDereference(), where
// the ".length" part was recognized and semantically checked, and finished here where the
// function syntax "()" is recognized.
//
// Return resulting tree node.
TIntermTyped* TParseContext::handleLengthMethod(const TSourceLoc& loc, TFunction* function, TIntermNode* intermNode)
{
int length = 0;
if (function->getParamCount() > 0)
error(loc, "method does not accept any arguments", function->getName().c_str(), "");
else {
const TType& type = intermNode->getAsTyped()->getType();
if (type.isArray()) {
if (type.isUnsizedArray()) {
if (intermNode->getAsSymbolNode() && isIoResizeArray(type)) {
// We could be between a layout declaration that gives a built-in io array implicit size and
// a user redeclaration of that array, meaning we have to substitute its implicit size here
// without actually redeclaring the array. (It is an error to use a member before the
// redeclaration, but not an error to use the array name itself.)
const TString& name = intermNode->getAsSymbolNode()->getName();
if (name == "gl_in" || name == "gl_out" || name == "gl_MeshVerticesNV" ||
name == "gl_MeshPrimitivesNV") {
length = getIoArrayImplicitSize(type.getQualifier());
}
}
if (length == 0) {
if (intermNode->getAsSymbolNode() && isIoResizeArray(type))
error(loc, "", function->getName().c_str(), "array must first be sized by a redeclaration or layout qualifier");
else if (isRuntimeLength(*intermNode->getAsTyped())) {
// Create a unary op and let the back end handle it
return intermediate.addBuiltInFunctionCall(loc, EOpArrayLength, true, intermNode, TType(EbtInt));
} else
error(loc, "", function->getName().c_str(), "array must be declared with a size before using this method");
}
} else if (type.getOuterArrayNode()) {
// If the array's outer size is specified by an intermediate node, it means the array's length
// was specified by a specialization constant. In such a case, we should return the node of the
// specialization constants to represent the length.
return type.getOuterArrayNode();
} else
length = type.getOuterArraySize();
} else if (type.isMatrix())
length = type.getMatrixCols();
else if (type.isVector())
length = type.getVectorSize();
else if (type.isCoopMat())
return intermediate.addBuiltInFunctionCall(loc, EOpArrayLength, true, intermNode, TType(EbtInt));
else {
// we should not get here, because earlier semantic checking should have prevented this path
error(loc, ".length()", "unexpected use of .length()", "");
}
}
if (length == 0)
length = 1;
return intermediate.addConstantUnion(length, loc);
}
//
// Add any needed implicit conversions for function-call arguments to input parameters.
//
void TParseContext::addInputArgumentConversions(const TFunction& function, TIntermNode*& arguments) const
{
TIntermAggregate* aggregate = arguments->getAsAggregate();
// Process each argument's conversion
for (int i = 0; i < function.getParamCount(); ++i) {
// At this early point there is a slight ambiguity between whether an aggregate 'arguments'
// is the single argument itself or its children are the arguments. Only one argument
// means take 'arguments' itself as the one argument.
TIntermTyped* arg = function.getParamCount() == 1 ? arguments->getAsTyped() : (aggregate ? aggregate->getSequence()[i]->getAsTyped() : arguments->getAsTyped());
if (*function[i].type != arg->getType()) {
if (function[i].type->getQualifier().isParamInput() &&
!function[i].type->isCoopMat()) {
// In-qualified arguments just need an extra node added above the argument to
// convert to the correct type.
arg = intermediate.addConversion(EOpFunctionCall, *function[i].type, arg);
if (arg) {
if (function.getParamCount() == 1)
arguments = arg;
else {
if (aggregate)
aggregate->getSequence()[i] = arg;
else
arguments = arg;
}
}
}
}
}
}
//
// Add any needed implicit output conversions for function-call arguments. This
// can require a new tree topology, complicated further by whether the function
// has a return value.
//
// Returns a node of a subtree that evaluates to the return value of the function.
//
TIntermTyped* TParseContext::addOutputArgumentConversions(const TFunction& function, TIntermAggregate& intermNode) const
{
TIntermSequence& arguments = intermNode.getSequence();
// Will there be any output conversions?
bool outputConversions = false;
for (int i = 0; i < function.getParamCount(); ++i) {
if (*function[i].type != arguments[i]->getAsTyped()->getType() && function[i].type->getQualifier().isParamOutput()) {
outputConversions = true;
break;
}
}
if (! outputConversions)
return &intermNode;
// Setup for the new tree, if needed:
//
// Output conversions need a different tree topology.
// Out-qualified arguments need a temporary of the correct type, with the call
// followed by an assignment of the temporary to the original argument:
// void: function(arg, ...) -> ( function(tempArg, ...), arg = tempArg, ...)
// ret = function(arg, ...) -> ret = (tempRet = function(tempArg, ...), arg = tempArg, ..., tempRet)
// Where the "tempArg" type needs no conversion as an argument, but will convert on assignment.
TIntermTyped* conversionTree = nullptr;
TVariable* tempRet = nullptr;
if (intermNode.getBasicType() != EbtVoid) {
// do the "tempRet = function(...), " bit from above
tempRet = makeInternalVariable("tempReturn", intermNode.getType());
TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, intermNode.getLoc());
conversionTree = intermediate.addAssign(EOpAssign, tempRetNode, &intermNode, intermNode.getLoc());
} else
conversionTree = &intermNode;
conversionTree = intermediate.makeAggregate(conversionTree);
// Process each argument's conversion
for (int i = 0; i < function.getParamCount(); ++i) {
if (*function[i].type != arguments[i]->getAsTyped()->getType()) {
if (function[i].type->getQualifier().isParamOutput()) {
// Out-qualified arguments need to use the topology set up above.
// do the " ...(tempArg, ...), arg = tempArg" bit from above
TType paramType;
paramType.shallowCopy(*function[i].type);
if (arguments[i]->getAsTyped()->getType().isParameterized() &&
!paramType.isParameterized()) {
paramType.shallowCopy(arguments[i]->getAsTyped()->getType());
paramType.copyTypeParameters(*arguments[i]->getAsTyped()->getType().getTypeParameters());
}
TVariable* tempArg = makeInternalVariable("tempArg", paramType);
tempArg->getWritableType().getQualifier().makeTemporary();
TIntermSymbol* tempArgNode = intermediate.addSymbol(*tempArg, intermNode.getLoc());
TIntermTyped* tempAssign = intermediate.addAssign(EOpAssign, arguments[i]->getAsTyped(), tempArgNode, arguments[i]->getLoc());
conversionTree = intermediate.growAggregate(conversionTree, tempAssign, arguments[i]->getLoc());
// replace the argument with another node for the same tempArg variable
arguments[i] = intermediate.addSymbol(*tempArg, intermNode.getLoc());
}
}
}
// Finalize the tree topology (see bigger comment above).
if (tempRet) {
// do the "..., tempRet" bit from above
TIntermSymbol* tempRetNode = intermediate.addSymbol(*tempRet, intermNode.getLoc());
conversionTree = intermediate.growAggregate(conversionTree, tempRetNode, intermNode.getLoc());
}
conversionTree = intermediate.setAggregateOperator(conversionTree, EOpComma, intermNode.getType(), intermNode.getLoc());
return conversionTree;
}
TIntermTyped* TParseContext::addAssign(const TSourceLoc& loc, TOperator op, TIntermTyped* left, TIntermTyped* right)
{
if ((op == EOpAddAssign || op == EOpSubAssign) && left->isReference())
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference2, "+= and -= on a buffer reference");
if (op == EOpAssign && left->getBasicType() == EbtSampler && right->getBasicType() == EbtSampler)
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "sampler assignment for bindless texture");
return intermediate.addAssign(op, left, right, loc);
}
void TParseContext::memorySemanticsCheck(const TSourceLoc& loc, const TFunction& fnCandidate, const TIntermOperator& callNode)
{
const TIntermSequence* argp = &callNode.getAsAggregate()->getSequence();
//const int gl_SemanticsRelaxed = 0x0;
const int gl_SemanticsAcquire = 0x2;
const int gl_SemanticsRelease = 0x4;
const int gl_SemanticsAcquireRelease = 0x8;
const int gl_SemanticsMakeAvailable = 0x2000;
const int gl_SemanticsMakeVisible = 0x4000;
const int gl_SemanticsVolatile = 0x8000;
//const int gl_StorageSemanticsNone = 0x0;
const int gl_StorageSemanticsBuffer = 0x40;
const int gl_StorageSemanticsShared = 0x100;
const int gl_StorageSemanticsImage = 0x800;
const int gl_StorageSemanticsOutput = 0x1000;
unsigned int semantics = 0, storageClassSemantics = 0;
unsigned int semantics2 = 0, storageClassSemantics2 = 0;
const TIntermTyped* arg0 = (*argp)[0]->getAsTyped();
const bool isMS = arg0->getBasicType() == EbtSampler && arg0->getType().getSampler().isMultiSample();
// Grab the semantics and storage class semantics from the operands, based on opcode
switch (callNode.getOp()) {
case EOpAtomicAdd:
case EOpAtomicSubtract:
case EOpAtomicMin:
case EOpAtomicMax:
case EOpAtomicAnd:
case EOpAtomicOr:
case EOpAtomicXor:
case EOpAtomicExchange:
case EOpAtomicStore:
storageClassSemantics = (*argp)[3]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[4]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpAtomicLoad:
storageClassSemantics = (*argp)[2]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[3]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpAtomicCompSwap:
storageClassSemantics = (*argp)[4]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[5]->getAsConstantUnion()->getConstArray()[0].getIConst();
storageClassSemantics2 = (*argp)[6]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics2 = (*argp)[7]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpImageAtomicAdd:
case EOpImageAtomicMin:
case EOpImageAtomicMax:
case EOpImageAtomicAnd:
case EOpImageAtomicOr:
case EOpImageAtomicXor:
case EOpImageAtomicExchange:
case EOpImageAtomicStore:
storageClassSemantics = (*argp)[isMS ? 5 : 4]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[isMS ? 6 : 5]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpImageAtomicLoad:
storageClassSemantics = (*argp)[isMS ? 4 : 3]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[isMS ? 5 : 4]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpImageAtomicCompSwap:
storageClassSemantics = (*argp)[isMS ? 6 : 5]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[isMS ? 7 : 6]->getAsConstantUnion()->getConstArray()[0].getIConst();
storageClassSemantics2 = (*argp)[isMS ? 8 : 7]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics2 = (*argp)[isMS ? 9 : 8]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpBarrier:
storageClassSemantics = (*argp)[2]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[3]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
case EOpMemoryBarrier:
storageClassSemantics = (*argp)[1]->getAsConstantUnion()->getConstArray()[0].getIConst();
semantics = (*argp)[2]->getAsConstantUnion()->getConstArray()[0].getIConst();
break;
default:
break;
}
if ((semantics & gl_SemanticsAcquire) &&
(callNode.getOp() == EOpAtomicStore || callNode.getOp() == EOpImageAtomicStore)) {
error(loc, "gl_SemanticsAcquire must not be used with (image) atomic store",
fnCandidate.getName().c_str(), "");
}
if ((semantics & gl_SemanticsRelease) &&
(callNode.getOp() == EOpAtomicLoad || callNode.getOp() == EOpImageAtomicLoad)) {
error(loc, "gl_SemanticsRelease must not be used with (image) atomic load",
fnCandidate.getName().c_str(), "");
}
if ((semantics & gl_SemanticsAcquireRelease) &&
(callNode.getOp() == EOpAtomicStore || callNode.getOp() == EOpImageAtomicStore ||
callNode.getOp() == EOpAtomicLoad || callNode.getOp() == EOpImageAtomicLoad)) {
error(loc, "gl_SemanticsAcquireRelease must not be used with (image) atomic load/store",
fnCandidate.getName().c_str(), "");
}
if (((semantics | semantics2) & ~(gl_SemanticsAcquire |
gl_SemanticsRelease |
gl_SemanticsAcquireRelease |
gl_SemanticsMakeAvailable |
gl_SemanticsMakeVisible |
gl_SemanticsVolatile))) {
error(loc, "Invalid semantics value", fnCandidate.getName().c_str(), "");
}
if (((storageClassSemantics | storageClassSemantics2) & ~(gl_StorageSemanticsBuffer |
gl_StorageSemanticsShared |
gl_StorageSemanticsImage |
gl_StorageSemanticsOutput))) {
error(loc, "Invalid storage class semantics value", fnCandidate.getName().c_str(), "");
}
if (callNode.getOp() == EOpMemoryBarrier) {
if (!IsPow2(semantics & (gl_SemanticsAcquire | gl_SemanticsRelease | gl_SemanticsAcquireRelease))) {
error(loc, "Semantics must include exactly one of gl_SemanticsRelease, gl_SemanticsAcquire, or "
"gl_SemanticsAcquireRelease", fnCandidate.getName().c_str(), "");
}
} else {
if (semantics & (gl_SemanticsAcquire | gl_SemanticsRelease | gl_SemanticsAcquireRelease)) {
if (!IsPow2(semantics & (gl_SemanticsAcquire | gl_SemanticsRelease | gl_SemanticsAcquireRelease))) {
error(loc, "Semantics must not include multiple of gl_SemanticsRelease, gl_SemanticsAcquire, or "
"gl_SemanticsAcquireRelease", fnCandidate.getName().c_str(), "");
}
}
if (semantics2 & (gl_SemanticsAcquire | gl_SemanticsRelease | gl_SemanticsAcquireRelease)) {
if (!IsPow2(semantics2 & (gl_SemanticsAcquire | gl_SemanticsRelease | gl_SemanticsAcquireRelease))) {
error(loc, "semUnequal must not include multiple of gl_SemanticsRelease, gl_SemanticsAcquire, or "
"gl_SemanticsAcquireRelease", fnCandidate.getName().c_str(), "");
}
}
}
if (callNode.getOp() == EOpMemoryBarrier) {
if (storageClassSemantics == 0) {
error(loc, "Storage class semantics must not be zero", fnCandidate.getName().c_str(), "");
}
}
if (callNode.getOp() == EOpBarrier && semantics != 0 && storageClassSemantics == 0) {
error(loc, "Storage class semantics must not be zero", fnCandidate.getName().c_str(), "");
}
if ((callNode.getOp() == EOpAtomicCompSwap || callNode.getOp() == EOpImageAtomicCompSwap) &&
(semantics2 & (gl_SemanticsRelease | gl_SemanticsAcquireRelease))) {
error(loc, "semUnequal must not be gl_SemanticsRelease or gl_SemanticsAcquireRelease",
fnCandidate.getName().c_str(), "");
}
if ((semantics & gl_SemanticsMakeAvailable) &&
!(semantics & (gl_SemanticsRelease | gl_SemanticsAcquireRelease))) {
error(loc, "gl_SemanticsMakeAvailable requires gl_SemanticsRelease or gl_SemanticsAcquireRelease",
fnCandidate.getName().c_str(), "");
}
if ((semantics & gl_SemanticsMakeVisible) &&
!(semantics & (gl_SemanticsAcquire | gl_SemanticsAcquireRelease))) {
error(loc, "gl_SemanticsMakeVisible requires gl_SemanticsAcquire or gl_SemanticsAcquireRelease",
fnCandidate.getName().c_str(), "");
}
if ((semantics & gl_SemanticsVolatile) &&
(callNode.getOp() == EOpMemoryBarrier || callNode.getOp() == EOpBarrier)) {
error(loc, "gl_SemanticsVolatile must not be used with memoryBarrier or controlBarrier",
fnCandidate.getName().c_str(), "");
}
if ((callNode.getOp() == EOpAtomicCompSwap || callNode.getOp() == EOpImageAtomicCompSwap) &&
((semantics ^ semantics2) & gl_SemanticsVolatile)) {
error(loc, "semEqual and semUnequal must either both include gl_SemanticsVolatile or neither",
fnCandidate.getName().c_str(), "");
}
}
//
// Do additional checking of built-in function calls that is not caught
// by normal semantic checks on argument type, extension tagging, etc.
//
// Assumes there has been a semantically correct match to a built-in function prototype.
//
void TParseContext::builtInOpCheck(const TSourceLoc& loc, const TFunction& fnCandidate, TIntermOperator& callNode)
{
// Set up convenience accessors to the argument(s). There is almost always
// multiple arguments for the cases below, but when there might be one,
// check the unaryArg first.
const TIntermSequence* argp = nullptr; // confusing to use [] syntax on a pointer, so this is to help get a reference
const TIntermTyped* unaryArg = nullptr;
const TIntermTyped* arg0 = nullptr;
if (callNode.getAsAggregate()) {
argp = &callNode.getAsAggregate()->getSequence();
if (argp->size() > 0)
arg0 = (*argp)[0]->getAsTyped();
} else {
assert(callNode.getAsUnaryNode());
unaryArg = callNode.getAsUnaryNode()->getOperand();
arg0 = unaryArg;
}
TString featureString;
const char* feature = nullptr;
switch (callNode.getOp()) {
case EOpTextureGather:
case EOpTextureGatherOffset:
case EOpTextureGatherOffsets:
{
// Figure out which variants are allowed by what extensions,
// and what arguments must be constant for which situations.
featureString = fnCandidate.getName();
featureString += "(...)";
feature = featureString.c_str();
profileRequires(loc, EEsProfile, 310, nullptr, feature);
int compArg = -1; // track which argument, if any, is the constant component argument
switch (callNode.getOp()) {
case EOpTextureGather:
// More than two arguments needs gpu_shader5, and rectangular or shadow needs gpu_shader5,
// otherwise, need GL_ARB_texture_gather.
if (fnCandidate.getParamCount() > 2 || fnCandidate[0].type->getSampler().dim == EsdRect || fnCandidate[0].type->getSampler().shadow) {
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 2;
} else
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_texture_gather, feature);
break;
case EOpTextureGatherOffset:
// GL_ARB_texture_gather is good enough for 2D non-shadow textures with no component argument
if (fnCandidate[0].type->getSampler().dim == Esd2D && ! fnCandidate[0].type->getSampler().shadow && fnCandidate.getParamCount() == 3)
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_texture_gather, feature);
else
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
if (! (*argp)[fnCandidate[0].type->getSampler().shadow ? 3 : 2]->getAsConstantUnion())
profileRequires(loc, EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5,
"non-constant offset argument");
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
break;
case EOpTextureGatherOffsets:
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
// check for constant offsets
if (! (*argp)[fnCandidate[0].type->getSampler().shadow ? 3 : 2]->getAsConstantUnion())
error(loc, "must be a compile-time constant:", feature, "offsets argument");
break;
default:
break;
}
if (compArg > 0 && compArg < fnCandidate.getParamCount()) {
if ((*argp)[compArg]->getAsConstantUnion()) {
int value = (*argp)[compArg]->getAsConstantUnion()->getConstArray()[0].getIConst();
if (value < 0 || value > 3)
error(loc, "must be 0, 1, 2, or 3:", feature, "component argument");
} else
error(loc, "must be a compile-time constant:", feature, "component argument");
}
bool bias = false;
if (callNode.getOp() == EOpTextureGather)
bias = fnCandidate.getParamCount() > 3;
else if (callNode.getOp() == EOpTextureGatherOffset ||
callNode.getOp() == EOpTextureGatherOffsets)
bias = fnCandidate.getParamCount() > 4;
if (bias) {
featureString = fnCandidate.getName();
featureString += "with bias argument";
feature = featureString.c_str();
profileRequires(loc, ~EEsProfile, 450, nullptr, feature);
requireExtensions(loc, 1, &E_GL_AMD_texture_gather_bias_lod, feature);
}
break;
}
case EOpTexture:
case EOpTextureLod:
{
if ((fnCandidate.getParamCount() > 2) && ((*argp)[1]->getAsTyped()->getType().getBasicType() == EbtFloat) &&
((*argp)[1]->getAsTyped()->getType().getVectorSize() == 4) && fnCandidate[0].type->getSampler().shadow) {
featureString = fnCandidate.getName();
if (callNode.getOp() == EOpTexture)
featureString += "(..., float bias)";
else
featureString += "(..., float lod)";
feature = featureString.c_str();
if ((fnCandidate[0].type->getSampler().dim == Esd2D && fnCandidate[0].type->getSampler().arrayed) || //2D Array Shadow
(fnCandidate[0].type->getSampler().dim == EsdCube && fnCandidate[0].type->getSampler().arrayed && fnCandidate.getParamCount() > 3) || // Cube Array Shadow
(fnCandidate[0].type->getSampler().dim == EsdCube && callNode.getOp() == EOpTextureLod)) { // Cube Shadow
requireExtensions(loc, 1, &E_GL_EXT_texture_shadow_lod, feature);
if (isEsProfile()) {
if (version < 320 &&
!extensionsTurnedOn(Num_AEP_texture_cube_map_array, AEP_texture_cube_map_array))
error(loc, "GL_EXT_texture_shadow_lod not supported for this ES version", feature, "");
else
profileRequires(loc, EEsProfile, 320, nullptr, feature);
} else { // Desktop
profileRequires(loc, ~EEsProfile, 130, nullptr, feature);
}
}
}
break;
}
case EOpSparseTextureGather:
case EOpSparseTextureGatherOffset:
case EOpSparseTextureGatherOffsets:
{
bool bias = false;
if (callNode.getOp() == EOpSparseTextureGather)
bias = fnCandidate.getParamCount() > 4;
else if (callNode.getOp() == EOpSparseTextureGatherOffset ||
callNode.getOp() == EOpSparseTextureGatherOffsets)
bias = fnCandidate.getParamCount() > 5;
if (bias) {
featureString = fnCandidate.getName();
featureString += "with bias argument";
feature = featureString.c_str();
profileRequires(loc, ~EEsProfile, 450, nullptr, feature);
requireExtensions(loc, 1, &E_GL_AMD_texture_gather_bias_lod, feature);
}
// As per GL_ARB_sparse_texture2 extension "Offsets" parameter must be constant integral expression
// for sparseTextureGatherOffsetsARB just as textureGatherOffsets
if (callNode.getOp() == EOpSparseTextureGatherOffsets) {
int offsetsArg = arg0->getType().getSampler().shadow ? 3 : 2;
if (!(*argp)[offsetsArg]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "offsets", "");
}
break;
}
case EOpSparseTextureGatherLod:
case EOpSparseTextureGatherLodOffset:
case EOpSparseTextureGatherLodOffsets:
{
requireExtensions(loc, 1, &E_GL_ARB_sparse_texture2, fnCandidate.getName().c_str());
break;
}
case EOpSwizzleInvocations:
{
if (! (*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "offset", "");
else {
unsigned offset[4] = {};
offset[0] = (*argp)[1]->getAsConstantUnion()->getConstArray()[0].getUConst();
offset[1] = (*argp)[1]->getAsConstantUnion()->getConstArray()[1].getUConst();
offset[2] = (*argp)[1]->getAsConstantUnion()->getConstArray()[2].getUConst();
offset[3] = (*argp)[1]->getAsConstantUnion()->getConstArray()[3].getUConst();
if (offset[0] > 3 || offset[1] > 3 || offset[2] > 3 || offset[3] > 3)
error(loc, "components must be in the range [0, 3]", "offset", "");
}
break;
}
case EOpSwizzleInvocationsMasked:
{
if (! (*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "mask", "");
else {
unsigned mask[3] = {};
mask[0] = (*argp)[1]->getAsConstantUnion()->getConstArray()[0].getUConst();
mask[1] = (*argp)[1]->getAsConstantUnion()->getConstArray()[1].getUConst();
mask[2] = (*argp)[1]->getAsConstantUnion()->getConstArray()[2].getUConst();
if (mask[0] > 31 || mask[1] > 31 || mask[2] > 31)
error(loc, "components must be in the range [0, 31]", "mask", "");
}
break;
}
case EOpTextureOffset:
case EOpTextureFetchOffset:
case EOpTextureProjOffset:
case EOpTextureLodOffset:
case EOpTextureProjLodOffset:
case EOpTextureGradOffset:
case EOpTextureProjGradOffset:
{
// Handle texture-offset limits checking
// Pick which argument has to hold constant offsets
int arg = -1;
switch (callNode.getOp()) {
case EOpTextureOffset: arg = 2; break;
case EOpTextureFetchOffset: arg = (arg0->getType().getSampler().isRect()) ? 2 : 3; break;
case EOpTextureProjOffset: arg = 2; break;
case EOpTextureLodOffset: arg = 3; break;
case EOpTextureProjLodOffset: arg = 3; break;
case EOpTextureGradOffset: arg = 4; break;
case EOpTextureProjGradOffset: arg = 4; break;
default:
assert(0);
break;
}
if (arg > 0) {
bool f16ShadowCompare = (*argp)[1]->getAsTyped()->getBasicType() == EbtFloat16 &&
arg0->getType().getSampler().shadow;
if (f16ShadowCompare)
++arg;
if (! (*argp)[arg]->getAsTyped()->getQualifier().isConstant())
error(loc, "argument must be compile-time constant", "texel offset", "");
else if ((*argp)[arg]->getAsConstantUnion()) {
const TType& type = (*argp)[arg]->getAsTyped()->getType();
for (int c = 0; c < type.getVectorSize(); ++c) {
int offset = (*argp)[arg]->getAsConstantUnion()->getConstArray()[c].getIConst();
if (offset > resources.maxProgramTexelOffset || offset < resources.minProgramTexelOffset)
error(loc, "value is out of range:", "texel offset",
"[gl_MinProgramTexelOffset, gl_MaxProgramTexelOffset]");
}
}
if (callNode.getOp() == EOpTextureOffset) {
TSampler s = arg0->getType().getSampler();
if (s.is2D() && s.isArrayed() && s.isShadow()) {
if (
((*argp)[1]->getAsTyped()->getType().getBasicType() == EbtFloat) &&
((*argp)[1]->getAsTyped()->getType().getVectorSize() == 4) &&
(fnCandidate.getParamCount() == 4)) {
featureString = fnCandidate.getName() + " for sampler2DArrayShadow";
feature = featureString.c_str();
requireExtensions(loc, 1, &E_GL_EXT_texture_shadow_lod, feature);
profileRequires(loc, EEsProfile, 300, nullptr, feature);
profileRequires(loc, ~EEsProfile, 130, nullptr, feature);
}
else if (isEsProfile())
error(loc, "TextureOffset does not support sampler2DArrayShadow : ", "sampler", "ES Profile");
else if (version <= 420)
error(loc, "TextureOffset does not support sampler2DArrayShadow : ", "sampler", "version <= 420");
}
}
if (callNode.getOp() == EOpTextureLodOffset) {
TSampler s = arg0->getType().getSampler();
if (s.is2D() && s.isArrayed() && s.isShadow() &&
((*argp)[1]->getAsTyped()->getType().getBasicType() == EbtFloat) &&
((*argp)[1]->getAsTyped()->getType().getVectorSize() == 4) &&
(fnCandidate.getParamCount() == 4)) {
featureString = fnCandidate.getName() + " for sampler2DArrayShadow";
feature = featureString.c_str();
profileRequires(loc, EEsProfile, 300, nullptr, feature);
profileRequires(loc, ~EEsProfile, 130, nullptr, feature);
requireExtensions(loc, 1, &E_GL_EXT_texture_shadow_lod, feature);
}
}
}
break;
}
case EOpTraceNV:
if (!(*argp)[10]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "a");
break;
case EOpTraceRayMotionNV:
if (!(*argp)[11]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "a");
break;
case EOpTraceKHR:
if (!(*argp)[10]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "a");
else {
unsigned int location = (*argp)[10]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(0, location) < 0)
error(loc, "with layout(location =", "no rayPayloadEXT/rayPayloadInEXT declared", "%d)", location);
}
break;
case EOpExecuteCallableNV:
if (!(*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "callable data number", "");
break;
case EOpExecuteCallableKHR:
if (!(*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "callable data number", "");
else {
unsigned int location = (*argp)[1]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(1, location) < 0)
error(loc, "with layout(location =", "no callableDataEXT/callableDataInEXT declared", "%d)", location);
}
break;
case EOpHitObjectTraceRayNV:
if (!(*argp)[11]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "");
else {
unsigned int location = (*argp)[11]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(0, location) < 0)
error(loc, "with layout(location =", "no rayPayloadEXT/rayPayloadInEXT declared", "%d)", location);
}
break;
case EOpHitObjectTraceRayMotionNV:
if (!(*argp)[12]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "");
else {
unsigned int location = (*argp)[12]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(0, location) < 0)
error(loc, "with layout(location =", "no rayPayloadEXT/rayPayloadInEXT declared", "%d)", location);
}
break;
case EOpHitObjectExecuteShaderNV:
if (!(*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "payload number", "");
else {
unsigned int location = (*argp)[1]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(0, location) < 0)
error(loc, "with layout(location =", "no rayPayloadEXT/rayPayloadInEXT declared", "%d)", location);
}
break;
case EOpHitObjectRecordHitNV:
if (!(*argp)[12]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "hitobjectattribute number", "");
else {
unsigned int location = (*argp)[12]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(2, location) < 0)
error(loc, "with layout(location =", "no hitObjectAttributeNV declared", "%d)", location);
}
break;
case EOpHitObjectRecordHitMotionNV:
if (!(*argp)[13]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "hitobjectattribute number", "");
else {
unsigned int location = (*argp)[13]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(2, location) < 0)
error(loc, "with layout(location =", "no hitObjectAttributeNV declared", "%d)", location);
}
break;
case EOpHitObjectRecordHitWithIndexNV:
if (!(*argp)[11]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "hitobjectattribute number", "");
else {
unsigned int location = (*argp)[11]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(2, location) < 0)
error(loc, "with layout(location =", "no hitObjectAttributeNV declared", "%d)", location);
}
break;
case EOpHitObjectRecordHitWithIndexMotionNV:
if (!(*argp)[12]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "hitobjectattribute number", "");
else {
unsigned int location = (*argp)[12]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(2, location) < 0)
error(loc, "with layout(location =", "no hitObjectAttributeNV declared", "%d)", location);
}
break;
case EOpHitObjectGetAttributesNV:
if (!(*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "hitobjectattribute number", "");
else {
unsigned int location = (*argp)[1]->getAsConstantUnion()->getAsConstantUnion()->getConstArray()[0].getUConst();
if (!extensionTurnedOn(E_GL_EXT_spirv_intrinsics) && intermediate.checkLocationRT(2, location) < 0)
error(loc, "with layout(location =", "no hitObjectAttributeNV declared", "%d)", location);
}
break;
case EOpRayQueryGetIntersectionType:
case EOpRayQueryGetIntersectionT:
case EOpRayQueryGetIntersectionInstanceCustomIndex:
case EOpRayQueryGetIntersectionInstanceId:
case EOpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffset:
case EOpRayQueryGetIntersectionGeometryIndex:
case EOpRayQueryGetIntersectionPrimitiveIndex:
case EOpRayQueryGetIntersectionBarycentrics:
case EOpRayQueryGetIntersectionFrontFace:
case EOpRayQueryGetIntersectionObjectRayDirection:
case EOpRayQueryGetIntersectionObjectRayOrigin:
case EOpRayQueryGetIntersectionObjectToWorld:
case EOpRayQueryGetIntersectionWorldToObject:
case EOpRayQueryGetIntersectionTriangleVertexPositionsEXT:
if (!(*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "committed", "");
break;
case EOpTextureQuerySamples:
case EOpImageQuerySamples:
// GL_ARB_shader_texture_image_samples
profileRequires(loc, ~EEsProfile, 450, E_GL_ARB_shader_texture_image_samples, "textureSamples and imageSamples");
break;
case EOpImageAtomicAdd:
case EOpImageAtomicMin:
case EOpImageAtomicMax:
case EOpImageAtomicAnd:
case EOpImageAtomicOr:
case EOpImageAtomicXor:
case EOpImageAtomicExchange:
case EOpImageAtomicCompSwap:
case EOpImageAtomicLoad:
case EOpImageAtomicStore:
{
// Make sure the image types have the correct layout() format and correct argument types
const TType& imageType = arg0->getType();
if (imageType.getSampler().type == EbtInt || imageType.getSampler().type == EbtUint ||
imageType.getSampler().type == EbtInt64 || imageType.getSampler().type == EbtUint64) {
if (imageType.getQualifier().getFormat() != ElfR32i && imageType.getQualifier().getFormat() != ElfR32ui &&
imageType.getQualifier().getFormat() != ElfR64i && imageType.getQualifier().getFormat() != ElfR64ui)
error(loc, "only supported on image with format r32i or r32ui", fnCandidate.getName().c_str(), "");
if (callNode.getType().getBasicType() == EbtInt64 && imageType.getQualifier().getFormat() != ElfR64i)
error(loc, "only supported on image with format r64i", fnCandidate.getName().c_str(), "");
else if (callNode.getType().getBasicType() == EbtUint64 && imageType.getQualifier().getFormat() != ElfR64ui)
error(loc, "only supported on image with format r64ui", fnCandidate.getName().c_str(), "");
} else if (imageType.getSampler().type == EbtFloat) {
if (fnCandidate.getName().compare(0, 19, "imageAtomicExchange") == 0) {
// imageAtomicExchange doesn't require an extension
} else if ((fnCandidate.getName().compare(0, 14, "imageAtomicAdd") == 0) ||
(fnCandidate.getName().compare(0, 15, "imageAtomicLoad") == 0) ||
(fnCandidate.getName().compare(0, 16, "imageAtomicStore") == 0)) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float, fnCandidate.getName().c_str());
} else if ((fnCandidate.getName().compare(0, 14, "imageAtomicMin") == 0) ||
(fnCandidate.getName().compare(0, 14, "imageAtomicMax") == 0)) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float2, fnCandidate.getName().c_str());
} else {
error(loc, "only supported on integer images", fnCandidate.getName().c_str(), "");
}
if (imageType.getQualifier().getFormat() != ElfR32f && isEsProfile())
error(loc, "only supported on image with format r32f", fnCandidate.getName().c_str(), "");
} else {
error(loc, "not supported on this image type", fnCandidate.getName().c_str(), "");
}
const size_t maxArgs = imageType.getSampler().isMultiSample() ? 5 : 4;
if (argp->size() > maxArgs) {
requireExtensions(loc, 1, &E_GL_KHR_memory_scope_semantics, fnCandidate.getName().c_str());
memorySemanticsCheck(loc, fnCandidate, callNode);
}
break;
}
case EOpAtomicAdd:
case EOpAtomicSubtract:
case EOpAtomicMin:
case EOpAtomicMax:
case EOpAtomicAnd:
case EOpAtomicOr:
case EOpAtomicXor:
case EOpAtomicExchange:
case EOpAtomicCompSwap:
case EOpAtomicLoad:
case EOpAtomicStore:
{
if (argp->size() > 3) {
requireExtensions(loc, 1, &E_GL_KHR_memory_scope_semantics, fnCandidate.getName().c_str());
memorySemanticsCheck(loc, fnCandidate, callNode);
if ((callNode.getOp() == EOpAtomicAdd || callNode.getOp() == EOpAtomicExchange ||
callNode.getOp() == EOpAtomicLoad || callNode.getOp() == EOpAtomicStore) &&
(arg0->getType().getBasicType() == EbtFloat ||
arg0->getType().getBasicType() == EbtDouble)) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float, fnCandidate.getName().c_str());
} else if ((callNode.getOp() == EOpAtomicAdd || callNode.getOp() == EOpAtomicExchange ||
callNode.getOp() == EOpAtomicLoad || callNode.getOp() == EOpAtomicStore ||
callNode.getOp() == EOpAtomicMin || callNode.getOp() == EOpAtomicMax) &&
arg0->getType().isFloatingDomain()) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float2, fnCandidate.getName().c_str());
}
} else if (arg0->getType().getBasicType() == EbtInt64 || arg0->getType().getBasicType() == EbtUint64) {
const char* const extensions[2] = { E_GL_NV_shader_atomic_int64,
E_GL_EXT_shader_atomic_int64 };
requireExtensions(loc, 2, extensions, fnCandidate.getName().c_str());
} else if ((callNode.getOp() == EOpAtomicAdd || callNode.getOp() == EOpAtomicExchange) &&
(arg0->getType().getBasicType() == EbtFloat ||
arg0->getType().getBasicType() == EbtDouble)) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float, fnCandidate.getName().c_str());
} else if ((callNode.getOp() == EOpAtomicAdd || callNode.getOp() == EOpAtomicExchange ||
callNode.getOp() == EOpAtomicLoad || callNode.getOp() == EOpAtomicStore ||
callNode.getOp() == EOpAtomicMin || callNode.getOp() == EOpAtomicMax) &&
arg0->getType().isFloatingDomain()) {
requireExtensions(loc, 1, &E_GL_EXT_shader_atomic_float2, fnCandidate.getName().c_str());
}
const TIntermTyped* base = TIntermediate::findLValueBase(arg0, true , true);
const char* errMsg = "Only l-values corresponding to shader block storage or shared variables can be used with "
"atomic memory functions.";
if (base) {
const TType* refType = (base->getType().isReference()) ? base->getType().getReferentType() : nullptr;
const TQualifier& qualifier =
(refType != nullptr) ? refType->getQualifier() : base->getType().getQualifier();
if (qualifier.storage != EvqShared && qualifier.storage != EvqBuffer &&
qualifier.storage != EvqtaskPayloadSharedEXT)
error(loc, errMsg, fnCandidate.getName().c_str(), "");
} else {
error(loc, errMsg, fnCandidate.getName().c_str(), "");
}
break;
}
case EOpInterpolateAtCentroid:
case EOpInterpolateAtSample:
case EOpInterpolateAtOffset:
case EOpInterpolateAtVertex:
// Make sure the first argument is an interpolant, or an array element of an interpolant
if (arg0->getType().getQualifier().storage != EvqVaryingIn) {
// It might still be an array element.
//
// We could check more, but the semantics of the first argument are already met; the
// only way to turn an array into a float/vec* is array dereference and swizzle.
//
// ES and desktop 4.3 and earlier: swizzles may not be used
// desktop 4.4 and later: swizzles may be used
bool swizzleOkay = (!isEsProfile()) && (version >= 440);
const TIntermTyped* base = TIntermediate::findLValueBase(arg0, swizzleOkay);
if (base == nullptr || base->getType().getQualifier().storage != EvqVaryingIn)
error(loc, "first argument must be an interpolant, or interpolant-array element", fnCandidate.getName().c_str(), "");
}
if (callNode.getOp() == EOpInterpolateAtVertex) {
if (!arg0->getType().getQualifier().isExplicitInterpolation())
error(loc, "argument must be qualified as __explicitInterpAMD in", "interpolant", "");
else {
if (! (*argp)[1]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "vertex index", "");
else {
unsigned vertexIdx = (*argp)[1]->getAsConstantUnion()->getConstArray()[0].getUConst();
if (vertexIdx > 2)
error(loc, "must be in the range [0, 2]", "vertex index", "");
}
}
}
break;
case EOpEmitStreamVertex:
case EOpEndStreamPrimitive:
if (version == 150)
requireExtensions(loc, 1, &E_GL_ARB_gpu_shader5, "if the verison is 150 , the EmitStreamVertex and EndStreamPrimitive only support at extension GL_ARB_gpu_shader5");
intermediate.setMultiStream();
break;
case EOpSubgroupClusteredAdd:
case EOpSubgroupClusteredMul:
case EOpSubgroupClusteredMin:
case EOpSubgroupClusteredMax:
case EOpSubgroupClusteredAnd:
case EOpSubgroupClusteredOr:
case EOpSubgroupClusteredXor:
// The <clusterSize> as used in the subgroupClustered<op>() operations must be:
// - An integral constant expression.
// - At least 1.
// - A power of 2.
if ((*argp)[1]->getAsConstantUnion() == nullptr)
error(loc, "argument must be compile-time constant", "cluster size", "");
else {
int size = (*argp)[1]->getAsConstantUnion()->getConstArray()[0].getIConst();
if (size < 1)
error(loc, "argument must be at least 1", "cluster size", "");
else if (!IsPow2(size))
error(loc, "argument must be a power of 2", "cluster size", "");
}
break;
case EOpSubgroupBroadcast:
case EOpSubgroupQuadBroadcast:
if (spvVersion.spv < EShTargetSpv_1_5) {
// <id> must be an integral constant expression.
if ((*argp)[1]->getAsConstantUnion() == nullptr)
error(loc, "argument must be compile-time constant", "id", "");
}
break;
case EOpBarrier:
case EOpMemoryBarrier:
if (argp->size() > 0) {
requireExtensions(loc, 1, &E_GL_KHR_memory_scope_semantics, fnCandidate.getName().c_str());
memorySemanticsCheck(loc, fnCandidate, callNode);
}
break;
case EOpMix:
if (profile == EEsProfile && version < 310) {
// Look for specific signatures
if ((*argp)[0]->getAsTyped()->getBasicType() != EbtFloat &&
(*argp)[1]->getAsTyped()->getBasicType() != EbtFloat &&
(*argp)[2]->getAsTyped()->getBasicType() == EbtBool) {
requireExtensions(loc, 1, &E_GL_EXT_shader_integer_mix, "specific signature of builtin mix");
}
}
if (profile != EEsProfile && version < 450) {
if ((*argp)[0]->getAsTyped()->getBasicType() != EbtFloat &&
(*argp)[0]->getAsTyped()->getBasicType() != EbtDouble &&
(*argp)[1]->getAsTyped()->getBasicType() != EbtFloat &&
(*argp)[1]->getAsTyped()->getBasicType() != EbtDouble &&
(*argp)[2]->getAsTyped()->getBasicType() == EbtBool) {
requireExtensions(loc, 1, &E_GL_EXT_shader_integer_mix, fnCandidate.getName().c_str());
}
}
break;
default:
break;
}
// Texture operations on texture objects (aside from texelFetch on a
// textureBuffer) require EXT_samplerless_texture_functions.
switch (callNode.getOp()) {
case EOpTextureQuerySize:
case EOpTextureQueryLevels:
case EOpTextureQuerySamples:
case EOpTextureFetch:
case EOpTextureFetchOffset:
{
const TSampler& sampler = fnCandidate[0].type->getSampler();
const bool isTexture = sampler.isTexture() && !sampler.isCombined();
const bool isBuffer = sampler.isBuffer();
const bool isFetch = callNode.getOp() == EOpTextureFetch || callNode.getOp() == EOpTextureFetchOffset;
if (isTexture && (!isBuffer || !isFetch))
requireExtensions(loc, 1, &E_GL_EXT_samplerless_texture_functions, fnCandidate.getName().c_str());
break;
}
default:
break;
}
if (callNode.isSubgroup()) {
// these require SPIR-V 1.3
if (spvVersion.spv > 0 && spvVersion.spv < EShTargetSpv_1_3)
error(loc, "requires SPIR-V 1.3", "subgroup op", "");
// Check that if extended types are being used that the correct extensions are enabled.
if (arg0 != nullptr) {
const TType& type = arg0->getType();
bool enhanced = intermediate.getEnhancedMsgs();
switch (type.getBasicType()) {
default:
break;
case EbtInt8:
case EbtUint8:
requireExtensions(loc, 1, &E_GL_EXT_shader_subgroup_extended_types_int8, type.getCompleteString(enhanced).c_str());
break;
case EbtInt16:
case EbtUint16:
requireExtensions(loc, 1, &E_GL_EXT_shader_subgroup_extended_types_int16, type.getCompleteString(enhanced).c_str());
break;
case EbtInt64:
case EbtUint64:
requireExtensions(loc, 1, &E_GL_EXT_shader_subgroup_extended_types_int64, type.getCompleteString(enhanced).c_str());
break;
case EbtFloat16:
requireExtensions(loc, 1, &E_GL_EXT_shader_subgroup_extended_types_float16, type.getCompleteString(enhanced).c_str());
break;
}
}
}
}
// Deprecated! Use PureOperatorBuiltins == true instead, in which case this
// functionality is handled in builtInOpCheck() instead of here.
//
// Do additional checking of built-in function calls that were not mapped
// to built-in operations (e.g., texturing functions).
//
// Assumes there has been a semantically correct match to a built-in function.
//
void TParseContext::nonOpBuiltInCheck(const TSourceLoc& loc, const TFunction& fnCandidate, TIntermAggregate& callNode)
{
// Further maintenance of this function is deprecated, because the "correct"
// future-oriented design is to not have to do string compares on function names.
// If PureOperatorBuiltins == true, then all built-ins should be mapped
// to a TOperator, and this function would then never get called.
assert(PureOperatorBuiltins == false);
// built-in texturing functions get their return value precision from the precision of the sampler
if (fnCandidate.getType().getQualifier().precision == EpqNone &&
fnCandidate.getParamCount() > 0 && fnCandidate[0].type->getBasicType() == EbtSampler)
callNode.getQualifier().precision = callNode.getSequence()[0]->getAsTyped()->getQualifier().precision;
if (fnCandidate.getName().compare(0, 7, "texture") == 0) {
if (fnCandidate.getName().compare(0, 13, "textureGather") == 0) {
TString featureString = fnCandidate.getName() + "(...)";
const char* feature = featureString.c_str();
profileRequires(loc, EEsProfile, 310, nullptr, feature);
int compArg = -1; // track which argument, if any, is the constant component argument
if (fnCandidate.getName().compare("textureGatherOffset") == 0) {
// GL_ARB_texture_gather is good enough for 2D non-shadow textures with no component argument
if (fnCandidate[0].type->getSampler().dim == Esd2D && ! fnCandidate[0].type->getSampler().shadow && fnCandidate.getParamCount() == 3)
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_texture_gather, feature);
else
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
int offsetArg = fnCandidate[0].type->getSampler().shadow ? 3 : 2;
if (! callNode.getSequence()[offsetArg]->getAsConstantUnion())
profileRequires(loc, EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5,
"non-constant offset argument");
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
} else if (fnCandidate.getName().compare("textureGatherOffsets") == 0) {
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
// check for constant offsets
int offsetArg = fnCandidate[0].type->getSampler().shadow ? 3 : 2;
if (! callNode.getSequence()[offsetArg]->getAsConstantUnion())
error(loc, "must be a compile-time constant:", feature, "offsets argument");
} else if (fnCandidate.getName().compare("textureGather") == 0) {
// More than two arguments needs gpu_shader5, and rectangular or shadow needs gpu_shader5,
// otherwise, need GL_ARB_texture_gather.
if (fnCandidate.getParamCount() > 2 || fnCandidate[0].type->getSampler().dim == EsdRect || fnCandidate[0].type->getSampler().shadow) {
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 2;
} else
profileRequires(loc, ~EEsProfile, 400, E_GL_ARB_texture_gather, feature);
}
if (compArg > 0 && compArg < fnCandidate.getParamCount()) {
if (callNode.getSequence()[compArg]->getAsConstantUnion()) {
int value = callNode.getSequence()[compArg]->getAsConstantUnion()->getConstArray()[0].getIConst();
if (value < 0 || value > 3)
error(loc, "must be 0, 1, 2, or 3:", feature, "component argument");
} else
error(loc, "must be a compile-time constant:", feature, "component argument");
}
} else {
// this is only for functions not starting "textureGather"...
if (fnCandidate.getName().find("Offset") != TString::npos) {
// Handle texture-offset limits checking
int arg = -1;
if (fnCandidate.getName().compare("textureOffset") == 0)
arg = 2;
else if (fnCandidate.getName().compare("texelFetchOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureProjOffset") == 0)
arg = 2;
else if (fnCandidate.getName().compare("textureLodOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureProjLodOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureGradOffset") == 0)
arg = 4;
else if (fnCandidate.getName().compare("textureProjGradOffset") == 0)
arg = 4;
if (arg > 0) {
if (! callNode.getSequence()[arg]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "texel offset", "");
else {
const TType& type = callNode.getSequence()[arg]->getAsTyped()->getType();
for (int c = 0; c < type.getVectorSize(); ++c) {
int offset = callNode.getSequence()[arg]->getAsConstantUnion()->getConstArray()[c].getIConst();
if (offset > resources.maxProgramTexelOffset || offset < resources.minProgramTexelOffset)
error(loc, "value is out of range:", "texel offset", "[gl_MinProgramTexelOffset, gl_MaxProgramTexelOffset]");
}
}
}
}
}
}
// GL_ARB_shader_texture_image_samples
if (fnCandidate.getName().compare(0, 14, "textureSamples") == 0 || fnCandidate.getName().compare(0, 12, "imageSamples") == 0)
profileRequires(loc, ~EEsProfile, 450, E_GL_ARB_shader_texture_image_samples, "textureSamples and imageSamples");
if (fnCandidate.getName().compare(0, 11, "imageAtomic") == 0) {
const TType& imageType = callNode.getSequence()[0]->getAsTyped()->getType();
if (imageType.getSampler().type == EbtInt || imageType.getSampler().type == EbtUint) {
if (imageType.getQualifier().getFormat() != ElfR32i && imageType.getQualifier().getFormat() != ElfR32ui)
error(loc, "only supported on image with format r32i or r32ui", fnCandidate.getName().c_str(), "");
} else {
if (fnCandidate.getName().compare(0, 19, "imageAtomicExchange") != 0)
error(loc, "only supported on integer images", fnCandidate.getName().c_str(), "");
else if (imageType.getQualifier().getFormat() != ElfR32f && isEsProfile())
error(loc, "only supported on image with format r32f", fnCandidate.getName().c_str(), "");
}
}
}
//
// Do any extra checking for a user function call.
//
void TParseContext::userFunctionCallCheck(const TSourceLoc& loc, TIntermAggregate& callNode)
{
TIntermSequence& arguments = callNode.getSequence();
for (int i = 0; i < (int)arguments.size(); ++i)
samplerConstructorLocationCheck(loc, "call argument", arguments[i]);
}
//
// Emit an error if this is a sampler constructor
//
void TParseContext::samplerConstructorLocationCheck(const TSourceLoc& loc, const char* token, TIntermNode* node)
{
if (node->getAsOperator() && node->getAsOperator()->getOp() == EOpConstructTextureSampler)
error(loc, "sampler constructor must appear at point of use", token, "");
}
//
// Handle seeing a built-in constructor in a grammar production.
//
TFunction* TParseContext::handleConstructorCall(const TSourceLoc& loc, const TPublicType& publicType)
{
TType type(publicType);
type.getQualifier().precision = EpqNone;
if (type.isArray()) {
profileRequires(loc, ENoProfile, 120, E_GL_3DL_array_objects, "arrayed constructor");
profileRequires(loc, EEsProfile, 300, nullptr, "arrayed constructor");
}
// Reuse EOpConstructTextureSampler for bindless image constructor
// uvec2 imgHandle;
// imageLoad(image1D(imgHandle), 0);
if (type.isImage() && extensionTurnedOn(E_GL_ARB_bindless_texture))
{
intermediate.setBindlessImageMode(currentCaller, AstRefTypeFunc);
}
TOperator op = intermediate.mapTypeToConstructorOp(type);
if (op == EOpNull) {
if (intermediate.getEnhancedMsgs() && type.getBasicType() == EbtSampler)
error(loc, "function not supported in this version; use texture() instead", "texture*D*", "");
else
error(loc, "cannot construct this type", type.getBasicString(), "");
op = EOpConstructFloat;
TType errorType(EbtFloat);
type.shallowCopy(errorType);
}
TString empty("");
return new TFunction(&empty, type, op);
}
// Handle seeing a precision qualifier in the grammar.
void TParseContext::handlePrecisionQualifier(const TSourceLoc& /*loc*/, TQualifier& qualifier, TPrecisionQualifier precision)
{
if (obeyPrecisionQualifiers())
qualifier.precision = precision;
}
// Check for messages to give on seeing a precision qualifier used in a
// declaration in the grammar.
void TParseContext::checkPrecisionQualifier(const TSourceLoc& loc, TPrecisionQualifier)
{
if (precisionManager.shouldWarnAboutDefaults()) {
warn(loc, "all default precisions are highp; use precision statements to quiet warning, e.g.:\n"
" \"precision mediump int; precision highp float;\"", "", "");
precisionManager.defaultWarningGiven();
}
}
//
// Same error message for all places assignments don't work.
//
void TParseContext::assignError(const TSourceLoc& loc, const char* op, TString left, TString right)
{
error(loc, "", op, "cannot convert from '%s' to '%s'",
right.c_str(), left.c_str());
}
//
// Same error message for all places unary operations don't work.
//
void TParseContext::unaryOpError(const TSourceLoc& loc, const char* op, TString operand)
{
error(loc, " wrong operand type", op,
"no operation '%s' exists that takes an operand of type %s (or there is no acceptable conversion)",
op, operand.c_str());
}
//
// Same error message for all binary operations don't work.
//
void TParseContext::binaryOpError(const TSourceLoc& loc, const char* op, TString left, TString right)
{
error(loc, " wrong operand types:", op,
"no operation '%s' exists that takes a left-hand operand of type '%s' and "
"a right operand of type '%s' (or there is no acceptable conversion)",
op, left.c_str(), right.c_str());
}
//
// A basic type of EbtVoid is a key that the name string was seen in the source, but
// it was not found as a variable in the symbol table. If so, give the error
// message and insert a dummy variable in the symbol table to prevent future errors.
//
void TParseContext::variableCheck(TIntermTyped*& nodePtr)
{
TIntermSymbol* symbol = nodePtr->getAsSymbolNode();
if (! symbol)
return;
if (symbol->getType().getBasicType() == EbtVoid) {
const char *extraInfoFormat = "";
if (spvVersion.vulkan != 0 && symbol->getName() == "gl_VertexID") {
extraInfoFormat = "(Did you mean gl_VertexIndex?)";
} else if (spvVersion.vulkan != 0 && symbol->getName() == "gl_InstanceID") {
extraInfoFormat = "(Did you mean gl_InstanceIndex?)";
}
error(symbol->getLoc(), "undeclared identifier", symbol->getName().c_str(), extraInfoFormat);
// Add to symbol table to prevent future error messages on the same name
if (symbol->getName().size() > 0) {
TVariable* fakeVariable = new TVariable(&symbol->getName(), TType(EbtFloat));
symbolTable.insert(*fakeVariable);
// substitute a symbol node for this new variable
nodePtr = intermediate.addSymbol(*fakeVariable, symbol->getLoc());
}
} else {
switch (symbol->getQualifier().storage) {
case EvqPointCoord:
profileRequires(symbol->getLoc(), ENoProfile, 120, nullptr, "gl_PointCoord");
break;
default: break; // some compilers want this
}
}
}
//
// Both test and if necessary, spit out an error, to see if the node is really
// an l-value that can be operated on this way.
//
// Returns true if there was an error.
//
bool TParseContext::lValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
TIntermBinary* binaryNode = node->getAsBinaryNode();
if (binaryNode) {
bool errorReturn = false;
switch(binaryNode->getOp()) {
case EOpIndexDirect:
case EOpIndexIndirect:
// ... tessellation control shader ...
// If a per-vertex output variable is used as an l-value, it is a
// compile-time or link-time error if the expression indicating the
// vertex index is not the identifier gl_InvocationID.
if (language == EShLangTessControl) {
const TType& leftType = binaryNode->getLeft()->getType();
if (leftType.getQualifier().storage == EvqVaryingOut && ! leftType.getQualifier().patch && binaryNode->getLeft()->getAsSymbolNode()) {
// we have a per-vertex output
const TIntermSymbol* rightSymbol = binaryNode->getRight()->getAsSymbolNode();
if (! rightSymbol || rightSymbol->getQualifier().builtIn != EbvInvocationId)
error(loc, "tessellation-control per-vertex output l-value must be indexed with gl_InvocationID", "[]", "");
}
}
break; // left node is checked by base class
case EOpVectorSwizzle:
errorReturn = lValueErrorCheck(loc, op, binaryNode->getLeft());
if (!errorReturn) {
int offset[4] = {0,0,0,0};
TIntermTyped* rightNode = binaryNode->getRight();
TIntermAggregate *aggrNode = rightNode->getAsAggregate();
for (TIntermSequence::iterator p = aggrNode->getSequence().begin();
p != aggrNode->getSequence().end(); p++) {
int value = (*p)->getAsTyped()->getAsConstantUnion()->getConstArray()[0].getIConst();
offset[value]++;
if (offset[value] > 1) {
error(loc, " l-value of swizzle cannot have duplicate components", op, "", "");
return true;
}
}
}
return errorReturn;
default:
break;
}
if (errorReturn) {
error(loc, " l-value required", op, "", "");
return true;
}
}
if (binaryNode && binaryNode->getOp() == EOpIndexDirectStruct && binaryNode->getLeft()->isReference())
return false;
// Let the base class check errors
if (TParseContextBase::lValueErrorCheck(loc, op, node))
return true;
const char* symbol = nullptr;
TIntermSymbol* symNode = node->getAsSymbolNode();
if (symNode != nullptr)
symbol = symNode->getName().c_str();
const char* message = nullptr;
switch (node->getQualifier().storage) {
case EvqVaryingIn: message = "can't modify shader input"; break;
case EvqInstanceId: message = "can't modify gl_InstanceID"; break;
case EvqVertexId: message = "can't modify gl_VertexID"; break;
case EvqFace: message = "can't modify gl_FrontFace"; break;
case EvqFragCoord: message = "can't modify gl_FragCoord"; break;
case EvqPointCoord: message = "can't modify gl_PointCoord"; break;
case EvqFragDepth:
intermediate.setDepthReplacing();
// "In addition, it is an error to statically write to gl_FragDepth in the fragment shader."
if (isEsProfile() && intermediate.getEarlyFragmentTests())
message = "can't modify gl_FragDepth if using early_fragment_tests";
break;
case EvqFragStencil:
intermediate.setStencilReplacing();
// "In addition, it is an error to statically write to gl_FragDepth in the fragment shader."
if (isEsProfile() && intermediate.getEarlyFragmentTests())
message = "can't modify EvqFragStencil if using early_fragment_tests";
break;
case EvqtaskPayloadSharedEXT:
if (language == EShLangMesh)
message = "can't modify variable with storage qualifier taskPayloadSharedEXT in mesh shaders";
break;
default:
break;
}
if (message == nullptr && binaryNode == nullptr && symNode == nullptr) {
error(loc, " l-value required", op, "", "");
return true;
}
//
// Everything else is okay, no error.
//
if (message == nullptr)
return false;
//
// If we get here, we have an error and a message.
//
if (symNode)
error(loc, " l-value required", op, "\"%s\" (%s)", symbol, message);
else
error(loc, " l-value required", op, "(%s)", message);
return true;
}
// Test for and give an error if the node can't be read from.
void TParseContext::rValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
// Let the base class check errors
TParseContextBase::rValueErrorCheck(loc, op, node);
TIntermSymbol* symNode = node->getAsSymbolNode();
if (!(symNode && symNode->getQualifier().isWriteOnly())) // base class checks
if (symNode && symNode->getQualifier().isExplicitInterpolation())
error(loc, "can't read from explicitly-interpolated object: ", op, symNode->getName().c_str());
// local_size_{xyz} must be assigned or specialized before gl_WorkGroupSize can be assigned.
if(node->getQualifier().builtIn == EbvWorkGroupSize &&
!(intermediate.isLocalSizeSet() || intermediate.isLocalSizeSpecialized()))
error(loc, "can't read from gl_WorkGroupSize before a fixed workgroup size has been declared", op, "");
}
//
// Both test, and if necessary spit out an error, to see if the node is really
// a constant.
//
void TParseContext::constantValueCheck(TIntermTyped* node, const char* token)
{
if (! node->getQualifier().isConstant())
error(node->getLoc(), "constant expression required", token, "");
}
//
// Both test, and if necessary spit out an error, to see if the node is really
// a 32-bit integer or can implicitly convert to one.
//
void TParseContext::integerCheck(const TIntermTyped* node, const char* token)
{
auto from_type = node->getBasicType();
if ((from_type == EbtInt || from_type == EbtUint ||
intermediate.canImplicitlyPromote(from_type, EbtInt, EOpNull) ||
intermediate.canImplicitlyPromote(from_type, EbtUint, EOpNull)) && node->isScalar())
return;
error(node->getLoc(), "scalar integer expression required", token, "");
}
//
// Both test, and if necessary spit out an error, to see if we are currently
// globally scoped.
//
void TParseContext::globalCheck(const TSourceLoc& loc, const char* token)
{
if (! symbolTable.atGlobalLevel())
error(loc, "not allowed in nested scope", token, "");
}
//
// Reserved errors for GLSL.
//
void TParseContext::reservedErrorCheck(const TSourceLoc& loc, const TString& identifier)
{
// "Identifiers starting with "gl_" are reserved for use by OpenGL, and may not be
// declared in a shader; this results in a compile-time error."
if (! symbolTable.atBuiltInLevel()) {
if (builtInName(identifier) && !extensionTurnedOn(E_GL_EXT_spirv_intrinsics))
// The extension GL_EXT_spirv_intrinsics allows us to declare identifiers starting with "gl_".
error(loc, "identifiers starting with \"gl_\" are reserved", identifier.c_str(), "");
// "__" are not supposed to be an error. ES 300 (and desktop) added the clarification:
// "In addition, all identifiers containing two consecutive underscores (__) are
// reserved; using such a name does not itself result in an error, but may result
// in undefined behavior."
// however, before that, ES tests required an error.
if (identifier.find("__") != TString::npos && !extensionTurnedOn(E_GL_EXT_spirv_intrinsics)) {
// The extension GL_EXT_spirv_intrinsics allows us to declare identifiers starting with "__".
if (isEsProfile() && version < 300)
error(loc, "identifiers containing consecutive underscores (\"__\") are reserved, and an error if version < 300", identifier.c_str(), "");
else
warn(loc, "identifiers containing consecutive underscores (\"__\") are reserved", identifier.c_str(), "");
}
}
}
//
// Reserved errors for the preprocessor.
//
void TParseContext::reservedPpErrorCheck(const TSourceLoc& loc, const char* identifier, const char* op)
{
// "__" are not supposed to be an error. ES 300 (and desktop) added the clarification:
// "All macro names containing two consecutive underscores ( __ ) are reserved;
// defining such a name does not itself result in an error, but may result in
// undefined behavior. All macro names prefixed with "GL_" ("GL" followed by a
// single underscore) are also reserved, and defining such a name results in a
// compile-time error."
// however, before that, ES tests required an error.
if (strncmp(identifier, "GL_", 3) == 0 && !extensionTurnedOn(E_GL_EXT_spirv_intrinsics))
// The extension GL_EXT_spirv_intrinsics allows us to declare macros prefixed with "GL_".
ppError(loc, "names beginning with \"GL_\" can't be (un)defined:", op, identifier);
else if (strncmp(identifier, "defined", 8) == 0)
if (relaxedErrors())
ppWarn(loc, "\"defined\" is (un)defined:", op, identifier);
else
ppError(loc, "\"defined\" can't be (un)defined:", op, identifier);
else if (strstr(identifier, "__") != nullptr && !extensionTurnedOn(E_GL_EXT_spirv_intrinsics)) {
// The extension GL_EXT_spirv_intrinsics allows us to declare macros prefixed with "__".
if (isEsProfile() && version >= 300 &&
(strcmp(identifier, "__LINE__") == 0 ||
strcmp(identifier, "__FILE__") == 0 ||
strcmp(identifier, "__VERSION__") == 0))
ppError(loc, "predefined names can't be (un)defined:", op, identifier);
else {
if (isEsProfile() && version < 300 && !relaxedErrors())
ppError(loc, "names containing consecutive underscores are reserved, and an error if version < 300:", op, identifier);
else
ppWarn(loc, "names containing consecutive underscores are reserved:", op, identifier);
}
}
}
//
// See if this version/profile allows use of the line-continuation character '\'.
//
// Returns true if a line continuation should be done.
//
bool TParseContext::lineContinuationCheck(const TSourceLoc& loc, bool endOfComment)
{
const char* message = "line continuation";
bool lineContinuationAllowed = (isEsProfile() && version >= 300) ||
(!isEsProfile() && (version >= 420 || extensionTurnedOn(E_GL_ARB_shading_language_420pack)));
if (endOfComment) {
if (lineContinuationAllowed)
warn(loc, "used at end of comment; the following line is still part of the comment", message, "");
else
warn(loc, "used at end of comment, but this version does not provide line continuation", message, "");
return lineContinuationAllowed;
}
if (relaxedErrors()) {
if (! lineContinuationAllowed)
warn(loc, "not allowed in this version", message, "");
return true;
} else {
profileRequires(loc, EEsProfile, 300, nullptr, message);
profileRequires(loc, ~EEsProfile, 420, E_GL_ARB_shading_language_420pack, message);
}
return lineContinuationAllowed;
}
bool TParseContext::builtInName(const TString& identifier)
{
return identifier.compare(0, 3, "gl_") == 0;
}
//
// Make sure there is enough data and not too many arguments provided to the
// constructor to build something of the type of the constructor. Also returns
// the type of the constructor.
//
// Part of establishing type is establishing specialization-constness.
// We don't yet know "top down" whether type is a specialization constant,
// but a const constructor can becomes a specialization constant if any of
// its children are, subject to KHR_vulkan_glsl rules:
//
// - int(), uint(), and bool() constructors for type conversions
// from any of the following types to any of the following types:
// * int
// * uint
// * bool
// - vector versions of the above conversion constructors
//
// Returns true if there was an error in construction.
//
bool TParseContext::constructorError(const TSourceLoc& loc, TIntermNode* node, TFunction& function, TOperator op, TType& type)
{
// See if the constructor does not establish the main type, only requalifies
// it, in which case the type comes from the argument instead of from the
// constructor function.
switch (op) {
case EOpConstructNonuniform:
if (node != nullptr && node->getAsTyped() != nullptr) {
type.shallowCopy(node->getAsTyped()->getType());
type.getQualifier().makeTemporary();
type.getQualifier().nonUniform = true;
}
break;
default:
type.shallowCopy(function.getType());
break;
}
TString constructorString;
if (intermediate.getEnhancedMsgs())
constructorString.append(type.getCompleteString(true, false, false, true)).append(" constructor");
else
constructorString.append("constructor");
// See if it's a matrix
bool constructingMatrix = false;
switch (op) {
case EOpConstructTextureSampler:
return constructorTextureSamplerError(loc, function);
case EOpConstructMat2x2:
case EOpConstructMat2x3:
case EOpConstructMat2x4:
case EOpConstructMat3x2:
case EOpConstructMat3x3:
case EOpConstructMat3x4:
case EOpConstructMat4x2:
case EOpConstructMat4x3:
case EOpConstructMat4x4:
case EOpConstructDMat2x2:
case EOpConstructDMat2x3:
case EOpConstructDMat2x4:
case EOpConstructDMat3x2:
case EOpConstructDMat3x3:
case EOpConstructDMat3x4:
case EOpConstructDMat4x2:
case EOpConstructDMat4x3:
case EOpConstructDMat4x4:
case EOpConstructF16Mat2x2:
case EOpConstructF16Mat2x3:
case EOpConstructF16Mat2x4:
case EOpConstructF16Mat3x2:
case EOpConstructF16Mat3x3:
case EOpConstructF16Mat3x4:
case EOpConstructF16Mat4x2:
case EOpConstructF16Mat4x3:
case EOpConstructF16Mat4x4:
constructingMatrix = true;
break;
default:
break;
}
//
// Walk the arguments for first-pass checks and collection of information.
//
int size = 0;
bool constType = true;
bool specConstType = false; // value is only valid if constType is true
bool full = false;
bool overFull = false;
bool matrixInMatrix = false;
bool arrayArg = false;
bool floatArgument = false;
bool intArgument = false;
for (int arg = 0; arg < function.getParamCount(); ++arg) {
if (function[arg].type->isArray()) {
if (function[arg].type->isUnsizedArray()) {
// Can't construct from an unsized array.
error(loc, "array argument must be sized", constructorString.c_str(), "");
return true;
}
arrayArg = true;
}
if (constructingMatrix && function[arg].type->isMatrix())
matrixInMatrix = true;
// 'full' will go to true when enough args have been seen. If we loop
// again, there is an extra argument.
if (full) {
// For vectors and matrices, it's okay to have too many components
// available, but not okay to have unused arguments.
overFull = true;
}
size += function[arg].type->computeNumComponents();
if (op != EOpConstructStruct && ! type.isArray() && size >= type.computeNumComponents())
full = true;
if (! function[arg].type->getQualifier().isConstant())
constType = false;
if (function[arg].type->getQualifier().isSpecConstant())
specConstType = true;
if (function[arg].type->isFloatingDomain())
floatArgument = true;
if (function[arg].type->isIntegerDomain())
intArgument = true;
if (type.isStruct()) {
if (function[arg].type->contains16BitFloat()) {
requireFloat16Arithmetic(loc, constructorString.c_str(), "can't construct structure containing 16-bit type");
}
if (function[arg].type->contains16BitInt()) {
requireInt16Arithmetic(loc, constructorString.c_str(), "can't construct structure containing 16-bit type");
}
if (function[arg].type->contains8BitInt()) {
requireInt8Arithmetic(loc, constructorString.c_str(), "can't construct structure containing 8-bit type");
}
}
}
if (op == EOpConstructNonuniform)
constType = false;
switch (op) {
case EOpConstructFloat16:
case EOpConstructF16Vec2:
case EOpConstructF16Vec3:
case EOpConstructF16Vec4:
if (type.isArray())
requireFloat16Arithmetic(loc, constructorString.c_str(), "16-bit arrays not supported");
if (type.isVector() && function.getParamCount() != 1)
requireFloat16Arithmetic(loc, constructorString.c_str(), "16-bit vectors only take vector types");
break;
case EOpConstructUint16:
case EOpConstructU16Vec2:
case EOpConstructU16Vec3:
case EOpConstructU16Vec4:
case EOpConstructInt16:
case EOpConstructI16Vec2:
case EOpConstructI16Vec3:
case EOpConstructI16Vec4:
if (type.isArray())
requireInt16Arithmetic(loc, constructorString.c_str(), "16-bit arrays not supported");
if (type.isVector() && function.getParamCount() != 1)
requireInt16Arithmetic(loc, constructorString.c_str(), "16-bit vectors only take vector types");
break;
case EOpConstructUint8:
case EOpConstructU8Vec2:
case EOpConstructU8Vec3:
case EOpConstructU8Vec4:
case EOpConstructInt8:
case EOpConstructI8Vec2:
case EOpConstructI8Vec3:
case EOpConstructI8Vec4:
if (type.isArray())
requireInt8Arithmetic(loc, constructorString.c_str(), "8-bit arrays not supported");
if (type.isVector() && function.getParamCount() != 1)
requireInt8Arithmetic(loc, constructorString.c_str(), "8-bit vectors only take vector types");
break;
default:
break;
}
// inherit constness from children
if (constType) {
bool makeSpecConst;
// Finish pinning down spec-const semantics
if (specConstType) {
switch (op) {
case EOpConstructInt8:
case EOpConstructInt:
case EOpConstructUint:
case EOpConstructBool:
case EOpConstructBVec2:
case EOpConstructBVec3:
case EOpConstructBVec4:
case EOpConstructIVec2:
case EOpConstructIVec3:
case EOpConstructIVec4:
case EOpConstructUVec2:
case EOpConstructUVec3:
case EOpConstructUVec4:
case EOpConstructUint8:
case EOpConstructInt16:
case EOpConstructUint16:
case EOpConstructInt64:
case EOpConstructUint64:
case EOpConstructI8Vec2:
case EOpConstructI8Vec3:
case EOpConstructI8Vec4:
case EOpConstructU8Vec2:
case EOpConstructU8Vec3:
case EOpConstructU8Vec4:
case EOpConstructI16Vec2:
case EOpConstructI16Vec3:
case EOpConstructI16Vec4:
case EOpConstructU16Vec2:
case EOpConstructU16Vec3:
case EOpConstructU16Vec4:
case EOpConstructI64Vec2:
case EOpConstructI64Vec3:
case EOpConstructI64Vec4:
case EOpConstructU64Vec2:
case EOpConstructU64Vec3:
case EOpConstructU64Vec4:
// This was the list of valid ones, if they aren't converting from float
// and aren't making an array.
makeSpecConst = ! floatArgument && ! type.isArray();
break;
case EOpConstructVec2:
case EOpConstructVec3:
case EOpConstructVec4:
// This was the list of valid ones, if they aren't converting from int
// and aren't making an array.
makeSpecConst = ! intArgument && !type.isArray();
break;
default:
// anything else wasn't white-listed in the spec as a conversion
makeSpecConst = false;
break;
}
} else
makeSpecConst = false;
if (makeSpecConst)
type.getQualifier().makeSpecConstant();
else if (specConstType)
type.getQualifier().makeTemporary();
else
type.getQualifier().storage = EvqConst;
}
if (type.isArray()) {
if (function.getParamCount() == 0) {
error(loc, "array constructor must have at least one argument", constructorString.c_str(), "");
return true;
}
if (type.isUnsizedArray()) {
// auto adapt the constructor type to the number of arguments
type.changeOuterArraySize(function.getParamCount());
} else if (type.getOuterArraySize() != function.getParamCount()) {
error(loc, "array constructor needs one argument per array element", constructorString.c_str(), "");
return true;
}
if (type.isArrayOfArrays()) {
// Types have to match, but we're still making the type.
// Finish making the type, and the comparison is done later
// when checking for conversion.
TArraySizes& arraySizes = *type.getArraySizes();
// At least the dimensionalities have to match.
if (! function[0].type->isArray() ||
arraySizes.getNumDims() != function[0].type->getArraySizes()->getNumDims() + 1) {
error(loc, "array constructor argument not correct type to construct array element", constructorString.c_str(), "");
return true;
}
if (arraySizes.isInnerUnsized()) {
// "Arrays of arrays ..., and the size for any dimension is optional"
// That means we need to adopt (from the first argument) the other array sizes into the type.
for (int d = 1; d < arraySizes.getNumDims(); ++d) {
if (arraySizes.getDimSize(d) == UnsizedArraySize) {
arraySizes.setDimSize(d, function[0].type->getArraySizes()->getDimSize(d - 1));
}
}
}
}
}
if (arrayArg && op != EOpConstructStruct && ! type.isArrayOfArrays()) {
error(loc, "constructing non-array constituent from array argument", constructorString.c_str(), "");
return true;
}
if (matrixInMatrix && ! type.isArray()) {
profileRequires(loc, ENoProfile, 120, nullptr, "constructing matrix from matrix");
// "If a matrix argument is given to a matrix constructor,
// it is a compile-time error to have any other arguments."
if (function.getParamCount() != 1)
error(loc, "matrix constructed from matrix can only have one argument", constructorString.c_str(), "");
return false;
}
if (overFull) {
error(loc, "too many arguments", constructorString.c_str(), "");
return true;
}
if (op == EOpConstructStruct && ! type.isArray() && (int)type.getStruct()->size() != function.getParamCount()) {
error(loc, "Number of constructor parameters does not match the number of structure fields", constructorString.c_str(), "");
return true;
}
if ((op != EOpConstructStruct && size != 1 && size < type.computeNumComponents()) ||
(op == EOpConstructStruct && size < type.computeNumComponents())) {
error(loc, "not enough data provided for construction", constructorString.c_str(), "");
return true;
}
if (type.isCoopMat() && function.getParamCount() != 1) {
error(loc, "wrong number of arguments", constructorString.c_str(), "");
return true;
}
if (type.isCoopMat() &&
!(function[0].type->isScalar() || function[0].type->isCoopMat())) {
error(loc, "Cooperative matrix constructor argument must be scalar or cooperative matrix", constructorString.c_str(), "");
return true;
}
TIntermTyped* typed = node->getAsTyped();
if (type.isCoopMat() && typed->getType().isCoopMat() &&
!type.sameCoopMatShapeAndUse(typed->getType())) {
error(loc, "Cooperative matrix type parameters mismatch", constructorString.c_str(), "");
return true;
}
if (typed == nullptr) {
error(loc, "constructor argument does not have a type", constructorString.c_str(), "");
return true;
}
if (op != EOpConstructStruct && op != EOpConstructNonuniform && typed->getBasicType() == EbtSampler) {
if (op == EOpConstructUVec2 && extensionTurnedOn(E_GL_ARB_bindless_texture)) {
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeFunc);
}
else {
error(loc, "cannot convert a sampler", constructorString.c_str(), "");
return true;
}
}
if (op != EOpConstructStruct && typed->isAtomic()) {
error(loc, "cannot convert an atomic_uint", constructorString.c_str(), "");
return true;
}
if (typed->getBasicType() == EbtVoid) {
error(loc, "cannot convert a void", constructorString.c_str(), "");
return true;
}
return false;
}
// Verify all the correct semantics for constructing a combined texture/sampler.
// Return true if the semantics are incorrect.
bool TParseContext::constructorTextureSamplerError(const TSourceLoc& loc, const TFunction& function)
{
TString constructorName = function.getType().getBasicTypeString(); // TODO: performance: should not be making copy; interface needs to change
const char* token = constructorName.c_str();
// verify the constructor for bindless texture, the input must be ivec2 or uvec2
if (function.getParamCount() == 1) {
TType* pType = function[0].type;
TBasicType basicType = pType->getBasicType();
bool isIntegerVec2 = ((basicType == EbtUint || basicType == EbtInt) && pType->getVectorSize() == 2);
bool bindlessMode = extensionTurnedOn(E_GL_ARB_bindless_texture);
if (isIntegerVec2 && bindlessMode) {
if (pType->getSampler().isImage())
intermediate.setBindlessImageMode(currentCaller, AstRefTypeFunc);
else
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeFunc);
return false;
} else {
if (!bindlessMode)
error(loc, "sampler-constructor requires the extension GL_ARB_bindless_texture enabled", token, "");
else
error(loc, "sampler-constructor requires the input to be ivec2 or uvec2", token, "");
return true;
}
}
// exactly two arguments needed
if (function.getParamCount() != 2) {
error(loc, "sampler-constructor requires two arguments", token, "");
return true;
}
// For now, not allowing arrayed constructors, the rest of this function
// is set up to allow them, if this test is removed:
if (function.getType().isArray()) {
error(loc, "sampler-constructor cannot make an array of samplers", token, "");
return true;
}
// first argument
// * the constructor's first argument must be a texture type
// * the dimensionality (1D, 2D, 3D, Cube, Rect, Buffer, MS, and Array)
// of the texture type must match that of the constructed sampler type
// (that is, the suffixes of the type of the first argument and the
// type of the constructor will be spelled the same way)
if (function[0].type->getBasicType() != EbtSampler ||
! function[0].type->getSampler().isTexture() ||
function[0].type->isArray()) {
error(loc, "sampler-constructor first argument must be a scalar *texture* type", token, "");
return true;
}
// simulate the first argument's impact on the result type, so it can be compared with the encapsulated operator!=()
TSampler texture = function.getType().getSampler();
texture.setCombined(false);
texture.shadow = false;
if (texture != function[0].type->getSampler()) {
error(loc, "sampler-constructor first argument must be a *texture* type"
" matching the dimensionality and sampled type of the constructor", token, "");
return true;
}
// second argument
// * the constructor's second argument must be a scalar of type
// *sampler* or *samplerShadow*
if ( function[1].type->getBasicType() != EbtSampler ||
! function[1].type->getSampler().isPureSampler() ||
function[1].type->isArray()) {
error(loc, "sampler-constructor second argument must be a scalar sampler or samplerShadow", token, "");
return true;
}
return false;
}
// Checks to see if a void variable has been declared and raise an error message for such a case
//
// returns true in case of an error
//
bool TParseContext::voidErrorCheck(const TSourceLoc& loc, const TString& identifier, const TBasicType basicType)
{
if (basicType == EbtVoid) {
error(loc, "illegal use of type 'void'", identifier.c_str(), "");
return true;
}
return false;
}
// Checks to see if the node (for the expression) contains a scalar boolean expression or not
void TParseContext::boolCheck(const TSourceLoc& loc, const TIntermTyped* type)
{
if (type->getBasicType() != EbtBool || type->isArray() || type->isMatrix() || type->isVector())
error(loc, "boolean expression expected", "", "");
}
// This function checks to see if the node (for the expression) contains a scalar boolean expression or not
void TParseContext::boolCheck(const TSourceLoc& loc, const TPublicType& pType)
{
if (pType.basicType != EbtBool || pType.arraySizes || pType.matrixCols > 1 || (pType.vectorSize > 1))
error(loc, "boolean expression expected", "", "");
}
void TParseContext::samplerCheck(const TSourceLoc& loc, const TType& type, const TString& identifier, TIntermTyped* /*initializer*/)
{
// Check that the appropriate extension is enabled if external sampler is used.
// There are two extensions. The correct one must be used based on GLSL version.
if (type.getBasicType() == EbtSampler && type.getSampler().isExternal()) {
if (version < 300) {
requireExtensions(loc, 1, &E_GL_OES_EGL_image_external, "samplerExternalOES");
} else {
requireExtensions(loc, 1, &E_GL_OES_EGL_image_external_essl3, "samplerExternalOES");
}
}
if (type.getSampler().isYuv()) {
requireExtensions(loc, 1, &E_GL_EXT_YUV_target, "__samplerExternal2DY2YEXT");
}
if (type.getQualifier().storage == EvqUniform)
return;
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtSampler)) {
// For bindless texture, sampler can be declared as an struct member
if (extensionTurnedOn(E_GL_ARB_bindless_texture)) {
if (type.getSampler().isImage())
intermediate.setBindlessImageMode(currentCaller, AstRefTypeVar);
else
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeVar);
}
else {
error(loc, "non-uniform struct contains a sampler or image:", type.getBasicTypeString().c_str(), identifier.c_str());
}
}
else if (type.getBasicType() == EbtSampler && type.getQualifier().storage != EvqUniform) {
// For bindless texture, sampler can be declared as an input/output/block member
if (extensionTurnedOn(E_GL_ARB_bindless_texture)) {
if (type.getSampler().isImage())
intermediate.setBindlessImageMode(currentCaller, AstRefTypeVar);
else
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeVar);
}
else {
// non-uniform sampler
// not yet: okay if it has an initializer
// if (! initializer)
if (type.getSampler().isAttachmentEXT() && type.getQualifier().storage != EvqTileImageEXT)
error(loc, "can only be used in tileImageEXT variables or function parameters:", type.getBasicTypeString().c_str(), identifier.c_str());
else if (type.getQualifier().storage != EvqTileImageEXT)
error(loc, "sampler/image types can only be used in uniform variables or function parameters:", type.getBasicTypeString().c_str(), identifier.c_str());
}
}
}
void TParseContext::atomicUintCheck(const TSourceLoc& loc, const TType& type, const TString& identifier)
{
if (type.getQualifier().storage == EvqUniform)
return;
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtAtomicUint))
error(loc, "non-uniform struct contains an atomic_uint:", type.getBasicTypeString().c_str(), identifier.c_str());
else if (type.getBasicType() == EbtAtomicUint && type.getQualifier().storage != EvqUniform)
error(loc, "atomic_uints can only be used in uniform variables or function parameters:", type.getBasicTypeString().c_str(), identifier.c_str());
}
void TParseContext::accStructCheck(const TSourceLoc& loc, const TType& type, const TString& identifier)
{
if (type.getQualifier().storage == EvqUniform)
return;
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtAccStruct))
error(loc, "non-uniform struct contains an accelerationStructureNV:", type.getBasicTypeString().c_str(), identifier.c_str());
else if (type.getBasicType() == EbtAccStruct && type.getQualifier().storage != EvqUniform)
error(loc, "accelerationStructureNV can only be used in uniform variables or function parameters:",
type.getBasicTypeString().c_str(), identifier.c_str());
}
void TParseContext::transparentOpaqueCheck(const TSourceLoc& loc, const TType& type, const TString& identifier)
{
if (parsingBuiltins)
return;
if (type.getQualifier().storage != EvqUniform)
return;
if (type.containsNonOpaque()) {
// Vulkan doesn't allow transparent uniforms outside of blocks
if (spvVersion.vulkan > 0 && !spvVersion.vulkanRelaxed)
vulkanRemoved(loc, "non-opaque uniforms outside a block");
// OpenGL wants locations on these (unless they are getting automapped)
if (spvVersion.openGl > 0 && !type.getQualifier().hasLocation() && !intermediate.getAutoMapLocations())
error(loc, "non-opaque uniform variables need a layout(location=L)", identifier.c_str(), "");
}
}
//
// Qualifier checks knowing the qualifier and that it is a member of a struct/block.
//
void TParseContext::memberQualifierCheck(glslang::TPublicType& publicType)
{
globalQualifierFixCheck(publicType.loc, publicType.qualifier, true);
checkNoShaderLayouts(publicType.loc, publicType.shaderQualifiers);
if (publicType.qualifier.isNonUniform()) {
error(publicType.loc, "not allowed on block or structure members", "nonuniformEXT", "");
publicType.qualifier.nonUniform = false;
}
}
//
// Check/fix just a full qualifier (no variables or types yet, but qualifier is complete) at global level.
//
void TParseContext::globalQualifierFixCheck(const TSourceLoc& loc, TQualifier& qualifier, bool isMemberCheck, const TPublicType* publicType)
{
bool nonuniformOkay = false;
// move from parameter/unknown qualifiers to pipeline in/out qualifiers
switch (qualifier.storage) {
case EvqIn:
profileRequires(loc, ENoProfile, 130, nullptr, "in for stage inputs");
profileRequires(loc, EEsProfile, 300, nullptr, "in for stage inputs");
qualifier.storage = EvqVaryingIn;
nonuniformOkay = true;
break;
case EvqOut:
profileRequires(loc, ENoProfile, 130, nullptr, "out for stage outputs");
profileRequires(loc, EEsProfile, 300, nullptr, "out for stage outputs");
qualifier.storage = EvqVaryingOut;
if (intermediate.isInvariantAll())
qualifier.invariant = true;
break;
case EvqInOut:
qualifier.storage = EvqVaryingIn;
error(loc, "cannot use 'inout' at global scope", "", "");
break;
case EvqGlobal:
case EvqTemporary:
nonuniformOkay = true;
break;
case EvqUniform:
// According to GLSL spec: The std430 qualifier is supported only for shader storage blocks; a shader using
// the std430 qualifier on a uniform block will fail to compile.
// Only check the global declaration: layout(std430) uniform;
if (blockName == nullptr &&
qualifier.layoutPacking == ElpStd430)
{
requireExtensions(loc, 1, &E_GL_EXT_scalar_block_layout, "default std430 layout for uniform");
}
if (publicType != nullptr && publicType->isImage() &&
(qualifier.layoutFormat > ElfExtSizeGuard && qualifier.layoutFormat < ElfCount))
qualifier.layoutFormat = mapLegacyLayoutFormat(qualifier.layoutFormat, publicType->sampler.getBasicType());
break;
default:
break;
}
if (!nonuniformOkay && qualifier.isNonUniform())
error(loc, "for non-parameter, can only apply to 'in' or no storage qualifier", "nonuniformEXT", "");
if (qualifier.isSpirvByReference())
error(loc, "can only apply to parameter", "spirv_by_reference", "");
if (qualifier.isSpirvLiteral())
error(loc, "can only apply to parameter", "spirv_literal", "");
// Storage qualifier isn't ready for memberQualifierCheck, we should skip invariantCheck for it.
if (!isMemberCheck || structNestingLevel > 0)
invariantCheck(loc, qualifier);
}
//
// Check a full qualifier and type (no variable yet) at global level.
//
void TParseContext::globalQualifierTypeCheck(const TSourceLoc& loc, const TQualifier& qualifier, const TPublicType& publicType)
{
if (! symbolTable.atGlobalLevel())
return;
if (!(publicType.userDef && publicType.userDef->isReference()) && !parsingBuiltins) {
if (qualifier.isMemoryQualifierImageAndSSBOOnly() && ! publicType.isImage() && publicType.qualifier.storage != EvqBuffer) {
error(loc, "memory qualifiers cannot be used on this type", "", "");
} else if (qualifier.isMemory() && (publicType.basicType != EbtSampler) && !publicType.qualifier.isUniformOrBuffer()) {
error(loc, "memory qualifiers cannot be used on this type", "", "");
}
}
if (qualifier.storage == EvqBuffer &&
publicType.basicType != EbtBlock &&
!qualifier.hasBufferReference())
error(loc, "buffers can be declared only as blocks", "buffer", "");
if (qualifier.storage != EvqVaryingIn && publicType.basicType == EbtDouble &&
extensionTurnedOn(E_GL_ARB_vertex_attrib_64bit) && language == EShLangVertex &&
version < 400) {
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 410, E_GL_ARB_gpu_shader_fp64, "vertex-shader `double` type");
}
if (qualifier.storage != EvqVaryingIn && qualifier.storage != EvqVaryingOut)
return;
if (publicType.shaderQualifiers.hasBlendEquation())
error(loc, "can only be applied to a standalone 'out'", "blend equation", "");
// now, knowing it is a shader in/out, do all the in/out semantic checks
if (publicType.basicType == EbtBool && !parsingBuiltins) {
error(loc, "cannot be bool", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (isTypeInt(publicType.basicType) || publicType.basicType == EbtDouble) {
profileRequires(loc, EEsProfile, 300, nullptr, "non-float shader input/output");
profileRequires(loc, ~EEsProfile, 130, nullptr, "non-float shader input/output");
}
if (!qualifier.flat && !qualifier.isExplicitInterpolation() && !qualifier.isPervertexNV() && !qualifier.isPervertexEXT()) {
if (isTypeInt(publicType.basicType) ||
publicType.basicType == EbtDouble ||
(publicType.userDef && ( publicType.userDef->containsBasicType(EbtInt)
|| publicType.userDef->containsBasicType(EbtUint)
|| publicType.userDef->contains16BitInt()
|| publicType.userDef->contains8BitInt()
|| publicType.userDef->contains64BitInt()
|| publicType.userDef->containsDouble()))) {
if (qualifier.storage == EvqVaryingIn && language == EShLangFragment)
error(loc, "must be qualified as flat", TType::getBasicString(publicType.basicType), GetStorageQualifierString(qualifier.storage));
else if (qualifier.storage == EvqVaryingOut && language == EShLangVertex && version == 300)
error(loc, "must be qualified as flat", TType::getBasicString(publicType.basicType), GetStorageQualifierString(qualifier.storage));
}
}
if (qualifier.isPatch() && qualifier.isInterpolation())
error(loc, "cannot use interpolation qualifiers with patch", "patch", "");
if (qualifier.isTaskPayload() && publicType.basicType == EbtBlock)
error(loc, "taskPayloadSharedEXT variables should not be declared as interface blocks", "taskPayloadSharedEXT", "");
if (qualifier.isTaskMemory() && publicType.basicType != EbtBlock)
error(loc, "taskNV variables can be declared only as blocks", "taskNV", "");
if (qualifier.storage == EvqVaryingIn) {
switch (language) {
case EShLangVertex:
if (publicType.basicType == EbtStruct) {
error(loc, "cannot be a structure", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (publicType.arraySizes) {
requireProfile(loc, ~EEsProfile, "vertex input arrays");
profileRequires(loc, ENoProfile, 150, nullptr, "vertex input arrays");
}
if (publicType.basicType == EbtDouble)
profileRequires(loc, ~EEsProfile, 410, E_GL_ARB_vertex_attrib_64bit, "vertex-shader `double` type input");
if (qualifier.isAuxiliary() || qualifier.isInterpolation() || qualifier.isMemory() || qualifier.invariant)
error(loc, "vertex input cannot be further qualified", "", "");
break;
case EShLangFragment:
if (publicType.userDef) {
profileRequires(loc, EEsProfile, 300, nullptr, "fragment-shader struct input");
profileRequires(loc, ~EEsProfile, 150, nullptr, "fragment-shader struct input");
if (publicType.userDef->containsStructure())
requireProfile(loc, ~EEsProfile, "fragment-shader struct input containing structure");
if (publicType.userDef->containsArray())
requireProfile(loc, ~EEsProfile, "fragment-shader struct input containing an array");
}
break;
case EShLangCompute:
if (! symbolTable.atBuiltInLevel())
error(loc, "global storage input qualifier cannot be used in a compute shader", "in", "");
break;
case EShLangTessControl:
if (qualifier.patch)
error(loc, "can only use on output in tessellation-control shader", "patch", "");
break;
default:
break;
}
} else {
// qualifier.storage == EvqVaryingOut
switch (language) {
case EShLangVertex:
if (publicType.userDef) {
profileRequires(loc, EEsProfile, 300, nullptr, "vertex-shader struct output");
profileRequires(loc, ~EEsProfile, 150, nullptr, "vertex-shader struct output");
if (publicType.userDef->containsStructure())
requireProfile(loc, ~EEsProfile, "vertex-shader struct output containing structure");
if (publicType.userDef->containsArray())
requireProfile(loc, ~EEsProfile, "vertex-shader struct output containing an array");
}
break;
case EShLangFragment:
profileRequires(loc, EEsProfile, 300, nullptr, "fragment shader output");
if (publicType.basicType == EbtStruct) {
error(loc, "cannot be a structure", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (publicType.matrixRows > 0) {
error(loc, "cannot be a matrix", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (qualifier.isAuxiliary())
error(loc, "can't use auxiliary qualifier on a fragment output", "centroid/sample/patch", "");
if (qualifier.isInterpolation())
error(loc, "can't use interpolation qualifier on a fragment output", "flat/smooth/noperspective", "");
if (publicType.basicType == EbtDouble || publicType.basicType == EbtInt64 || publicType.basicType == EbtUint64)
error(loc, "cannot contain a double, int64, or uint64", GetStorageQualifierString(qualifier.storage), "");
break;
case EShLangCompute:
error(loc, "global storage output qualifier cannot be used in a compute shader", "out", "");
break;
case EShLangTessEvaluation:
if (qualifier.patch)
error(loc, "can only use on input in tessellation-evaluation shader", "patch", "");
break;
default:
break;
}
}
}
//
// Merge characteristics of the 'src' qualifier into the 'dst'.
// If there is duplication, issue error messages, unless 'force'
// is specified, which means to just override default settings.
//
// Also, when force is false, it will be assumed that 'src' follows
// 'dst', for the purpose of error checking order for versions
// that require specific orderings of qualifiers.
//
void TParseContext::mergeQualifiers(const TSourceLoc& loc, TQualifier& dst, const TQualifier& src, bool force)
{
// Multiple auxiliary qualifiers (mostly done later by 'individual qualifiers')
if (src.isAuxiliary() && dst.isAuxiliary())
error(loc, "can only have one auxiliary qualifier (centroid, patch, and sample)", "", "");
// Multiple interpolation qualifiers (mostly done later by 'individual qualifiers')
if (src.isInterpolation() && dst.isInterpolation())
error(loc, "can only have one interpolation qualifier (flat, smooth, noperspective, __explicitInterpAMD)", "", "");
// Ordering
if (! force && ((!isEsProfile() && version < 420) ||
(isEsProfile() && version < 310))
&& ! extensionTurnedOn(E_GL_ARB_shading_language_420pack)) {
// non-function parameters
if (src.isNoContraction() && (dst.invariant || dst.isInterpolation() || dst.isAuxiliary() || dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "precise qualifier must appear first", "", "");
if (src.invariant && (dst.isInterpolation() || dst.isAuxiliary() || dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "invariant qualifier must appear before interpolation, storage, and precision qualifiers ", "", "");
else if (src.isInterpolation() && (dst.isAuxiliary() || dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "interpolation qualifiers must appear before storage and precision qualifiers", "", "");
else if (src.isAuxiliary() && (dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "Auxiliary qualifiers (centroid, patch, and sample) must appear before storage and precision qualifiers", "", "");
else if (src.storage != EvqTemporary && (dst.precision != EpqNone))
error(loc, "precision qualifier must appear as last qualifier", "", "");
// function parameters
if (src.isNoContraction() && (dst.storage == EvqConst || dst.storage == EvqIn || dst.storage == EvqOut))
error(loc, "precise qualifier must appear first", "", "");
if (src.storage == EvqConst && (dst.storage == EvqIn || dst.storage == EvqOut))
error(loc, "in/out must appear before const", "", "");
}
// Storage qualification
if (dst.storage == EvqTemporary || dst.storage == EvqGlobal)
dst.storage = src.storage;
else if ((dst.storage == EvqIn && src.storage == EvqOut) ||
(dst.storage == EvqOut && src.storage == EvqIn))
dst.storage = EvqInOut;
else if ((dst.storage == EvqIn && src.storage == EvqConst) ||
(dst.storage == EvqConst && src.storage == EvqIn))
dst.storage = EvqConstReadOnly;
else if (src.storage != EvqTemporary &&
src.storage != EvqGlobal)
error(loc, "too many storage qualifiers", GetStorageQualifierString(src.storage), "");
// Precision qualifiers
if (! force && src.precision != EpqNone && dst.precision != EpqNone)
error(loc, "only one precision qualifier allowed", GetPrecisionQualifierString(src.precision), "");
if (dst.precision == EpqNone || (force && src.precision != EpqNone))
dst.precision = src.precision;
if (!force && ((src.coherent && (dst.devicecoherent || dst.queuefamilycoherent || dst.workgroupcoherent || dst.subgroupcoherent || dst.shadercallcoherent)) ||
(src.devicecoherent && (dst.coherent || dst.queuefamilycoherent || dst.workgroupcoherent || dst.subgroupcoherent || dst.shadercallcoherent)) ||
(src.queuefamilycoherent && (dst.coherent || dst.devicecoherent || dst.workgroupcoherent || dst.subgroupcoherent || dst.shadercallcoherent)) ||
(src.workgroupcoherent && (dst.coherent || dst.devicecoherent || dst.queuefamilycoherent || dst.subgroupcoherent || dst.shadercallcoherent)) ||
(src.subgroupcoherent && (dst.coherent || dst.devicecoherent || dst.queuefamilycoherent || dst.workgroupcoherent || dst.shadercallcoherent)) ||
(src.shadercallcoherent && (dst.coherent || dst.devicecoherent || dst.queuefamilycoherent || dst.workgroupcoherent || dst.subgroupcoherent)))) {
error(loc, "only one coherent/devicecoherent/queuefamilycoherent/workgroupcoherent/subgroupcoherent/shadercallcoherent qualifier allowed",
GetPrecisionQualifierString(src.precision), "");
}
// Layout qualifiers
mergeObjectLayoutQualifiers(dst, src, false);
// individual qualifiers
bool repeated = false;
#define MERGE_SINGLETON(field) repeated |= dst.field && src.field; dst.field |= src.field;
MERGE_SINGLETON(invariant);
MERGE_SINGLETON(centroid);
MERGE_SINGLETON(smooth);
MERGE_SINGLETON(flat);
MERGE_SINGLETON(specConstant);
MERGE_SINGLETON(noContraction);
MERGE_SINGLETON(nopersp);
MERGE_SINGLETON(explicitInterp);
MERGE_SINGLETON(perPrimitiveNV);
MERGE_SINGLETON(perViewNV);
MERGE_SINGLETON(perTaskNV);
MERGE_SINGLETON(patch);
MERGE_SINGLETON(sample);
MERGE_SINGLETON(coherent);
MERGE_SINGLETON(devicecoherent);
MERGE_SINGLETON(queuefamilycoherent);
MERGE_SINGLETON(workgroupcoherent);
MERGE_SINGLETON(subgroupcoherent);
MERGE_SINGLETON(shadercallcoherent);
MERGE_SINGLETON(nonprivate);
MERGE_SINGLETON(volatil);
MERGE_SINGLETON(restrict);
MERGE_SINGLETON(readonly);
MERGE_SINGLETON(writeonly);
MERGE_SINGLETON(nonUniform);
// SPIR-V storage class qualifier (GL_EXT_spirv_intrinsics)
dst.spirvStorageClass = src.spirvStorageClass;
// SPIR-V decorate qualifiers (GL_EXT_spirv_intrinsics)
if (src.hasSprivDecorate()) {
if (dst.hasSprivDecorate()) {
const TSpirvDecorate& srcSpirvDecorate = src.getSpirvDecorate();
TSpirvDecorate& dstSpirvDecorate = dst.getSpirvDecorate();
for (auto& decorate : srcSpirvDecorate.decorates) {
if (dstSpirvDecorate.decorates.find(decorate.first) != dstSpirvDecorate.decorates.end())
error(loc, "too many SPIR-V decorate qualifiers", "spirv_decorate", "(decoration=%u)", decorate.first);
else
dstSpirvDecorate.decorates.insert(decorate);
}
for (auto& decorateId : srcSpirvDecorate.decorateIds) {
if (dstSpirvDecorate.decorateIds.find(decorateId.first) != dstSpirvDecorate.decorateIds.end())
error(loc, "too many SPIR-V decorate qualifiers", "spirv_decorate_id", "(decoration=%u)", decorateId.first);
else
dstSpirvDecorate.decorateIds.insert(decorateId);
}
for (auto& decorateString : srcSpirvDecorate.decorateStrings) {
if (dstSpirvDecorate.decorates.find(decorateString.first) != dstSpirvDecorate.decorates.end())
error(loc, "too many SPIR-V decorate qualifiers", "spirv_decorate_string", "(decoration=%u)", decorateString.first);
else
dstSpirvDecorate.decorateStrings.insert(decorateString);
}
} else {
dst.spirvDecorate = src.spirvDecorate;
}
}
if (repeated)
error(loc, "replicated qualifiers", "", "");
}
void TParseContext::setDefaultPrecision(const TSourceLoc& loc, TPublicType& publicType, TPrecisionQualifier qualifier)
{
TBasicType basicType = publicType.basicType;
if (basicType == EbtSampler) {
defaultSamplerPrecision[computeSamplerTypeIndex(publicType.sampler)] = qualifier;
return; // all is well
}
if (basicType == EbtInt || basicType == EbtFloat) {
if (publicType.isScalar()) {
defaultPrecision[basicType] = qualifier;
if (basicType == EbtInt) {
defaultPrecision[EbtUint] = qualifier;
precisionManager.explicitIntDefaultSeen();
} else
precisionManager.explicitFloatDefaultSeen();
return; // all is well
}
}
if (basicType == EbtAtomicUint) {
if (qualifier != EpqHigh)
error(loc, "can only apply highp to atomic_uint", "precision", "");
return;
}
error(loc, "cannot apply precision statement to this type; use 'float', 'int' or a sampler type", TType::getBasicString(basicType), "");
}
// used to flatten the sampler type space into a single dimension
// correlates with the declaration of defaultSamplerPrecision[]
int TParseContext::computeSamplerTypeIndex(TSampler& sampler)
{
int arrayIndex = sampler.arrayed ? 1 : 0;
int shadowIndex = sampler.shadow ? 1 : 0;
int externalIndex = sampler.isExternal() ? 1 : 0;
int imageIndex = sampler.isImageClass() ? 1 : 0;
int msIndex = sampler.isMultiSample() ? 1 : 0;
int flattened = EsdNumDims * (EbtNumTypes * (2 * (2 * (2 * (2 * arrayIndex + msIndex) + imageIndex) + shadowIndex) +
externalIndex) + sampler.type) + sampler.dim;
assert(flattened < maxSamplerIndex);
return flattened;
}
TPrecisionQualifier TParseContext::getDefaultPrecision(TPublicType& publicType)
{
if (publicType.basicType == EbtSampler)
return defaultSamplerPrecision[computeSamplerTypeIndex(publicType.sampler)];
else
return defaultPrecision[publicType.basicType];
}
void TParseContext::precisionQualifierCheck(const TSourceLoc& loc, TBasicType baseType, TQualifier& qualifier, bool isCoopMat)
{
// Built-in symbols are allowed some ambiguous precisions, to be pinned down
// later by context.
if (! obeyPrecisionQualifiers() || parsingBuiltins)
return;
if (baseType == EbtAtomicUint && qualifier.precision != EpqNone && qualifier.precision != EpqHigh)
error(loc, "atomic counters can only be highp", "atomic_uint", "");
if (isCoopMat)
return;
if (baseType == EbtFloat || baseType == EbtUint || baseType == EbtInt || baseType == EbtSampler || baseType == EbtAtomicUint) {
if (qualifier.precision == EpqNone) {
if (relaxedErrors())
warn(loc, "type requires declaration of default precision qualifier", TType::getBasicString(baseType), "substituting 'mediump'");
else
error(loc, "type requires declaration of default precision qualifier", TType::getBasicString(baseType), "");
qualifier.precision = EpqMedium;
defaultPrecision[baseType] = EpqMedium;
}
} else if (qualifier.precision != EpqNone)
error(loc, "type cannot have precision qualifier", TType::getBasicString(baseType), "");
}
void TParseContext::parameterTypeCheck(const TSourceLoc& loc, TStorageQualifier qualifier, const TType& type)
{
if ((qualifier == EvqOut || qualifier == EvqInOut) && type.isOpaque() && !intermediate.getBindlessMode())
error(loc, "samplers and atomic_uints cannot be output parameters", type.getBasicTypeString().c_str(), "");
if (!parsingBuiltins && type.contains16BitFloat())
requireFloat16Arithmetic(loc, type.getBasicTypeString().c_str(), "float16 types can only be in uniform block or buffer storage");
if (!parsingBuiltins && type.contains16BitInt())
requireInt16Arithmetic(loc, type.getBasicTypeString().c_str(), "(u)int16 types can only be in uniform block or buffer storage");
if (!parsingBuiltins && type.contains8BitInt())
requireInt8Arithmetic(loc, type.getBasicTypeString().c_str(), "(u)int8 types can only be in uniform block or buffer storage");
}
bool TParseContext::containsFieldWithBasicType(const TType& type, TBasicType basicType)
{
if (type.getBasicType() == basicType)
return true;
if (type.getBasicType() == EbtStruct) {
const TTypeList& structure = *type.getStruct();
for (unsigned int i = 0; i < structure.size(); ++i) {
if (containsFieldWithBasicType(*structure[i].type, basicType))
return true;
}
}
return false;
}
//
// Do size checking for an array type's size.
//
void TParseContext::arraySizeCheck(const TSourceLoc& loc, TIntermTyped* expr, TArraySize& sizePair,
const char* sizeType, const bool allowZero)
{
bool isConst = false;
sizePair.node = nullptr;
int size = 1;
TIntermConstantUnion* constant = expr->getAsConstantUnion();
if (constant) {
// handle true (non-specialization) constant
size = constant->getConstArray()[0].getIConst();
isConst = true;
} else {
// see if it's a specialization constant instead
if (expr->getQualifier().isSpecConstant()) {
isConst = true;
sizePair.node = expr;
TIntermSymbol* symbol = expr->getAsSymbolNode();
if (symbol && symbol->getConstArray().size() > 0)
size = symbol->getConstArray()[0].getIConst();
} else if (expr->getAsUnaryNode() && expr->getAsUnaryNode()->getOp() == glslang::EOpArrayLength &&
expr->getAsUnaryNode()->getOperand()->getType().isCoopMatNV()) {
isConst = true;
size = 1;
sizePair.node = expr->getAsUnaryNode();
}
}
sizePair.size = size;
if (! isConst || (expr->getBasicType() != EbtInt && expr->getBasicType() != EbtUint)) {
error(loc, sizeType, "", "must be a constant integer expression");
return;
}
if (allowZero) {
if (size < 0) {
error(loc, sizeType, "", "must be a non-negative integer");
return;
}
} else {
if (size <= 0) {
error(loc, sizeType, "", "must be a positive integer");
return;
}
}
}
//
// See if this qualifier can be an array.
//
// Returns true if there is an error.
//
bool TParseContext::arrayQualifierError(const TSourceLoc& loc, const TQualifier& qualifier)
{
if (qualifier.storage == EvqConst) {
profileRequires(loc, ENoProfile, 120, E_GL_3DL_array_objects, "const array");
profileRequires(loc, EEsProfile, 300, nullptr, "const array");
}
if (qualifier.storage == EvqVaryingIn && language == EShLangVertex) {
requireProfile(loc, ~EEsProfile, "vertex input arrays");
profileRequires(loc, ENoProfile, 150, nullptr, "vertex input arrays");
}
return false;
}
//
// See if this qualifier and type combination can be an array.
// Assumes arrayQualifierError() was also called to catch the type-invariant tests.
//
// Returns true if there is an error.
//
bool TParseContext::arrayError(const TSourceLoc& loc, const TType& type)
{
if (type.getQualifier().storage == EvqVaryingOut && language == EShLangVertex) {
if (type.isArrayOfArrays())
requireProfile(loc, ~EEsProfile, "vertex-shader array-of-array output");
else if (type.isStruct())
requireProfile(loc, ~EEsProfile, "vertex-shader array-of-struct output");
}
if (type.getQualifier().storage == EvqVaryingIn && language == EShLangFragment) {
if (type.isArrayOfArrays())
requireProfile(loc, ~EEsProfile, "fragment-shader array-of-array input");
else if (type.isStruct())
requireProfile(loc, ~EEsProfile, "fragment-shader array-of-struct input");
}
if (type.getQualifier().storage == EvqVaryingOut && language == EShLangFragment) {
if (type.isArrayOfArrays())
requireProfile(loc, ~EEsProfile, "fragment-shader array-of-array output");
}
return false;
}
//
// Require array to be completely sized
//
void TParseContext::arraySizeRequiredCheck(const TSourceLoc& loc, const TArraySizes& arraySizes)
{
if (!parsingBuiltins && arraySizes.hasUnsized())
error(loc, "array size required", "", "");
}
void TParseContext::structArrayCheck(const TSourceLoc& /*loc*/, const TType& type)
{
const TTypeList& structure = *type.getStruct();
for (int m = 0; m < (int)structure.size(); ++m) {
const TType& member = *structure[m].type;
if (member.isArray())
arraySizeRequiredCheck(structure[m].loc, *member.getArraySizes());
}
}
void TParseContext::arraySizesCheck(const TSourceLoc& loc, const TQualifier& qualifier, TArraySizes* arraySizes,
const TIntermTyped* initializer, bool lastMember)
{
assert(arraySizes);
// always allow special built-in ins/outs sized to topologies
if (parsingBuiltins)
return;
// initializer must be a sized array, in which case
// allow the initializer to set any unknown array sizes
if (initializer != nullptr) {
if (initializer->getType().isUnsizedArray())
error(loc, "array initializer must be sized", "[]", "");
return;
}
// No environment allows any non-outer-dimension to be implicitly sized
if (arraySizes->isInnerUnsized()) {
error(loc, "only outermost dimension of an array of arrays can be implicitly sized", "[]", "");
arraySizes->clearInnerUnsized();
}
if (arraySizes->isInnerSpecialization() &&
(qualifier.storage != EvqTemporary && qualifier.storage != EvqGlobal && qualifier.storage != EvqShared && qualifier.storage != EvqConst))
error(loc, "only outermost dimension of an array of arrays can be a specialization constant", "[]", "");
// desktop always allows outer-dimension-unsized variable arrays,
if (!isEsProfile())
return;
// for ES, if size isn't coming from an initializer, it has to be explicitly declared now,
// with very few exceptions
// implicitly-sized io exceptions:
switch (language) {
case EShLangGeometry:
if (qualifier.storage == EvqVaryingIn)
if ((isEsProfile() && version >= 320) ||
extensionsTurnedOn(Num_AEP_geometry_shader, AEP_geometry_shader))
return;
break;
case EShLangTessControl:
if ( qualifier.storage == EvqVaryingIn ||
(qualifier.storage == EvqVaryingOut && ! qualifier.isPatch()))
if ((isEsProfile() && version >= 320) ||
extensionsTurnedOn(Num_AEP_tessellation_shader, AEP_tessellation_shader))
return;
break;
case EShLangTessEvaluation:
if ((qualifier.storage == EvqVaryingIn && ! qualifier.isPatch()) ||
qualifier.storage == EvqVaryingOut)
if ((isEsProfile() && version >= 320) ||
extensionsTurnedOn(Num_AEP_tessellation_shader, AEP_tessellation_shader))
return;
break;
case EShLangMesh:
if (qualifier.storage == EvqVaryingOut)
if ((isEsProfile() && version >= 320) ||
extensionsTurnedOn(Num_AEP_mesh_shader, AEP_mesh_shader))
return;
break;
default:
break;
}
// last member of ssbo block exception:
if (qualifier.storage == EvqBuffer && lastMember)
return;
arraySizeRequiredCheck(loc, *arraySizes);
}
void TParseContext::arrayOfArrayVersionCheck(const TSourceLoc& loc, const TArraySizes* sizes)
{
if (sizes == nullptr || sizes->getNumDims() == 1)
return;
const char* feature = "arrays of arrays";
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature);
profileRequires(loc, EEsProfile, 310, nullptr, feature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, nullptr, feature);
}
//
// Do all the semantic checking for declaring or redeclaring an array, with and
// without a size, and make the right changes to the symbol table.
//
void TParseContext::declareArray(const TSourceLoc& loc, const TString& identifier, const TType& type, TSymbol*& symbol)
{
if (symbol == nullptr) {
bool currentScope;
symbol = symbolTable.find(identifier, nullptr, &currentScope);
if (symbol && builtInName(identifier) && ! symbolTable.atBuiltInLevel()) {
// bad shader (errors already reported) trying to redeclare a built-in name as an array
symbol = nullptr;
return;
}
if (symbol == nullptr || ! currentScope) {
//
// Successfully process a new definition.
// (Redeclarations have to take place at the same scope; otherwise they are hiding declarations)
//
symbol = new TVariable(&identifier, type);
symbolTable.insert(*symbol);
if (symbolTable.atGlobalLevel())
trackLinkage(*symbol);
if (! symbolTable.atBuiltInLevel()) {
if (isIoResizeArray(type)) {
ioArraySymbolResizeList.push_back(symbol);
checkIoArraysConsistency(loc, true);
} else
fixIoArraySize(loc, symbol->getWritableType());
}
return;
}
if (symbol->getAsAnonMember()) {
error(loc, "cannot redeclare a user-block member array", identifier.c_str(), "");
symbol = nullptr;
return;
}
}
//
// Process a redeclaration.
//
if (symbol == nullptr) {
error(loc, "array variable name expected", identifier.c_str(), "");
return;
}
// redeclareBuiltinVariable() should have already done the copyUp()
TType& existingType = symbol->getWritableType();
if (! existingType.isArray()) {
error(loc, "redeclaring non-array as array", identifier.c_str(), "");
return;
}
if (! existingType.sameElementType(type)) {
error(loc, "redeclaration of array with a different element type", identifier.c_str(), "");
return;
}
if (! existingType.sameInnerArrayness(type)) {
error(loc, "redeclaration of array with a different array dimensions or sizes", identifier.c_str(), "");
return;
}
if (existingType.isSizedArray()) {
// be more leniant for input arrays to geometry shaders and tessellation control outputs, where the redeclaration is the same size
if (! (isIoResizeArray(type) && existingType.getOuterArraySize() == type.getOuterArraySize()))
error(loc, "redeclaration of array with size", identifier.c_str(), "");
return;
}
arrayLimitCheck(loc, identifier, type.getOuterArraySize());
existingType.updateArraySizes(type);
if (isIoResizeArray(type))
checkIoArraysConsistency(loc);
}
// Policy and error check for needing a runtime sized array.
void TParseContext::checkRuntimeSizable(const TSourceLoc& loc, const TIntermTyped& base)
{
// runtime length implies runtime sizeable, so no problem
if (isRuntimeLength(base))
return;
if (base.getType().getQualifier().builtIn == EbvSampleMask)
return;
// Check for last member of a bufferreference type, which is runtime sizeable
// but doesn't support runtime length
if (base.getType().getQualifier().storage == EvqBuffer) {
const TIntermBinary* binary = base.getAsBinaryNode();
if (binary != nullptr &&
binary->getOp() == EOpIndexDirectStruct &&
binary->getLeft()->isReference()) {
const int index = binary->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst();
const int memberCount = (int)binary->getLeft()->getType().getReferentType()->getStruct()->size();
if (index == memberCount - 1)
return;
}
}
// check for additional things allowed by GL_EXT_nonuniform_qualifier
if (base.getBasicType() == EbtSampler || base.getBasicType() == EbtAccStruct || base.getBasicType() == EbtRayQuery ||
base.getBasicType() == EbtHitObjectNV || (base.getBasicType() == EbtBlock && base.getType().getQualifier().isUniformOrBuffer()))
requireExtensions(loc, 1, &E_GL_EXT_nonuniform_qualifier, "variable index");
else
error(loc, "", "[", "array must be redeclared with a size before being indexed with a variable");
}
// Policy decision for whether a run-time .length() is allowed.
bool TParseContext::isRuntimeLength(const TIntermTyped& base) const
{
if (base.getType().getQualifier().storage == EvqBuffer) {
// in a buffer block
const TIntermBinary* binary = base.getAsBinaryNode();
if (binary != nullptr && binary->getOp() == EOpIndexDirectStruct) {
// is it the last member?
const int index = binary->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst();
if (binary->getLeft()->isReference())
return false;
const int memberCount = (int)binary->getLeft()->getType().getStruct()->size();
if (index == memberCount - 1)
return true;
}
}
return false;
}
// Check if mesh perviewNV attributes have a view dimension
// and resize it to gl_MaxMeshViewCountNV when implicitly sized.
void TParseContext::checkAndResizeMeshViewDim(const TSourceLoc& loc, TType& type, bool isBlockMember)
{
// see if member is a per-view attribute
if (!type.getQualifier().isPerView())
return;
if ((isBlockMember && type.isArray()) || (!isBlockMember && type.isArrayOfArrays())) {
// since we don't have the maxMeshViewCountNV set during parsing builtins, we hardcode the value.
int maxViewCount = parsingBuiltins ? 4 : resources.maxMeshViewCountNV;
// For block members, outermost array dimension is the view dimension.
// For non-block members, outermost array dimension is the vertex/primitive dimension
// and 2nd outermost is the view dimension.
int viewDim = isBlockMember ? 0 : 1;
int viewDimSize = type.getArraySizes()->getDimSize(viewDim);
if (viewDimSize != UnsizedArraySize && viewDimSize != maxViewCount)
error(loc, "mesh view output array size must be gl_MaxMeshViewCountNV or implicitly sized", "[]", "");
else if (viewDimSize == UnsizedArraySize)
type.getArraySizes()->setDimSize(viewDim, maxViewCount);
}
else {
error(loc, "requires a view array dimension", "perviewNV", "");
}
}
// Returns true if the first argument to the #line directive is the line number for the next line.
//
// Desktop, pre-version 3.30: "After processing this directive
// (including its new-line), the implementation will behave as if it is compiling at line number line+1 and
// source string number source-string-number."
//
// Desktop, version 3.30 and later, and ES: "After processing this directive
// (including its new-line), the implementation will behave as if it is compiling at line number line and
// source string number source-string-number.
bool TParseContext::lineDirectiveShouldSetNextLine() const
{
return isEsProfile() || version >= 330;
}
//
// Enforce non-initializer type/qualifier rules.
//
void TParseContext::nonInitConstCheck(const TSourceLoc& loc, TString& identifier, TType& type)
{
//
// Make the qualifier make sense, given that there is not an initializer.
//
if (type.getQualifier().storage == EvqConst ||
type.getQualifier().storage == EvqConstReadOnly) {
type.getQualifier().makeTemporary();
error(loc, "variables with qualifier 'const' must be initialized", identifier.c_str(), "");
}
}
//
// See if the identifier is a built-in symbol that can be redeclared, and if so,
// copy the symbol table's read-only built-in variable to the current
// global level, where it can be modified based on the passed in type.
//
// Returns nullptr if no redeclaration took place; meaning a normal declaration still
// needs to occur for it, not necessarily an error.
//
// Returns a redeclared and type-modified variable if a redeclarated occurred.
//
TSymbol* TParseContext::redeclareBuiltinVariable(const TSourceLoc& loc, const TString& identifier,
const TQualifier& qualifier, const TShaderQualifiers& publicType)
{
if (! builtInName(identifier) || symbolTable.atBuiltInLevel() || ! symbolTable.atGlobalLevel())
return nullptr;
bool nonEsRedecls = (!isEsProfile() && (version >= 130 || identifier == "gl_TexCoord"));
bool esRedecls = (isEsProfile() &&
(version >= 320 || extensionsTurnedOn(Num_AEP_shader_io_blocks, AEP_shader_io_blocks)));
if (! esRedecls && ! nonEsRedecls)
return nullptr;
// Special case when using GL_ARB_separate_shader_objects
bool ssoPre150 = false; // means the only reason this variable is redeclared is due to this combination
if (!isEsProfile() && version <= 140 && extensionTurnedOn(E_GL_ARB_separate_shader_objects)) {
if (identifier == "gl_Position" ||
identifier == "gl_PointSize" ||
identifier == "gl_ClipVertex" ||
identifier == "gl_FogFragCoord")
ssoPre150 = true;
}
// Potentially redeclaring a built-in variable...
if (ssoPre150 ||
(identifier == "gl_FragDepth" && ((nonEsRedecls && version >= 420) || esRedecls)) ||
(identifier == "gl_FragCoord" && ((nonEsRedecls && version >= 140) || esRedecls)) ||
identifier == "gl_ClipDistance" ||
identifier == "gl_CullDistance" ||
identifier == "gl_ShadingRateEXT" ||
identifier == "gl_PrimitiveShadingRateEXT" ||
identifier == "gl_FrontColor" ||
identifier == "gl_BackColor" ||
identifier == "gl_FrontSecondaryColor" ||
identifier == "gl_BackSecondaryColor" ||
identifier == "gl_SecondaryColor" ||
(identifier == "gl_Color" && language == EShLangFragment) ||
(identifier == "gl_FragStencilRefARB" && (nonEsRedecls && version >= 140)
&& language == EShLangFragment) ||
identifier == "gl_SampleMask" ||
identifier == "gl_Layer" ||
identifier == "gl_PrimitiveIndicesNV" ||
identifier == "gl_PrimitivePointIndicesEXT" ||
identifier == "gl_PrimitiveLineIndicesEXT" ||
identifier == "gl_PrimitiveTriangleIndicesEXT" ||
identifier == "gl_TexCoord") {
// Find the existing symbol, if any.
bool builtIn;
TSymbol* symbol = symbolTable.find(identifier, &builtIn);
// If the symbol was not found, this must be a version/profile/stage
// that doesn't have it.
if (! symbol)
return nullptr;
// If it wasn't at a built-in level, then it's already been redeclared;
// that is, this is a redeclaration of a redeclaration; reuse that initial
// redeclaration. Otherwise, make the new one.
if (builtIn) {
makeEditable(symbol);
symbolTable.amendSymbolIdLevel(*symbol);
}
// Now, modify the type of the copy, as per the type of the current redeclaration.
TQualifier& symbolQualifier = symbol->getWritableType().getQualifier();
if (ssoPre150) {
if (intermediate.inIoAccessed(identifier))
error(loc, "cannot redeclare after use", identifier.c_str(), "");
if (qualifier.hasLayout())
error(loc, "cannot apply layout qualifier to", "redeclaration", symbol->getName().c_str());
if (qualifier.isMemory() || qualifier.isAuxiliary() || (language == EShLangVertex && qualifier.storage != EvqVaryingOut) ||
(language == EShLangFragment && qualifier.storage != EvqVaryingIn))
error(loc, "cannot change storage, memory, or auxiliary qualification of", "redeclaration", symbol->getName().c_str());
if (! qualifier.smooth)
error(loc, "cannot change interpolation qualification of", "redeclaration", symbol->getName().c_str());
} else if (identifier == "gl_FrontColor" ||
identifier == "gl_BackColor" ||
identifier == "gl_FrontSecondaryColor" ||
identifier == "gl_BackSecondaryColor" ||
identifier == "gl_SecondaryColor" ||
identifier == "gl_Color") {
symbolQualifier.flat = qualifier.flat;
symbolQualifier.smooth = qualifier.smooth;
symbolQualifier.nopersp = qualifier.nopersp;
if (qualifier.hasLayout())
error(loc, "cannot apply layout qualifier to", "redeclaration", symbol->getName().c_str());
if (qualifier.isMemory() || qualifier.isAuxiliary() || symbol->getType().getQualifier().storage != qualifier.storage)
error(loc, "cannot change storage, memory, or auxiliary qualification of", "redeclaration", symbol->getName().c_str());
} else if (identifier == "gl_TexCoord" ||
identifier == "gl_ClipDistance" ||
identifier == "gl_CullDistance") {
if (qualifier.hasLayout() || qualifier.isMemory() || qualifier.isAuxiliary() ||
qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
symbolQualifier.storage != qualifier.storage)
error(loc, "cannot change qualification of", "redeclaration", symbol->getName().c_str());
} else if (identifier == "gl_FragCoord") {
if (!intermediate.getTexCoordRedeclared() && intermediate.inIoAccessed("gl_FragCoord"))
error(loc, "cannot redeclare after use", "gl_FragCoord", "");
if (qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
qualifier.isMemory() || qualifier.isAuxiliary())
error(loc, "can only change layout qualification of", "redeclaration", symbol->getName().c_str());
if (qualifier.storage != EvqVaryingIn)
error(loc, "cannot change input storage qualification of", "redeclaration", symbol->getName().c_str());
if (! builtIn && (publicType.pixelCenterInteger != intermediate.getPixelCenterInteger() ||
publicType.originUpperLeft != intermediate.getOriginUpperLeft()))
error(loc, "cannot redeclare with different qualification:", "redeclaration", symbol->getName().c_str());
intermediate.setTexCoordRedeclared();
if (publicType.pixelCenterInteger)
intermediate.setPixelCenterInteger();
if (publicType.originUpperLeft)
intermediate.setOriginUpperLeft();
} else if (identifier == "gl_FragDepth") {
if (qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
qualifier.isMemory() || qualifier.isAuxiliary())
error(loc, "can only change layout qualification of", "redeclaration", symbol->getName().c_str());
if (qualifier.storage != EvqVaryingOut)
error(loc, "cannot change output storage qualification of", "redeclaration", symbol->getName().c_str());
if (publicType.layoutDepth != EldNone) {
if (intermediate.inIoAccessed("gl_FragDepth"))
error(loc, "cannot redeclare after use", "gl_FragDepth", "");
if (! intermediate.setDepth(publicType.layoutDepth))
error(loc, "all redeclarations must use the same depth layout on", "redeclaration", symbol->getName().c_str());
}
} else if (identifier == "gl_FragStencilRefARB") {
if (qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
qualifier.isMemory() || qualifier.isAuxiliary())
error(loc, "can only change layout qualification of", "redeclaration", symbol->getName().c_str());
if (qualifier.storage != EvqVaryingOut)
error(loc, "cannot change output storage qualification of", "redeclaration", symbol->getName().c_str());
if (publicType.layoutStencil != ElsNone) {
if (intermediate.inIoAccessed("gl_FragStencilRefARB"))
error(loc, "cannot redeclare after use", "gl_FragStencilRefARB", "");
if (!intermediate.setStencil(publicType.layoutStencil))
error(loc, "all redeclarations must use the same stencil layout on", "redeclaration",
symbol->getName().c_str());
}
}
else if (
identifier == "gl_PrimitiveIndicesNV") {
if (qualifier.hasLayout())
error(loc, "cannot apply layout qualifier to", "redeclaration", symbol->getName().c_str());
if (qualifier.storage != EvqVaryingOut)
error(loc, "cannot change output storage qualification of", "redeclaration", symbol->getName().c_str());
}
else if (identifier == "gl_SampleMask") {
if (!publicType.layoutOverrideCoverage) {
error(loc, "redeclaration only allowed for override_coverage layout", "redeclaration", symbol->getName().c_str());
}
intermediate.setLayoutOverrideCoverage();
}
else if (identifier == "gl_Layer") {
if (!qualifier.layoutViewportRelative && qualifier.layoutSecondaryViewportRelativeOffset == -2048)
error(loc, "redeclaration only allowed for viewport_relative or secondary_view_offset layout", "redeclaration", symbol->getName().c_str());
symbolQualifier.layoutViewportRelative = qualifier.layoutViewportRelative;
symbolQualifier.layoutSecondaryViewportRelativeOffset = qualifier.layoutSecondaryViewportRelativeOffset;
}
// TODO: semantics quality: separate smooth from nothing declared, then use IsInterpolation for several tests above
return symbol;
}
return nullptr;
}
//
// Either redeclare the requested block, or give an error message why it can't be done.
//
// TODO: functionality: explicitly sizing members of redeclared blocks is not giving them an explicit size
void TParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& newTypeList, const TString& blockName,
const TString* instanceName, TArraySizes* arraySizes)
{
const char* feature = "built-in block redeclaration";
profileRequires(loc, EEsProfile, 320, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, feature);
profileRequires(loc, ~EEsProfile, 410, E_GL_ARB_separate_shader_objects, feature);
if (blockName != "gl_PerVertex" && blockName != "gl_PerFragment" &&
blockName != "gl_MeshPerVertexNV" && blockName != "gl_MeshPerPrimitiveNV" &&
blockName != "gl_MeshPerVertexEXT" && blockName != "gl_MeshPerPrimitiveEXT") {
error(loc, "cannot redeclare block: ", "block declaration", blockName.c_str());
return;
}
// Redeclaring a built-in block...
if (instanceName && ! builtInName(*instanceName)) {
error(loc, "cannot redeclare a built-in block with a user name", instanceName->c_str(), "");
return;
}
// Blocks with instance names are easy to find, lookup the instance name,
// Anonymous blocks need to be found via a member.
bool builtIn;
TSymbol* block;
if (instanceName)
block = symbolTable.find(*instanceName, &builtIn);
else
block = symbolTable.find(newTypeList.front().type->getFieldName(), &builtIn);
// If the block was not found, this must be a version/profile/stage
// that doesn't have it, or the instance name is wrong.
const char* errorName = instanceName ? instanceName->c_str() : newTypeList.front().type->getFieldName().c_str();
if (! block) {
error(loc, "no declaration found for redeclaration", errorName, "");
return;
}
// Built-in blocks cannot be redeclared more than once, which if happened,
// we'd be finding the already redeclared one here, rather than the built in.
if (! builtIn) {
error(loc, "can only redeclare a built-in block once, and before any use", blockName.c_str(), "");
return;
}
// Copy the block to make a writable version, to insert into the block table after editing.
block = symbolTable.copyUpDeferredInsert(block);
if (block->getType().getBasicType() != EbtBlock) {
error(loc, "cannot redeclare a non block as a block", errorName, "");
return;
}
// Fix XFB stuff up, it applies to the order of the redeclaration, not
// the order of the original members.
if (currentBlockQualifier.storage == EvqVaryingOut && globalOutputDefaults.hasXfbBuffer()) {
if (!currentBlockQualifier.hasXfbBuffer())
currentBlockQualifier.layoutXfbBuffer = globalOutputDefaults.layoutXfbBuffer;
if (!currentBlockQualifier.hasStream())
currentBlockQualifier.layoutStream = globalOutputDefaults.layoutStream;
fixXfbOffsets(currentBlockQualifier, newTypeList);
}
// Edit and error check the container against the redeclaration
// - remove unused members
// - ensure remaining qualifiers/types match
TType& type = block->getWritableType();
// if gl_PerVertex is redeclared for the purpose of passing through "gl_Position"
// for passthrough purpose, the redeclared block should have the same qualifers as
// the current one
if (currentBlockQualifier.layoutPassthrough) {
type.getQualifier().layoutPassthrough = currentBlockQualifier.layoutPassthrough;
type.getQualifier().storage = currentBlockQualifier.storage;
type.getQualifier().layoutStream = currentBlockQualifier.layoutStream;
type.getQualifier().layoutXfbBuffer = currentBlockQualifier.layoutXfbBuffer;
}
TTypeList::iterator member = type.getWritableStruct()->begin();
size_t numOriginalMembersFound = 0;
while (member != type.getStruct()->end()) {
// look for match
bool found = false;
TTypeList::const_iterator newMember;
TSourceLoc memberLoc;
memberLoc.init();
for (newMember = newTypeList.begin(); newMember != newTypeList.end(); ++newMember) {
if (member->type->getFieldName() == newMember->type->getFieldName()) {
found = true;
memberLoc = newMember->loc;
break;
}
}
if (found) {
++numOriginalMembersFound;
// - ensure match between redeclared members' types
// - check for things that can't be changed
// - update things that can be changed
TType& oldType = *member->type;
const TType& newType = *newMember->type;
if (! newType.sameElementType(oldType))
error(memberLoc, "cannot redeclare block member with a different type", member->type->getFieldName().c_str(), "");
if (oldType.isArray() != newType.isArray())
error(memberLoc, "cannot change arrayness of redeclared block member", member->type->getFieldName().c_str(), "");
else if (! oldType.getQualifier().isPerView() && ! oldType.sameArrayness(newType) && oldType.isSizedArray())
error(memberLoc, "cannot change array size of redeclared block member", member->type->getFieldName().c_str(), "");
else if (! oldType.getQualifier().isPerView() && newType.isArray())
arrayLimitCheck(loc, member->type->getFieldName(), newType.getOuterArraySize());
if (oldType.getQualifier().isPerView() && ! newType.getQualifier().isPerView())
error(memberLoc, "missing perviewNV qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
else if (! oldType.getQualifier().isPerView() && newType.getQualifier().isPerView())
error(memberLoc, "cannot add perviewNV qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
else if (newType.getQualifier().isPerView()) {
if (oldType.getArraySizes()->getNumDims() != newType.getArraySizes()->getNumDims())
error(memberLoc, "cannot change arrayness of redeclared block member", member->type->getFieldName().c_str(), "");
else if (! newType.isUnsizedArray() && newType.getOuterArraySize() != resources.maxMeshViewCountNV)
error(loc, "mesh view output array size must be gl_MaxMeshViewCountNV or implicitly sized", "[]", "");
else if (newType.getArraySizes()->getNumDims() == 2) {
int innerDimSize = newType.getArraySizes()->getDimSize(1);
arrayLimitCheck(memberLoc, member->type->getFieldName(), innerDimSize);
oldType.getArraySizes()->setDimSize(1, innerDimSize);
}
}
if (oldType.getQualifier().isPerPrimitive() && ! newType.getQualifier().isPerPrimitive())
error(memberLoc, "missing perprimitiveNV qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
else if (! oldType.getQualifier().isPerPrimitive() && newType.getQualifier().isPerPrimitive())
error(memberLoc, "cannot add perprimitiveNV qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().isMemory())
error(memberLoc, "cannot add memory qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().hasNonXfbLayout())
error(memberLoc, "cannot add non-XFB layout to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().patch)
error(memberLoc, "cannot add patch to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().hasXfbBuffer() &&
newType.getQualifier().layoutXfbBuffer != currentBlockQualifier.layoutXfbBuffer)
error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_buffer", "");
if (newType.getQualifier().hasStream() &&
newType.getQualifier().layoutStream != currentBlockQualifier.layoutStream)
error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_stream", "");
oldType.getQualifier().centroid = newType.getQualifier().centroid;
oldType.getQualifier().sample = newType.getQualifier().sample;
oldType.getQualifier().invariant = newType.getQualifier().invariant;
oldType.getQualifier().noContraction = newType.getQualifier().noContraction;
oldType.getQualifier().smooth = newType.getQualifier().smooth;
oldType.getQualifier().flat = newType.getQualifier().flat;
oldType.getQualifier().nopersp = newType.getQualifier().nopersp;
oldType.getQualifier().layoutXfbOffset = newType.getQualifier().layoutXfbOffset;
oldType.getQualifier().layoutXfbBuffer = newType.getQualifier().layoutXfbBuffer;
oldType.getQualifier().layoutXfbStride = newType.getQualifier().layoutXfbStride;
if (oldType.getQualifier().layoutXfbOffset != TQualifier::layoutXfbBufferEnd) {
// If any member has an xfb_offset, then the block's xfb_buffer inherents current xfb_buffer,
// and for xfb processing, the member needs it as well, along with xfb_stride.
type.getQualifier().layoutXfbBuffer = currentBlockQualifier.layoutXfbBuffer;
oldType.getQualifier().layoutXfbBuffer = currentBlockQualifier.layoutXfbBuffer;
}
if (oldType.isUnsizedArray() && newType.isSizedArray())
oldType.changeOuterArraySize(newType.getOuterArraySize());
// check and process the member's type, which will include managing xfb information
layoutTypeCheck(loc, oldType);
// go to next member
++member;
} else {
// For missing members of anonymous blocks that have been redeclared,
// hide the original (shared) declaration.
// Instance-named blocks can just have the member removed.
if (instanceName)
member = type.getWritableStruct()->erase(member);
else {
member->type->hideMember();
++member;
}
}
}
if (spvVersion.vulkan > 0) {
// ...then streams apply to built-in blocks, instead of them being only on stream 0
type.getQualifier().layoutStream = currentBlockQualifier.layoutStream;
}
if (numOriginalMembersFound < newTypeList.size())
error(loc, "block redeclaration has extra members", blockName.c_str(), "");
if (type.isArray() != (arraySizes != nullptr) ||
(type.isArray() && arraySizes != nullptr && type.getArraySizes()->getNumDims() != arraySizes->getNumDims()))
error(loc, "cannot change arrayness of redeclared block", blockName.c_str(), "");
else if (type.isArray()) {
// At this point, we know both are arrays and both have the same number of dimensions.
// It is okay for a built-in block redeclaration to be unsized, and keep the size of the
// original block declaration.
if (!arraySizes->isSized() && type.isSizedArray())
arraySizes->changeOuterSize(type.getOuterArraySize());
// And, okay to be giving a size to the array, by the redeclaration
if (!type.isSizedArray() && arraySizes->isSized())
type.changeOuterArraySize(arraySizes->getOuterSize());
// Now, they must match in all dimensions.
if (type.isSizedArray() && *type.getArraySizes() != *arraySizes)
error(loc, "cannot change array size of redeclared block", blockName.c_str(), "");
}
symbolTable.insert(*block);
// Check for general layout qualifier errors
layoutObjectCheck(loc, *block);
// Tracking for implicit sizing of array
if (isIoResizeArray(block->getType())) {
ioArraySymbolResizeList.push_back(block);
checkIoArraysConsistency(loc, true);
} else if (block->getType().isArray())
fixIoArraySize(loc, block->getWritableType());
// Save it in the AST for linker use.
trackLinkage(*block);
}
void TParseContext::paramCheckFixStorage(const TSourceLoc& loc, const TStorageQualifier& qualifier, TType& type)
{
switch (qualifier) {
case EvqConst:
case EvqConstReadOnly:
type.getQualifier().storage = EvqConstReadOnly;
break;
case EvqIn:
case EvqOut:
case EvqInOut:
case EvqTileImageEXT:
type.getQualifier().storage = qualifier;
break;
case EvqGlobal:
case EvqTemporary:
type.getQualifier().storage = EvqIn;
break;
default:
type.getQualifier().storage = EvqIn;
error(loc, "storage qualifier not allowed on function parameter", GetStorageQualifierString(qualifier), "");
break;
}
}
void TParseContext::paramCheckFix(const TSourceLoc& loc, const TQualifier& qualifier, TType& type)
{
if (qualifier.isMemory()) {
type.getQualifier().volatil = qualifier.volatil;
type.getQualifier().coherent = qualifier.coherent;
type.getQualifier().devicecoherent = qualifier.devicecoherent ;
type.getQualifier().queuefamilycoherent = qualifier.queuefamilycoherent;
type.getQualifier().workgroupcoherent = qualifier.workgroupcoherent;
type.getQualifier().subgroupcoherent = qualifier.subgroupcoherent;
type.getQualifier().shadercallcoherent = qualifier.shadercallcoherent;
type.getQualifier().nonprivate = qualifier.nonprivate;
type.getQualifier().readonly = qualifier.readonly;
type.getQualifier().writeonly = qualifier.writeonly;
type.getQualifier().restrict = qualifier.restrict;
}
if (qualifier.isAuxiliary() ||
qualifier.isInterpolation())
error(loc, "cannot use auxiliary or interpolation qualifiers on a function parameter", "", "");
if (qualifier.hasLayout())
error(loc, "cannot use layout qualifiers on a function parameter", "", "");
if (qualifier.invariant)
error(loc, "cannot use invariant qualifier on a function parameter", "", "");
if (qualifier.isNoContraction()) {
if (qualifier.isParamOutput())
type.getQualifier().setNoContraction();
else
warn(loc, "qualifier has no effect on non-output parameters", "precise", "");
}
if (qualifier.isNonUniform())
type.getQualifier().nonUniform = qualifier.nonUniform;
if (qualifier.isSpirvByReference())
type.getQualifier().setSpirvByReference();
if (qualifier.isSpirvLiteral()) {
if (type.getBasicType() == EbtFloat || type.getBasicType() == EbtInt || type.getBasicType() == EbtUint ||
type.getBasicType() == EbtBool)
type.getQualifier().setSpirvLiteral();
else
error(loc, "cannot use spirv_literal qualifier", type.getBasicTypeString().c_str(), "");
}
paramCheckFixStorage(loc, qualifier.storage, type);
}
void TParseContext::nestedBlockCheck(const TSourceLoc& loc)
{
if (structNestingLevel > 0 || blockNestingLevel > 0)
error(loc, "cannot nest a block definition inside a structure or block", "", "");
++blockNestingLevel;
}
void TParseContext::nestedStructCheck(const TSourceLoc& loc)
{
if (structNestingLevel > 0 || blockNestingLevel > 0)
error(loc, "cannot nest a structure definition inside a structure or block", "", "");
++structNestingLevel;
}
void TParseContext::arrayObjectCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
// Some versions don't allow comparing arrays or structures containing arrays
if (type.containsArray()) {
profileRequires(loc, ENoProfile, 120, E_GL_3DL_array_objects, op);
profileRequires(loc, EEsProfile, 300, nullptr, op);
}
}
void TParseContext::opaqueCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (containsFieldWithBasicType(type, EbtSampler) && !extensionTurnedOn(E_GL_ARB_bindless_texture))
error(loc, "can't use with samplers or structs containing samplers", op, "");
}
void TParseContext::referenceCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (containsFieldWithBasicType(type, EbtReference))
error(loc, "can't use with reference types", op, "");
}
void TParseContext::storage16BitAssignmentCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtFloat16))
requireFloat16Arithmetic(loc, op, "can't use with structs containing float16");
if (type.isArray() && type.getBasicType() == EbtFloat16)
requireFloat16Arithmetic(loc, op, "can't use with arrays containing float16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtInt16))
requireInt16Arithmetic(loc, op, "can't use with structs containing int16");
if (type.isArray() && type.getBasicType() == EbtInt16)
requireInt16Arithmetic(loc, op, "can't use with arrays containing int16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtUint16))
requireInt16Arithmetic(loc, op, "can't use with structs containing uint16");
if (type.isArray() && type.getBasicType() == EbtUint16)
requireInt16Arithmetic(loc, op, "can't use with arrays containing uint16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtInt8))
requireInt8Arithmetic(loc, op, "can't use with structs containing int8");
if (type.isArray() && type.getBasicType() == EbtInt8)
requireInt8Arithmetic(loc, op, "can't use with arrays containing int8");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtUint8))
requireInt8Arithmetic(loc, op, "can't use with structs containing uint8");
if (type.isArray() && type.getBasicType() == EbtUint8)
requireInt8Arithmetic(loc, op, "can't use with arrays containing uint8");
}
void TParseContext::specializationCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (type.containsSpecializationSize())
error(loc, "can't use with types containing arrays sized with a specialization constant", op, "");
}
void TParseContext::structTypeCheck(const TSourceLoc& /*loc*/, TPublicType& publicType)
{
const TTypeList& typeList = *publicType.userDef->getStruct();
// fix and check for member storage qualifiers and types that don't belong within a structure
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
const TSourceLoc& memberLoc = typeList[member].loc;
if (memberQualifier.isAuxiliary() ||
memberQualifier.isInterpolation() ||
(memberQualifier.storage != EvqTemporary && memberQualifier.storage != EvqGlobal))
error(memberLoc, "cannot use storage or interpolation qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.isMemory())
error(memberLoc, "cannot use memory qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.hasLayout()) {
error(memberLoc, "cannot use layout qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
memberQualifier.clearLayout();
}
if (memberQualifier.invariant)
error(memberLoc, "cannot use invariant qualifier on structure members", typeList[member].type->getFieldName().c_str(), "");
}
}
//
// See if this loop satisfies the limitations for ES 2.0 (version 100) for loops in Appendex A:
//
// "The loop index has type int or float.
//
// "The for statement has the form:
// for ( init-declaration ; condition ; expression )
// init-declaration has the form: type-specifier identifier = constant-expression
// condition has the form: loop-index relational_operator constant-expression
// where relational_operator is one of: > >= < <= == or !=
// expression [sic] has one of the following forms:
// loop-index++
// loop-index--
// loop-index += constant-expression
// loop-index -= constant-expression
//
// The body is handled in an AST traversal.
//
void TParseContext::inductiveLoopCheck(const TSourceLoc& loc, TIntermNode* init, TIntermLoop* loop)
{
// loop index init must exist and be a declaration, which shows up in the AST as an aggregate of size 1 of the declaration
bool badInit = false;
if (! init || ! init->getAsAggregate() || init->getAsAggregate()->getSequence().size() != 1)
badInit = true;
TIntermBinary* binaryInit = nullptr;
if (! badInit) {
// get the declaration assignment
binaryInit = init->getAsAggregate()->getSequence()[0]->getAsBinaryNode();
if (! binaryInit)
badInit = true;
}
if (badInit) {
error(loc, "inductive-loop init-declaration requires the form \"type-specifier loop-index = constant-expression\"", "limitations", "");
return;
}
// loop index must be type int or float
if (! binaryInit->getType().isScalar() || (binaryInit->getBasicType() != EbtInt && binaryInit->getBasicType() != EbtFloat)) {
error(loc, "inductive loop requires a scalar 'int' or 'float' loop index", "limitations", "");
return;
}
// init is the form "loop-index = constant"
if (binaryInit->getOp() != EOpAssign || ! binaryInit->getLeft()->getAsSymbolNode() || ! binaryInit->getRight()->getAsConstantUnion()) {
error(loc, "inductive-loop init-declaration requires the form \"type-specifier loop-index = constant-expression\"", "limitations", "");
return;
}
// get the unique id of the loop index
long long loopIndex = binaryInit->getLeft()->getAsSymbolNode()->getId();
inductiveLoopIds.insert(loopIndex);
// condition's form must be "loop-index relational-operator constant-expression"
bool badCond = ! loop->getTest();
if (! badCond) {
TIntermBinary* binaryCond = loop->getTest()->getAsBinaryNode();
badCond = ! binaryCond;
if (! badCond) {
switch (binaryCond->getOp()) {
case EOpGreaterThan:
case EOpGreaterThanEqual:
case EOpLessThan:
case EOpLessThanEqual:
case EOpEqual:
case EOpNotEqual:
break;
default:
badCond = true;
}
}
if (binaryCond && (! binaryCond->getLeft()->getAsSymbolNode() ||
binaryCond->getLeft()->getAsSymbolNode()->getId() != loopIndex ||
! binaryCond->getRight()->getAsConstantUnion()))
badCond = true;
}
if (badCond) {
error(loc, "inductive-loop condition requires the form \"loop-index <comparison-op> constant-expression\"", "limitations", "");
return;
}
// loop-index++
// loop-index--
// loop-index += constant-expression
// loop-index -= constant-expression
bool badTerminal = ! loop->getTerminal();
if (! badTerminal) {
TIntermUnary* unaryTerminal = loop->getTerminal()->getAsUnaryNode();
TIntermBinary* binaryTerminal = loop->getTerminal()->getAsBinaryNode();
if (unaryTerminal || binaryTerminal) {
switch(loop->getTerminal()->getAsOperator()->getOp()) {
case EOpPostDecrement:
case EOpPostIncrement:
case EOpAddAssign:
case EOpSubAssign:
break;
default:
badTerminal = true;
}
} else
badTerminal = true;
if (binaryTerminal && (! binaryTerminal->getLeft()->getAsSymbolNode() ||
binaryTerminal->getLeft()->getAsSymbolNode()->getId() != loopIndex ||
! binaryTerminal->getRight()->getAsConstantUnion()))
badTerminal = true;
if (unaryTerminal && (! unaryTerminal->getOperand()->getAsSymbolNode() ||
unaryTerminal->getOperand()->getAsSymbolNode()->getId() != loopIndex))
badTerminal = true;
}
if (badTerminal) {
error(loc, "inductive-loop termination requires the form \"loop-index++, loop-index--, loop-index += constant-expression, or loop-index -= constant-expression\"", "limitations", "");
return;
}
// the body
inductiveLoopBodyCheck(loop->getBody(), loopIndex, symbolTable);
}
// Do limit checks for built-in arrays.
void TParseContext::arrayLimitCheck(const TSourceLoc& loc, const TString& identifier, int size)
{
if (identifier.compare("gl_TexCoord") == 0)
limitCheck(loc, size, "gl_MaxTextureCoords", "gl_TexCoord array size");
else if (identifier.compare("gl_ClipDistance") == 0)
limitCheck(loc, size, "gl_MaxClipDistances", "gl_ClipDistance array size");
else if (identifier.compare("gl_CullDistance") == 0)
limitCheck(loc, size, "gl_MaxCullDistances", "gl_CullDistance array size");
else if (identifier.compare("gl_ClipDistancePerViewNV") == 0)
limitCheck(loc, size, "gl_MaxClipDistances", "gl_ClipDistancePerViewNV array size");
else if (identifier.compare("gl_CullDistancePerViewNV") == 0)
limitCheck(loc, size, "gl_MaxCullDistances", "gl_CullDistancePerViewNV array size");
}
// See if the provided value is less than or equal to the symbol indicated by limit,
// which should be a constant in the symbol table.
void TParseContext::limitCheck(const TSourceLoc& loc, int value, const char* limit, const char* feature)
{
TSymbol* symbol = symbolTable.find(limit);
assert(symbol->getAsVariable());
const TConstUnionArray& constArray = symbol->getAsVariable()->getConstArray();
assert(! constArray.empty());
if (value > constArray[0].getIConst())
error(loc, "must be less than or equal to", feature, "%s (%d)", limit, constArray[0].getIConst());
}
//
// Do any additional error checking, etc., once we know the parsing is done.
//
void TParseContext::finish()
{
TParseContextBase::finish();
if (parsingBuiltins)
return;
// Check on array indexes for ES 2.0 (version 100) limitations.
for (size_t i = 0; i < needsIndexLimitationChecking.size(); ++i)
constantIndexExpressionCheck(needsIndexLimitationChecking[i]);
// Check for stages that are enabled by extension.
// Can't do this at the beginning, it is chicken and egg to add a stage by
// extension.
// Stage-specific features were correctly tested for already, this is just
// about the stage itself.
switch (language) {
case EShLangGeometry:
if (isEsProfile() && version == 310)
requireExtensions(getCurrentLoc(), Num_AEP_geometry_shader, AEP_geometry_shader, "geometry shaders");
break;
case EShLangTessControl:
case EShLangTessEvaluation:
if (isEsProfile() && version == 310)
requireExtensions(getCurrentLoc(), Num_AEP_tessellation_shader, AEP_tessellation_shader, "tessellation shaders");
else if (!isEsProfile() && version < 400)
requireExtensions(getCurrentLoc(), 1, &E_GL_ARB_tessellation_shader, "tessellation shaders");
break;
case EShLangCompute:
if (!isEsProfile() && version < 430)
requireExtensions(getCurrentLoc(), 1, &E_GL_ARB_compute_shader, "compute shaders");
break;
case EShLangTask:
requireExtensions(getCurrentLoc(), Num_AEP_mesh_shader, AEP_mesh_shader, "task shaders");
break;
case EShLangMesh:
requireExtensions(getCurrentLoc(), Num_AEP_mesh_shader, AEP_mesh_shader, "mesh shaders");
break;
default:
break;
}
// Set default outputs for GL_NV_geometry_shader_passthrough
if (language == EShLangGeometry && extensionTurnedOn(E_SPV_NV_geometry_shader_passthrough)) {
if (intermediate.getOutputPrimitive() == ElgNone) {
switch (intermediate.getInputPrimitive()) {
case ElgPoints: intermediate.setOutputPrimitive(ElgPoints); break;
case ElgLines: intermediate.setOutputPrimitive(ElgLineStrip); break;
case ElgTriangles: intermediate.setOutputPrimitive(ElgTriangleStrip); break;
default: break;
}
}
if (intermediate.getVertices() == TQualifier::layoutNotSet) {
switch (intermediate.getInputPrimitive()) {
case ElgPoints: intermediate.setVertices(1); break;
case ElgLines: intermediate.setVertices(2); break;
case ElgTriangles: intermediate.setVertices(3); break;
default: break;
}
}
}
}
//
// Layout qualifier stuff.
//
// Put the id's layout qualification into the public type, for qualifiers not having a number set.
// This is before we know any type information for error checking.
void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publicType, TString& id)
{
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
if (id == TQualifier::getLayoutMatrixString(ElmColumnMajor)) {
publicType.qualifier.layoutMatrix = ElmColumnMajor;
return;
}
if (id == TQualifier::getLayoutMatrixString(ElmRowMajor)) {
publicType.qualifier.layoutMatrix = ElmRowMajor;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpPacked)) {
if (spvVersion.spv != 0) {
if (spvVersion.vulkanRelaxed)
return; // silently ignore qualifier
else
spvRemoved(loc, "packed");
}
publicType.qualifier.layoutPacking = ElpPacked;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpShared)) {
if (spvVersion.spv != 0) {
if (spvVersion.vulkanRelaxed)
return; // silently ignore qualifier
else
spvRemoved(loc, "shared");
}
publicType.qualifier.layoutPacking = ElpShared;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpStd140)) {
publicType.qualifier.layoutPacking = ElpStd140;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpStd430)) {
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "std430");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, E_GL_ARB_shader_storage_buffer_object, "std430");
profileRequires(loc, EEsProfile, 310, nullptr, "std430");
publicType.qualifier.layoutPacking = ElpStd430;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpScalar)) {
requireVulkan(loc, "scalar");
requireExtensions(loc, 1, &E_GL_EXT_scalar_block_layout, "scalar block layout");
publicType.qualifier.layoutPacking = ElpScalar;
return;
}
// TODO: compile-time performance: may need to stop doing linear searches
for (TLayoutFormat format = (TLayoutFormat)(ElfNone + 1); format < ElfCount; format = (TLayoutFormat)(format + 1)) {
if (id == TQualifier::getLayoutFormatString(format)) {
if ((format > ElfEsFloatGuard && format < ElfFloatGuard) ||
(format > ElfEsIntGuard && format < ElfIntGuard) ||
(format > ElfEsUintGuard && format < ElfCount))
requireProfile(loc, ENoProfile | ECoreProfile | ECompatibilityProfile, "image load-store format");
profileRequires(loc, ENoProfile | ECoreProfile | ECompatibilityProfile, 420, E_GL_ARB_shader_image_load_store, "image load store");
profileRequires(loc, EEsProfile, 310, E_GL_ARB_shader_image_load_store, "image load store");
publicType.qualifier.layoutFormat = format;
return;
}
}
if (id == "push_constant") {
requireVulkan(loc, "push_constant");
publicType.qualifier.layoutPushConstant = true;
return;
}
if (id == "buffer_reference") {
requireVulkan(loc, "buffer_reference");
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference, "buffer_reference");
publicType.qualifier.layoutBufferReference = true;
intermediate.setUseStorageBuffer();
intermediate.setUsePhysicalStorageBuffer();
return;
}
if (id == "bindless_sampler") {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "bindless_sampler");
publicType.qualifier.layoutBindlessSampler = true;
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeLayout);
return;
}
if (id == "bindless_image") {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "bindless_image");
publicType.qualifier.layoutBindlessImage = true;
intermediate.setBindlessImageMode(currentCaller, AstRefTypeLayout);
return;
}
if (id == "bound_sampler") {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "bound_sampler");
publicType.qualifier.layoutBindlessSampler = false;
return;
}
if (id == "bound_image") {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "bound_image");
publicType.qualifier.layoutBindlessImage = false;
return;
}
if (language == EShLangGeometry || language == EShLangTessEvaluation || language == EShLangMesh) {
if (id == TQualifier::getGeometryString(ElgTriangles)) {
publicType.shaderQualifiers.geometry = ElgTriangles;
return;
}
if (language == EShLangGeometry || language == EShLangMesh) {
if (id == TQualifier::getGeometryString(ElgPoints)) {
publicType.shaderQualifiers.geometry = ElgPoints;
return;
}
if (id == TQualifier::getGeometryString(ElgLines)) {
publicType.shaderQualifiers.geometry = ElgLines;
return;
}
if (language == EShLangGeometry) {
if (id == TQualifier::getGeometryString(ElgLineStrip)) {
publicType.shaderQualifiers.geometry = ElgLineStrip;
return;
}
if (id == TQualifier::getGeometryString(ElgLinesAdjacency)) {
publicType.shaderQualifiers.geometry = ElgLinesAdjacency;
return;
}
if (id == TQualifier::getGeometryString(ElgTrianglesAdjacency)) {
publicType.shaderQualifiers.geometry = ElgTrianglesAdjacency;
return;
}
if (id == TQualifier::getGeometryString(ElgTriangleStrip)) {
publicType.shaderQualifiers.geometry = ElgTriangleStrip;
return;
}
if (id == "passthrough") {
requireExtensions(loc, 1, &E_SPV_NV_geometry_shader_passthrough, "geometry shader passthrough");
publicType.qualifier.layoutPassthrough = true;
intermediate.setGeoPassthroughEXT();
return;
}
}
} else {
assert(language == EShLangTessEvaluation);
// input primitive
if (id == TQualifier::getGeometryString(ElgTriangles)) {
publicType.shaderQualifiers.geometry = ElgTriangles;
return;
}
if (id == TQualifier::getGeometryString(ElgQuads)) {
publicType.shaderQualifiers.geometry = ElgQuads;
return;
}
if (id == TQualifier::getGeometryString(ElgIsolines)) {
publicType.shaderQualifiers.geometry = ElgIsolines;
return;
}
// vertex spacing
if (id == TQualifier::getVertexSpacingString(EvsEqual)) {
publicType.shaderQualifiers.spacing = EvsEqual;
return;
}
if (id == TQualifier::getVertexSpacingString(EvsFractionalEven)) {
publicType.shaderQualifiers.spacing = EvsFractionalEven;
return;
}
if (id == TQualifier::getVertexSpacingString(EvsFractionalOdd)) {
publicType.shaderQualifiers.spacing = EvsFractionalOdd;
return;
}
// triangle order
if (id == TQualifier::getVertexOrderString(EvoCw)) {
publicType.shaderQualifiers.order = EvoCw;
return;
}
if (id == TQualifier::getVertexOrderString(EvoCcw)) {
publicType.shaderQualifiers.order = EvoCcw;
return;
}
// point mode
if (id == "point_mode") {
publicType.shaderQualifiers.pointMode = true;
return;
}
}
}
if (language == EShLangFragment) {
if (id == "origin_upper_left") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile | ENoProfile, "origin_upper_left");
if (profile == ENoProfile) {
profileRequires(loc,ECoreProfile | ECompatibilityProfile, 140, E_GL_ARB_fragment_coord_conventions, "origin_upper_left");
}
publicType.shaderQualifiers.originUpperLeft = true;
return;
}
if (id == "pixel_center_integer") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile | ENoProfile, "pixel_center_integer");
if (profile == ENoProfile) {
profileRequires(loc,ECoreProfile | ECompatibilityProfile, 140, E_GL_ARB_fragment_coord_conventions, "pixel_center_integer");
}
publicType.shaderQualifiers.pixelCenterInteger = true;
return;
}
if (id == "early_fragment_tests") {
profileRequires(loc, ENoProfile | ECoreProfile | ECompatibilityProfile, 420, E_GL_ARB_shader_image_load_store, "early_fragment_tests");
profileRequires(loc, EEsProfile, 310, nullptr, "early_fragment_tests");
publicType.shaderQualifiers.earlyFragmentTests = true;
return;
}
if (id == "early_and_late_fragment_tests_amd") {
profileRequires(loc, ENoProfile | ECoreProfile | ECompatibilityProfile, 420, E_GL_AMD_shader_early_and_late_fragment_tests, "early_and_late_fragment_tests_amd");
profileRequires(loc, EEsProfile, 310, nullptr, "early_and_late_fragment_tests_amd");
publicType.shaderQualifiers.earlyAndLateFragmentTestsAMD = true;
return;
}
if (id == "post_depth_coverage") {
requireExtensions(loc, Num_post_depth_coverageEXTs, post_depth_coverageEXTs, "post depth coverage");
if (extensionTurnedOn(E_GL_ARB_post_depth_coverage)) {
publicType.shaderQualifiers.earlyFragmentTests = true;
}
publicType.shaderQualifiers.postDepthCoverage = true;
return;
}
/* id is transformed into lower case in the beginning of this function. */
if (id == "non_coherent_color_attachment_readext") {
requireExtensions(loc, 1, &E_GL_EXT_shader_tile_image, "non_coherent_color_attachment_readEXT");
publicType.shaderQualifiers.nonCoherentColorAttachmentReadEXT = true;
return;
}
if (id == "non_coherent_depth_attachment_readext") {
requireExtensions(loc, 1, &E_GL_EXT_shader_tile_image, "non_coherent_depth_attachment_readEXT");
publicType.shaderQualifiers.nonCoherentDepthAttachmentReadEXT = true;
return;
}
if (id == "non_coherent_stencil_attachment_readext") {
requireExtensions(loc, 1, &E_GL_EXT_shader_tile_image, "non_coherent_stencil_attachment_readEXT");
publicType.shaderQualifiers.nonCoherentStencilAttachmentReadEXT = true;
return;
}
for (TLayoutDepth depth = (TLayoutDepth)(EldNone + 1); depth < EldCount; depth = (TLayoutDepth)(depth+1)) {
if (id == TQualifier::getLayoutDepthString(depth)) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "depth layout qualifier");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, nullptr, "depth layout qualifier");
publicType.shaderQualifiers.layoutDepth = depth;
return;
}
}
for (TLayoutStencil stencil = (TLayoutStencil)(ElsNone + 1); stencil < ElsCount; stencil = (TLayoutStencil)(stencil+1)) {
if (id == TQualifier::getLayoutStencilString(stencil)) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "stencil layout qualifier");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, nullptr, "stencil layout qualifier");
publicType.shaderQualifiers.layoutStencil = stencil;
return;
}
}
for (TInterlockOrdering order = (TInterlockOrdering)(EioNone + 1); order < EioCount; order = (TInterlockOrdering)(order+1)) {
if (id == TQualifier::getInterlockOrderingString(order)) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "fragment shader interlock layout qualifier");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 450, nullptr, "fragment shader interlock layout qualifier");
requireExtensions(loc, 1, &E_GL_ARB_fragment_shader_interlock, TQualifier::getInterlockOrderingString(order));
if (order == EioShadingRateInterlockOrdered || order == EioShadingRateInterlockUnordered)
requireExtensions(loc, 1, &E_GL_NV_shading_rate_image, TQualifier::getInterlockOrderingString(order));
publicType.shaderQualifiers.interlockOrdering = order;
return;
}
}
if (id.compare(0, 13, "blend_support") == 0) {
bool found = false;
for (TBlendEquationShift be = (TBlendEquationShift)0; be < EBlendCount; be = (TBlendEquationShift)(be + 1)) {
if (id == TQualifier::getBlendEquationString(be)) {
profileRequires(loc, EEsProfile, 320, E_GL_KHR_blend_equation_advanced, "blend equation");
profileRequires(loc, ~EEsProfile, 0, E_GL_KHR_blend_equation_advanced, "blend equation");
intermediate.addBlendEquation(be);
publicType.shaderQualifiers.blendEquation = true;
found = true;
break;
}
}
if (! found)
error(loc, "unknown blend equation", "blend_support", "");
return;
}
if (id == "override_coverage") {
requireExtensions(loc, 1, &E_GL_NV_sample_mask_override_coverage, "sample mask override coverage");
publicType.shaderQualifiers.layoutOverrideCoverage = true;
return;
}
}
if (language == EShLangVertex ||
language == EShLangTessControl ||
language == EShLangTessEvaluation ||
language == EShLangGeometry ) {
if (id == "viewport_relative") {
requireExtensions(loc, 1, &E_GL_NV_viewport_array2, "view port array2");
publicType.qualifier.layoutViewportRelative = true;
return;
}
} else {
if (language == EShLangRayGen || language == EShLangIntersect ||
language == EShLangAnyHit || language == EShLangClosestHit ||
language == EShLangMiss || language == EShLangCallable) {
if (id == "shaderrecordnv" || id == "shaderrecordext") {
if (id == "shaderrecordnv") {
requireExtensions(loc, 1, &E_GL_NV_ray_tracing, "shader record NV");
} else {
requireExtensions(loc, 1, &E_GL_EXT_ray_tracing, "shader record EXT");
}
publicType.qualifier.layoutShaderRecord = true;
return;
} else if (id == "hitobjectshaderrecordnv") {
requireExtensions(loc, 1, &E_GL_NV_shader_invocation_reorder, "hitobject shader record NV");
publicType.qualifier.layoutHitObjectShaderRecordNV = true;
return;
}
}
}
if (language == EShLangCompute) {
if (id.compare(0, 17, "derivative_group_") == 0) {
requireExtensions(loc, 1, &E_GL_NV_compute_shader_derivatives, "compute shader derivatives");
if (id == "derivative_group_quadsnv") {
publicType.shaderQualifiers.layoutDerivativeGroupQuads = true;
return;
} else if (id == "derivative_group_linearnv") {
publicType.shaderQualifiers.layoutDerivativeGroupLinear = true;
return;
}
}
}
if (id == "primitive_culling") {
requireExtensions(loc, 1, &E_GL_EXT_ray_flags_primitive_culling, "primitive culling");
publicType.shaderQualifiers.layoutPrimitiveCulling = true;
return;
}
error(loc, "unrecognized layout identifier, or qualifier requires assignment (e.g., binding = 4)", id.c_str(), "");
}
// Put the id's layout qualifier value into the public type, for qualifiers having a number set.
// This is before we know any type information for error checking.
void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publicType, TString& id, const TIntermTyped* node)
{
const char* feature = "layout-id value";
const char* nonLiteralFeature = "non-literal layout-id value";
integerCheck(node, feature);
const TIntermConstantUnion* constUnion = node->getAsConstantUnion();
int value;
bool nonLiteral = false;
if (constUnion) {
value = constUnion->getConstArray()[0].getIConst();
if (! constUnion->isLiteral()) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, nonLiteralFeature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, nonLiteralFeature);
}
} else {
// grammar should have give out the error message
value = 0;
nonLiteral = true;
}
if (value < 0) {
error(loc, "cannot be negative", feature, "");
return;
}
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
if (id == "offset") {
// "offset" can be for either
// - uniform offsets
// - atomic_uint offsets
const char* feature = "offset";
if (spvVersion.spv == 0) {
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature);
const char* exts[2] = { E_GL_ARB_enhanced_layouts, E_GL_ARB_shader_atomic_counters };
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 2, exts, feature);
profileRequires(loc, EEsProfile, 310, nullptr, feature);
}
publicType.qualifier.layoutOffset = value;
publicType.qualifier.explicitOffset = true;
if (nonLiteral)
error(loc, "needs a literal integer", "offset", "");
return;
} else if (id == "align") {
const char* feature = "uniform buffer-member align";
if (spvVersion.spv == 0) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature);
}
// "The specified alignment must be a power of 2, or a compile-time error results."
if (! IsPow2(value))
error(loc, "must be a power of 2", "align", "");
else
publicType.qualifier.layoutAlign = value;
if (nonLiteral)
error(loc, "needs a literal integer", "align", "");
return;
} else if (id == "location") {
profileRequires(loc, EEsProfile, 300, nullptr, "location");
const char* exts[2] = { E_GL_ARB_separate_shader_objects, E_GL_ARB_explicit_attrib_location };
// GL_ARB_explicit_uniform_location requires 330 or GL_ARB_explicit_attrib_location we do not need to add it here
profileRequires(loc, ~EEsProfile, 330, 2, exts, "location");
if ((unsigned int)value >= TQualifier::layoutLocationEnd)
error(loc, "location is too large", id.c_str(), "");
else
publicType.qualifier.layoutLocation = value;
if (nonLiteral)
error(loc, "needs a literal integer", "location", "");
return;
} else if (id == "set") {
if ((unsigned int)value >= TQualifier::layoutSetEnd)
error(loc, "set is too large", id.c_str(), "");
else
publicType.qualifier.layoutSet = value;
if (value != 0)
requireVulkan(loc, "descriptor set");
if (nonLiteral)
error(loc, "needs a literal integer", "set", "");
return;
} else if (id == "binding") {
profileRequires(loc, ~EEsProfile, 420, E_GL_ARB_shading_language_420pack, "binding");
profileRequires(loc, EEsProfile, 310, nullptr, "binding");
if ((unsigned int)value >= TQualifier::layoutBindingEnd)
error(loc, "binding is too large", id.c_str(), "");
else
publicType.qualifier.layoutBinding = value;
if (nonLiteral)
error(loc, "needs a literal integer", "binding", "");
return;
}
if (id == "constant_id") {
requireSpv(loc, "constant_id");
if (value >= (int)TQualifier::layoutSpecConstantIdEnd) {
error(loc, "specialization-constant id is too large", id.c_str(), "");
} else {
publicType.qualifier.layoutSpecConstantId = value;
publicType.qualifier.specConstant = true;
if (! intermediate.addUsedConstantId(value))
error(loc, "specialization-constant id already used", id.c_str(), "");
}
if (nonLiteral)
error(loc, "needs a literal integer", "constant_id", "");
return;
}
if (id == "component") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "component");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, "component");
if ((unsigned)value >= TQualifier::layoutComponentEnd)
error(loc, "component is too large", id.c_str(), "");
else
publicType.qualifier.layoutComponent = value;
if (nonLiteral)
error(loc, "needs a literal integer", "component", "");
return;
}
if (id.compare(0, 4, "xfb_") == 0) {
// "Any shader making any static use (after preprocessing) of any of these
// *xfb_* qualifiers will cause the shader to be in a transform feedback
// capturing mode and hence responsible for describing the transform feedback
// setup."
intermediate.setXfbMode();
const char* feature = "transform feedback qualifier";
requireStage(loc, (EShLanguageMask)(EShLangVertexMask | EShLangGeometryMask | EShLangTessControlMask | EShLangTessEvaluationMask), feature);
requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature);
if (id == "xfb_buffer") {
// "It is a compile-time error to specify an *xfb_buffer* that is greater than
// the implementation-dependent constant gl_MaxTransformFeedbackBuffers."
if (value >= resources.maxTransformFeedbackBuffers)
error(loc, "buffer is too large:", id.c_str(), "gl_MaxTransformFeedbackBuffers is %d", resources.maxTransformFeedbackBuffers);
if (value >= (int)TQualifier::layoutXfbBufferEnd)
error(loc, "buffer is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbBufferEnd-1);
else
publicType.qualifier.layoutXfbBuffer = value;
if (nonLiteral)
error(loc, "needs a literal integer", "xfb_buffer", "");
return;
} else if (id == "xfb_offset") {
if (value >= (int)TQualifier::layoutXfbOffsetEnd)
error(loc, "offset is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbOffsetEnd-1);
else
publicType.qualifier.layoutXfbOffset = value;
if (nonLiteral)
error(loc, "needs a literal integer", "xfb_offset", "");
return;
} else if (id == "xfb_stride") {
// "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the
// implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents."
if (value > 4 * resources.maxTransformFeedbackInterleavedComponents) {
error(loc, "1/4 stride is too large:", id.c_str(), "gl_MaxTransformFeedbackInterleavedComponents is %d",
resources.maxTransformFeedbackInterleavedComponents);
}
if (value >= (int)TQualifier::layoutXfbStrideEnd)
error(loc, "stride is too large:", id.c_str(), "internal max is %d", TQualifier::layoutXfbStrideEnd-1);
else
publicType.qualifier.layoutXfbStride = value;
if (nonLiteral)
error(loc, "needs a literal integer", "xfb_stride", "");
return;
}
}
if (id == "input_attachment_index") {
requireVulkan(loc, "input_attachment_index");
if (value >= (int)TQualifier::layoutAttachmentEnd)
error(loc, "attachment index is too large", id.c_str(), "");
else
publicType.qualifier.layoutAttachment = value;
if (nonLiteral)
error(loc, "needs a literal integer", "input_attachment_index", "");
return;
}
if (id == "num_views") {
requireExtensions(loc, Num_OVR_multiview_EXTs, OVR_multiview_EXTs, "num_views");
publicType.shaderQualifiers.numViews = value;
if (nonLiteral)
error(loc, "needs a literal integer", "num_views", "");
return;
}
if (language == EShLangVertex ||
language == EShLangTessControl ||
language == EShLangTessEvaluation ||
language == EShLangGeometry) {
if (id == "secondary_view_offset") {
requireExtensions(loc, 1, &E_GL_NV_stereo_view_rendering, "stereo view rendering");
publicType.qualifier.layoutSecondaryViewportRelativeOffset = value;
if (nonLiteral)
error(loc, "needs a literal integer", "secondary_view_offset", "");
return;
}
}
if (id == "buffer_reference_align") {
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference, "buffer_reference_align");
if (! IsPow2(value))
error(loc, "must be a power of 2", "buffer_reference_align", "");
else
publicType.qualifier.layoutBufferReferenceAlign = IntLog2(value);
if (nonLiteral)
error(loc, "needs a literal integer", "buffer_reference_align", "");
return;
}
switch (language) {
case EShLangTessControl:
if (id == "vertices") {
if (value == 0)
error(loc, "must be greater than 0", "vertices", "");
else
publicType.shaderQualifiers.vertices = value;
if (nonLiteral)
error(loc, "needs a literal integer", "vertices", "");
return;
}
break;
case EShLangGeometry:
if (id == "invocations") {
profileRequires(loc, ECompatibilityProfile | ECoreProfile, 400, nullptr, "invocations");
if (value == 0)
error(loc, "must be at least 1", "invocations", "");
else
publicType.shaderQualifiers.invocations = value;
if (nonLiteral)
error(loc, "needs a literal integer", "invocations", "");
return;
}
if (id == "max_vertices") {
publicType.shaderQualifiers.vertices = value;
if (value > resources.maxGeometryOutputVertices)
error(loc, "too large, must be less than gl_MaxGeometryOutputVertices", "max_vertices", "");
if (nonLiteral)
error(loc, "needs a literal integer", "max_vertices", "");
return;
}
if (id == "stream") {
requireProfile(loc, ~EEsProfile, "selecting output stream");
publicType.qualifier.layoutStream = value;
if (value > 0)
intermediate.setMultiStream();
if (nonLiteral)
error(loc, "needs a literal integer", "stream", "");
return;
}
break;
case EShLangFragment:
if (id == "index") {
requireProfile(loc, ECompatibilityProfile | ECoreProfile | EEsProfile, "index layout qualifier on fragment output");
const char* exts[2] = { E_GL_ARB_separate_shader_objects, E_GL_ARB_explicit_attrib_location };
profileRequires(loc, ECompatibilityProfile | ECoreProfile, 330, 2, exts, "index layout qualifier on fragment output");
profileRequires(loc, EEsProfile ,310, E_GL_EXT_blend_func_extended, "index layout qualifier on fragment output");
// "It is also a compile-time error if a fragment shader sets a layout index to less than 0 or greater than 1."
if (value < 0 || value > 1) {
value = 0;
error(loc, "value must be 0 or 1", "index", "");
}
publicType.qualifier.layoutIndex = value;
if (nonLiteral)
error(loc, "needs a literal integer", "index", "");
return;
}
break;
case EShLangMesh:
if (id == "max_vertices") {
requireExtensions(loc, Num_AEP_mesh_shader, AEP_mesh_shader, "max_vertices");
publicType.shaderQualifiers.vertices = value;
int max = extensionTurnedOn(E_GL_EXT_mesh_shader) ? resources.maxMeshOutputVerticesEXT
: resources.maxMeshOutputVerticesNV;
if (value > max) {
TString maxsErrtring = "too large, must be less than ";
maxsErrtring.append(extensionTurnedOn(E_GL_EXT_mesh_shader) ? "gl_MaxMeshOutputVerticesEXT"
: "gl_MaxMeshOutputVerticesNV");
error(loc, maxsErrtring.c_str(), "max_vertices", "");
}
if (nonLiteral)
error(loc, "needs a literal integer", "max_vertices", "");
return;
}
if (id == "max_primitives") {
requireExtensions(loc, Num_AEP_mesh_shader, AEP_mesh_shader, "max_primitives");
publicType.shaderQualifiers.primitives = value;
int max = extensionTurnedOn(E_GL_EXT_mesh_shader) ? resources.maxMeshOutputPrimitivesEXT
: resources.maxMeshOutputPrimitivesNV;
if (value > max) {
TString maxsErrtring = "too large, must be less than ";
maxsErrtring.append(extensionTurnedOn(E_GL_EXT_mesh_shader) ? "gl_MaxMeshOutputPrimitivesEXT"
: "gl_MaxMeshOutputPrimitivesNV");
error(loc, maxsErrtring.c_str(), "max_primitives", "");
}
if (nonLiteral)
error(loc, "needs a literal integer", "max_primitives", "");
return;
}
// Fall through
case EShLangTask:
// Fall through
case EShLangCompute:
if (id.compare(0, 11, "local_size_") == 0) {
if (language == EShLangMesh || language == EShLangTask) {
requireExtensions(loc, Num_AEP_mesh_shader, AEP_mesh_shader, "gl_WorkGroupSize");
} else {
profileRequires(loc, EEsProfile, 310, nullptr, "gl_WorkGroupSize");
profileRequires(loc, ~EEsProfile, 430, E_GL_ARB_compute_shader, "gl_WorkGroupSize");
}
if (nonLiteral)
error(loc, "needs a literal integer", "local_size", "");
if (id.size() == 12 && value == 0) {
error(loc, "must be at least 1", id.c_str(), "");
return;
}
if (id == "local_size_x") {
publicType.shaderQualifiers.localSize[0] = value;
publicType.shaderQualifiers.localSizeNotDefault[0] = true;
return;
}
if (id == "local_size_y") {
publicType.shaderQualifiers.localSize[1] = value;
publicType.shaderQualifiers.localSizeNotDefault[1] = true;
return;
}
if (id == "local_size_z") {
publicType.shaderQualifiers.localSize[2] = value;
publicType.shaderQualifiers.localSizeNotDefault[2] = true;
return;
}
if (spvVersion.spv != 0) {
if (id == "local_size_x_id") {
publicType.shaderQualifiers.localSizeSpecId[0] = value;
return;
}
if (id == "local_size_y_id") {
publicType.shaderQualifiers.localSizeSpecId[1] = value;
return;
}
if (id == "local_size_z_id") {
publicType.shaderQualifiers.localSizeSpecId[2] = value;
return;
}
}
}
break;
default:
break;
}
error(loc, "there is no such layout identifier for this stage taking an assigned value", id.c_str(), "");
}
// Merge any layout qualifier information from src into dst, leaving everything else in dst alone
//
// "More than one layout qualifier may appear in a single declaration.
// Additionally, the same layout-qualifier-name can occur multiple times
// within a layout qualifier or across multiple layout qualifiers in the
// same declaration. When the same layout-qualifier-name occurs
// multiple times, in a single declaration, the last occurrence overrides
// the former occurrence(s). Further, if such a layout-qualifier-name
// will effect subsequent declarations or other observable behavior, it
// is only the last occurrence that will have any effect, behaving as if
// the earlier occurrence(s) within the declaration are not present.
// This is also true for overriding layout-qualifier-names, where one
// overrides the other (e.g., row_major vs. column_major); only the last
// occurrence has any effect."
void TParseContext::mergeObjectLayoutQualifiers(TQualifier& dst, const TQualifier& src, bool inheritOnly)
{
if (src.hasMatrix())
dst.layoutMatrix = src.layoutMatrix;
if (src.hasPacking())
dst.layoutPacking = src.layoutPacking;
if (src.hasStream())
dst.layoutStream = src.layoutStream;
if (src.hasFormat())
dst.layoutFormat = src.layoutFormat;
if (src.hasXfbBuffer())
dst.layoutXfbBuffer = src.layoutXfbBuffer;
if (src.hasBufferReferenceAlign())
dst.layoutBufferReferenceAlign = src.layoutBufferReferenceAlign;
if (src.hasAlign())
dst.layoutAlign = src.layoutAlign;
if (! inheritOnly) {
if (src.hasLocation())
dst.layoutLocation = src.layoutLocation;
if (src.hasOffset())
dst.layoutOffset = src.layoutOffset;
if (src.hasSet())
dst.layoutSet = src.layoutSet;
if (src.layoutBinding != TQualifier::layoutBindingEnd)
dst.layoutBinding = src.layoutBinding;
if (src.hasSpecConstantId())
dst.layoutSpecConstantId = src.layoutSpecConstantId;
if (src.hasComponent())
dst.layoutComponent = src.layoutComponent;
if (src.hasIndex())
dst.layoutIndex = src.layoutIndex;
if (src.hasXfbStride())
dst.layoutXfbStride = src.layoutXfbStride;
if (src.hasXfbOffset())
dst.layoutXfbOffset = src.layoutXfbOffset;
if (src.hasAttachment())
dst.layoutAttachment = src.layoutAttachment;
if (src.layoutPushConstant)
dst.layoutPushConstant = true;
if (src.layoutBufferReference)
dst.layoutBufferReference = true;
if (src.layoutPassthrough)
dst.layoutPassthrough = true;
if (src.layoutViewportRelative)
dst.layoutViewportRelative = true;
if (src.layoutSecondaryViewportRelativeOffset != -2048)
dst.layoutSecondaryViewportRelativeOffset = src.layoutSecondaryViewportRelativeOffset;
if (src.layoutShaderRecord)
dst.layoutShaderRecord = true;
if (src.layoutBindlessSampler)
dst.layoutBindlessSampler = true;
if (src.layoutBindlessImage)
dst.layoutBindlessImage = true;
if (src.pervertexNV)
dst.pervertexNV = true;
if (src.pervertexEXT)
dst.pervertexEXT = true;
if (src.layoutHitObjectShaderRecordNV)
dst.layoutHitObjectShaderRecordNV = true;
}
}
// Do error layout error checking given a full variable/block declaration.
void TParseContext::layoutObjectCheck(const TSourceLoc& loc, const TSymbol& symbol)
{
const TType& type = symbol.getType();
const TQualifier& qualifier = type.getQualifier();
// first, cross check WRT to just the type
layoutTypeCheck(loc, type);
// now, any remaining error checking based on the object itself
if (qualifier.hasAnyLocation()) {
switch (qualifier.storage) {
case EvqUniform:
case EvqBuffer:
if (symbol.getAsVariable() == nullptr)
error(loc, "can only be used on variable declaration", "location", "");
break;
default:
break;
}
}
// user-variable location check, which are required for SPIR-V in/out:
// - variables have it directly,
// - blocks have it on each member (already enforced), so check first one
if (spvVersion.spv > 0 && !parsingBuiltins && qualifier.builtIn == EbvNone &&
!qualifier.hasLocation() && !intermediate.getAutoMapLocations()) {
switch (qualifier.storage) {
case EvqVaryingIn:
case EvqVaryingOut:
if (!type.getQualifier().isTaskMemory() &&
!type.getQualifier().hasSprivDecorate() &&
(type.getBasicType() != EbtBlock ||
(!(*type.getStruct())[0].type->getQualifier().hasLocation() &&
(*type.getStruct())[0].type->getQualifier().builtIn == EbvNone)))
error(loc, "SPIR-V requires location for user input/output", "location", "");
break;
default:
break;
}
}
// Check packing and matrix
if (qualifier.hasUniformLayout()) {
switch (qualifier.storage) {
case EvqUniform:
case EvqBuffer:
if (type.getBasicType() != EbtBlock) {
if (qualifier.hasMatrix())
error(loc, "cannot specify matrix layout on a variable declaration", "layout", "");
if (qualifier.hasPacking())
error(loc, "cannot specify packing on a variable declaration", "layout", "");
// "The offset qualifier can only be used on block members of blocks..."
if (qualifier.hasOffset() && !type.isAtomic())
error(loc, "cannot specify on a variable declaration", "offset", "");
// "The align qualifier can only be used on blocks or block members..."
if (qualifier.hasAlign())
error(loc, "cannot specify on a variable declaration", "align", "");
if (qualifier.isPushConstant())
error(loc, "can only specify on a uniform block", "push_constant", "");
if (qualifier.isShaderRecord())
error(loc, "can only specify on a buffer block", "shaderRecordNV", "");
if (qualifier.hasLocation() && type.isAtomic())
error(loc, "cannot specify on atomic counter", "location", "");
}
break;
default:
// these were already filtered by layoutTypeCheck() (or its callees)
break;
}
}
}
// "For some blocks declared as arrays, the location can only be applied at the block level:
// When a block is declared as an array where additional locations are needed for each member
// for each block array element, it is a compile-time error to specify locations on the block
// members. That is, when locations would be under specified by applying them on block members,
// they are not allowed on block members. For arrayed interfaces (those generally having an
// extra level of arrayness due to interface expansion), the outer array is stripped before
// applying this rule."
void TParseContext::layoutMemberLocationArrayCheck(const TSourceLoc& loc, bool memberWithLocation,
TArraySizes* arraySizes)
{
if (memberWithLocation && arraySizes != nullptr) {
if (arraySizes->getNumDims() > (currentBlockQualifier.isArrayedIo(language) ? 1 : 0))
error(loc, "cannot use in a block array where new locations are needed for each block element",
"location", "");
}
}
// Do layout error checking with respect to a type.
void TParseContext::layoutTypeCheck(const TSourceLoc& loc, const TType& type)
{
const TQualifier& qualifier = type.getQualifier();
// first, intra-layout qualifier-only error checking
layoutQualifierCheck(loc, qualifier);
// now, error checking combining type and qualifier
if (qualifier.hasAnyLocation()) {
if (qualifier.hasLocation()) {
if (qualifier.storage == EvqVaryingOut && language == EShLangFragment) {
if (qualifier.layoutLocation >= (unsigned int)resources.maxDrawBuffers)
error(loc, "too large for fragment output", "location", "");
}
}
if (qualifier.hasComponent()) {
// "It is a compile-time error if this sequence of components gets larger than 3."
if (qualifier.layoutComponent + type.getVectorSize() * (type.getBasicType() == EbtDouble ? 2 : 1) > 4)
error(loc, "type overflows the available 4 components", "component", "");
// "It is a compile-time error to apply the component qualifier to a matrix, a structure, a block, or an array containing any of these."
if (type.isMatrix() || type.getBasicType() == EbtBlock || type.getBasicType() == EbtStruct)
error(loc, "cannot apply to a matrix, structure, or block", "component", "");
// " It is a compile-time error to use component 1 or 3 as the beginning of a double or dvec2."
if (type.getBasicType() == EbtDouble)
if (qualifier.layoutComponent & 1)
error(loc, "doubles cannot start on an odd-numbered component", "component", "");
}
switch (qualifier.storage) {
case EvqVaryingIn:
case EvqVaryingOut:
if (type.getBasicType() == EbtBlock)
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, "location qualifier on in/out block");
if (type.getQualifier().isTaskMemory())
error(loc, "cannot apply to taskNV in/out blocks", "location", "");
break;
case EvqUniform:
case EvqBuffer:
if (type.getBasicType() == EbtBlock)
error(loc, "cannot apply to uniform or buffer block", "location", "");
else if (type.getBasicType() == EbtSampler && type.getSampler().isAttachmentEXT())
error(loc, "only applies to", "location", "%s with storage tileImageEXT", type.getBasicTypeString().c_str());
break;
case EvqtaskPayloadSharedEXT:
error(loc, "cannot apply to taskPayloadSharedEXT", "location", "");
break;
case EvqPayload:
case EvqPayloadIn:
case EvqHitAttr:
case EvqCallableData:
case EvqCallableDataIn:
case EvqHitObjectAttrNV:
case EvqSpirvStorageClass:
break;
case EvqTileImageEXT:
break;
default:
error(loc, "can only apply to uniform, buffer, in, or out storage qualifiers", "location", "");
break;
}
bool typeCollision;
int repeated = intermediate.addUsedLocation(qualifier, type, typeCollision);
if (repeated >= 0 && ! typeCollision)
error(loc, "overlapping use of location", "location", "%d", repeated);
// "fragment-shader outputs/tileImageEXT ... if two variables are placed within the same
// location, they must have the same underlying type (floating-point or integer)"
if (typeCollision && language == EShLangFragment && (qualifier.isPipeOutput() || qualifier.storage == EvqTileImageEXT))
error(loc, "fragment outputs or tileImageEXTs sharing the same location", "location", "%d must be the same basic type", repeated);
}
if (qualifier.hasXfbOffset() && qualifier.hasXfbBuffer()) {
if (type.isUnsizedArray()) {
error(loc, "unsized array", "xfb_offset", "in buffer %d", qualifier.layoutXfbBuffer);
} else {
int repeated = intermediate.addXfbBufferOffset(type);
if (repeated >= 0)
error(loc, "overlapping offsets at", "xfb_offset", "offset %d in buffer %d", repeated, qualifier.layoutXfbBuffer);
}
// "The offset must be a multiple of the size of the first component of the first
// qualified variable or block member, or a compile-time error results. Further, if applied to an aggregate
// containing a double or 64-bit integer, the offset must also be a multiple of 8..."
if ((type.containsBasicType(EbtDouble) || type.containsBasicType(EbtInt64) || type.containsBasicType(EbtUint64)) &&
! IsMultipleOfPow2(qualifier.layoutXfbOffset, 8))
error(loc, "type contains double or 64-bit integer; xfb_offset must be a multiple of 8", "xfb_offset", "");
else if ((type.containsBasicType(EbtBool) || type.containsBasicType(EbtFloat) ||
type.containsBasicType(EbtInt) || type.containsBasicType(EbtUint)) &&
! IsMultipleOfPow2(qualifier.layoutXfbOffset, 4))
error(loc, "must be a multiple of size of first component", "xfb_offset", "");
// ..., if applied to an aggregate containing a half float or 16-bit integer, the offset must also be a multiple of 2..."
else if ((type.contains16BitFloat() || type.containsBasicType(EbtInt16) || type.containsBasicType(EbtUint16)) &&
!IsMultipleOfPow2(qualifier.layoutXfbOffset, 2))
error(loc, "type contains half float or 16-bit integer; xfb_offset must be a multiple of 2", "xfb_offset", "");
}
if (qualifier.hasXfbStride() && qualifier.hasXfbBuffer()) {
if (! intermediate.setXfbBufferStride(qualifier.layoutXfbBuffer, qualifier.layoutXfbStride))
error(loc, "all stride settings must match for xfb buffer", "xfb_stride", "%d", qualifier.layoutXfbBuffer);
}
if (qualifier.hasBinding()) {
// Binding checking, from the spec:
//
// "If the binding point for any uniform or shader storage block instance is less than zero, or greater than or
// equal to the implementation-dependent maximum number of uniform buffer bindings, a compile-time
// error will occur. When the binding identifier is used with a uniform or shader storage block instanced as
// an array of size N, all elements of the array from binding through binding + N - 1 must be within this
// range."
//
if (!type.isOpaque() && type.getBasicType() != EbtBlock && type.getBasicType() != EbtSpirvType)
error(loc, "requires block, or sampler/image, or atomic-counter type", "binding", "");
if (type.getBasicType() == EbtSampler) {
int lastBinding = qualifier.layoutBinding;
if (type.isArray()) {
if (spvVersion.vulkan == 0) {
if (type.isSizedArray())
lastBinding += (type.getCumulativeArraySize() - 1);
else {
warn(loc, "assuming binding count of one for compile-time checking of binding numbers for unsized array", "[]", "");
}
}
}
if (spvVersion.vulkan == 0 && lastBinding >= resources.maxCombinedTextureImageUnits)
error(loc, "sampler binding not less than gl_MaxCombinedTextureImageUnits", "binding", type.isArray() ? "(using array)" : "");
}
if (type.isAtomic() && !spvVersion.vulkanRelaxed) {
if (qualifier.layoutBinding >= (unsigned int)resources.maxAtomicCounterBindings) {
error(loc, "atomic_uint binding is too large; see gl_MaxAtomicCounterBindings", "binding", "");
return;
}
}
} else if (!intermediate.getAutoMapBindings()) {
// some types require bindings
// atomic_uint
if (type.isAtomic())
error(loc, "layout(binding=X) is required", "atomic_uint", "");
// SPIR-V
if (spvVersion.spv > 0) {
if (qualifier.isUniformOrBuffer()) {
if (type.getBasicType() == EbtBlock && !qualifier.isPushConstant() &&
!qualifier.isShaderRecord() &&
!qualifier.hasAttachment() &&
!qualifier.hasBufferReference())
error(loc, "uniform/buffer blocks require layout(binding=X)", "binding", "");
else if (spvVersion.vulkan > 0 && type.getBasicType() == EbtSampler && !type.getSampler().isAttachmentEXT())
error(loc, "sampler/texture/image requires layout(binding=X)", "binding", "");
}
}
}
// some things can't have arrays of arrays
if (type.isArrayOfArrays()) {
if (spvVersion.vulkan > 0) {
if (type.isOpaque() || (type.getQualifier().isUniformOrBuffer() && type.getBasicType() == EbtBlock))
warn(loc, "Generating SPIR-V array-of-arrays, but Vulkan only supports single array level for this resource", "[][]", "");
}
}
// "The offset qualifier can only be used on block members of blocks..."
if (qualifier.hasOffset()) {
if (type.getBasicType() == EbtBlock)
error(loc, "only applies to block members, not blocks", "offset", "");
}
// Image format
if (qualifier.hasFormat()) {
if (! type.isImage() && !intermediate.getBindlessImageMode())
error(loc, "only apply to images", TQualifier::getLayoutFormatString(qualifier.getFormat()), "");
else {
if (type.getSampler().type == EbtFloat && qualifier.getFormat() > ElfFloatGuard)
error(loc, "does not apply to floating point images", TQualifier::getLayoutFormatString(qualifier.getFormat()), "");
if (type.getSampler().type == EbtInt && (qualifier.getFormat() < ElfFloatGuard || qualifier.getFormat() > ElfIntGuard))
error(loc, "does not apply to signed integer images", TQualifier::getLayoutFormatString(qualifier.getFormat()), "");
if (type.getSampler().type == EbtUint && qualifier.getFormat() < ElfIntGuard)
error(loc, "does not apply to unsigned integer images", TQualifier::getLayoutFormatString(qualifier.getFormat()), "");
if (isEsProfile()) {
// "Except for image variables qualified with the format qualifiers r32f, r32i, and r32ui, image variables must
// specify either memory qualifier readonly or the memory qualifier writeonly."
if (! (qualifier.getFormat() == ElfR32f || qualifier.getFormat() == ElfR32i || qualifier.getFormat() == ElfR32ui)) {
if (! qualifier.isReadOnly() && ! qualifier.isWriteOnly())
error(loc, "format requires readonly or writeonly memory qualifier", TQualifier::getLayoutFormatString(qualifier.getFormat()), "");
}
}
}
} else if (type.isImage() && ! qualifier.isWriteOnly() && !intermediate.getBindlessImageMode()) {
const char *explanation = "image variables not declared 'writeonly' and without a format layout qualifier";
requireProfile(loc, ECoreProfile | ECompatibilityProfile, explanation);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 0, E_GL_EXT_shader_image_load_formatted, explanation);
}
if (qualifier.isPushConstant()) {
if (type.getBasicType() != EbtBlock)
error(loc, "can only be used with a block", "push_constant", "");
if (type.isArray())
error(loc, "Push constants blocks can't be an array", "push_constant", "");
}
if (qualifier.hasBufferReference() && type.getBasicType() != EbtBlock)
error(loc, "can only be used with a block", "buffer_reference", "");
if (qualifier.isShaderRecord() && type.getBasicType() != EbtBlock)
error(loc, "can only be used with a block", "shaderRecordNV", "");
// input attachment
if (type.isSubpass()) {
if (extensionTurnedOn(E_GL_EXT_shader_tile_image))
error(loc, "can not be used with GL_EXT_shader_tile_image enabled", type.getSampler().getString().c_str(), "");
if (! qualifier.hasAttachment())
error(loc, "requires an input_attachment_index layout qualifier", "subpass", "");
} else {
if (qualifier.hasAttachment())
error(loc, "can only be used with a subpass", "input_attachment_index", "");
}
// specialization-constant id
if (qualifier.hasSpecConstantId()) {
if (type.getQualifier().storage != EvqConst)
error(loc, "can only be applied to 'const'-qualified scalar", "constant_id", "");
if (! type.isScalar())
error(loc, "can only be applied to a scalar", "constant_id", "");
switch (type.getBasicType())
{
case EbtInt8:
case EbtUint8:
case EbtInt16:
case EbtUint16:
case EbtInt:
case EbtUint:
case EbtInt64:
case EbtUint64:
case EbtBool:
case EbtFloat:
case EbtDouble:
case EbtFloat16:
break;
default:
error(loc, "cannot be applied to this type", "constant_id", "");
break;
}
}
}
static bool storageCanHaveLayoutInBlock(const enum TStorageQualifier storage)
{
switch (storage) {
case EvqUniform:
case EvqBuffer:
case EvqShared:
return true;
default:
return false;
}
}
// Do layout error checking that can be done within a layout qualifier proper, not needing to know
// if there are blocks, atomic counters, variables, etc.
void TParseContext::layoutQualifierCheck(const TSourceLoc& loc, const TQualifier& qualifier)
{
if (qualifier.storage == EvqShared && qualifier.hasLayout()) {
if (spvVersion.spv > 0 && spvVersion.spv < EShTargetSpv_1_4) {
error(loc, "shared block requires at least SPIR-V 1.4", "shared block", "");
}
profileRequires(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, 0, E_GL_EXT_shared_memory_block, "shared block");
}
// "It is a compile-time error to use *component* without also specifying the location qualifier (order does not matter)."
if (qualifier.hasComponent() && ! qualifier.hasLocation())
error(loc, "must specify 'location' to use 'component'", "component", "");
if (qualifier.hasAnyLocation()) {
// "As with input layout qualifiers, all shaders except compute shaders
// allow *location* layout qualifiers on output variable declarations,
// output block declarations, and output block member declarations."
switch (qualifier.storage) {
case EvqVaryingIn:
{
const char* feature = "location qualifier on input";
if (isEsProfile() && version < 310)
requireStage(loc, EShLangVertex, feature);
else
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, feature);
if (language == EShLangVertex) {
const char* exts[2] = { E_GL_ARB_separate_shader_objects, E_GL_ARB_explicit_attrib_location };
profileRequires(loc, ~EEsProfile, 330, 2, exts, feature);
profileRequires(loc, EEsProfile, 300, nullptr, feature);
} else {
profileRequires(loc, ~EEsProfile, 410, E_GL_ARB_separate_shader_objects, feature);
profileRequires(loc, EEsProfile, 310, nullptr, feature);
}
break;
}
case EvqVaryingOut:
{
const char* feature = "location qualifier on output";
if (isEsProfile() && version < 310)
requireStage(loc, EShLangFragment, feature);
else
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, feature);
if (language == EShLangFragment) {
const char* exts[2] = { E_GL_ARB_separate_shader_objects, E_GL_ARB_explicit_attrib_location };
profileRequires(loc, ~EEsProfile, 330, 2, exts, feature);
profileRequires(loc, EEsProfile, 300, nullptr, feature);
} else {
profileRequires(loc, ~EEsProfile, 410, E_GL_ARB_separate_shader_objects, feature);
profileRequires(loc, EEsProfile, 310, nullptr, feature);
}
break;
}
case EvqUniform:
case EvqBuffer:
{
const char* feature = "location qualifier on uniform or buffer";
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile | ENoProfile, feature);
profileRequires(loc, ~EEsProfile, 330, E_GL_ARB_explicit_attrib_location, feature);
profileRequires(loc, ~EEsProfile, 430, E_GL_ARB_explicit_uniform_location, feature);
profileRequires(loc, EEsProfile, 310, nullptr, feature);
break;
}
default:
break;
}
if (qualifier.hasIndex()) {
if (qualifier.storage != EvqVaryingOut)
error(loc, "can only be used on an output", "index", "");
if (! qualifier.hasLocation())
error(loc, "can only be used with an explicit location", "index", "");
}
}
if (qualifier.hasBinding()) {
if (! qualifier.isUniformOrBuffer() && !qualifier.isTaskMemory())
error(loc, "requires uniform or buffer storage qualifier", "binding", "");
}
if (qualifier.hasStream()) {
if (!qualifier.isPipeOutput())
error(loc, "can only be used on an output", "stream", "");
}
if (qualifier.hasXfb()) {
if (!qualifier.isPipeOutput())
error(loc, "can only be used on an output", "xfb layout qualifier", "");
}
if (qualifier.hasUniformLayout()) {
if (!storageCanHaveLayoutInBlock(qualifier.storage) && !qualifier.isTaskMemory()) {
if (qualifier.hasMatrix() || qualifier.hasPacking())
error(loc, "matrix or packing qualifiers can only be used on a uniform or buffer", "layout", "");
if (qualifier.hasOffset() || qualifier.hasAlign())
error(loc, "offset/align can only be used on a uniform or buffer", "layout", "");
}
}
if (qualifier.isPushConstant()) {
if (qualifier.storage != EvqUniform)
error(loc, "can only be used with a uniform", "push_constant", "");
if (qualifier.hasSet())
error(loc, "cannot be used with push_constant", "set", "");
if (qualifier.hasBinding())
error(loc, "cannot be used with push_constant", "binding", "");
}
if (qualifier.hasBufferReference()) {
if (qualifier.storage != EvqBuffer)
error(loc, "can only be used with buffer", "buffer_reference", "");
}
if (qualifier.isShaderRecord()) {
if (qualifier.storage != EvqBuffer)
error(loc, "can only be used with a buffer", "shaderRecordNV", "");
if (qualifier.hasBinding())
error(loc, "cannot be used with shaderRecordNV", "binding", "");
if (qualifier.hasSet())
error(loc, "cannot be used with shaderRecordNV", "set", "");
}
if (qualifier.storage == EvqTileImageEXT) {
if (qualifier.hasSet())
error(loc, "cannot be used with tileImageEXT", "set", "");
if (!qualifier.hasLocation())
error(loc, "can only be used with an explicit location", "tileImageEXT", "");
}
if (qualifier.storage == EvqHitAttr && qualifier.hasLayout()) {
error(loc, "cannot apply layout qualifiers to hitAttributeNV variable", "hitAttributeNV", "");
}
}
// For places that can't have shader-level layout qualifiers
void TParseContext::checkNoShaderLayouts(const TSourceLoc& loc, const TShaderQualifiers& shaderQualifiers)
{
const char* message = "can only apply to a standalone qualifier";
if (shaderQualifiers.geometry != ElgNone)
error(loc, message, TQualifier::getGeometryString(shaderQualifiers.geometry), "");
if (shaderQualifiers.spacing != EvsNone)
error(loc, message, TQualifier::getVertexSpacingString(shaderQualifiers.spacing), "");
if (shaderQualifiers.order != EvoNone)
error(loc, message, TQualifier::getVertexOrderString(shaderQualifiers.order), "");
if (shaderQualifiers.pointMode)
error(loc, message, "point_mode", "");
if (shaderQualifiers.invocations != TQualifier::layoutNotSet)
error(loc, message, "invocations", "");
for (int i = 0; i < 3; ++i) {
if (shaderQualifiers.localSize[i] > 1)
error(loc, message, "local_size", "");
if (shaderQualifiers.localSizeSpecId[i] != TQualifier::layoutNotSet)
error(loc, message, "local_size id", "");
}
if (shaderQualifiers.vertices != TQualifier::layoutNotSet) {
if (language == EShLangGeometry || language == EShLangMesh)
error(loc, message, "max_vertices", "");
else if (language == EShLangTessControl)
error(loc, message, "vertices", "");
else
assert(0);
}
if (shaderQualifiers.earlyFragmentTests)
error(loc, message, "early_fragment_tests", "");
if (shaderQualifiers.postDepthCoverage)
error(loc, message, "post_depth_coverage", "");
if (shaderQualifiers.nonCoherentColorAttachmentReadEXT)
error(loc, message, "non_coherent_color_attachment_readEXT", "");
if (shaderQualifiers.nonCoherentDepthAttachmentReadEXT)
error(loc, message, "non_coherent_depth_attachment_readEXT", "");
if (shaderQualifiers.nonCoherentStencilAttachmentReadEXT)
error(loc, message, "non_coherent_stencil_attachment_readEXT", "");
if (shaderQualifiers.primitives != TQualifier::layoutNotSet) {
if (language == EShLangMesh)
error(loc, message, "max_primitives", "");
else
assert(0);
}
if (shaderQualifiers.hasBlendEquation())
error(loc, message, "blend equation", "");
if (shaderQualifiers.numViews != TQualifier::layoutNotSet)
error(loc, message, "num_views", "");
if (shaderQualifiers.interlockOrdering != EioNone)
error(loc, message, TQualifier::getInterlockOrderingString(shaderQualifiers.interlockOrdering), "");
if (shaderQualifiers.layoutPrimitiveCulling)
error(loc, "can only be applied as standalone", "primitive_culling", "");
}
// Correct and/or advance an object's offset layout qualifier.
void TParseContext::fixOffset(const TSourceLoc& loc, TSymbol& symbol)
{
const TQualifier& qualifier = symbol.getType().getQualifier();
if (symbol.getType().isAtomic()) {
if (qualifier.hasBinding() && (int)qualifier.layoutBinding < resources.maxAtomicCounterBindings) {
// Set the offset
int offset;
if (qualifier.hasOffset())
offset = qualifier.layoutOffset;
else
offset = atomicUintOffsets[qualifier.layoutBinding];
if (offset % 4 != 0)
error(loc, "atomic counters offset should align based on 4:", "offset", "%d", offset);
symbol.getWritableType().getQualifier().layoutOffset = offset;
// Check for overlap
int numOffsets = 4;
if (symbol.getType().isArray()) {
if (symbol.getType().isSizedArray() && !symbol.getType().getArraySizes()->isInnerUnsized())
numOffsets *= symbol.getType().getCumulativeArraySize();
else {
// "It is a compile-time error to declare an unsized array of atomic_uint."
error(loc, "array must be explicitly sized", "atomic_uint", "");
}
}
int repeated = intermediate.addUsedOffsets(qualifier.layoutBinding, offset, numOffsets);
if (repeated >= 0)
error(loc, "atomic counters sharing the same offset:", "offset", "%d", repeated);
// Bump the default offset
atomicUintOffsets[qualifier.layoutBinding] = offset + numOffsets;
}
}
}
//
// Look up a function name in the symbol table, and make sure it is a function.
//
// Return the function symbol if found, otherwise nullptr.
//
const TFunction* TParseContext::findFunction(const TSourceLoc& loc, const TFunction& call, bool& builtIn)
{
if (symbolTable.isFunctionNameVariable(call.getName())) {
error(loc, "can't use function syntax on variable", call.getName().c_str(), "");
return nullptr;
}
const TFunction* function = nullptr;
// debugPrintfEXT has var args and is in the symbol table as "debugPrintfEXT()",
// mangled to "debugPrintfEXT("
if (call.getName() == "debugPrintfEXT") {
TSymbol* symbol = symbolTable.find("debugPrintfEXT(", &builtIn);
if (symbol)
return symbol->getAsFunction();
}
bool explicitTypesEnabled = extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_int8) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_int16) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_int32) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_int64) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_float16) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_float32) ||
extensionTurnedOn(E_GL_EXT_shader_explicit_arithmetic_types_float64);
if (isEsProfile())
function = (explicitTypesEnabled && version >= 310)
? findFunctionExplicitTypes(loc, call, builtIn)
: ((extensionTurnedOn(E_GL_EXT_shader_implicit_conversions) && version >= 310)
? findFunction120(loc, call, builtIn)
: findFunctionExact(loc, call, builtIn));
else if (version < 120)
function = findFunctionExact(loc, call, builtIn);
else if (version < 400) {
bool needfindFunction400 = extensionTurnedOn(E_GL_ARB_gpu_shader_fp64) || extensionTurnedOn(E_GL_ARB_gpu_shader5);
function = needfindFunction400 ? findFunction400(loc, call, builtIn) : findFunction120(loc, call, builtIn);
}
else if (explicitTypesEnabled)
function = findFunctionExplicitTypes(loc, call, builtIn);
else
function = findFunction400(loc, call, builtIn);
return function;
}
// Function finding algorithm for ES and desktop 110.
const TFunction* TParseContext::findFunctionExact(const TSourceLoc& loc, const TFunction& call, bool& builtIn)
{
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol == nullptr) {
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
return nullptr;
}
return symbol->getAsFunction();
}
// Function finding algorithm for desktop versions 120 through 330.
const TFunction* TParseContext::findFunction120(const TSourceLoc& loc, const TFunction& call, bool& builtIn)
{
// first, look for an exact match
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol)
return symbol->getAsFunction();
// exact match not found, look through a list of overloaded functions of the same name
// "If no exact match is found, then [implicit conversions] will be applied to find a match. Mismatched types
// on input parameters (in or inout or default) must have a conversion from the calling argument type to the
// formal parameter type. Mismatched types on output parameters (out or inout) must have a conversion
// from the formal parameter type to the calling argument type. When argument conversions are used to find
// a match, it is a semantic error if there are multiple ways to apply these conversions to make the call match
// more than one function."
const TFunction* candidate = nullptr;
TVector<const TFunction*> candidateList;
symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
for (auto it = candidateList.begin(); it != candidateList.end(); ++it) {
const TFunction& function = *(*it);
// to even be a potential match, number of arguments has to match
if (call.getParamCount() != function.getParamCount())
continue;
bool possibleMatch = true;
for (int i = 0; i < function.getParamCount(); ++i) {
// same types is easy
if (*function[i].type == *call[i].type)
continue;
// We have a mismatch in type, see if it is implicitly convertible
if (function[i].type->isArray() || call[i].type->isArray() ||
! function[i].type->sameElementShape(*call[i].type))
possibleMatch = false;
else {
// do direction-specific checks for conversion of basic type
if (function[i].type->getQualifier().isParamInput()) {
if (! intermediate.canImplicitlyPromote(call[i].type->getBasicType(), function[i].type->getBasicType()))
possibleMatch = false;
}
if (function[i].type->getQualifier().isParamOutput()) {
if (! intermediate.canImplicitlyPromote(function[i].type->getBasicType(), call[i].type->getBasicType()))
possibleMatch = false;
}
}
if (! possibleMatch)
break;
}
if (possibleMatch) {
if (candidate) {
// our second match, meaning ambiguity
error(loc, "ambiguous function signature match: multiple signatures match under implicit type conversion", call.getName().c_str(), "");
} else
candidate = &function;
}
}
if (candidate == nullptr)
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
return candidate;
}
// Function finding algorithm for desktop version 400 and above.
//
// "When function calls are resolved, an exact type match for all the arguments
// is sought. If an exact match is found, all other functions are ignored, and
// the exact match is used. If no exact match is found, then the implicit
// conversions in section 4.1.10 Implicit Conversions will be applied to find
// a match. Mismatched types on input parameters (in or inout or default) must
// have a conversion from the calling argument type to the formal parameter type.
// Mismatched types on output parameters (out or inout) must have a conversion
// from the formal parameter type to the calling argument type.
//
// "If implicit conversions can be used to find more than one matching function,
// a single best-matching function is sought. To determine a best match, the
// conversions between calling argument and formal parameter types are compared
// for each function argument and pair of matching functions. After these
// comparisons are performed, each pair of matching functions are compared.
// A function declaration A is considered a better match than function
// declaration B if
//
// * for at least one function argument, the conversion for that argument in A
// is better than the corresponding conversion in B; and
// * there is no function argument for which the conversion in B is better than
// the corresponding conversion in A.
//
// "If a single function declaration is considered a better match than every
// other matching function declaration, it will be used. Otherwise, a
// compile-time semantic error for an ambiguous overloaded function call occurs.
//
// "To determine whether the conversion for a single argument in one match is
// better than that for another match, the following rules are applied, in order:
//
// 1. An exact match is better than a match involving any implicit conversion.
// 2. A match involving an implicit conversion from float to double is better
// than a match involving any other implicit conversion.
// 3. A match involving an implicit conversion from either int or uint to float
// is better than a match involving an implicit conversion from either int
// or uint to double.
//
// "If none of the rules above apply to a particular pair of conversions, neither
// conversion is considered better than the other."
//
const TFunction* TParseContext::findFunction400(const TSourceLoc& loc, const TFunction& call, bool& builtIn)
{
// first, look for an exact match
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol)
return symbol->getAsFunction();
// no exact match, use the generic selector, parameterized by the GLSL rules
// create list of candidates to send
TVector<const TFunction*> candidateList;
symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
// can 'from' convert to 'to'?
const auto convertible = [this,builtIn](const TType& from, const TType& to, TOperator, int) -> bool {
if (from == to)
return true;
if (from.coopMatParameterOK(to))
return true;
// Allow a sized array to be passed through an unsized array parameter, for coopMatLoad/Store functions
if (builtIn && from.isArray() && to.isUnsizedArray()) {
TType fromElementType(from, 0);
TType toElementType(to, 0);
if (fromElementType == toElementType)
return true;
}
if (from.isArray() || to.isArray() || ! from.sameElementShape(to))
return false;
if (from.isCoopMat() && to.isCoopMat())
return from.sameCoopMatBaseType(to);
return intermediate.canImplicitlyPromote(from.getBasicType(), to.getBasicType());
};
// Is 'to2' a better conversion than 'to1'?
// Ties should not be considered as better.
// Assumes 'convertible' already said true.
const auto better = [](const TType& from, const TType& to1, const TType& to2) -> bool {
// 1. exact match
if (from == to2)
return from != to1;
if (from == to1)
return false;
// 2. float -> double is better
if (from.getBasicType() == EbtFloat) {
if (to2.getBasicType() == EbtDouble && to1.getBasicType() != EbtDouble)
return true;
}
// 3. -> float is better than -> double
return to2.getBasicType() == EbtFloat && to1.getBasicType() == EbtDouble;
};
// for ambiguity reporting
bool tie = false;
// send to the generic selector
const TFunction* bestMatch = selectFunction(candidateList, call, convertible, better, tie);
if (bestMatch == nullptr)
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
else if (tie)
error(loc, "ambiguous best function under implicit type conversion", call.getName().c_str(), "");
return bestMatch;
}
// "To determine whether the conversion for a single argument in one match
// is better than that for another match, the conversion is assigned of the
// three ranks ordered from best to worst:
// 1. Exact match: no conversion.
// 2. Promotion: integral or floating-point promotion.
// 3. Conversion: integral conversion, floating-point conversion,
// floating-integral conversion.
// A conversion C1 is better than a conversion C2 if the rank of C1 is
// better than the rank of C2."
const TFunction* TParseContext::findFunctionExplicitTypes(const TSourceLoc& loc, const TFunction& call, bool& builtIn)
{
// first, look for an exact match
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol)
return symbol->getAsFunction();
// no exact match, use the generic selector, parameterized by the GLSL rules
// create list of candidates to send
TVector<const TFunction*> candidateList;
symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
// can 'from' convert to 'to'?
const auto convertible = [this,builtIn](const TType& from, const TType& to, TOperator, int) -> bool {
if (from == to)
return true;
if (from.coopMatParameterOK(to))
return true;
// Allow a sized array to be passed through an unsized array parameter, for coopMatLoad/Store functions
if (builtIn && from.isArray() && to.isUnsizedArray()) {
TType fromElementType(from, 0);
TType toElementType(to, 0);
if (fromElementType == toElementType)
return true;
}
if (from.isArray() || to.isArray() || ! from.sameElementShape(to))
return false;
if (from.isCoopMat() && to.isCoopMat())
return from.sameCoopMatBaseType(to);
return intermediate.canImplicitlyPromote(from.getBasicType(), to.getBasicType());
};
// Is 'to2' a better conversion than 'to1'?
// Ties should not be considered as better.
// Assumes 'convertible' already said true.
const auto better = [this](const TType& from, const TType& to1, const TType& to2) -> bool {
// 1. exact match
if (from == to2)
return from != to1;
if (from == to1)
return false;
// 2. Promotion (integral, floating-point) is better
TBasicType from_type = from.getBasicType();
TBasicType to1_type = to1.getBasicType();
TBasicType to2_type = to2.getBasicType();
bool isPromotion1 = (intermediate.isIntegralPromotion(from_type, to1_type) ||
intermediate.isFPPromotion(from_type, to1_type));
bool isPromotion2 = (intermediate.isIntegralPromotion(from_type, to2_type) ||
intermediate.isFPPromotion(from_type, to2_type));
if (isPromotion2)
return !isPromotion1;
if(isPromotion1)
return false;
// 3. Conversion (integral, floating-point , floating-integral)
bool isConversion1 = (intermediate.isIntegralConversion(from_type, to1_type) ||
intermediate.isFPConversion(from_type, to1_type) ||
intermediate.isFPIntegralConversion(from_type, to1_type));
bool isConversion2 = (intermediate.isIntegralConversion(from_type, to2_type) ||
intermediate.isFPConversion(from_type, to2_type) ||
intermediate.isFPIntegralConversion(from_type, to2_type));
return isConversion2 && !isConversion1;
};
// for ambiguity reporting
bool tie = false;
// send to the generic selector
const TFunction* bestMatch = selectFunction(candidateList, call, convertible, better, tie);
if (bestMatch == nullptr)
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
else if (tie)
error(loc, "ambiguous best function under implicit type conversion", call.getName().c_str(), "");
return bestMatch;
}
//
// Adjust function calls that aren't declared in Vulkan to a
// calls with equivalent effects
//
TIntermTyped* TParseContext::vkRelaxedRemapFunctionCall(const TSourceLoc& loc, TFunction* function, TIntermNode* arguments)
{
TIntermTyped* result = nullptr;
if (function->getBuiltInOp() != EOpNull) {
return nullptr;
}
if (function->getName() == "atomicCounterIncrement") {
// change atomicCounterIncrement into an atomicAdd of 1
TString name("atomicAdd");
TType uintType(EbtUint);
TFunction realFunc(&name, function->getType());
// Use copyParam to avoid shared ownership of the 'type' field
// of the parameter.
for (int i = 0; i < function->getParamCount(); ++i) {
realFunc.addParameter(TParameter().copyParam((*function)[i]));
}
TParameter tmpP = { nullptr, &uintType };
realFunc.addParameter(TParameter().copyParam(tmpP));
arguments = intermediate.growAggregate(arguments, intermediate.addConstantUnion(1, loc, true));
result = handleFunctionCall(loc, &realFunc, arguments);
} else if (function->getName() == "atomicCounterDecrement") {
// change atomicCounterDecrement into an atomicAdd with -1
// and subtract 1 from result, to return post-decrement value
TString name("atomicAdd");
TType uintType(EbtUint);
TFunction realFunc(&name, function->getType());
for (int i = 0; i < function->getParamCount(); ++i) {
realFunc.addParameter(TParameter().copyParam((*function)[i]));
}
TParameter tmpP = { nullptr, &uintType };
realFunc.addParameter(TParameter().copyParam(tmpP));
arguments = intermediate.growAggregate(arguments, intermediate.addConstantUnion(-1, loc, true));
result = handleFunctionCall(loc, &realFunc, arguments);
// post decrement, so that it matches AtomicCounterDecrement semantics
if (result) {
result = handleBinaryMath(loc, "-", EOpSub, result, intermediate.addConstantUnion(1, loc, true));
}
} else if (function->getName() == "atomicCounter") {
// change atomicCounter into a direct read of the variable
if (arguments->getAsTyped()) {
result = arguments->getAsTyped();
}
}
return result;
}
// When a declaration includes a type, but not a variable name, it can be used
// to establish defaults.
void TParseContext::declareTypeDefaults(const TSourceLoc& loc, const TPublicType& publicType)
{
if (publicType.basicType == EbtAtomicUint && publicType.qualifier.hasBinding()) {
if (publicType.qualifier.layoutBinding >= (unsigned int)resources.maxAtomicCounterBindings) {
error(loc, "atomic_uint binding is too large", "binding", "");
return;
}
if (publicType.qualifier.hasOffset())
atomicUintOffsets[publicType.qualifier.layoutBinding] = publicType.qualifier.layoutOffset;
return;
}
if (publicType.arraySizes) {
error(loc, "expect an array name", "", "");
}
if (publicType.qualifier.hasLayout() && !publicType.qualifier.hasBufferReference())
warn(loc, "useless application of layout qualifier", "layout", "");
}
void TParseContext::coopMatTypeParametersCheck(const TSourceLoc& loc, const TPublicType& publicType)
{
if (parsingBuiltins)
return;
if (publicType.isCoopmatKHR()) {
if (publicType.typeParameters == nullptr) {
error(loc, "coopmat missing type parameters", "", "");
return;
}
switch (publicType.typeParameters->basicType) {
case EbtFloat:
case EbtFloat16:
case EbtInt:
case EbtInt8:
case EbtInt16:
case EbtUint:
case EbtUint8:
case EbtUint16:
break;
default:
error(loc, "coopmat invalid basic type", TType::getBasicString(publicType.typeParameters->basicType), "");
break;
}
if (publicType.typeParameters->arraySizes->getNumDims() != 4) {
error(loc, "coopmat incorrect number of type parameters", "", "");
return;
}
int use = publicType.typeParameters->arraySizes->getDimSize(3);
if (use < 0 || use > 2) {
error(loc, "coopmat invalid matrix Use", "", "");
return;
}
}
}
bool TParseContext::vkRelaxedRemapUniformVariable(const TSourceLoc& loc, TString& identifier, const TPublicType&,
TArraySizes*, TIntermTyped* initializer, TType& type)
{
if (parsingBuiltins || symbolTable.atBuiltInLevel() || !symbolTable.atGlobalLevel() ||
type.getQualifier().storage != EvqUniform ||
!(type.containsNonOpaque()|| type.getBasicType() == EbtAtomicUint)) {
return false;
}
if (type.getQualifier().hasLocation()) {
warn(loc, "ignoring layout qualifier for uniform", identifier.c_str(), "location");
type.getQualifier().layoutLocation = TQualifier::layoutLocationEnd;
}
if (initializer) {
warn(loc, "Ignoring initializer for uniform", identifier.c_str(), "");
initializer = nullptr;
}
if (type.isArray()) {
// do array size checks here
arraySizesCheck(loc, type.getQualifier(), type.getArraySizes(), initializer, false);
if (arrayQualifierError(loc, type.getQualifier()) || arrayError(loc, type)) {
error(loc, "array param error", identifier.c_str(), "");
}
}
// do some checking on the type as it was declared
layoutTypeCheck(loc, type);
int bufferBinding = TQualifier::layoutBindingEnd;
TVariable* updatedBlock = nullptr;
// Convert atomic_uint into members of a buffer block
if (type.isAtomic()) {
type.setBasicType(EbtUint);
type.getQualifier().storage = EvqBuffer;
type.getQualifier().volatil = true;
type.getQualifier().coherent = true;
// xxTODO: use logic from fixOffset() to apply explicit member offset
bufferBinding = type.getQualifier().layoutBinding;
type.getQualifier().layoutBinding = TQualifier::layoutBindingEnd;
type.getQualifier().explicitOffset = false;
growAtomicCounterBlock(bufferBinding, loc, type, identifier, nullptr);
updatedBlock = atomicCounterBuffers[bufferBinding];
}
if (!updatedBlock) {
growGlobalUniformBlock(loc, type, identifier, nullptr);
updatedBlock = globalUniformBlock;
}
//
// don't assign explicit member offsets here
// if any are assigned, need to be updated here and in the merge/link step
// fixBlockUniformOffsets(updatedBlock->getWritableType().getQualifier(), *updatedBlock->getWritableType().getWritableStruct());
// checks on update buffer object
layoutObjectCheck(loc, *updatedBlock);
TSymbol* symbol = symbolTable.find(identifier);
if (!symbol) {
if (updatedBlock == globalUniformBlock)
error(loc, "error adding uniform to default uniform block", identifier.c_str(), "");
else
error(loc, "error adding atomic counter to atomic counter block", identifier.c_str(), "");
return false;
}
// merge qualifiers
mergeObjectLayoutQualifiers(updatedBlock->getWritableType().getQualifier(), type.getQualifier(), true);
return true;
}
//
// Do everything necessary to handle a variable (non-block) declaration.
// Either redeclaring a variable, or making a new one, updating the symbol
// table, and all error checking.
//
// Returns a subtree node that computes an initializer, if needed.
// Returns nullptr if there is no code to execute for initialization.
//
// 'publicType' is the type part of the declaration (to the left)
// 'arraySizes' is the arrayness tagged on the identifier (to the right)
//
TIntermNode* TParseContext::declareVariable(const TSourceLoc& loc, TString& identifier, const TPublicType& publicType,
TArraySizes* arraySizes, TIntermTyped* initializer)
{
// Make a fresh type that combines the characteristics from the individual
// identifier syntax and the declaration-type syntax.
TType type(publicType);
type.transferArraySizes(arraySizes);
type.copyArrayInnerSizes(publicType.arraySizes);
arrayOfArrayVersionCheck(loc, type.getArraySizes());
if (initializer) {
if (type.getBasicType() == EbtRayQuery) {
error(loc, "ray queries can only be initialized by using the rayQueryInitializeEXT intrinsic:", "=", identifier.c_str());
} else if (type.getBasicType() == EbtHitObjectNV) {
error(loc, "hit objects cannot be initialized using initializers", "=", identifier.c_str());
}
}
if (type.isCoopMatKHR()) {
intermediate.setUseVulkanMemoryModel();
intermediate.setUseStorageBuffer();
if (!publicType.typeParameters || !publicType.typeParameters->arraySizes ||
publicType.typeParameters->arraySizes->getNumDims() != 3) {
error(loc, "unexpected number type parameters", identifier.c_str(), "");
}
if (publicType.typeParameters) {
if (!isTypeFloat(publicType.typeParameters->basicType) && !isTypeInt(publicType.typeParameters->basicType)) {
error(loc, "expected 8, 16, 32, or 64 bit signed or unsigned integer or 16, 32, or 64 bit float type", identifier.c_str(), "");
}
}
}
else if (type.isCoopMatNV()) {
intermediate.setUseVulkanMemoryModel();
intermediate.setUseStorageBuffer();
if (!publicType.typeParameters || publicType.typeParameters->arraySizes->getNumDims() != 4) {
error(loc, "expected four type parameters", identifier.c_str(), "");
}
if (publicType.typeParameters) {
if (isTypeFloat(publicType.basicType) &&
publicType.typeParameters->arraySizes->getDimSize(0) != 16 &&
publicType.typeParameters->arraySizes->getDimSize(0) != 32 &&
publicType.typeParameters->arraySizes->getDimSize(0) != 64) {
error(loc, "expected 16, 32, or 64 bits for first type parameter", identifier.c_str(), "");
}
if (isTypeInt(publicType.basicType) &&
publicType.typeParameters->arraySizes->getDimSize(0) != 8 &&
publicType.typeParameters->arraySizes->getDimSize(0) != 16 &&
publicType.typeParameters->arraySizes->getDimSize(0) != 32) {
error(loc, "expected 8, 16, or 32 bits for first type parameter", identifier.c_str(), "");
}
}
} else {
if (publicType.typeParameters && publicType.typeParameters->arraySizes->getNumDims() != 0) {
error(loc, "unexpected type parameters", identifier.c_str(), "");
}
}
if (voidErrorCheck(loc, identifier, type.getBasicType()))
return nullptr;
if (initializer)
rValueErrorCheck(loc, "initializer", initializer);
else
nonInitConstCheck(loc, identifier, type);
samplerCheck(loc, type, identifier, initializer);
transparentOpaqueCheck(loc, type, identifier);
atomicUintCheck(loc, type, identifier);
accStructCheck(loc, type, identifier);
checkAndResizeMeshViewDim(loc, type, /*isBlockMember*/ false);
if (type.getQualifier().storage == EvqConst && type.containsReference()) {
error(loc, "variables with reference type can't have qualifier 'const'", "qualifier", "");
}
if (type.getQualifier().storage != EvqUniform && type.getQualifier().storage != EvqBuffer) {
if (type.contains16BitFloat())
requireFloat16Arithmetic(loc, "qualifier", "float16 types can only be in uniform block or buffer storage");
if (type.contains16BitInt())
requireInt16Arithmetic(loc, "qualifier", "(u)int16 types can only be in uniform block or buffer storage");
if (type.contains8BitInt())
requireInt8Arithmetic(loc, "qualifier", "(u)int8 types can only be in uniform block or buffer storage");
}
if (type.getQualifier().storage == EvqtaskPayloadSharedEXT)
intermediate.addTaskPayloadEXTCount();
if (type.getQualifier().storage == EvqShared && type.containsCoopMat())
error(loc, "qualifier", "Cooperative matrix types must not be used in shared memory", "");
if (profile == EEsProfile) {
if (type.getQualifier().isPipeInput() && type.getBasicType() == EbtStruct) {
if (type.getQualifier().isArrayedIo(language)) {
TType perVertexType(type, 0);
if (perVertexType.containsArray() && perVertexType.containsBuiltIn() == false) {
error(loc, "A per vertex structure containing an array is not allowed as input in ES", type.getTypeName().c_str(), "");
}
}
else if (type.containsArray() && type.containsBuiltIn() == false) {
error(loc, "A structure containing an array is not allowed as input in ES", type.getTypeName().c_str(), "");
}
if (type.containsStructure())
error(loc, "A structure containing an struct is not allowed as input in ES", type.getTypeName().c_str(), "");
}
}
if (identifier != "gl_FragCoord" && (publicType.shaderQualifiers.originUpperLeft || publicType.shaderQualifiers.pixelCenterInteger))
error(loc, "can only apply origin_upper_left and pixel_center_origin to gl_FragCoord", "layout qualifier", "");
if (identifier != "gl_FragDepth" && publicType.shaderQualifiers.getDepth() != EldNone)
error(loc, "can only apply depth layout to gl_FragDepth", "layout qualifier", "");
if (identifier != "gl_FragStencilRefARB" && publicType.shaderQualifiers.getStencil() != ElsNone)
error(loc, "can only apply depth layout to gl_FragStencilRefARB", "layout qualifier", "");
// Check for redeclaration of built-ins and/or attempting to declare a reserved name
TSymbol* symbol = redeclareBuiltinVariable(loc, identifier, type.getQualifier(), publicType.shaderQualifiers);
if (symbol == nullptr)
reservedErrorCheck(loc, identifier);
if (symbol == nullptr && spvVersion.vulkan > 0 && spvVersion.vulkanRelaxed) {
bool remapped = vkRelaxedRemapUniformVariable(loc, identifier, publicType, arraySizes, initializer, type);
if (remapped) {
return nullptr;
}
}
inheritGlobalDefaults(type.getQualifier());
// Declare the variable
if (type.isArray()) {
// Check that implicit sizing is only where allowed.
arraySizesCheck(loc, type.getQualifier(), type.getArraySizes(), initializer, false);
if (! arrayQualifierError(loc, type.getQualifier()) && ! arrayError(loc, type))
declareArray(loc, identifier, type, symbol);
if (initializer) {
profileRequires(loc, ENoProfile, 120, E_GL_3DL_array_objects, "initializer");
profileRequires(loc, EEsProfile, 300, nullptr, "initializer");
}
} else {
// non-array case
if (symbol == nullptr)
symbol = declareNonArray(loc, identifier, type);
else if (type != symbol->getType())
error(loc, "cannot change the type of", "redeclaration", symbol->getName().c_str());
}
if (symbol == nullptr)
return nullptr;
// Deal with initializer
TIntermNode* initNode = nullptr;
if (symbol != nullptr && initializer) {
TVariable* variable = symbol->getAsVariable();
if (! variable) {
error(loc, "initializer requires a variable, not a member", identifier.c_str(), "");
return nullptr;
}
initNode = executeInitializer(loc, initializer, variable);
}
// look for errors in layout qualifier use
layoutObjectCheck(loc, *symbol);
// fix up
fixOffset(loc, *symbol);
return initNode;
}
// Pick up global defaults from the provide global defaults into dst.
void TParseContext::inheritGlobalDefaults(TQualifier& dst) const
{
if (dst.storage == EvqVaryingOut) {
if (! dst.hasStream() && language == EShLangGeometry)
dst.layoutStream = globalOutputDefaults.layoutStream;
if (! dst.hasXfbBuffer())
dst.layoutXfbBuffer = globalOutputDefaults.layoutXfbBuffer;
}
}
//
// Make an internal-only variable whose name is for debug purposes only
// and won't be searched for. Callers will only use the return value to use
// the variable, not the name to look it up. It is okay if the name
// is the same as other names; there won't be any conflict.
//
TVariable* TParseContext::makeInternalVariable(const char* name, const TType& type) const
{
TString* nameString = NewPoolTString(name);
TVariable* variable = new TVariable(nameString, type);
symbolTable.makeInternalVariable(*variable);
return variable;
}
//
// Declare a non-array variable, the main point being there is no redeclaration
// for resizing allowed.
//
// Return the successfully declared variable.
//
TVariable* TParseContext::declareNonArray(const TSourceLoc& loc, const TString& identifier, const TType& type)
{
// make a new variable
TVariable* variable = new TVariable(&identifier, type);
ioArrayCheck(loc, type, identifier);
// add variable to symbol table
if (symbolTable.insert(*variable)) {
if (symbolTable.atGlobalLevel())
trackLinkage(*variable);
return variable;
}
error(loc, "redefinition", variable->getName().c_str(), "");
return nullptr;
}
//
// Handle all types of initializers from the grammar.
//
// Returning nullptr just means there is no code to execute to handle the
// initializer, which will, for example, be the case for constant initializers.
//
TIntermNode* TParseContext::executeInitializer(const TSourceLoc& loc, TIntermTyped* initializer, TVariable* variable)
{
// A null initializer is an aggregate that hasn't had an op assigned yet
// (still EOpNull, no relation to nullInit), and has no children.
bool nullInit = initializer->getAsAggregate() && initializer->getAsAggregate()->getOp() == EOpNull &&
initializer->getAsAggregate()->getSequence().size() == 0;
//
// Identifier must be of type constant, a global, or a temporary, and
// starting at version 120, desktop allows uniforms to have initializers.
//
TStorageQualifier qualifier = variable->getType().getQualifier().storage;
if (! (qualifier == EvqTemporary || qualifier == EvqGlobal || qualifier == EvqConst ||
(qualifier == EvqUniform && !isEsProfile() && version >= 120))) {
if (qualifier == EvqShared) {
// GL_EXT_null_initializer allows this for shared, if it's a null initializer
if (nullInit) {
const char* feature = "initialization with shared qualifier";
profileRequires(loc, EEsProfile, 0, E_GL_EXT_null_initializer, feature);
profileRequires(loc, ~EEsProfile, 0, E_GL_EXT_null_initializer, feature);
} else {
error(loc, "initializer can only be a null initializer ('{}')", "shared", "");
}
} else {
error(loc, " cannot initialize this type of qualifier ",
variable->getType().getStorageQualifierString(), "");
return nullptr;
}
}
if (nullInit) {
// only some types can be null initialized
if (variable->getType().containsUnsizedArray()) {
error(loc, "null initializers can't size unsized arrays", "{}", "");
return nullptr;
}
if (variable->getType().containsOpaque()) {
error(loc, "null initializers can't be used on opaque values", "{}", "");
return nullptr;
}
variable->getWritableType().getQualifier().setNullInit();
return nullptr;
}
arrayObjectCheck(loc, variable->getType(), "array initializer");
//
// If the initializer was from braces { ... }, we convert the whole subtree to a
// constructor-style subtree, allowing the rest of the code to operate
// identically for both kinds of initializers.
//
// Type can't be deduced from the initializer list, so a skeletal type to
// follow has to be passed in. Constness and specialization-constness
// should be deduced bottom up, not dictated by the skeletal type.
//
TType skeletalType;
skeletalType.shallowCopy(variable->getType());
skeletalType.getQualifier().makeTemporary();
initializer = convertInitializerList(loc, skeletalType, initializer);
if (! initializer) {
// error recovery; don't leave const without constant values
if (qualifier == EvqConst)
variable->getWritableType().getQualifier().makeTemporary();
return nullptr;
}
// Fix outer arrayness if variable is unsized, getting size from the initializer
if (initializer->getType().isSizedArray() && variable->getType().isUnsizedArray())
variable->getWritableType().changeOuterArraySize(initializer->getType().getOuterArraySize());
// Inner arrayness can also get set by an initializer
if (initializer->getType().isArrayOfArrays() && variable->getType().isArrayOfArrays() &&
initializer->getType().getArraySizes()->getNumDims() ==
variable->getType().getArraySizes()->getNumDims()) {
// adopt unsized sizes from the initializer's sizes
for (int d = 1; d < variable->getType().getArraySizes()->getNumDims(); ++d) {
if (variable->getType().getArraySizes()->getDimSize(d) == UnsizedArraySize) {
variable->getWritableType().getArraySizes()->setDimSize(d,
initializer->getType().getArraySizes()->getDimSize(d));
}
}
}
// Uniforms require a compile-time constant initializer
if (qualifier == EvqUniform && ! initializer->getType().getQualifier().isFrontEndConstant()) {
error(loc, "uniform initializers must be constant", "=", "'%s'",
variable->getType().getCompleteString(intermediate.getEnhancedMsgs()).c_str());
variable->getWritableType().getQualifier().makeTemporary();
return nullptr;
}
// Global consts require a constant initializer (specialization constant is okay)
if (qualifier == EvqConst && symbolTable.atGlobalLevel() && ! initializer->getType().getQualifier().isConstant()) {
error(loc, "global const initializers must be constant", "=", "'%s'",
variable->getType().getCompleteString(intermediate.getEnhancedMsgs()).c_str());
variable->getWritableType().getQualifier().makeTemporary();
return nullptr;
}
// Const variables require a constant initializer, depending on version
if (qualifier == EvqConst) {
if (! initializer->getType().getQualifier().isConstant()) {
const char* initFeature = "non-constant initializer";
requireProfile(loc, ~EEsProfile, initFeature);
profileRequires(loc, ~EEsProfile, 420, E_GL_ARB_shading_language_420pack, initFeature);
variable->getWritableType().getQualifier().storage = EvqConstReadOnly;
qualifier = EvqConstReadOnly;
}
} else {
// Non-const global variables in ES need a const initializer.
//
// "In declarations of global variables with no storage qualifier or with a const
// qualifier any initializer must be a constant expression."
if (symbolTable.atGlobalLevel() && ! initializer->getType().getQualifier().isConstant()) {
const char* initFeature =
"non-constant global initializer (needs GL_EXT_shader_non_constant_global_initializers)";
if (isEsProfile()) {
if (relaxedErrors() && ! extensionTurnedOn(E_GL_EXT_shader_non_constant_global_initializers))
warn(loc, "not allowed in this version", initFeature, "");
else
profileRequires(loc, EEsProfile, 0, E_GL_EXT_shader_non_constant_global_initializers, initFeature);
}
}
}
if (qualifier == EvqConst || qualifier == EvqUniform) {
// Compile-time tagging of the variable with its constant value...
initializer = intermediate.addConversion(EOpAssign, variable->getType(), initializer);
if (! initializer || ! initializer->getType().getQualifier().isConstant() ||
variable->getType() != initializer->getType()) {
error(loc, "non-matching or non-convertible constant type for const initializer",
variable->getType().getStorageQualifierString(), "");
variable->getWritableType().getQualifier().makeTemporary();
return nullptr;
}
// We either have a folded constant in getAsConstantUnion, or we have to use
// the initializer's subtree in the AST to represent the computation of a
// specialization constant.
assert(initializer->getAsConstantUnion() || initializer->getType().getQualifier().isSpecConstant());
if (initializer->getAsConstantUnion())
variable->setConstArray(initializer->getAsConstantUnion()->getConstArray());
else {
// It's a specialization constant.
variable->getWritableType().getQualifier().makeSpecConstant();
// Keep the subtree that computes the specialization constant with the variable.
// Later, a symbol node will adopt the subtree from the variable.
variable->setConstSubtree(initializer);
}
} else {
// normal assigning of a value to a variable...
specializationCheck(loc, initializer->getType(), "initializer");
TIntermSymbol* intermSymbol = intermediate.addSymbol(*variable, loc);
TIntermTyped* initNode = intermediate.addAssign(EOpAssign, intermSymbol, initializer, loc);
if (! initNode)
assignError(loc, "=", intermSymbol->getCompleteString(intermediate.getEnhancedMsgs()), initializer->getCompleteString(intermediate.getEnhancedMsgs()));
return initNode;
}
return nullptr;
}
//
// Reprocess any initializer-list (the "{ ... }" syntax) parts of the
// initializer.
//
// Need to hierarchically assign correct types and implicit
// conversions. Will do this mimicking the same process used for
// creating a constructor-style initializer, ensuring we get the
// same form. However, it has to in parallel walk the 'type'
// passed in, as type cannot be deduced from an initializer list.
//
TIntermTyped* TParseContext::convertInitializerList(const TSourceLoc& loc, const TType& type, TIntermTyped* initializer)
{
// Will operate recursively. Once a subtree is found that is constructor style,
// everything below it is already good: Only the "top part" of the initializer
// can be an initializer list, where "top part" can extend for several (or all) levels.
// see if we have bottomed out in the tree within the initializer-list part
TIntermAggregate* initList = initializer->getAsAggregate();
if (! initList || initList->getOp() != EOpNull)
return initializer;
// Of the initializer-list set of nodes, need to process bottom up,
// so recurse deep, then process on the way up.
// Go down the tree here...
if (type.isArray()) {
// The type's array might be unsized, which could be okay, so base sizes on the size of the aggregate.
// Later on, initializer execution code will deal with array size logic.
TType arrayType;
arrayType.shallowCopy(type); // sharing struct stuff is fine
arrayType.copyArraySizes(*type.getArraySizes()); // but get a fresh copy of the array information, to edit below
// edit array sizes to fill in unsized dimensions
arrayType.changeOuterArraySize((int)initList->getSequence().size());
TIntermTyped* firstInit = initList->getSequence()[0]->getAsTyped();
if (arrayType.isArrayOfArrays() && firstInit->getType().isArray() &&
arrayType.getArraySizes()->getNumDims() == firstInit->getType().getArraySizes()->getNumDims() + 1) {
for (int d = 1; d < arrayType.getArraySizes()->getNumDims(); ++d) {
if (arrayType.getArraySizes()->getDimSize(d) == UnsizedArraySize)
arrayType.getArraySizes()->setDimSize(d, firstInit->getType().getArraySizes()->getDimSize(d - 1));
}
}
TType elementType(arrayType, 0); // dereferenced type
for (size_t i = 0; i < initList->getSequence().size(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, elementType, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == nullptr)
return nullptr;
}
return addConstructor(loc, initList, arrayType);
} else if (type.isStruct()) {
if (type.getStruct()->size() != initList->getSequence().size()) {
error(loc, "wrong number of structure members", "initializer list", "");
return nullptr;
}
for (size_t i = 0; i < type.getStruct()->size(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, *(*type.getStruct())[i].type, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == nullptr)
return nullptr;
}
} else if (type.isMatrix()) {
if (type.getMatrixCols() != (int)initList->getSequence().size()) {
error(loc, "wrong number of matrix columns:", "initializer list", type.getCompleteString(intermediate.getEnhancedMsgs()).c_str());
return nullptr;
}
TType vectorType(type, 0); // dereferenced type
for (int i = 0; i < type.getMatrixCols(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, vectorType, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == nullptr)
return nullptr;
}
} else if (type.isVector()) {
if (type.getVectorSize() != (int)initList->getSequence().size()) {
error(loc, "wrong vector size (or rows in a matrix column):", "initializer list", type.getCompleteString(intermediate.getEnhancedMsgs()).c_str());
return nullptr;
}
TBasicType destType = type.getBasicType();
for (int i = 0; i < type.getVectorSize(); ++i) {
TBasicType initType = initList->getSequence()[i]->getAsTyped()->getBasicType();
if (destType != initType && !intermediate.canImplicitlyPromote(initType, destType)) {
error(loc, "type mismatch in initializer list", "initializer list", type.getCompleteString(intermediate.getEnhancedMsgs()).c_str());
return nullptr;
}
}
} else {
error(loc, "unexpected initializer-list type:", "initializer list", type.getCompleteString(intermediate.getEnhancedMsgs()).c_str());
return nullptr;
}
// Now that the subtree is processed, process this node as if the
// initializer list is a set of arguments to a constructor.
TIntermNode* emulatedConstructorArguments;
if (initList->getSequence().size() == 1)
emulatedConstructorArguments = initList->getSequence()[0];
else
emulatedConstructorArguments = initList;
return addConstructor(loc, emulatedConstructorArguments, type);
}
//
// Test for the correctness of the parameters passed to various constructor functions
// and also convert them to the right data type, if allowed and required.
//
// 'node' is what to construct from.
// 'type' is what type to construct.
//
// Returns nullptr for an error or the constructed node (aggregate or typed) for no error.
//
TIntermTyped* TParseContext::addConstructor(const TSourceLoc& loc, TIntermNode* node, const TType& type)
{
if (node == nullptr || node->getAsTyped() == nullptr)
return nullptr;
rValueErrorCheck(loc, "constructor", node->getAsTyped());
TIntermAggregate* aggrNode = node->getAsAggregate();
TOperator op = intermediate.mapTypeToConstructorOp(type);
// Combined texture-sampler constructors are completely semantic checked
// in constructorTextureSamplerError()
if (op == EOpConstructTextureSampler) {
if (aggrNode != nullptr) {
if (aggrNode->getSequence()[1]->getAsTyped()->getType().getSampler().shadow) {
// Transfer depth into the texture (SPIR-V image) type, as a hint
// for tools to know this texture/image is a depth image.
aggrNode->getSequence()[0]->getAsTyped()->getWritableType().getSampler().shadow = true;
}
return intermediate.setAggregateOperator(aggrNode, op, type, loc);
}
}
TTypeList::const_iterator memberTypes;
if (op == EOpConstructStruct)
memberTypes = type.getStruct()->begin();
TType elementType;
if (type.isArray()) {
TType dereferenced(type, 0);
elementType.shallowCopy(dereferenced);
} else
elementType.shallowCopy(type);
bool singleArg;
if (aggrNode) {
if (aggrNode->getOp() != EOpNull)
singleArg = true;
else
singleArg = false;
} else
singleArg = true;
TIntermTyped *newNode;
if (singleArg) {
// If structure constructor or array constructor is being called
// for only one parameter inside the structure, we need to call constructAggregate function once.
if (type.isArray())
newNode = constructAggregate(node, elementType, 1, node->getLoc());
else if (op == EOpConstructStruct)
newNode = constructAggregate(node, *(*memberTypes).type, 1, node->getLoc());
else
newNode = constructBuiltIn(type, op, node->getAsTyped(), node->getLoc(), false);
if (newNode && (type.isArray() || op == EOpConstructStruct))
newNode = intermediate.setAggregateOperator(newNode, EOpConstructStruct, type, loc);
return newNode;
}
//
// Handle list of arguments.
//
TIntermSequence &sequenceVector = aggrNode->getSequence(); // Stores the information about the parameter to the constructor
// if the structure constructor contains more than one parameter, then construct
// each parameter
int paramCount = 0; // keeps track of the constructor parameter number being checked
// for each parameter to the constructor call, check to see if the right type is passed or convert them
// to the right type if possible (and allowed).
// for structure constructors, just check if the right type is passed, no conversion is allowed.
for (TIntermSequence::iterator p = sequenceVector.begin();
p != sequenceVector.end(); p++, paramCount++) {
if (type.isArray())
newNode = constructAggregate(*p, elementType, paramCount+1, node->getLoc());
else if (op == EOpConstructStruct)
newNode = constructAggregate(*p, *(memberTypes[paramCount]).type, paramCount+1, node->getLoc());
else
newNode = constructBuiltIn(type, op, (*p)->getAsTyped(), node->getLoc(), true);
if (newNode)
*p = newNode;
else
return nullptr;
}
TIntermTyped *ret_node = intermediate.setAggregateOperator(aggrNode, op, type, loc);
TIntermAggregate *agg_node = ret_node->getAsAggregate();
if (agg_node && (agg_node->isVector() || agg_node->isArray() || agg_node->isMatrix()))
agg_node->updatePrecision();
return ret_node;
}
// Function for constructor implementation. Calls addUnaryMath with appropriate EOp value
// for the parameter to the constructor (passed to this function). Essentially, it converts
// the parameter types correctly. If a constructor expects an int (like ivec2) and is passed a
// float, then float is converted to int.
//
// Returns nullptr for an error or the constructed node.
//
TIntermTyped* TParseContext::constructBuiltIn(const TType& type, TOperator op, TIntermTyped* node, const TSourceLoc& loc,
bool subset)
{
// If we are changing a matrix in both domain of basic type and to a non matrix,
// do the shape change first (by default, below, basic type is changed before shape).
// This avoids requesting a matrix of a new type that is going to be discarded anyway.
// TODO: This could be generalized to more type combinations, but that would require
// more extensive testing and full algorithm rework. For now, the need to do two changes makes
// the recursive call work, and avoids the most egregious case of creating integer matrices.
if (node->getType().isMatrix() && (type.isScalar() || type.isVector()) &&
type.isFloatingDomain() != node->getType().isFloatingDomain()) {
TType transitionType(node->getBasicType(), glslang::EvqTemporary, type.getVectorSize(), 0, 0, node->isVector());
TOperator transitionOp = intermediate.mapTypeToConstructorOp(transitionType);
node = constructBuiltIn(transitionType, transitionOp, node, loc, false);
}
TIntermTyped* newNode;
TOperator basicOp;
//
// First, convert types as needed.
//
switch (op) {
case EOpConstructVec2:
case EOpConstructVec3:
case EOpConstructVec4:
case EOpConstructMat2x2:
case EOpConstructMat2x3:
case EOpConstructMat2x4:
case EOpConstructMat3x2:
case EOpConstructMat3x3:
case EOpConstructMat3x4:
case EOpConstructMat4x2:
case EOpConstructMat4x3:
case EOpConstructMat4x4:
case EOpConstructFloat:
basicOp = EOpConstructFloat;
break;
case EOpConstructIVec2:
case EOpConstructIVec3:
case EOpConstructIVec4:
case EOpConstructInt:
basicOp = EOpConstructInt;
break;
case EOpConstructUVec2:
if (node->getType().getBasicType() == EbtReference) {
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference_uvec2, "reference conversion to uvec2");
TIntermTyped* newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvPtrToUvec2, true, node,
type);
return newNode;
} else if (node->getType().getBasicType() == EbtSampler) {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "sampler conversion to uvec2");
// force the basic type of the constructor param to uvec2, otherwise spv builder will
// report some errors
TIntermTyped* newSrcNode = intermediate.createConversion(EbtUint, node);
newSrcNode->getAsTyped()->getWritableType().setVectorSize(2);
TIntermTyped* newNode =
intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConstructUVec2, false, newSrcNode, type);
return newNode;
}
case EOpConstructUVec3:
case EOpConstructUVec4:
case EOpConstructUint:
basicOp = EOpConstructUint;
break;
case EOpConstructBVec2:
case EOpConstructBVec3:
case EOpConstructBVec4:
case EOpConstructBool:
basicOp = EOpConstructBool;
break;
case EOpConstructTextureSampler:
if ((node->getType().getBasicType() == EbtUint || node->getType().getBasicType() == EbtInt) &&
node->getType().getVectorSize() == 2) {
requireExtensions(loc, 1, &E_GL_ARB_bindless_texture, "ivec2/uvec2 convert to texture handle");
// No matter ivec2 or uvec2, Set EOpPackUint2x32 just to generate an opBitcast op code
TIntermTyped* newNode =
intermediate.addBuiltInFunctionCall(node->getLoc(), EOpPackUint2x32, true, node, type);
return newNode;
}
case EOpConstructDVec2:
case EOpConstructDVec3:
case EOpConstructDVec4:
case EOpConstructDMat2x2:
case EOpConstructDMat2x3:
case EOpConstructDMat2x4:
case EOpConstructDMat3x2:
case EOpConstructDMat3x3:
case EOpConstructDMat3x4:
case EOpConstructDMat4x2:
case EOpConstructDMat4x3:
case EOpConstructDMat4x4:
case EOpConstructDouble:
basicOp = EOpConstructDouble;
break;
case EOpConstructF16Vec2:
case EOpConstructF16Vec3:
case EOpConstructF16Vec4:
case EOpConstructF16Mat2x2:
case EOpConstructF16Mat2x3:
case EOpConstructF16Mat2x4:
case EOpConstructF16Mat3x2:
case EOpConstructF16Mat3x3:
case EOpConstructF16Mat3x4:
case EOpConstructF16Mat4x2:
case EOpConstructF16Mat4x3:
case EOpConstructF16Mat4x4:
case EOpConstructFloat16:
basicOp = EOpConstructFloat16;
// 8/16-bit storage extensions don't support constructing composites of 8/16-bit types,
// so construct a 32-bit type and convert
if (!intermediate.getArithemeticFloat16Enabled()) {
TType tempType(EbtFloat, EvqTemporary, type.getVectorSize());
newNode = node;
if (tempType != newNode->getType()) {
TOperator aggregateOp;
if (op == EOpConstructFloat16)
aggregateOp = EOpConstructFloat;
else
aggregateOp = (TOperator)(EOpConstructVec2 + op - EOpConstructF16Vec2);
newNode = intermediate.setAggregateOperator(newNode, aggregateOp, tempType, node->getLoc());
}
newNode = intermediate.addConversion(EbtFloat16, newNode);
return newNode;
}
break;
case EOpConstructI8Vec2:
case EOpConstructI8Vec3:
case EOpConstructI8Vec4:
case EOpConstructInt8:
basicOp = EOpConstructInt8;
// 8/16-bit storage extensions don't support constructing composites of 8/16-bit types,
// so construct a 32-bit type and convert
if (!intermediate.getArithemeticInt8Enabled()) {
TType tempType(EbtInt, EvqTemporary, type.getVectorSize());
newNode = node;
if (tempType != newNode->getType()) {
TOperator aggregateOp;
if (op == EOpConstructInt8)
aggregateOp = EOpConstructInt;
else
aggregateOp = (TOperator)(EOpConstructIVec2 + op - EOpConstructI8Vec2);
newNode = intermediate.setAggregateOperator(newNode, aggregateOp, tempType, node->getLoc());
}
newNode = intermediate.addConversion(EbtInt8, newNode);
return newNode;
}
break;
case EOpConstructU8Vec2:
case EOpConstructU8Vec3:
case EOpConstructU8Vec4:
case EOpConstructUint8:
basicOp = EOpConstructUint8;
// 8/16-bit storage extensions don't support constructing composites of 8/16-bit types,
// so construct a 32-bit type and convert
if (!intermediate.getArithemeticInt8Enabled()) {
TType tempType(EbtUint, EvqTemporary, type.getVectorSize());
newNode = node;
if (tempType != newNode->getType()) {
TOperator aggregateOp;
if (op == EOpConstructUint8)
aggregateOp = EOpConstructUint;
else
aggregateOp = (TOperator)(EOpConstructUVec2 + op - EOpConstructU8Vec2);
newNode = intermediate.setAggregateOperator(newNode, aggregateOp, tempType, node->getLoc());
}
newNode = intermediate.addConversion(EbtUint8, newNode);
return newNode;
}
break;
case EOpConstructI16Vec2:
case EOpConstructI16Vec3:
case EOpConstructI16Vec4:
case EOpConstructInt16:
basicOp = EOpConstructInt16;
// 8/16-bit storage extensions don't support constructing composites of 8/16-bit types,
// so construct a 32-bit type and convert
if (!intermediate.getArithemeticInt16Enabled()) {
TType tempType(EbtInt, EvqTemporary, type.getVectorSize());
newNode = node;
if (tempType != newNode->getType()) {
TOperator aggregateOp;
if (op == EOpConstructInt16)
aggregateOp = EOpConstructInt;
else
aggregateOp = (TOperator)(EOpConstructIVec2 + op - EOpConstructI16Vec2);
newNode = intermediate.setAggregateOperator(newNode, aggregateOp, tempType, node->getLoc());
}
newNode = intermediate.addConversion(EbtInt16, newNode);
return newNode;
}
break;
case EOpConstructU16Vec2:
case EOpConstructU16Vec3:
case EOpConstructU16Vec4:
case EOpConstructUint16:
basicOp = EOpConstructUint16;
// 8/16-bit storage extensions don't support constructing composites of 8/16-bit types,
// so construct a 32-bit type and convert
if (!intermediate.getArithemeticInt16Enabled()) {
TType tempType(EbtUint, EvqTemporary, type.getVectorSize());
newNode = node;
if (tempType != newNode->getType()) {
TOperator aggregateOp;
if (op == EOpConstructUint16)
aggregateOp = EOpConstructUint;
else
aggregateOp = (TOperator)(EOpConstructUVec2 + op - EOpConstructU16Vec2);
newNode = intermediate.setAggregateOperator(newNode, aggregateOp, tempType, node->getLoc());
}
newNode = intermediate.addConversion(EbtUint16, newNode);
return newNode;
}
break;
case EOpConstructI64Vec2:
case EOpConstructI64Vec3:
case EOpConstructI64Vec4:
case EOpConstructInt64:
basicOp = EOpConstructInt64;
break;
case EOpConstructUint64:
if (type.isScalar() && node->getType().isReference()) {
TIntermTyped* newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvPtrToUint64, true, node, type);
return newNode;
}
// fall through
case EOpConstructU64Vec2:
case EOpConstructU64Vec3:
case EOpConstructU64Vec4:
basicOp = EOpConstructUint64;
break;
case EOpConstructNonuniform:
// Make a nonuniform copy of node
newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpCopyObject, true, node, type);
return newNode;
case EOpConstructReference:
// construct reference from reference
if (node->getType().isReference()) {
newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConstructReference, true, node, type);
return newNode;
// construct reference from uint64
} else if (node->getType().isScalar() && node->getType().getBasicType() == EbtUint64) {
TIntermTyped* newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvUint64ToPtr, true, node,
type);
return newNode;
// construct reference from uvec2
} else if (node->getType().isVector() && node->getType().getBasicType() == EbtUint &&
node->getVectorSize() == 2) {
requireExtensions(loc, 1, &E_GL_EXT_buffer_reference_uvec2, "uvec2 conversion to reference");
TIntermTyped* newNode = intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvUvec2ToPtr, true, node,
type);
return newNode;
} else {
return nullptr;
}
case EOpConstructCooperativeMatrixNV:
case EOpConstructCooperativeMatrixKHR:
if (node->getType() == type) {
return node;
}
if (!node->getType().isCoopMat()) {
if (type.getBasicType() != node->getType().getBasicType()) {
node = intermediate.addConversion(type.getBasicType(), node);
if (node == nullptr)
return nullptr;
}
node = intermediate.setAggregateOperator(node, op, type, node->getLoc());
} else {
TOperator op = EOpNull;
switch (type.getBasicType()) {
default:
assert(0);
break;
case EbtInt:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToInt; break;
case EbtFloat16: op = EOpConvFloat16ToInt; break;
case EbtUint8: op = EOpConvUint8ToInt; break;
case EbtInt8: op = EOpConvInt8ToInt; break;
case EbtUint16: op = EOpConvUint16ToInt; break;
case EbtInt16: op = EOpConvInt16ToInt; break;
case EbtUint: op = EOpConvUintToInt; break;
default: assert(0);
}
break;
case EbtUint:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToUint; break;
case EbtFloat16: op = EOpConvFloat16ToUint; break;
case EbtUint8: op = EOpConvUint8ToUint; break;
case EbtInt8: op = EOpConvInt8ToUint; break;
case EbtUint16: op = EOpConvUint16ToUint; break;
case EbtInt16: op = EOpConvInt16ToUint; break;
case EbtInt: op = EOpConvIntToUint; break;
default: assert(0);
}
break;
case EbtInt16:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToInt16; break;
case EbtFloat16: op = EOpConvFloat16ToInt16; break;
case EbtUint8: op = EOpConvUint8ToInt16; break;
case EbtInt8: op = EOpConvInt8ToInt16; break;
case EbtUint16: op = EOpConvUint16ToInt16; break;
case EbtInt: op = EOpConvIntToInt16; break;
case EbtUint: op = EOpConvUintToInt16; break;
default: assert(0);
}
break;
case EbtUint16:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToUint16; break;
case EbtFloat16: op = EOpConvFloat16ToUint16; break;
case EbtUint8: op = EOpConvUint8ToUint16; break;
case EbtInt8: op = EOpConvInt8ToUint16; break;
case EbtInt16: op = EOpConvInt16ToUint16; break;
case EbtInt: op = EOpConvIntToUint16; break;
case EbtUint: op = EOpConvUintToUint16; break;
default: assert(0);
}
break;
case EbtInt8:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToInt8; break;
case EbtFloat16: op = EOpConvFloat16ToInt8; break;
case EbtUint8: op = EOpConvUint8ToInt8; break;
case EbtInt16: op = EOpConvInt16ToInt8; break;
case EbtUint16: op = EOpConvUint16ToInt8; break;
case EbtInt: op = EOpConvIntToInt8; break;
case EbtUint: op = EOpConvUintToInt8; break;
default: assert(0);
}
break;
case EbtUint8:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToUint8; break;
case EbtFloat16: op = EOpConvFloat16ToUint8; break;
case EbtInt8: op = EOpConvInt8ToUint8; break;
case EbtInt16: op = EOpConvInt16ToUint8; break;
case EbtUint16: op = EOpConvUint16ToUint8; break;
case EbtInt: op = EOpConvIntToUint8; break;
case EbtUint: op = EOpConvUintToUint8; break;
default: assert(0);
}
break;
case EbtFloat:
switch (node->getType().getBasicType()) {
case EbtFloat16: op = EOpConvFloat16ToFloat; break;
case EbtInt8: op = EOpConvInt8ToFloat; break;
case EbtUint8: op = EOpConvUint8ToFloat; break;
case EbtInt16: op = EOpConvInt16ToFloat; break;
case EbtUint16: op = EOpConvUint16ToFloat; break;
case EbtInt: op = EOpConvIntToFloat; break;
case EbtUint: op = EOpConvUintToFloat; break;
default: assert(0);
}
break;
case EbtFloat16:
switch (node->getType().getBasicType()) {
case EbtFloat: op = EOpConvFloatToFloat16; break;
case EbtInt8: op = EOpConvInt8ToFloat16; break;
case EbtUint8: op = EOpConvUint8ToFloat16; break;
case EbtInt16: op = EOpConvInt16ToFloat16; break;
case EbtUint16: op = EOpConvUint16ToFloat16; break;
case EbtInt: op = EOpConvIntToFloat16; break;
case EbtUint: op = EOpConvUintToFloat16; break;
default: assert(0);
}
break;
}
node = intermediate.addUnaryNode(op, node, node->getLoc(), type);
// If it's a (non-specialization) constant, it must be folded.
if (node->getAsUnaryNode()->getOperand()->getAsConstantUnion())
return node->getAsUnaryNode()->getOperand()->getAsConstantUnion()->fold(op, node->getType());
}
return node;
case EOpConstructAccStruct:
if ((node->getType().isScalar() && node->getType().getBasicType() == EbtUint64)) {
// construct acceleration structure from uint64
requireExtensions(loc, Num_ray_tracing_EXTs, ray_tracing_EXTs, "uint64_t conversion to acclerationStructureEXT");
return intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvUint64ToAccStruct, true, node,
type);
} else if (node->getType().isVector() && node->getType().getBasicType() == EbtUint && node->getVectorSize() == 2) {
// construct acceleration structure from uint64
requireExtensions(loc, Num_ray_tracing_EXTs, ray_tracing_EXTs, "uvec2 conversion to accelerationStructureEXT");
return intermediate.addBuiltInFunctionCall(node->getLoc(), EOpConvUvec2ToAccStruct, true, node,
type);
} else
return nullptr;
default:
error(loc, "unsupported construction", "", "");
return nullptr;
}
newNode = intermediate.addUnaryMath(basicOp, node, node->getLoc());
if (newNode == nullptr) {
error(loc, "can't convert", "constructor", "");
return nullptr;
}
//
// Now, if there still isn't an operation to do the construction, and we need one, add one.
//
// Otherwise, skip out early.
if (subset || (newNode != node && newNode->getType() == type))
return newNode;
// setAggregateOperator will insert a new node for the constructor, as needed.
return intermediate.setAggregateOperator(newNode, op, type, loc);
}
// This function tests for the type of the parameters to the structure or array constructor. Raises
// an error message if the expected type does not match the parameter passed to the constructor.
//
// Returns nullptr for an error or the input node itself if the expected and the given parameter types match.
//
TIntermTyped* TParseContext::constructAggregate(TIntermNode* node, const TType& type, int paramCount, const TSourceLoc& loc)
{
TIntermTyped* converted = intermediate.addConversion(EOpConstructStruct, type, node->getAsTyped());
if (! converted || converted->getType() != type) {
bool enhanced = intermediate.getEnhancedMsgs();
error(loc, "", "constructor", "cannot convert parameter %d from '%s' to '%s'", paramCount,
node->getAsTyped()->getType().getCompleteString(enhanced).c_str(), type.getCompleteString(enhanced).c_str());
return nullptr;
}
return converted;
}
// If a memory qualifier is present in 'to', also make it present in 'from'.
void TParseContext::inheritMemoryQualifiers(const TQualifier& from, TQualifier& to)
{
if (from.isReadOnly())
to.readonly = from.readonly;
if (from.isWriteOnly())
to.writeonly = from.writeonly;
if (from.coherent)
to.coherent = from.coherent;
if (from.volatil)
to.volatil = from.volatil;
if (from.restrict)
to.restrict = from.restrict;
}
//
// Update qualifier layoutBindlessImage & layoutBindlessSampler on block member
//
void TParseContext::updateBindlessQualifier(TType& memberType)
{
if (memberType.containsSampler()) {
if (memberType.isStruct()) {
TTypeList* typeList = memberType.getWritableStruct();
for (unsigned int member = 0; member < typeList->size(); ++member) {
TType* subMemberType = (*typeList)[member].type;
updateBindlessQualifier(*subMemberType);
}
}
else if (memberType.getSampler().isImage()) {
intermediate.setBindlessImageMode(currentCaller, AstRefTypeLayout);
memberType.getQualifier().layoutBindlessImage = true;
}
else {
intermediate.setBindlessTextureMode(currentCaller, AstRefTypeLayout);
memberType.getQualifier().layoutBindlessSampler = true;
}
}
}
//
// Do everything needed to add an interface block.
//
void TParseContext::declareBlock(const TSourceLoc& loc, TTypeList& typeList, const TString* instanceName,
TArraySizes* arraySizes)
{
if (spvVersion.vulkan > 0 && spvVersion.vulkanRelaxed)
blockStorageRemap(loc, blockName, currentBlockQualifier);
blockStageIoCheck(loc, currentBlockQualifier);
blockQualifierCheck(loc, currentBlockQualifier, instanceName != nullptr);
if (arraySizes != nullptr) {
arraySizesCheck(loc, currentBlockQualifier, arraySizes, nullptr, false);
arrayOfArrayVersionCheck(loc, arraySizes);
if (arraySizes->getNumDims() > 1)
requireProfile(loc, ~EEsProfile, "array-of-array of block");
}
// Inherit and check member storage qualifiers WRT to the block-level qualifier.
for (unsigned int member = 0; member < typeList.size(); ++member) {
TType& memberType = *typeList[member].type;
TQualifier& memberQualifier = memberType.getQualifier();
const TSourceLoc& memberLoc = typeList[member].loc;
if (memberQualifier.storage != EvqTemporary && memberQualifier.storage != EvqGlobal && memberQualifier.storage != currentBlockQualifier.storage)
error(memberLoc, "member storage qualifier cannot contradict block storage qualifier", memberType.getFieldName().c_str(), "");
memberQualifier.storage = currentBlockQualifier.storage;
globalQualifierFixCheck(memberLoc, memberQualifier);
inheritMemoryQualifiers(currentBlockQualifier, memberQualifier);
if (currentBlockQualifier.perPrimitiveNV)
memberQualifier.perPrimitiveNV = currentBlockQualifier.perPrimitiveNV;
if (currentBlockQualifier.perViewNV)
memberQualifier.perViewNV = currentBlockQualifier.perViewNV;
if (currentBlockQualifier.perTaskNV)
memberQualifier.perTaskNV = currentBlockQualifier.perTaskNV;
if (currentBlockQualifier.storage == EvqtaskPayloadSharedEXT)
memberQualifier.storage = EvqtaskPayloadSharedEXT;
if (memberQualifier.storage == EvqSpirvStorageClass)
error(memberLoc, "member cannot have a spirv_storage_class qualifier", memberType.getFieldName().c_str(), "");
if (memberQualifier.hasSprivDecorate() && !memberQualifier.getSpirvDecorate().decorateIds.empty())
error(memberLoc, "member cannot have a spirv_decorate_id qualifier", memberType.getFieldName().c_str(), "");
if ((currentBlockQualifier.storage == EvqUniform || currentBlockQualifier.storage == EvqBuffer) && (memberQualifier.isInterpolation() || memberQualifier.isAuxiliary()))
error(memberLoc, "member of uniform or buffer block cannot have an auxiliary or interpolation qualifier", memberType.getFieldName().c_str(), "");
if (memberType.isArray())
arraySizesCheck(memberLoc, currentBlockQualifier, memberType.getArraySizes(), nullptr, member == typeList.size() - 1);
if (memberQualifier.hasOffset()) {
if (spvVersion.spv == 0) {
profileRequires(memberLoc, ~EEsProfile, 440, E_GL_ARB_enhanced_layouts, "\"offset\" on block member");
profileRequires(memberLoc, EEsProfile, 300, E_GL_ARB_enhanced_layouts, "\"offset\" on block member");
}
}
// For bindless texture, sampler can be declared as uniform/storage block member,
if (memberType.containsOpaque()) {
if (memberType.containsSampler() && extensionTurnedOn(E_GL_ARB_bindless_texture))
updateBindlessQualifier(memberType);
else
error(memberLoc, "member of block cannot be or contain a sampler, image, or atomic_uint type", typeList[member].type->getFieldName().c_str(), "");
}
if (memberType.containsCoopMat())
error(memberLoc, "member of block cannot be or contain a cooperative matrix type", typeList[member].type->getFieldName().c_str(), "");
}
// This might be a redeclaration of a built-in block. If so, redeclareBuiltinBlock() will
// do all the rest.
if (! symbolTable.atBuiltInLevel() && builtInName(*blockName)) {
redeclareBuiltinBlock(loc, typeList, *blockName, instanceName, arraySizes);
return;
}
// Not a redeclaration of a built-in; check that all names are user names.
reservedErrorCheck(loc, *blockName);
if (instanceName)
reservedErrorCheck(loc, *instanceName);
for (unsigned int member = 0; member < typeList.size(); ++member)
reservedErrorCheck(typeList[member].loc, typeList[member].type->getFieldName());
// Make default block qualification, and adjust the member qualifications
TQualifier defaultQualification;
switch (currentBlockQualifier.storage) {
case EvqUniform: defaultQualification = globalUniformDefaults; break;
case EvqBuffer: defaultQualification = globalBufferDefaults; break;
case EvqVaryingIn: defaultQualification = globalInputDefaults; break;
case EvqVaryingOut: defaultQualification = globalOutputDefaults; break;
case EvqShared: defaultQualification = globalSharedDefaults; break;
default: defaultQualification.clear(); break;
}
// Special case for "push_constant uniform", which has a default of std430,
// contrary to normal uniform defaults, and can't have a default tracked for it.
if ((currentBlockQualifier.isPushConstant() && !currentBlockQualifier.hasPacking()) ||
(currentBlockQualifier.isShaderRecord() && !currentBlockQualifier.hasPacking()))
currentBlockQualifier.layoutPacking = ElpStd430;
// Special case for "taskNV in/out", which has a default of std430,
if (currentBlockQualifier.isTaskMemory() && !currentBlockQualifier.hasPacking())
currentBlockQualifier.layoutPacking = ElpStd430;
// fix and check for member layout qualifiers
mergeObjectLayoutQualifiers(defaultQualification, currentBlockQualifier, true);
// "The align qualifier can only be used on blocks or block members, and only for blocks declared with std140 or std430 layouts."
if (currentBlockQualifier.hasAlign()) {
if (defaultQualification.layoutPacking != ElpStd140 &&
defaultQualification.layoutPacking != ElpStd430 &&
defaultQualification.layoutPacking != ElpScalar) {
error(loc, "can only be used with std140, std430, or scalar layout packing", "align", "");
defaultQualification.layoutAlign = -1;
}
}
bool memberWithLocation = false;
bool memberWithoutLocation = false;
bool memberWithPerViewQualifier = false;
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
const TSourceLoc& memberLoc = typeList[member].loc;
if (memberQualifier.hasStream()) {
if (defaultQualification.layoutStream != memberQualifier.layoutStream)
error(memberLoc, "member cannot contradict block", "stream", "");
}
// "This includes a block's inheritance of the
// current global default buffer, a block member's inheritance of the block's
// buffer, and the requirement that any *xfb_buffer* declared on a block
// member must match the buffer inherited from the block."
if (memberQualifier.hasXfbBuffer()) {
if (defaultQualification.layoutXfbBuffer != memberQualifier.layoutXfbBuffer)
error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_buffer", "");
}
if (memberQualifier.hasPacking())
error(memberLoc, "member of block cannot have a packing layout qualifier", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.hasLocation()) {
const char* feature = "location on block member";
switch (currentBlockQualifier.storage) {
case EvqVaryingIn:
case EvqVaryingOut:
requireProfile(memberLoc, ECoreProfile | ECompatibilityProfile | EEsProfile, feature);
profileRequires(memberLoc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature);
profileRequires(memberLoc, EEsProfile, 320, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, feature);
memberWithLocation = true;
break;
default:
error(memberLoc, "can only use in an in/out block", feature, "");
break;
}
} else
memberWithoutLocation = true;
// "The offset qualifier can only be used on block members of blocks declared with std140 or std430 layouts."
// "The align qualifier can only be used on blocks or block members, and only for blocks declared with std140 or std430 layouts."
if (memberQualifier.hasAlign() || memberQualifier.hasOffset()) {
if (defaultQualification.layoutPacking != ElpStd140 &&
defaultQualification.layoutPacking != ElpStd430 &&
defaultQualification.layoutPacking != ElpScalar)
error(memberLoc, "can only be used with std140, std430, or scalar layout packing", "offset/align", "");
}
if (memberQualifier.isPerView()) {
memberWithPerViewQualifier = true;
}
TQualifier newMemberQualification = defaultQualification;
mergeQualifiers(memberLoc, newMemberQualification, memberQualifier, false);
memberQualifier = newMemberQualification;
}
layoutMemberLocationArrayCheck(loc, memberWithLocation, arraySizes);
// Ensure that the block has an XfbBuffer assigned. This is needed
// because if the block has a XfbOffset assigned, then it is
// assumed that it has implicitly assigned the current global
// XfbBuffer, and because it's members need to be assigned a
// XfbOffset if they lack it.
if (currentBlockQualifier.storage == EvqVaryingOut && globalOutputDefaults.hasXfbBuffer()) {
if (!currentBlockQualifier.hasXfbBuffer() && currentBlockQualifier.hasXfbOffset())
currentBlockQualifier.layoutXfbBuffer = globalOutputDefaults.layoutXfbBuffer;
}
// Process the members
fixBlockLocations(loc, currentBlockQualifier, typeList, memberWithLocation, memberWithoutLocation);
fixXfbOffsets(currentBlockQualifier, typeList);
fixBlockUniformOffsets(currentBlockQualifier, typeList);
fixBlockUniformLayoutMatrix(currentBlockQualifier, &typeList, nullptr);
fixBlockUniformLayoutPacking(currentBlockQualifier, &typeList, nullptr);
for (unsigned int member = 0; member < typeList.size(); ++member)
layoutTypeCheck(typeList[member].loc, *typeList[member].type);
if (memberWithPerViewQualifier) {
for (unsigned int member = 0; member < typeList.size(); ++member) {
checkAndResizeMeshViewDim(typeList[member].loc, *typeList[member].type, /*isBlockMember*/ true);
}
}
// reverse merge, so that currentBlockQualifier now has all layout information
// (can't use defaultQualification directly, it's missing other non-layout-default-class qualifiers)
mergeObjectLayoutQualifiers(currentBlockQualifier, defaultQualification, true);
//
// Build and add the interface block as a new type named 'blockName'
//
TType blockType(&typeList, *blockName, currentBlockQualifier);
if (arraySizes != nullptr)
blockType.transferArraySizes(arraySizes);
if (arraySizes == nullptr)
ioArrayCheck(loc, blockType, instanceName ? *instanceName : *blockName);
if (currentBlockQualifier.hasBufferReference()) {
if (currentBlockQualifier.storage != EvqBuffer)
error(loc, "can only be used with buffer", "buffer_reference", "");
// Create the block reference type. If it was forward-declared, detect that
// as a referent struct type with no members. Replace the referent type with
// blockType.
TType blockNameType(EbtReference, blockType, *blockName);
TVariable* blockNameVar = new TVariable(blockName, blockNameType, true);
if (! symbolTable.insert(*blockNameVar)) {
TSymbol* existingName = symbolTable.find(*blockName);
if (existingName->getType().isReference() &&
existingName->getType().getReferentType()->getStruct() &&
existingName->getType().getReferentType()->getStruct()->size() == 0 &&
existingName->getType().getQualifier().storage == blockType.getQualifier().storage) {
existingName->getType().getReferentType()->deepCopy(blockType);
} else {
error(loc, "block name cannot be redefined", blockName->c_str(), "");
}
}
if (!instanceName) {
return;
}
} else {
//
// Don't make a user-defined type out of block name; that will cause an error
// if the same block name gets reused in a different interface.
//
// "Block names have no other use within a shader
// beyond interface matching; it is a compile-time error to use a block name at global scope for anything
// other than as a block name (e.g., use of a block name for a global variable name or function name is
// currently reserved)."
//
// Use the symbol table to prevent normal reuse of the block's name, as a variable entry,
// whose type is EbtBlock, but without all the structure; that will come from the type
// the instances point to.
//
TType blockNameType(EbtBlock, blockType.getQualifier().storage);
TVariable* blockNameVar = new TVariable(blockName, blockNameType);
if (! symbolTable.insert(*blockNameVar)) {
TSymbol* existingName = symbolTable.find(*blockName);
if (existingName->getType().getBasicType() == EbtBlock) {
if (existingName->getType().getQualifier().storage == blockType.getQualifier().storage) {
error(loc, "Cannot reuse block name within the same interface:", blockName->c_str(), blockType.getStorageQualifierString());
return;
}
} else {
error(loc, "block name cannot redefine a non-block name", blockName->c_str(), "");
return;
}
}
}
// Add the variable, as anonymous or named instanceName.
// Make an anonymous variable if no name was provided.
if (! instanceName)
instanceName = NewPoolTString("");
TVariable& variable = *new TVariable(instanceName, blockType);
if (! symbolTable.insert(variable)) {
if (*instanceName == "")
error(loc, "nameless block contains a member that already has a name at global scope", blockName->c_str(), "");
else
error(loc, "block instance name redefinition", variable.getName().c_str(), "");
return;
}
// Check for general layout qualifier errors
layoutObjectCheck(loc, variable);
// fix up
if (isIoResizeArray(blockType)) {
ioArraySymbolResizeList.push_back(&variable);
checkIoArraysConsistency(loc, true);
} else
fixIoArraySize(loc, variable.getWritableType());
// Save it in the AST for linker use.
trackLinkage(variable);
}
//
// allow storage type of block to be remapped at compile time
//
void TParseContext::blockStorageRemap(const TSourceLoc&, const TString* instanceName, TQualifier& qualifier)
{
TBlockStorageClass type = intermediate.getBlockStorageOverride(instanceName->c_str());
if (type != EbsNone) {
qualifier.setBlockStorage(type);
}
}
// Do all block-declaration checking regarding the combination of in/out/uniform/buffer
// with a particular stage.
void TParseContext::blockStageIoCheck(const TSourceLoc& loc, const TQualifier& qualifier)
{
const char *extsrt[2] = { E_GL_NV_ray_tracing, E_GL_EXT_ray_tracing };
switch (qualifier.storage) {
case EvqUniform:
profileRequires(loc, EEsProfile, 300, nullptr, "uniform block");
profileRequires(loc, ENoProfile, 140, E_GL_ARB_uniform_buffer_object, "uniform block");
if (currentBlockQualifier.layoutPacking == ElpStd430 && ! currentBlockQualifier.isPushConstant())
requireExtensions(loc, 1, &E_GL_EXT_scalar_block_layout, "std430 requires the buffer storage qualifier");
break;
case EvqBuffer:
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "buffer block");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, E_GL_ARB_shader_storage_buffer_object, "buffer block");
profileRequires(loc, EEsProfile, 310, nullptr, "buffer block");
break;
case EvqVaryingIn:
profileRequires(loc, ~EEsProfile, 150, E_GL_ARB_separate_shader_objects, "input block");
// It is a compile-time error to have an input block in a vertex shader or an output block in a fragment shader
// "Compute shaders do not permit user-defined input variables..."
requireStage(loc, (EShLanguageMask)(EShLangTessControlMask|EShLangTessEvaluationMask|EShLangGeometryMask|
EShLangFragmentMask|EShLangMeshMask), "input block");
if (language == EShLangFragment) {
profileRequires(loc, EEsProfile, 320, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, "fragment input block");
} else if (language == EShLangMesh && ! qualifier.isTaskMemory()) {
error(loc, "input blocks cannot be used in a mesh shader", "out", "");
}
break;
case EvqVaryingOut:
profileRequires(loc, ~EEsProfile, 150, E_GL_ARB_separate_shader_objects, "output block");
requireStage(loc, (EShLanguageMask)(EShLangVertexMask|EShLangTessControlMask|EShLangTessEvaluationMask|
EShLangGeometryMask|EShLangMeshMask|EShLangTaskMask), "output block");
// ES 310 can have a block before shader_io is turned on, so skip this test for built-ins
if (language == EShLangVertex && ! parsingBuiltins) {
profileRequires(loc, EEsProfile, 320, Num_AEP_shader_io_blocks, AEP_shader_io_blocks, "vertex output block");
} else if (language == EShLangMesh && qualifier.isTaskMemory()) {
error(loc, "can only use on input blocks in mesh shader", "taskNV", "");
} else if (language == EShLangTask && ! qualifier.isTaskMemory()) {
error(loc, "output blocks cannot be used in a task shader", "out", "");
}
break;
case EvqShared:
if (spvVersion.spv > 0 && spvVersion.spv < EShTargetSpv_1_4) {
error(loc, "shared block requires at least SPIR-V 1.4", "shared block", "");
}
profileRequires(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, 0, E_GL_EXT_shared_memory_block, "shared block");
break;
case EvqPayload:
profileRequires(loc, ~EEsProfile, 460, 2, extsrt, "rayPayloadNV block");
requireStage(loc, (EShLanguageMask)(EShLangRayGenMask | EShLangAnyHitMask | EShLangClosestHitMask | EShLangMissMask),
"rayPayloadNV block");
break;
case EvqPayloadIn:
profileRequires(loc, ~EEsProfile, 460, 2, extsrt, "rayPayloadInNV block");
requireStage(loc, (EShLanguageMask)(EShLangAnyHitMask | EShLangClosestHitMask | EShLangMissMask),
"rayPayloadInNV block");
break;
case EvqHitAttr:
profileRequires(loc, ~EEsProfile, 460, 2, extsrt, "hitAttributeNV block");
requireStage(loc, (EShLanguageMask)(EShLangIntersectMask | EShLangAnyHitMask | EShLangClosestHitMask), "hitAttributeNV block");
break;
case EvqCallableData:
profileRequires(loc, ~EEsProfile, 460, 2, extsrt, "callableDataNV block");
requireStage(loc, (EShLanguageMask)(EShLangRayGenMask | EShLangClosestHitMask | EShLangMissMask | EShLangCallableMask),
"callableDataNV block");
break;
case EvqCallableDataIn:
profileRequires(loc, ~EEsProfile, 460, 2, extsrt, "callableDataInNV block");
requireStage(loc, (EShLanguageMask)(EShLangCallableMask), "callableDataInNV block");
break;
case EvqHitObjectAttrNV:
profileRequires(loc, ~EEsProfile, 460, E_GL_NV_shader_invocation_reorder, "hitObjectAttributeNV block");
requireStage(loc, (EShLanguageMask)(EShLangRayGenMask | EShLangClosestHitMask | EShLangMissMask), "hitObjectAttributeNV block");
break;
default:
error(loc, "only uniform, buffer, in, or out blocks are supported", blockName->c_str(), "");
break;
}
}
// Do all block-declaration checking regarding its qualifiers.
void TParseContext::blockQualifierCheck(const TSourceLoc& loc, const TQualifier& qualifier, bool /*instanceName*/)
{
// The 4.5 specification says:
//
// interface-block :
// layout-qualifieropt interface-qualifier block-name { member-list } instance-nameopt ;
//
// interface-qualifier :
// in
// out
// patch in
// patch out
// uniform
// buffer
//
// Note however memory qualifiers aren't included, yet the specification also says
//
// "...memory qualifiers may also be used in the declaration of shader storage blocks..."
if (qualifier.isInterpolation())
error(loc, "cannot use interpolation qualifiers on an interface block", "flat/smooth/noperspective", "");
if (qualifier.centroid)
error(loc, "cannot use centroid qualifier on an interface block", "centroid", "");
if (qualifier.isSample())
error(loc, "cannot use sample qualifier on an interface block", "sample", "");
if (qualifier.invariant)
error(loc, "cannot use invariant qualifier on an interface block", "invariant", "");
if (qualifier.isPushConstant())
intermediate.addPushConstantCount();
if (qualifier.isShaderRecord())
intermediate.addShaderRecordCount();
if (qualifier.isTaskMemory())
intermediate.addTaskNVCount();
}
//
// "For a block, this process applies to the entire block, or until the first member
// is reached that has a location layout qualifier. When a block member is declared with a location
// qualifier, its location comes from that qualifier: The member's location qualifier overrides the block-level
// declaration. Subsequent members are again assigned consecutive locations, based on the newest location,
// until the next member declared with a location qualifier. The values used for locations do not have to be
// declared in increasing order."
void TParseContext::fixBlockLocations(const TSourceLoc& loc, TQualifier& qualifier, TTypeList& typeList, bool memberWithLocation, bool memberWithoutLocation)
{
// "If a block has no block-level location layout qualifier, it is required that either all or none of its members
// have a location layout qualifier, or a compile-time error results."
if (! qualifier.hasLocation() && memberWithLocation && memberWithoutLocation)
error(loc, "either the block needs a location, or all members need a location, or no members have a location", "location", "");
else {
if (memberWithLocation) {
// remove any block-level location and make it per *every* member
int nextLocation = 0; // by the rule above, initial value is not relevant
if (qualifier.hasAnyLocation()) {
nextLocation = qualifier.layoutLocation;
qualifier.layoutLocation = TQualifier::layoutLocationEnd;
if (qualifier.hasComponent()) {
// "It is a compile-time error to apply the *component* qualifier to a ... block"
error(loc, "cannot apply to a block", "component", "");
}
if (qualifier.hasIndex()) {
error(loc, "cannot apply to a block", "index", "");
}
}
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
const TSourceLoc& memberLoc = typeList[member].loc;
if (! memberQualifier.hasLocation()) {
if (nextLocation >= (int)TQualifier::layoutLocationEnd)
error(memberLoc, "location is too large", "location", "");
memberQualifier.layoutLocation = nextLocation;
memberQualifier.layoutComponent = TQualifier::layoutComponentEnd;
}
nextLocation = memberQualifier.layoutLocation + intermediate.computeTypeLocationSize(
*typeList[member].type, language);
}
}
}
}
void TParseContext::fixXfbOffsets(TQualifier& qualifier, TTypeList& typeList)
{
// "If a block is qualified with xfb_offset, all its
// members are assigned transform feedback buffer offsets. If a block is not qualified with xfb_offset, any
// members of that block not qualified with an xfb_offset will not be assigned transform feedback buffer
// offsets."
if (! qualifier.hasXfbBuffer() || ! qualifier.hasXfbOffset())
return;
int nextOffset = qualifier.layoutXfbOffset;
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
bool contains64BitType = false;
bool contains32BitType = false;
bool contains16BitType = false;
int memberSize = intermediate.computeTypeXfbSize(*typeList[member].type, contains64BitType, contains32BitType, contains16BitType);
// see if we need to auto-assign an offset to this member
if (! memberQualifier.hasXfbOffset()) {
// "if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8"
if (contains64BitType)
RoundToPow2(nextOffset, 8);
else if (contains32BitType)
RoundToPow2(nextOffset, 4);
else if (contains16BitType)
RoundToPow2(nextOffset, 2);
memberQualifier.layoutXfbOffset = nextOffset;
} else
nextOffset = memberQualifier.layoutXfbOffset;
nextOffset += memberSize;
}
// The above gave all block members an offset, so we can take it off the block now,
// which will avoid double counting the offset usage.
qualifier.layoutXfbOffset = TQualifier::layoutXfbOffsetEnd;
}
// Calculate and save the offset of each block member, using the recursively
// defined block offset rules and the user-provided offset and align.
//
// Also, compute and save the total size of the block. For the block's size, arrayness
// is not taken into account, as each element is backed by a separate buffer.
//
void TParseContext::fixBlockUniformOffsets(TQualifier& qualifier, TTypeList& typeList)
{
if (!storageCanHaveLayoutInBlock(qualifier.storage) && !qualifier.isTaskMemory())
return;
if (qualifier.layoutPacking != ElpStd140 && qualifier.layoutPacking != ElpStd430 && qualifier.layoutPacking != ElpScalar)
return;
int offset = 0;
int memberSize;
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
const TSourceLoc& memberLoc = typeList[member].loc;
// "When align is applied to an array, it effects only the start of the array, not the array's internal stride."
// modify just the children's view of matrix layout, if there is one for this member
TLayoutMatrix subMatrixLayout = typeList[member].type->getQualifier().layoutMatrix;
int dummyStride;
int memberAlignment = intermediate.getMemberAlignment(*typeList[member].type, memberSize, dummyStride, qualifier.layoutPacking,
subMatrixLayout != ElmNone ? subMatrixLayout == ElmRowMajor : qualifier.layoutMatrix == ElmRowMajor);
if (memberQualifier.hasOffset()) {
// "The specified offset must be a multiple
// of the base alignment of the type of the block member it qualifies, or a compile-time error results."
if (! IsMultipleOfPow2(memberQualifier.layoutOffset, memberAlignment))
error(memberLoc, "must be a multiple of the member's alignment", "offset",
"(layout offset = %d | member alignment = %d)", memberQualifier.layoutOffset, memberAlignment);
// GLSL: "It is a compile-time error to specify an offset that is smaller than the offset of the previous
// member in the block or that lies within the previous member of the block"
if (spvVersion.spv == 0) {
if (memberQualifier.layoutOffset < offset)
error(memberLoc, "cannot lie in previous members", "offset", "");
// "The offset qualifier forces the qualified member to start at or after the specified
// integral-constant expression, which will be its byte offset from the beginning of the buffer.
// "The actual offset of a member is computed as
// follows: If offset was declared, start with that offset, otherwise start with the next available offset."
offset = std::max(offset, memberQualifier.layoutOffset);
} else {
// TODO: Vulkan: "It is a compile-time error to have any offset, explicit or assigned,
// that lies within another member of the block."
offset = memberQualifier.layoutOffset;
}
}
// "The actual alignment of a member will be the greater of the specified align alignment and the standard
// (e.g., std140) base alignment for the member's type."
if (memberQualifier.hasAlign())
memberAlignment = std::max(memberAlignment, memberQualifier.layoutAlign);
// "If the resulting offset is not a multiple of the actual alignment,
// increase it to the first offset that is a multiple of
// the actual alignment."
RoundToPow2(offset, memberAlignment);
typeList[member].type->getQualifier().layoutOffset = offset;
offset += memberSize;
}
}
//
// Spread LayoutMatrix to uniform block member, if a uniform block member is a struct,
// we need spread LayoutMatrix to this struct member too. and keep this rule for recursive.
//
void TParseContext::fixBlockUniformLayoutMatrix(TQualifier& qualifier, TTypeList* originTypeList,
TTypeList* tmpTypeList)
{
assert(tmpTypeList == nullptr || originTypeList->size() == tmpTypeList->size());
for (unsigned int member = 0; member < originTypeList->size(); ++member) {
if (qualifier.layoutPacking != ElpNone) {
if (tmpTypeList == nullptr) {
if (((*originTypeList)[member].type->isMatrix() ||
(*originTypeList)[member].type->getBasicType() == EbtStruct) &&
(*originTypeList)[member].type->getQualifier().layoutMatrix == ElmNone) {
(*originTypeList)[member].type->getQualifier().layoutMatrix = qualifier.layoutMatrix;
}
} else {
if (((*tmpTypeList)[member].type->isMatrix() ||
(*tmpTypeList)[member].type->getBasicType() == EbtStruct) &&
(*tmpTypeList)[member].type->getQualifier().layoutMatrix == ElmNone) {
(*tmpTypeList)[member].type->getQualifier().layoutMatrix = qualifier.layoutMatrix;
}
}
}
if ((*originTypeList)[member].type->getBasicType() == EbtStruct) {
TQualifier* memberQualifier = nullptr;
// block member can be declare a matrix style, so it should be update to the member's style
if ((*originTypeList)[member].type->getQualifier().layoutMatrix == ElmNone) {
memberQualifier = &qualifier;
} else {
memberQualifier = &((*originTypeList)[member].type->getQualifier());
}
const TType* tmpType = tmpTypeList == nullptr ?
(*originTypeList)[member].type->clone() : (*tmpTypeList)[member].type;
fixBlockUniformLayoutMatrix(*memberQualifier, (*originTypeList)[member].type->getWritableStruct(),
tmpType->getWritableStruct());
const TTypeList* structure = recordStructCopy(matrixFixRecord, (*originTypeList)[member].type, tmpType);
if (tmpTypeList == nullptr) {
(*originTypeList)[member].type->setStruct(const_cast<TTypeList*>(structure));
}
if (tmpTypeList != nullptr) {
(*tmpTypeList)[member].type->setStruct(const_cast<TTypeList*>(structure));
}
}
}
}
//
// Spread LayoutPacking to matrix or aggregate block members. If a block member is a struct or
// array of struct, spread LayoutPacking recursively to its matrix or aggregate members.
//
void TParseContext::fixBlockUniformLayoutPacking(TQualifier& qualifier, TTypeList* originTypeList,
TTypeList* tmpTypeList)
{
assert(tmpTypeList == nullptr || originTypeList->size() == tmpTypeList->size());
for (unsigned int member = 0; member < originTypeList->size(); ++member) {
if (qualifier.layoutPacking != ElpNone) {
if (tmpTypeList == nullptr) {
if ((*originTypeList)[member].type->getQualifier().layoutPacking == ElpNone &&
!(*originTypeList)[member].type->isScalarOrVector()) {
(*originTypeList)[member].type->getQualifier().layoutPacking = qualifier.layoutPacking;
}
} else {
if ((*tmpTypeList)[member].type->getQualifier().layoutPacking == ElpNone &&
!(*tmpTypeList)[member].type->isScalarOrVector()) {
(*tmpTypeList)[member].type->getQualifier().layoutPacking = qualifier.layoutPacking;
}
}
}
if ((*originTypeList)[member].type->getBasicType() == EbtStruct) {
// Deep copy the type in pool.
// Because, struct use in different block may have different layout qualifier.
// We have to new a object to distinguish between them.
const TType* tmpType = tmpTypeList == nullptr ?
(*originTypeList)[member].type->clone() : (*tmpTypeList)[member].type;
fixBlockUniformLayoutPacking(qualifier, (*originTypeList)[member].type->getWritableStruct(),
tmpType->getWritableStruct());
const TTypeList* structure = recordStructCopy(packingFixRecord, (*originTypeList)[member].type, tmpType);
if (tmpTypeList == nullptr) {
(*originTypeList)[member].type->setStruct(const_cast<TTypeList*>(structure));
}
if (tmpTypeList != nullptr) {
(*tmpTypeList)[member].type->setStruct(const_cast<TTypeList*>(structure));
}
}
}
}
// For an identifier that is already declared, add more qualification to it.
void TParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, const TString& identifier)
{
TSymbol* symbol = symbolTable.find(identifier);
// A forward declaration of a block reference looks to the grammar like adding
// a qualifier to an existing symbol. Detect this and create the block reference
// type with an empty type list, which will be filled in later in
// TParseContext::declareBlock.
if (!symbol && qualifier.hasBufferReference()) {
TTypeList typeList;
TType blockType(&typeList, identifier, qualifier);
TType blockNameType(EbtReference, blockType, identifier);
TVariable* blockNameVar = new TVariable(&identifier, blockNameType, true);
if (! symbolTable.insert(*blockNameVar)) {
error(loc, "block name cannot redefine a non-block name", blockName->c_str(), "");
}
return;
}
if (! symbol) {
error(loc, "identifier not previously declared", identifier.c_str(), "");
return;
}
if (symbol->getAsFunction()) {
error(loc, "cannot re-qualify a function name", identifier.c_str(), "");
return;
}
if (qualifier.isAuxiliary() ||
qualifier.isMemory() ||
qualifier.isInterpolation() ||
qualifier.hasLayout() ||
qualifier.storage != EvqTemporary ||
qualifier.precision != EpqNone) {
error(loc, "cannot add storage, auxiliary, memory, interpolation, layout, or precision qualifier to an existing variable", identifier.c_str(), "");
return;
}
// For read-only built-ins, add a new symbol for holding the modified qualifier.
// This will bring up an entire block, if a block type has to be modified (e.g., gl_Position inside a block)
if (symbol->isReadOnly())
symbol = symbolTable.copyUp(symbol);
if (qualifier.invariant) {
if (intermediate.inIoAccessed(identifier))
error(loc, "cannot change qualification after use", "invariant", "");
symbol->getWritableType().getQualifier().invariant = true;
invariantCheck(loc, symbol->getType().getQualifier());
} else if (qualifier.isNoContraction()) {
if (intermediate.inIoAccessed(identifier))
error(loc, "cannot change qualification after use", "precise", "");
symbol->getWritableType().getQualifier().setNoContraction();
} else if (qualifier.specConstant) {
symbol->getWritableType().getQualifier().makeSpecConstant();
if (qualifier.hasSpecConstantId())
symbol->getWritableType().getQualifier().layoutSpecConstantId = qualifier.layoutSpecConstantId;
} else
warn(loc, "unknown requalification", "", "");
}
void TParseContext::addQualifierToExisting(const TSourceLoc& loc, TQualifier qualifier, TIdentifierList& identifiers)
{
for (unsigned int i = 0; i < identifiers.size(); ++i)
addQualifierToExisting(loc, qualifier, *identifiers[i]);
}
// Make sure 'invariant' isn't being applied to a non-allowed object.
void TParseContext::invariantCheck(const TSourceLoc& loc, const TQualifier& qualifier)
{
if (! qualifier.invariant)
return;
bool pipeOut = qualifier.isPipeOutput();
bool pipeIn = qualifier.isPipeInput();
if ((version >= 300 && isEsProfile()) || (!isEsProfile() && version >= 420)) {
if (! pipeOut)
error(loc, "can only apply to an output", "invariant", "");
} else {
if ((language == EShLangVertex && pipeIn) || (! pipeOut && ! pipeIn))
error(loc, "can only apply to an output, or to an input in a non-vertex stage\n", "invariant", "");
}
}
//
// Updating default qualifier for the case of a declaration with just a qualifier,
// no type, block, or identifier.
//
void TParseContext::updateStandaloneQualifierDefaults(const TSourceLoc& loc, const TPublicType& publicType)
{
if (publicType.shaderQualifiers.vertices != TQualifier::layoutNotSet) {
assert(language == EShLangTessControl || language == EShLangGeometry || language == EShLangMesh);
const char* id = (language == EShLangTessControl) ? "vertices" : "max_vertices";
if (publicType.qualifier.storage != EvqVaryingOut)
error(loc, "can only apply to 'out'", id, "");
if (! intermediate.setVertices(publicType.shaderQualifiers.vertices))
error(loc, "cannot change previously set layout value", id, "");
if (language == EShLangTessControl)
checkIoArraysConsistency(loc);
}
if (publicType.shaderQualifiers.primitives != TQualifier::layoutNotSet) {
assert(language == EShLangMesh);
const char* id = "max_primitives";
if (publicType.qualifier.storage != EvqVaryingOut)
error(loc, "can only apply to 'out'", id, "");
if (! intermediate.setPrimitives(publicType.shaderQualifiers.primitives))
error(loc, "cannot change previously set layout value", id, "");
}
if (publicType.shaderQualifiers.invocations != TQualifier::layoutNotSet) {
if (publicType.qualifier.storage != EvqVaryingIn)
error(loc, "can only apply to 'in'", "invocations", "");
if (! intermediate.setInvocations(publicType.shaderQualifiers.invocations))
error(loc, "cannot change previously set layout value", "invocations", "");
}
if (publicType.shaderQualifiers.geometry != ElgNone) {
if (publicType.qualifier.storage == EvqVaryingIn) {
switch (publicType.shaderQualifiers.geometry) {
case ElgPoints:
case ElgLines:
case ElgLinesAdjacency:
case ElgTriangles:
case ElgTrianglesAdjacency:
case ElgQuads:
case ElgIsolines:
if (language == EShLangMesh) {
error(loc, "cannot apply to input", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
}
if (intermediate.setInputPrimitive(publicType.shaderQualifiers.geometry)) {
if (language == EShLangGeometry)
checkIoArraysConsistency(loc);
} else
error(loc, "cannot change previously set input primitive", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
default:
error(loc, "cannot apply to input", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
}
} else if (publicType.qualifier.storage == EvqVaryingOut) {
switch (publicType.shaderQualifiers.geometry) {
case ElgLines:
case ElgTriangles:
if (language != EShLangMesh) {
error(loc, "cannot apply to 'out'", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
}
// Fall through
case ElgPoints:
case ElgLineStrip:
case ElgTriangleStrip:
if (! intermediate.setOutputPrimitive(publicType.shaderQualifiers.geometry))
error(loc, "cannot change previously set output primitive", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
default:
error(loc, "cannot apply to 'out'", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
}
} else
error(loc, "cannot apply to:", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), GetStorageQualifierString(publicType.qualifier.storage));
}
if (publicType.shaderQualifiers.spacing != EvsNone) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if (! intermediate.setVertexSpacing(publicType.shaderQualifiers.spacing))
error(loc, "cannot change previously set vertex spacing", TQualifier::getVertexSpacingString(publicType.shaderQualifiers.spacing), "");
} else
error(loc, "can only apply to 'in'", TQualifier::getVertexSpacingString(publicType.shaderQualifiers.spacing), "");
}
if (publicType.shaderQualifiers.order != EvoNone) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if (! intermediate.setVertexOrder(publicType.shaderQualifiers.order))
error(loc, "cannot change previously set vertex order", TQualifier::getVertexOrderString(publicType.shaderQualifiers.order), "");
} else
error(loc, "can only apply to 'in'", TQualifier::getVertexOrderString(publicType.shaderQualifiers.order), "");
}
if (publicType.shaderQualifiers.pointMode) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setPointMode();
else
error(loc, "can only apply to 'in'", "point_mode", "");
}
for (int i = 0; i < 3; ++i) {
if (publicType.shaderQualifiers.localSizeNotDefault[i]) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if (! intermediate.setLocalSize(i, publicType.shaderQualifiers.localSize[i]))
error(loc, "cannot change previously set size", "local_size", "");
else {
int max = 0;
if (language == EShLangCompute) {
switch (i) {
case 0: max = resources.maxComputeWorkGroupSizeX; break;
case 1: max = resources.maxComputeWorkGroupSizeY; break;
case 2: max = resources.maxComputeWorkGroupSizeZ; break;
default: break;
}
if (intermediate.getLocalSize(i) > (unsigned int)max)
error(loc, "too large; see gl_MaxComputeWorkGroupSize", "local_size", "");
} else if (language == EShLangMesh) {
switch (i) {
case 0:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxMeshWorkGroupSizeX_EXT :
resources.maxMeshWorkGroupSizeX_NV;
break;
case 1:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxMeshWorkGroupSizeY_EXT :
resources.maxMeshWorkGroupSizeY_NV ;
break;
case 2:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxMeshWorkGroupSizeZ_EXT :
resources.maxMeshWorkGroupSizeZ_NV ;
break;
default: break;
}
if (intermediate.getLocalSize(i) > (unsigned int)max) {
TString maxsErrtring = "too large, see ";
maxsErrtring.append(extensionTurnedOn(E_GL_EXT_mesh_shader) ?
"gl_MaxMeshWorkGroupSizeEXT" : "gl_MaxMeshWorkGroupSizeNV");
error(loc, maxsErrtring.c_str(), "local_size", "");
}
} else if (language == EShLangTask) {
switch (i) {
case 0:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxTaskWorkGroupSizeX_EXT :
resources.maxTaskWorkGroupSizeX_NV;
break;
case 1:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxTaskWorkGroupSizeY_EXT:
resources.maxTaskWorkGroupSizeY_NV;
break;
case 2:
max = extensionTurnedOn(E_GL_EXT_mesh_shader) ?
resources.maxTaskWorkGroupSizeZ_EXT:
resources.maxTaskWorkGroupSizeZ_NV;
break;
default: break;
}
if (intermediate.getLocalSize(i) > (unsigned int)max) {
TString maxsErrtring = "too large, see ";
maxsErrtring.append(extensionTurnedOn(E_GL_EXT_mesh_shader) ?
"gl_MaxTaskWorkGroupSizeEXT" : "gl_MaxTaskWorkGroupSizeNV");
error(loc, maxsErrtring.c_str(), "local_size", "");
}
} else {
assert(0);
}
// Fix the existing constant gl_WorkGroupSize with this new information.
TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
if (workGroupSize != nullptr)
workGroupSize->getWritableConstArray()[i].setUConst(intermediate.getLocalSize(i));
}
} else
error(loc, "can only apply to 'in'", "local_size", "");
}
if (publicType.shaderQualifiers.localSizeSpecId[i] != TQualifier::layoutNotSet) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if (! intermediate.setLocalSizeSpecId(i, publicType.shaderQualifiers.localSizeSpecId[i]))
error(loc, "cannot change previously set size", "local_size", "");
} else
error(loc, "can only apply to 'in'", "local_size id", "");
// Set the workgroup built-in variable as a specialization constant
TVariable* workGroupSize = getEditableVariable("gl_WorkGroupSize");
if (workGroupSize != nullptr)
workGroupSize->getWritableType().getQualifier().specConstant = true;
}
}
if (publicType.shaderQualifiers.earlyFragmentTests) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setEarlyFragmentTests();
else
error(loc, "can only apply to 'in'", "early_fragment_tests", "");
}
if (publicType.shaderQualifiers.earlyAndLateFragmentTestsAMD) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setEarlyAndLateFragmentTestsAMD();
else
error(loc, "can only apply to 'in'", "early_and_late_fragment_tests_amd", "");
}
if (publicType.shaderQualifiers.postDepthCoverage) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setPostDepthCoverage();
else
error(loc, "can only apply to 'in'", "post_coverage_coverage", "");
}
if (publicType.shaderQualifiers.nonCoherentColorAttachmentReadEXT) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setNonCoherentColorAttachmentReadEXT();
else
error(loc, "can only apply to 'in'", "non_coherent_color_attachment_readEXT", "");
}
if (publicType.shaderQualifiers.nonCoherentDepthAttachmentReadEXT) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setNonCoherentDepthAttachmentReadEXT();
else
error(loc, "can only apply to 'in'", "non_coherent_depth_attachment_readEXT", "");
}
if (publicType.shaderQualifiers.nonCoherentStencilAttachmentReadEXT) {
if (publicType.qualifier.storage == EvqVaryingIn)
intermediate.setNonCoherentStencilAttachmentReadEXT();
else
error(loc, "can only apply to 'in'", "non_coherent_stencil_attachment_readEXT", "");
}
if (publicType.shaderQualifiers.hasBlendEquation()) {
if (publicType.qualifier.storage != EvqVaryingOut)
error(loc, "can only apply to 'out'", "blend equation", "");
}
if (publicType.shaderQualifiers.interlockOrdering) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if (!intermediate.setInterlockOrdering(publicType.shaderQualifiers.interlockOrdering))
error(loc, "cannot change previously set fragment shader interlock ordering", TQualifier::getInterlockOrderingString(publicType.shaderQualifiers.interlockOrdering), "");
}
else
error(loc, "can only apply to 'in'", TQualifier::getInterlockOrderingString(publicType.shaderQualifiers.interlockOrdering), "");
}
if (publicType.shaderQualifiers.layoutDerivativeGroupQuads &&
publicType.shaderQualifiers.layoutDerivativeGroupLinear) {
error(loc, "cannot be both specified", "derivative_group_quadsNV and derivative_group_linearNV", "");
}
if (publicType.shaderQualifiers.layoutDerivativeGroupQuads) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if ((intermediate.getLocalSize(0) & 1) ||
(intermediate.getLocalSize(1) & 1))
error(loc, "requires local_size_x and local_size_y to be multiple of two", "derivative_group_quadsNV", "");
else
intermediate.setLayoutDerivativeMode(LayoutDerivativeGroupQuads);
}
else
error(loc, "can only apply to 'in'", "derivative_group_quadsNV", "");
}
if (publicType.shaderQualifiers.layoutDerivativeGroupLinear) {
if (publicType.qualifier.storage == EvqVaryingIn) {
if((intermediate.getLocalSize(0) *
intermediate.getLocalSize(1) *
intermediate.getLocalSize(2)) % 4 != 0)
error(loc, "requires total group size to be multiple of four", "derivative_group_linearNV", "");
else
intermediate.setLayoutDerivativeMode(LayoutDerivativeGroupLinear);
}
else
error(loc, "can only apply to 'in'", "derivative_group_linearNV", "");
}
// Check mesh out array sizes, once all the necessary out qualifiers are defined.
if ((language == EShLangMesh) &&
(intermediate.getVertices() != TQualifier::layoutNotSet) &&
(intermediate.getPrimitives() != TQualifier::layoutNotSet) &&
(intermediate.getOutputPrimitive() != ElgNone))
{
checkIoArraysConsistency(loc);
}
if (publicType.shaderQualifiers.layoutPrimitiveCulling) {
if (publicType.qualifier.storage != EvqTemporary)
error(loc, "layout qualifier can not have storage qualifiers", "primitive_culling","", "");
else {
intermediate.setLayoutPrimitiveCulling();
}
// Exit early as further checks are not valid
return;
}
const TQualifier& qualifier = publicType.qualifier;
if (qualifier.isAuxiliary() ||
qualifier.isMemory() ||
qualifier.isInterpolation() ||
qualifier.precision != EpqNone)
error(loc, "cannot use auxiliary, memory, interpolation, or precision qualifier in a default qualifier declaration (declaration with no type)", "qualifier", "");
// "The offset qualifier can only be used on block members of blocks..."
// "The align qualifier can only be used on blocks or block members..."
if (qualifier.hasOffset() ||
qualifier.hasAlign())
error(loc, "cannot use offset or align qualifiers in a default qualifier declaration (declaration with no type)", "layout qualifier", "");
layoutQualifierCheck(loc, qualifier);
switch (qualifier.storage) {
case EvqUniform:
if (qualifier.hasMatrix())
globalUniformDefaults.layoutMatrix = qualifier.layoutMatrix;
if (qualifier.hasPacking())
globalUniformDefaults.layoutPacking = qualifier.layoutPacking;
break;
case EvqBuffer:
if (qualifier.hasMatrix())
globalBufferDefaults.layoutMatrix = qualifier.layoutMatrix;
if (qualifier.hasPacking())
globalBufferDefaults.layoutPacking = qualifier.layoutPacking;
break;
case EvqVaryingIn:
break;
case EvqVaryingOut:
if (qualifier.hasStream())
globalOutputDefaults.layoutStream = qualifier.layoutStream;
if (qualifier.hasXfbBuffer())
globalOutputDefaults.layoutXfbBuffer = qualifier.layoutXfbBuffer;
if (globalOutputDefaults.hasXfbBuffer() && qualifier.hasXfbStride()) {
if (! intermediate.setXfbBufferStride(globalOutputDefaults.layoutXfbBuffer, qualifier.layoutXfbStride))
error(loc, "all stride settings must match for xfb buffer", "xfb_stride", "%d", qualifier.layoutXfbBuffer);
}
break;
case EvqShared:
if (qualifier.hasMatrix())
globalSharedDefaults.layoutMatrix = qualifier.layoutMatrix;
if (qualifier.hasPacking())
globalSharedDefaults.layoutPacking = qualifier.layoutPacking;
break;
default:
error(loc, "default qualifier requires 'uniform', 'buffer', 'in', 'out' or 'shared' storage qualification", "", "");
return;
}
if (qualifier.hasBinding())
error(loc, "cannot declare a default, include a type or full declaration", "binding", "");
if (qualifier.hasAnyLocation())
error(loc, "cannot declare a default, use a full declaration", "location/component/index", "");
if (qualifier.hasXfbOffset())
error(loc, "cannot declare a default, use a full declaration", "xfb_offset", "");
if (qualifier.isPushConstant())
error(loc, "cannot declare a default, can only be used on a block", "push_constant", "");
if (qualifier.hasBufferReference())
error(loc, "cannot declare a default, can only be used on a block", "buffer_reference", "");
if (qualifier.hasSpecConstantId())
error(loc, "cannot declare a default, can only be used on a scalar", "constant_id", "");
if (qualifier.isShaderRecord())
error(loc, "cannot declare a default, can only be used on a block", "shaderRecordNV", "");
}
//
// Take the sequence of statements that has been built up since the last case/default,
// put it on the list of top-level nodes for the current (inner-most) switch statement,
// and follow that by the case/default we are on now. (See switch topology comment on
// TIntermSwitch.)
//
void TParseContext::wrapupSwitchSubsequence(TIntermAggregate* statements, TIntermNode* branchNode)
{
TIntermSequence* switchSequence = switchSequenceStack.back();
if (statements) {
if (switchSequence->size() == 0)
error(statements->getLoc(), "cannot have statements before first case/default label", "switch", "");
statements->setOperator(EOpSequence);
switchSequence->push_back(statements);
}
if (branchNode) {
// check all previous cases for the same label (or both are 'default')
for (unsigned int s = 0; s < switchSequence->size(); ++s) {
TIntermBranch* prevBranch = (*switchSequence)[s]->getAsBranchNode();
if (prevBranch) {
TIntermTyped* prevExpression = prevBranch->getExpression();
TIntermTyped* newExpression = branchNode->getAsBranchNode()->getExpression();
if (prevExpression == nullptr && newExpression == nullptr)
error(branchNode->getLoc(), "duplicate label", "default", "");
else if (prevExpression != nullptr &&
newExpression != nullptr &&
prevExpression->getAsConstantUnion() &&
newExpression->getAsConstantUnion() &&
prevExpression->getAsConstantUnion()->getConstArray()[0].getIConst() ==
newExpression->getAsConstantUnion()->getConstArray()[0].getIConst())
error(branchNode->getLoc(), "duplicated value", "case", "");
}
}
switchSequence->push_back(branchNode);
}
}
//
// Turn the top-level node sequence built up of wrapupSwitchSubsequence9)
// into a switch node.
//
TIntermNode* TParseContext::addSwitch(const TSourceLoc& loc, TIntermTyped* expression, TIntermAggregate* lastStatements)
{
profileRequires(loc, EEsProfile, 300, nullptr, "switch statements");
profileRequires(loc, ENoProfile, 130, nullptr, "switch statements");
wrapupSwitchSubsequence(lastStatements, nullptr);
if (expression == nullptr ||
(expression->getBasicType() != EbtInt && expression->getBasicType() != EbtUint) ||
expression->getType().isArray() || expression->getType().isMatrix() || expression->getType().isVector())
error(loc, "condition must be a scalar integer expression", "switch", "");
// If there is nothing to do, drop the switch but still execute the expression
TIntermSequence* switchSequence = switchSequenceStack.back();
if (switchSequence->size() == 0)
return expression;
if (lastStatements == nullptr) {
// This was originally an ERRROR, because early versions of the specification said
// "it is an error to have no statement between a label and the end of the switch statement."
// The specifications were updated to remove this (being ill-defined what a "statement" was),
// so, this became a warning. However, 3.0 tests still check for the error.
if (isEsProfile() && (version <= 300 || version >= 320) && ! relaxedErrors())
error(loc, "last case/default label not followed by statements", "switch", "");
else if (!isEsProfile() && (version <= 430 || version >= 460))
error(loc, "last case/default label not followed by statements", "switch", "");
else
warn(loc, "last case/default label not followed by statements", "switch", "");
// emulate a break for error recovery
lastStatements = intermediate.makeAggregate(intermediate.addBranch(EOpBreak, loc));
lastStatements->setOperator(EOpSequence);
switchSequence->push_back(lastStatements);
}
TIntermAggregate* body = new TIntermAggregate(EOpSequence);
body->getSequence() = *switchSequenceStack.back();
body->setLoc(loc);
TIntermSwitch* switchNode = new TIntermSwitch(expression, body);
switchNode->setLoc(loc);
return switchNode;
}
//
// When a struct used in block, and has it's own layout packing, layout matrix,
// record the origin structure of a struct to map, and Record the structure copy to the copy table,
//
const TTypeList* TParseContext::recordStructCopy(TStructRecord& record, const TType* originType, const TType* tmpType)
{
size_t memberCount = tmpType->getStruct()->size();
size_t originHash = 0, tmpHash = 0;
std::hash<size_t> hasher;
for (size_t i = 0; i < memberCount; i++) {
size_t originMemberHash = hasher(originType->getStruct()->at(i).type->getQualifier().layoutPacking +
originType->getStruct()->at(i).type->getQualifier().layoutMatrix);
size_t tmpMemberHash = hasher(tmpType->getStruct()->at(i).type->getQualifier().layoutPacking +
tmpType->getStruct()->at(i).type->getQualifier().layoutMatrix);
originHash = hasher((originHash ^ originMemberHash) << 1);
tmpHash = hasher((tmpHash ^ tmpMemberHash) << 1);
}
const TTypeList* originStruct = originType->getStruct();
const TTypeList* tmpStruct = tmpType->getStruct();
if (originHash != tmpHash) {
auto fixRecords = record.find(originStruct);
if (fixRecords != record.end()) {
auto fixRecord = fixRecords->second.find(tmpHash);
if (fixRecord != fixRecords->second.end()) {
return fixRecord->second;
} else {
record[originStruct][tmpHash] = tmpStruct;
return tmpStruct;
}
} else {
record[originStruct] = std::map<size_t, const TTypeList*>();
record[originStruct][tmpHash] = tmpStruct;
return tmpStruct;
}
}
return originStruct;
}
TLayoutFormat TParseContext::mapLegacyLayoutFormat(TLayoutFormat legacyLayoutFormat, TBasicType imageType)
{
TLayoutFormat layoutFormat = ElfNone;
if (imageType == EbtFloat) {
switch (legacyLayoutFormat) {
case ElfSize1x16: layoutFormat = ElfR16f; break;
case ElfSize1x32: layoutFormat = ElfR32f; break;
case ElfSize2x32: layoutFormat = ElfRg32f; break;
case ElfSize4x32: layoutFormat = ElfRgba32f; break;
default: break;
}
} else if (imageType == EbtUint) {
switch (legacyLayoutFormat) {
case ElfSize1x8: layoutFormat = ElfR8ui; break;
case ElfSize1x16: layoutFormat = ElfR16ui; break;
case ElfSize1x32: layoutFormat = ElfR32ui; break;
case ElfSize2x32: layoutFormat = ElfRg32ui; break;
case ElfSize4x32: layoutFormat = ElfRgba32ui; break;
default: break;
}
} else if (imageType == EbtInt) {
switch (legacyLayoutFormat) {
case ElfSize1x8: layoutFormat = ElfR8i; break;
case ElfSize1x16: layoutFormat = ElfR16i; break;
case ElfSize1x32: layoutFormat = ElfR32i; break;
case ElfSize2x32: layoutFormat = ElfRg32i; break;
case ElfSize4x32: layoutFormat = ElfRgba32i; break;
default: break;
}
}
return layoutFormat;
}
} // end namespace glslang