// // Copyright (C) 2013 LunarG, Inc. // Copyright (C) 2017 ARM Limited. // Copyright (C) 2015-2018 Google, 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. // // // Do link-time merging and validation of intermediate representations. // // Basic model is that during compilation, each compilation unit (shader) is // compiled into one TIntermediate instance. Then, at link time, multiple // units for the same stage can be merged together, which can generate errors. // Then, after all merging, a single instance of TIntermediate represents // the whole stage. A final error check can be done on the resulting stage, // even if no merging was done (i.e., the stage was only one compilation unit). // #include "localintermediate.h" #include "../Include/InfoSink.h" #include "SymbolTable.h" namespace glslang { // // Link-time error emitter. // void TIntermediate::error(TInfoSink& infoSink, const char* message) { #ifndef GLSLANG_WEB infoSink.info.prefix(EPrefixError); infoSink.info << "Linking " << StageName(language) << " stage: " << message << "\n"; #endif ++numErrors; } // Link-time warning. void TIntermediate::warn(TInfoSink& infoSink, const char* message) { #ifndef GLSLANG_WEB infoSink.info.prefix(EPrefixWarning); infoSink.info << "Linking " << StageName(language) << " stage: " << message << "\n"; #endif } // TODO: 4.4 offset/align: "Two blocks linked together in the same program with the same block // name must have the exact same set of members qualified with offset and their integral-constant // expression values must be the same, or a link-time error results." // // Merge the information from 'unit' into 'this' // void TIntermediate::merge(TInfoSink& infoSink, TIntermediate& unit) { #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) mergeCallGraphs(infoSink, unit); mergeModes(infoSink, unit); mergeTrees(infoSink, unit); #endif } // // check that link objects between stages // void TIntermediate::mergeUniformObjects(TInfoSink& infoSink, TIntermediate& unit) { if (unit.treeRoot == nullptr || treeRoot == nullptr) return; // Get the linker-object lists TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); TIntermSequence unitLinkerObjects = unit.findLinkerObjects()->getSequence(); // filter unitLinkerObjects to only contain uniforms auto end = std::remove_if(unitLinkerObjects.begin(), unitLinkerObjects.end(), [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqUniform && node->getAsSymbolNode()->getQualifier().storage != EvqBuffer; }); unitLinkerObjects.resize(end - unitLinkerObjects.begin()); // merge uniforms and do error checking bool mergeExistingOnly = false; mergeGlobalUniformBlocks(infoSink, unit, mergeExistingOnly); mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); } // // do error checking on the shader boundary in / out vars // void TIntermediate::checkStageIO(TInfoSink& infoSink, TIntermediate& unit) { if (unit.treeRoot == nullptr || treeRoot == nullptr) return; // Get copies of the linker-object lists TIntermSequence linkerObjects = findLinkerObjects()->getSequence(); TIntermSequence unitLinkerObjects = unit.findLinkerObjects()->getSequence(); // filter linkerObjects to only contain out variables auto end = std::remove_if(linkerObjects.begin(), linkerObjects.end(), [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqVaryingOut; }); linkerObjects.resize(end - linkerObjects.begin()); // filter unitLinkerObjects to only contain in variables auto unitEnd = std::remove_if(unitLinkerObjects.begin(), unitLinkerObjects.end(), [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqVaryingIn; }); unitLinkerObjects.resize(unitEnd - unitLinkerObjects.begin()); // do matching and error checking mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); // TODO: final check; make sure that any statically used `in` have matching `out` written to } void TIntermediate::mergeCallGraphs(TInfoSink& infoSink, TIntermediate& unit) { if (unit.getNumEntryPoints() > 0) { if (getNumEntryPoints() > 0) error(infoSink, "can't handle multiple entry points per stage"); else { entryPointName = unit.getEntryPointName(); entryPointMangledName = unit.getEntryPointMangledName(); } } numEntryPoints += unit.getNumEntryPoints(); callGraph.insert(callGraph.end(), unit.callGraph.begin(), unit.callGraph.end()); } #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) #define MERGE_MAX(member) member = std::max(member, unit.member) #define MERGE_TRUE(member) if (unit.member) member = unit.member; void TIntermediate::mergeModes(TInfoSink& infoSink, TIntermediate& unit) { if (language != unit.language) error(infoSink, "stages must match when linking into a single stage"); if (getSource() == EShSourceNone) setSource(unit.getSource()); if (getSource() != unit.getSource()) error(infoSink, "can't link compilation units from different source languages"); if (treeRoot == nullptr) { profile = unit.profile; version = unit.version; requestedExtensions = unit.requestedExtensions; } else { if ((isEsProfile()) != (unit.isEsProfile())) error(infoSink, "Cannot cross link ES and desktop profiles"); else if (unit.profile == ECompatibilityProfile) profile = ECompatibilityProfile; version = std::max(version, unit.version); requestedExtensions.insert(unit.requestedExtensions.begin(), unit.requestedExtensions.end()); } MERGE_MAX(spvVersion.spv); MERGE_MAX(spvVersion.vulkanGlsl); MERGE_MAX(spvVersion.vulkan); MERGE_MAX(spvVersion.openGl); MERGE_TRUE(spvVersion.vulkanRelaxed); numErrors += unit.getNumErrors(); // Only one push_constant is allowed, mergeLinkerObjects() will ensure the push_constant // is the same for all units. if (numPushConstants > 1 || unit.numPushConstants > 1) error(infoSink, "Only one push_constant block is allowed per stage"); numPushConstants = std::min(numPushConstants + unit.numPushConstants, 1); if (unit.invocations != TQualifier::layoutNotSet) { if (invocations == TQualifier::layoutNotSet) invocations = unit.invocations; else if (invocations != unit.invocations) error(infoSink, "number of invocations must match between compilation units"); } if (vertices == TQualifier::layoutNotSet) vertices = unit.vertices; else if (unit.vertices != TQualifier::layoutNotSet && vertices != unit.vertices) { if (language == EShLangGeometry || language == EShLangMeshNV) error(infoSink, "Contradictory layout max_vertices values"); else if (language == EShLangTessControl) error(infoSink, "Contradictory layout vertices values"); else assert(0); } if (primitives == TQualifier::layoutNotSet) primitives = unit.primitives; else if (primitives != unit.primitives) { if (language == EShLangMeshNV) error(infoSink, "Contradictory layout max_primitives values"); else assert(0); } if (inputPrimitive == ElgNone) inputPrimitive = unit.inputPrimitive; else if (unit.inputPrimitive != ElgNone && inputPrimitive != unit.inputPrimitive) error(infoSink, "Contradictory input layout primitives"); if (outputPrimitive == ElgNone) outputPrimitive = unit.outputPrimitive; else if (unit.outputPrimitive != ElgNone && outputPrimitive != unit.outputPrimitive) error(infoSink, "Contradictory output layout primitives"); if (originUpperLeft != unit.originUpperLeft || pixelCenterInteger != unit.pixelCenterInteger) error(infoSink, "gl_FragCoord redeclarations must match across shaders"); if (vertexSpacing == EvsNone) vertexSpacing = unit.vertexSpacing; else if (vertexSpacing != unit.vertexSpacing) error(infoSink, "Contradictory input vertex spacing"); if (vertexOrder == EvoNone) vertexOrder = unit.vertexOrder; else if (vertexOrder != unit.vertexOrder) error(infoSink, "Contradictory triangle ordering"); MERGE_TRUE(pointMode); for (int i = 0; i < 3; ++i) { if (unit.localSizeNotDefault[i]) { if (!localSizeNotDefault[i]) { localSize[i] = unit.localSize[i]; localSizeNotDefault[i] = true; } else if (localSize[i] != unit.localSize[i]) error(infoSink, "Contradictory local size"); } if (localSizeSpecId[i] == TQualifier::layoutNotSet) localSizeSpecId[i] = unit.localSizeSpecId[i]; else if (localSizeSpecId[i] != unit.localSizeSpecId[i]) error(infoSink, "Contradictory local size specialization ids"); } MERGE_TRUE(earlyFragmentTests); MERGE_TRUE(postDepthCoverage); if (depthLayout == EldNone) depthLayout = unit.depthLayout; else if (depthLayout != unit.depthLayout) error(infoSink, "Contradictory depth layouts"); MERGE_TRUE(depthReplacing); MERGE_TRUE(hlslFunctionality1); blendEquations |= unit.blendEquations; MERGE_TRUE(xfbMode); for (size_t b = 0; b < xfbBuffers.size(); ++b) { if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) xfbBuffers[b].stride = unit.xfbBuffers[b].stride; else if (xfbBuffers[b].stride != unit.xfbBuffers[b].stride) error(infoSink, "Contradictory xfb_stride"); xfbBuffers[b].implicitStride = std::max(xfbBuffers[b].implicitStride, unit.xfbBuffers[b].implicitStride); if (unit.xfbBuffers[b].contains64BitType) xfbBuffers[b].contains64BitType = true; if (unit.xfbBuffers[b].contains32BitType) xfbBuffers[b].contains32BitType = true; if (unit.xfbBuffers[b].contains16BitType) xfbBuffers[b].contains16BitType = true; // TODO: 4.4 link: enhanced layouts: compare ranges } MERGE_TRUE(multiStream); MERGE_TRUE(layoutOverrideCoverage); MERGE_TRUE(geoPassthroughEXT); for (unsigned int i = 0; i < unit.shiftBinding.size(); ++i) { if (unit.shiftBinding[i] > 0) setShiftBinding((TResourceType)i, unit.shiftBinding[i]); } for (unsigned int i = 0; i < unit.shiftBindingForSet.size(); ++i) { for (auto it = unit.shiftBindingForSet[i].begin(); it != unit.shiftBindingForSet[i].end(); ++it) setShiftBindingForSet((TResourceType)i, it->second, it->first); } resourceSetBinding.insert(resourceSetBinding.end(), unit.resourceSetBinding.begin(), unit.resourceSetBinding.end()); MERGE_TRUE(autoMapBindings); MERGE_TRUE(autoMapLocations); MERGE_TRUE(invertY); MERGE_TRUE(flattenUniformArrays); MERGE_TRUE(useUnknownFormat); MERGE_TRUE(hlslOffsets); MERGE_TRUE(useStorageBuffer); MERGE_TRUE(invariantAll); MERGE_TRUE(hlslIoMapping); // TODO: sourceFile // TODO: sourceText // TODO: processes MERGE_TRUE(needToLegalize); MERGE_TRUE(binaryDoubleOutput); MERGE_TRUE(usePhysicalStorageBuffer); } // // Merge the 'unit' AST into 'this' AST. // That includes rationalizing the unique IDs, which were set up independently, // and might have overlaps that are not the same symbol, or might have different // IDs for what should be the same shared symbol. // void TIntermediate::mergeTrees(TInfoSink& infoSink, TIntermediate& unit) { if (unit.treeRoot == nullptr) return; if (treeRoot == nullptr) { treeRoot = unit.treeRoot; return; } // Getting this far means we have two existing trees to merge... numShaderRecordBlocks += unit.numShaderRecordBlocks; numTaskNVBlocks += unit.numTaskNVBlocks; // Get the top-level globals of each unit TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); TIntermSequence& unitGlobals = unit.treeRoot->getAsAggregate()->getSequence(); // Get the linker-object lists TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); const TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); // Map by global name to unique ID to rationalize the same object having // differing IDs in different trees. TIdMaps idMaps; long long idShift; seedIdMap(idMaps, idShift); remapIds(idMaps, idShift + 1, unit); mergeBodies(infoSink, globals, unitGlobals); bool mergeExistingOnly = false; mergeGlobalUniformBlocks(infoSink, unit, mergeExistingOnly); mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); ioAccessed.insert(unit.ioAccessed.begin(), unit.ioAccessed.end()); } #endif static const TString& getNameForIdMap(TIntermSymbol* symbol) { TShaderInterface si = symbol->getType().getShaderInterface(); if (si == EsiNone) return symbol->getName(); else return symbol->getType().getTypeName(); } // Traverser that seeds an ID map with all built-ins, and tracks the // maximum ID used, currently using (maximum ID + 1) as new symbol id shift seed. // Level id will keep same after shifting. // (It would be nice to put this in a function, but that causes warnings // on having no bodies for the copy-constructor/operator=.) class TBuiltInIdTraverser : public TIntermTraverser { public: TBuiltInIdTraverser(TIdMaps& idMaps) : idMaps(idMaps), idShift(0) { } // If it's a built in, add it to the map. virtual void visitSymbol(TIntermSymbol* symbol) { const TQualifier& qualifier = symbol->getType().getQualifier(); if (qualifier.builtIn != EbvNone) { TShaderInterface si = symbol->getType().getShaderInterface(); idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); } idShift = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | std::max(idShift & TSymbolTable::uniqueIdMask, symbol->getId() & TSymbolTable::uniqueIdMask); } long long getIdShift() const { return idShift; } protected: TBuiltInIdTraverser(TBuiltInIdTraverser&); TBuiltInIdTraverser& operator=(TBuiltInIdTraverser&); TIdMaps& idMaps; long long idShift; }; // Traverser that seeds an ID map with non-builtins. // (It would be nice to put this in a function, but that causes warnings // on having no bodies for the copy-constructor/operator=.) class TUserIdTraverser : public TIntermTraverser { public: TUserIdTraverser(TIdMaps& idMaps) : idMaps(idMaps) { } // If its a non-built-in global, add it to the map. virtual void visitSymbol(TIntermSymbol* symbol) { const TQualifier& qualifier = symbol->getType().getQualifier(); if (qualifier.builtIn == EbvNone) { TShaderInterface si = symbol->getType().getShaderInterface(); idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); } } protected: TUserIdTraverser(TUserIdTraverser&); TUserIdTraverser& operator=(TUserIdTraverser&); TIdMaps& idMaps; // over biggest id }; // Initialize the the ID map with what we know of 'this' AST. void TIntermediate::seedIdMap(TIdMaps& idMaps, long long& idShift) { // all built-ins everywhere need to align on IDs and contribute to the max ID TBuiltInIdTraverser builtInIdTraverser(idMaps); treeRoot->traverse(&builtInIdTraverser); idShift = builtInIdTraverser.getIdShift() & TSymbolTable::uniqueIdMask; // user variables in the linker object list need to align on ids TUserIdTraverser userIdTraverser(idMaps); findLinkerObjects()->traverse(&userIdTraverser); } // Traverser to map an AST ID to what was known from the seeding AST. // (It would be nice to put this in a function, but that causes warnings // on having no bodies for the copy-constructor/operator=.) class TRemapIdTraverser : public TIntermTraverser { public: TRemapIdTraverser(const TIdMaps& idMaps, long long idShift) : idMaps(idMaps), idShift(idShift) { } // Do the mapping: // - if the same symbol, adopt the 'this' ID // - otherwise, ensure a unique ID by shifting to a new space virtual void visitSymbol(TIntermSymbol* symbol) { const TQualifier& qualifier = symbol->getType().getQualifier(); bool remapped = false; if (qualifier.isLinkable() || qualifier.builtIn != EbvNone) { TShaderInterface si = symbol->getType().getShaderInterface(); auto it = idMaps[si].find(getNameForIdMap(symbol)); if (it != idMaps[si].end()) { uint64_t id = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | (it->second & TSymbolTable::uniqueIdMask); symbol->changeId(id); remapped = true; } } if (!remapped) symbol->changeId(symbol->getId() + idShift); } protected: TRemapIdTraverser(TRemapIdTraverser&); TRemapIdTraverser& operator=(TRemapIdTraverser&); const TIdMaps& idMaps; long long idShift; }; void TIntermediate::remapIds(const TIdMaps& idMaps, long long idShift, TIntermediate& unit) { // Remap all IDs to either share or be unique, as dictated by the idMap and idShift. TRemapIdTraverser idTraverser(idMaps, idShift); unit.getTreeRoot()->traverse(&idTraverser); } // // Merge the function bodies and global-level initializers from unitGlobals into globals. // Will error check duplication of function bodies for the same signature. // void TIntermediate::mergeBodies(TInfoSink& infoSink, TIntermSequence& globals, const TIntermSequence& unitGlobals) { // TODO: link-time performance: Processing in alphabetical order will be faster // Error check the global objects, not including the linker objects for (unsigned int child = 0; child < globals.size() - 1; ++child) { for (unsigned int unitChild = 0; unitChild < unitGlobals.size() - 1; ++unitChild) { TIntermAggregate* body = globals[child]->getAsAggregate(); TIntermAggregate* unitBody = unitGlobals[unitChild]->getAsAggregate(); if (body && unitBody && body->getOp() == EOpFunction && unitBody->getOp() == EOpFunction && body->getName() == unitBody->getName()) { error(infoSink, "Multiple function bodies in multiple compilation units for the same signature in the same stage:"); infoSink.info << " " << globals[child]->getAsAggregate()->getName() << "\n"; } } } // Merge the global objects, just in front of the linker objects globals.insert(globals.end() - 1, unitGlobals.begin(), unitGlobals.end() - 1); } static inline bool isSameInterface(TIntermSymbol* symbol, EShLanguage stage, TIntermSymbol* unitSymbol, EShLanguage unitStage) { return // 1) same stage and same shader interface (stage == unitStage && symbol->getType().getShaderInterface() == unitSymbol->getType().getShaderInterface()) || // 2) accross stages and both are uniform or buffer (symbol->getQualifier().storage == EvqUniform && unitSymbol->getQualifier().storage == EvqUniform) || (symbol->getQualifier().storage == EvqBuffer && unitSymbol->getQualifier().storage == EvqBuffer) || // 3) in/out matched across stage boundary (stage < unitStage && symbol->getQualifier().storage == EvqVaryingOut && unitSymbol->getQualifier().storage == EvqVaryingIn) || (unitStage < stage && symbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().storage == EvqVaryingOut); } // // Global Unfiform block stores any default uniforms (i.e. uniforms without a block) // If two linked stages declare the same member, they are meant to be the same uniform // and need to be in the same block // merge the members of different stages to allow them to be linked properly // as a single block // void TIntermediate::mergeGlobalUniformBlocks(TInfoSink& infoSink, TIntermediate& unit, bool mergeExistingOnly) { TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); // build lists of default blocks from the intermediates TIntermSequence defaultBlocks; TIntermSequence unitDefaultBlocks; auto filter = [](TIntermSequence& list, TIntermNode* node) { if (node->getAsSymbolNode()->getQualifier().defaultBlock) { list.push_back(node); } }; std::for_each(linkerObjects.begin(), linkerObjects.end(), [&defaultBlocks, &filter](TIntermNode* node) { filter(defaultBlocks, node); }); std::for_each(unitLinkerObjects.begin(), unitLinkerObjects.end(), [&unitDefaultBlocks, &filter](TIntermNode* node) { filter(unitDefaultBlocks, node); }); auto itUnitBlock = unitDefaultBlocks.begin(); for (; itUnitBlock != unitDefaultBlocks.end(); itUnitBlock++) { bool add = !mergeExistingOnly; auto itBlock = defaultBlocks.begin(); for (; itBlock != defaultBlocks.end(); itBlock++) { TIntermSymbol* block = (*itBlock)->getAsSymbolNode(); TIntermSymbol* unitBlock = (*itUnitBlock)->getAsSymbolNode(); assert(block && unitBlock); // if the two default blocks match, then merge their definitions if (block->getType().getTypeName() == unitBlock->getType().getTypeName() && block->getQualifier().storage == unitBlock->getQualifier().storage) { add = false; mergeBlockDefinitions(infoSink, block, unitBlock, &unit); } } if (add) { // push back on original list; won't change the size of the list we're iterating over linkerObjects.push_back(*itUnitBlock); } } } void TIntermediate::mergeBlockDefinitions(TInfoSink& infoSink, TIntermSymbol* block, TIntermSymbol* unitBlock, TIntermediate* unit) { if (block->getType() == unitBlock->getType()) { return; } if (block->getType().getTypeName() != unitBlock->getType().getTypeName() || block->getType().getBasicType() != unitBlock->getType().getBasicType() || block->getQualifier().storage != unitBlock->getQualifier().storage || block->getQualifier().layoutSet != unitBlock->getQualifier().layoutSet) { // different block names likely means different blocks return; } // merge the struct // order of declarations doesn't matter and they matched based on member name TTypeList* memberList = block->getType().getWritableStruct(); TTypeList* unitMemberList = unitBlock->getType().getWritableStruct(); // keep track of which members have changed position // so we don't have to search the array again std::map memberIndexUpdates; size_t memberListStartSize = memberList->size(); for (unsigned int i = 0; i < unitMemberList->size(); ++i) { bool merge = true; for (unsigned int j = 0; j < memberListStartSize; ++j) { if ((*memberList)[j].type->getFieldName() == (*unitMemberList)[i].type->getFieldName()) { merge = false; const TType* memberType = (*memberList)[j].type; const TType* unitMemberType = (*unitMemberList)[i].type; // compare types // don't need as many checks as when merging symbols, since // initializers and most qualifiers are stripped when the member is moved into the block if ((*memberType) != (*unitMemberType)) { error(infoSink, "Types must match:"); infoSink.info << " " << memberType->getFieldName() << ": "; infoSink.info << "\"" << memberType->getCompleteString() << "\" versus "; infoSink.info << "\"" << unitMemberType->getCompleteString() << "\"\n"; } memberIndexUpdates[i] = j; } } if (merge) { memberList->push_back((*unitMemberList)[i]); memberIndexUpdates[i] = (unsigned int)memberList->size() - 1; } } TType unitType; unitType.shallowCopy(unitBlock->getType()); // update symbol node in unit tree, // and other nodes that may reference it class TMergeBlockTraverser : public TIntermTraverser { public: TMergeBlockTraverser(const glslang::TType &type, const glslang::TType& unitType, glslang::TIntermediate& unit, const std::map& memberIdxUpdates) : newType(type), unitType(unitType), unit(unit), memberIndexUpdates(memberIdxUpdates) { } virtual ~TMergeBlockTraverser() { } const glslang::TType& newType; // type with modifications const glslang::TType& unitType; // copy of original type glslang::TIntermediate& unit; // intermediate that is being updated const std::map& memberIndexUpdates; virtual void visitSymbol(TIntermSymbol* symbol) { glslang::TType& symType = symbol->getWritableType(); if (symType == unitType) { // each symbol node has a local copy of the unitType // if merging involves changing properties that aren't shared objects // they should be updated in all instances // e.g. the struct list is a ptr to an object, so it can be updated // once, outside the traverser //*symType.getWritableStruct() = *newType.getStruct(); } } virtual bool visitBinary(TVisit, glslang::TIntermBinary* node) { if (node->getOp() == EOpIndexDirectStruct && node->getLeft()->getType() == unitType) { // this is a dereference to a member of the block since the // member list changed, need to update this to point to the // right index assert(node->getRight()->getAsConstantUnion()); glslang::TIntermConstantUnion* constNode = node->getRight()->getAsConstantUnion(); unsigned int memberIdx = constNode->getConstArray()[0].getUConst(); unsigned int newIdx = memberIndexUpdates.at(memberIdx); TIntermTyped* newConstNode = unit.addConstantUnion(newIdx, node->getRight()->getLoc()); node->setRight(newConstNode); delete constNode; return true; } return true; } } finalLinkTraverser(block->getType(), unitType, *unit, memberIndexUpdates); // update the tree to use the new type unit->getTreeRoot()->traverse(&finalLinkTraverser); // update the member list (*unitMemberList) = (*memberList); } // // Merge the linker objects from unitLinkerObjects into linkerObjects. // Duplication is expected and filtered out, but contradictions are an error. // void TIntermediate::mergeLinkerObjects(TInfoSink& infoSink, TIntermSequence& linkerObjects, const TIntermSequence& unitLinkerObjects, EShLanguage unitStage) { // Error check and merge the linker objects (duplicates should not be created) std::size_t initialNumLinkerObjects = linkerObjects.size(); for (unsigned int unitLinkObj = 0; unitLinkObj < unitLinkerObjects.size(); ++unitLinkObj) { bool merge = true; for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); assert(symbol && unitSymbol); bool isSameSymbol = false; // If they are both blocks in the same shader interface, // match by the block-name, not the identifier name. if (symbol->getType().getBasicType() == EbtBlock && unitSymbol->getType().getBasicType() == EbtBlock) { if (isSameInterface(symbol, getStage(), unitSymbol, unitStage)) { isSameSymbol = symbol->getType().getTypeName() == unitSymbol->getType().getTypeName(); } } else if (symbol->getName() == unitSymbol->getName()) isSameSymbol = true; if (isSameSymbol) { // filter out copy merge = false; // but if one has an initializer and the other does not, update // the initializer if (symbol->getConstArray().empty() && ! unitSymbol->getConstArray().empty()) symbol->setConstArray(unitSymbol->getConstArray()); // Similarly for binding if (! symbol->getQualifier().hasBinding() && unitSymbol->getQualifier().hasBinding()) symbol->getQualifier().layoutBinding = unitSymbol->getQualifier().layoutBinding; // Similarly for location if (!symbol->getQualifier().hasLocation() && unitSymbol->getQualifier().hasLocation()) { symbol->getQualifier().layoutLocation = unitSymbol->getQualifier().layoutLocation; } // Update implicit array sizes mergeImplicitArraySizes(symbol->getWritableType(), unitSymbol->getType()); // Check for consistent types/qualification/initializers etc. mergeErrorCheck(infoSink, *symbol, *unitSymbol, unitStage); } // If different symbols, verify they arn't push_constant since there can only be one per stage else if (symbol->getQualifier().isPushConstant() && unitSymbol->getQualifier().isPushConstant() && getStage() == unitStage) error(infoSink, "Only one push_constant block is allowed per stage"); } if (merge) { linkerObjects.push_back(unitLinkerObjects[unitLinkObj]); // for anonymous blocks, check that their members don't conflict with other names if (unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getBasicType() == EbtBlock && IsAnonymous(unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getName())) { for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); assert(symbol && unitSymbol); auto checkName = [this, unitSymbol, &infoSink](const TString& name) { for (unsigned int i = 0; i < unitSymbol->getType().getStruct()->size(); ++i) { if (name == (*unitSymbol->getType().getStruct())[i].type->getFieldName()) { error(infoSink, "Anonymous member name used for global variable or other anonymous member: "); infoSink.info << (*unitSymbol->getType().getStruct())[i].type->getCompleteString() << "\n"; } } }; if (isSameInterface(symbol, getStage(), unitSymbol, unitStage)) { checkName(symbol->getName()); // check members of other anonymous blocks if (symbol->getBasicType() == EbtBlock && IsAnonymous(symbol->getName())) { for (unsigned int i = 0; i < symbol->getType().getStruct()->size(); ++i) { checkName((*symbol->getType().getStruct())[i].type->getFieldName()); } } } } } } } } // TODO 4.5 link functionality: cull distance array size checking // Recursively merge the implicit array sizes through the objects' respective type trees. void TIntermediate::mergeImplicitArraySizes(TType& type, const TType& unitType) { if (type.isUnsizedArray()) { if (unitType.isUnsizedArray()) { type.updateImplicitArraySize(unitType.getImplicitArraySize()); if (unitType.isArrayVariablyIndexed()) type.setArrayVariablyIndexed(); } else if (unitType.isSizedArray()) type.changeOuterArraySize(unitType.getOuterArraySize()); } // Type mismatches are caught and reported after this, just be careful for now. if (! type.isStruct() || ! unitType.isStruct() || type.getStruct()->size() != unitType.getStruct()->size()) return; for (int i = 0; i < (int)type.getStruct()->size(); ++i) mergeImplicitArraySizes(*(*type.getStruct())[i].type, *(*unitType.getStruct())[i].type); } // // Compare two global objects from two compilation units and see if they match // well enough. Rules can be different for intra- vs. cross-stage matching. // // This function only does one of intra- or cross-stage matching per call. // void TIntermediate::mergeErrorCheck(TInfoSink& infoSink, const TIntermSymbol& symbol, const TIntermSymbol& unitSymbol, EShLanguage unitStage) { #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) bool crossStage = getStage() != unitStage; bool writeTypeComparison = false; // Types have to match { // but, we make an exception if one is an implicit array and the other is sized // or if the array sizes differ because of the extra array dimension on some in/out boundaries bool arraysMatch = false; if (isIoResizeArray(symbol.getType(), getStage()) || isIoResizeArray(unitSymbol.getType(), unitStage)) { // if the arrays have an extra dimension because of the stage. // compare dimensions while ignoring the outer dimension unsigned int firstDim = isIoResizeArray(symbol.getType(), getStage()) ? 1 : 0; unsigned int numDim = symbol.getArraySizes() ? symbol.getArraySizes()->getNumDims() : 0; unsigned int unitFirstDim = isIoResizeArray(unitSymbol.getType(), unitStage) ? 1 : 0; unsigned int unitNumDim = unitSymbol.getArraySizes() ? unitSymbol.getArraySizes()->getNumDims() : 0; arraysMatch = (numDim - firstDim) == (unitNumDim - unitFirstDim); // check that array sizes match as well for (unsigned int i = 0; i < (numDim - firstDim) && arraysMatch; i++) { if (symbol.getArraySizes()->getDimSize(firstDim + i) != unitSymbol.getArraySizes()->getDimSize(unitFirstDim + i)) { arraysMatch = false; break; } } } else { arraysMatch = symbol.getType().sameArrayness(unitSymbol.getType()) || (symbol.getType().isArray() && unitSymbol.getType().isArray() && (symbol.getType().isUnsizedArray() || unitSymbol.getType().isUnsizedArray())); } if (!symbol.getType().sameElementType(unitSymbol.getType()) || !symbol.getType().sameTypeParameters(unitSymbol.getType()) || !arraysMatch ) { writeTypeComparison = true; error(infoSink, "Types must match:"); } } // Interface block member-wise layout qualifiers have to match if (symbol.getType().getBasicType() == EbtBlock && unitSymbol.getType().getBasicType() == EbtBlock && symbol.getType().getStruct() && unitSymbol.getType().getStruct() && symbol.getType().sameStructType(unitSymbol.getType())) { for (unsigned int i = 0; i < symbol.getType().getStruct()->size(); ++i) { const TQualifier& qualifier = (*symbol.getType().getStruct())[i].type->getQualifier(); const TQualifier& unitQualifier = (*unitSymbol.getType().getStruct())[i].type->getQualifier(); if (qualifier.layoutMatrix != unitQualifier.layoutMatrix || qualifier.layoutOffset != unitQualifier.layoutOffset || qualifier.layoutAlign != unitQualifier.layoutAlign || qualifier.layoutLocation != unitQualifier.layoutLocation || qualifier.layoutComponent != unitQualifier.layoutComponent) { error(infoSink, "Interface block member layout qualifiers must match:"); writeTypeComparison = true; } } } bool isInOut = crossStage && ((symbol.getQualifier().storage == EvqVaryingIn && unitSymbol.getQualifier().storage == EvqVaryingOut) || (symbol.getQualifier().storage == EvqVaryingOut && unitSymbol.getQualifier().storage == EvqVaryingIn)); // Qualifiers have to (almost) match // Storage... if (!isInOut && symbol.getQualifier().storage != unitSymbol.getQualifier().storage) { error(infoSink, "Storage qualifiers must match:"); writeTypeComparison = true; } // Uniform and buffer blocks must either both have an instance name, or // must both be anonymous. The names don't need to match though. if (symbol.getQualifier().isUniformOrBuffer() && (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()))) { error(infoSink, "Matched Uniform or Storage blocks must all be anonymous," " or all be named:"); writeTypeComparison = true; } if (symbol.getQualifier().storage == unitSymbol.getQualifier().storage && (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()) || (!IsAnonymous(symbol.getName()) && symbol.getName() != unitSymbol.getName()))) { warn(infoSink, "Matched shader interfaces are using different instance names."); writeTypeComparison = true; } // Precision... if (!isInOut && symbol.getQualifier().precision != unitSymbol.getQualifier().precision) { error(infoSink, "Precision qualifiers must match:"); writeTypeComparison = true; } // Invariance... if (! crossStage && symbol.getQualifier().invariant != unitSymbol.getQualifier().invariant) { error(infoSink, "Presence of invariant qualifier must match:"); writeTypeComparison = true; } // Precise... if (! crossStage && symbol.getQualifier().isNoContraction() != unitSymbol.getQualifier().isNoContraction()) { error(infoSink, "Presence of precise qualifier must match:"); writeTypeComparison = true; } // Auxiliary and interpolation... // "interpolation qualification (e.g., flat) and auxiliary qualification (e.g. centroid) may differ. // These mismatches are allowed between any pair of stages ... // those provided in the fragment shader supersede those provided in previous stages." if (!crossStage && (symbol.getQualifier().centroid != unitSymbol.getQualifier().centroid || symbol.getQualifier().smooth != unitSymbol.getQualifier().smooth || symbol.getQualifier().flat != unitSymbol.getQualifier().flat || symbol.getQualifier().isSample()!= unitSymbol.getQualifier().isSample() || symbol.getQualifier().isPatch() != unitSymbol.getQualifier().isPatch() || symbol.getQualifier().isNonPerspective() != unitSymbol.getQualifier().isNonPerspective())) { error(infoSink, "Interpolation and auxiliary storage qualifiers must match:"); writeTypeComparison = true; } // Memory... if (symbol.getQualifier().coherent != unitSymbol.getQualifier().coherent || symbol.getQualifier().devicecoherent != unitSymbol.getQualifier().devicecoherent || symbol.getQualifier().queuefamilycoherent != unitSymbol.getQualifier().queuefamilycoherent || symbol.getQualifier().workgroupcoherent != unitSymbol.getQualifier().workgroupcoherent || symbol.getQualifier().subgroupcoherent != unitSymbol.getQualifier().subgroupcoherent || symbol.getQualifier().shadercallcoherent!= unitSymbol.getQualifier().shadercallcoherent || symbol.getQualifier().nonprivate != unitSymbol.getQualifier().nonprivate || symbol.getQualifier().volatil != unitSymbol.getQualifier().volatil || symbol.getQualifier().restrict != unitSymbol.getQualifier().restrict || symbol.getQualifier().readonly != unitSymbol.getQualifier().readonly || symbol.getQualifier().writeonly != unitSymbol.getQualifier().writeonly) { error(infoSink, "Memory qualifiers must match:"); writeTypeComparison = true; } // Layouts... // TODO: 4.4 enhanced layouts: Generalize to include offset/align: current spec // requires separate user-supplied offset from actual computed offset, but // current implementation only has one offset. if (symbol.getQualifier().layoutMatrix != unitSymbol.getQualifier().layoutMatrix || symbol.getQualifier().layoutPacking != unitSymbol.getQualifier().layoutPacking || symbol.getQualifier().layoutLocation != unitSymbol.getQualifier().layoutLocation || symbol.getQualifier().layoutComponent != unitSymbol.getQualifier().layoutComponent || symbol.getQualifier().layoutIndex != unitSymbol.getQualifier().layoutIndex || symbol.getQualifier().layoutBinding != unitSymbol.getQualifier().layoutBinding || (symbol.getQualifier().hasBinding() && (symbol.getQualifier().layoutOffset != unitSymbol.getQualifier().layoutOffset))) { error(infoSink, "Layout qualification must match:"); writeTypeComparison = true; } // Initializers have to match, if both are present, and if we don't already know the types don't match if (! writeTypeComparison) { if (! symbol.getConstArray().empty() && ! unitSymbol.getConstArray().empty()) { if (symbol.getConstArray() != unitSymbol.getConstArray()) { error(infoSink, "Initializers must match:"); infoSink.info << " " << symbol.getName() << "\n"; } } } if (writeTypeComparison) { infoSink.info << " " << symbol.getName() << ": \"" << symbol.getType().getCompleteString() << "\" versus "; if (symbol.getName() != unitSymbol.getName()) infoSink.info << unitSymbol.getName() << ": "; infoSink.info << "\"" << unitSymbol.getType().getCompleteString() << "\"\n"; } #endif } void TIntermediate::sharedBlockCheck(TInfoSink& infoSink) { bool has_shared_block = false; bool has_shared_non_block = false; TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); for (size_t i = 0; i < linkObjects.size(); ++i) { const TType& type = linkObjects[i]->getAsTyped()->getType(); const TQualifier& qualifier = type.getQualifier(); if (qualifier.storage == glslang::EvqShared) { if (type.getBasicType() == glslang::EbtBlock) has_shared_block = true; else has_shared_non_block = true; } } if (has_shared_block && has_shared_non_block) error(infoSink, "cannot mix use of shared variables inside and outside blocks"); } // // Do final link-time error checking of a complete (merged) intermediate representation. // (Much error checking was done during merging). // // Also, lock in defaults of things not set, including array sizes. // void TIntermediate::finalCheck(TInfoSink& infoSink, bool keepUncalled) { if (getTreeRoot() == nullptr) return; if (numEntryPoints < 1) { if (getSource() == EShSourceGlsl) error(infoSink, "Missing entry point: Each stage requires one entry point"); else warn(infoSink, "Entry point not found"); } // recursion and missing body checking checkCallGraphCycles(infoSink); checkCallGraphBodies(infoSink, keepUncalled); // overlap/alias/missing I/O, etc. inOutLocationCheck(infoSink); #ifndef GLSLANG_WEB if (getNumPushConstants() > 1) error(infoSink, "Only one push_constant block is allowed per stage"); // invocations if (invocations == TQualifier::layoutNotSet) invocations = 1; if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipVertex")) error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_ClipVertex")) error(infoSink, "Can only use one of gl_CullDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); if (userOutputUsed() && (inIoAccessed("gl_FragColor") || inIoAccessed("gl_FragData"))) error(infoSink, "Cannot use gl_FragColor or gl_FragData when using user-defined outputs"); if (inIoAccessed("gl_FragColor") && inIoAccessed("gl_FragData")) error(infoSink, "Cannot use both gl_FragColor and gl_FragData"); for (size_t b = 0; b < xfbBuffers.size(); ++b) { if (xfbBuffers[b].contains64BitType) RoundToPow2(xfbBuffers[b].implicitStride, 8); else if (xfbBuffers[b].contains32BitType) RoundToPow2(xfbBuffers[b].implicitStride, 4); else if (xfbBuffers[b].contains16BitType) RoundToPow2(xfbBuffers[b].implicitStride, 2); // "It is a compile-time or link-time error to have // any xfb_offset that overflows xfb_stride, whether stated on declarations before or after the xfb_stride, or // in different compilation units. While xfb_stride can be declared multiple times for the same buffer, it is a // compile-time or link-time error to have different values specified for the stride for the same buffer." if (xfbBuffers[b].stride != TQualifier::layoutXfbStrideEnd && xfbBuffers[b].implicitStride > xfbBuffers[b].stride) { error(infoSink, "xfb_stride is too small to hold all buffer entries:"); infoSink.info.prefix(EPrefixError); infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << ", minimum stride needed: " << xfbBuffers[b].implicitStride << "\n"; } if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) xfbBuffers[b].stride = xfbBuffers[b].implicitStride; // "If the buffer is capturing any // outputs with double-precision or 64-bit integer components, the stride must be a multiple of 8, otherwise it must be a // multiple of 4, or a compile-time or link-time error results." if (xfbBuffers[b].contains64BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 8)) { error(infoSink, "xfb_stride must be multiple of 8 for buffer holding a double or 64-bit integer:"); infoSink.info.prefix(EPrefixError); infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; } else if (xfbBuffers[b].contains32BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 4)) { error(infoSink, "xfb_stride must be multiple of 4:"); infoSink.info.prefix(EPrefixError); infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; } // "If the buffer is capturing any // outputs with half-precision or 16-bit integer components, the stride must be a multiple of 2" else if (xfbBuffers[b].contains16BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 2)) { error(infoSink, "xfb_stride must be multiple of 2 for buffer holding a half float or 16-bit integer:"); infoSink.info.prefix(EPrefixError); infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; } // "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the // implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents." if (xfbBuffers[b].stride > (unsigned int)(4 * resources->maxTransformFeedbackInterleavedComponents)) { error(infoSink, "xfb_stride is too large:"); infoSink.info.prefix(EPrefixError); infoSink.info << " xfb_buffer " << (unsigned int)b << ", components (1/4 stride) needed are " << xfbBuffers[b].stride/4 << ", gl_MaxTransformFeedbackInterleavedComponents is " << resources->maxTransformFeedbackInterleavedComponents << "\n"; } } switch (language) { case EShLangVertex: break; case EShLangTessControl: if (vertices == TQualifier::layoutNotSet) error(infoSink, "At least one shader must specify an output layout(vertices=...)"); break; case EShLangTessEvaluation: if (getSource() == EShSourceGlsl) { if (inputPrimitive == ElgNone) error(infoSink, "At least one shader must specify an input layout primitive"); if (vertexSpacing == EvsNone) vertexSpacing = EvsEqual; if (vertexOrder == EvoNone) vertexOrder = EvoCcw; } break; case EShLangGeometry: if (inputPrimitive == ElgNone) error(infoSink, "At least one shader must specify an input layout primitive"); if (outputPrimitive == ElgNone) error(infoSink, "At least one shader must specify an output layout primitive"); if (vertices == TQualifier::layoutNotSet) error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); break; case EShLangFragment: // for GL_ARB_post_depth_coverage, EarlyFragmentTest is set automatically in // ParseHelper.cpp. So if we reach here, this must be GL_EXT_post_depth_coverage // requiring explicit early_fragment_tests if (getPostDepthCoverage() && !getEarlyFragmentTests()) error(infoSink, "post_depth_coverage requires early_fragment_tests"); break; case EShLangCompute: sharedBlockCheck(infoSink); break; case EShLangRayGen: case EShLangIntersect: case EShLangAnyHit: case EShLangClosestHit: case EShLangMiss: case EShLangCallable: if (numShaderRecordBlocks > 1) error(infoSink, "Only one shaderRecordNV buffer block is allowed per stage"); break; case EShLangMeshNV: // NV_mesh_shader doesn't allow use of both single-view and per-view builtins. if (inIoAccessed("gl_Position") && inIoAccessed("gl_PositionPerViewNV")) error(infoSink, "Can only use one of gl_Position or gl_PositionPerViewNV"); if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipDistancePerViewNV")) error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipDistancePerViewNV"); if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_CullDistancePerViewNV")) error(infoSink, "Can only use one of gl_CullDistance or gl_CullDistancePerViewNV"); if (inIoAccessed("gl_Layer") && inIoAccessed("gl_LayerPerViewNV")) error(infoSink, "Can only use one of gl_Layer or gl_LayerPerViewNV"); if (inIoAccessed("gl_ViewportMask") && inIoAccessed("gl_ViewportMaskPerViewNV")) error(infoSink, "Can only use one of gl_ViewportMask or gl_ViewportMaskPerViewNV"); if (outputPrimitive == ElgNone) error(infoSink, "At least one shader must specify an output layout primitive"); if (vertices == TQualifier::layoutNotSet) error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); if (primitives == TQualifier::layoutNotSet) error(infoSink, "At least one shader must specify a layout(max_primitives = value)"); // fall through case EShLangTaskNV: if (numTaskNVBlocks > 1) error(infoSink, "Only one taskNV interface block is allowed per shader"); sharedBlockCheck(infoSink); break; default: error(infoSink, "Unknown Stage."); break; } // Process the tree for any node-specific work. class TFinalLinkTraverser : public TIntermTraverser { public: TFinalLinkTraverser() { } virtual ~TFinalLinkTraverser() { } virtual void visitSymbol(TIntermSymbol* symbol) { // Implicitly size arrays. // If an unsized array is left as unsized, it effectively // becomes run-time sized. symbol->getWritableType().adoptImplicitArraySizes(false); } } finalLinkTraverser; treeRoot->traverse(&finalLinkTraverser); #endif } // // See if the call graph contains any static recursion, which is disallowed // by the specification. // void TIntermediate::checkCallGraphCycles(TInfoSink& infoSink) { // Clear fields we'll use for this. for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { call->visited = false; call->currentPath = false; call->errorGiven = false; } // // Loop, looking for a new connected subgraph. One subgraph is handled per loop iteration. // TCall* newRoot; do { // See if we have unvisited parts of the graph. newRoot = 0; for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { if (! call->visited) { newRoot = &(*call); break; } } // If not, we are done. if (! newRoot) break; // Otherwise, we found a new subgraph, process it: // See what all can be reached by this new root, and if any of // that is recursive. This is done by depth-first traversals, seeing // if a new call is found that was already in the currentPath (a back edge), // thereby detecting recursion. std::list stack; newRoot->currentPath = true; // currentPath will be true iff it is on the stack stack.push_back(newRoot); while (! stack.empty()) { // get a caller TCall* call = stack.back(); // Add to the stack just one callee. // This algorithm always terminates, because only !visited and !currentPath causes a push // and all pushes change currentPath to true, and all pops change visited to true. TGraph::iterator child = callGraph.begin(); for (; child != callGraph.end(); ++child) { // If we already visited this node, its whole subgraph has already been processed, so skip it. if (child->visited) continue; if (call->callee == child->caller) { if (child->currentPath) { // Then, we found a back edge if (! child->errorGiven) { error(infoSink, "Recursion detected:"); infoSink.info << " " << call->callee << " calling " << child->callee << "\n"; child->errorGiven = true; recursive = true; } } else { child->currentPath = true; stack.push_back(&(*child)); break; } } } if (child == callGraph.end()) { // no more callees, we bottomed out, never look at this node again stack.back()->currentPath = false; stack.back()->visited = true; stack.pop_back(); } } // end while, meaning nothing left to process in this subtree } while (newRoot); // redundant loop check; should always exit via the 'break' above } // // See which functions are reachable from the entry point and which have bodies. // Reachable ones with missing bodies are errors. // Unreachable bodies are dead code. // void TIntermediate::checkCallGraphBodies(TInfoSink& infoSink, bool keepUncalled) { // Clear fields we'll use for this. for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { call->visited = false; call->calleeBodyPosition = -1; } // The top level of the AST includes function definitions (bodies). // Compare these to function calls in the call graph. // We'll end up knowing which have bodies, and if so, // how to map the call-graph node to the location in the AST. TIntermSequence &functionSequence = getTreeRoot()->getAsAggregate()->getSequence(); std::vector reachable(functionSequence.size(), true); // so that non-functions are reachable for (int f = 0; f < (int)functionSequence.size(); ++f) { glslang::TIntermAggregate* node = functionSequence[f]->getAsAggregate(); if (node && (node->getOp() == glslang::EOpFunction)) { if (node->getName().compare(getEntryPointMangledName().c_str()) != 0) reachable[f] = false; // so that function bodies are unreachable, until proven otherwise for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { if (call->callee == node->getName()) call->calleeBodyPosition = f; } } } // Start call-graph traversal by visiting the entry point nodes. for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { if (call->caller.compare(getEntryPointMangledName().c_str()) == 0) call->visited = true; } // Propagate 'visited' through the call-graph to every part of the graph it // can reach (seeded with the entry-point setting above). bool changed; do { changed = false; for (auto call1 = callGraph.begin(); call1 != callGraph.end(); ++call1) { if (call1->visited) { for (TGraph::iterator call2 = callGraph.begin(); call2 != callGraph.end(); ++call2) { if (! call2->visited) { if (call1->callee == call2->caller) { changed = true; call2->visited = true; } } } } } } while (changed); // Any call-graph node set to visited but without a callee body is an error. for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { if (call->visited) { if (call->calleeBodyPosition == -1) { error(infoSink, "No function definition (body) found: "); infoSink.info << " " << call->callee << "\n"; } else reachable[call->calleeBodyPosition] = true; } } // Bodies in the AST not reached by the call graph are dead; // clear them out, since they can't be reached and also can't // be translated further due to possibility of being ill defined. if (! keepUncalled) { for (int f = 0; f < (int)functionSequence.size(); ++f) { if (! reachable[f]) functionSequence[f] = nullptr; } functionSequence.erase(std::remove(functionSequence.begin(), functionSequence.end(), nullptr), functionSequence.end()); } } // // Satisfy rules for location qualifiers on inputs and outputs // void TIntermediate::inOutLocationCheck(TInfoSink& infoSink) { // ES 3.0 requires all outputs to have location qualifiers if there is more than one output bool fragOutWithNoLocation = false; int numFragOut = 0; // TODO: linker functionality: location collision checking TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); for (size_t i = 0; i < linkObjects.size(); ++i) { const TType& type = linkObjects[i]->getAsTyped()->getType(); const TQualifier& qualifier = type.getQualifier(); if (language == EShLangFragment) { if (qualifier.storage == EvqVaryingOut && qualifier.builtIn == EbvNone) { ++numFragOut; if (!qualifier.hasAnyLocation()) fragOutWithNoLocation = true; } } } if (isEsProfile()) { if (numFragOut > 1 && fragOutWithNoLocation) error(infoSink, "when more than one fragment shader output, all must have location qualifiers"); } } TIntermAggregate* TIntermediate::findLinkerObjects() const { // Get the top-level globals TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); // Get the last member of the sequences, expected to be the linker-object lists assert(globals.back()->getAsAggregate()->getOp() == EOpLinkerObjects); return globals.back()->getAsAggregate(); } // See if a variable was both a user-declared output and used. // Note: the spec discusses writing to one, but this looks at read or write, which // is more useful, and perhaps the spec should be changed to reflect that. bool TIntermediate::userOutputUsed() const { const TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); bool found = false; for (size_t i = 0; i < linkerObjects.size(); ++i) { const TIntermSymbol& symbolNode = *linkerObjects[i]->getAsSymbolNode(); if (symbolNode.getQualifier().storage == EvqVaryingOut && symbolNode.getName().compare(0, 3, "gl_") != 0 && inIoAccessed(symbolNode.getName())) { found = true; break; } } return found; } // Accumulate locations used for inputs, outputs, and uniforms, payload and callable data // and check for collisions as the accumulation is done. // // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. // // typeCollision is set to true if there is no direct collision, but the types in the same location // are different. // int TIntermediate::addUsedLocation(const TQualifier& qualifier, const TType& type, bool& typeCollision) { typeCollision = false; int set; int setRT; if (qualifier.isPipeInput()) set = 0; else if (qualifier.isPipeOutput()) set = 1; else if (qualifier.storage == EvqUniform) set = 2; else if (qualifier.storage == EvqBuffer) set = 3; else if (qualifier.isAnyPayload()) setRT = 0; else if (qualifier.isAnyCallable()) setRT = 1; else return -1; int size; if (qualifier.isAnyPayload() || qualifier.isAnyCallable()) { size = 1; } else if (qualifier.isUniformOrBuffer() || qualifier.isTaskMemory()) { if (type.isSizedArray()) size = type.getCumulativeArraySize(); else size = 1; } else { // Strip off the outer array dimension for those having an extra one. if (type.isArray() && qualifier.isArrayedIo(language)) { TType elementType(type, 0); size = computeTypeLocationSize(elementType, language); } else size = computeTypeLocationSize(type, language); } // Locations, and components within locations. // // Almost always, dealing with components means a single location is involved. // The exception is a dvec3. From the spec: // // "A dvec3 will consume all four components of the first location and components 0 and 1 of // the second location. This leaves components 2 and 3 available for other component-qualified // declarations." // // That means, without ever mentioning a component, a component range // for a different location gets specified, if it's not a vertex shader input. (!) // (A vertex shader input will show using only one location, even for a dvec3/4.) // // So, for the case of dvec3, we need two independent ioRanges. // // For raytracing IO (payloads and callabledata) each declaration occupies a single // slot irrespective of type. int collision = -1; // no collision #ifndef GLSLANG_WEB if (qualifier.isAnyPayload() || qualifier.isAnyCallable()) { TRange range(qualifier.layoutLocation, qualifier.layoutLocation); collision = checkLocationRT(setRT, qualifier.layoutLocation); if (collision < 0) usedIoRT[setRT].push_back(range); } else if (size == 2 && type.getBasicType() == EbtDouble && type.getVectorSize() == 3 && (qualifier.isPipeInput() || qualifier.isPipeOutput())) { // Dealing with dvec3 in/out split across two locations. // Need two io-ranges. // The case where the dvec3 doesn't start at component 0 was previously caught as overflow. // First range: TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation); TRange componentRange(0, 3); TIoRange range(locationRange, componentRange, type.getBasicType(), 0); // check for collisions collision = checkLocationRange(set, range, type, typeCollision); if (collision < 0) { usedIo[set].push_back(range); // Second range: TRange locationRange2(qualifier.layoutLocation + 1, qualifier.layoutLocation + 1); TRange componentRange2(0, 1); TIoRange range2(locationRange2, componentRange2, type.getBasicType(), 0); // check for collisions collision = checkLocationRange(set, range2, type, typeCollision); if (collision < 0) usedIo[set].push_back(range2); } } else #endif { // Not a dvec3 in/out split across two locations, generic path. // Need a single IO-range block. TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation + size - 1); TRange componentRange(0, 3); if (qualifier.hasComponent() || type.getVectorSize() > 0) { int consumedComponents = type.getVectorSize() * (type.getBasicType() == EbtDouble ? 2 : 1); if (qualifier.hasComponent()) componentRange.start = qualifier.layoutComponent; componentRange.last = componentRange.start + consumedComponents - 1; } // combine location and component ranges TIoRange range(locationRange, componentRange, type.getBasicType(), qualifier.hasIndex() ? qualifier.getIndex() : 0); // check for collisions, except for vertex inputs on desktop targeting OpenGL if (! (!isEsProfile() && language == EShLangVertex && qualifier.isPipeInput()) || spvVersion.vulkan > 0) collision = checkLocationRange(set, range, type, typeCollision); if (collision < 0) usedIo[set].push_back(range); } return collision; } // Compare a new (the passed in) 'range' against the existing set, and see // if there are any collisions. // // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. // int TIntermediate::checkLocationRange(int set, const TIoRange& range, const TType& type, bool& typeCollision) { for (size_t r = 0; r < usedIo[set].size(); ++r) { if (range.overlap(usedIo[set][r])) { // there is a collision; pick one return std::max(range.location.start, usedIo[set][r].location.start); } else if (range.location.overlap(usedIo[set][r].location) && type.getBasicType() != usedIo[set][r].basicType) { // aliased-type mismatch typeCollision = true; return std::max(range.location.start, usedIo[set][r].location.start); } } return -1; // no collision } int TIntermediate::checkLocationRT(int set, int location) { TRange range(location, location); for (size_t r = 0; r < usedIoRT[set].size(); ++r) { if (range.overlap(usedIoRT[set][r])) { return range.start; } } return -1; // no collision } // Accumulate bindings and offsets, and check for collisions // as the accumulation is done. // // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. // int TIntermediate::addUsedOffsets(int binding, int offset, int numOffsets) { TRange bindingRange(binding, binding); TRange offsetRange(offset, offset + numOffsets - 1); TOffsetRange range(bindingRange, offsetRange); // check for collisions, except for vertex inputs on desktop for (size_t r = 0; r < usedAtomics.size(); ++r) { if (range.overlap(usedAtomics[r])) { // there is a collision; pick one return std::max(offset, usedAtomics[r].offset.start); } } usedAtomics.push_back(range); return -1; // no collision } // Accumulate used constant_id values. // // Return false is one was already used. bool TIntermediate::addUsedConstantId(int id) { if (usedConstantId.find(id) != usedConstantId.end()) return false; usedConstantId.insert(id); return true; } // Recursively figure out how many locations are used up by an input or output type. // Return the size of type, as measured by "locations". int TIntermediate::computeTypeLocationSize(const TType& type, EShLanguage stage) { // "If the declared input is an array of size n and each element takes m locations, it will be assigned m * n // consecutive locations..." if (type.isArray()) { // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness // TODO: are there valid cases of having an unsized array with a location? If so, running this code too early. TType elementType(type, 0); if (type.isSizedArray() && !type.getQualifier().isPerView()) return type.getOuterArraySize() * computeTypeLocationSize(elementType, stage); else { #ifndef GLSLANG_WEB // unset perViewNV attributes for arrayed per-view outputs: "perviewNV vec4 v[MAX_VIEWS][3];" elementType.getQualifier().perViewNV = false; #endif return computeTypeLocationSize(elementType, stage); } } // "The locations consumed by block and structure members are determined by applying the rules above // recursively..." if (type.isStruct()) { int size = 0; for (int member = 0; member < (int)type.getStruct()->size(); ++member) { TType memberType(type, member); size += computeTypeLocationSize(memberType, stage); } return size; } // ES: "If a shader input is any scalar or vector type, it will consume a single location." // Desktop: "If a vertex shader input is any scalar or vector type, it will consume a single location. If a non-vertex // shader input is a scalar or vector type other than dvec3 or dvec4, it will consume a single location, while // types dvec3 or dvec4 will consume two consecutive locations. Inputs of type double and dvec2 will // consume only a single location, in all stages." if (type.isScalar()) return 1; if (type.isVector()) { if (stage == EShLangVertex && type.getQualifier().isPipeInput()) return 1; if (type.getBasicType() == EbtDouble && type.getVectorSize() > 2) return 2; else return 1; } // "If the declared input is an n x m single- or double-precision matrix, ... // The number of locations assigned for each matrix will be the same as // for an n-element array of m-component vectors..." if (type.isMatrix()) { TType columnType(type, 0); return type.getMatrixCols() * computeTypeLocationSize(columnType, stage); } assert(0); return 1; } // Same as computeTypeLocationSize but for uniforms int TIntermediate::computeTypeUniformLocationSize(const TType& type) { // "Individual elements of a uniform array are assigned // consecutive locations with the first element taking location // location." if (type.isArray()) { // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness TType elementType(type, 0); if (type.isSizedArray()) { return type.getOuterArraySize() * computeTypeUniformLocationSize(elementType); } else { // TODO: are there valid cases of having an implicitly-sized array with a location? If so, running this code too early. return computeTypeUniformLocationSize(elementType); } } // "Each subsequent inner-most member or element gets incremental // locations for the entire structure or array." if (type.isStruct()) { int size = 0; for (int member = 0; member < (int)type.getStruct()->size(); ++member) { TType memberType(type, member); size += computeTypeUniformLocationSize(memberType); } return size; } return 1; } #ifndef GLSLANG_WEB // Accumulate xfb buffer ranges and check for collisions as the accumulation is done. // // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. // int TIntermediate::addXfbBufferOffset(const TType& type) { const TQualifier& qualifier = type.getQualifier(); assert(qualifier.hasXfbOffset() && qualifier.hasXfbBuffer()); TXfbBuffer& buffer = xfbBuffers[qualifier.layoutXfbBuffer]; // compute the range unsigned int size = computeTypeXfbSize(type, buffer.contains64BitType, buffer.contains32BitType, buffer.contains16BitType); buffer.implicitStride = std::max(buffer.implicitStride, qualifier.layoutXfbOffset + size); TRange range(qualifier.layoutXfbOffset, qualifier.layoutXfbOffset + size - 1); // check for collisions for (size_t r = 0; r < buffer.ranges.size(); ++r) { if (range.overlap(buffer.ranges[r])) { // there is a collision; pick an example to return return std::max(range.start, buffer.ranges[r].start); } } buffer.ranges.push_back(range); return -1; // no collision } // Recursively figure out how many bytes of xfb buffer are used by the given type. // Return the size of type, in bytes. // Sets contains64BitType to true if the type contains a 64-bit data type. // Sets contains32BitType to true if the type contains a 32-bit data type. // Sets contains16BitType to true if the type contains a 16-bit data type. // N.B. Caller must set contains64BitType, contains32BitType, and contains16BitType to false before calling. unsigned int TIntermediate::computeTypeXfbSize(const TType& type, bool& contains64BitType, bool& contains32BitType, bool& contains16BitType) const { // "...if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, // and the space taken in the buffer will be a multiple of 8. // ...within the qualified entity, subsequent components are each // assigned, in order, to the next available offset aligned to a multiple of // that component's size. Aggregate types are flattened down to the component // level to get this sequence of components." if (type.isSizedArray()) { // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness // Unsized array use to xfb should be a compile error. TType elementType(type, 0); return type.getOuterArraySize() * computeTypeXfbSize(elementType, contains64BitType, contains16BitType, contains16BitType); } if (type.isStruct()) { unsigned int size = 0; bool structContains64BitType = false; bool structContains32BitType = false; bool structContains16BitType = false; for (int member = 0; member < (int)type.getStruct()->size(); ++member) { TType memberType(type, member); // "... if applied to // an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, // and the space taken in the buffer will be a multiple of 8." bool memberContains64BitType = false; bool memberContains32BitType = false; bool memberContains16BitType = false; int memberSize = computeTypeXfbSize(memberType, memberContains64BitType, memberContains32BitType, memberContains16BitType); if (memberContains64BitType) { structContains64BitType = true; RoundToPow2(size, 8); } else if (memberContains32BitType) { structContains32BitType = true; RoundToPow2(size, 4); } else if (memberContains16BitType) { structContains16BitType = true; RoundToPow2(size, 2); } size += memberSize; } if (structContains64BitType) { contains64BitType = true; RoundToPow2(size, 8); } else if (structContains32BitType) { contains32BitType = true; RoundToPow2(size, 4); } else if (structContains16BitType) { contains16BitType = true; RoundToPow2(size, 2); } return size; } int numComponents; if (type.isScalar()) numComponents = 1; else if (type.isVector()) numComponents = type.getVectorSize(); else if (type.isMatrix()) numComponents = type.getMatrixCols() * type.getMatrixRows(); else { assert(0); numComponents = 1; } if (type.getBasicType() == EbtDouble || type.getBasicType() == EbtInt64 || type.getBasicType() == EbtUint64) { contains64BitType = true; return 8 * numComponents; } else if (type.getBasicType() == EbtFloat16 || type.getBasicType() == EbtInt16 || type.getBasicType() == EbtUint16) { contains16BitType = true; return 2 * numComponents; } else if (type.getBasicType() == EbtInt8 || type.getBasicType() == EbtUint8) return numComponents; else { contains32BitType = true; return 4 * numComponents; } } #endif const int baseAlignmentVec4Std140 = 16; // Return the size and alignment of a component of the given type. // The size is returned in the 'size' parameter // Return value is the alignment.. int TIntermediate::getBaseAlignmentScalar(const TType& type, int& size) { #ifdef GLSLANG_WEB size = 4; return 4; #endif switch (type.getBasicType()) { case EbtInt64: case EbtUint64: case EbtDouble: size = 8; return 8; case EbtFloat16: size = 2; return 2; case EbtInt8: case EbtUint8: size = 1; return 1; case EbtInt16: case EbtUint16: size = 2; return 2; case EbtReference: size = 8; return 8; default: size = 4; return 4; } } // Implement base-alignment and size rules from section 7.6.2.2 Standard Uniform Block Layout // Operates recursively. // // If std140 is true, it does the rounding up to vec4 size required by std140, // otherwise it does not, yielding std430 rules. // // The size is returned in the 'size' parameter // // The stride is only non-0 for arrays or matrices, and is the stride of the // top-level object nested within the type. E.g., for an array of matrices, // it is the distances needed between matrices, despite the rules saying the // stride comes from the flattening down to vectors. // // Return value is the alignment of the type. int TIntermediate::getBaseAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) { int alignment; bool std140 = layoutPacking == glslang::ElpStd140; // When using the std140 storage layout, structures will be laid out in buffer // storage with its members stored in monotonically increasing order based on their // location in the declaration. A structure and each structure member have a base // offset and a base alignment, from which an aligned offset is computed by rounding // the base offset up to a multiple of the base alignment. The base offset of the first // member of a structure is taken from the aligned offset of the structure itself. The // base offset of all other structure members is derived by taking the offset of the // last basic machine unit consumed by the previous member and adding one. Each // structure member is stored in memory at its aligned offset. The members of a top- // level uniform block are laid out in buffer storage by treating the uniform block as // a structure with a base offset of zero. // // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. // // 2. If the member is a two- or four-component vector with components consuming N basic // machine units, the base alignment is 2N or 4N, respectively. // // 3. If the member is a three-component vector with components consuming N // basic machine units, the base alignment is 4N. // // 4. If the member is an array of scalars or vectors, the base alignment and array // stride are set to match the base alignment of a single array element, according // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. The // array may have padding at the end; the base offset of the member following // the array is rounded up to the next multiple of the base alignment. // // 5. If the member is a column-major matrix with C columns and R rows, the // matrix is stored identically to an array of C column vectors with R // components each, according to rule (4). // // 6. If the member is an array of S column-major matrices with C columns and // R rows, the matrix is stored identically to a row of S X C column vectors // with R components each, according to rule (4). // // 7. If the member is a row-major matrix with C columns and R rows, the matrix // is stored identically to an array of R row vectors with C components each, // according to rule (4). // // 8. If the member is an array of S row-major matrices with C columns and R // rows, the matrix is stored identically to a row of S X R row vectors with C // components each, according to rule (4). // // 9. If the member is a structure, the base alignment of the structure is N , where // N is the largest base alignment value of any of its members, and rounded // up to the base alignment of a vec4. The individual members of this substructure // are then assigned offsets by applying this set of rules recursively, // where the base offset of the first member of the sub-structure is equal to the // aligned offset of the structure. The structure may have padding at the end; // the base offset of the member following the sub-structure is rounded up to // the next multiple of the base alignment of the structure. // // 10. If the member is an array of S structures, the S elements of the array are laid // out in order, according to rule (9). // // Assuming, for rule 10: The stride is the same as the size of an element. stride = 0; int dummyStride; // rules 4, 6, 8, and 10 if (type.isArray()) { // TODO: perf: this might be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness TType derefType(type, 0); alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); if (std140) alignment = std::max(baseAlignmentVec4Std140, alignment); RoundToPow2(size, alignment); stride = size; // uses full matrix size for stride of an array of matrices (not quite what rule 6/8, but what's expected) // uses the assumption for rule 10 in the comment above // use one element to represent the last member of SSBO which is unsized array int arraySize = (type.isUnsizedArray() && (type.getOuterArraySize() == 0)) ? 1 : type.getOuterArraySize(); size = stride * arraySize; return alignment; } // rule 9 if (type.getBasicType() == EbtStruct) { const TTypeList& memberList = *type.getStruct(); size = 0; int maxAlignment = std140 ? baseAlignmentVec4Std140 : 0; for (size_t m = 0; m < memberList.size(); ++m) { int memberSize; // modify just the children's view of matrix layout, if there is one for this member TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; int memberAlignment = getBaseAlignment(*memberList[m].type, memberSize, dummyStride, layoutPacking, (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); maxAlignment = std::max(maxAlignment, memberAlignment); RoundToPow2(size, memberAlignment); size += memberSize; } // The structure may have padding at the end; the base offset of // the member following the sub-structure is rounded up to the next // multiple of the base alignment of the structure. RoundToPow2(size, maxAlignment); return maxAlignment; } // rule 1 if (type.isScalar()) return getBaseAlignmentScalar(type, size); // rules 2 and 3 if (type.isVector()) { int scalarAlign = getBaseAlignmentScalar(type, size); switch (type.getVectorSize()) { case 1: // HLSL has this, GLSL does not return scalarAlign; case 2: size *= 2; return 2 * scalarAlign; default: size *= type.getVectorSize(); return 4 * scalarAlign; } } // rules 5 and 7 if (type.isMatrix()) { // rule 5: deref to row, not to column, meaning the size of vector is num columns instead of num rows TType derefType(type, 0, rowMajor); alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); if (std140) alignment = std::max(baseAlignmentVec4Std140, alignment); RoundToPow2(size, alignment); stride = size; // use intra-matrix stride for stride of a just a matrix if (rowMajor) size = stride * type.getMatrixRows(); else size = stride * type.getMatrixCols(); return alignment; } assert(0); // all cases should be covered above size = baseAlignmentVec4Std140; return baseAlignmentVec4Std140; } // To aid the basic HLSL rule about crossing vec4 boundaries. bool TIntermediate::improperStraddle(const TType& type, int size, int offset) { if (! type.isVector() || type.isArray()) return false; return size <= 16 ? offset / 16 != (offset + size - 1) / 16 : offset % 16 != 0; } int TIntermediate::getScalarAlignment(const TType& type, int& size, int& stride, bool rowMajor) { int alignment; stride = 0; int dummyStride; if (type.isArray()) { TType derefType(type, 0); alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); stride = size; RoundToPow2(stride, alignment); size = stride * (type.getOuterArraySize() - 1) + size; return alignment; } if (type.getBasicType() == EbtStruct) { const TTypeList& memberList = *type.getStruct(); size = 0; int maxAlignment = 0; for (size_t m = 0; m < memberList.size(); ++m) { int memberSize; // modify just the children's view of matrix layout, if there is one for this member TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; int memberAlignment = getScalarAlignment(*memberList[m].type, memberSize, dummyStride, (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); maxAlignment = std::max(maxAlignment, memberAlignment); RoundToPow2(size, memberAlignment); size += memberSize; } return maxAlignment; } if (type.isScalar()) return getBaseAlignmentScalar(type, size); if (type.isVector()) { int scalarAlign = getBaseAlignmentScalar(type, size); size *= type.getVectorSize(); return scalarAlign; } if (type.isMatrix()) { TType derefType(type, 0, rowMajor); alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); stride = size; // use intra-matrix stride for stride of a just a matrix if (rowMajor) size = stride * type.getMatrixRows(); else size = stride * type.getMatrixCols(); return alignment; } assert(0); // all cases should be covered above size = 1; return 1; } int TIntermediate::getMemberAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) { if (layoutPacking == glslang::ElpScalar) { return getScalarAlignment(type, size, stride, rowMajor); } else { return getBaseAlignment(type, size, stride, layoutPacking, rowMajor); } } // shared calculation by getOffset and getOffsets void TIntermediate::updateOffset(const TType& parentType, const TType& memberType, int& offset, int& memberSize) { int dummyStride; // modify just the children's view of matrix layout, if there is one for this member TLayoutMatrix subMatrixLayout = memberType.getQualifier().layoutMatrix; int memberAlignment = getMemberAlignment(memberType, memberSize, dummyStride, parentType.getQualifier().layoutPacking, subMatrixLayout != ElmNone ? subMatrixLayout == ElmRowMajor : parentType.getQualifier().layoutMatrix == ElmRowMajor); RoundToPow2(offset, memberAlignment); } // Lookup or calculate the offset of a block member, using the recursively // defined block offset rules. int TIntermediate::getOffset(const TType& type, int index) { const TTypeList& memberList = *type.getStruct(); // Don't calculate offset if one is present, it could be user supplied // and different than what would be calculated. That is, this is faster, // but not just an optimization. if (memberList[index].type->getQualifier().hasOffset()) return memberList[index].type->getQualifier().layoutOffset; int memberSize = 0; int offset = 0; for (int m = 0; m <= index; ++m) { updateOffset(type, *memberList[m].type, offset, memberSize); if (m < index) offset += memberSize; } return offset; } // Calculate the block data size. // Block arrayness is not taken into account, each element is backed by a separate buffer. int TIntermediate::getBlockSize(const TType& blockType) { const TTypeList& memberList = *blockType.getStruct(); int lastIndex = (int)memberList.size() - 1; int lastOffset = getOffset(blockType, lastIndex); int lastMemberSize; int dummyStride; getMemberAlignment(*memberList[lastIndex].type, lastMemberSize, dummyStride, blockType.getQualifier().layoutPacking, blockType.getQualifier().layoutMatrix == ElmRowMajor); return lastOffset + lastMemberSize; } int TIntermediate::computeBufferReferenceTypeSize(const TType& type) { assert(type.isReference()); int size = getBlockSize(*type.getReferentType()); int align = type.getBufferReferenceAlignment(); if (align) { size = (size + align - 1) & ~(align-1); } return size; } #ifndef GLSLANG_WEB bool TIntermediate::isIoResizeArray(const TType& type, EShLanguage language) { return type.isArray() && ((language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn) || (language == EShLangTessControl && type.getQualifier().storage == EvqVaryingOut && ! type.getQualifier().patch) || (language == EShLangFragment && type.getQualifier().storage == EvqVaryingIn && type.getQualifier().pervertexNV) || (language == EShLangMeshNV && type.getQualifier().storage == EvqVaryingOut && !type.getQualifier().perTaskNV)); } #endif // not GLSLANG_WEB } // end namespace glslang