| // |
| // Copyright (C) 2017-2018 Google, Inc. |
| // Copyright (C) 2017 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/Include/Common.h" |
| #include "../glslang/MachineIndependent/Scan.h" |
| #include "../glslang/MachineIndependent/preprocessor/PpContext.h" |
| |
| #include "../glslang/OSDependent/osinclude.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <cctype> |
| #include <array> |
| #include <set> |
| |
| 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, &sourceEntryPointName), |
| annotationNestingLevel(0), |
| inputPatch(nullptr), |
| nextInLocation(0), nextOutLocation(0), |
| entryPointFunction(nullptr), |
| entryPointFunctionBody(nullptr), |
| gsStreamOutput(nullptr), |
| clipDistanceOutput(nullptr), |
| cullDistanceOutput(nullptr), |
| clipDistanceInput(nullptr), |
| cullDistanceInput(nullptr) |
| { |
| globalUniformDefaults.clear(); |
| globalUniformDefaults.layoutMatrix = ElmRowMajor; |
| globalUniformDefaults.layoutPacking = ElpStd140; |
| |
| globalBufferDefaults.clear(); |
| globalBufferDefaults.layoutMatrix = ElmRowMajor; |
| globalBufferDefaults.layoutPacking = ElpStd430; |
| |
| globalInputDefaults.clear(); |
| globalOutputDefaults.clear(); |
| |
| clipSemanticNSizeIn.fill(0); |
| cullSemanticNSizeIn.fill(0); |
| clipSemanticNSizeOut.fill(0); |
| cullSemanticNSizeOut.fill(0); |
| |
| // "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; |
| } |
| |
| 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.getFilenameStr() << "(" << 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 || node->getAsTyped() == nullptr) |
| return false; |
| |
| const TIntermAggregate* lhsAsAggregate = node->getAsAggregate(); |
| const TIntermBinary* lhsAsBinary = node->getAsBinaryNode(); |
| |
| // If it's a swizzled/indexed aggregate, look at the left node instead. |
| if (lhsAsBinary != nullptr && |
| (lhsAsBinary->getOp() == EOpVectorSwizzle || lhsAsBinary->getOp() == EOpIndexDirect)) |
| lhsAsAggregate = lhsAsBinary->getLeft()->getAsAggregate(); |
| if (lhsAsAggregate != nullptr && lhsAsAggregate->getOp() == EOpImageLoad) |
| return true; |
| |
| return false; |
| } |
| |
| void HlslParseContext::growGlobalUniformBlock(const TSourceLoc& loc, TType& memberType, const TString& memberName, |
| TTypeList* newTypeList) |
| { |
| newTypeList = nullptr; |
| correctUniform(memberType.getQualifier()); |
| if (memberType.isStruct()) { |
| auto it = ioTypeMap.find(memberType.getStruct()); |
| if (it != ioTypeMap.end() && it->second.uniform) |
| newTypeList = it->second.uniform; |
| } |
| TParseContextBase::growGlobalUniformBlock(loc, memberType, memberName, newTypeList); |
| } |
| |
| // |
| // Return a TLayoutFormat corresponding to the given texture type. |
| // |
| TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const TType& txType) |
| { |
| if (txType.isStruct()) { |
| // TODO: implement. |
| error(loc, "unimplemented: structure type in image or buffer", "", ""); |
| return ElfNone; |
| } |
| |
| const int components = txType.getVectorSize(); |
| const TBasicType txBasicType = txType.getBasicType(); |
| |
| 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 (txBasicType) { |
| 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; |
| } |
| } |
| |
| // We tolerate samplers as l-values, even though they are nominally |
| // illegal, because we expect a later optimization to eliminate them. |
| if (node->getType().getBasicType() == EbtSampler) { |
| intermediate.setNeedsLegalization(); |
| return false; |
| } |
| |
| // 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); |
| }; |
| |
| // Return true if swizzle or index writes all components of the given variable. |
| const auto writesAllComponents = [&](TIntermSymbol* var, TIntermBinary* swizzle) -> bool { |
| if (swizzle == nullptr) // not a swizzle or index |
| return true; |
| |
| // Track which components are being set. |
| std::array<bool, 4> compIsSet; |
| compIsSet.fill(false); |
| |
| const TIntermConstantUnion* asConst = swizzle->getRight()->getAsConstantUnion(); |
| const TIntermAggregate* asAggregate = swizzle->getRight()->getAsAggregate(); |
| |
| // This could be either a direct index, or a swizzle. |
| if (asConst) { |
| compIsSet[asConst->getConstArray()[0].getIConst()] = true; |
| } else if (asAggregate) { |
| const TIntermSequence& seq = asAggregate->getSequence(); |
| for (int comp=0; comp<int(seq.size()); ++comp) |
| compIsSet[seq[comp]->getAsConstantUnion()->getConstArray()[0].getIConst()] = true; |
| } else { |
| assert(0); |
| } |
| |
| // Return true if all components are being set by the index or swizzle |
| return std::all_of(compIsSet.begin(), compIsSet.begin() + var->getType().getVectorSize(), |
| [](bool isSet) { return isSet; } ); |
| }; |
| |
| // Create swizzle matching input swizzle |
| const auto addSwizzle = [&](TIntermSymbol* var, TIntermBinary* swizzle) -> TIntermTyped* { |
| if (swizzle) |
| return intermediate.addBinaryNode(swizzle->getOp(), var, swizzle->getRight(), loc, swizzle->getType()); |
| else |
| return var; |
| }; |
| |
| TIntermBinary* lhsAsBinary = lhs->getAsBinaryNode(); |
| TIntermAggregate* lhsAsAggregate = lhs->getAsAggregate(); |
| bool lhsIsSwizzle = false; |
| |
| // If it's a swizzled L-value, remember the swizzle, and use the LHS. |
| if (lhsAsBinary != nullptr && (lhsAsBinary->getOp() == EOpVectorSwizzle || lhsAsBinary->getOp() == EOpIndexDirect)) { |
| lhsAsAggregate = lhsAsBinary->getLeft()->getAsAggregate(); |
| lhsIsSwizzle = true; |
| } |
| |
| TIntermTyped* object = lhsAsAggregate->getSequence()[0]->getAsTyped(); |
| TIntermTyped* coord = lhsAsAggregate->getSequence()[1]->getAsTyped(); |
| |
| const TSampler& texSampler = object->getType().getSampler(); |
| |
| TType objDerefType; |
| getTextureReturnType(texSampler, objDerefType); |
| |
| 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 |
| // |
| // If the lvalue is swizzled, we apply that when writing the temp variable, like so: |
| // ... |
| // rhsTmp.some_swizzle = ... |
| // For partial writes, an error is generated. |
| |
| TIntermSymbol* rhsTmp = rhs->getAsSymbolNode(); |
| TIntermTyped* coordTmp = coord; |
| |
| if (rhsTmp == nullptr || isModifyOp || lhsIsSwizzle) { |
| rhsTmp = makeInternalVariableNode(loc, "storeTemp", objDerefType); |
| |
| // Partial updates not yet supported |
| if (!writesAllComponents(rhsTmp, lhsAsBinary)) { |
| error(loc, "unimplemented: partial image updates", "", ""); |
| } |
| |
| // Assign storeTemp = rhs |
| if (isModifyOp) { |
| // We have to make a temp var for the coordinate, to avoid evaluating it twice. |
| coordTmp = makeInternalVariableNode(loc, "coordTemp", coord->getType()); |
| makeBinary(EOpAssign, coordTmp, coord); // coordtmp = load[param1] |
| makeLoad(rhsTmp, object, coordTmp, objDerefType); // rhsTmp = OpImageLoad(object, coordTmp) |
| } |
| |
| // rhsTmp op= rhs. |
| makeBinary(assignOp, addSwizzle(intermediate.addSymbol(*rhsTmp), lhsAsBinary), 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 = makeInternalVariableNode(loc, "storeTemp", objDerefType); |
| TIntermTyped* coordTmp = makeInternalVariableNode(loc, "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 = makeInternalVariableNode(loc, "storeTempPre", objDerefType); |
| TIntermSymbol* rhsTmp2 = makeInternalVariableNode(loc, "storeTempPost", objDerefType); |
| TIntermTyped* coordTmp = makeInternalVariableNode(loc, "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; |
| |
| // These pragmas are case insensitive in HLSL, so we'll compare in lower case. |
| TVector<TString> lowerTokens = tokens; |
| |
| for (auto it = lowerTokens.begin(); it != lowerTokens.end(); ++it) |
| std::transform(it->begin(), it->end(), it->begin(), ::tolower); |
| |
| // Handle pack_matrix |
| if (tokens.size() == 4 && lowerTokens[0] == "pack_matrix" && tokens[1] == "(" && tokens[3] == ")") { |
| // Note that HLSL semantic order is Mrc, not Mcr like SPIR-V, so we reverse the sense. |
| // Row major becomes column major and vice versa. |
| |
| if (lowerTokens[2] == "row_major") { |
| globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmColumnMajor; |
| } else if (lowerTokens[2] == "column_major") { |
| globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmRowMajor; |
| } else { |
| // unknown majorness strings are treated as (HLSL column major)==(SPIR-V row major) |
| warn(loc, "unknown pack_matrix pragma value", tokens[2].c_str(), ""); |
| globalUniformDefaults.layoutMatrix = globalBufferDefaults.layoutMatrix = ElmRowMajor; |
| } |
| return; |
| } |
| |
| // Handle once |
| if (lowerTokens[0] == "once") { |
| warn(loc, "not implemented", "#pragma once", ""); |
| return; |
| } |
| } |
| |
| // |
| // Look at a '.' matrix selector string and change it into components |
| // for a matrix. There are two types: |
| // |
| // _21 second row, first column (one based) |
| // _m21 third row, second column (zero based) |
| // |
| // Returns true if there is no error. |
| // |
| bool HlslParseContext::parseMatrixSwizzleSelector(const TSourceLoc& loc, const TString& fields, int cols, int rows, |
| TSwizzleSelectors<TMatrixSelector>& components) |
| { |
| int startPos[MaxSwizzleSelectors]; |
| int numComps = 0; |
| TString compString = fields; |
| |
| // Find where each component starts, |
| // recording the first character position after the '_'. |
| for (size_t c = 0; c < compString.size(); ++c) { |
| if (compString[c] == '_') { |
| if (numComps >= MaxSwizzleSelectors) { |
| error(loc, "matrix component swizzle has too many components", compString.c_str(), ""); |
| return false; |
| } |
| if (c > compString.size() - 3 || |
| ((compString[c+1] == 'm' || compString[c+1] == 'M') && c > compString.size() - 4)) { |
| error(loc, "matrix component swizzle missing", compString.c_str(), ""); |
| return false; |
| } |
| startPos[numComps++] = (int)c + 1; |
| } |
| } |
| |
| // Process each component |
| for (int i = 0; i < numComps; ++i) { |
| int pos = startPos[i]; |
| int bias = -1; |
| if (compString[pos] == 'm' || compString[pos] == 'M') { |
| bias = 0; |
| ++pos; |
| } |
| TMatrixSelector comp; |
| comp.coord1 = compString[pos+0] - '0' + bias; |
| comp.coord2 = compString[pos+1] - '0' + bias; |
| if (comp.coord1 < 0 || comp.coord1 >= cols) { |
| error(loc, "matrix row component out of range", compString.c_str(), ""); |
| return false; |
| } |
| if (comp.coord2 < 0 || comp.coord2 >= rows) { |
| error(loc, "matrix column component out of range", compString.c_str(), ""); |
| return false; |
| } |
| components.push_back(comp); |
| } |
| |
| return true; |
| } |
| |
| // If the 'comps' express a column of a matrix, |
| // return the column. Column means the first coords all match. |
| // |
| // Otherwise, return -1. |
| // |
| int HlslParseContext::getMatrixComponentsColumn(int rows, const TSwizzleSelectors<TMatrixSelector>& selector) |
| { |
| int col = -1; |
| |
| // right number of comps? |
| if (selector.size() != rows) |
| return -1; |
| |
| // all comps in the same column? |
| // rows in order? |
| col = selector[0].coord1; |
| for (int i = 0; i < rows; ++i) { |
| if (col != selector[i].coord1) |
| return -1; |
| if (i != selector[i].coord2) |
| return -1; |
| } |
| |
| return col; |
| } |
| |
| // |
| // Handle seeing a variable identifier in the grammar. |
| // |
| TIntermTyped* HlslParseContext::handleVariable(const TSourceLoc& loc, const TString* string) |
| { |
| int thisDepth; |
| TSymbol* symbol = symbolTable.find(*string, thisDepth); |
| if (symbol && symbol->getAsVariable() && symbol->getAsVariable()->isUserType()) { |
| error(loc, "expected symbol, not user-defined type", string->c_str(), ""); |
| return nullptr; |
| } |
| |
| const TVariable* variable = nullptr; |
| const TAnonMember* anon = symbol ? symbol->getAsAnonMember() : nullptr; |
| TIntermTyped* node = nullptr; |
| if (anon) { |
| // It was a member of an anonymous container, which could be a 'this' structure. |
| |
| // Create a subtree for its dereference. |
| if (thisDepth > 0) { |
| variable = getImplicitThis(thisDepth); |
| if (variable == nullptr) |
| error(loc, "cannot access member variables (static member function?)", "this", ""); |
| } |
| if (variable == nullptr) |
| 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 == nullptr) { |
| error(loc, "unknown variable", string->c_str(), ""); |
| 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()) { |
| if (! mipsOperatorMipArg.empty() && mipsOperatorMipArg.back().mipLevel == nullptr) { |
| // The first operator[] to a .mips[] sequence is the mip level. We'll remember it. |
| mipsOperatorMipArg.back().mipLevel = index; |
| return base; // next [] index is to the same base. |
| } else { |
| TIntermAggregate* load = new TIntermAggregate(sampler.isImage() ? EOpImageLoad : EOpTextureFetch); |
| |
| TType sampReturnType; |
| getTextureReturnType(sampler, sampReturnType); |
| |
| load->setType(sampReturnType); |
| load->setLoc(loc); |
| load->getSequence().push_back(base); |
| load->getSequence().push_back(index); |
| |
| // Textures need a MIP. If we saw one go by, use it. Otherwise, use zero. |
| if (sampler.isTexture()) { |
| if (! mipsOperatorMipArg.empty()) { |
| load->getSequence().push_back(mipsOperatorMipArg.back().mipLevel); |
| mipsOperatorMipArg.pop_back(); |
| } else { |
| load->getSequence().push_back(intermediate.addConstantUnion(0, loc, true)); |
| } |
| } |
| |
| return load; |
| } |
| } |
| } |
| |
| // Handle operator[] on structured buffers: this indexes into the array element of the buffer. |
| // indexStructBufferContent returns nullptr if it isn't a structuredbuffer (SSBO). |
| TIntermTyped* sbArray = indexStructBufferContent(loc, base); |
| if (sbArray != nullptr) { |
| if (sbArray == nullptr) |
| return nullptr; |
| |
| // Now we'll apply the [] index to that array |
| const TOperator idxOp = (index->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect; |
| |
| TIntermTyped* element = intermediate.addIndex(idxOp, sbArray, index, loc); |
| const TType derefType(sbArray->getType(), 0); |
| element->setType(derefType); |
| return element; |
| } |
| |
| return nullptr; |
| } |
| |
| // |
| // Cast index value to a uint if it isn't already (for operator[], load indexes, etc) |
| TIntermTyped* HlslParseContext::makeIntegerIndex(TIntermTyped* index) |
| { |
| const TBasicType indexBasicType = index->getType().getBasicType(); |
| const int vecSize = index->getType().getVectorSize(); |
| |
| // We can use int types directly as the index |
| if (indexBasicType == EbtInt || indexBasicType == EbtUint || |
| indexBasicType == EbtInt64 || indexBasicType == EbtUint64) |
| return index; |
| |
| // Cast index to unsigned integer if it isn't one. |
| return intermediate.addConversion(EOpConstructUint, TType(EbtUint, EvqTemporary, vecSize), index); |
| } |
| |
| // |
| // Handle seeing a base[index] dereference in the grammar. |
| // |
| TIntermTyped* HlslParseContext::handleBracketDereference(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index) |
| { |
| index = makeIntegerIndex(index); |
| |
| if (index == nullptr) { |
| error(loc, " unknown index type ", "", ""); |
| return nullptr; |
| } |
| |
| 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().isFrontEndConstant()) |
| indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst(); |
| |
| 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().isFrontEndConstant() && |
| index->getQualifier().isFrontEndConstant()) { |
| // both base and index are front-end constants |
| checkIndex(loc, base->getType(), indexValue); |
| return intermediate.foldDereference(base, indexValue, loc); |
| } else { |
| // at least one of base and index is variable... |
| |
| if (index->getQualifier().isFrontEndConstant()) |
| checkIndex(loc, base->getType(), indexValue); |
| |
| if (base->getType().isScalarOrVec1()) |
| result = base; |
| else if (base->getAsSymbolNode() && wasFlattened(base)) { |
| if (index->getQualifier().storage != EvqConst) |
| error(loc, "Invalid variable index to flattened array", base->getAsSymbolNode()->getName().c_str(), ""); |
| |
| result = flattenAccess(base, indexValue); |
| flattened = (result != base); |
| } else { |
| if (index->getQualifier().isFrontEndConstant()) { |
| if (base->getType().isUnsizedArray()) |
| base->getWritableType().updateImplicitArraySize(indexValue + 1); |
| else |
| checkIndex(loc, base->getType(), 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; |
| } |
| |
| // 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 == nullptr) |
| 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; |
| } |
| // |
| // Return true if the name is a struct buffer method |
| // |
| bool HlslParseContext::isStructBufferMethod(const TString& name) const |
| { |
| return |
| name == "GetDimensions" || |
| name == "Load" || |
| name == "Load2" || |
| name == "Load3" || |
| name == "Load4" || |
| name == "Store" || |
| name == "Store2" || |
| name == "Store3" || |
| name == "Store4" || |
| name == "InterlockedAdd" || |
| name == "InterlockedAnd" || |
| name == "InterlockedCompareExchange" || |
| name == "InterlockedCompareStore" || |
| name == "InterlockedExchange" || |
| name == "InterlockedMax" || |
| name == "InterlockedMin" || |
| name == "InterlockedOr" || |
| name == "InterlockedXor" || |
| name == "IncrementCounter" || |
| name == "DecrementCounter" || |
| name == "Append" || |
| name == "Consume"; |
| } |
| |
| // |
| // Handle seeing a base.field dereference in the grammar, where 'field' is a |
| // swizzle or member variable. |
| // |
| TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TIntermTyped* base, const TString& field) |
| { |
| variableCheck(base); |
| |
| if (base->isArray()) { |
| error(loc, "cannot apply to an array:", ".", field.c_str()); |
| return base; |
| } |
| |
| TIntermTyped* result = base; |
| |
| if (base->getType().getBasicType() == EbtSampler) { |
| // Handle .mips[mipid][pos] operation on textures |
| const TSampler& sampler = base->getType().getSampler(); |
| if (sampler.isTexture() && field == "mips") { |
| // Push a null to signify that we expect a mip level under operator[] next. |
| mipsOperatorMipArg.push_back(tMipsOperatorData(loc, nullptr)); |
| // Keep 'result' pointing to 'base', since we expect an operator[] to go by next. |
| } else { |
| if (field == "mips") |
| error(loc, "unexpected texture type for .mips[][] operator:", |
| base->getType().getCompleteString().c_str(), ""); |
| else |
| error(loc, "unexpected operator on texture type:", field.c_str(), |
| base->getType().getCompleteString().c_str()); |
| } |
| } else if (base->isVector() || base->isScalar()) { |
| TSwizzleSelectors<TVectorSelector> selectors; |
| parseSwizzleSelector(loc, field, base->getVectorSize(), selectors); |
| |
| if (base->isScalar()) { |
| if (selectors.size() == 1) |
| return result; |
| else { |
| TType type(base->getBasicType(), EvqTemporary, selectors.size()); |
| return addConstructor(loc, base, type); |
| } |
| } |
| if (base->getVectorSize() == 1) { |
| TType scalarType(base->getBasicType(), EvqTemporary, 1); |
| if (selectors.size() == 1) |
| return addConstructor(loc, base, scalarType); |
| else { |
| TType vectorType(base->getBasicType(), EvqTemporary, selectors.size()); |
| return addConstructor(loc, addConstructor(loc, base, scalarType), vectorType); |
| } |
| } |
| |
| 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)); |
| } 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())); |
| } |
| } |
| } else if (base->isMatrix()) { |
| TSwizzleSelectors<TMatrixSelector> selectors; |
| if (! parseMatrixSwizzleSelector(loc, field, base->getMatrixCols(), base->getMatrixRows(), selectors)) |
| return result; |
| |
| if (selectors.size() == 1) { |
| // Representable by m[c][r] |
| if (base->getType().getQualifier().isFrontEndConstant()) { |
| result = intermediate.foldDereference(base, selectors[0].coord1, loc); |
| result = intermediate.foldDereference(result, selectors[0].coord2, loc); |
| } else { |
| result = intermediate.addIndex(EOpIndexDirect, base, |
| intermediate.addConstantUnion(selectors[0].coord1, loc), |
| loc); |
| TType dereferencedCol(base->getType(), 0); |
| result->setType(dereferencedCol); |
| result = intermediate.addIndex(EOpIndexDirect, result, |
| intermediate.addConstantUnion(selectors[0].coord2, loc), |
| loc); |
| TType dereferenced(dereferencedCol, 0); |
| result->setType(dereferenced); |
| } |
| } else { |
| int column = getMatrixComponentsColumn(base->getMatrixRows(), selectors); |
| if (column >= 0) { |
| // Representable by m[c] |
| if (base->getType().getQualifier().isFrontEndConstant()) |
| result = intermediate.foldDereference(base, column, loc); |
| else { |
| result = intermediate.addIndex(EOpIndexDirect, base, intermediate.addConstantUnion(column, loc), |
| loc); |
| TType dereferenced(base->getType(), 0); |
| result->setType(dereferenced); |
| } |
| } else { |
| // general case, not a column, not a single component |
| TIntermTyped* index = intermediate.addSwizzle(selectors, loc); |
| result = intermediate.addIndex(EOpMatrixSwizzle, base, index, loc); |
| result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision, |
| selectors.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)) { |
| result = flattenAccess(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; |
| } |
| |
| // |
| // Return true if the field should be treated as a built-in method. |
| // Return false otherwise. |
| // |
| bool HlslParseContext::isBuiltInMethod(const TSourceLoc&, TIntermTyped* base, const TString& field) |
| { |
| if (base == nullptr) |
| return false; |
| |
| variableCheck(base); |
| |
| if (base->getType().getBasicType() == EbtSampler) { |
| return true; |
| } else if (isStructBufferType(base->getType()) && isStructBufferMethod(field)) { |
| return true; |
| } else if (field == "Append" || |
| field == "RestartStrip") { |
| // We cannot check the type here: it may be sanitized if we're not compiling a geometry shader, but |
| // the code is around in the shader source. |
| return true; |
| } else |
| return false; |
| } |
| |
| // Independently establish a built-in that is a member of a structure. |
| // 'arraySizes' are what's desired for the independent built-in, whatever |
| // the higher-level source/expression of them was. |
| void HlslParseContext::splitBuiltIn(const TString& baseName, const TType& memberType, const TArraySizes* arraySizes, |
| const TQualifier& outerQualifier) |
| { |
| // Because of arrays of structs, we might be asked more than once, |
| // but the arraySizes passed in should have captured the whole thing |
| // the first time. |
| // However, clip/cull rely on multiple updates. |
| if (!isClipOrCullDistance(memberType)) |
| if (splitBuiltIns.find(tInterstageIoData(memberType.getQualifier().builtIn, outerQualifier.storage)) != |
| splitBuiltIns.end()) |
| return; |
| |
| TVariable* ioVar = makeInternalVariable(baseName + "." + memberType.getFieldName(), memberType); |
| |
| if (arraySizes != nullptr && !memberType.isArray()) |
| ioVar->getWritableType().copyArraySizes(*arraySizes); |
| |
| splitBuiltIns[tInterstageIoData(memberType.getQualifier().builtIn, outerQualifier.storage)] = ioVar; |
| if (!isClipOrCullDistance(ioVar->getType())) |
| trackLinkage(*ioVar); |
| |
| // Merge qualifier from the user structure |
| mergeQualifiers(ioVar->getWritableType().getQualifier(), outerQualifier); |
| |
| // Fix the builtin type if needed (e.g, some types require fixed array sizes, no matter how the |
| // shader declared them). This is done after mergeQualifiers(), in case fixBuiltInIoType looks |
| // at the qualifier to determine e.g, in or out qualifications. |
| fixBuiltInIoType(ioVar->getWritableType()); |
| |
| // But, not location, we're losing that |
| ioVar->getWritableType().getQualifier().layoutLocation = TQualifier::layoutLocationEnd; |
| } |
| |
| // Split a type into |
| // 1. a struct of non-I/O members |
| // 2. a collection of independent I/O variables |
| void HlslParseContext::split(const TVariable& variable) |
| { |
| // Create a new variable: |
| const TType& clonedType = *variable.getType().clone(); |
| const TType& splitType = split(clonedType, variable.getName(), clonedType.getQualifier()); |
| splitNonIoVars[variable.getUniqueId()] = makeInternalVariable(variable.getName(), splitType); |
| } |
| |
| // Recursive implementation of split(). |
| // Returns reference to the modified type. |
| const TType& HlslParseContext::split(const TType& type, const TString& name, const TQualifier& outerQualifier) |
| { |
| if (type.isStruct()) { |
| TTypeList* userStructure = type.getWritableStruct(); |
| for (auto ioType = userStructure->begin(); ioType != userStructure->end(); ) { |
| if (ioType->type->isBuiltIn()) { |
| // move out the built-in |
| splitBuiltIn(name, *ioType->type, type.getArraySizes(), outerQualifier); |
| ioType = userStructure->erase(ioType); |
| } else { |
| split(*ioType->type, name + "." + ioType->type->getFieldName(), outerQualifier); |
| ++ioType; |
| } |
| } |
| } |
| |
| return type; |
| } |
| |
| // Is this an aggregate that should be flattened? |
| // Can be applied to intermediate levels of type in a hierarchy. |
| // Some things like flattening uniform arrays are only about the top level |
| // of the aggregate, triggered on 'topLevel'. |
| bool HlslParseContext::shouldFlatten(const TType& type, TStorageQualifier qualifier, bool topLevel) const |
| { |
| switch (qualifier) { |
| case EvqVaryingIn: |
| case EvqVaryingOut: |
| return type.isStruct() || type.isArray(); |
| case EvqUniform: |
| return (type.isArray() && intermediate.getFlattenUniformArrays() && topLevel) || |
| (type.isStruct() && type.containsOpaque()); |
| default: |
| return false; |
| }; |
| } |
| |
| // Top level variable flattening: construct data |
| void HlslParseContext::flatten(const TVariable& variable, bool linkage) |
| { |
| const TType& type = variable.getType(); |
| |
| // If it's a standalone built-in, there is nothing to flatten |
| if (type.isBuiltIn() && !type.isStruct()) |
| return; |
| |
| auto entry = flattenMap.insert(std::make_pair(variable.getUniqueId(), |
| TFlattenData(type.getQualifier().layoutBinding, |
| type.getQualifier().layoutLocation))); |
| |
| // the item is a map pair, so first->second is the TFlattenData itself. |
| flatten(variable, type, entry.first->second, variable.getName(), linkage, type.getQualifier(), nullptr); |
| } |
| |
| // 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 - perhaps 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 TVariable& variable, const TType& type, |
| TFlattenData& flattenData, TString name, bool linkage, |
| const TQualifier& outerQualifier, |
| const TArraySizes* builtInArraySizes) |
| { |
| // 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(variable, type, flattenData, name, linkage, outerQualifier); |
| else if (type.isStruct()) |
| return flattenStruct(variable, type, flattenData, name, linkage, outerQualifier, builtInArraySizes); |
| 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 TVariable& variable, const TType& type, TFlattenData& flattenData, |
| const TString& memberName, bool linkage, |
| const TQualifier& outerQualifier, |
| const TArraySizes* builtInArraySizes) |
| { |
| if (!shouldFlatten(type, outerQualifier.storage, false)) { |
| // This is as far as we flatten. Insert the variable. |
| TVariable* memberVariable = makeInternalVariable(memberName, type); |
| mergeQualifiers(memberVariable->getWritableType().getQualifier(), variable.getType().getQualifier()); |
| |
| if (flattenData.nextBinding != TQualifier::layoutBindingEnd) |
| memberVariable->getWritableType().getQualifier().layoutBinding = flattenData.nextBinding++; |
| |
| if (memberVariable->getType().isBuiltIn()) { |
| // inherited locations are nonsensical for built-ins (TODO: what if semantic had a number) |
| memberVariable->getWritableType().getQualifier().layoutLocation = TQualifier::layoutLocationEnd; |
| } else { |
| // inherited locations must be auto bumped, not replicated |
| if (flattenData.nextLocation != TQualifier::layoutLocationEnd) { |
| memberVariable->getWritableType().getQualifier().layoutLocation = flattenData.nextLocation; |
| flattenData.nextLocation += intermediate.computeTypeLocationSize(memberVariable->getType(), language); |
| nextOutLocation = std::max(nextOutLocation, flattenData.nextLocation); |
| } |
| } |
| |
| flattenData.offsets.push_back(static_cast<int>(flattenData.members.size())); |
| flattenData.members.push_back(memberVariable); |
| |
| if (linkage) |
| trackLinkage(*memberVariable); |
| |
| return static_cast<int>(flattenData.offsets.size()) - 1; // location of the member reference |
| } else { |
| // Further recursion required |
| return flatten(variable, type, flattenData, memberName, linkage, outerQualifier, builtInArraySizes); |
| } |
| } |
| |
| // Figure out the mapping between an aggregate's top members and an |
| // equivalent set of individual variables. |
| // |
| // Assumes shouldFlatten() or equivalent was called first. |
| int HlslParseContext::flattenStruct(const TVariable& variable, const TType& type, |
| TFlattenData& flattenData, TString name, bool linkage, |
| const TQualifier& outerQualifier, |
| const TArraySizes* builtInArraySizes) |
| { |
| assert(type.isStruct()); |
| |
| auto members = *type.getStruct(); |
| |
| // Reserve space for this tree level. |
| int start = static_cast<int>(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; |
| if (dereferencedType.isBuiltIn()) |
| splitBuiltIn(variable.getName(), dereferencedType, builtInArraySizes, outerQualifier); |
| else { |
| const int mpos = addFlattenedMember(variable, dereferencedType, flattenData, |
| name + "." + dereferencedType.getFieldName(), |
| linkage, outerQualifier, |
| builtInArraySizes == nullptr && dereferencedType.isArray() |
| ? dereferencedType.getArraySizes() |
| : builtInArraySizes); |
| flattenData.offsets[pos++] = mpos; |
| } |
| } |
| |
| 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 TVariable& variable, const TType& type, |
| TFlattenData& flattenData, TString name, bool linkage, |
| const TQualifier& outerQualifier) |
| { |
| assert(type.isSizedArray()); |
| |
| const int size = type.getOuterArraySize(); |
| const TType dereferencedType(type, 0); |
| |
| if (name.empty()) |
| name = variable.getName(); |
| |
| // Reserve space for this tree level. |
| int start = static_cast<int>(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(variable, dereferencedType, flattenData, |
| name + elementNumBuf, linkage, outerQualifier, |
| type.getArraySizes()); |
| |
| 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()); |
| } |
| |
| // Return true if we have split this structure |
| bool HlslParseContext::wasSplit(const TIntermTyped* node) const |
| { |
| return node != nullptr && node->getAsSymbolNode() != nullptr && |
| wasSplit(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 wasFlattened() or equivalent was called first. |
| TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member) |
| { |
| const TType dereferencedType(base->getType(), member); // dereferenced type |
| const TIntermSymbol& symbolNode = *base->getAsSymbolNode(); |
| TIntermTyped* flattened = flattenAccess(symbolNode.getId(), member, base->getQualifier().storage, |
| dereferencedType, symbolNode.getFlattenSubset()); |
| |
| return flattened ? flattened : base; |
| } |
| TIntermTyped* HlslParseContext::flattenAccess(int uniqueId, int member, TStorageQualifier outerStorage, |
| const TType& dereferencedType, int subset) |
| { |
| const auto flattenData = flattenMap.find(uniqueId); |
| |
| if (flattenData == flattenMap.end()) |
| return nullptr; |
| |
| // Calculate new cumulative offset from the packed tree |
| int newSubset = flattenData->second.offsets[subset >= 0 ? subset + member : member]; |
| |
| TIntermSymbol* subsetSymbol; |
| if (!shouldFlatten(dereferencedType, outerStorage, false)) { |
| // Finished flattening: create symbol for variable |
| member = flattenData->second.offsets[newSubset]; |
| const TVariable* memberVariable = flattenData->second.members[member]; |
| subsetSymbol = intermediate.addSymbol(*memberVariable); |
| subsetSymbol->setFlattenSubset(-1); |
| } else { |
| |
| // If this is not the final flattening, accumulate the position and return |
| // an object of the partially dereferenced type. |
| subsetSymbol = new TIntermSymbol(uniqueId, "flattenShadow", dereferencedType); |
| subsetSymbol->setFlattenSubset(newSubset); |
| } |
| |
| return subsetSymbol; |
| } |
| |
| // For finding where the first leaf is in a subtree of a multi-level aggregate |
| // that is just getting a subset assigned. Follows the same logic as flattenAccess, |
| // but logically going down the "left-most" tree branch each step of the way. |
| // |
| // Returns the offset into the first leaf of the subset. |
| int HlslParseContext::findSubtreeOffset(const TIntermNode& node) const |
| { |
| const TIntermSymbol* sym = node.getAsSymbolNode(); |
| if (sym == nullptr) |
| return 0; |
| if (!sym->isArray() && !sym->isStruct()) |
| return 0; |
| int subset = sym->getFlattenSubset(); |
| if (subset == -1) |
| return 0; |
| |
| // Getting this far means a partial aggregate is identified by the flatten subset. |
| // Find the first leaf of the subset. |
| |
| const auto flattenData = flattenMap.find(sym->getId()); |
| if (flattenData == flattenMap.end()) |
| return 0; |
| |
| return findSubtreeOffset(sym->getType(), subset, flattenData->second.offsets); |
| |
| do { |
| subset = flattenData->second.offsets[subset]; |
| } while (true); |
| } |
| // Recursively do the desent |
| int HlslParseContext::findSubtreeOffset(const TType& type, int subset, const TVector<int>& offsets) const |
| { |
| if (!type.isArray() && !type.isStruct()) |
| return offsets[subset]; |
| TType derefType(type, 0); |
| return findSubtreeOffset(derefType, offsets[subset], offsets); |
| }; |
| |
| // Find and return the split IO TVariable for id, or nullptr if none. |
| TVariable* HlslParseContext::getSplitNonIoVar(int id) const |
| { |
| const auto splitNonIoVar = splitNonIoVars.find(id); |
| if (splitNonIoVar == splitNonIoVars.end()) |
| return nullptr; |
| |
| return splitNonIoVar->second; |
| } |
| |
| // Pass through to base class after remembering built-in mappings. |
| void HlslParseContext::trackLinkage(TSymbol& symbol) |
| { |
| TBuiltInVariable biType = symbol.getType().getQualifier().builtIn; |
| |
| if (biType != EbvNone) |
| builtInTessLinkageSymbols[biType] = symbol.clone(); |
| |
| TParseContextBase::trackLinkage(symbol); |
| } |
| |
| |
| // Returns true if the built-in is a clip or cull distance variable. |
| bool HlslParseContext::isClipOrCullDistance(TBuiltInVariable builtIn) |
| { |
| return builtIn == EbvClipDistance || builtIn == EbvCullDistance; |
| } |
| |
| // Some types require fixed array sizes in SPIR-V, but can be scalars or |
| // arrays of sizes SPIR-V doesn't allow. For example, tessellation factors. |
| // This creates the right size. A conversion is performed when the internal |
| // type is copied to or from the external type. This corrects the externally |
| // facing input or output type to abide downstream semantics. |
| void HlslParseContext::fixBuiltInIoType(TType& type) |
| { |
| int requiredArraySize = 0; |
| int requiredVectorSize = 0; |
| |
| switch (type.getQualifier().builtIn) { |
| case EbvTessLevelOuter: requiredArraySize = 4; break; |
| case EbvTessLevelInner: requiredArraySize = 2; break; |
| |
| case EbvSampleMask: |
| { |
| // Promote scalar to array of size 1. Leave existing arrays alone. |
| if (!type.isArray()) |
| requiredArraySize = 1; |
| break; |
| } |
| |
| case EbvWorkGroupId: requiredVectorSize = 3; break; |
| case EbvGlobalInvocationId: requiredVectorSize = 3; break; |
| case EbvLocalInvocationId: requiredVectorSize = 3; break; |
| case EbvTessCoord: requiredVectorSize = 3; break; |
| |
| default: |
| if (isClipOrCullDistance(type)) { |
| const int loc = type.getQualifier().layoutLocation; |
| |
| if (type.getQualifier().builtIn == EbvClipDistance) { |
| if (type.getQualifier().storage == EvqVaryingIn) |
| clipSemanticNSizeIn[loc] = type.getVectorSize(); |
| else |
| clipSemanticNSizeOut[loc] = type.getVectorSize(); |
| } else { |
| if (type.getQualifier().storage == EvqVaryingIn) |
| cullSemanticNSizeIn[loc] = type.getVectorSize(); |
| else |
| cullSemanticNSizeOut[loc] = type.getVectorSize(); |
| } |
| } |
| |
| return; |
| } |
| |
| // Alter or set vector size as needed. |
| if (requiredVectorSize > 0) { |
| TType newType(type.getBasicType(), type.getQualifier().storage, requiredVectorSize); |
| newType.getQualifier() = type.getQualifier(); |
| |
| type.shallowCopy(newType); |
| } |
| |
| // Alter or set array size as needed. |
| if (requiredArraySize > 0) { |
| if (!type.isArray() || type.getOuterArraySize() != requiredArraySize) { |
| TArraySizes* arraySizes = new TArraySizes; |
| arraySizes->addInnerSize(requiredArraySize); |
| type.transferArraySizes(arraySizes); |
| } |
| } |
| } |
| |
| // Variables that correspond to the user-interface in and out of a stage |
| // (not the built-in interface) are |
| // - assigned locations |
| // - 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::assignToInterface(TVariable& variable) |
| { |
| const auto assignLocation = [&](TVariable& variable) { |
| TType& type = variable.getWritableType(); |
| if (!type.isStruct() || type.getStruct()->size() > 0) { |
| TQualifier& qualifier = type.getQualifier(); |
| if (qualifier.storage == EvqVaryingIn || qualifier.storage == EvqVaryingOut) { |
| if (qualifier.builtIn == EbvNone && !qualifier.hasLocation()) { |
| // Strip off the outer array dimension for those having an extra one. |
| int size; |
| if (type.isArray() && qualifier.isArrayedIo(language)) { |
| TType elementType(type, 0); |
| size = intermediate.computeTypeLocationSize(elementType, language); |
| } else |
| size = intermediate.computeTypeLocationSize(type, language); |
| |
| if (qualifier.storage == EvqVaryingIn) { |
| variable.getWritableType().getQualifier().layoutLocation = nextInLocation; |
| nextInLocation += size; |
| } else { |
| variable.getWritableType().getQualifier().layoutLocation = nextOutLocation; |
| nextOutLocation += size; |
| } |
| } |
| trackLinkage(variable); |
| } |
| } |
| }; |
| |
| if (wasFlattened(variable.getUniqueId())) { |
| auto& memberList = flattenMap[variable.getUniqueId()].members; |
| for (auto member = memberList.begin(); member != memberList.end(); ++member) |
| assignLocation(**member); |
| } else if (wasSplit(variable.getUniqueId())) { |
| TVariable* splitIoVar = getSplitNonIoVar(variable.getUniqueId()); |
| assignLocation(*splitIoVar); |
| } else { |
| assignLocation(variable); |
| } |
| } |
| |
| // |
| // Handle seeing a function declarator in the grammar. This is the precursor |
| // to recognizing a function prototype or function definition. |
| // |
| void 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(), ""); |
| } |
| |
| // For struct buffers with counters, we must pass the counter buffer as hidden parameter. |
| // This adds the hidden parameter to the parameter list in 'paramNodes' if needed. |
| // Otherwise, it's a no-op |
| void HlslParseContext::addStructBufferHiddenCounterParam(const TSourceLoc& loc, TParameter& param, |
| TIntermAggregate*& paramNodes) |
| { |
| if (! hasStructBuffCounter(*param.type)) |
| return; |
| |
| const TString counterBlockName(intermediate.addCounterBufferName(*param.name)); |
| |
| TType counterType; |
| counterBufferType(loc, counterType); |
| TVariable *variable = makeInternalVariable(counterBlockName, counterType); |
| |
| if (! symbolTable.insert(*variable)) |
| error(loc, "redefinition", variable->getName().c_str(), ""); |
| |
| paramNodes = intermediate.growAggregate(paramNodes, |
| intermediate.addSymbol(*variable, loc), |
| loc); |
| } |
| |
| // |
| // Handle seeing the function prototype in front of a function definition in the grammar. |
| // The body is handled after this function returns. |
| // |
| // Returns an aggregate of parameter-symbol nodes. |
| // |
| TIntermAggregate* HlslParseContext::handleFunctionDefinition(const TSourceLoc& loc, TFunction& function, |
| const TAttributes& attributes, |
| TIntermNode*& entryPointTree) |
| { |
| currentCaller = function.getMangledName(); |
| TSymbol* symbol = symbolTable.find(function.getMangledName()); |
| TFunction* prevDec = symbol ? symbol->getAsFunction() : nullptr; |
| |
| if (prevDec == nullptr) |
| 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; |
| |
| // Entry points need different I/O and other handling, transform it so the |
| // rest of this function doesn't care. |
| entryPointTree = transformEntryPoint(loc, function, attributes); |
| |
| // |
| // 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); |
| |
| if (i == 0 && function.hasImplicitThis()) { |
| // Anonymous 'this' members are already in a symbol-table level, |
| // and we need to know what function parameter to map them to. |
| symbolTable.makeInternalVariable(*variable); |
| pushImplicitThis(variable); |
| } |
| |
| // Insert the parameters with name in the symbol table. |
| if (! symbolTable.insert(*variable)) |
| error(loc, "redefinition", variable->getName().c_str(), ""); |
| |
| // Add parameters to the AST list. |
| if (shouldFlatten(variable->getType(), variable->getType().getQualifier().storage, true)) { |
| // Expand the AST parameter nodes (but not the name mangling or symbol table view) |
| // for structures that need to be flattened. |
| flatten(*variable, false); |
| const TTypeList* structure = variable->getType().getStruct(); |
| for (int mem = 0; mem < (int)structure->size(); ++mem) { |
| paramNodes = intermediate.growAggregate(paramNodes, |
| flattenAccess(variable->getUniqueId(), mem, |
| variable->getType().getQualifier().storage, |
| *(*structure)[mem].type), |
| loc); |
| } |
| } else { |
| // Add the parameter to the AST |
| paramNodes = intermediate.growAggregate(paramNodes, |
| intermediate.addSymbol(*variable, loc), |
| loc); |
| } |
| |
| // Add hidden AST parameter for struct buffer counters, if needed. |
| addStructBufferHiddenCounterParam(loc, param, paramNodes); |
| } else |
| paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(*param.type, loc), loc); |
| } |
| if (function.hasIllegalImplicitThis()) |
| pushImplicitThis(nullptr); |
| |
| intermediate.setAggregateOperator(paramNodes, EOpParameters, TType(EbtVoid), loc); |
| loopNestingLevel = 0; |
| controlFlowNestingLevel = 0; |
| postEntryPointReturn = false; |
| |
| return paramNodes; |
| } |
| |
| // Handle all [attrib] attribute for the shader entry point |
| void HlslParseContext::handleEntryPointAttributes(const TSourceLoc& loc, const TAttributes& attributes) |
| { |
| for (auto it = attributes.begin(); it != attributes.end(); ++it) { |
| switch (it->name) { |
| case EatNumThreads: |
| { |
| const TIntermSequence& sequence = it->args->getSequence(); |
| for (int lid = 0; lid < int(sequence.size()); ++lid) |
| intermediate.setLocalSize(lid, sequence[lid]->getAsConstantUnion()->getConstArray()[0].getIConst()); |
| break; |
| } |
| case EatMaxVertexCount: |
| { |
| int maxVertexCount; |
| |
| if (! it->getInt(maxVertexCount)) { |
| error(loc, "invalid maxvertexcount", "", ""); |
| } else { |
| if (! intermediate.setVertices(maxVertexCount)) |
| error(loc, "cannot change previously set maxvertexcount attribute", "", ""); |
| } |
| break; |
| } |
| case EatPatchConstantFunc: |
| { |
| TString pcfName; |
| if (! it->getString(pcfName, 0, false)) { |
| error(loc, "invalid patch constant function", "", ""); |
| } else { |
| patchConstantFunctionName = pcfName; |
| } |
| break; |
| } |
| case EatDomain: |
| { |
| // Handle [domain("...")] |
| TString domainStr; |
| if (! it->getString(domainStr)) { |
| error(loc, "invalid domain", "", ""); |
| } else { |
| TLayoutGeometry domain = ElgNone; |
| |
| if (domainStr == "tri") { |
| domain = ElgTriangles; |
| } else if (domainStr == "quad") { |
| domain = ElgQuads; |
| } else if (domainStr == "isoline") { |
| domain = ElgIsolines; |
| } else { |
| error(loc, "unsupported domain type", domainStr.c_str(), ""); |
| } |
| |
| if (language == EShLangTessEvaluation) { |
| if (! intermediate.setInputPrimitive(domain)) |
| error(loc, "cannot change previously set domain", TQualifier::getGeometryString(domain), ""); |
| } else { |
| if (! intermediate.setOutputPrimitive(domain)) |
| error(loc, "cannot change previously set domain", TQualifier::getGeometryString(domain), ""); |
| } |
| } |
| break; |
| } |
| case EatOutputTopology: |
| { |
| // Handle [outputtopology("...")] |
| TString topologyStr; |
| if (! it->getString(topologyStr)) { |
| error(loc, "invalid outputtopology", "", ""); |
| } else { |
| TVertexOrder vertexOrder = EvoNone; |
| TLayoutGeometry primitive = ElgNone; |
| |
| if (topologyStr == "point") { |
| intermediate.setPointMode(); |
| } else if (topologyStr == "line") { |
| primitive = ElgIsolines; |
| } else if (topologyStr == "triangle_cw") { |
| vertexOrder = EvoCw; |
| primitive = ElgTriangles; |
| } else if (topologyStr == "triangle_ccw") { |
| vertexOrder = EvoCcw; |
| primitive = ElgTriangles; |
| } else { |
| error(loc, "unsupported outputtopology type", topologyStr.c_str(), ""); |
| } |
| |
| if (vertexOrder != EvoNone) { |
| if (! intermediate.setVertexOrder(vertexOrder)) { |
| error(loc, "cannot change previously set outputtopology", |
| TQualifier::getVertexOrderString(vertexOrder), ""); |
| } |
| } |
| if (primitive != ElgNone) |
| intermediate.setOutputPrimitive(primitive); |
| } |
| break; |
| } |
| case EatPartitioning: |
| { |
| // Handle [partitioning("...")] |
| TString partitionStr; |
| if (! it->getString(partitionStr)) { |
| error(loc, "invalid partitioning", "", ""); |
| } else { |
| TVertexSpacing partitioning = EvsNone; |
| |
| if (partitionStr == "integer") { |
| partitioning = EvsEqual; |
| } else if (partitionStr == "fractional_even") { |
| partitioning = EvsFractionalEven; |
| } else if (partitionStr == "fractional_odd") { |
| partitioning = EvsFractionalOdd; |
| //} else if (partition == "pow2") { // TODO: currently nothing to map this to. |
| } else { |
| error(loc, "unsupported partitioning type", partitionStr.c_str(), ""); |
| } |
| |
| if (! intermediate.setVertexSpacing(partitioning)) |
| error(loc, "cannot change previously set partitioning", |
| TQualifier::getVertexSpacingString(partitioning), ""); |
| } |
| break; |
| } |
| case EatOutputControlPoints: |
| { |
| // Handle [outputcontrolpoints("...")] |
| int ctrlPoints; |
| if (! it->getInt(ctrlPoints)) { |
| error(loc, "invalid outputcontrolpoints", "", ""); |
| } else { |
| if (! intermediate.setVertices(ctrlPoints)) { |
| error(loc, "cannot change previously set outputcontrolpoints attribute", "", ""); |
| } |
| } |
| break; |
| } |
| case EatEarlyDepthStencil: |
| intermediate.setEarlyFragmentTests(); |
| break; |
| case EatBuiltIn: |
| case EatLocation: |
| // tolerate these because of dual use of entrypoint and type attributes |
| break; |
| default: |
| warn(loc, "attribute does not apply to entry point", "", ""); |
| break; |
| } |
| } |
| } |
| |
| // Update the given type with any type-like attribute information in the |
| // attributes. |
| void HlslParseContext::transferTypeAttributes(const TSourceLoc& loc, const TAttributes& attributes, TType& type, |
| bool allowEntry) |
| { |
| if (attributes.size() == 0) |
| return; |
| |
| int value; |
| TString builtInString; |
| for (auto it = attributes.begin(); it != attributes.end(); ++it) { |
| switch (it->name) { |
| case EatLocation: |
| // location |
| if (it->getInt(value)) |
| type.getQualifier().layoutLocation = value; |
| else |
| error(loc, "needs a literal integer", "location", ""); |
| break; |
| case EatBinding: |
| // binding |
| if (it->getInt(value)) { |
| type.getQualifier().layoutBinding = value; |
| type.getQualifier().layoutSet = 0; |
| } else |
| error(loc, "needs a literal integer", "binding", ""); |
| // set |
| if (it->getInt(value, 1)) |
| type.getQualifier().layoutSet = value; |
| break; |
| case EatGlobalBinding: |
| // global cbuffer binding |
| if (it->getInt(value)) |
| globalUniformBinding = value; |
| else |
| error(loc, "needs a literal integer", "global binding", ""); |
| // global cbuffer set |
| if (it->getInt(value, 1)) |
| globalUniformSet = value; |
| break; |
| case EatInputAttachment: |
| // input attachment |
| if (it->getInt(value)) |
| type.getQualifier().layoutAttachment = value; |
| else |
| error(loc, "needs a literal integer", "input attachment", ""); |
| break; |
| case EatBuiltIn: |
| // PointSize built-in |
| if (it->getString(builtInString, 0, false)) { |
| if (builtInString == "PointSize") |
| type.getQualifier().builtIn = EbvPointSize; |
| } |
| break; |
| case EatPushConstant: |
| // push_constant |
| type.getQualifier().layoutPushConstant = true; |
| break; |
| case EatConstantId: |
| // specialization constant |
| if (it->getInt(value)) { |
| TSourceLoc loc; |
| loc.init(); |
| setSpecConstantId(loc, type.getQualifier(), value); |
| } |
| break; |
| default: |
| if (! allowEntry) |
| warn(loc, "attribute does not apply to a type", "", ""); |
| break; |
| } |
| } |
| } |
| |
| // |
| // Do all special handling for the entry point, including wrapping |
| // the shader's entry point with the official entry point that will call it. |
| // |
| // The following: |
| // |
| // retType shaderEntryPoint(args...) // shader declared entry point |
| // { body } |
| // |
| // Becomes |
| // |
| // out retType ret; |
| // in iargs<that are input>...; |
| // out oargs<that are output> ...; |
| // |
| // void shaderEntryPoint() // synthesized, but official, entry point |
| // { |
| // args<that are input> = iargs...; |
| // ret = @shaderEntryPoint(args...); |
| // oargs = args<that are output>...; |
| // } |
| // retType @shaderEntryPoint(args...) |
| // { body } |
| // |
| // The symbol table will still map the original entry point name to the |
| // the modified function and its new name: |
| // |
| // symbol table: shaderEntryPoint -> @shaderEntryPoint |
| // |
| // Returns nullptr if no entry-point tree was built, otherwise, returns |
| // a subtree that creates the entry point. |
| // |
| TIntermNode* HlslParseContext::transformEntryPoint(const TSourceLoc& loc, TFunction& userFunction, |
| const TAttributes& attributes) |
| { |
| // Return true if this is a tessellation patch constant function input to a domain shader. |
| const auto isDsPcfInput = [this](const TType& type) { |
| return language == EShLangTessEvaluation && |
| type.contains([](const TType* t) { |
| return t->getQualifier().builtIn == EbvTessLevelOuter || |
| t->getQualifier().builtIn == EbvTessLevelInner; |
| }); |
| }; |
| |
| // if we aren't in the entry point, fix the IO as such and exit |
| if (userFunction.getName().compare(intermediate.getEntryPointName().c_str()) != 0) { |
| remapNonEntryPointIO(userFunction); |
| return nullptr; |
| } |
| |
| entryPointFunction = &userFunction; // needed in finish() |
| |
| // Handle entry point attributes |
| handleEntryPointAttributes(loc, attributes); |
| |
| // entry point logic... |
| |
| // Move parameters and return value to shader in/out |
| TVariable* entryPointOutput; // gets created in remapEntryPointIO |
| TVector<TVariable*> inputs; |
| TVector<TVariable*> outputs; |
| remapEntryPointIO(userFunction, entryPointOutput, inputs, outputs); |
| |
| // Further this return/in/out transform by flattening, splitting, and assigning locations |
| const auto makeVariableInOut = [&](TVariable& variable) { |
| if (variable.getType().isStruct()) { |
| if (variable.getType().getQualifier().isArrayedIo(language)) { |
| if (variable.getType().containsBuiltIn()) |
| split(variable); |
| } else if (shouldFlatten(variable.getType(), EvqVaryingIn /* not assigned yet, but close enough */, true)) |
| flatten(variable, false /* don't track linkage here, it will be tracked in assignToInterface() */); |
| } |
| // TODO: flatten arrays too |
| // TODO: flatten everything in I/O |
| // TODO: replace all split with flatten, make all paths can create flattened I/O, then split code can be removed |
| |
| // For clip and cull distance, multiple output variables potentially get merged |
| // into one in assignClipCullDistance. That code in assignClipCullDistance |
| // handles the interface logic, so we avoid it here in that case. |
| if (!isClipOrCullDistance(variable.getType())) |
| assignToInterface(variable); |
| }; |
| if (entryPointOutput != nullptr) |
| makeVariableInOut(*entryPointOutput); |
| for (auto it = inputs.begin(); it != inputs.end(); ++it) |
| if (!isDsPcfInput((*it)->getType())) // wait until the end for PCF input (see comment below) |
| makeVariableInOut(*(*it)); |
| for (auto it = outputs.begin(); it != outputs.end(); ++it) |
| makeVariableInOut(*(*it)); |
| |
| // In the domain shader, PCF input must be at the end of the linkage. That's because in the |
| // hull shader there is no ordering: the output comes from the separate PCF, which does not |
| // participate in the argument list. That is always put at the end of the HS linkage, so the |
| // input side of the DS must match. The argument may be in any position in the DS argument list |
| // however, so this ensures the linkage is built in the correct order regardless of argument order. |
| if (language == EShLangTessEvaluation) { |
| for (auto it = inputs.begin(); it != inputs.end(); ++it) |
| if (isDsPcfInput((*it)->getType())) |
| makeVariableInOut(*(*it)); |
| } |
| |
| // Synthesize the call |
| |
| pushScope(); // matches the one in handleFunctionBody() |
| |
| // new signature |
| TType voidType(EbtVoid); |
| TFunction synthEntryPoint(&userFunction.getName(), voidType); |
| TIntermAggregate* synthParams = new TIntermAggregate(); |
| intermediate.setAggregateOperator(synthParams, EOpParameters, voidType, loc); |
| intermediate.setEntryPointMangledName(synthEntryPoint.getMangledName().c_str()); |
| intermediate.incrementEntryPointCount(); |
| TFunction callee(&userFunction.getName(), voidType); // call based on old name, which is still in the symbol table |
| |
| // change original name |
| userFunction.addPrefix("@"); // change the name in the function, but not in the symbol table |
| |
| // Copy inputs (shader-in -> calling arg), while building up the call node |
| TVector<TVariable*> argVars; |
| TIntermAggregate* synthBody = new TIntermAggregate(); |
| auto inputIt = inputs.begin(); |
| TIntermTyped* callingArgs = nullptr; |
| |
| for (int i = 0; i < userFunction.getParamCount(); i++) { |
| TParameter& param = userFunction[i]; |
| argVars.push_back(makeInternalVariable(*param.name, *param.type)); |
| argVars.back()->getWritableType().getQualifier().makeTemporary(); |
| |
| // Track the input patch, which is the only non-builtin supported by hull shader PCF. |
| if (param.getDeclaredBuiltIn() == EbvInputPatch) |
| inputPatch = argVars.back(); |
| |
| TIntermSymbol* arg = intermediate.addSymbol(*argVars.back()); |
| handleFunctionArgument(&callee, callingArgs, arg); |
| if (param.type->getQualifier().isParamInput()) { |
| intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign, arg, |
| intermediate.addSymbol(**inputIt))); |
| inputIt++; |
| } |
| } |
| |
| // Call |
| currentCaller = synthEntryPoint.getMangledName(); |
| TIntermTyped* callReturn = handleFunctionCall(loc, &callee, callingArgs); |
| currentCaller = userFunction.getMangledName(); |
| |
| // Return value |
| if (entryPointOutput) { |
| TIntermTyped* returnAssign; |
| |
| // For hull shaders, the wrapped entry point return value is written to |
| // an array element as indexed by invocation ID, which we might have to make up. |
| // This is required to match SPIR-V semantics. |
| if (language == EShLangTessControl) { |
| TIntermSymbol* invocationIdSym = findTessLinkageSymbol(EbvInvocationId); |
| |
| // If there is no user declared invocation ID, we must make one. |
| if (invocationIdSym == nullptr) { |
| TType invocationIdType(EbtUint, EvqIn, 1); |
| TString* invocationIdName = NewPoolTString("InvocationId"); |
| invocationIdType.getQualifier().builtIn = EbvInvocationId; |
| |
| TVariable* variable = makeInternalVariable(*invocationIdName, invocationIdType); |
| |
| globalQualifierFix(loc, variable->getWritableType().getQualifier()); |
| trackLinkage(*variable); |
| |
| invocationIdSym = intermediate.addSymbol(*variable); |
| } |
| |
| TIntermTyped* element = intermediate.addIndex(EOpIndexIndirect, intermediate.addSymbol(*entryPointOutput), |
| invocationIdSym, loc); |
| |
| // Set the type of the array element being dereferenced |
| const TType derefElementType(entryPointOutput->getType(), 0); |
| element->setType(derefElementType); |
| |
| returnAssign = handleAssign(loc, EOpAssign, element, callReturn); |
| } else { |
| returnAssign = handleAssign(loc, EOpAssign, intermediate.addSymbol(*entryPointOutput), callReturn); |
| } |
| intermediate.growAggregate(synthBody, returnAssign); |
| } else |
| intermediate.growAggregate(synthBody, callReturn); |
| |
| // Output copies |
| auto outputIt = outputs.begin(); |
| for (int i = 0; i < userFunction.getParamCount(); i++) { |
| TParameter& param = userFunction[i]; |
| |
| // GS outputs are via emit, so we do not copy them here. |
| if (param.type->getQualifier().isParamOutput()) { |
| if (param.getDeclaredBuiltIn() == EbvGsOutputStream) { |
| // GS output stream does not assign outputs here: it's the Append() method |
| // which writes to the output, probably multiple times separated by Emit. |
| // We merely remember the output to use, here. |
| gsStreamOutput = *outputIt; |
| } else { |
| intermediate.growAggregate(synthBody, handleAssign(loc, EOpAssign, |
| intermediate.addSymbol(**outputIt), |
| intermediate.addSymbol(*argVars[i]))); |
| } |
| |
| outputIt++; |
| } |
| } |
| |
| // Put the pieces together to form a full function subtree |
| // for the synthesized entry point. |
| synthBody->setOperator(EOpSequence); |
| TIntermNode* synthFunctionDef = synthParams; |
| handleFunctionBody(loc, synthEntryPoint, synthBody, synthFunctionDef); |
| |
| entryPointFunctionBody = synthBody; |
| |
| return synthFunctionDef; |
| } |
| |
| 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.hasImplicitThis()) |
| popImplicitThis(); |
| |
| 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, TVariable*& returnValue, |
| TVector<TVariable*>& inputs, TVector<TVariable*>& outputs) |
| { |
| // We might have in input structure type with no decorations that caused it |
| // to look like an input type, yet it has (e.g.) interpolation types that |
| // must be modified that turn it into an input type. |
| // Hence, a missing ioTypeMap for 'input' might need to be synthesized. |
| const auto synthesizeEditedInput = [this](TType& type) { |
| // True if a type needs to be 'flat' |
| const auto needsFlat = [](const TType& type) { |
| return type.containsBasicType(EbtInt) || |
| type.containsBasicType(EbtUint) || |
| type.containsBasicType(EbtInt64) || |
| type.containsBasicType(EbtUint64) || |
| type.containsBasicType(EbtBool) || |
| type.containsBasicType(EbtDouble); |
| }; |
| |
| if (language == EShLangFragment && needsFlat(type)) { |
| if (type.isStruct()) { |
| TTypeList* finalList = nullptr; |
| auto it = ioTypeMap.find(type.getStruct()); |
| if (it == ioTypeMap.end() || it->second.input == nullptr) { |
| // Getting here means we have no input struct, but we need one. |
| auto list = new TTypeList; |
| for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) { |
| TType* newType = new TType; |
| newType->shallowCopy(*member->type); |
| TTypeLoc typeLoc = { newType, member->loc }; |
| list->push_back(typeLoc); |
| } |
| // install the new input type |
| if (it == ioTypeMap.end()) { |
| tIoKinds newLists = { list, nullptr, nullptr }; |
| ioTypeMap[type.getStruct()] = newLists; |
| } else |
| it->second.input = list; |
| finalList = list; |
| } else |
| finalList = it->second.input; |
| // edit for 'flat' |
| for (auto member = finalList->begin(); member != finalList->end(); ++member) { |
| if (needsFlat(*member->type)) { |
| member->type->getQualifier().clearInterpolation(); |
| member->type->getQualifier().flat = true; |
| } |
| } |
| } else { |
| type.getQualifier().clearInterpolation(); |
| type.getQualifier().flat = true; |
| } |
| } |
| }; |
| |
| // Do the actual work to make a type be a shader input or output variable, |
| // and clear the original to be non-IO (for use as a normal function parameter/return). |
| const auto makeIoVariable = [this](const char* name, TType& type, TStorageQualifier storage) -> TVariable* { |
| TVariable* ioVariable = makeInternalVariable(name, type); |
| clearUniformInputOutput(type.getQualifier()); |
| if (type.isStruct()) { |
| auto newLists = ioTypeMap.find(ioVariable->getType().getStruct()); |
| if (newLists != ioTypeMap.end()) { |
| if (storage == EvqVaryingIn && newLists->second.input) |
| ioVariable->getWritableType().setStruct(newLists->second.input); |
| else if (storage == EvqVaryingOut && newLists->second.output) |
| ioVariable->getWritableType().setStruct(newLists->second.output); |
| } |
| } |
| if (storage == EvqVaryingIn) { |
| correctInput(ioVariable->getWritableType().getQualifier()); |
| if (language == EShLangTessEvaluation) |
| if (!ioVariable->getType().isArray()) |
| ioVariable->getWritableType().getQualifier().patch = true; |
| } else { |
| correctOutput(ioVariable->getWritableType().getQualifier()); |
| } |
| ioVariable->getWritableType().getQualifier().storage = storage; |
| |
| fixBuiltInIoType(ioVariable->getWritableType()); |
| |
| return ioVariable; |
| }; |
| |
| // return value is actually a shader-scoped output (out) |
| if (function.getType().getBasicType() == EbtVoid) { |
| returnValue = nullptr; |
| } else { |
| if (language == EShLangTessControl) { |
| // tessellation evaluation in HLSL writes a per-ctrl-pt value, but it needs to be an |
| // array in SPIR-V semantics. We'll write to it indexed by invocation ID. |
| |
| returnValue = makeIoVariable("@entryPointOutput", function.getWritableType(), EvqVaryingOut); |
| |
| TType outputType; |
| outputType.shallowCopy(function.getType()); |
| |
| // vertices has necessarily already been set when handling entry point attributes. |
| TArraySizes* arraySizes = new TArraySizes; |
| arraySizes->addInnerSize(intermediate.getVertices()); |
| outputType.transferArraySizes(arraySizes); |
| |
| clearUniformInputOutput(function.getWritableType().getQualifier()); |
| returnValue = makeIoVariable("@entryPointOutput", outputType, EvqVaryingOut); |
| } else { |
| returnValue = makeIoVariable("@entryPointOutput", function.getWritableType(), EvqVaryingOut); |
| } |
| } |
| |
| // parameters are actually shader-scoped inputs and outputs (in or out) |
| for (int i = 0; i < function.getParamCount(); i++) { |
| TType& paramType = *function[i].type; |
| if (paramType.getQualifier().isParamInput()) { |
| synthesizeEditedInput(paramType); |
| TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType, EvqVaryingIn); |
| inputs.push_back(argAsGlobal); |
| } |
| if (paramType.getQualifier().isParamOutput()) { |
| TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType, EvqVaryingOut); |
| outputs.push_back(argAsGlobal); |
| } |
| } |
| } |
| |
| // 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) |
| { |
| // return value |
| if (function.getType().getBasicType() != EbtVoid) |
| clearUniformInputOutput(function.getWritableType().getQualifier()); |
| |
| // parameters. |
| // References to structuredbuffer types are left unmodified |
| for (int i = 0; i < function.getParamCount(); i++) |
| if (!isReference(*function[i].type)) |
| clearUniformInputOutput(function[i].type->getQualifier()); |
| } |
| |
| // 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.addUniShapeConversion(EOpReturn, *currentFunctionType, value); |
| if (value == nullptr || *currentFunctionType != value->getType()) { |
| error(loc, "type does not match, or is not convertible to, the function's return type", "return", ""); |
| return value; |
| } |
| } |
| |
| return intermediate.addBranch(EOpReturn, value, loc); |
| } |
| |
| void HlslParseContext::handleFunctionArgument(TFunction* function, |
| TIntermTyped*& arguments, TIntermTyped* newArg) |
| { |
| TParameter param = { 0, new TType, nullptr }; |
| param.type->shallowCopy(newArg->getType()); |
| |
| function->addParameter(param); |
| if (arguments) |
| arguments = intermediate.growAggregate(arguments, newArg); |
| else |
| arguments = newArg; |
| } |
| |
| // Position may require special handling: we can optionally invert Y. |
| // See: https://github.com/KhronosGroup/glslang/issues/1173 |
| // https://github.com/KhronosGroup/glslang/issues/494 |
| TIntermTyped* HlslParseContext::assignPosition(const TSourceLoc& loc, TOperator op, |
| TIntermTyped* left, TIntermTyped* right) |
| { |
| // If we are not asked for Y inversion, use a plain old assign. |
| if (!intermediate.getInvertY()) |
| return intermediate.addAssign(op, left, right, loc); |
| |
| // If we get here, we should invert Y. |
| TIntermAggregate* assignList = nullptr; |
| |
| // If this is a complex rvalue, we don't want to dereference it many times. Create a temporary. |
| TVariable* rhsTempVar = nullptr; |
| rhsTempVar = makeInternalVariable("@position", right->getType()); |
| rhsTempVar->getWritableType().getQualifier().makeTemporary(); |
| |
| { |
| TIntermTyped* rhsTempSym = intermediate.addSymbol(*rhsTempVar, loc); |
| assignList = intermediate.growAggregate(assignList, |
| intermediate.addAssign(EOpAssign, rhsTempSym, right, loc), loc); |
| } |
| |
| // pos.y = -pos.y |
| { |
| const int Y = 1; |
| |
| TIntermTyped* tempSymL = intermediate.addSymbol(*rhsTempVar, loc); |
| TIntermTyped* tempSymR = intermediate.addSymbol(*rhsTempVar, loc); |
| TIntermTyped* index = intermediate.addConstantUnion(Y, loc); |
| |
| TIntermTyped* lhsElement = intermediate.addIndex(EOpIndexDirect, tempSymL, index, loc); |
| TIntermTyped* rhsElement = intermediate.addIndex(EOpIndexDirect, tempSymR, index, loc); |
| |
| const TType derefType(right->getType(), 0); |
| |
| lhsElement->setType(derefType); |
| rhsElement->setType(derefType); |
| |
| TIntermTyped* yNeg = intermediate.addUnaryMath(EOpNegative, rhsElement, loc); |
| |
| assignList = intermediate.growAggregate(assignList, intermediate.addAssign(EOpAssign, lhsElement, yNeg, loc)); |
| } |
| |
| // Assign the rhs temp (now with Y inversion) to the final output |
| { |
| TIntermTyped* rhsTempSym = intermediate.addSymbol(*rhsTempVar, loc); |
| assignList = intermediate.growAggregate(assignList, intermediate.addAssign(op, left, rhsTempSym, loc)); |
| } |
| |
| assert(assignList != nullptr); |
| assignList->setOperator(EOpSequence); |
| |
| return assignList; |
| } |
| |
| // Clip and cull distance require special handling due to a semantic mismatch. In HLSL, |
| // these can be float scalar, float vector, or arrays of float scalar or float vector. |
| // In SPIR-V, they are arrays of scalar floats in all cases. We must copy individual components |
| // (e.g, both x and y components of a float2) out into the destination float array. |
| // |
| // The values are assigned to sequential members of the output array. The inner dimension |
| // is vector components. The outer dimension is array elements. |
| TIntermAggregate* HlslParseContext::assignClipCullDistance(const TSourceLoc& loc, TOperator op, int semanticId, |
| TIntermTyped* left, TIntermTyped* right) |
| { |
| switch (language) { |
| case EShLangFragment: |
| case EShLangVertex: |
| case EShLangGeometry: |
| break; |
| default: |
| error(loc, "unimplemented: clip/cull not currently implemented for this stage", "", ""); |
| return nullptr; |
| } |
| |
| TVariable** clipCullVar = nullptr; |
| |
| // Figure out if we are assigning to, or from, clip or cull distance. |
| const bool isOutput = isClipOrCullDistance(left->getType()); |
| |
| // This is the rvalue or lvalue holding the clip or cull distance. |
| TIntermTyped* clipCullNode = isOutput ? left : right; |
| // This is the value going into or out of the clip or cull distance. |
| TIntermTyped* internalNode = isOutput ? right : left; |
| |
| const TBuiltInVariable builtInType = clipCullNode->getQualifier().builtIn; |
| |
| decltype(clipSemanticNSizeIn)* semanticNSize = nullptr; |
| |
| // Refer to either the clip or the cull distance, depending on semantic. |
| switch (builtInType) { |
| case EbvClipDistance: |
| clipCullVar = isOutput ? &clipDistanceOutput : &clipDistanceInput; |
| semanticNSize = isOutput ? &clipSemanticNSizeOut : &clipSemanticNSizeIn; |
| break; |
| case EbvCullDistance: |
| clipCullVar = isOutput ? &cullDistanceOutput : &cullDistanceInput; |
| semanticNSize = isOutput ? &cullSemanticNSizeOut : &cullSemanticNSizeIn; |
| break; |
| |
| // called invalidly: we expected a clip or a cull distance. |
| // static compile time problem: should not happen. |
| default: assert(0); return nullptr; |
| } |
| |
| // This is the offset in the destination array of a given semantic's data |
| std::array<int, maxClipCullRegs> semanticOffset; |
| |
| // Calculate offset of variable of semantic N in destination array |
| int arrayLoc = 0; |
| int vecItems = 0; |
| |
| for (int x = 0; x < maxClipCullRegs; ++x) { |
| // See if we overflowed the vec4 packing |
| if ((vecItems + (*semanticNSize)[x]) > 4) { |
| arrayLoc = (arrayLoc + 3) & (~0x3); // round up to next multiple of 4 |
| vecItems = 0; |
| } |
| |
| semanticOffset[x] = arrayLoc; |
| vecItems += (*semanticNSize)[x]; |
| arrayLoc += (*semanticNSize)[x]; |
| } |
| |
| |
| // It can have up to 2 array dimensions (in the case of geometry shader inputs) |
| const TArraySizes* const internalArraySizes = internalNode->getType().getArraySizes(); |
| const int internalArrayDims = internalNode->getType().isArray() ? internalArraySizes->getNumDims() : 0; |
| // vector sizes: |
| const int internalVectorSize = internalNode->getType().getVectorSize(); |
| // array sizes, or 1 if it's not an array: |
| const int internalInnerArraySize = (internalArrayDims > 0 ? internalArraySizes->getDimSize(internalArrayDims-1) : 1); |
| const int internalOuterArraySize = (internalArrayDims > 1 ? internalArraySizes->getDimSize(0) : 1); |
| |
| // The created type may be an array of arrays, e.g, for geometry shader inputs. |
| const bool isImplicitlyArrayed = (language == EShLangGeometry && !isOutput); |
| |
| // If we haven't created the output already, create it now. |
| if (*clipCullVar == nullptr) { |
| // ClipDistance and CullDistance are handled specially in the entry point input/output copy |
| // algorithm, because they may need to be unpacked from components of vectors (or a scalar) |
| // into a float array, or vice versa. Here, we make the array the right size and type, |
| // which depends on the incoming data, which has several potential dimensions: |
| // * Semantic ID |
| // * vector size |
| // * array size |
| // Of those, semantic ID and array size cannot appear simultaneously. |
| // |
| // Also to note: for implicitly arrayed forms (e.g, geometry shader inputs), we need to create two |
| // array dimensions. The shader's declaration may have one or two array dimensions. One is always |
| // the geometry's dimension. |
| |
| const bool useInnerSize = internalArrayDims > 1 || !isImplicitlyArrayed; |
| |
| const int requiredInnerArraySize = arrayLoc * (useInnerSize ? internalInnerArraySize : 1); |
| const int requiredOuterArraySize = (internalArrayDims > 0) ? internalArraySizes->getDimSize(0) : 1; |
| |
| TType clipCullType(EbtFloat, clipCullNode->getType().getQualifier().storage, 1); |
| clipCullType.getQualifier() = clipCullNode->getType().getQualifier(); |
| |
| // Create required array dimension |
| TArraySizes* arraySizes = new TArraySizes; |
| if (isImplicitlyArrayed) |
| arraySizes->addInnerSize(requiredOuterArraySize); |
| arraySizes->addInnerSize(requiredInnerArraySize); |
| clipCullType.transferArraySizes(arraySizes); |
| |
| // Obtain symbol name: we'll use that for the symbol we introduce. |
| TIntermSymbol* sym = clipCullNode->getAsSymbolNode(); |
| assert(sym != nullptr); |
| |
| // We are moving the semantic ID from the layout location, so it is no longer needed or |
| // desired there. |
| clipCullType.getQualifier().layoutLocation = TQualifier::layoutLocationEnd; |
| |
| // Create variable and track its linkage |
| *clipCullVar = makeInternalVariable(sym->getName().c_str(), clipCullType); |
| |
| trackLinkage(**clipCullVar); |
| } |
| |
| // Create symbol for the clip or cull variable. |
| TIntermSymbol* clipCullSym = intermediate.addSymbol(**clipCullVar); |
| |
| // vector sizes: |
| const int clipCullVectorSize = clipCullSym->getType().getVectorSize(); |
| |
| // array sizes, or 1 if it's not an array: |
| const TArraySizes* const clipCullArraySizes = clipCullSym->getType().getArraySizes(); |
| const int clipCullOuterArraySize = isImplicitlyArrayed ? clipCullArraySizes->getDimSize(0) : 1; |
| const int clipCullInnerArraySize = clipCullArraySizes->getDimSize(isImplicitlyArrayed ? 1 : 0); |
| |
| // clipCullSym has got to be an array of scalar floats, per SPIR-V semantics. |
| // fixBuiltInIoType() should have handled that upstream. |
| assert(clipCullSym->getType().isArray()); |
| assert(clipCullSym->getType().getVectorSize() == 1); |
| assert(clipCullSym->getType().getBasicType() == EbtFloat); |
| |
| // We may be creating multiple sub-assignments. This is an aggregate to hold them. |
| // TODO: it would be possible to be clever sometimes and avoid the sequence node if not needed. |
| TIntermAggregate* assignList = nullptr; |
| |
| // Holds individual component assignments as we make them. |
| TIntermTyped* clipCullAssign = nullptr; |
| |
| // If the types are homomorphic, use a simple assign. No need to mess about with |
| // individual components. |
| if (clipCullSym->getType().isArray() == internalNode->getType().isArray() && |
| clipCullInnerArraySize == internalInnerArraySize && |
| clipCullOuterArraySize == internalOuterArraySize && |
| clipCullVectorSize == internalVectorSize) { |
| |
| if (isOutput) |
| clipCullAssign = intermediate.addAssign(op, clipCullSym, internalNode, loc); |
| else |
| clipCullAssign = intermediate.addAssign(op, internalNode, clipCullSym, loc); |
| |
| assignList = intermediate.growAggregate(assignList, clipCullAssign); |
| assignList->setOperator(EOpSequence); |
| |
| return assignList; |
| } |
| |
| // We are going to copy each component of the internal (per array element if indicated) to sequential |
| // array elements of the clipCullSym. This tracks the lhs element we're writing to as we go along. |
| // We may be starting in the middle - e.g, for a non-zero semantic ID calculated above. |
| int clipCullInnerArrayPos = semanticOffset[semanticId]; |
| int clipCullOuterArrayPos = 0; |
| |
| // Lambda to add an index to a node, set the type of the result, and return the new node. |
| const auto addIndex = [this, &loc](TIntermTyped* node, int pos) -> TIntermTyped* { |
| const TType derefType(node->getType(), 0); |
| node = intermediate.addIndex(EOpIndexDirect, node, intermediate.addConstantUnion(pos, loc), loc); |
| node->setType(derefType); |
| return node; |
| }; |
| |
| // Loop through every component of every element of the internal, and copy to or from the matching external. |
| for (int internalOuterArrayPos = 0; internalOuterArrayPos < internalOuterArraySize; ++internalOuterArrayPos) { |
| for (int internalInnerArrayPos = 0; internalInnerArrayPos < internalInnerArraySize; ++internalInnerArrayPos) { |
| for (int internalComponent = 0; internalComponent < internalVectorSize; ++internalComponent) { |
| // clip/cull array member to read from / write to: |
| TIntermTyped* clipCullMember = clipCullSym; |
| |
| // If implicitly arrayed, there is an outer array dimension involved |
| if (isImplicitlyArrayed) |
| clipCullMember = addIndex(clipCullMember, clipCullOuterArrayPos); |
| |
| // Index into proper array position for clip cull member |
| clipCullMember = addIndex(clipCullMember, clipCullInnerArrayPos++); |
| |
| // if needed, start over with next outer array slice. |
| if (isImplicitlyArrayed && clipCullInnerArrayPos >= clipCullInnerArraySize) { |
| clipCullInnerArrayPos = semanticOffset[semanticId]; |
| ++clipCullOuterArrayPos; |
| } |
| |
| // internal member to read from / write to: |
| TIntermTyped* internalMember = internalNode; |
| |
| // If internal node has outer array dimension, index appropriately. |
| if (internalArrayDims > 1) |
| internalMember = addIndex(internalMember, internalOuterArrayPos); |
| |
| // If internal node has inner array dimension, index appropriately. |
| if (internalArrayDims > 0) |
| internalMember = addIndex(internalMember, internalInnerArrayPos); |
| |
| // If internal node is a vector, extract the component of interest. |
| if (internalNode->getType().isVector()) |
| internalMember = addIndex(internalMember, internalComponent); |
| |
| // Create an assignment: output from internal to clip cull, or input from clip cull to internal. |
| if (isOutput) |
| clipCullAssign = intermediate.addAssign(op, clipCullMember, internalMember, loc); |
| else |
| clipCullAssign = intermediate.addAssign(op, internalMember, clipCullMember, loc); |
| |
| // Track assignment in the sequence. |
| assignList = intermediate.growAggregate(assignList, clipCullAssign); |
| } |
| } |
| } |
| |
| assert(assignList != nullptr); |
| assignList->setOperator(EOpSequence); |
| |
| return assignList; |
| } |
| |
| // Some simple source assignments need to be flattened to a sequence |
| // of AST assignments. Catch these and flatten, otherwise, pass through |
| // to intermediate.addAssign(). |
| // |
| // Also, assignment to matrix swizzles requires multiple component assignments, |
| // intercept those as well. |
| TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op, TIntermTyped* left, |
| TIntermTyped* right) |
| { |
| if (left == nullptr || right == nullptr) |
| return nullptr; |
| |
| // writing to opaques will require fixing transforms |
| if (left->getType().containsOpaque()) |
| intermediate.setNeedsLegalization(); |
| |
| if (left->getAsOperator() && left->getAsOperator()->getOp() == EOpMatrixSwizzle) |
| return handleAssignToMatrixSwizzle(loc, op, left, right); |
| |
| // Return true if the given node is an index operation into a split variable. |
| const auto indexesSplit = [this](const TIntermTyped* node) -> bool { |
| const TIntermBinary* binaryNode = node->getAsBinaryNode(); |
| |
| if (binaryNode == nullptr) |
| return false; |
| |
| return (binaryNode->getOp() == EOpIndexDirect || binaryNode->getOp() == EOpIndexIndirect) && |
| wasSplit(binaryNode->getLeft()); |
| }; |
| |
| // Return true if this stage assigns clip position with potentially inverted Y |
| const auto assignsClipPos = [this](const TIntermTyped* node) -> bool { |
| return node->getType().getQualifier().builtIn == EbvPosition && |
| (language == EShLangVertex || language == EShLangGeometry || language == EShLangTessEvaluation); |
| }; |
| |
| const bool isSplitLeft = wasSplit(left) || indexesSplit(left); |
| const bool isSplitRight = wasSplit(right) || indexesSplit(right); |
| |
| const bool isFlattenLeft = wasFlattened(left); |
| const bool isFlattenRight = wasFlattened(right); |
| |
| // OK to do a single assign if neither side is split or flattened. Otherwise, |
| // fall through to a member-wise copy. |
| if (!isFlattenLeft && !isFlattenRight && !isSplitLeft && !isSplitRight) { |
| // Clip and cull distance requires more processing. See comment above assignClipCullDistance. |
| if (isClipOrCullDistance(left->getType()) || isClipOrCullDistance(right->getType())) { |
| const bool isOutput = isClipOrCullDistance(left->getType()); |
| |
| const int semanticId = (isOutput ? left : right)->getType().getQualifier().layoutLocation; |
| return assignClipCullDistance(loc, op, semanticId, left, right); |
| } else if (assignsClipPos(left)) { |
| // Position can require special handling: see comment above assignPosition |
| return assignPosition(loc, op, left, right); |
| } else if (left->getQualifier().builtIn == EbvSampleMask) { |
| // Certain builtins are required to be arrayed outputs in SPIR-V, but may internally be scalars |
| // in the shader. Copy the scalar RHS into the LHS array element zero, if that happens. |
| if (left->isArray() && !right->isArray()) { |
| const TType derefType(left->getType(), 0); |
| left = intermediate.addIndex(EOpIndexDirect, left, intermediate.addConstantUnion(0, loc), loc); |
| left->setType(derefType); |
| // Fall through to add assign. |
| } |
| } |
| |
| return intermediate.addAssign |