// // Copyright (C) 2016-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. // #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) #include "../Include/Common.h" #include "../Include/InfoSink.h" #include "../Include/Types.h" #include "gl_types.h" #include "iomapper.h" #include "SymbolTable.h" // // Map IO bindings. // // High-level algorithm for one stage: // // 1. Traverse all code (live+dead) to find the explicitly provided bindings. // // 2. Traverse (just) the live code to determine which non-provided bindings // require auto-numbering. We do not auto-number dead ones. // // 3. Traverse all the code to apply the bindings: // a. explicitly given bindings are offset according to their type // b. implicit live bindings are auto-numbered into the holes, using // any open binding slot. // c. implicit dead bindings are left un-bound. // namespace glslang { class TVarGatherTraverser : public TLiveTraverser { public: TVarGatherTraverser(const TIntermediate& i, bool traverseDeadCode, TVarLiveMap& inList, TVarLiveMap& outList, TVarLiveMap& uniformList) : TLiveTraverser(i, traverseDeadCode, true, true, false) , inputList(inList) , outputList(outList) , uniformList(uniformList) { } virtual void visitSymbol(TIntermSymbol* base) { TVarLiveMap* target = nullptr; if (base->getQualifier().storage == EvqVaryingIn) target = &inputList; else if (base->getQualifier().storage == EvqVaryingOut) target = &outputList; else if (base->getQualifier().isUniformOrBuffer() && !base->getQualifier().isPushConstant()) target = &uniformList; // If a global is being visited, then we should also traverse it incase it's evaluation // ends up visiting inputs we want to tag as live else if (base->getQualifier().storage == EvqGlobal) addGlobalReference(base->getAccessName()); if (target) { TVarEntryInfo ent = {base->getId(), base, ! traverseAll}; ent.stage = intermediate.getStage(); TVarLiveMap::iterator at = target->find( ent.symbol->getAccessName()); // std::lower_bound(target->begin(), target->end(), ent, TVarEntryInfo::TOrderById()); if (at != target->end() && at->second.id == ent.id) at->second.live = at->second.live || ! traverseAll; // update live state else (*target)[ent.symbol->getAccessName()] = ent; } } private: TVarLiveMap& inputList; TVarLiveMap& outputList; TVarLiveMap& uniformList; }; class TVarSetTraverser : public TLiveTraverser { public: TVarSetTraverser(const TIntermediate& i, const TVarLiveMap& inList, const TVarLiveMap& outList, const TVarLiveMap& uniformList) : TLiveTraverser(i, true, true, true, false) , inputList(inList) , outputList(outList) , uniformList(uniformList) { } virtual void visitSymbol(TIntermSymbol* base) { const TVarLiveMap* source; if (base->getQualifier().storage == EvqVaryingIn) source = &inputList; else if (base->getQualifier().storage == EvqVaryingOut) source = &outputList; else if (base->getQualifier().isUniformOrBuffer()) source = &uniformList; else return; TVarEntryInfo ent = { base->getId() }; // Fix a defect, when block has no instance name, we need to find its block name TVarLiveMap::const_iterator at = source->find(base->getAccessName()); if (at == source->end()) return; if (at->second.id != ent.id) return; if (at->second.newBinding != -1) base->getWritableType().getQualifier().layoutBinding = at->second.newBinding; if (at->second.newSet != -1) base->getWritableType().getQualifier().layoutSet = at->second.newSet; if (at->second.newLocation != -1) base->getWritableType().getQualifier().layoutLocation = at->second.newLocation; if (at->second.newComponent != -1) base->getWritableType().getQualifier().layoutComponent = at->second.newComponent; if (at->second.newIndex != -1) base->getWritableType().getQualifier().layoutIndex = at->second.newIndex; } private: const TVarLiveMap& inputList; const TVarLiveMap& outputList; const TVarLiveMap& uniformList; }; struct TNotifyUniformAdaptor { EShLanguage stage; TIoMapResolver& resolver; inline TNotifyUniformAdaptor(EShLanguage s, TIoMapResolver& r) : stage(s) , resolver(r) { } inline void operator()(std::pair& entKey) { resolver.notifyBinding(stage, entKey.second); } private: TNotifyUniformAdaptor& operator=(TNotifyUniformAdaptor&) = delete; }; struct TNotifyInOutAdaptor { EShLanguage stage; TIoMapResolver& resolver; inline TNotifyInOutAdaptor(EShLanguage s, TIoMapResolver& r) : stage(s) , resolver(r) { } inline void operator()(std::pair& entKey) { resolver.notifyInOut(entKey.second.stage, entKey.second); } private: TNotifyInOutAdaptor& operator=(TNotifyInOutAdaptor&) = delete; }; struct TResolverUniformAdaptor { TResolverUniformAdaptor(EShLanguage s, TIoMapResolver& r, TVarLiveMap* uniform[EShLangCount], TInfoSink& i, bool& e) : stage(s) , resolver(r) , infoSink(i) , error(e) { memcpy(uniformVarMap, uniform, EShLangCount * (sizeof(TVarLiveMap*))); } inline void operator()(std::pair& entKey) { TVarEntryInfo& ent = entKey.second; ent.newLocation = -1; ent.newComponent = -1; ent.newBinding = -1; ent.newSet = -1; ent.newIndex = -1; const bool isValid = resolver.validateBinding(stage, ent); if (isValid) { resolver.resolveSet(ent.stage, ent); resolver.resolveBinding(ent.stage, ent); resolver.resolveUniformLocation(ent.stage, ent); if (ent.newBinding != -1) { if (ent.newBinding >= int(TQualifier::layoutBindingEnd)) { TString err = "mapped binding out of range: " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); error = true; } if (ent.symbol->getQualifier().hasBinding()) { for (uint32_t idx = EShLangVertex; idx < EShLangCount; ++idx) { if (idx == ent.stage || uniformVarMap[idx] == nullptr) continue; auto entKey2 = uniformVarMap[idx]->find(entKey.first); if (entKey2 != uniformVarMap[idx]->end()) { entKey2->second.newBinding = ent.newBinding; } } } } if (ent.newSet != -1) { if (ent.newSet >= int(TQualifier::layoutSetEnd)) { TString err = "mapped set out of range: " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); error = true; } if (ent.symbol->getQualifier().hasSet()) { for (uint32_t idx = EShLangVertex; idx < EShLangCount; ++idx) { if ((idx == stage) || (uniformVarMap[idx] == nullptr)) continue; auto entKey2 = uniformVarMap[idx]->find(entKey.first); if (entKey2 != uniformVarMap[idx]->end()) { entKey2->second.newSet = ent.newSet; } } } } } else { TString errorMsg = "Invalid binding: " + entKey.first; infoSink.info.message(EPrefixInternalError, errorMsg.c_str()); error = true; } } inline void setStage(EShLanguage s) { stage = s; } EShLanguage stage; TIoMapResolver& resolver; TInfoSink& infoSink; bool& error; TVarLiveMap* uniformVarMap[EShLangCount]; private: TResolverUniformAdaptor& operator=(TResolverUniformAdaptor&) = delete; }; struct TResolverInOutAdaptor { TResolverInOutAdaptor(EShLanguage s, TIoMapResolver& r, TInfoSink& i, bool& e) : stage(s) , resolver(r) , infoSink(i) , error(e) { } inline void operator()(std::pair& entKey) { TVarEntryInfo& ent = entKey.second; ent.newLocation = -1; ent.newComponent = -1; ent.newBinding = -1; ent.newSet = -1; ent.newIndex = -1; const bool isValid = resolver.validateInOut(ent.stage, ent); if (isValid) { resolver.resolveInOutLocation(stage, ent); resolver.resolveInOutComponent(stage, ent); resolver.resolveInOutIndex(stage, ent); } else { TString errorMsg; if (ent.symbol->getType().getQualifier().semanticName != nullptr) { errorMsg = "Invalid shader In/Out variable semantic: "; errorMsg += ent.symbol->getType().getQualifier().semanticName; } else { errorMsg = "Invalid shader In/Out variable: "; errorMsg += ent.symbol->getName(); } infoSink.info.message(EPrefixInternalError, errorMsg.c_str()); error = true; } } inline void setStage(EShLanguage s) { stage = s; } EShLanguage stage; TIoMapResolver& resolver; TInfoSink& infoSink; bool& error; private: TResolverInOutAdaptor& operator=(TResolverInOutAdaptor&) = delete; }; // The class is used for reserving explicit uniform locations and ubo/ssbo/opaque bindings // xxTODO: maybe this logic should be moved into the resolver's "validateInOut" and "validateUniform" struct TSymbolValidater { TSymbolValidater(TIoMapResolver& r, TInfoSink& i, TVarLiveMap* in[EShLangCount], TVarLiveMap* out[EShLangCount], TVarLiveMap* uniform[EShLangCount], bool& hadError, EProfile profile, int version) : resolver(r) , infoSink(i) , hadError(hadError) , profile(profile) , version(version) { memcpy(inVarMaps, in, EShLangCount * (sizeof(TVarLiveMap*))); memcpy(outVarMaps, out, EShLangCount * (sizeof(TVarLiveMap*))); memcpy(uniformVarMap, uniform, EShLangCount * (sizeof(TVarLiveMap*))); std::map anonymousMemberMap; std::vector usedUniformLocation; std::vector usedUniformName; usedUniformLocation.clear(); usedUniformName.clear(); for (int i = 0; i < EShLangCount; i++) { if (uniformVarMap[i]) { for (auto uniformVar : *uniformVarMap[i]) { TIntermSymbol* pSymbol = uniformVar.second.symbol; TQualifier qualifier = uniformVar.second.symbol->getQualifier(); TString symbolName = pSymbol->getAccessName(); // All the uniform needs multi-stage location check (block/default) int uniformLocation = qualifier.layoutLocation; if (uniformLocation != TQualifier::layoutLocationEnd) { // Total size of current uniform, could be block, struct or other types. int size = TIntermediate::computeTypeUniformLocationSize(pSymbol->getType()); TRange locationRange(uniformLocation, uniformLocation + size - 1); // Combine location and component ranges int overlapLocation = -1; bool diffLocation = false; // Check for collisions, except for vertex inputs on desktop targeting OpenGL overlapLocation = checkLocationOverlap(locationRange, usedUniformLocation, symbolName, usedUniformName, diffLocation); // Overlap locations of uniforms, regardless of components (multi stages) if (overlapLocation == -1) { usedUniformLocation.push_back(locationRange); usedUniformName.push_back(symbolName); } else if (overlapLocation >= 0) { if (diffLocation == true) { TString err = ("Uniform location should be equal for same uniforms: " +std::to_string(overlapLocation)).c_str(); infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; break; } else { TString err = ("Uniform location overlaps across stages: " + std::to_string(overlapLocation)).c_str(); infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; break; } } } if ((uniformVar.second.symbol->getBasicType() == EbtBlock) && IsAnonymous(uniformVar.second.symbol->getName())) { auto blockType = uniformVar.second.symbol->getType().getStruct(); for (size_t memberIdx = 0; memberIdx < blockType->size(); ++memberIdx) { auto memberName = (*blockType)[memberIdx].type->getFieldName(); if (anonymousMemberMap.find(memberName) != anonymousMemberMap.end()) { if (anonymousMemberMap[memberName] != uniformVar.second.symbol->getType().getTypeName()) { TString err = "Invalid block member name: " + memberName; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; break; } } else { anonymousMemberMap[memberName] = uniformVar.second.symbol->getType().getTypeName(); } } } if (hadError) break; } } } } // In case we need to new an intermediate, which costs too much int checkLocationOverlap(const TRange& locationRange, std::vector& usedUniformLocation, const TString symbolName, std::vector& usedUniformName, bool& diffLocation) { for (size_t r = 0; r < usedUniformLocation.size(); ++r) { if (usedUniformName[r] == symbolName) { diffLocation = true; return (usedUniformLocation[r].start == locationRange.start && usedUniformLocation[r].last == locationRange.last) ? -2 : std::max(locationRange.start, usedUniformLocation[r].start); } if (locationRange.overlap(usedUniformLocation[r])) { // there is a collision; pick one return std::max(locationRange.start, usedUniformLocation[r].start); } } return -1; // no collision } inline void operator()(std::pair& entKey) { TVarEntryInfo& ent1 = entKey.second; TIntermSymbol* base = ent1.symbol; const TType& type = ent1.symbol->getType(); const TString& name = entKey.first; TString mangleName1, mangleName2; EShLanguage stage = ent1.stage; EShLanguage preStage, currentStage, nextStage; preStage = EShLangCount; for (int i = stage - 1; i >= 0; i--) { if (inVarMaps[i] != nullptr) { preStage = static_cast(i); break; } } currentStage = stage; nextStage = EShLangCount; for (int i = stage + 1; i < EShLangCount; i++) { if (inVarMaps[i] != nullptr) { nextStage = static_cast(i); break; } } if (type.getQualifier().isArrayedIo(stage)) { TType subType(type, 0); subType.appendMangledName(mangleName1); } else { type.appendMangledName(mangleName1); } // basic checking that symbols match // more extensive checking in the link stage if (base->getQualifier().storage == EvqVaryingIn) { // validate stage in; if (preStage == EShLangCount) return; if (TSymbolTable::isBuiltInSymbol(base->getId())) return; if (outVarMaps[preStage] != nullptr) { auto ent2 = outVarMaps[preStage]->find(name); uint32_t location = base->getType().getQualifier().layoutLocation; if (ent2 == outVarMaps[preStage]->end() && location != glslang::TQualifier::layoutLocationEnd) { for (auto var = outVarMaps[preStage]->begin(); var != ent2; var++) { if (var->second.symbol->getType().getQualifier().layoutLocation == location) { ent2 = var; break; } } } if (ent2 != outVarMaps[preStage]->end()) { auto& type1 = base->getType(); auto& type2 = ent2->second.symbol->getType(); hadError = hadError || typeCheck(&type1, &type2, name.c_str(), false); if (ent2->second.symbol->getType().getQualifier().isArrayedIo(preStage)) { TType subType(ent2->second.symbol->getType(), 0); subType.appendMangledName(mangleName2); } else { ent2->second.symbol->getType().appendMangledName(mangleName2); } if (mangleName1 == mangleName2) { // For ES 3.0 only, other versions have no such restrictions // According to ES 3.0 spec: The type and presence of the interpolation qualifiers and // storage qualifiers of variables with the same name declared in all linked shaders must // match, otherwise the link command will fail. if (profile == EEsProfile && version == 300) { // Don't need to check smooth qualifier, as it uses the default interpolation mode if (ent1.stage == EShLangFragment && type1.isBuiltIn() == false) { if (type1.getQualifier().flat != type2.getQualifier().flat || type1.getQualifier().nopersp != type2.getQualifier().nopersp) { TString err = "Interpolation qualifier mismatch : " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; } } } return; } else { TString err = "Invalid In/Out variable type : " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; } } else if (!base->getType().isBuiltIn()) { // According to spec: A link error is generated if any statically referenced input variable // or block does not have a matching output if (profile == EEsProfile && ent1.live) { hadError = true; TString errorStr = name + ": not been declare as a output variable in pre shader stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } } return; } } else if (base->getQualifier().storage == EvqVaryingOut) { // validate stage out; if (nextStage == EShLangCount) return; if (TSymbolTable::isBuiltInSymbol(base->getId())) return; if (inVarMaps[nextStage] != nullptr) { auto ent2 = inVarMaps[nextStage]->find(name); if (ent2 != inVarMaps[nextStage]->end()) { if (ent2->second.symbol->getType().getQualifier().isArrayedIo(nextStage)) { TType subType(ent2->second.symbol->getType(), 0); subType.appendMangledName(mangleName2); } else { ent2->second.symbol->getType().appendMangledName(mangleName2); } if (mangleName1 == mangleName2) return; else { TString err = "Invalid In/Out variable type : " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; } } return; } } else if (base->getQualifier().isUniformOrBuffer() && !base->getQualifier().isPushConstant()) { // validate uniform type; for (int i = 0; i < EShLangCount; i++) { if (i != currentStage && outVarMaps[i] != nullptr) { auto ent2 = uniformVarMap[i]->find(name); if (ent2 != uniformVarMap[i]->end()) { ent2->second.symbol->getType().appendMangledName(mangleName2); if (mangleName1 != mangleName2) { ent2->second.symbol->getType().sameElementType(type); TString err = "Invalid Uniform variable type : " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; } mangleName2.clear(); // validate instance name of blocks if (hadError == false && base->getType().getBasicType() == EbtBlock && IsAnonymous(base->getName()) != IsAnonymous(ent2->second.symbol->getName())) { TString err = "Matched uniform block names must also either all be lacking " "an instance name or all having an instance name: " + entKey.first; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; } // validate uniform block member qualifier and member names auto& type1 = base->getType(); auto& type2 = ent2->second.symbol->getType(); if (hadError == false && base->getType().getBasicType() == EbtBlock) { hadError = hadError || typeCheck(&type1, &type2, name.c_str(), true); } else { hadError = hadError || typeCheck(&type1, &type2, name.c_str(), false); } } else if (base->getBasicType() == EbtBlock) { if (IsAnonymous(base->getName())) { // The name of anonymous block member can't same with default uniform variable. auto blockType1 = base->getType().getStruct(); for (size_t memberIdx = 0; memberIdx < blockType1->size(); ++memberIdx) { auto memberName = (*blockType1)[memberIdx].type->getFieldName(); if (uniformVarMap[i]->find(memberName) != uniformVarMap[i]->end()) { TString err = "Invalid Uniform variable name : " + memberName; infoSink.info.message(EPrefixInternalError, err.c_str()); hadError = true; break; } } } } } } } } TVarLiveMap *inVarMaps[EShLangCount], *outVarMaps[EShLangCount], *uniformVarMap[EShLangCount]; // Use for mark current shader stage for resolver TIoMapResolver& resolver; TInfoSink& infoSink; bool& hadError; EProfile profile; int version; private: TSymbolValidater& operator=(TSymbolValidater&) = delete; bool qualifierCheck(const TType* const type1, const TType* const type2, const std::string& name, bool isBlock) { bool hasError = false; const TQualifier& qualifier1 = type1->getQualifier(); const TQualifier& qualifier2 = type2->getQualifier(); if (((isBlock == false) && (type1->getQualifier().storage == EvqUniform && type2->getQualifier().storage == EvqUniform)) || (type1->getQualifier().storage == EvqGlobal && type2->getQualifier().storage == EvqGlobal)) { if (qualifier1.precision != qualifier2.precision) { hasError = true; std::string errorStr = name + ": have precision conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } if (qualifier1.hasFormat() && qualifier2.hasFormat()) { if (qualifier1.layoutFormat != qualifier2.layoutFormat) { hasError = true; std::string errorStr = name + ": have layout format conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } } } if (isBlock == true) { if (qualifier1.layoutPacking != qualifier2.layoutPacking) { hasError = true; std::string errorStr = name + ": have layoutPacking conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } if (qualifier1.layoutMatrix != qualifier2.layoutMatrix) { hasError = true; std::string errorStr = name + ": have layoutMatrix conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } if (qualifier1.layoutOffset != qualifier2.layoutOffset) { hasError = true; std::string errorStr = name + ": have layoutOffset conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } if (qualifier1.layoutAlign != qualifier2.layoutAlign) { hasError = true; std::string errorStr = name + ": have layoutAlign conflict cross stage."; infoSink.info.message(EPrefixError, errorStr.c_str()); } } return hasError; } bool typeCheck(const TType* const type1, const TType* const type2, const std::string& name, bool isBlock) { bool hasError = false; if (!(type1->isStruct() && type2->isStruct())) { hasError = hasError || qualifierCheck(type1, type2, name, isBlock); } else { if (type1->getBasicType() == EbtBlock && type2->getBasicType() == EbtBlock) isBlock = true; const TTypeList* typeList1 = type1->getStruct(); const TTypeList* typeList2 = type2->getStruct(); std::string newName = name; size_t memberCount = typeList1->size(); size_t index2 = 0; for (size_t index = 0; index < memberCount; index++, index2++) { // Skip inactive member if (typeList1->at(index).type->getBasicType() == EbtVoid) continue; while (index2 < typeList2->size() && typeList2->at(index2).type->getBasicType() == EbtVoid) { ++index2; } // TypeList1 has more members in list if (index2 == typeList2->size()) { std::string errorStr = name + ": struct mismatch."; infoSink.info.message(EPrefixError, errorStr.c_str()); hasError = true; break; } if (typeList1->at(index).type->getFieldName() != typeList2->at(index2).type->getFieldName()) { std::string errorStr = name + ": member name mismatch."; infoSink.info.message(EPrefixError, errorStr.c_str()); hasError = true; } else { newName = typeList1->at(index).type->getFieldName().c_str(); } hasError = hasError || typeCheck(typeList1->at(index).type, typeList2->at(index2).type, newName, isBlock); } while (index2 < typeList2->size()) { // TypeList2 has more members if (typeList2->at(index2).type->getBasicType() != EbtVoid) { std::string errorStr = name + ": struct mismatch."; infoSink.info.message(EPrefixError, errorStr.c_str()); hasError = true; break; } ++index2; } } return hasError; } }; struct TSlotCollector { TSlotCollector(TIoMapResolver& r, TInfoSink& i) : resolver(r), infoSink(i) { } inline void operator()(std::pair& entKey) { resolver.reserverStorageSlot(entKey.second, infoSink); resolver.reserverResourceSlot(entKey.second, infoSink); } TIoMapResolver& resolver; TInfoSink& infoSink; private: TSlotCollector& operator=(TSlotCollector&) = delete; }; TDefaultIoResolverBase::TDefaultIoResolverBase(const TIntermediate& intermediate) : intermediate(intermediate) , nextUniformLocation(intermediate.getUniformLocationBase()) , nextInputLocation(0) , nextOutputLocation(0) { memset(stageMask, false, sizeof(bool) * (EShLangCount + 1)); memset(stageIntermediates, 0, sizeof(TIntermediate*) * (EShLangCount)); stageIntermediates[intermediate.getStage()] = &intermediate; } int TDefaultIoResolverBase::getBaseBinding(EShLanguage stage, TResourceType res, unsigned int set) const { return stageIntermediates[stage] ? selectBaseBinding(stageIntermediates[stage]->getShiftBinding(res), stageIntermediates[stage]->getShiftBindingForSet(res, set)) : selectBaseBinding(intermediate.getShiftBinding(res), intermediate.getShiftBindingForSet(res, set)); } const std::vector& TDefaultIoResolverBase::getResourceSetBinding(EShLanguage stage) const { return stageIntermediates[stage] ? stageIntermediates[stage]->getResourceSetBinding() : intermediate.getResourceSetBinding(); } bool TDefaultIoResolverBase::doAutoBindingMapping() const { return intermediate.getAutoMapBindings(); } bool TDefaultIoResolverBase::doAutoLocationMapping() const { return intermediate.getAutoMapLocations(); } TDefaultIoResolverBase::TSlotSet::iterator TDefaultIoResolverBase::findSlot(int set, int slot) { return std::lower_bound(slots[set].begin(), slots[set].end(), slot); } bool TDefaultIoResolverBase::checkEmpty(int set, int slot) { TSlotSet::iterator at = findSlot(set, slot); return ! (at != slots[set].end() && *at == slot); } int TDefaultIoResolverBase::reserveSlot(int set, int slot, int size) { TSlotSet::iterator at = findSlot(set, slot); // tolerate aliasing, by not double-recording aliases // (policy about appropriateness of the alias is higher up) for (int i = 0; i < size; i++) { if (at == slots[set].end() || *at != slot + i) at = slots[set].insert(at, slot + i); ++at; } return slot; } int TDefaultIoResolverBase::getFreeSlot(int set, int base, int size) { TSlotSet::iterator at = findSlot(set, base); if (at == slots[set].end()) return reserveSlot(set, base, size); // look for a big enough gap for (; at != slots[set].end(); ++at) { if (*at - base >= size) break; base = *at + 1; } return reserveSlot(set, base, size); } int TDefaultIoResolverBase::resolveSet(EShLanguage stage, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); if (type.getQualifier().hasSet()) { return ent.newSet = type.getQualifier().layoutSet; } // If a command line or API option requested a single descriptor set, use that (if not overrided by spaceN) if (getResourceSetBinding(stage).size() == 1) { return ent.newSet = atoi(getResourceSetBinding(stage)[0].c_str()); } return ent.newSet = 0; } int TDefaultIoResolverBase::resolveUniformLocation(EShLanguage /*stage*/, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); const char* name = ent.symbol->getAccessName().c_str(); // kick out of not doing this if (! doAutoLocationMapping()) { return ent.newLocation = -1; } // no locations added if already present, a built-in variable, a block, or an opaque if (type.getQualifier().hasLocation() || type.isBuiltIn() || type.getBasicType() == EbtBlock || type.isAtomic() || (type.containsOpaque() && intermediate.getSpv().openGl == 0)) { return ent.newLocation = -1; } // no locations on blocks of built-in variables if (type.isStruct()) { if (type.getStruct()->size() < 1) { return ent.newLocation = -1; } if ((*type.getStruct())[0].type->isBuiltIn()) { return ent.newLocation = -1; } } int location = intermediate.getUniformLocationOverride(name); if (location != -1) { return ent.newLocation = location; } location = nextUniformLocation; nextUniformLocation += TIntermediate::computeTypeUniformLocationSize(type); return ent.newLocation = location; } int TDefaultIoResolverBase::resolveInOutLocation(EShLanguage stage, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); // kick out of not doing this if (! doAutoLocationMapping()) { return ent.newLocation = -1; } // no locations added if already present, or a built-in variable if (type.getQualifier().hasLocation() || type.isBuiltIn()) { return ent.newLocation = -1; } // no locations on blocks of built-in variables if (type.isStruct()) { if (type.getStruct()->size() < 1) { return ent.newLocation = -1; } if ((*type.getStruct())[0].type->isBuiltIn()) { return ent.newLocation = -1; } } // point to the right input or output location counter int& nextLocation = type.getQualifier().isPipeInput() ? nextInputLocation : nextOutputLocation; // Placeholder. This does not do proper cross-stage lining up, nor // work with mixed location/no-location declarations. int location = nextLocation; int typeLocationSize; // Don’t take into account the outer-most array if the stage’s // interface is automatically an array. typeLocationSize = computeTypeLocationSize(type, stage); nextLocation += typeLocationSize; return ent.newLocation = location; } int TDefaultIoResolverBase::resolveInOutComponent(EShLanguage /*stage*/, TVarEntryInfo& ent) { return ent.newComponent = -1; } int TDefaultIoResolverBase::resolveInOutIndex(EShLanguage /*stage*/, TVarEntryInfo& ent) { return ent.newIndex = -1; } uint32_t TDefaultIoResolverBase::computeTypeLocationSize(const TType& type, EShLanguage stage) { int typeLocationSize; // Don’t take into account the outer-most array if the stage’s // interface is automatically an array. if (type.getQualifier().isArrayedIo(stage)) { TType elementType(type, 0); typeLocationSize = TIntermediate::computeTypeLocationSize(elementType, stage); } else { typeLocationSize = TIntermediate::computeTypeLocationSize(type, stage); } return typeLocationSize; } //TDefaultGlslIoResolver TResourceType TDefaultGlslIoResolver::getResourceType(const glslang::TType& type) { if (isImageType(type)) { return EResImage; } if (isTextureType(type)) { return EResTexture; } if (isSsboType(type)) { return EResSsbo; } if (isSamplerType(type)) { return EResSampler; } if (isUboType(type)) { return EResUbo; } return EResCount; } TDefaultGlslIoResolver::TDefaultGlslIoResolver(const TIntermediate& intermediate) : TDefaultIoResolverBase(intermediate) , preStage(EShLangCount) , currentStage(EShLangCount) { } int TDefaultGlslIoResolver::resolveInOutLocation(EShLanguage stage, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); const TString& name = ent.symbol->getAccessName(); if (currentStage != stage) { preStage = currentStage; currentStage = stage; } // kick out if not doing this if (! doAutoLocationMapping()) { return ent.newLocation = -1; } // expand the location to each element if the symbol is a struct or array if (type.getQualifier().hasLocation()) { return ent.newLocation = type.getQualifier().layoutLocation; } // no locations added if already present, or a built-in variable if (type.isBuiltIn()) { return ent.newLocation = -1; } // no locations on blocks of built-in variables if (type.isStruct()) { if (type.getStruct()->size() < 1) { return ent.newLocation = -1; } if ((*type.getStruct())[0].type->isBuiltIn()) { return ent.newLocation = -1; } } int typeLocationSize = computeTypeLocationSize(type, stage); int location = type.getQualifier().layoutLocation; bool hasLocation = false; EShLanguage keyStage(EShLangCount); TStorageQualifier storage; storage = EvqInOut; if (type.getQualifier().isPipeInput()) { // If this symbol is a input, search pre stage's out keyStage = preStage; } if (type.getQualifier().isPipeOutput()) { // If this symbol is a output, search next stage's in keyStage = currentStage; } // The in/out in current stage is not declared with location, but it is possible declared // with explicit location in other stages, find the storageSlotMap firstly to check whether // the in/out has location int resourceKey = buildStorageKey(keyStage, storage); if (! storageSlotMap[resourceKey].empty()) { TVarSlotMap::iterator iter = storageSlotMap[resourceKey].find(name); if (iter != storageSlotMap[resourceKey].end()) { // If interface resource be found, set it has location and this symbol's new location // equal the symbol's explicit location declaration in pre or next stage. // // vs: out vec4 a; // fs: layout(..., location = 3,...) in vec4 a; hasLocation = true; location = iter->second; // if we want deal like that: // vs: layout(location=4) out vec4 a; // out vec4 b; // // fs: in vec4 a; // layout(location = 4) in vec4 b; // we need retraverse the map. } if (! hasLocation) { // If interface resource note found, It's mean the location in two stage are both implicit declarat. // So we should find a new slot for this interface. // // vs: out vec4 a; // fs: in vec4 a; location = getFreeSlot(resourceKey, 0, typeLocationSize); storageSlotMap[resourceKey][name] = location; } } else { // the first interface declarated in a program. TVarSlotMap varSlotMap; location = getFreeSlot(resourceKey, 0, typeLocationSize); varSlotMap[name] = location; storageSlotMap[resourceKey] = varSlotMap; } //Update location return ent.newLocation = location; } int TDefaultGlslIoResolver::resolveUniformLocation(EShLanguage /*stage*/, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); const TString& name = ent.symbol->getAccessName(); // kick out of not doing this if (! doAutoLocationMapping()) { return ent.newLocation = -1; } // expand the location to each element if the symbol is a struct or array if (type.getQualifier().hasLocation() && (type.isStruct() || type.isArray())) { return ent.newLocation = type.getQualifier().layoutLocation; } else { // no locations added if already present, a built-in variable, a block, or an opaque if (type.getQualifier().hasLocation() || type.isBuiltIn() || type.getBasicType() == EbtBlock || type.isAtomic() || (type.containsOpaque() && intermediate.getSpv().openGl == 0)) { return ent.newLocation = -1; } // no locations on blocks of built-in variables if (type.isStruct()) { if (type.getStruct()->size() < 1) { return ent.newLocation = -1; } if ((*type.getStruct())[0].type->isBuiltIn()) { return ent.newLocation = -1; } } } int location = intermediate.getUniformLocationOverride(name.c_str()); if (location != -1) { return ent.newLocation = location; } int size = TIntermediate::computeTypeUniformLocationSize(type); // The uniform in current stage is not declared with location, but it is possible declared // with explicit location in other stages, find the storageSlotMap firstly to check whether // the uniform has location bool hasLocation = false; int resourceKey = buildStorageKey(EShLangCount, EvqUniform); TVarSlotMap& slotMap = storageSlotMap[resourceKey]; // Check dose shader program has uniform resource if (! slotMap.empty()) { // If uniform resource not empty, try find a same name uniform TVarSlotMap::iterator iter = slotMap.find(name); if (iter != slotMap.end()) { // If uniform resource be found, set it has location and this symbol's new location // equal the uniform's explicit location declaration in other stage. // // vs: uniform vec4 a; // fs: layout(..., location = 3,...) uniform vec4 a; hasLocation = true; location = iter->second; } if (! hasLocation) { // No explicit location declaration in other stage. // So we should find a new slot for this uniform. // // vs: uniform vec4 a; // fs: uniform vec4 a; location = getFreeSlot(resourceKey, 0, computeTypeLocationSize(type, currentStage)); storageSlotMap[resourceKey][name] = location; } } else { // the first uniform declaration in a program. TVarSlotMap varSlotMap; location = getFreeSlot(resourceKey, 0, size); varSlotMap[name] = location; storageSlotMap[resourceKey] = varSlotMap; } return ent.newLocation = location; } int TDefaultGlslIoResolver::resolveBinding(EShLanguage stage, TVarEntryInfo& ent) { const TType& type = ent.symbol->getType(); const TString& name = ent.symbol->getAccessName(); // On OpenGL arrays of opaque types take a separate binding for each element int numBindings = intermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1; TResourceType resource = getResourceType(type); // don't need to handle uniform symbol, it will be handled in resolveUniformLocation if (resource == EResUbo && type.getBasicType() != EbtBlock) { return ent.newBinding = -1; } // There is no 'set' qualifier in OpenGL shading language, each resource has its own // binding name space, so remap the 'set' to resource type which make each resource // binding is valid from 0 to MAX_XXRESOURCE_BINDINGS int set = intermediate.getSpv().openGl != 0 ? resource : ent.newSet; int resourceKey = set; if (resource < EResCount) { if (type.getQualifier().hasBinding()) { int newBinding = reserveSlot(resourceKey, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding, numBindings); return ent.newBinding = newBinding; } else { // The resource in current stage is not declared with binding, but it is possible declared // with explicit binding in other stages, find the resourceSlotMap firstly to check whether // the resource has binding, don't need to allocate if it already has a binding bool hasBinding = false; ent.newBinding = -1; // leave as -1 if it isn't set below if (! resourceSlotMap[resourceKey].empty()) { TVarSlotMap::iterator iter = resourceSlotMap[resourceKey].find(name); if (iter != resourceSlotMap[resourceKey].end()) { hasBinding = true; ent.newBinding = iter->second; } } if (!hasBinding && (ent.live && doAutoBindingMapping())) { // find free slot, the caller did make sure it passes all vars with binding // first and now all are passed that do not have a binding and needs one int binding = getFreeSlot(resourceKey, getBaseBinding(stage, resource, set), numBindings); resourceSlotMap[resourceKey][name] = binding; ent.newBinding = binding; } return ent.newBinding; } } return ent.newBinding = -1; } void TDefaultGlslIoResolver::beginResolve(EShLanguage stage) { // reset stage state if (stage == EShLangCount) preStage = currentStage = stage; // update stage state else if (currentStage != stage) { preStage = currentStage; currentStage = stage; } } void TDefaultGlslIoResolver::endResolve(EShLanguage /*stage*/) { // TODO nothing } void TDefaultGlslIoResolver::beginCollect(EShLanguage stage) { // reset stage state if (stage == EShLangCount) preStage = currentStage = stage; // update stage state else if (currentStage != stage) { preStage = currentStage; currentStage = stage; } } void TDefaultGlslIoResolver::endCollect(EShLanguage /*stage*/) { // TODO nothing } void TDefaultGlslIoResolver::reserverStorageSlot(TVarEntryInfo& ent, TInfoSink& infoSink) { const TType& type = ent.symbol->getType(); const TString& name = ent.symbol->getAccessName(); TStorageQualifier storage = type.getQualifier().storage; EShLanguage stage(EShLangCount); switch (storage) { case EvqUniform: if (type.getBasicType() != EbtBlock && type.getQualifier().hasLocation()) { // // Reserve the slots for the uniforms who has explicit location int storageKey = buildStorageKey(EShLangCount, EvqUniform); int location = type.getQualifier().layoutLocation; TVarSlotMap& varSlotMap = storageSlotMap[storageKey]; TVarSlotMap::iterator iter = varSlotMap.find(name); if (iter == varSlotMap.end()) { int numLocations = TIntermediate::computeTypeUniformLocationSize(type); reserveSlot(storageKey, location, numLocations); varSlotMap[name] = location; } else { // Allocate location by name for OpenGL driver, so the uniform in different // stages should be declared with the same location if (iter->second != location) { TString errorMsg = "Invalid location: " + name; infoSink.info.message(EPrefixInternalError, errorMsg.c_str()); hasError = true; } } } break; case EvqVaryingIn: case EvqVaryingOut: // // Reserve the slots for the inout who has explicit location if (type.getQualifier().hasLocation()) { stage = storage == EvqVaryingIn ? preStage : stage; stage = storage == EvqVaryingOut ? currentStage : stage; int storageKey = buildStorageKey(stage, EvqInOut); int location = type.getQualifier().layoutLocation; TVarSlotMap& varSlotMap = storageSlotMap[storageKey]; TVarSlotMap::iterator iter = varSlotMap.find(name); if (iter == varSlotMap.end()) { int numLocations = TIntermediate::computeTypeUniformLocationSize(type); reserveSlot(storageKey, location, numLocations); varSlotMap[name] = location; } else { // Allocate location by name for OpenGL driver, so the uniform in different // stages should be declared with the same location if (iter->second != location) { TString errorMsg = "Invalid location: " + name; infoSink.info.message(EPrefixInternalError, errorMsg.c_str()); hasError = true; } } } break; default: break; } } void TDefaultGlslIoResolver::reserverResourceSlot(TVarEntryInfo& ent, TInfoSink& infoSink) { const TType& type = ent.symbol->getType(); const TString& name = ent.symbol->getAccessName(); TResourceType resource = getResourceType(type); int set = intermediate.getSpv().openGl != 0 ? resource : resolveSet(ent.stage, ent); int resourceKey = set; if (type.getQualifier().hasBinding()) { TVarSlotMap& varSlotMap = resourceSlotMap[resourceKey]; TVarSlotMap::iterator iter = varSlotMap.find(name); int binding = type.getQualifier().layoutBinding + getBaseBinding(ent.stage, resource, set); if (iter == varSlotMap.end()) { // Reserve the slots for the ubo, ssbo and opaques who has explicit binding int numBindings = intermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1; varSlotMap[name] = binding; reserveSlot(resourceKey, binding, numBindings); } else { // Allocate binding by name for OpenGL driver, so the resource in different // stages should be declared with the same binding if (iter->second != binding) { TString errorMsg = "Invalid binding: " + name; infoSink.info.message(EPrefixInternalError, errorMsg.c_str()); hasError = true; } } } } //TDefaultGlslIoResolver end /* * Basic implementation of glslang::TIoMapResolver that replaces the * previous offset behavior. * It does the same, uses the offsets for the corresponding uniform * types. Also respects the EOptionAutoMapBindings flag and binds * them if needed. */ /* * Default resolver */ struct TDefaultIoResolver : public TDefaultIoResolverBase { TDefaultIoResolver(const TIntermediate& intermediate) : TDefaultIoResolverBase(intermediate) { } bool validateBinding(EShLanguage /*stage*/, TVarEntryInfo& /*ent*/) override { return true; } TResourceType getResourceType(const glslang::TType& type) override { if (isImageType(type)) { return EResImage; } if (isTextureType(type)) { return EResTexture; } if (isSsboType(type)) { return EResSsbo; } if (isSamplerType(type)) { return EResSampler; } if (isUboType(type)) { return EResUbo; } return EResCount; } int resolveBinding(EShLanguage stage, TVarEntryInfo& ent) override { const TType& type = ent.symbol->getType(); const int set = getLayoutSet(type); // On OpenGL arrays of opaque types take a seperate binding for each element int numBindings = intermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1; TResourceType resource = getResourceType(type); if (resource < EResCount) { if (type.getQualifier().hasBinding()) { return ent.newBinding = reserveSlot( set, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding, numBindings); } else if (ent.live && doAutoBindingMapping()) { // find free slot, the caller did make sure it passes all vars with binding // first and now all are passed that do not have a binding and needs one return ent.newBinding = getFreeSlot(set, getBaseBinding(stage, resource, set), numBindings); } } return ent.newBinding = -1; } }; #ifdef ENABLE_HLSL /******************************************************************************** The following IO resolver maps types in HLSL register space, as follows: t - for shader resource views (SRV) TEXTURE1D TEXTURE1DARRAY TEXTURE2D TEXTURE2DARRAY TEXTURE3D TEXTURECUBE TEXTURECUBEARRAY TEXTURE2DMS TEXTURE2DMSARRAY STRUCTUREDBUFFER BYTEADDRESSBUFFER BUFFER TBUFFER s - for samplers SAMPLER SAMPLER1D SAMPLER2D SAMPLER3D SAMPLERCUBE SAMPLERSTATE SAMPLERCOMPARISONSTATE u - for unordered access views (UAV) RWBYTEADDRESSBUFFER RWSTRUCTUREDBUFFER APPENDSTRUCTUREDBUFFER CONSUMESTRUCTUREDBUFFER RWBUFFER RWTEXTURE1D RWTEXTURE1DARRAY RWTEXTURE2D RWTEXTURE2DARRAY RWTEXTURE3D b - for constant buffer views (CBV) CBUFFER CONSTANTBUFFER ********************************************************************************/ struct TDefaultHlslIoResolver : public TDefaultIoResolverBase { TDefaultHlslIoResolver(const TIntermediate& intermediate) : TDefaultIoResolverBase(intermediate) { } bool validateBinding(EShLanguage /*stage*/, TVarEntryInfo& /*ent*/) override { return true; } TResourceType getResourceType(const glslang::TType& type) override { if (isUavType(type)) { return EResUav; } if (isSrvType(type)) { return EResTexture; } if (isSamplerType(type)) { return EResSampler; } if (isUboType(type)) { return EResUbo; } return EResCount; } int resolveBinding(EShLanguage stage, TVarEntryInfo& ent) override { const TType& type = ent.symbol->getType(); const int set = getLayoutSet(type); TResourceType resource = getResourceType(type); if (resource < EResCount) { if (type.getQualifier().hasBinding()) { return ent.newBinding = reserveSlot(set, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding); } else if (ent.live && doAutoBindingMapping()) { // find free slot, the caller did make sure it passes all vars with binding // first and now all are passed that do not have a binding and needs one return ent.newBinding = getFreeSlot(set, getBaseBinding(stage, resource, set)); } } return ent.newBinding = -1; } }; #endif // Map I/O variables to provided offsets, and make bindings for // unbound but live variables. // // Returns false if the input is too malformed to do this. bool TIoMapper::addStage(EShLanguage stage, TIntermediate& intermediate, TInfoSink& infoSink, TIoMapResolver* resolver) { bool somethingToDo = ! intermediate.getResourceSetBinding().empty() || intermediate.getAutoMapBindings() || intermediate.getAutoMapLocations(); // Restrict the stricter condition to further check 'somethingToDo' only if 'somethingToDo' has not been set, reduce // unnecessary or insignificant for-loop operation after 'somethingToDo' have been true. for (int res = 0; (res < EResCount && !somethingToDo); ++res) { somethingToDo = somethingToDo || (intermediate.getShiftBinding(TResourceType(res)) != 0) || intermediate.hasShiftBindingForSet(TResourceType(res)); } if (! somethingToDo && resolver == nullptr) return true; if (intermediate.getNumEntryPoints() != 1 || intermediate.isRecursive()) return false; TIntermNode* root = intermediate.getTreeRoot(); if (root == nullptr) return false; // if no resolver is provided, use the default resolver with the given shifts and auto map settings TDefaultIoResolver defaultResolver(intermediate); #ifdef ENABLE_HLSL TDefaultHlslIoResolver defaultHlslResolver(intermediate); if (resolver == nullptr) { // TODO: use a passed in IO mapper for this if (intermediate.usingHlslIoMapping()) resolver = &defaultHlslResolver; else resolver = &defaultResolver; } #else resolver = &defaultResolver; #endif resolver->addStage(stage, intermediate); TVarLiveMap inVarMap, outVarMap, uniformVarMap; TVarLiveVector inVector, outVector, uniformVector; TVarGatherTraverser iter_binding_all(intermediate, true, inVarMap, outVarMap, uniformVarMap); TVarGatherTraverser iter_binding_live(intermediate, false, inVarMap, outVarMap, uniformVarMap); root->traverse(&iter_binding_all); iter_binding_live.pushFunction(intermediate.getEntryPointMangledName().c_str()); while (! iter_binding_live.destinations.empty()) { TIntermNode* destination = iter_binding_live.destinations.back(); iter_binding_live.destinations.pop_back(); destination->traverse(&iter_binding_live); } // sort entries by priority. see TVarEntryInfo::TOrderByPriority for info. for (auto& var : inVarMap) { inVector.push_back(var); } std::sort(inVector.begin(), inVector.end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); for (auto& var : outVarMap) { outVector.push_back(var); } std::sort(outVector.begin(), outVector.end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); for (auto& var : uniformVarMap) { uniformVector.push_back(var); } std::sort(uniformVector.begin(), uniformVector.end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); bool hadError = false; TVarLiveMap* dummyUniformVarMap[EShLangCount] = {}; TNotifyInOutAdaptor inOutNotify(stage, *resolver); TNotifyUniformAdaptor uniformNotify(stage, *resolver); TResolverUniformAdaptor uniformResolve(stage, *resolver, dummyUniformVarMap, infoSink, hadError); TResolverInOutAdaptor inOutResolve(stage, *resolver, infoSink, hadError); resolver->beginNotifications(stage); std::for_each(inVector.begin(), inVector.end(), inOutNotify); std::for_each(outVector.begin(), outVector.end(), inOutNotify); std::for_each(uniformVector.begin(), uniformVector.end(), uniformNotify); resolver->endNotifications(stage); resolver->beginResolve(stage); for (auto& var : inVector) { inOutResolve(var); } std::for_each(inVector.begin(), inVector.end(), [&inVarMap](TVarLivePair p) { auto at = inVarMap.find(p.second.symbol->getAccessName()); if (at != inVarMap.end() && p.second.id == at->second.id) at->second = p.second; }); for (auto& var : outVector) { inOutResolve(var); } std::for_each(outVector.begin(), outVector.end(), [&outVarMap](TVarLivePair p) { auto at = outVarMap.find(p.second.symbol->getAccessName()); if (at != outVarMap.end() && p.second.id == at->second.id) at->second = p.second; }); std::for_each(uniformVector.begin(), uniformVector.end(), uniformResolve); std::for_each(uniformVector.begin(), uniformVector.end(), [&uniformVarMap](TVarLivePair p) { auto at = uniformVarMap.find(p.second.symbol->getAccessName()); if (at != uniformVarMap.end() && p.second.id == at->second.id) at->second = p.second; }); resolver->endResolve(stage); if (!hadError) { TVarSetTraverser iter_iomap(intermediate, inVarMap, outVarMap, uniformVarMap); root->traverse(&iter_iomap); } return !hadError; } // Map I/O variables to provided offsets, and make bindings for // unbound but live variables. // // Returns false if the input is too malformed to do this. bool TGlslIoMapper::addStage(EShLanguage stage, TIntermediate& intermediate, TInfoSink& infoSink, TIoMapResolver* resolver) { bool somethingToDo = !intermediate.getResourceSetBinding().empty() || intermediate.getAutoMapBindings() || intermediate.getAutoMapLocations(); // Profile and version are use for symbol validate. profile = intermediate.getProfile(); version = intermediate.getVersion(); // Restrict the stricter condition to further check 'somethingToDo' only if 'somethingToDo' has not been set, reduce // unnecessary or insignificant for-loop operation after 'somethingToDo' have been true. for (int res = 0; (res < EResCount && !somethingToDo); ++res) { somethingToDo = somethingToDo || (intermediate.getShiftBinding(TResourceType(res)) != 0) || intermediate.hasShiftBindingForSet(TResourceType(res)); } if (! somethingToDo && resolver == nullptr) { return true; } if (intermediate.getNumEntryPoints() != 1 || intermediate.isRecursive()) { return false; } TIntermNode* root = intermediate.getTreeRoot(); if (root == nullptr) { return false; } // if no resolver is provided, use the default resolver with the given shifts and auto map settings TDefaultGlslIoResolver defaultResolver(intermediate); #ifdef ENABLE_HLSL TDefaultHlslIoResolver defaultHlslResolver(intermediate); if (resolver == nullptr) { // TODO: use a passed in IO mapper for this if (intermediate.usingHlslIoMapping()) resolver = &defaultHlslResolver; else resolver = &defaultResolver; } #else if (resolver == nullptr) { resolver = &defaultResolver; } #endif resolver->addStage(stage, intermediate); inVarMaps[stage] = new TVarLiveMap(); outVarMaps[stage] = new TVarLiveMap(); uniformVarMap[stage] = new TVarLiveMap(); TVarGatherTraverser iter_binding_all(intermediate, true, *inVarMaps[stage], *outVarMaps[stage], *uniformVarMap[stage]); TVarGatherTraverser iter_binding_live(intermediate, false, *inVarMaps[stage], *outVarMaps[stage], *uniformVarMap[stage]); root->traverse(&iter_binding_all); iter_binding_live.pushFunction(intermediate.getEntryPointMangledName().c_str()); while (! iter_binding_live.destinations.empty()) { TIntermNode* destination = iter_binding_live.destinations.back(); iter_binding_live.destinations.pop_back(); destination->traverse(&iter_binding_live); } TNotifyInOutAdaptor inOutNotify(stage, *resolver); TNotifyUniformAdaptor uniformNotify(stage, *resolver); // Resolve current stage input symbol location with previous stage output here, // uniform symbol, ubo, ssbo and opaque symbols are per-program resource, // will resolve uniform symbol location and ubo/ssbo/opaque binding in doMap() resolver->beginNotifications(stage); std::for_each(inVarMaps[stage]->begin(), inVarMaps[stage]->end(), inOutNotify); std::for_each(outVarMaps[stage]->begin(), outVarMaps[stage]->end(), inOutNotify); std::for_each(uniformVarMap[stage]->begin(), uniformVarMap[stage]->end(), uniformNotify); resolver->endNotifications(stage); TSlotCollector slotCollector(*resolver, infoSink); resolver->beginCollect(stage); std::for_each(inVarMaps[stage]->begin(), inVarMaps[stage]->end(), slotCollector); std::for_each(outVarMaps[stage]->begin(), outVarMaps[stage]->end(), slotCollector); std::for_each(uniformVarMap[stage]->begin(), uniformVarMap[stage]->end(), slotCollector); resolver->endCollect(stage); intermediates[stage] = &intermediate; return !hadError; } bool TGlslIoMapper::doMap(TIoMapResolver* resolver, TInfoSink& infoSink) { resolver->endResolve(EShLangCount); if (!hadError) { //Resolve uniform location, ubo/ssbo/opaque bindings across stages TResolverUniformAdaptor uniformResolve(EShLangCount, *resolver, uniformVarMap, infoSink, hadError); TResolverInOutAdaptor inOutResolve(EShLangCount, *resolver, infoSink, hadError); TSymbolValidater symbolValidater(*resolver, infoSink, inVarMaps, outVarMaps, uniformVarMap, hadError, profile, version); TVarLiveVector inVectors[EShLangCount]; TVarLiveVector outVectors[EShLangCount]; TVarLiveVector uniformVector; resolver->beginResolve(EShLangCount); for (int stage = EShLangVertex; stage < EShLangCount; stage++) { if (inVarMaps[stage] != nullptr) { inOutResolve.setStage(EShLanguage(stage)); // copy vars into a sorted list std::for_each(inVarMaps[stage]->begin(), inVarMaps[stage]->end(), [&inVectors, stage](TVarLivePair p) { inVectors[stage].push_back(p); }); std::sort(inVectors[stage].begin(), inVectors[stage].end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); std::for_each(outVarMaps[stage]->begin(), outVarMaps[stage]->end(), [&outVectors, stage](TVarLivePair p) { outVectors[stage].push_back(p); }); std::sort(outVectors[stage].begin(), outVectors[stage].end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); for (auto& var : inVectors[stage]) { symbolValidater(var); } for (auto& var : inVectors[stage]) { inOutResolve(var); } for (auto& var : outVectors[stage]) { symbolValidater(var); } for (auto& var : outVectors[stage]) { inOutResolve(var); } // copy results back into maps std::for_each(inVectors[stage].begin(), inVectors[stage].end(), [this, stage](TVarLivePair p) { auto at = inVarMaps[stage]->find(p.first); if (at != inVarMaps[stage]->end()) at->second = p.second; }); std::for_each(outVectors[stage].begin(), outVectors[stage].end(), [this, stage](TVarLivePair p) { auto at = outVarMaps[stage]->find(p.first); if (at != outVarMaps[stage]->end()) at->second = p.second; }); } if (uniformVarMap[stage] != nullptr) { uniformResolve.setStage(EShLanguage(stage)); for (auto& var : *(uniformVarMap[stage])) { uniformVector.push_back(var); } } } std::sort(uniformVector.begin(), uniformVector.end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriorityAndLive()(p1.second, p2.second); }); for (auto& var : uniformVector) { symbolValidater(var); } for (auto& var : uniformVector) { uniformResolve(var); } std::sort(uniformVector.begin(), uniformVector.end(), [](const TVarLivePair& p1, const TVarLivePair& p2) -> bool { return TVarEntryInfo::TOrderByPriority()(p1.second, p2.second); }); resolver->endResolve(EShLangCount); for (size_t stage = 0; stage < EShLangCount; stage++) { if (intermediates[stage] != nullptr) { // traverse each stage, set new location to each input/output and unifom symbol, set new binding to // ubo, ssbo and opaque symbols TVarLiveMap** pUniformVarMap = uniformResolve.uniformVarMap; std::for_each(uniformVector.begin(), uniformVector.end(), [pUniformVarMap, stage](TVarLivePair p) { auto at = pUniformVarMap[stage]->find(p.second.symbol->getAccessName()); if (at != pUniformVarMap[stage]->end() && at->second.id == p.second.id){ int resolvedBinding = at->second.newBinding; at->second = p.second; if (resolvedBinding > 0) at->second.newBinding = resolvedBinding; } }); TVarSetTraverser iter_iomap(*intermediates[stage], *inVarMaps[stage], *outVarMaps[stage], *uniformResolve.uniformVarMap[stage]); intermediates[stage]->getTreeRoot()->traverse(&iter_iomap); } } return !hadError; } else { return false; } } } // end namespace glslang #endif // !GLSLANG_WEB && !GLSLANG_ANGLE