blob: 54cd10fcc6dd2516218183874cd0a00784417357 [file] [log] [blame]
//
//Copyright (C) 2016 Google, Inc.
//Copyright (C) 2016 LunarG, Inc.
//
//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 "hlslParseHelper.h"
#include "hlslScanContext.h"
#include "hlslGrammar.h"
#include "hlslAttributes.h"
#include "../glslang/MachineIndependent/Scan.h"
#include "../glslang/MachineIndependent/preprocessor/PpContext.h"
#include "../glslang/OSDependent/osinclude.h"
#include <algorithm>
#include <functional>
#include <cctype>
namespace glslang {
HlslParseContext::HlslParseContext(TSymbolTable& symbolTable, TIntermediate& interm, bool parsingBuiltins,
int version, EProfile profile, const SpvVersion& spvVersion, EShLanguage language, TInfoSink& infoSink,
const TString sourceEntryPointName,
bool forwardCompatible, EShMessages messages) :
TParseContextBase(symbolTable, interm, parsingBuiltins, version, profile, spvVersion, language, infoSink, forwardCompatible, messages),
contextPragma(true, false),
loopNestingLevel(0), annotationNestingLevel(0), structNestingLevel(0), controlFlowNestingLevel(0),
postEntryPointReturn(false),
limits(resources.limits),
entryPointOutput(nullptr),
nextInLocation(0), nextOutLocation(0),
sourceEntryPointName(sourceEntryPointName)
{
globalUniformDefaults.clear();
globalUniformDefaults.layoutMatrix = ElmRowMajor;
globalUniformDefaults.layoutPacking = ElpStd140;
globalBufferDefaults.clear();
globalBufferDefaults.layoutMatrix = ElmRowMajor;
globalBufferDefaults.layoutPacking = ElpStd430;
globalInputDefaults.clear();
globalOutputDefaults.clear();
// "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 (spvVersion.spv == 0 || spvVersion.vulkan == 0)
infoSink.info << "ERROR: HLSL currently only supported when requesting SPIR-V for Vulkan.\n";
}
HlslParseContext::~HlslParseContext()
{
}
void HlslParseContext::initializeExtensionBehavior()
{
TParseContextBase::initializeExtensionBehavior();
// HLSL allows #line by default.
extensionBehavior[E_GL_GOOGLE_cpp_style_line_directive] = EBhEnable;
}
void HlslParseContext::setLimits(const TBuiltInResource& r)
{
resources = r;
intermediate.setLimits(resources);
}
//
// Parse an array of strings using the parser in HlslRules.
//
// Returns true for successful acceptance of the shader, false if any errors.
//
bool HlslParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner& input, bool versionWillBeError)
{
currentScanner = &input;
ppContext.setInput(input, versionWillBeError);
HlslScanContext scanContext(*this, ppContext);
HlslGrammar grammar(scanContext, *this);
if (!grammar.parse()) {
// Print a message formated such that if you click on the message it will take you right to
// the line through most UIs.
const glslang::TSourceLoc& sourceLoc = input.getSourceLoc();
infoSink.info << sourceLoc.name << "(" << sourceLoc.line << "): error at column " << sourceLoc.column << ", HLSL parsing failed.\n";
++numErrors;
return false;
}
finish();
return numErrors == 0;
}
//
// Return true if this l-value node should be converted in some manner.
// For instance: turning a load aggregate into a store in an l-value.
//
bool HlslParseContext::shouldConvertLValue(const TIntermNode* node) const
{
if (node == nullptr)
return false;
const TIntermAggregate* lhsAsAggregate = node->getAsAggregate();
if (lhsAsAggregate != nullptr && lhsAsAggregate->getOp() == EOpImageLoad)
return true;
return false;
}
//
// Return a TLayoutFormat corresponding to the given texture type.
//
TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const TType& txType)
{
const int components = txType.getVectorSize();
const auto selectFormat = [this,&components](TLayoutFormat v1, TLayoutFormat v2, TLayoutFormat v4) -> TLayoutFormat {
if (intermediate.getNoStorageFormat())
return ElfNone;
return components == 1 ? v1 :
components == 2 ? v2 : v4;
};
switch (txType.getBasicType()) {
case EbtFloat: return selectFormat(ElfR32f, ElfRg32f, ElfRgba32f);
case EbtInt: return selectFormat(ElfR32i, ElfRg32i, ElfRgba32i);
case EbtUint: return selectFormat(ElfR32ui, ElfRg32ui, ElfRgba32ui);
default:
error(loc, "unknown basic type in image format", "", "");
return ElfNone;
}
}
//
// 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 HlslParseContext::lValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
if (shouldConvertLValue(node)) {
// if we're writing to a texture, it must be an RW form.
TIntermAggregate* lhsAsAggregate = node->getAsAggregate();
TIntermTyped* object = lhsAsAggregate->getSequence()[0]->getAsTyped();
if (!object->getType().getSampler().isImage()) {
error(loc, "operator[] on a non-RW texture must be an r-value", "", "");
return true;
}
}
// Let the base class check errors
return TParseContextBase::lValueErrorCheck(loc, op, node);
}
//
// This function handles l-value conversions and verifications. It uses, but is not synonymous
// with lValueErrorCheck. That function accepts an l-value directly, while this one must be
// given the surrounding tree - e.g, with an assignment, so we can convert the assign into a
// series of other image operations.
//
// Most things are passed through unmodified, except for error checking.
//
TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
if (node == nullptr)
return nullptr;
TIntermBinary* nodeAsBinary = node->getAsBinaryNode();
TIntermUnary* nodeAsUnary = node->getAsUnaryNode();
TIntermAggregate* sequence = nullptr;
TIntermTyped* lhs = nodeAsUnary ? nodeAsUnary->getOperand() :
nodeAsBinary ? nodeAsBinary->getLeft() :
nullptr;
// Early bail out if there is no conversion to apply
if (!shouldConvertLValue(lhs)) {
if (lhs != nullptr)
if (lValueErrorCheck(loc, op, lhs))
return nullptr;
return node;
}
// *** If we get here, we're going to apply some conversion to an l-value.
// Helper to create a load.
const auto makeLoad = [&](TIntermSymbol* rhsTmp, TIntermTyped* object, TIntermTyped* coord, const TType& derefType) {
TIntermAggregate* loadOp = new TIntermAggregate(EOpImageLoad);
loadOp->setLoc(loc);
loadOp->getSequence().push_back(object);
loadOp->getSequence().push_back(intermediate.addSymbol(*coord->getAsSymbolNode()));
loadOp->setType(derefType);
sequence = intermediate.growAggregate(sequence,
intermediate.addAssign(EOpAssign, rhsTmp, loadOp, loc),
loc);
};
// Helper to create a store.
const auto makeStore = [&](TIntermTyped* object, TIntermTyped* coord, TIntermSymbol* rhsTmp) {
TIntermAggregate* storeOp = new TIntermAggregate(EOpImageStore);
storeOp->getSequence().push_back(object);
storeOp->getSequence().push_back(coord);
storeOp->getSequence().push_back(intermediate.addSymbol(*rhsTmp));
storeOp->setLoc(loc);
storeOp->setType(TType(EbtVoid));
sequence = intermediate.growAggregate(sequence, storeOp);
};
// Helper to create an assign.
const auto makeBinary = [&](TOperator op, TIntermTyped* lhs, TIntermTyped* rhs) {
sequence = intermediate.growAggregate(sequence,
intermediate.addBinaryNode(op, lhs, rhs, loc, lhs->getType()),
loc);
};
// Helper to complete sequence by adding trailing variable, so we evaluate to the right value.
const auto finishSequence = [&](TIntermSymbol* rhsTmp, const TType& derefType) -> TIntermAggregate* {
// Add a trailing use of the temp, so the sequence returns the proper value.
sequence = intermediate.growAggregate(sequence, intermediate.addSymbol(*rhsTmp));
sequence->setOperator(EOpSequence);
sequence->setLoc(loc);
sequence->setType(derefType);
return sequence;
};
// Helper to add unary op
const auto makeUnary = [&](TOperator op, TIntermSymbol* rhsTmp) {
sequence = intermediate.growAggregate(sequence,
intermediate.addUnaryNode(op, intermediate.addSymbol(*rhsTmp), loc,
rhsTmp->getType()),
loc);
};
// helper to create a temporary variable
const auto addTmpVar = [&](const char* name, const TType& derefType) -> TIntermSymbol* {
TVariable* tmpVar = makeInternalVariable(name, derefType);
tmpVar->getWritableType().getQualifier().makeTemporary();
return intermediate.addSymbol(*tmpVar, loc);
};
TIntermAggregate* lhsAsAggregate = lhs->getAsAggregate();
TIntermTyped* object = lhsAsAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* coord = lhsAsAggregate->getSequence()[1]->getAsTyped();
const TSampler& texSampler = object->getType().getSampler();
const TType objDerefType(texSampler.type, EvqTemporary, texSampler.vectorSize);
if (nodeAsBinary) {
TIntermTyped* rhs = nodeAsBinary->getRight();
const TOperator assignOp = nodeAsBinary->getOp();
bool isModifyOp = false;
switch (assignOp) {
case EOpAddAssign:
case EOpSubAssign:
case EOpMulAssign:
case EOpVectorTimesMatrixAssign:
case EOpVectorTimesScalarAssign:
case EOpMatrixTimesScalarAssign:
case EOpMatrixTimesMatrixAssign:
case EOpDivAssign:
case EOpModAssign:
case EOpAndAssign:
case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign:
case EOpLeftShiftAssign:
case EOpRightShiftAssign:
isModifyOp = true;
// fall through...
case EOpAssign:
{
// Since this is an lvalue, we'll convert an image load to a sequence like this (to still provide the value):
// OpSequence
// OpImageStore(object, lhs, rhs)
// rhs
// But if it's not a simple symbol RHS (say, a fn call), we don't want to duplicate the RHS, so we'll convert
// instead to this:
// OpSequence
// rhsTmp = rhs
// OpImageStore(object, coord, rhsTmp)
// rhsTmp
// If this is a read-modify-write op, like +=, we issue:
// OpSequence
// coordtmp = load's param1
// rhsTmp = OpImageLoad(object, coordTmp)
// rhsTmp op= rhs
// OpImageStore(object, coordTmp, rhsTmp)
// rhsTmp
TIntermSymbol* rhsTmp = rhs->getAsSymbolNode();
TIntermTyped* coordTmp = coord;
if (rhsTmp == nullptr || isModifyOp) {
rhsTmp = addTmpVar("storeTemp", objDerefType);
// Assign storeTemp = rhs
if (isModifyOp) {
// We have to make a temp var for the coordinate, to avoid evaluating it twice.
coordTmp = addTmpVar("coordTemp", coord->getType());
makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
makeLoad(rhsTmp, object, coordTmp, objDerefType); // rhsTmp = OpImageLoad(object, coordTmp)
}
// rhsTmp op= rhs.
makeBinary(assignOp, intermediate.addSymbol(*rhsTmp), rhs);
}
makeStore(object, coordTmp, rhsTmp); // add a store
return finishSequence(rhsTmp, objDerefType); // return rhsTmp from sequence
}
default:
break;
}
}
if (nodeAsUnary) {
const TOperator assignOp = nodeAsUnary->getOp();
switch (assignOp) {
case EOpPreIncrement:
case EOpPreDecrement:
{
// We turn this into:
// OpSequence
// coordtmp = load's param1
// rhsTmp = OpImageLoad(object, coordTmp)
// rhsTmp op
// OpImageStore(object, coordTmp, rhsTmp)
// rhsTmp
TIntermSymbol* rhsTmp = addTmpVar("storeTemp", objDerefType);
TIntermTyped* coordTmp = addTmpVar("coordTemp", coord->getType());
makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
makeLoad(rhsTmp, object, coordTmp, objDerefType); // rhsTmp = OpImageLoad(object, coordTmp)
makeUnary(assignOp, rhsTmp); // op rhsTmp
makeStore(object, coordTmp, rhsTmp); // OpImageStore(object, coordTmp, rhsTmp)
return finishSequence(rhsTmp, objDerefType); // return rhsTmp from sequence
}
case EOpPostIncrement:
case EOpPostDecrement:
{
// We turn this into:
// OpSequence
// coordtmp = load's param1
// rhsTmp1 = OpImageLoad(object, coordTmp)
// rhsTmp2 = rhsTmp1
// rhsTmp2 op
// OpImageStore(object, coordTmp, rhsTmp2)
// rhsTmp1 (pre-op value)
TIntermSymbol* rhsTmp1 = addTmpVar("storeTempPre", objDerefType);
TIntermSymbol* rhsTmp2 = addTmpVar("storeTempPost", objDerefType);
TIntermTyped* coordTmp = addTmpVar("coordTemp", coord->getType());
makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1]
makeLoad(rhsTmp1, object, coordTmp, objDerefType); // rhsTmp1 = OpImageLoad(object, coordTmp)
makeBinary(EOpAssign, rhsTmp2, rhsTmp1); // rhsTmp2 = rhsTmp1
makeUnary(assignOp, rhsTmp2); // rhsTmp op
makeStore(object, coordTmp, rhsTmp2); // OpImageStore(object, coordTmp, rhsTmp2)
return finishSequence(rhsTmp1, objDerefType); // return rhsTmp from sequence
}
default:
break;
}
}
if (lhs)
if (lValueErrorCheck(loc, op, lhs))
return nullptr;
return node;
}
void HlslParseContext::handlePragma(const TSourceLoc& loc, const TVector<TString>& tokens)
{
if (pragmaCallback)
pragmaCallback(loc.line, tokens);
if (tokens.size() == 0)
return;
}
//
// Look at a '.' field selector string and change it into offsets
// for a vector or scalar
//
// Returns true if there is no error.
//
bool HlslParseContext::parseVectorFields(const TSourceLoc& loc, const TString& compString, int vecSize, TVectorFields& fields)
{
fields.num = (int)compString.size();
if (fields.num > 4) {
error(loc, "illegal vector field selection", compString.c_str(), "");
return false;
}
enum {
exyzw,
ergba,
estpq,
} fieldSet[4];
for (int i = 0; i < fields.num; ++i) {
switch (compString[i]) {
case 'x':
fields.offsets[i] = 0;
fieldSet[i] = exyzw;
break;
case 'r':
fields.offsets[i] = 0;
fieldSet[i] = ergba;
break;
case 's':
fields.offsets[i] = 0;
fieldSet[i] = estpq;
break;
case 'y':
fields.offsets[i] = 1;
fieldSet[i] = exyzw;
break;
case 'g':
fields.offsets[i] = 1;
fieldSet[i] = ergba;
break;
case 't':
fields.offsets[i] = 1;
fieldSet[i] = estpq;
break;
case 'z':
fields.offsets[i] = 2;
fieldSet[i] = exyzw;
break;
case 'b':
fields.offsets[i] = 2;
fieldSet[i] = ergba;
break;
case 'p':
fields.offsets[i] = 2;
fieldSet[i] = estpq;
break;
case 'w':
fields.offsets[i] = 3;
fieldSet[i] = exyzw;
break;
case 'a':
fields.offsets[i] = 3;
fieldSet[i] = ergba;
break;
case 'q':
fields.offsets[i] = 3;
fieldSet[i] = estpq;
break;
default:
error(loc, "illegal vector field selection", compString.c_str(), "");
return false;
}
}
for (int i = 0; i < fields.num; ++i) {
if (fields.offsets[i] >= vecSize) {
error(loc, "vector field selection out of range", compString.c_str(), "");
return false;
}
if (i > 0) {
if (fieldSet[i] != fieldSet[i - 1]) {
error(loc, "illegal - vector component fields not from the same set", compString.c_str(), "");
return false;
}
}
}
return true;
}
//
// Handle seeing a variable identifier in the grammar.
//
TIntermTyped* HlslParseContext::handleVariable(const TSourceLoc& loc, TSymbol* symbol, const TString* string)
{
if (symbol == nullptr)
symbol = symbolTable.find(*string);
if (symbol && symbol->getAsVariable() && symbol->getAsVariable()->isUserType()) {
error(loc, "expected symbol, not user-defined type", string->c_str(), "");
return nullptr;
}
// Error check for requiring specific extensions present.
if (symbol && symbol->getNumExtensions())
requireExtensions(loc, symbol->getNumExtensions(), symbol->getExtensions(), symbol->getName().c_str());
const TVariable* variable;
const TAnonMember* anon = symbol ? symbol->getAsAnonMember() : nullptr;
TIntermTyped* node = 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().getBasicType() == EbtBlock ||
variable->getType().getBasicType() == EbtStruct) && variable->getType().getStruct() == nullptr) {
error(loc, "cannot be used (maybe an instance name is needed)", 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);
return node;
}
//
// Handle operator[] on any objects it applies to. Currently:
// Textures
// Buffers
//
TIntermTyped* HlslParseContext::handleBracketOperator(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
{
// handle r-value operator[] on textures and images. l-values will be processed later.
if (base->getType().getBasicType() == EbtSampler && !base->isArray()) {
const TSampler& sampler = base->getType().getSampler();
if (sampler.isImage() || sampler.isTexture()) {
TIntermAggregate* load = new TIntermAggregate(sampler.isImage() ? EOpImageLoad : EOpTextureFetch);
load->setType(TType(sampler.type, EvqTemporary, sampler.vectorSize));
load->setLoc(loc);
load->getSequence().push_back(base);
load->getSequence().push_back(index);
// Textures need a MIP. First indirection is always to mip 0. If there's another, we'll add it
// later.
if (sampler.isTexture())
load->getSequence().push_back(intermediate.addConstantUnion(0, loc, true));
return load;
}
}
return nullptr;
}
//
// Handle seeing a base[index] dereference in the grammar.
//
TIntermTyped* HlslParseContext::handleBracketDereference(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
{
TIntermTyped* result = handleBracketOperator(loc, base, index);
if (result != nullptr)
return result; // it was handled as an operator[]
bool flattened = false;
int indexValue = 0;
if (index->getQualifier().storage == EvqConst) {
indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst();
checkIndex(loc, base->getType(), indexValue);
}
variableCheck(base);
if (! base->isArray() && ! base->isMatrix() && ! base->isVector()) {
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", "");
} else if (base->getType().getQualifier().storage == EvqConst && index->getQualifier().storage == EvqConst)
return intermediate.foldDereference(base, indexValue, loc);
else {
// at least one of base and index is variable...
if (base->getAsSymbolNode() && (wasFlattened(base) || shouldFlatten(base->getType()))) {
if (index->getQualifier().storage != EvqConst)
error(loc, "Invalid variable index to flattened uniform array", base->getAsSymbolNode()->getName().c_str(), "");
result = flattenAccess(loc, base, indexValue);
flattened = (result != base);
} else {
if (index->getQualifier().storage == EvqConst) {
if (base->getType().isImplicitlySizedArray())
updateImplicitArraySize(loc, base, indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
}
}
if (result == nullptr) {
// Insert dummy error-recovery result
result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
} else {
// If the array reference was flattened, it has the correct type. E.g, if it was
// a uniform array, it was flattened INTO a set of scalar uniforms, not scalar temps.
// In that case, we preserve the qualifiers.
if (!flattened) {
// Insert valid dereferenced result
TType newType(base->getType(), 0); // dereferenced type
if (base->getType().getQualifier().storage == EvqConst && index->getQualifier().storage == EvqConst)
newType.getQualifier().storage = EvqConst;
else
newType.getQualifier().storage = EvqTemporary;
result->setType(newType);
}
}
return result;
}
void HlslParseContext::checkIndex(const TSourceLoc& /*loc*/, const TType& /*type*/, int& /*index*/)
{
// HLSL todo: any rules for index fixups?
}
// Handle seeing a binary node with a math operation.
TIntermTyped* HlslParseContext::handleBinaryMath(const TSourceLoc& loc, const char* str, TOperator op, TIntermTyped* left, TIntermTyped* right)
{
TIntermTyped* result = intermediate.addBinaryMath(op, left, right, loc);
if (! result)
binaryOpError(loc, str, left->getCompleteString(), right->getCompleteString());
return result;
}
// Handle seeing a unary node with a math operation.
TIntermTyped* HlslParseContext::handleUnaryMath(const TSourceLoc& loc, const char* str, TOperator op, TIntermTyped* childNode)
{
TIntermTyped* result = intermediate.addUnaryMath(op, childNode, loc);
if (result)
return result;
else
unaryOpError(loc, str, childNode->getCompleteString());
return childNode;
}
//
// Handle seeing a base.field dereference in the grammar.
//
TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TIntermTyped* base, const TString& field)
{
variableCheck(base);
//
// methods 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(), etc.
//
if (field == "length") {
return intermediate.addMethod(base, TType(EbtInt), &field, loc);
} else if (field == "CalculateLevelOfDetail" ||
field == "CalculateLevelOfDetailUnclamped" ||
field == "Gather" ||
field == "GatherRed" ||
field == "GatherGreen" ||
field == "GatherBlue" ||
field == "GatherAlpha" ||
field == "GatherCmp" ||
field == "GatherCmpRed" ||
field == "GatherCmpGreen" ||
field == "GatherCmpBlue" ||
field == "GatherCmpAlpha" ||
field == "GetDimensions" ||
field == "GetSamplePosition" ||
field == "Load" ||
field == "Sample" ||
field == "SampleBias" ||
field == "SampleCmp" ||
field == "SampleCmpLevelZero" ||
field == "SampleGrad" ||
field == "SampleLevel") {
// If it's not a method on a sampler object, we fall through in case it is a struct member.
if (base->getType().getBasicType() == EbtSampler) {
const TSampler& sampler = base->getType().getSampler();
if (! sampler.isPureSampler()) {
const int vecSize = sampler.isShadow() ? 1 : 4; // TODO: handle arbitrary sample return sizes
return intermediate.addMethod(base, TType(sampler.type, EvqTemporary, vecSize), &field, loc);
}
}
} else if (field == "Append" ||
field == "RestartStrip") {
// These methods only valid on stage in variables
// TODO: ... which are stream out types, if there's any way to test that here.
if (base->getType().getQualifier().storage == EvqVaryingOut) {
return intermediate.addMethod(base, TType(EbtVoid), &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;
}
// 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()) {
TVectorFields fields;
if (! parseVectorFields(loc, field, base->getVectorSize(), fields)) {
fields.num = 1;
fields.offsets[0] = 0;
}
if (base->isScalar()) {
if (fields.num == 1)
return result;
else {
TType type(base->getBasicType(), EvqTemporary, fields.num);
return addConstructor(loc, base, type);
}
}
if (base->getVectorSize() == 1) {
TType scalarType(base->getBasicType(), EvqTemporary, 1);
if (fields.num == 1)
return addConstructor(loc, base, scalarType);
else {
TType vectorType(base->getBasicType(), EvqTemporary, fields.num);
return addConstructor(loc, addConstructor(loc, base, scalarType), vectorType);
}
}
if (base->getType().getQualifier().isFrontEndConstant())
result = intermediate.foldSwizzle(base, fields, loc);
else {
if (fields.num == 1) {
TIntermTyped* index = intermediate.addConstantUnion(fields.offsets[0], loc);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary));
} else {
TString vectorString = field;
TIntermTyped* index = intermediate.addSwizzle(fields, loc);
result = intermediate.addIndex(EOpVectorSwizzle, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision, (int)vectorString.size()));
}
}
} else if (base->getBasicType() == EbtStruct || base->getBasicType() == EbtBlock) {
const TTypeList* fields = 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->getAsSymbolNode() && (wasFlattened(base) || shouldFlatten(base->getType())))
result = flattenAccess(loc, base, member);
else {
if (base->getType().getQualifier().storage == EvqConst)
result = intermediate.foldDereference(base, member, loc);
else {
TIntermTyped* index = intermediate.addConstantUnion(member, loc);
result = intermediate.addIndex(EOpIndexDirectStruct, base, index, loc);
result->setType(*(*fields)[member].type);
}
}
} 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().c_str());
return result;
}
// Determine whether we should flatten an arbitrary type.
bool HlslParseContext::shouldFlatten(const TType& type) const
{
return shouldFlattenIO(type) || shouldFlattenUniform(type);
}
// Is this an IO variable that can't be passed down the stack?
// E.g., pipeline inputs to the vertex stage and outputs from the fragment stage.
bool HlslParseContext::shouldFlattenIO(const TType& type) const
{
if (! inEntryPoint)
return false;
const TStorageQualifier qualifier = type.getQualifier().storage;
return type.isStruct() &&
(qualifier == EvqVaryingIn ||
qualifier == EvqVaryingOut);
}
// Is this a uniform array which should be flattened?
bool HlslParseContext::shouldFlattenUniform(const TType& type) const
{
const TStorageQualifier qualifier = type.getQualifier().storage;
return ((type.isArray() && intermediate.getFlattenUniformArrays()) || type.isStruct()) &&
qualifier == EvqUniform &&
type.containsOpaque();
}
// Top level variable flattening: construct data
void HlslParseContext::flatten(const TSourceLoc& loc, const TVariable& variable)
{
const TType& type = variable.getType();
// emplace gives back a pair whose .first is an iterator to the item...
auto entry = flattenMap.emplace(variable.getUniqueId(),
TFlattenData(type.getQualifier().layoutBinding));
// ... and the item is a map pair, so first->second is the TFlattenData itself.
flatten(loc, variable, type, entry.first->second, "");
}
// Recursively flatten the given variable at the provided type, building the flattenData as we go.
//
// This is mutually recursive with flattenStruct and flattenArray.
// We are going to flatten an arbitrarily nested composite structure into a linear sequence of
// members, and later on, we want to turn a path through the tree structure into a final
// location in this linear sequence.
//
// If the tree was N-ary, that can be directly calculated. However, we are dealing with
// arbitrary numbers - peraps a struct of 7 members containing an array of 3. Thus, we must
// build a data structure to allow the sequence of bracket and dot operators on arrays and
// structs to arrive at the proper member.
//
// To avoid storing a tree with pointers, we are going to flatten the tree into a vector of integers.
// The leaves are the indexes into the flattened member array.
// Each level will have the next location for the Nth item stored sequentially, so for instance:
//
// struct { float2 a[2]; int b; float4 c[3] };
//
// This will produce the following flattened tree:
// Pos: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
// (3, 7, 8, 5, 6, 0, 1, 2, 11, 12, 13, 3, 4, 5}
//
// Given a reference to mystruct.c[1], the access chain is (2,1), so we traverse:
// (0+2) = 8 --> (8+1) = 12 --> 12 = 4
//
// so the 4th flattened member in traversal order is ours.
//
int HlslParseContext::flatten(const TSourceLoc& loc, const TVariable& variable, const TType& type,
TFlattenData& flattenData, TString name)
{
// TODO: when struct splitting is in place we can remove this restriction.
if (language == EShLangGeometry) {
const TType derefType(type, 0);
if (!isFinalFlattening(derefType) && type.getQualifier().storage == EvqVaryingIn)
error(loc, "recursive type not yet supported in GS input", variable.getName().c_str(), "");
}
// If something is an arrayed struct, the array flattener will recursively call flatten()
// to then flatten the struct, so this is an "if else": we don't do both.
if (type.isArray())
return flattenArray(loc, variable, type, flattenData, name);
else if (type.isStruct())
return flattenStruct(loc, variable, type, flattenData, name);
else {
assert(0); // should never happen
return -1;
}
}
// Add a single flattened member to the flattened data being tracked for the composite
// Returns true for the final flattening level.
int HlslParseContext::addFlattenedMember(const TSourceLoc& loc,
const TVariable& variable, const TType& type, TFlattenData& flattenData,
const TString& memberName, bool track)
{
if (isFinalFlattening(type)) {
// This is as far as we flatten. Insert the variable.
TVariable* memberVariable = makeInternalVariable(memberName.c_str(), type);
mergeQualifiers(memberVariable->getWritableType().getQualifier(), variable.getType().getQualifier());
if (flattenData.nextBinding != TQualifier::layoutBindingEnd)
memberVariable->getWritableType().getQualifier().layoutBinding = flattenData.nextBinding++;
flattenData.offsets.push_back(flattenData.members.size());
flattenData.members.push_back(memberVariable);
if (track)
trackLinkageDeferred(*memberVariable);
return flattenData.offsets.size()-1; // location of the member reference
} else {
// Further recursion required
return flatten(loc, variable, type, flattenData, memberName);
}
}
// Figure out the mapping between an aggregate's top members and an
// equivalent set of individual variables.
//
// N.B. Erases memory of I/O-related annotations in the original type's member,
// effecting a transfer of this information to the flattened variable form.
//
// Assumes shouldFlatten() or equivalent was called first.
int HlslParseContext::flattenStruct(const TSourceLoc& loc, const TVariable& variable, const TType& type,
TFlattenData& flattenData, TString name)
{
assert(type.isStruct());
auto members = *type.getStruct();
// Reserve space for this tree level.
int start = flattenData.offsets.size();
int pos = start;
flattenData.offsets.resize(int(pos + members.size()), -1);
for (int member = 0; member < (int)members.size(); ++member) {
TType& dereferencedType = *members[member].type;
const TString memberName = name + (name.empty() ? "" : ".") + dereferencedType.getFieldName();
const int mpos = addFlattenedMember(loc, variable, dereferencedType, flattenData, memberName, false);
flattenData.offsets[pos++] = mpos;
// N.B. Erase I/O-related annotations from the source-type member.
dereferencedType.getQualifier().makeTemporary();
}
return start;
}
// Figure out mapping between an array's members and an
// equivalent set of individual variables.
//
// Assumes shouldFlatten() or equivalent was called first.
int HlslParseContext::flattenArray(const TSourceLoc& loc, const TVariable& variable, const TType& type,
TFlattenData& flattenData, TString name)
{
assert(type.isArray());
if (type.isImplicitlySizedArray())
error(loc, "cannot flatten implicitly sized array", variable.getName().c_str(), "");
const int size = type.getOuterArraySize();
const TType dereferencedType(type, 0);
if (name.empty())
name = variable.getName();
// Reserve space for this tree level.
int start = flattenData.offsets.size();
int pos = start;
flattenData.offsets.resize(int(pos + size), -1);
for (int element=0; element < size; ++element) {
char elementNumBuf[20]; // sufficient for MAXINT
snprintf(elementNumBuf, sizeof(elementNumBuf)-1, "[%d]", element);
const int mpos = addFlattenedMember(loc, variable, dereferencedType, flattenData,
name + elementNumBuf, true);
flattenData.offsets[pos++] = mpos;
}
return start;
}
// Return true if we have flattened this node.
bool HlslParseContext::wasFlattened(const TIntermTyped* node) const
{
return node != nullptr &&
node->getAsSymbolNode() != nullptr &&
wasFlattened(node->getAsSymbolNode()->getId());
}
// Turn an access into an aggregate that was flattened to instead be
// an access to the individual variable the member was flattened to.
// Assumes shouldFlatten() or equivalent was called first.
TIntermTyped* HlslParseContext::flattenAccess(const TSourceLoc&, TIntermTyped* base, int member)
{
const TType dereferencedType(base->getType(), member); // dereferenced type
const TIntermSymbol& symbolNode = *base->getAsSymbolNode();
const auto flattenData = flattenMap.find(symbolNode.getId());
if (flattenData == flattenMap.end())
return base;
// Calculate new cumulative offset from the packed tree
flattenOffset.back() = flattenData->second.offsets[flattenOffset.back() + member];
if (isFinalFlattening(dereferencedType)) {
// Finished flattening: create symbol for variable
member = flattenData->second.offsets[flattenOffset.back()];
const TVariable* memberVariable = flattenData->second.members[member];
return intermediate.addSymbol(*memberVariable);
} else {
// If this is not the final flattening, accumulate the position and return
// an object of the partially dereferenced type.
return new TIntermSymbol(symbolNode.getId(), "flattenShadow", dereferencedType);
}
}
// Variables that correspond to the user-interface in and out of a stage
// (not the built-in interface) are assigned locations and
// registered as a linkage node (part of the stage's external interface).
//
// Assumes it is called in the order in which locations should be assigned.
void HlslParseContext::assignLocations(TVariable& variable)
{
const auto assignLocation = [&](TVariable& variable) {
const TQualifier& qualifier = variable.getType().getQualifier();
if (qualifier.storage == EvqVaryingIn || qualifier.storage == EvqVaryingOut) {
if (qualifier.builtIn == EbvNone) {
if (qualifier.storage == EvqVaryingIn) {
variable.getWritableType().getQualifier().layoutLocation = nextInLocation;
nextInLocation += intermediate.computeTypeLocationSize(variable.getType());
} else {
variable.getWritableType().getQualifier().layoutLocation = nextOutLocation;
nextOutLocation += intermediate.computeTypeLocationSize(variable.getType());
}
}
trackLinkage(variable);
}
};
if (wasFlattened(variable.getUniqueId())) {
auto& memberList = flattenMap[variable.getUniqueId()].members;
for (auto member = memberList.begin(); member != memberList.end(); ++member)
assignLocation(**member);
} else
assignLocation(variable);
}
//
// Handle seeing a function declarator in the grammar. This is the precursor
// to recognizing a function prototype or function definition.
//
TFunction& HlslParseContext::handleFunctionDeclarator(const TSourceLoc& loc, TFunction& function, bool prototype)
{
//
// 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).
//
bool builtIn;
TSymbol* symbol = symbolTable.find(function.getMangledName(), &builtIn);
const TFunction* prevDec = symbol ? symbol->getAsFunction() : 0;
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* HlslParseContext::handleFunctionDefinition(const TSourceLoc& loc, TFunction& function,
const TAttributeMap& attributes)
{
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;
inEntryPoint = function.getName().compare(intermediate.getEntryPointName().c_str()) == 0;
if (inEntryPoint) {
intermediate.setEntryPointMangledName(function.getMangledName().c_str());
intermediate.incrementEntryPointCount();
remapEntryPointIO(function);
if (entryPointOutput) {
if (shouldFlatten(entryPointOutput->getType()))
flatten(loc, *entryPointOutput);
assignLocations(*entryPointOutput);
}
} else
remapNonEntryPointIO(function);
// Insert the $Global constant buffer.
// TODO: this design fails if new members are declared between function definitions.
if (! insertGlobalUniformBlock())
error(loc, "failed to insert the global constant buffer", "uniform", "");
//
// New symbol table scope for body of function plus its arguments
//
pushScope();
//
// 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 AST, 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 {
// get IO straightened out
if (inEntryPoint) {
if (shouldFlatten(*param.type))
flatten(loc, *variable);
assignLocations(*variable);
}
// Transfer ownership of name pointer to symbol table.
param.name = nullptr;
// Add the parameter to the AST
paramNodes = intermediate.growAggregate(paramNodes,
intermediate.addSymbol(*variable, loc),
loc);
}
} else
paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(*param.type, loc), loc);
}
intermediate.setAggregateOperator(paramNodes, EOpParameters, TType(EbtVoid), loc);
loopNestingLevel = 0;
controlFlowNestingLevel = 0;
postEntryPointReturn = false;
// Handle function attributes
if (inEntryPoint) {
const TIntermAggregate* numThreads = attributes[EatNumThreads];
if (numThreads != nullptr) {
const TIntermSequence& sequence = numThreads->getSequence();
for (int lid = 0; lid < int(sequence.size()); ++lid)
intermediate.setLocalSize(lid, sequence[lid]->getAsConstantUnion()->getConstArray()[0].getIConst());
}
const TIntermAggregate* maxVertexCount = attributes[EatMaxVertexCount];
if (maxVertexCount != nullptr) {
intermediate.setVertices(maxVertexCount->getSequence()[0]->getAsConstantUnion()->getConstArray()[0].getIConst());
}
}
return paramNodes;
}
void HlslParseContext::handleFunctionBody(const TSourceLoc& loc, TFunction& function, TIntermNode* functionBody, TIntermNode*& node)
{
node = intermediate.growAggregate(node, functionBody);
intermediate.setAggregateOperator(node, EOpFunction, function.getType(), loc);
node->getAsAggregate()->setName(function.getMangledName().c_str());
popScope();
if (function.getType().getBasicType() != EbtVoid && ! functionReturnsValue)
error(loc, "function does not return a value:", "", function.getName().c_str());
}
// AST I/O is done through shader globals declared in the 'in' or 'out'
// storage class. An HLSL entry point has a return value, input parameters
// and output parameters. These need to get remapped to the AST I/O.
void HlslParseContext::remapEntryPointIO(TFunction& function)
{
// Will auto-assign locations here to the inputs/outputs defined by the entry point
const auto remapType = [&](TType& type) {
const auto remapBuiltInType = [&](TType& type) {
switch (type.getQualifier().builtIn) {
case EbvFragDepthGreater:
intermediate.setDepth(EldGreater);
type.getQualifier().builtIn = EbvFragDepth;
break;
case EbvFragDepthLesser:
intermediate.setDepth(EldLess);
type.getQualifier().builtIn = EbvFragDepth;
break;
default:
break;
}
};
remapBuiltInType(type);
if (type.isStruct()) {
auto members = *type.getStruct();
for (auto member = members.begin(); member != members.end(); ++member)
remapBuiltInType(*member->type);
}
};
// return value is actually a shader-scoped output (out)
if (function.getType().getBasicType() != EbtVoid) {
entryPointOutput = makeInternalVariable("@entryPointOutput", function.getType());
entryPointOutput->getWritableType().getQualifier().storage = EvqVaryingOut;
remapType(function.getWritableType());
}
// parameters are actually shader-scoped inputs and outputs (in or out)
for (int i = 0; i < function.getParamCount(); i++) {
TType& paramType = *function[i].type;
paramType.getQualifier().storage = paramType.getQualifier().isParamInput() ? EvqVaryingIn : EvqVaryingOut;
remapType(paramType);
}
}
// An HLSL function that looks like an entry point, but is not,
// declares entry point IO built-ins, but these have to be undone.
void HlslParseContext::remapNonEntryPointIO(TFunction& function)
{
const auto remapBuiltInType = [&](TType& type) { type.getQualifier().builtIn = EbvNone; };
// return value
if (function.getType().getBasicType() != EbtVoid)
remapBuiltInType(function.getWritableType());
// parameters
for (int i = 0; i < function.getParamCount(); i++)
remapBuiltInType(*function[i].type);
}
// Handle function returns, including type conversions to the function return type
// if necessary.
TIntermNode* HlslParseContext::handleReturnValue(const TSourceLoc& loc, TIntermTyped* value)
{
functionReturnsValue = true;
if (currentFunctionType->getBasicType() == EbtVoid) {
error(loc, "void function cannot return a value", "return", "");
return intermediate.addBranch(EOpReturn, loc);
} else if (*currentFunctionType != value->getType()) {
value = intermediate.addConversion(EOpReturn, *currentFunctionType, value);
if (value && *currentFunctionType != value->getType())
value = intermediate.addShapeConversion(EOpReturn, *currentFunctionType, value);
if (value == nullptr) {
error(loc, "type does not match, or is not convertible to, the function's return type", "return", "");
return value;
}
}
// The entry point needs to send any return value to the entry-point output instead.
// So, a subtree is built up, as a two-part sequence, with the first part being an
// assignment subtree, and the second part being a return with no value.
//
// Otherwise, for a non entry point, just return a return statement.
if (inEntryPoint) {
assert(entryPointOutput != nullptr); // should have been error tested at the beginning
TIntermSymbol* left = new TIntermSymbol(entryPointOutput->getUniqueId(), entryPointOutput->getName(),
entryPointOutput->getType());
TIntermNode* returnSequence = handleAssign(loc, EOpAssign, left, value);
returnSequence = intermediate.makeAggregate(returnSequence);
returnSequence = intermediate.growAggregate(returnSequence, intermediate.addBranch(EOpReturn, loc), loc);
returnSequence->getAsAggregate()->setOperator(EOpSequence);
return returnSequence;
} else
return intermediate.addBranch(EOpReturn, value, loc);
}
void HlslParseContext::handleFunctionArgument(TFunction* function, TIntermTyped*& arguments, TIntermTyped* newArg)
{
TParameter param = { 0, new TType };
param.type->shallowCopy(newArg->getType());
function->addParameter(param);
if (arguments)
arguments = intermediate.growAggregate(arguments, newArg);
else
arguments = newArg;
}
// Some simple source assignments need to be flattened to a sequence
// of AST assignments. Catch these and flatten, otherwise, pass through
// to intermediate.addAssign().
TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op, TIntermTyped* left, TIntermTyped* right) const
{
if (left == nullptr || right == nullptr)
return nullptr;
const auto mustFlatten = [&](const TIntermTyped& node) {
return wasFlattened(&node) && node.getAsSymbolNode() &&
flattenMap.find(node.getAsSymbolNode()->getId()) != flattenMap.end();
};
const bool flattenLeft = mustFlatten(*left);
const bool flattenRight = mustFlatten(*right);
if (! flattenLeft && ! flattenRight)
return intermediate.addAssign(op, left, right, loc);
TIntermAggregate* assignList = nullptr;
const TVector<TVariable*>* leftVariables = nullptr;
const TVector<TVariable*>* rightVariables = nullptr;
// A temporary to store the right node's value, so we don't keep indirecting into it
// if it's not a simple symbol.
TVariable* rhsTempVar = nullptr;
// If the RHS is a simple symbol node, we'll copy it for each member.
TIntermSymbol* cloneSymNode = nullptr;
// Array structs are not yet handled in flattening. (Compilation error upstream, so
// this should never fire).
assert(!(left->getType().isStruct() && left->getType().isArray()));
int memberCount = 0;
// Track how many items there are to copy.
if (left->getType().isStruct())
memberCount = (int)left->getType().getStruct()->size();
if (left->getType().isArray())
memberCount = left->getType().getCumulativeArraySize();
if (flattenLeft)
leftVariables = &flattenMap.find(left->getAsSymbolNode()->getId())->second.members;
if (flattenRight) {
rightVariables = &flattenMap.find(right->getAsSymbolNode()->getId())->second.members;
} else {
// The RHS is not flattened. There are several cases:
// 1. 1 item to copy: Use the RHS directly.
// 2. >1 item, simple symbol RHS: we'll create a new TIntermSymbol node for each, but no assign to temp.
// 3. >1 item, complex RHS: assign it to a new temp variable, and create a TIntermSymbol for each member.
if (memberCount <= 1) {
// case 1: we'll use the symbol directly below. Nothing to do.
} else {
if (right->getAsSymbolNode() != nullptr) {
// case 2: we'll copy the symbol per iteration below.
cloneSymNode = right->getAsSymbolNode();
} else {
// case 3: assign to a temp, and indirect into that.
rhsTempVar = makeInternalVariable("flattenTemp", right->getType());
rhsTempVar->getWritableType().getQualifier().makeTemporary();
TIntermTyped* noFlattenRHS = intermediate.addSymbol(*rhsTempVar, loc);
// Add this to the aggregate being built.
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, noFlattenRHS, right, loc), loc);
}
}
}
int memberIdx = 0;
const auto getMember = [&](bool flatten, TIntermTyped* node,
const TVector<TVariable*>& memberVariables, int member,
TOperator op, const TType& memberType) -> TIntermTyped * {
TIntermTyped* subTree;
if (flatten && isFinalFlattening(memberType)) {
subTree = intermediate.addSymbol(*memberVariables[memberIdx++]);
} else {
subTree = intermediate.addIndex(op, node, intermediate.addConstantUnion(member, loc), loc);
subTree->setType(memberType);
}
return subTree;
};
// Cannot use auto here, because this is recursive, and auto can't work out the type without seeing the
// whole thing. So, we'll resort to an explicit type via std::function.
const std::function<void(TIntermTyped* left, TIntermTyped* right)>
traverse = [&](TIntermTyped* left, TIntermTyped* right) -> void {
// If we get here, we are assigning to or from a whole array or struct that must be
// flattened, so have to do member-by-member assignment:
if (left->getType().isArray()) {
// array case
const TType dereferencedType(left->getType(), 0);
for (int element=0; element < left->getType().getOuterArraySize(); ++element) {
// Add a new AST symbol node if we have a temp variable holding a complex RHS.
TIntermTyped* subRight = getMember(flattenRight, right, *rightVariables, element,
EOpIndexDirect, dereferencedType);
TIntermTyped* subLeft = getMember(flattenLeft, left, *leftVariables, element,
EOpIndexDirect, dereferencedType);
if (isFinalFlattening(dereferencedType))
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, subLeft, subRight, loc), loc);
else
traverse(subLeft, subRight);
}
} else if (left->getType().isStruct()) {
// struct case
const auto& members = *left->getType().getStruct();
for (int member = 0; member < (int)members.size(); ++member) {
TIntermTyped* subRight = getMember(flattenRight, right, *rightVariables, member,
EOpIndexDirectStruct, *members[member].type);
TIntermTyped* subLeft = getMember(flattenLeft, left, *leftVariables, member,
EOpIndexDirectStruct, *members[member].type);
if (isFinalFlattening(*members[member].type))
assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, subLeft, subRight, loc), loc);
else
traverse(subLeft, subRight);
}
} else {
assert(0); // we should never be called on a non-flattenable thing, because
// that case bails out above to a simple copy.
}
};
// Use the proper RHS node: a new symbol from a TVariable, copy
// of an TIntermSymbol node, or sometimes the right node directly.
right = rhsTempVar ? intermediate.addSymbol(*rhsTempVar, loc) :
cloneSymNode ? intermediate.addSymbol(*cloneSymNode) :
right;
// This makes the whole assignment, recursing through subtypes as needed.
traverse(left, right);
assert(assignList != nullptr);
assignList->setOperator(EOpSequence);
return assignList;
}
//
// HLSL atomic operations have slightly different arguments than
// GLSL/AST/SPIRV. The semantics are converted below in decomposeIntrinsic.
// This provides the post-decomposition equivalent opcode.
//
TOperator HlslParseContext::mapAtomicOp(const TSourceLoc& loc, TOperator op, bool isImage)
{
switch (op) {
case EOpInterlockedAdd: return isImage ? EOpImageAtomicAdd : EOpAtomicAdd;
case EOpInterlockedAnd: return isImage ? EOpImageAtomicAnd : EOpAtomicAnd;
case EOpInterlockedCompareExchange: return isImage ? EOpImageAtomicCompSwap : EOpAtomicCompSwap;
case EOpInterlockedMax: return isImage ? EOpImageAtomicMax : EOpAtomicMax;
case EOpInterlockedMin: return isImage ? EOpImageAtomicMin : EOpAtomicMin;
case EOpInterlockedOr: return isImage ? EOpImageAtomicOr : EOpAtomicOr;
case EOpInterlockedXor: return isImage ? EOpImageAtomicXor : EOpAtomicXor;
case EOpInterlockedExchange: return isImage ? EOpImageAtomicExchange : EOpAtomicExchange;
case EOpInterlockedCompareStore: // TODO: ...
default:
error(loc, "unknown atomic operation", "unknown op", "");
return EOpNull;
}
}
//
// Create a combined sampler/texture from separate sampler and texture.
//
TIntermAggregate* HlslParseContext::handleSamplerTextureCombine(const TSourceLoc& loc, TIntermTyped* argTex, TIntermTyped* argSampler)
{
TIntermAggregate* txcombine = new TIntermAggregate(EOpConstructTextureSampler);
txcombine->getSequence().push_back(argTex);
txcombine->getSequence().push_back(argSampler);
TSampler samplerType = argTex->getType().getSampler();
samplerType.combined = true;
samplerType.shadow = argSampler->getType().getSampler().shadow;
txcombine->setType(TType(samplerType, EvqTemporary));
txcombine->setLoc(loc);
return txcombine;
}
//
// Decompose DX9 and DX10 sample intrinsics & object methods into AST
//
void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
{
if (!node || !node->getAsOperator())
return;
const auto clampReturn = [&loc, &node, this](TIntermTyped* result, const TSampler& sampler) -> TIntermTyped* {
// Sampler return must always be a vec4, but we can construct a shorter vector
result->setType(TType(node->getType().getBasicType(), EvqTemporary, node->getVectorSize()));
if (sampler.vectorSize < (unsigned)node->getVectorSize()) {
// Too many components. Construct shorter vector from it.
const TType clampedType(result->getType().getBasicType(), EvqTemporary, sampler.vectorSize);
const TOperator op = intermediate.mapTypeToConstructorOp(clampedType);
result = constructBuiltIn(clampedType, op, result, loc, false);
}
result->setLoc(loc);
return result;
};
const TOperator op = node->getAsOperator()->getOp();
const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
switch (op) {
// **** DX9 intrinsics: ****
case EOpTexture:
{
// Texture with ddx & ddy is really gradient form in HLSL
if (argAggregate->getSequence().size() == 4)
node->getAsAggregate()->setOperator(EOpTextureGrad);
break;
}
case EOpTextureBias:
{
TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // sampler
TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // coord
// HLSL puts bias in W component of coordinate. We extract it and add it to
// the argument list, instead
TIntermTyped* w = intermediate.addConstantUnion(3, loc, true);
TIntermTyped* bias = intermediate.addIndex(EOpIndexDirect, arg1, w, loc);
TOperator constructOp = EOpNull;
const TSampler& sampler = arg0->getType().getSampler();
switch (sampler.dim) {
case Esd1D: constructOp = EOpConstructFloat; break; // 1D
case Esd2D: constructOp = EOpConstructVec2; break; // 2D
case Esd3D: constructOp = EOpConstructVec3; break; // 3D
case EsdCube: constructOp = EOpConstructVec3; break; // also 3D
default: break;
}
TIntermAggregate* constructCoord = new TIntermAggregate(constructOp);
constructCoord->getSequence().push_back(arg1);
constructCoord->setLoc(loc);
// The input vector should never be less than 2, since there's always a bias.
// The max is for safety, and should be a no-op.
constructCoord->setType(TType(arg1->getBasicType(), EvqTemporary, std::max(arg1->getVectorSize() - 1, 0)));
TIntermAggregate* tex = new TIntermAggregate(EOpTexture);
tex->getSequence().push_back(arg0); // sampler
tex->getSequence().push_back(constructCoord); // coordinate
tex->getSequence().push_back(bias); // bias
node = clampReturn(tex, sampler);
break;
}
// **** DX10 methods: ****
case EOpMethodSample: // fall through
case EOpMethodSampleBias: // ...
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* argBias = nullptr;
TIntermTyped* argOffset = nullptr;
const TSampler& sampler = argTex->getType().getSampler();
int nextArg = 3;
if (op == EOpMethodSampleBias) // SampleBias has a bias arg
argBias = argAggregate->getSequence()[nextArg++]->getAsTyped();
TOperator textureOp = EOpTexture;
if ((int)argAggregate->getSequence().size() == (nextArg+1)) { // last parameter is offset form
textureOp = EOpTextureOffset;
argOffset = argAggregate->getSequence()[nextArg++]->getAsTyped();
}
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
TIntermAggregate* txsample = new TIntermAggregate(textureOp);
txsample->getSequence().push_back(txcombine);
txsample->getSequence().push_back(argCoord);
if (argBias != nullptr)
txsample->getSequence().push_back(argBias);
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
break;
}
case EOpMethodSampleGrad: // ...
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* argDDX = argAggregate->getSequence()[3]->getAsTyped();
TIntermTyped* argDDY = argAggregate->getSequence()[4]->getAsTyped();
TIntermTyped* argOffset = nullptr;
const TSampler& sampler = argTex->getType().getSampler();
TOperator textureOp = EOpTextureGrad;
if (argAggregate->getSequence().size() == 6) { // last parameter is offset form
textureOp = EOpTextureGradOffset;
argOffset = argAggregate->getSequence()[5]->getAsTyped();
}
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
TIntermAggregate* txsample = new TIntermAggregate(textureOp);
txsample->getSequence().push_back(txcombine);
txsample->getSequence().push_back(argCoord);
txsample->getSequence().push_back(argDDX);
txsample->getSequence().push_back(argDDY);
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
break;
}
case EOpMethodGetDimensions:
{
// AST returns a vector of results, which we break apart component-wise into
// separate values to assign to the HLSL method's outputs, ala:
// tx . GetDimensions(width, height);
// float2 sizeQueryTemp = EOpTextureQuerySize
// width = sizeQueryTemp.X;
// height = sizeQueryTemp.Y;
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
const TType& texType = argTex->getType();
assert(texType.getBasicType() == EbtSampler);
const TSampler& sampler = texType.getSampler();
const TSamplerDim dim = sampler.dim;
const bool isImage = sampler.isImage();
const int numArgs = (int)argAggregate->getSequence().size();
int numDims = 0;
switch (dim) {
case Esd1D: numDims = 1; break; // W
case Esd2D: numDims = 2; break; // W, H
case Esd3D: numDims = 3; break; // W, H, D
case EsdCube: numDims = 2; break; // W, H (cube)
case EsdBuffer: numDims = 1; break; // W (buffers)
default:
assert(0 && "unhandled texture dimension");
}
// Arrayed adds another dimension for the number of array elements
if (sampler.isArrayed())
++numDims;
// Establish whether we're querying mip levels
const bool mipQuery = (numArgs > (numDims + 1)) && (!sampler.isMultiSample());
// AST assumes integer return. Will be converted to float if required.
TIntermAggregate* sizeQuery = new TIntermAggregate(isImage ? EOpImageQuerySize : EOpTextureQuerySize);
sizeQuery->getSequence().push_back(argTex);
// If we're querying an explicit LOD, add the LOD, which is always arg #1
if (mipQuery) {
TIntermTyped* queryLod = argAggregate->getSequence()[1]->getAsTyped();
sizeQuery->getSequence().push_back(queryLod);
}
sizeQuery->setType(TType(EbtUint, EvqTemporary, numDims));
sizeQuery->setLoc(loc);
// Return value from size query
TVariable* tempArg = makeInternalVariable("sizeQueryTemp", sizeQuery->getType());
tempArg->getWritableType().getQualifier().makeTemporary();
TIntermTyped* sizeQueryAssign = intermediate.addAssign(EOpAssign,
intermediate.addSymbol(*tempArg, loc),
sizeQuery, loc);
// Compound statement for assigning outputs
TIntermAggregate* compoundStatement = intermediate.makeAggregate(sizeQueryAssign, loc);
// Index of first output parameter
const int outParamBase = mipQuery ? 2 : 1;
for (int compNum = 0; compNum < numDims; ++compNum) {
TIntermTyped* indexedOut = nullptr;
TIntermSymbol* sizeQueryReturn = intermediate.addSymbol(*tempArg, loc);
if (numDims > 1) {
TIntermTyped* component = intermediate.addConstantUnion(compNum, loc, true);
indexedOut = intermediate.addIndex(EOpIndexDirect, sizeQueryReturn, component, loc);
indexedOut->setType(TType(EbtUint, EvqTemporary, 1));
indexedOut->setLoc(loc);
} else {
indexedOut = sizeQueryReturn;
}
TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + compNum]->getAsTyped();
TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, indexedOut, loc);
compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
}
// handle mip level parameter
if (mipQuery) {
TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + numDims]->getAsTyped();
TIntermAggregate* levelsQuery = new TIntermAggregate(EOpTextureQueryLevels);
levelsQuery->getSequence().push_back(argTex);
levelsQuery->setType(TType(EbtUint, EvqTemporary, 1));
levelsQuery->setLoc(loc);
TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, levelsQuery, loc);
compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
}
// 2DMS formats query # samples, which needs a different query op
if (sampler.isMultiSample()) {
TIntermTyped* outParam = argAggregate->getSequence()[outParamBase + numDims]->getAsTyped();
TIntermAggregate* samplesQuery = new TIntermAggregate(EOpImageQuerySamples);
samplesQuery->getSequence().push_back(argTex);
samplesQuery->setType(TType(EbtUint, EvqTemporary, 1));
samplesQuery->setLoc(loc);
TIntermTyped* compAssign = intermediate.addAssign(EOpAssign, outParam, samplesQuery, loc);
compoundStatement = intermediate.growAggregate(compoundStatement, compAssign);
}
compoundStatement->setOperator(EOpSequence);
compoundStatement->setLoc(loc);
compoundStatement->setType(TType(EbtVoid));
node = compoundStatement;
break;
}
case EOpMethodSampleCmp: // fall through...
case EOpMethodSampleCmpLevelZero:
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* argCmpVal = argAggregate->getSequence()[3]->getAsTyped();
TIntermTyped* argOffset = nullptr;
// optional offset value
if (argAggregate->getSequence().size() > 4)
argOffset = argAggregate->getSequence()[4]->getAsTyped();
const int coordDimWithCmpVal = argCoord->getType().getVectorSize() + 1; // +1 for cmp
// AST wants comparison value as one of the texture coordinates
TOperator constructOp = EOpNull;
switch (coordDimWithCmpVal) {
// 1D can't happen: there's always at least 1 coordinate dimension + 1 cmp val
case 2: constructOp = EOpConstructVec2; break;
case 3: constructOp = EOpConstructVec3; break;
case 4: constructOp = EOpConstructVec4; break;
case 5: constructOp = EOpConstructVec4; break; // cubeArrayShadow, cmp value is separate arg.
default: assert(0); break;
}
TIntermAggregate* coordWithCmp = new TIntermAggregate(constructOp);
coordWithCmp->getSequence().push_back(argCoord);
if (coordDimWithCmpVal != 5) // cube array shadow is special.
coordWithCmp->getSequence().push_back(argCmpVal);
coordWithCmp->setLoc(loc);
coordWithCmp->setType(TType(argCoord->getBasicType(), EvqTemporary, std::min(coordDimWithCmpVal, 4)));
TOperator textureOp = (op == EOpMethodSampleCmpLevelZero ? EOpTextureLod : EOpTexture);
if (argOffset != nullptr)
textureOp = (op == EOpMethodSampleCmpLevelZero ? EOpTextureLodOffset : EOpTextureOffset);
// Create combined sampler & texture op
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
TIntermAggregate* txsample = new TIntermAggregate(textureOp);
txsample->getSequence().push_back(txcombine);
txsample->getSequence().push_back(coordWithCmp);
if (coordDimWithCmpVal == 5) // cube array shadow is special: cmp val follows coord.
txsample->getSequence().push_back(argCmpVal);
// the LevelZero form uses 0 as an explicit LOD
if (op == EOpMethodSampleCmpLevelZero)
txsample->getSequence().push_back(intermediate.addConstantUnion(0.0, EbtFloat, loc, true));
// Add offset if present
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
txsample->setType(node->getType());
txsample->setLoc(loc);
node = txsample;
break;
}
case EOpMethodLoad:
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argOffset = nullptr;
TIntermTyped* lodComponent = nullptr;
TIntermTyped* coordSwizzle = nullptr;
const TSampler& sampler = argTex->getType().getSampler();
const bool isMS = sampler.isMultiSample();
const bool isBuffer = sampler.dim == EsdBuffer;
const bool isImage = sampler.isImage();
const TBasicType coordBaseType = argCoord->getType().getBasicType();
// Last component of coordinate is the mip level, for non-MS. we separate them here:
if (isMS || isBuffer || isImage) {
// MS, Buffer, and Image have no LOD
coordSwizzle = argCoord;
} else {
// Extract coordinate
TVectorFields coordFields(0,1,2,3);
coordFields.num = argCoord->getType().getVectorSize() - (isMS ? 0 : 1);
TIntermTyped* coordIdx = intermediate.addSwizzle(coordFields, loc);
coordSwizzle = intermediate.addIndex(EOpVectorSwizzle, argCoord, coordIdx, loc);
coordSwizzle->setType(TType(coordBaseType, EvqTemporary, coordFields.num));
// Extract LOD
TIntermTyped* lodIdx = intermediate.addConstantUnion(coordFields.num, loc, true);
lodComponent = intermediate.addIndex(EOpIndexDirect, argCoord, lodIdx, loc);
lodComponent->setType(TType(coordBaseType, EvqTemporary, 1));
}
const int numArgs = (int)argAggregate->getSequence().size();
const bool hasOffset = ((!isMS && numArgs == 3) || (isMS && numArgs == 4));
// Create texel fetch
const TOperator fetchOp = (isImage ? EOpImageLoad :
hasOffset ? EOpTextureFetchOffset :
EOpTextureFetch);
TIntermAggregate* txfetch = new TIntermAggregate(fetchOp);
// Build up the fetch
txfetch->getSequence().push_back(argTex);
txfetch->getSequence().push_back(coordSwizzle);
if (isMS) {
// add 2DMS sample index
TIntermTyped* argSampleIdx = argAggregate->getSequence()[2]->getAsTyped();
txfetch->getSequence().push_back(argSampleIdx);
} else if (isBuffer) {
// Nothing else to do for buffers.
} else if (isImage) {
// Nothing else to do for images.
} else {
// 2DMS and buffer have no LOD, but everything else does.
txfetch->getSequence().push_back(lodComponent);
}
// Obtain offset arg, if there is one.
if (hasOffset) {
const int offsetPos = (isMS ? 3 : 2);
argOffset = argAggregate->getSequence()[offsetPos]->getAsTyped();
txfetch->getSequence().push_back(argOffset);
}
node = clampReturn(txfetch, sampler);
break;
}
case EOpMethodSampleLevel:
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* argLod = argAggregate->getSequence()[3]->getAsTyped();
TIntermTyped* argOffset = nullptr;
const TSampler& sampler = argTex->getType().getSampler();
const int numArgs = (int)argAggregate->getSequence().size();
if (numArgs == 5) // offset, if present
argOffset = argAggregate->getSequence()[4]->getAsTyped();
const TOperator textureOp = (argOffset == nullptr ? EOpTextureLod : EOpTextureLodOffset);
TIntermAggregate* txsample = new TIntermAggregate(textureOp);
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
txsample->getSequence().push_back(txcombine);
txsample->getSequence().push_back(argCoord);
txsample->getSequence().push_back(argLod);
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
break;
}
case EOpMethodGather:
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* argOffset = nullptr;
// Offset is optional
if (argAggregate->getSequence().size() > 3)
argOffset = argAggregate->getSequence()[3]->getAsTyped();
const TOperator textureOp = (argOffset == nullptr ? EOpTextureGather : EOpTextureGatherOffset);
TIntermAggregate* txgather = new TIntermAggregate(textureOp);
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
txgather->getSequence().push_back(txcombine);
txgather->getSequence().push_back(argCoord);
// Offset if not given is implicitly channel 0 (red)
if (argOffset != nullptr)
txgather->getSequence().push_back(argOffset);
txgather->setType(node->getType());
txgather->setLoc(loc);
node = txgather;
break;
}
case EOpMethodGatherRed: // fall through...
case EOpMethodGatherGreen: // ...
case EOpMethodGatherBlue: // ...
case EOpMethodGatherAlpha: // ...
case EOpMethodGatherCmpRed: // ...
case EOpMethodGatherCmpGreen: // ...
case EOpMethodGatherCmpBlue: // ...
case EOpMethodGatherCmpAlpha: // ...
{
int channel = 0; // the channel we are gathering
int cmpValues = 0; // 1 if there is a compare value (handier than a bool below)
switch (op) {
case EOpMethodGatherCmpRed: cmpValues = 1; // fall through
case EOpMethodGatherRed: channel = 0; break;
case EOpMethodGatherCmpGreen: cmpValues = 1; // fall through
case EOpMethodGatherGreen: channel = 1; break;
case EOpMethodGatherCmpBlue: cmpValues = 1; // fall through
case EOpMethodGatherBlue: channel = 2; break;
case EOpMethodGatherCmpAlpha: cmpValues = 1; // fall through
case EOpMethodGatherAlpha: channel = 3; break;
default: assert(0); break;
}
// For now, we have nothing to map the component-wise comparison forms
// to, because neither GLSL nor SPIR-V has such an opcode. Issue an
// unimplemented error instead. Most of the machinery is here if that
// should ever become available.
if (cmpValues) {
error(loc, "unimplemented: component-level gather compare", "", "");
return;
}
int arg = 0;
TIntermTyped* argTex = argAggregate->getSequence()[arg++]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[arg++]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[arg++]->getAsTyped();
TIntermTyped* argOffset = nullptr;
TIntermTyped* argOffsets[4] = { nullptr, nullptr, nullptr, nullptr };
// TIntermTyped* argStatus = nullptr; // TODO: residency
TIntermTyped* argCmp = nullptr;
const TSamplerDim dim = argTex->getType().getSampler().dim;
const int argSize = (int)argAggregate->getSequence().size();
bool hasStatus = (argSize == (5+cmpValues) || argSize == (8+cmpValues));
bool hasOffset1 = false;
bool hasOffset4 = false;
// Only 2D forms can have offsets. Discover if we have 0, 1 or 4 offsets.
if (dim == Esd2D) {
hasOffset1 = (argSize == (4+cmpValues) || argSize == (5+cmpValues));
hasOffset4 = (argSize == (7+cmpValues) || argSize == (8+cmpValues));
}
assert(!(hasOffset1 && hasOffset4));
TOperator textureOp = EOpTextureGather;
// Compare forms have compare value
if (cmpValues != 0)
argCmp = argOffset = argAggregate->getSequence()[arg++]->getAsTyped();
// Some forms have single offset
if (hasOffset1) {
textureOp = EOpTextureGatherOffset; // single offset form
argOffset = argAggregate->getSequence()[arg++]->getAsTyped();
}
// Some forms have 4 gather offsets
if (hasOffset4) {
textureOp = EOpTextureGatherOffsets; // note plural, for 4 offset form
for (int offsetNum = 0; offsetNum < 4; ++offsetNum)
argOffsets[offsetNum] = argAggregate->getSequence()[arg++]->getAsTyped();
}
// Residency status
if (hasStatus) {
// argStatus = argAggregate->getSequence()[arg++]->getAsTyped();
error(loc, "unimplemented: residency status", "", "");
return;
}
TIntermAggregate* txgather = new TIntermAggregate(textureOp);
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
TIntermTyped* argChannel = intermediate.addConstantUnion(channel, loc, true);
txgather->getSequence().push_back(txcombine);
txgather->getSequence().push_back(argCoord);
// AST wants an array of 4 offsets, where HLSL has separate args. Here
// we construct an array from the separate args.
if (hasOffset4) {
TType arrayType(EbtInt, EvqTemporary, 2);
TArraySizes arraySizes;
arraySizes.addInnerSize(4);
arrayType.newArraySizes(arraySizes);
TIntermAggregate* initList = new TIntermAggregate(EOpNull);
for (int offsetNum = 0; offsetNum < 4; ++offsetNum)
initList->getSequence().push_back(argOffsets[offsetNum]);
argOffset = addConstructor(loc, initList, arrayType);
}
// Add comparison value if we have one
if (argTex->getType().getSampler().isShadow())
txgather->getSequence().push_back(argCmp);
// Add offset (either 1, or an array of 4) if we have one
if (argOffset != nullptr)
txgather->getSequence().push_back(argOffset);
txgather->getSequence().push_back(argChannel);
txgather->setType(node->getType());
txgather->setLoc(loc);
node = txgather;
break;
}
case EOpMethodCalculateLevelOfDetail:
case EOpMethodCalculateLevelOfDetailUnclamped:
{
TIntermTyped* argTex = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* argSamp = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* argCoord = argAggregate->getSequence()[2]->getAsTyped();
TIntermAggregate* txquerylod = new TIntermAggregate(EOpTextureQueryLod);
TIntermAggregate* txcombine = handleSamplerTextureCombine(loc, argTex, argSamp);
txquerylod->getSequence().push_back(txcombine);
txquerylod->getSequence().push_back(argCoord);
TIntermTyped* lodComponent = intermediate.addConstantUnion(0, loc, true);
TIntermTyped* lodComponentIdx = intermediate.addIndex(EOpIndexDirect, txquerylod, lodComponent, loc);
lodComponentIdx->setType(TType(EbtFloat, EvqTemporary, 1));
node = lodComponentIdx;
// We cannot currently obtain the unclamped LOD
if (op == EOpMethodCalculateLevelOfDetailUnclamped)
error(loc, "unimplemented: CalculateLevelOfDetailUnclamped", "", "");
break;
}
case EOpMethodGetSamplePosition:
{
error(loc, "unimplemented: GetSamplePosition", "", "");
break;
}
default:
break; // most pass through unchanged
}
}
//
// Decompose geometry shader methods
//
void HlslParseContext::decomposeGeometryMethods(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
{
if (!node || !node->getAsOperator())
return;
const TOperator op = node->getAsOperator()->getOp();
const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
switch (op) {
case EOpMethodAppend:
if (argAggregate) {
TIntermAggregate* sequence = nullptr;
TIntermAggregate* emit = new TIntermAggregate(EOpEmitVertex);
emit->setLoc(loc);
emit->setType(TType(EbtVoid));
sequence = intermediate.growAggregate(sequence,
intermediate.addAssign(EOpAssign,
argAggregate->getSequence()[0]->getAsTyped(),
argAggregate->getSequence()[1]->getAsTyped(), loc),
loc);
sequence = intermediate.growAggregate(sequence, emit);
sequence->setOperator(EOpSequence);
sequence->setLoc(loc);
sequence->setType(TType(EbtVoid));
node = sequence;
}
break;
case EOpMethodRestartStrip:
{
TIntermAggregate* cut = new TIntermAggregate(EOpEndPrimitive);
cut->setLoc(loc);
cut->setType(TType(EbtVoid));
node = cut;
}
break;
default:
break; // most pass through unchanged
}
}
//
// Optionally decompose intrinsics to AST opcodes.
//
void HlslParseContext::decomposeIntrinsic(const TSourceLoc& loc, TIntermTyped*& node, TIntermNode* arguments)
{
// Helper to find image data for image atomics:
// OpImageLoad(image[idx])
// We take the image load apart and add its params to the atomic op aggregate node
const auto imageAtomicParams = [this, &loc, &node](TIntermAggregate* atomic, TIntermTyped* load) {
TIntermAggregate* loadOp = load->getAsAggregate();
if (loadOp == nullptr) {
error(loc, "unknown image type in atomic operation", "", "");
node = nullptr;
return;
}
atomic->getSequence().push_back(loadOp->getSequence()[0]);
atomic->getSequence().push_back(loadOp->getSequence()[1]);
};
// Return true if this is an imageLoad, which we will change to an image atomic.
const auto isImageParam = [](TIntermTyped* image) -> bool {
TIntermAggregate* imageAggregate = image->getAsAggregate();
return imageAggregate != nullptr && imageAggregate->getOp() == EOpImageLoad;
};
// HLSL intrinsics can be pass through to native AST opcodes, or decomposed here to existing AST
// opcodes for compatibility with existing software stacks.
static const bool decomposeHlslIntrinsics = true;
if (!decomposeHlslIntrinsics || !node || !node->getAsOperator())
return;
const TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
TIntermUnary* fnUnary = node->getAsUnaryNode();
const TOperator op = node->getAsOperator()->getOp();
switch (op) {
case EOpGenMul:
{
// mul(a,b) -> MatrixTimesMatrix, MatrixTimesVector, MatrixTimesScalar, VectorTimesScalar, Dot, Mul
// Since we are treating HLSL rows like GLSL columns (the first matrix indirection),
// we must reverse the operand order here. Hence, arg0 gets sequence[1], etc.
TIntermTyped* arg0 = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* arg1 = argAggregate->getSequence()[0]->getAsTyped();
if (arg0->isVector() && arg1->isVector()) { // vec * vec
node->getAsAggregate()->setOperator(EOpDot);
} else {
node = handleBinaryMath(loc, "mul", EOpMul, arg0, arg1);
}
break;
}
case EOpRcp:
{
// rcp(a) -> 1 / a
TIntermTyped* arg0 = fnUnary->getOperand();
TBasicType type0 = arg0->getBasicType();
TIntermTyped* one = intermediate.addConstantUnion(1, type0, loc, true);
node = handleBinaryMath(loc, "rcp", EOpDiv, one, arg0);
break;
}
case EOpSaturate:
{
// saturate(a) -> clamp(a,0,1)
TIntermTyped* arg0 = fnUnary->getOperand();
TBasicType type0 = arg0->getBasicType();
TIntermAggregate* clamp = new TIntermAggregate(EOpClamp);
clamp->getSequence().push_back(arg0);
clamp->getSequence().push_back(intermediate.addConstantUnion(0, type0, loc, true));
clamp->getSequence().push_back(intermediate.addConstantUnion(1, type0, loc, true));
clamp->setLoc(loc);
clamp->setType(node->getType());
clamp->getWritableType().getQualifier().makeTemporary();
node = clamp;
break;
}
case EOpSinCos:
{
// sincos(a,b,c) -> b = sin(a), c = cos(a)
TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* arg2 = argAggregate->getSequence()[2]->getAsTyped();
TIntermTyped* sinStatement = handleUnaryMath(loc, "sin", EOpSin, arg0);
TIntermTyped* cosStatement = handleUnaryMath(loc, "cos", EOpCos, arg0);
TIntermTyped* sinAssign = intermediate.addAssign(EOpAssign, arg1, sinStatement, loc);
TIntermTyped* cosAssign = intermediate.addAssign(EOpAssign, arg2, cosStatement, loc);
TIntermAggregate* compoundStatement = intermediate.makeAggregate(sinAssign, loc);
compoundStatement = intermediate.growAggregate(compoundStatement, cosAssign);
compoundStatement->setOperator(EOpSequence);
compoundStatement->setLoc(loc);
compoundStatement->setType(TType(EbtVoid));
node = compoundStatement;
break;
}
case EOpClip:
{
// clip(a) -> if (any(a<0)) discard;
TIntermTyped* arg0 = fnUnary->getOperand();
TBasicType type0 = arg0->getBasicType();
TIntermTyped* compareNode = nullptr;
// For non-scalars: per experiment with FXC compiler, discard if any component < 0.
if (!arg0->isScalar()) {
// component-wise compare: a < 0
TIntermAggregate* less = new TIntermAggregate(EOpLessThan);
less->getSequence().push_back(arg0);
less->setLoc(loc);
// make vec or mat of bool matching dimensions of input
less->setType(TType(EbtBool, EvqTemporary,
arg0->getType().getVectorSize(),
arg0->getType().getMatrixCols(),
arg0->getType().getMatrixRows(),
arg0->getType().isVector()));
// calculate # of components for comparison const
const int constComponentCount =
std::max(arg0->getType().getVectorSize(), 1) *
std::max(arg0->getType().getMatrixCols(), 1) *
std::max(arg0->getType().getMatrixRows(), 1);
TConstUnion zero;
zero.setDConst(0.0);
TConstUnionArray zeros(constComponentCount, zero);
less->getSequence().push_back(intermediate.addConstantUnion(zeros, arg0->getType(), loc, true));
compareNode = intermediate.addBuiltInFunctionCall(loc, EOpAny, true, less, TType(EbtBool));
} else {
TIntermTyped* zero = intermediate.addConstantUnion(0, type0, loc, true);
compareNode = handleBinaryMath(loc, "clip", EOpLessThan, arg0, zero);
}
TIntermBranch* killNode = intermediate.addBranch(EOpKill, loc);
node = new TIntermSelection(compareNode, killNode, nullptr);
node->setLoc(loc);
break;
}
case EOpLog10:
{
// log10(a) -> log2(a) * 0.301029995663981 (== 1/log2(10))
TIntermTyped* arg0 = fnUnary->getOperand();
TIntermTyped* log2 = handleUnaryMath(loc, "log2", EOpLog2, arg0);
TIntermTyped* base = intermediate.addConstantUnion(0.301029995663981f, EbtFloat, loc, true);
node = handleBinaryMath(loc, "mul", EOpMul, log2, base);
break;
}
case EOpDst:
{
// dest.x = 1;
// dest.y = src0.y * src1.y;
// dest.z = src0.z;
// dest.w = src1.w;
TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped();
TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped();
TIntermTyped* y = intermediate.addConstantUnion(1, loc, true);
TIntermTyped* z = intermediate.addConstantUnion(2, loc, true);
TIntermTyped* w = intermediate.addConstantUnion(3, loc, true);
TIntermTyped* src0y = intermediate.addIndex(EOpIndexDirect, arg0, y, loc);
TIntermTyped* src1y = intermediate.addIndex(EOpIndexDirect, arg1, y, loc);
TIntermTyped* src0z = intermediate.addIndex(EOpIndexDirect, arg0, z, loc);
TIntermTyped* src1w = intermediate.addIndex(EOpIndexDirect, arg1, w, loc);
TIntermAggregate* dst = new TIntermAggregate(EOpConstructVec4);
dst->getSequence().push_back(intermediate.addConstantUnion(1.0, EbtFloat, loc, true));
dst->getSequence().push_back(handleBinaryMath(loc, "mul", EOpMul, src0y, src1y));
dst->getSequence().push_back(src0z);
dst->getSequence().push_back(src1w);
dst->setType(TType(EbtFloat, EvqTemporary, 4));
dst->setLoc(loc);
node = dst;
break;
}
case EOpInterlockedAdd: // optional last argument (if present) is assigned from return value
case EOpInterlockedMin: // ...
case EOpInterlockedMax: // ...
case EOpInterlockedAnd: // ...
case EOpInterlockedOr: // ...
case EOpInterlockedXor: // ...
case EOpInterlockedExchange: // always has output arg
{
TIntermTyped* arg0 = argAggregate->getSequence()[0]->getAsTyped(); // dest
TIntermTyped* arg1 = argAggregate->getSequence()[1]->getAsTyped(); // value
TIntermTyped* arg2 = nullptr;