| // |
| // Copyright (C) 2002-2005 3Dlabs Inc. Ltd. |
| // Copyright (C) 2013-2016 LunarG, Inc. |
| // Copyright (C) 2015-2020 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. |
| // |
| |
| // |
| // Implement the top-level of interface to the compiler/linker, |
| // as defined in ShaderLang.h |
| // This is the platform independent interface between an OGL driver |
| // and the shading language compiler/linker. |
| // |
| #include <cstring> |
| #include <iostream> |
| #include <sstream> |
| #include <memory> |
| #include "SymbolTable.h" |
| #include "ParseHelper.h" |
| #include "Scan.h" |
| #include "ScanContext.h" |
| |
| #ifdef ENABLE_HLSL |
| #include "../../hlsl/hlslParseHelper.h" |
| #include "../../hlsl/hlslParseables.h" |
| #include "../../hlsl/hlslScanContext.h" |
| #endif |
| |
| #include "../Include/ShHandle.h" |
| #include "../../OGLCompilersDLL/InitializeDll.h" |
| |
| #include "preprocessor/PpContext.h" |
| |
| #define SH_EXPORTING |
| #include "../Public/ShaderLang.h" |
| #include "reflection.h" |
| #include "iomapper.h" |
| #include "Initialize.h" |
| |
| // TODO: this really shouldn't be here, it is only because of the trial addition |
| // of printing pre-processed tokens, which requires knowing the string literal |
| // token to print ", but none of that seems appropriate for this file. |
| #include "preprocessor/PpTokens.h" |
| |
| namespace { // anonymous namespace for file-local functions and symbols |
| |
| // Total number of successful initializers of glslang: a refcount |
| // Shared global; access should be protected by a global mutex/critical section. |
| int NumberOfClients = 0; |
| |
| using namespace glslang; |
| |
| // Create a language specific version of parseables. |
| TBuiltInParseables* CreateBuiltInParseables(TInfoSink& infoSink, EShSource source) |
| { |
| switch (source) { |
| case EShSourceGlsl: return new TBuiltIns(); // GLSL builtIns |
| #ifdef ENABLE_HLSL |
| case EShSourceHlsl: return new TBuiltInParseablesHlsl(); // HLSL intrinsics |
| #endif |
| |
| default: |
| infoSink.info.message(EPrefixInternalError, "Unable to determine source language"); |
| return nullptr; |
| } |
| } |
| |
| // Create a language specific version of a parse context. |
| TParseContextBase* CreateParseContext(TSymbolTable& symbolTable, TIntermediate& intermediate, |
| int version, EProfile profile, EShSource source, |
| EShLanguage language, TInfoSink& infoSink, |
| SpvVersion spvVersion, bool forwardCompatible, EShMessages messages, |
| bool parsingBuiltIns, std::string sourceEntryPointName = "") |
| { |
| switch (source) { |
| case EShSourceGlsl: { |
| if (sourceEntryPointName.size() == 0) |
| intermediate.setEntryPointName("main"); |
| TString entryPoint = sourceEntryPointName.c_str(); |
| return new TParseContext(symbolTable, intermediate, parsingBuiltIns, version, profile, spvVersion, |
| language, infoSink, forwardCompatible, messages, &entryPoint); |
| } |
| #ifdef ENABLE_HLSL |
| case EShSourceHlsl: |
| return new HlslParseContext(symbolTable, intermediate, parsingBuiltIns, version, profile, spvVersion, |
| language, infoSink, sourceEntryPointName.c_str(), forwardCompatible, messages); |
| #endif |
| default: |
| infoSink.info.message(EPrefixInternalError, "Unable to determine source language"); |
| return nullptr; |
| } |
| } |
| |
| // Local mapping functions for making arrays of symbol tables.... |
| |
| const int VersionCount = 17; // index range in MapVersionToIndex |
| |
| int MapVersionToIndex(int version) |
| { |
| int index = 0; |
| |
| switch (version) { |
| case 100: index = 0; break; |
| case 110: index = 1; break; |
| case 120: index = 2; break; |
| case 130: index = 3; break; |
| case 140: index = 4; break; |
| case 150: index = 5; break; |
| case 300: index = 6; break; |
| case 330: index = 7; break; |
| case 400: index = 8; break; |
| case 410: index = 9; break; |
| case 420: index = 10; break; |
| case 430: index = 11; break; |
| case 440: index = 12; break; |
| case 310: index = 13; break; |
| case 450: index = 14; break; |
| case 500: index = 0; break; // HLSL |
| case 320: index = 15; break; |
| case 460: index = 16; break; |
| default: assert(0); break; |
| } |
| |
| assert(index < VersionCount); |
| |
| return index; |
| } |
| |
| const int SpvVersionCount = 3; // index range in MapSpvVersionToIndex |
| |
| int MapSpvVersionToIndex(const SpvVersion& spvVersion) |
| { |
| int index = 0; |
| |
| if (spvVersion.openGl > 0) |
| index = 1; |
| else if (spvVersion.vulkan > 0) |
| index = 2; |
| |
| assert(index < SpvVersionCount); |
| |
| return index; |
| } |
| |
| const int ProfileCount = 4; // index range in MapProfileToIndex |
| |
| int MapProfileToIndex(EProfile profile) |
| { |
| int index = 0; |
| |
| switch (profile) { |
| case ENoProfile: index = 0; break; |
| case ECoreProfile: index = 1; break; |
| case ECompatibilityProfile: index = 2; break; |
| case EEsProfile: index = 3; break; |
| default: break; |
| } |
| |
| assert(index < ProfileCount); |
| |
| return index; |
| } |
| |
| const int SourceCount = 2; |
| |
| int MapSourceToIndex(EShSource source) |
| { |
| int index = 0; |
| |
| switch (source) { |
| case EShSourceGlsl: index = 0; break; |
| case EShSourceHlsl: index = 1; break; |
| default: break; |
| } |
| |
| assert(index < SourceCount); |
| |
| return index; |
| } |
| |
| // only one of these needed for non-ES; ES needs 2 for different precision defaults of built-ins |
| enum EPrecisionClass { |
| EPcGeneral, |
| EPcFragment, |
| EPcCount |
| }; |
| |
| // A process-global symbol table per version per profile for built-ins common |
| // to multiple stages (languages), and a process-global symbol table per version |
| // per profile per stage for built-ins unique to each stage. They will be sparsely |
| // populated, so they will only be generated as needed. |
| // |
| // Each has a different set of built-ins, and we want to preserve that from |
| // compile to compile. |
| // |
| TSymbolTable* CommonSymbolTable[VersionCount][SpvVersionCount][ProfileCount][SourceCount][EPcCount] = {}; |
| TSymbolTable* SharedSymbolTables[VersionCount][SpvVersionCount][ProfileCount][SourceCount][EShLangCount] = {}; |
| |
| TPoolAllocator* PerProcessGPA = nullptr; |
| |
| // |
| // Parse and add to the given symbol table the content of the given shader string. |
| // |
| bool InitializeSymbolTable(const TString& builtIns, int version, EProfile profile, const SpvVersion& spvVersion, EShLanguage language, |
| EShSource source, TInfoSink& infoSink, TSymbolTable& symbolTable) |
| { |
| TIntermediate intermediate(language, version, profile); |
| |
| intermediate.setSource(source); |
| |
| std::unique_ptr<TParseContextBase> parseContext(CreateParseContext(symbolTable, intermediate, version, profile, source, |
| language, infoSink, spvVersion, true, EShMsgDefault, |
| true)); |
| |
| TShader::ForbidIncluder includer; |
| TPpContext ppContext(*parseContext, "", includer); |
| TScanContext scanContext(*parseContext); |
| parseContext->setScanContext(&scanContext); |
| parseContext->setPpContext(&ppContext); |
| |
| // |
| // Push the symbol table to give it an initial scope. This |
| // push should not have a corresponding pop, so that built-ins |
| // are preserved, and the test for an empty table fails. |
| // |
| |
| symbolTable.push(); |
| |
| const char* builtInShaders[2]; |
| size_t builtInLengths[2]; |
| builtInShaders[0] = builtIns.c_str(); |
| builtInLengths[0] = builtIns.size(); |
| |
| if (builtInLengths[0] == 0) |
| return true; |
| |
| TInputScanner input(1, builtInShaders, builtInLengths); |
| if (! parseContext->parseShaderStrings(ppContext, input) != 0) { |
| infoSink.info.message(EPrefixInternalError, "Unable to parse built-ins"); |
| printf("Unable to parse built-ins\n%s\n", infoSink.info.c_str()); |
| printf("%s\n", builtInShaders[0]); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int CommonIndex(EProfile profile, EShLanguage language) |
| { |
| return (profile == EEsProfile && language == EShLangFragment) ? EPcFragment : EPcGeneral; |
| } |
| |
| // |
| // To initialize per-stage shared tables, with the common table already complete. |
| // |
| void InitializeStageSymbolTable(TBuiltInParseables& builtInParseables, int version, EProfile profile, const SpvVersion& spvVersion, |
| EShLanguage language, EShSource source, TInfoSink& infoSink, TSymbolTable** commonTable, |
| TSymbolTable** symbolTables) |
| { |
| #ifdef GLSLANG_WEB |
| profile = EEsProfile; |
| version = 310; |
| #endif |
| |
| (*symbolTables[language]).adoptLevels(*commonTable[CommonIndex(profile, language)]); |
| InitializeSymbolTable(builtInParseables.getStageString(language), version, profile, spvVersion, language, source, |
| infoSink, *symbolTables[language]); |
| builtInParseables.identifyBuiltIns(version, profile, spvVersion, language, *symbolTables[language]); |
| if (profile == EEsProfile && version >= 300) |
| (*symbolTables[language]).setNoBuiltInRedeclarations(); |
| if (version == 110) |
| (*symbolTables[language]).setSeparateNameSpaces(); |
| } |
| |
| // |
| // Initialize the full set of shareable symbol tables; |
| // The common (cross-stage) and those shareable per-stage. |
| // |
| bool InitializeSymbolTables(TInfoSink& infoSink, TSymbolTable** commonTable, TSymbolTable** symbolTables, int version, EProfile profile, const SpvVersion& spvVersion, EShSource source) |
| { |
| #ifdef GLSLANG_WEB |
| profile = EEsProfile; |
| version = 310; |
| #endif |
| |
| std::unique_ptr<TBuiltInParseables> builtInParseables(CreateBuiltInParseables(infoSink, source)); |
| |
| if (builtInParseables == nullptr) |
| return false; |
| |
| builtInParseables->initialize(version, profile, spvVersion); |
| |
| // do the common tables |
| InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, EShLangVertex, source, |
| infoSink, *commonTable[EPcGeneral]); |
| if (profile == EEsProfile) |
| InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, EShLangFragment, source, |
| infoSink, *commonTable[EPcFragment]); |
| |
| // do the per-stage tables |
| |
| // always have vertex and fragment |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangVertex, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangFragment, source, |
| infoSink, commonTable, symbolTables); |
| |
| #ifndef GLSLANG_WEB |
| // check for tessellation |
| if ((profile != EEsProfile && version >= 150) || |
| (profile == EEsProfile && version >= 310)) { |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTessControl, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTessEvaluation, source, |
| infoSink, commonTable, symbolTables); |
| } |
| |
| // check for geometry |
| if ((profile != EEsProfile && version >= 150) || |
| (profile == EEsProfile && version >= 310)) |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangGeometry, source, |
| infoSink, commonTable, symbolTables); |
| #endif |
| |
| // check for compute |
| if ((profile != EEsProfile && version >= 420) || |
| (profile == EEsProfile && version >= 310)) |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangCompute, source, |
| infoSink, commonTable, symbolTables); |
| |
| // check for ray tracing stages |
| if (profile != EEsProfile && version >= 450) { |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangRayGen, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangIntersect, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangAnyHit, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangClosestHit, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangMiss, source, |
| infoSink, commonTable, symbolTables); |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangCallable, source, |
| infoSink, commonTable, symbolTables); |
| } |
| |
| // check for mesh |
| if ((profile != EEsProfile && version >= 450) || |
| (profile == EEsProfile && version >= 320)) |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangMeshNV, source, |
| infoSink, commonTable, symbolTables); |
| |
| // check for task |
| if ((profile != EEsProfile && version >= 450) || |
| (profile == EEsProfile && version >= 320)) |
| InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTaskNV, source, |
| infoSink, commonTable, symbolTables); |
| |
| return true; |
| } |
| |
| bool AddContextSpecificSymbols(const TBuiltInResource* resources, TInfoSink& infoSink, TSymbolTable& symbolTable, int version, |
| EProfile profile, const SpvVersion& spvVersion, EShLanguage language, EShSource source) |
| { |
| std::unique_ptr<TBuiltInParseables> builtInParseables(CreateBuiltInParseables(infoSink, source)); |
| |
| if (builtInParseables == nullptr) |
| return false; |
| |
| builtInParseables->initialize(*resources, version, profile, spvVersion, language); |
| InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, language, source, infoSink, symbolTable); |
| builtInParseables->identifyBuiltIns(version, profile, spvVersion, language, symbolTable, *resources); |
| |
| return true; |
| } |
| |
| // |
| // To do this on the fly, we want to leave the current state of our thread's |
| // pool allocator intact, so: |
| // - Switch to a new pool for parsing the built-ins |
| // - Do the parsing, which builds the symbol table, using the new pool |
| // - Switch to the process-global pool to save a copy of the resulting symbol table |
| // - Free up the new pool used to parse the built-ins |
| // - Switch back to the original thread's pool |
| // |
| // This only gets done the first time any thread needs a particular symbol table |
| // (lazy evaluation). |
| // |
| void SetupBuiltinSymbolTable(int version, EProfile profile, const SpvVersion& spvVersion, EShSource source) |
| { |
| TInfoSink infoSink; |
| |
| // Make sure only one thread tries to do this at a time |
| glslang::GetGlobalLock(); |
| |
| // See if it's already been done for this version/profile combination |
| int versionIndex = MapVersionToIndex(version); |
| int spvVersionIndex = MapSpvVersionToIndex(spvVersion); |
| int profileIndex = MapProfileToIndex(profile); |
| int sourceIndex = MapSourceToIndex(source); |
| if (CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][EPcGeneral]) { |
| glslang::ReleaseGlobalLock(); |
| |
| return; |
| } |
| |
| // Switch to a new pool |
| TPoolAllocator& previousAllocator = GetThreadPoolAllocator(); |
| TPoolAllocator* builtInPoolAllocator = new TPoolAllocator; |
| SetThreadPoolAllocator(builtInPoolAllocator); |
| |
| // Dynamically allocate the local symbol tables so we can control when they are deallocated WRT when the pool is popped. |
| TSymbolTable* commonTable[EPcCount]; |
| TSymbolTable* stageTables[EShLangCount]; |
| for (int precClass = 0; precClass < EPcCount; ++precClass) |
| commonTable[precClass] = new TSymbolTable; |
| for (int stage = 0; stage < EShLangCount; ++stage) |
| stageTables[stage] = new TSymbolTable; |
| |
| // Generate the local symbol tables using the new pool |
| InitializeSymbolTables(infoSink, commonTable, stageTables, version, profile, spvVersion, source); |
| |
| // Switch to the process-global pool |
| SetThreadPoolAllocator(PerProcessGPA); |
| |
| // Copy the local symbol tables from the new pool to the global tables using the process-global pool |
| for (int precClass = 0; precClass < EPcCount; ++precClass) { |
| if (! commonTable[precClass]->isEmpty()) { |
| CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass] = new TSymbolTable; |
| CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass]->copyTable(*commonTable[precClass]); |
| CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass]->readOnly(); |
| } |
| } |
| for (int stage = 0; stage < EShLangCount; ++stage) { |
| if (! stageTables[stage]->isEmpty()) { |
| SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage] = new TSymbolTable; |
| SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->adoptLevels(*CommonSymbolTable |
| [versionIndex][spvVersionIndex][profileIndex][sourceIndex][CommonIndex(profile, (EShLanguage)stage)]); |
| SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->copyTable(*stageTables[stage]); |
| SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->readOnly(); |
| } |
| } |
| |
| // Clean up the local tables before deleting the pool they used. |
| for (int precClass = 0; precClass < EPcCount; ++precClass) |
| delete commonTable[precClass]; |
| for (int stage = 0; stage < EShLangCount; ++stage) |
| delete stageTables[stage]; |
| |
| delete builtInPoolAllocator; |
| SetThreadPoolAllocator(&previousAllocator); |
| |
| glslang::ReleaseGlobalLock(); |
| } |
| |
| // Function to Print all builtins |
| void DumpBuiltinSymbolTable(TInfoSink& infoSink, const TSymbolTable& symbolTable) |
| { |
| #ifndef GLSLANG_WEB |
| infoSink.debug << "BuiltinSymbolTable {\n"; |
| |
| symbolTable.dump(infoSink, true); |
| |
| infoSink.debug << "}\n"; |
| #endif |
| } |
| |
| // Return true if the shader was correctly specified for version/profile/stage. |
| bool DeduceVersionProfile(TInfoSink& infoSink, EShLanguage stage, bool versionNotFirst, int defaultVersion, |
| EShSource source, int& version, EProfile& profile, const SpvVersion& spvVersion) |
| { |
| const int FirstProfileVersion = 150; |
| bool correct = true; |
| |
| if (source == EShSourceHlsl) { |
| version = 500; // shader model; currently a characteristic of glslang, not the input |
| profile = ECoreProfile; // allow doubles in prototype parsing |
| return correct; |
| } |
| |
| // Get a version... |
| if (version == 0) { |
| version = defaultVersion; |
| // infoSink.info.message(EPrefixWarning, "#version: statement missing; use #version on first line of shader"); |
| } |
| |
| // Get a good profile... |
| if (profile == ENoProfile) { |
| if (version == 300 || version == 310 || version == 320) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: versions 300, 310, and 320 require specifying the 'es' profile"); |
| profile = EEsProfile; |
| } else if (version == 100) |
| profile = EEsProfile; |
| else if (version >= FirstProfileVersion) |
| profile = ECoreProfile; |
| else |
| profile = ENoProfile; |
| } else { |
| // a profile was provided... |
| if (version < 150) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: versions before 150 do not allow a profile token"); |
| if (version == 100) |
| profile = EEsProfile; |
| else |
| profile = ENoProfile; |
| } else if (version == 300 || version == 310 || version == 320) { |
| if (profile != EEsProfile) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: versions 300, 310, and 320 support only the es profile"); |
| } |
| profile = EEsProfile; |
| } else { |
| if (profile == EEsProfile) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: only version 300, 310, and 320 support the es profile"); |
| if (version >= FirstProfileVersion) |
| profile = ECoreProfile; |
| else |
| profile = ENoProfile; |
| } |
| // else: typical desktop case... e.g., "#version 410 core" |
| } |
| } |
| |
| // Fix version... |
| switch (version) { |
| // ES versions |
| case 100: break; |
| case 300: break; |
| case 310: break; |
| case 320: break; |
| |
| // desktop versions |
| case 110: break; |
| case 120: break; |
| case 130: break; |
| case 140: break; |
| case 150: break; |
| case 330: break; |
| case 400: break; |
| case 410: break; |
| case 420: break; |
| case 430: break; |
| case 440: break; |
| case 450: break; |
| case 460: break; |
| |
| // unknown version |
| default: |
| correct = false; |
| infoSink.info.message(EPrefixError, "version not supported"); |
| if (profile == EEsProfile) |
| version = 310; |
| else { |
| version = 450; |
| profile = ECoreProfile; |
| } |
| break; |
| } |
| |
| #ifndef GLSLANG_WEB |
| // Correct for stage type... |
| switch (stage) { |
| case EShLangGeometry: |
| if ((profile == EEsProfile && version < 310) || |
| (profile != EEsProfile && version < 150)) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: geometry shaders require es profile with version 310 or non-es profile with version 150 or above"); |
| version = (profile == EEsProfile) ? 310 : 150; |
| if (profile == EEsProfile || profile == ENoProfile) |
| profile = ECoreProfile; |
| } |
| break; |
| case EShLangTessControl: |
| case EShLangTessEvaluation: |
| if ((profile == EEsProfile && version < 310) || |
| (profile != EEsProfile && version < 150)) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: tessellation shaders require es profile with version 310 or non-es profile with version 150 or above"); |
| version = (profile == EEsProfile) ? 310 : 400; // 150 supports the extension, correction is to 400 which does not |
| if (profile == EEsProfile || profile == ENoProfile) |
| profile = ECoreProfile; |
| } |
| break; |
| case EShLangCompute: |
| if ((profile == EEsProfile && version < 310) || |
| (profile != EEsProfile && version < 420)) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: compute shaders require es profile with version 310 or above, or non-es profile with version 420 or above"); |
| version = profile == EEsProfile ? 310 : 420; |
| } |
| break; |
| case EShLangRayGen: |
| case EShLangIntersect: |
| case EShLangAnyHit: |
| case EShLangClosestHit: |
| case EShLangMiss: |
| case EShLangCallable: |
| if (profile == EEsProfile || version < 460) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: ray tracing shaders require non-es profile with version 460 or above"); |
| version = 460; |
| } |
| break; |
| case EShLangMeshNV: |
| case EShLangTaskNV: |
| if ((profile == EEsProfile && version < 320) || |
| (profile != EEsProfile && version < 450)) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: mesh/task shaders require es profile with version 320 or above, or non-es profile with version 450 or above"); |
| version = profile == EEsProfile ? 320 : 450; |
| } |
| default: |
| break; |
| } |
| |
| if (profile == EEsProfile && version >= 300 && versionNotFirst) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: statement must appear first in es-profile shader; before comments or newlines"); |
| } |
| |
| // Check for SPIR-V compatibility |
| if (spvVersion.spv != 0) { |
| switch (profile) { |
| case EEsProfile: |
| if (version < 310) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: ES shaders for SPIR-V require version 310 or higher"); |
| version = 310; |
| } |
| break; |
| case ECompatibilityProfile: |
| infoSink.info.message(EPrefixError, "#version: compilation for SPIR-V does not support the compatibility profile"); |
| break; |
| default: |
| if (spvVersion.vulkan > 0 && version < 140) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: Desktop shaders for Vulkan SPIR-V require version 140 or higher"); |
| version = 140; |
| } |
| if (spvVersion.openGl >= 100 && version < 330) { |
| correct = false; |
| infoSink.info.message(EPrefixError, "#version: Desktop shaders for OpenGL SPIR-V require version 330 or higher"); |
| version = 330; |
| } |
| break; |
| } |
| } |
| #endif |
| |
| return correct; |
| } |
| |
| // There are multiple paths in for setting environment stuff. |
| // TEnvironment takes precedence, for what it sets, so sort all this out. |
| // Ideally, the internal code could be made to use TEnvironment, but for |
| // now, translate it to the historically used parameters. |
| void TranslateEnvironment(const TEnvironment* environment, EShMessages& messages, EShSource& source, |
| EShLanguage& stage, SpvVersion& spvVersion) |
| { |
| // Set up environmental defaults, first ignoring 'environment'. |
| if (messages & EShMsgSpvRules) |
| spvVersion.spv = EShTargetSpv_1_0; |
| if (messages & EShMsgVulkanRules) { |
| spvVersion.vulkan = EShTargetVulkan_1_0; |
| spvVersion.vulkanGlsl = 100; |
| } else if (spvVersion.spv != 0) |
| spvVersion.openGl = 100; |
| |
| // Now, override, based on any content set in 'environment'. |
| // 'environment' must be cleared to ESh*None settings when items |
| // are not being set. |
| if (environment != nullptr) { |
| // input language |
| if (environment->input.languageFamily != EShSourceNone) { |
| stage = environment->input.stage; |
| switch (environment->input.dialect) { |
| case EShClientNone: |
| break; |
| case EShClientVulkan: |
| spvVersion.vulkanGlsl = environment->input.dialectVersion; |
| break; |
| case EShClientOpenGL: |
| spvVersion.openGl = environment->input.dialectVersion; |
| break; |
| case EShClientCount: |
| assert(0); |
| break; |
| } |
| switch (environment->input.languageFamily) { |
| case EShSourceNone: |
| break; |
| case EShSourceGlsl: |
| source = EShSourceGlsl; |
| messages = static_cast<EShMessages>(messages & ~EShMsgReadHlsl); |
| break; |
| case EShSourceHlsl: |
| source = EShSourceHlsl; |
| messages = static_cast<EShMessages>(messages | EShMsgReadHlsl); |
| break; |
| case EShSourceCount: |
| assert(0); |
| break; |
| } |
| } |
| |
| // client |
| switch (environment->client.client) { |
| case EShClientVulkan: |
| spvVersion.vulkan = environment->client.version; |
| break; |
| default: |
| break; |
| } |
| |
| // generated code |
| switch (environment->target.language) { |
| case EshTargetSpv: |
| spvVersion.spv = environment->target.version; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| // Most processes are recorded when set in the intermediate representation, |
| // These are the few that are not. |
| void RecordProcesses(TIntermediate& intermediate, EShMessages messages, const std::string& sourceEntryPointName) |
| { |
| if ((messages & EShMsgRelaxedErrors) != 0) |
| intermediate.addProcess("relaxed-errors"); |
| if ((messages & EShMsgSuppressWarnings) != 0) |
| intermediate.addProcess("suppress-warnings"); |
| if ((messages & EShMsgKeepUncalled) != 0) |
| intermediate.addProcess("keep-uncalled"); |
| if (sourceEntryPointName.size() > 0) { |
| intermediate.addProcess("source-entrypoint"); |
| intermediate.addProcessArgument(sourceEntryPointName); |
| } |
| } |
| |
| // This is the common setup and cleanup code for PreprocessDeferred and |
| // CompileDeferred. |
| // It takes any callable with a signature of |
| // bool (TParseContextBase& parseContext, TPpContext& ppContext, |
| // TInputScanner& input, bool versionWillBeError, |
| // TSymbolTable& , TIntermediate& , |
| // EShOptimizationLevel , EShMessages ); |
| // Which returns false if a failure was detected and true otherwise. |
| // |
| template<typename ProcessingContext> |
| bool ProcessDeferred( |
| TCompiler* compiler, |
| const char* const shaderStrings[], |
| const int numStrings, |
| const int* inputLengths, |
| const char* const stringNames[], |
| const char* customPreamble, |
| const EShOptimizationLevel optLevel, |
| const TBuiltInResource* resources, |
| int defaultVersion, // use 100 for ES environment, 110 for desktop; this is the GLSL version, not SPIR-V or Vulkan |
| EProfile defaultProfile, |
| // set version/profile to defaultVersion/defaultProfile regardless of the #version |
| // directive in the source code |
| bool forceDefaultVersionAndProfile, |
| bool forwardCompatible, // give errors for use of deprecated features |
| EShMessages messages, // warnings/errors/AST; things to print out |
| TIntermediate& intermediate, // returned tree, etc. |
| ProcessingContext& processingContext, |
| bool requireNonempty, |
| TShader::Includer& includer, |
| const std::string sourceEntryPointName = "", |
| const TEnvironment* environment = nullptr) // optional way of fully setting all versions, overriding the above |
| { |
| // This must be undone (.pop()) by the caller, after it finishes consuming the created tree. |
| GetThreadPoolAllocator().push(); |
| |
| if (numStrings == 0) |
| return true; |
| |
| // Move to length-based strings, rather than null-terminated strings. |
| // Also, add strings to include the preamble and to ensure the shader is not null, |
| // which lets the grammar accept what was a null (post preprocessing) shader. |
| // |
| // Shader will look like |
| // string 0: system preamble |
| // string 1: custom preamble |
| // string 2...numStrings+1: user's shader |
| // string numStrings+2: "int;" |
| const int numPre = 2; |
| const int numPost = requireNonempty? 1 : 0; |
| const int numTotal = numPre + numStrings + numPost; |
| std::unique_ptr<size_t[]> lengths(new size_t[numTotal]); |
| std::unique_ptr<const char*[]> strings(new const char*[numTotal]); |
| std::unique_ptr<const char*[]> names(new const char*[numTotal]); |
| for (int s = 0; s < numStrings; ++s) { |
| strings[s + numPre] = shaderStrings[s]; |
| if (inputLengths == nullptr || inputLengths[s] < 0) |
| lengths[s + numPre] = strlen(shaderStrings[s]); |
| else |
| lengths[s + numPre] = inputLengths[s]; |
| } |
| if (stringNames != nullptr) { |
| for (int s = 0; s < numStrings; ++s) |
| names[s + numPre] = stringNames[s]; |
| } else { |
| for (int s = 0; s < numStrings; ++s) |
| names[s + numPre] = nullptr; |
| } |
| |
| // Get all the stages, languages, clients, and other environment |
| // stuff sorted out. |
| EShSource sourceGuess = (messages & EShMsgReadHlsl) != 0 ? EShSourceHlsl : EShSourceGlsl; |
| SpvVersion spvVersion; |
| EShLanguage stage = compiler->getLanguage(); |
| TranslateEnvironment(environment, messages, sourceGuess, stage, spvVersion); |
| #ifdef ENABLE_HLSL |
| EShSource source = sourceGuess; |
| if (environment != nullptr && environment->target.hlslFunctionality1) |
| intermediate.setHlslFunctionality1(); |
| #else |
| const EShSource source = EShSourceGlsl; |
| #endif |
| // First, without using the preprocessor or parser, find the #version, so we know what |
| // symbol tables, processing rules, etc. to set up. This does not need the extra strings |
| // outlined above, just the user shader, after the system and user preambles. |
| glslang::TInputScanner userInput(numStrings, &strings[numPre], &lengths[numPre]); |
| int version = 0; |
| EProfile profile = ENoProfile; |
| bool versionNotFirstToken = false; |
| bool versionNotFirst = (source == EShSourceHlsl) |
| ? true |
| : userInput.scanVersion(version, profile, versionNotFirstToken); |
| bool versionNotFound = version == 0; |
| if (forceDefaultVersionAndProfile && source == EShSourceGlsl) { |
| #ifndef GLSLANG_WEB |
| if (! (messages & EShMsgSuppressWarnings) && ! versionNotFound && |
| (version != defaultVersion || profile != defaultProfile)) { |
| compiler->infoSink.info << "Warning, (version, profile) forced to be (" |
| << defaultVersion << ", " << ProfileName(defaultProfile) |
| << "), while in source code it is (" |
| << version << ", " << ProfileName(profile) << ")\n"; |
| } |
| #endif |
| if (versionNotFound) { |
| versionNotFirstToken = false; |
| versionNotFirst = false; |
| versionNotFound = false; |
| } |
| version = defaultVersion; |
| profile = defaultProfile; |
| } |
| |
| bool goodVersion = DeduceVersionProfile(compiler->infoSink, stage, |
| versionNotFirst, defaultVersion, source, version, profile, spvVersion); |
| #ifdef GLSLANG_WEB |
| profile = EEsProfile; |
| version = 310; |
| #endif |
| |
| bool versionWillBeError = (versionNotFound || (profile == EEsProfile && version >= 300 && versionNotFirst)); |
| #ifndef GLSLANG_WEB |
| bool warnVersionNotFirst = false; |
| if (! versionWillBeError && versionNotFirstToken) { |
| if (messages & EShMsgRelaxedErrors) |
| warnVersionNotFirst = true; |
| else |
| versionWillBeError = true; |
| } |
| #endif |
| |
| intermediate.setSource(source); |
| intermediate.setVersion(version); |
| intermediate.setProfile(profile); |
| intermediate.setSpv(spvVersion); |
| RecordProcesses(intermediate, messages, sourceEntryPointName); |
| if (spvVersion.vulkan > 0) |
| intermediate.setOriginUpperLeft(); |
| #ifdef ENABLE_HLSL |
| if ((messages & EShMsgHlslOffsets) || source == EShSourceHlsl) |
| intermediate.setHlslOffsets(); |
| #endif |
| if (messages & EShMsgDebugInfo) { |
| intermediate.setSourceFile(names[numPre]); |
| for (int s = 0; s < numStrings; ++s) { |
| // The string may not be null-terminated, so make sure we provide |
| // the length along with the string. |
| intermediate.addSourceText(strings[numPre + s], lengths[numPre + s]); |
| } |
| } |
| SetupBuiltinSymbolTable(version, profile, spvVersion, source); |
| |
| TSymbolTable* cachedTable = SharedSymbolTables[MapVersionToIndex(version)] |
| [MapSpvVersionToIndex(spvVersion)] |
| [MapProfileToIndex(profile)] |
| [MapSourceToIndex(source)] |
| [stage]; |
| |
| // Dynamically allocate the symbol table so we can control when it is deallocated WRT the pool. |
| std::unique_ptr<TSymbolTable> symbolTable(new TSymbolTable); |
| if (cachedTable) |
| symbolTable->adoptLevels(*cachedTable); |
| |
| // Add built-in symbols that are potentially context dependent; |
| // they get popped again further down. |
| if (! AddContextSpecificSymbols(resources, compiler->infoSink, *symbolTable, version, profile, spvVersion, |
| stage, source)) { |
| return false; |
| } |
| |
| if (messages & EShMsgBuiltinSymbolTable) |
| DumpBuiltinSymbolTable(compiler->infoSink, *symbolTable); |
| |
| // |
| // Now we can process the full shader under proper symbols and rules. |
| // |
| |
| std::unique_ptr<TParseContextBase> parseContext(CreateParseContext(*symbolTable, intermediate, version, profile, source, |
| stage, compiler->infoSink, |
| spvVersion, forwardCompatible, messages, false, sourceEntryPointName)); |
| TPpContext ppContext(*parseContext, names[numPre] ? names[numPre] : "", includer); |
| |
| // only GLSL (bison triggered, really) needs an externally set scan context |
| glslang::TScanContext scanContext(*parseContext); |
| if (source == EShSourceGlsl) |
| parseContext->setScanContext(&scanContext); |
| |
| parseContext->setPpContext(&ppContext); |
| parseContext->setLimits(*resources); |
| if (! goodVersion) |
| parseContext->addError(); |
| #ifndef GLSLANG_WEB |
| if (warnVersionNotFirst) { |
| TSourceLoc loc; |
| loc.init(); |
| parseContext->warn(loc, "Illegal to have non-comment, non-whitespace tokens before #version", "#version", ""); |
| } |
| #endif |
| |
| parseContext->initializeExtensionBehavior(); |
| |
| // Fill in the strings as outlined above. |
| std::string preamble; |
| parseContext->getPreamble(preamble); |
| strings[0] = preamble.c_str(); |
| lengths[0] = strlen(strings[0]); |
| names[0] = nullptr; |
| strings[1] = customPreamble; |
| lengths[1] = strlen(strings[1]); |
| names[1] = nullptr; |
| assert(2 == numPre); |
| if (requireNonempty) { |
| const int postIndex = numStrings + numPre; |
| strings[postIndex] = "\n int;"; |
| lengths[postIndex] = strlen(strings[numStrings + numPre]); |
| names[postIndex] = nullptr; |
| } |
| TInputScanner fullInput(numStrings + numPre + numPost, strings.get(), lengths.get(), names.get(), numPre, numPost); |
| |
| // Push a new symbol allocation scope that will get used for the shader's globals. |
| symbolTable->push(); |
| |
| bool success = processingContext(*parseContext, ppContext, fullInput, |
| versionWillBeError, *symbolTable, |
| intermediate, optLevel, messages); |
| return success; |
| } |
| |
| #ifndef GLSLANG_WEB |
| |
| // Responsible for keeping track of the most recent source string and line in |
| // the preprocessor and outputting newlines appropriately if the source string |
| // or line changes. |
| class SourceLineSynchronizer { |
| public: |
| SourceLineSynchronizer(const std::function<int()>& lastSourceIndex, |
| std::string* output) |
| : getLastSourceIndex(lastSourceIndex), output(output), lastSource(-1), lastLine(0) {} |
| // SourceLineSynchronizer(const SourceLineSynchronizer&) = delete; |
| // SourceLineSynchronizer& operator=(const SourceLineSynchronizer&) = delete; |
| |
| // Sets the internally tracked source string index to that of the most |
| // recently read token. If we switched to a new source string, returns |
| // true and inserts a newline. Otherwise, returns false and outputs nothing. |
| bool syncToMostRecentString() { |
| if (getLastSourceIndex() != lastSource) { |
| // After switching to a new source string, we need to reset lastLine |
| // because line number resets every time a new source string is |
| // used. We also need to output a newline to separate the output |
| // from the previous source string (if there is one). |
| if (lastSource != -1 || lastLine != 0) |
| *output += '\n'; |
| lastSource = getLastSourceIndex(); |
| lastLine = -1; |
| return true; |
| } |
| return false; |
| } |
| |
| // Calls syncToMostRecentString() and then sets the internally tracked line |
| // number to tokenLine. If we switched to a new line, returns true and inserts |
| // newlines appropriately. Otherwise, returns false and outputs nothing. |
| bool syncToLine(int tokenLine) { |
| syncToMostRecentString(); |
| const bool newLineStarted = lastLine < tokenLine; |
| for (; lastLine < tokenLine; ++lastLine) { |
| if (lastLine > 0) *output += '\n'; |
| } |
| return newLineStarted; |
| } |
| |
| // Sets the internally tracked line number to newLineNum. |
| void setLineNum(int newLineNum) { lastLine = newLineNum; } |
| |
| private: |
| SourceLineSynchronizer& operator=(const SourceLineSynchronizer&); |
| |
| // A function for getting the index of the last valid source string we've |
| // read tokens from. |
| const std::function<int()> getLastSourceIndex; |
| // output string for newlines. |
| std::string* output; |
| // lastSource is the source string index (starting from 0) of the last token |
| // processed. It is tracked in order for newlines to be inserted when a new |
| // source string starts. -1 means we haven't started processing any source |
| // string. |
| int lastSource; |
| // lastLine is the line number (starting from 1) of the last token processed. |
| // It is tracked in order for newlines to be inserted when a token appears |
| // on a new line. 0 means we haven't started processing any line in the |
| // current source string. |
| int lastLine; |
| }; |
| |
| // DoPreprocessing is a valid ProcessingContext template argument, |
| // which only performs the preprocessing step of compilation. |
| // It places the result in the "string" argument to its constructor. |
| // |
| // This is not an officially supported or fully working path. |
| struct DoPreprocessing { |
| explicit DoPreprocessing(std::string* string): outputString(string) {} |
| bool operator()(TParseContextBase& parseContext, TPpContext& ppContext, |
| TInputScanner& input, bool versionWillBeError, |
| TSymbolTable&, TIntermediate&, |
| EShOptimizationLevel, EShMessages) |
| { |
| // This is a list of tokens that do not require a space before or after. |
| static const std::string unNeededSpaceTokens = ";()[]"; |
| static const std::string noSpaceBeforeTokens = ","; |
| glslang::TPpToken ppToken; |
| |
| parseContext.setScanner(&input); |
| ppContext.setInput(input, versionWillBeError); |
| |
| std::string outputBuffer; |
| SourceLineSynchronizer lineSync( |
| std::bind(&TInputScanner::getLastValidSourceIndex, &input), &outputBuffer); |
| |
| parseContext.setExtensionCallback([&lineSync, &outputBuffer]( |
| int line, const char* extension, const char* behavior) { |
| lineSync.syncToLine(line); |
| outputBuffer += "#extension "; |
| outputBuffer += extension; |
| outputBuffer += " : "; |
| outputBuffer += behavior; |
| }); |
| |
| parseContext.setLineCallback([&lineSync, &outputBuffer, &parseContext]( |
| int curLineNum, int newLineNum, bool hasSource, int sourceNum, const char* sourceName) { |
| // SourceNum is the number of the source-string that is being parsed. |
| lineSync.syncToLine(curLineNum); |
| outputBuffer += "#line "; |
| outputBuffer += std::to_string(newLineNum); |
| if (hasSource) { |
| outputBuffer += ' '; |
| if (sourceName != nullptr) { |
| outputBuffer += '\"'; |
| outputBuffer += sourceName; |
| outputBuffer += '\"'; |
| } else { |
| outputBuffer += std::to_string(sourceNum); |
| } |
| } |
| if (parseContext.lineDirectiveShouldSetNextLine()) { |
| // newLineNum is the new line number for the line following the #line |
| // directive. So the new line number for the current line is |
| newLineNum -= 1; |
| } |
| outputBuffer += '\n'; |
| // And we are at the next line of the #line directive now. |
| lineSync.setLineNum(newLineNum + 1); |
| }); |
| |
| parseContext.setVersionCallback( |
| [&lineSync, &outputBuffer](int line, int version, const char* str) { |
| lineSync.syncToLine(line); |
| outputBuffer += "#version "; |
| outputBuffer += std::to_string(version); |
| if (str) { |
| outputBuffer += ' '; |
| outputBuffer += str; |
| } |
| }); |
| |
| parseContext.setPragmaCallback([&lineSync, &outputBuffer]( |
| int line, const glslang::TVector<glslang::TString>& ops) { |
| lineSync.syncToLine(line); |
| outputBuffer += "#pragma "; |
| for(size_t i = 0; i < ops.size(); ++i) { |
| outputBuffer += ops[i].c_str(); |
| } |
| }); |
| |
| parseContext.setErrorCallback([&lineSync, &outputBuffer]( |
| int line, const char* errorMessage) { |
| lineSync.syncToLine(line); |
| outputBuffer += "#error "; |
| outputBuffer += errorMessage; |
| }); |
| |
| int lastToken = EndOfInput; // lastToken records the last token processed. |
| do { |
| int token = ppContext.tokenize(ppToken); |
| if (token == EndOfInput) |
| break; |
| |
| bool isNewString = lineSync.syncToMostRecentString(); |
| bool isNewLine = lineSync.syncToLine(ppToken.loc.line); |
| |
| if (isNewLine) { |
| // Don't emit whitespace onto empty lines. |
| // Copy any whitespace characters at the start of a line |
| // from the input to the output. |
| outputBuffer += std::string(ppToken.loc.column - 1, ' '); |
| } |
| |
| // Output a space in between tokens, but not at the start of a line, |
| // and also not around special tokens. This helps with readability |
| // and consistency. |
| if (!isNewString && !isNewLine && lastToken != EndOfInput && |
| (unNeededSpaceTokens.find((char)token) == std::string::npos) && |
| (unNeededSpaceTokens.find((char)lastToken) == std::string::npos) && |
| (noSpaceBeforeTokens.find((char)token) == std::string::npos)) { |
| outputBuffer += ' '; |
| } |
| lastToken = token; |
| if (token == PpAtomConstString) |
| outputBuffer += "\""; |
| outputBuffer += ppToken.name; |
| if (token == PpAtomConstString) |
| outputBuffer += "\""; |
| } while (true); |
| outputBuffer += '\n'; |
| *outputString = std::move(outputBuffer); |
| |
| bool success = true; |
| if (parseContext.getNumErrors() > 0) { |
| success = false; |
| parseContext.infoSink.info.prefix(EPrefixError); |
| parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors. No code generated.\n\n"; |
| } |
| return success; |
| } |
| std::string* outputString; |
| }; |
| |
| #endif |
| |
| // DoFullParse is a valid ProcessingConext template argument for fully |
| // parsing the shader. It populates the "intermediate" with the AST. |
| struct DoFullParse{ |
| bool operator()(TParseContextBase& parseContext, TPpContext& ppContext, |
| TInputScanner& fullInput, bool versionWillBeError, |
| TSymbolTable&, TIntermediate& intermediate, |
| EShOptimizationLevel optLevel, EShMessages messages) |
| { |
| bool success = true; |
| // Parse the full shader. |
| if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError)) |
| success = false; |
| |
| if (success && intermediate.getTreeRoot()) { |
| if (optLevel == EShOptNoGeneration) |
| parseContext.infoSink.info.message(EPrefixNone, "No errors. No code generation or linking was requested."); |
| else |
| success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.getLanguage()); |
| } else if (! success) { |
| parseContext.infoSink.info.prefix(EPrefixError); |
| parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors. No code generated.\n\n"; |
| } |
| |
| if (messages & EShMsgAST) |
| intermediate.output(parseContext.infoSink, true); |
| |
| return success; |
| } |
| }; |
| |
| #ifndef GLSLANG_WEB |
| // Take a single compilation unit, and run the preprocessor on it. |
| // Return: True if there were no issues found in preprocessing, |
| // False if during preprocessing any unknown version, pragmas or |
| // extensions were found. |
| // |
| // NOTE: Doing just preprocessing to obtain a correct preprocessed shader string |
| // is not an officially supported or fully working path. |
| bool PreprocessDeferred( |
| TCompiler* compiler, |
| const char* const shaderStrings[], |
| const int numStrings, |
| const int* inputLengths, |
| const char* const stringNames[], |
| const char* preamble, |
| const EShOptimizationLevel optLevel, |
| const TBuiltInResource* resources, |
| int defaultVersion, // use 100 for ES environment, 110 for desktop |
| EProfile defaultProfile, |
| bool forceDefaultVersionAndProfile, |
| bool forwardCompatible, // give errors for use of deprecated features |
| EShMessages messages, // warnings/errors/AST; things to print out |
| TShader::Includer& includer, |
| TIntermediate& intermediate, // returned tree, etc. |
| std::string* outputString) |
| { |
| DoPreprocessing parser(outputString); |
| return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, stringNames, |
| preamble, optLevel, resources, defaultVersion, |
| defaultProfile, forceDefaultVersionAndProfile, |
| forwardCompatible, messages, intermediate, parser, |
| false, includer); |
| } |
| #endif |
| |
| // |
| // do a partial compile on the given strings for a single compilation unit |
| // for a potential deferred link into a single stage (and deferred full compile of that |
| // stage through machine-dependent compilation). |
| // |
| // all preprocessing, parsing, semantic checks, etc. for a single compilation unit |
| // are done here. |
| // |
| // return: the tree and other information is filled into the intermediate argument, |
| // and true is returned by the function for success. |
| // |
| bool CompileDeferred( |
| TCompiler* compiler, |
| const char* const shaderStrings[], |
| const int numStrings, |
| const int* inputLengths, |
| const char* const stringNames[], |
| const char* preamble, |
| const EShOptimizationLevel optLevel, |
| const TBuiltInResource* resources, |
| int defaultVersion, // use 100 for ES environment, 110 for desktop |
| EProfile defaultProfile, |
| bool forceDefaultVersionAndProfile, |
| bool forwardCompatible, // give errors for use of deprecated features |
| EShMessages messages, // warnings/errors/AST; things to print out |
| TIntermediate& intermediate,// returned tree, etc. |
| TShader::Includer& includer, |
| const std::string sourceEntryPointName = "", |
| TEnvironment* environment = nullptr) |
| { |
| DoFullParse parser; |
| return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, stringNames, |
| preamble, optLevel, resources, defaultVersion, |
| defaultProfile, forceDefaultVersionAndProfile, |
| forwardCompatible, messages, intermediate, parser, |
| true, includer, sourceEntryPointName, environment); |
| } |
| |
| } // end anonymous namespace for local functions |
| |
| // |
| // ShInitialize() should be called exactly once per process, not per thread. |
| // |
| int ShInitialize() |
| { |
| glslang::InitGlobalLock(); |
| |
| if (! InitProcess()) |
| return 0; |
| |
| glslang::GetGlobalLock(); |
| ++NumberOfClients; |
| glslang::ReleaseGlobalLock(); |
| |
| if (PerProcessGPA == nullptr) |
| PerProcessGPA = new TPoolAllocator(); |
| |
| glslang::TScanContext::fillInKeywordMap(); |
| #ifdef ENABLE_HLSL |
| glslang::HlslScanContext::fillInKeywordMap(); |
| #endif |
| |
| return 1; |
| } |
| |
| // |
| // Driver calls these to create and destroy compiler/linker |
| // objects. |
| // |
| |
| ShHandle ShConstructCompiler(const EShLanguage language, int debugOptions) |
| { |
| if (!InitThread()) |
| return 0; |
| |
| TShHandleBase* base = static_cast<TShHandleBase*>(ConstructCompiler(language, debugOptions)); |
| |
| return reinterpret_cast<void*>(base); |
| } |
| |
| ShHandle ShConstructLinker(const EShExecutable executable, int debugOptions) |
| { |
| if (!InitThread()) |
| return 0; |
| |
| TShHandleBase* base = static_cast<TShHandleBase*>(ConstructLinker(executable, debugOptions)); |
| |
| return reinterpret_cast<void*>(base); |
| } |
| |
| ShHandle ShConstructUniformMap() |
| { |
| if (!InitThread()) |
| return 0; |
| |
| TShHandleBase* base = static_cast<TShHandleBase*>(ConstructUniformMap()); |
| |
| return reinterpret_cast<void*>(base); |
| } |
| |
| void ShDestruct(ShHandle handle) |
| { |
| if (handle == 0) |
| return; |
| |
| TShHandleBase* base = static_cast<TShHandleBase*>(handle); |
| |
| if (base->getAsCompiler()) |
| DeleteCompiler(base->getAsCompiler()); |
| else if (base->getAsLinker()) |
| DeleteLinker(base->getAsLinker()); |
| else if (base->getAsUniformMap()) |
| DeleteUniformMap(base->getAsUniformMap()); |
| } |
| |
| // |
| // Cleanup symbol tables |
| // |
| int ShFinalize() |
| { |
| glslang::GetGlobalLock(); |
| --NumberOfClients; |
| assert(NumberOfClients >= 0); |
| bool finalize = NumberOfClients == 0; |
| glslang::ReleaseGlobalLock(); |
| if (! finalize) |
| return 1; |
| |
| for (int version = 0; version < VersionCount; ++version) { |
| for (int spvVersion = 0; spvVersion < SpvVersionCount; ++spvVersion) { |
| for (int p = 0; p < ProfileCount; ++p) { |
| for (int source = 0; source < SourceCount; ++source) { |
| for (int stage = 0; stage < EShLangCount; ++stage) { |
| delete SharedSymbolTables[version][spvVersion][p][source][stage]; |
| SharedSymbolTables[version][spvVersion][p][source][stage] = 0; |
| } |
| } |
| } |
| } |
| } |
| |
| for (int version = 0; version < VersionCount; ++version) { |
| for (int spvVersion = 0; spvVersion < SpvVersionCount; ++spvVersion) { |
| for (int p = 0; p < ProfileCount; ++p) { |
| for (int source = 0; source < SourceCount; ++source) { |
| for (int pc = 0; pc < EPcCount; ++pc) { |
| delete CommonSymbolTable[version][spvVersion][p][source][pc]; |
| CommonSymbolTable[version][spvVersion][p][source][pc] = 0; |
| } |
| } |
| } |
| } |
| } |
| |
| if (PerProcessGPA != nullptr) { |
| delete PerProcessGPA; |
| PerProcessGPA = nullptr; |
| } |
| |
| glslang::TScanContext::deleteKeywordMap(); |
| #ifdef ENABLE_HLSL |
| glslang::HlslScanContext::deleteKeywordMap(); |
| #endif |
| |
| return 1; |
| } |
| |
| // |
| // Do a full compile on the given strings for a single compilation unit |
| // forming a complete stage. The result of the machine dependent compilation |
| // is left in the provided compile object. |
| // |
| // Return: The return value is really boolean, indicating |
| // success (1) or failure (0). |
| // |
| int ShCompile( |
| const ShHandle handle, |
| const char* const shaderStrings[], |
| const int numStrings, |
| const int* inputLengths, |
| const EShOptimizationLevel optLevel, |
| const TBuiltInResource* resources, |
| int /*debugOptions*/, |
| int defaultVersion, // use 100 for ES environment, 110 for desktop |
| bool forwardCompatible, // give errors for use of deprecated features |
| EShMessages messages // warnings/errors/AST; things to print out |
| ) |
| { |
| // Map the generic handle to the C++ object |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| TCompiler* compiler = base->getAsCompiler(); |
| if (compiler == 0) |
| return 0; |
| |
| SetThreadPoolAllocator(compiler->getPool()); |
| |
| compiler->infoSink.info.erase(); |
| compiler->infoSink.debug.erase(); |
| |
| TIntermediate intermediate(compiler->getLanguage()); |
| TShader::ForbidIncluder includer; |
| bool success = CompileDeferred(compiler, shaderStrings, numStrings, inputLengths, nullptr, |
| "", optLevel, resources, defaultVersion, ENoProfile, false, |
| forwardCompatible, messages, intermediate, includer); |
| |
| // |
| // Call the machine dependent compiler |
| // |
| if (success && intermediate.getTreeRoot() && optLevel != EShOptNoGeneration) |
| success = compiler->compile(intermediate.getTreeRoot(), intermediate.getVersion(), intermediate.getProfile()); |
| |
| intermediate.removeTree(); |
| |
| // Throw away all the temporary memory used by the compilation process. |
| // The push was done in the CompileDeferred() call above. |
| GetThreadPoolAllocator().pop(); |
| |
| return success ? 1 : 0; |
| } |
| |
| // |
| // Link the given compile objects. |
| // |
| // Return: The return value of is really boolean, indicating |
| // success or failure. |
| // |
| int ShLinkExt( |
| const ShHandle linkHandle, |
| const ShHandle compHandles[], |
| const int numHandles) |
| { |
| if (linkHandle == 0 || numHandles == 0) |
| return 0; |
| |
| THandleList cObjects; |
| |
| for (int i = 0; i < numHandles; ++i) { |
| if (compHandles[i] == 0) |
| return 0; |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(compHandles[i]); |
| if (base->getAsLinker()) { |
| cObjects.push_back(base->getAsLinker()); |
| } |
| if (base->getAsCompiler()) |
| cObjects.push_back(base->getAsCompiler()); |
| |
| if (cObjects[i] == 0) |
| return 0; |
| } |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(linkHandle); |
| TLinker* linker = static_cast<TLinker*>(base->getAsLinker()); |
| |
| SetThreadPoolAllocator(linker->getPool()); |
| |
| if (linker == 0) |
| return 0; |
| |
| linker->infoSink.info.erase(); |
| |
| for (int i = 0; i < numHandles; ++i) { |
| if (cObjects[i]->getAsCompiler()) { |
| if (! cObjects[i]->getAsCompiler()->linkable()) { |
| linker->infoSink.info.message(EPrefixError, "Not all shaders have valid object code."); |
| return 0; |
| } |
| } |
| } |
| |
| bool ret = linker->link(cObjects); |
| |
| return ret ? 1 : 0; |
| } |
| |
| // |
| // ShSetEncrpytionMethod is a place-holder for specifying |
| // how source code is encrypted. |
| // |
| void ShSetEncryptionMethod(ShHandle handle) |
| { |
| if (handle == 0) |
| return; |
| } |
| |
| // |
| // Return any compiler/linker/uniformmap log of messages for the application. |
| // |
| const char* ShGetInfoLog(const ShHandle handle) |
| { |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = static_cast<TShHandleBase*>(handle); |
| TInfoSink* infoSink; |
| |
| if (base->getAsCompiler()) |
| infoSink = &(base->getAsCompiler()->getInfoSink()); |
| else if (base->getAsLinker()) |
| infoSink = &(base->getAsLinker()->getInfoSink()); |
| else |
| return 0; |
| |
| infoSink->info << infoSink->debug.c_str(); |
| return infoSink->info.c_str(); |
| } |
| |
| // |
| // Return the resulting binary code from the link process. Structure |
| // is machine dependent. |
| // |
| const void* ShGetExecutable(const ShHandle handle) |
| { |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| |
| TLinker* linker = static_cast<TLinker*>(base->getAsLinker()); |
| if (linker == 0) |
| return 0; |
| |
| return linker->getObjectCode(); |
| } |
| |
| // |
| // Let the linker know where the application said it's attributes are bound. |
| // The linker does not use these values, they are remapped by the ICD or |
| // hardware. It just needs them to know what's aliased. |
| // |
| // Return: The return value of is really boolean, indicating |
| // success or failure. |
| // |
| int ShSetVirtualAttributeBindings(const ShHandle handle, const ShBindingTable* table) |
| { |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| TLinker* linker = static_cast<TLinker*>(base->getAsLinker()); |
| |
| if (linker == 0) |
| return 0; |
| |
| linker->setAppAttributeBindings(table); |
| |
| return 1; |
| } |
| |
| // |
| // Let the linker know where the predefined attributes have to live. |
| // |
| int ShSetFixedAttributeBindings(const ShHandle handle, const ShBindingTable* table) |
| { |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| TLinker* linker = static_cast<TLinker*>(base->getAsLinker()); |
| |
| if (linker == 0) |
| return 0; |
| |
| linker->setFixedAttributeBindings(table); |
| return 1; |
| } |
| |
| // |
| // Some attribute locations are off-limits to the linker... |
| // |
| int ShExcludeAttributes(const ShHandle handle, int *attributes, int count) |
| { |
| if (handle == 0) |
| return 0; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| TLinker* linker = static_cast<TLinker*>(base->getAsLinker()); |
| if (linker == 0) |
| return 0; |
| |
| linker->setExcludedAttributes(attributes, count); |
| |
| return 1; |
| } |
| |
| // |
| // Return the index for OpenGL to use for knowing where a uniform lives. |
| // |
| // Return: The return value of is really boolean, indicating |
| // success or failure. |
| // |
| int ShGetUniformLocation(const ShHandle handle, const char* name) |
| { |
| if (handle == 0) |
| return -1; |
| |
| TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle); |
| TUniformMap* uniformMap= base->getAsUniformMap(); |
| if (uniformMap == 0) |
| return -1; |
| |
| return uniformMap->getLocation(name); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // Deferred-Lowering C++ Interface |
| // ----------------------------------- |
| // |
| // Below is a new alternate C++ interface that might potentially replace the above |
| // opaque handle-based interface. |
| // |
| // See more detailed comment in ShaderLang.h |
| // |
| |
| namespace glslang { |
| |
| #include "../Include/revision.h" |
| |
| #define QUOTE(s) #s |
| #define STR(n) QUOTE(n) |
| |
| const char* GetEsslVersionString() |
| { |
| return "OpenGL ES GLSL 3.20 glslang Khronos. " STR(GLSLANG_MINOR_VERSION) "." STR(GLSLANG_PATCH_LEVEL); |
| } |
| |
| const char* GetGlslVersionString() |
| { |
| return "4.60 glslang Khronos. " STR(GLSLANG_MINOR_VERSION) "." STR(GLSLANG_PATCH_LEVEL); |
| } |
| |
| int GetKhronosToolId() |
| { |
| return 8; |
| } |
| |
| bool InitializeProcess() |
| { |
| return ShInitialize() != 0; |
| } |
| |
| void FinalizeProcess() |
| { |
| ShFinalize(); |
| } |
| |
| class TDeferredCompiler : public TCompiler { |
| public: |
| TDeferredCompiler(EShLanguage s, TInfoSink& i) : TCompiler(s, i) { } |
| virtual bool compile(TIntermNode*, int = 0, EProfile = ENoProfile) { return true; } |
| }; |
| |
| TShader::TShader(EShLanguage s) |
| : stage(s), lengths(nullptr), stringNames(nullptr), preamble("") |
| { |
| pool = new TPoolAllocator; |
| infoSink = new TInfoSink; |
| compiler = new TDeferredCompiler(stage, *infoSink); |
| intermediate = new TIntermediate(s); |
| |
| // clear environment (avoid constructors in them for use in a C interface) |
| environment.input.languageFamily = EShSourceNone; |
| environment.input.dialect = EShClientNone; |
| environment.client.client = EShClientNone; |
| environment.target.language = EShTargetNone; |
| environment.target.hlslFunctionality1 = false; |
| } |
| |
| TShader::~TShader() |
| { |
| delete infoSink; |
| delete compiler; |
| delete intermediate; |
| delete pool; |
| } |
| |
| void TShader::setStrings(const char* const* s, int n) |
| { |
| strings = s; |
| numStrings = n; |
| lengths = nullptr; |
| } |
| |
| void TShader::setStringsWithLengths(const char* const* s, const int* l, int n) |
| { |
| strings = s; |
| numStrings = n; |
| lengths = l; |
| } |
| |
| void TShader::setStringsWithLengthsAndNames( |
| const char* const* s, const int* l, const char* const* names, int n) |
| { |
| strings = s; |
| numStrings = n; |
| lengths = l; |
| stringNames = names; |
| } |
| |
| void TShader::setEntryPoint(const char* entryPoint) |
| { |
| intermediate->setEntryPointName(entryPoint); |
| } |
| |
| void TShader::setSourceEntryPoint(const char* name) |
| { |
| sourceEntryPointName = name; |
| } |
| |
| // Log initial settings and transforms. |
| // See comment for class TProcesses. |
| void TShader::addProcesses(const std::vector<std::string>& p) |
| { |
| intermediate->addProcesses(p); |
| } |
| |
| void TShader::setInvertY(bool invert) { intermediate->setInvertY(invert); } |
| void TShader::setNanMinMaxClamp(bool useNonNan) { intermediate->setNanMinMaxClamp(useNonNan); } |
| |
| #ifndef GLSLANG_WEB |
| |
| // Set binding base for given resource type |
| void TShader::setShiftBinding(TResourceType res, unsigned int base) { |
| intermediate->setShiftBinding(res, base); |
| } |
| |
| // Set binding base for given resource type for a given binding set. |
| void TShader::setShiftBindingForSet(TResourceType res, unsigned int base, unsigned int set) { |
| intermediate->setShiftBindingForSet(res, base, set); |
| } |
| |
| // Set binding base for sampler types |
| void TShader::setShiftSamplerBinding(unsigned int base) { setShiftBinding(EResSampler, base); } |
| // Set binding base for texture types (SRV) |
| void TShader::setShiftTextureBinding(unsigned int base) { setShiftBinding(EResTexture, base); } |
| // Set binding base for image types |
| void TShader::setShiftImageBinding(unsigned int base) { setShiftBinding(EResImage, base); } |
| // Set binding base for uniform buffer objects (CBV) |
| void TShader::setShiftUboBinding(unsigned int base) { setShiftBinding(EResUbo, base); } |
| // Synonym for setShiftUboBinding, to match HLSL language. |
| void TShader::setShiftCbufferBinding(unsigned int base) { setShiftBinding(EResUbo, base); } |
| // Set binding base for UAV (unordered access view) |
| void TShader::setShiftUavBinding(unsigned int base) { setShiftBinding(EResUav, base); } |
| // Set binding base for SSBOs |
| void TShader::setShiftSsboBinding(unsigned int base) { setShiftBinding(EResSsbo, base); } |
| // Enables binding automapping using TIoMapper |
| void TShader::setAutoMapBindings(bool map) { intermediate->setAutoMapBindings(map); } |
| // Enables position.Y output negation in vertex shader |
| |
| // Fragile: currently within one stage: simple auto-assignment of location |
| void TShader::setAutoMapLocations(bool map) { intermediate->setAutoMapLocations(map); } |
| void TShader::addUniformLocationOverride(const char* name, int loc) |
| { |
| intermediate->addUniformLocationOverride(name, loc); |
| } |
| void TShader::setUniformLocationBase(int base) |
| { |
| intermediate->setUniformLocationBase(base); |
| } |
| void TShader::setNoStorageFormat(bool useUnknownFormat) { intermediate->setNoStorageFormat(useUnknownFormat); } |
| void TShader::setResourceSetBinding(const std::vector<std::string>& base) { intermediate->setResourceSetBinding(base); } |
| void TShader::setTextureSamplerTransformMode(EShTextureSamplerTransformMode mode) { intermediate->setTextureSamplerTransformMode(mode); } |
| #endif |
| |
| #ifdef ENABLE_HLSL |
| // See comment above TDefaultHlslIoMapper in iomapper.cpp: |
| void TShader::setHlslIoMapping(bool hlslIoMap) { intermediate->setHlslIoMapping(hlslIoMap); } |
| void TShader::setFlattenUniformArrays(bool flatten) { intermediate->setFlattenUniformArrays(flatten); } |
| #endif |
| |
| // |
| // Turn the shader strings into a parse tree in the TIntermediate. |
| // |
| // Returns true for success. |
| // |
| bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, |
| bool forwardCompatible, EShMessages messages, Includer& includer) |
| { |
| if (! InitThread()) |
| return false; |
| SetThreadPoolAllocator(pool); |
| |
| if (! preamble) |
| preamble = ""; |
| |
| return CompileDeferred(compiler, strings, numStrings, lengths, stringNames, |
| preamble, EShOptNone, builtInResources, defaultVersion, |
| defaultProfile, forceDefaultVersionAndProfile, |
| forwardCompatible, messages, *intermediate, includer, sourceEntryPointName, |
| &environment); |
| } |
| |
| #ifndef GLSLANG_WEB |
| // Fill in a string with the result of preprocessing ShaderStrings |
| // Returns true if all extensions, pragmas and version strings were valid. |
| // |
| // NOTE: Doing just preprocessing to obtain a correct preprocessed shader string |
| // is not an officially supported or fully working path. |
| bool TShader::preprocess(const TBuiltInResource* builtInResources, |
| int defaultVersion, EProfile defaultProfile, |
| bool forceDefaultVersionAndProfile, |
| bool forwardCompatible, EShMessages message, |
| std::string* output_string, |
| Includer& includer) |
| { |
| if (! InitThread()) |
| return false; |
| SetThreadPoolAllocator(pool); |
| |
| if (! preamble) |
| preamble = ""; |
| |
| return PreprocessDeferred(compiler, strings, numStrings, lengths, stringNames, preamble, |
| EShOptNone, builtInResources, defaultVersion, |
| defaultProfile, forceDefaultVersionAndProfile, |
| forwardCompatible, message, includer, *intermediate, output_string); |
| } |
| #endif |
| |
| const char* TShader::getInfoLog() |
| { |
| return infoSink->info.c_str(); |
| } |
| |
| const char* TShader::getInfoDebugLog() |
| { |
| return infoSink->debug.c_str(); |
| } |
| |
| TProgram::TProgram() : |
| #ifndef GLSLANG_WEB |
| reflection(0), |
| #endif |
| linked(false) |
| { |
| pool = new TPoolAllocator; |
| infoSink = new TInfoSink; |
| for (int s = 0; s < EShLangCount; ++s) { |
| intermediate[s] = 0; |
| newedIntermediate[s] = false; |
| } |
| } |
| |
| TProgram::~TProgram() |
| { |
| delete infoSink; |
| #ifndef GLSLANG_WEB |
| delete reflection; |
| #endif |
| |
| for (int s = 0; s < EShLangCount; ++s) |
| if (newedIntermediate[s]) |
| delete intermediate[s]; |
| |
| delete pool; |
| } |
| |
| // |
| // Merge the compilation units within each stage into a single TIntermediate. |
| // All starting compilation units need to be the result of calling TShader::parse(). |
| // |
| // Return true for success. |
| // |
| bool TProgram::link(EShMessages messages) |
| { |
| if (linked) |
| return false; |
| linked = true; |
| |
| bool error = false; |
| |
| SetThreadPoolAllocator(pool); |
| |
| for (int s = 0; s < EShLangCount; ++s) { |
| if (! linkStage((EShLanguage)s, messages)) |
| error = true; |
| } |
| |
| // TODO: Link: cross-stage error checking |
| |
| return ! error; |
| } |
| |
| // |
| // Merge the compilation units within the given stage into a single TIntermediate. |
| // |
| // Return true for success. |
| // |
| bool TProgram::linkStage(EShLanguage stage, EShMessages messages) |
| { |
| if (stages[stage].size() == 0) |
| return true; |
| |
| #ifndef GLSLANG_WEB |
| int numEsShaders = 0, numNonEsShaders = 0; |
| for (auto it = stages[stage].begin(); it != stages[stage].end(); ++it) { |
| if ((*it)->intermediate->getProfile() == EEsProfile) { |
| numEsShaders++; |
| } else { |
| numNonEsShaders++; |
| } |
| } |
| |
| if (numEsShaders > 0 && numNonEsShaders > 0) { |
| infoSink->info.message(EPrefixError, "Cannot mix ES profile with non-ES profile shaders"); |
| return false; |
| } else if (numEsShaders > 1) { |
| infoSink->info.message(EPrefixError, "Cannot attach multiple ES shaders of the same type to a single program"); |
| return false; |
| } |
| |
| // |
| // Be efficient for the common single compilation unit per stage case, |
| // reusing it's TIntermediate instead of merging into a new one. |
| // |
| TIntermediate *firstIntermediate = stages[stage].front()->intermediate; |
| if (stages[stage].size() == 1) |
| intermediate[stage] = firstIntermediate; |
| else { |
| intermediate[stage] = new TIntermediate(stage, |
| firstIntermediate->getVersion(), |
| firstIntermediate->getProfile()); |
| |
| |
| // The new TIntermediate must use the same origin as the original TIntermediates. |
| // Otherwise linking will fail due to different coordinate systems. |
| if (firstIntermediate->getOriginUpperLeft()) { |
| intermediate[stage]->setOriginUpperLeft(); |
| } |
| intermediate[stage]->setSpv(firstIntermediate->getSpv()); |
| |
| newedIntermediate[stage] = true; |
| } |
| |
| if (messages & EShMsgAST) |
| infoSink->info << "\nLinked " << StageName(stage) << " stage:\n\n"; |
| |
| if (stages[stage].size() > 1) { |
| std::list<TShader*>::const_iterator it; |
| for (it = stages[stage].begin(); it != stages[stage].end(); ++it) |
| intermediate[stage]->merge(*infoSink, *(*it)->intermediate); |
| } |
| #else |
| intermediate[stage] = stages[stage].front()->intermediate; |
| #endif |
| intermediate[stage]->finalCheck(*infoSink, (messages & EShMsgKeepUncalled) != 0); |
| |
| if (messages & EShMsgAST) |
| intermediate[stage]->output(*infoSink, true); |
| |
| return intermediate[stage]->getNumErrors() == 0; |
| } |
| |
| const char* TProgram::getInfoLog() |
| { |
| return infoSink->info.c_str(); |
| } |
| |
| const char* TProgram::getInfoDebugLog() |
| { |
| return infoSink->debug.c_str(); |
| } |
| |
| #ifndef GLSLANG_WEB |
| |
| // |
| // Reflection implementation. |
| // |
| |
| bool TProgram::buildReflection(int opts) |
| { |
| if (! linked || reflection != nullptr) |
| return false; |
| |
| int firstStage = EShLangVertex, lastStage = EShLangFragment; |
| |
| if (opts & EShReflectionIntermediateIO) { |
| // if we're reflecting intermediate I/O, determine the first and last stage linked and use those as the |
| // boundaries for which stages generate pipeline inputs/outputs |
| firstStage = EShLangCount; |
| lastStage = 0; |
| for (int s = 0; s < EShLangCount; ++s) { |
| if (intermediate[s]) { |
| firstStage = std::min(firstStage, s); |
| lastStage = std::max(lastStage, s); |
| } |
| } |
| } |
| |
| reflection = new TReflection((EShReflectionOptions)opts, (EShLanguage)firstStage, (EShLanguage)lastStage); |
| |
| for (int s = 0; s < EShLangCount; ++s) { |
| if (intermediate[s]) { |
| if (! reflection->addStage((EShLanguage)s, *intermediate[s])) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| unsigned TProgram::getLocalSize(int dim) const { return reflection->getLocalSize(dim); } |
| int TProgram::getReflectionIndex(const char* name) const { return reflection->getIndex(name); } |
| int TProgram::getReflectionPipeIOIndex(const char* name, const bool inOrOut) const |
| { return reflection->getPipeIOIndex(name, inOrOut); } |
| |
| int TProgram::getNumUniformVariables() const { return reflection->getNumUniforms(); } |
| const TObjectReflection& TProgram::getUniform(int index) const { return reflection->getUniform(index); } |
| int TProgram::getNumUniformBlocks() const { return reflection->getNumUniformBlocks(); } |
| const TObjectReflection& TProgram::getUniformBlock(int index) const { return reflection->getUniformBlock(index); } |
| int TProgram::getNumPipeInputs() const { return reflection->getNumPipeInputs(); } |
| const TObjectReflection& TProgram::getPipeInput(int index) const { return reflection->getPipeInput(index); } |
| int TProgram::getNumPipeOutputs() const { return reflection->getNumPipeOutputs(); } |
| const TObjectReflection& TProgram::getPipeOutput(int index) const { return reflection->getPipeOutput(index); } |
| int TProgram::getNumBufferVariables() const { return reflection->getNumBufferVariables(); } |
| const TObjectReflection& TProgram::getBufferVariable(int index) const { return reflection->getBufferVariable(index); } |
| int TProgram::getNumBufferBlocks() const { return reflection->getNumStorageBuffers(); } |
| const TObjectReflection& TProgram::getBufferBlock(int index) const { return reflection->getStorageBufferBlock(index); } |
| int TProgram::getNumAtomicCounters() const { return reflection->getNumAtomicCounters(); } |
| const TObjectReflection& TProgram::getAtomicCounter(int index) const { return reflection->getAtomicCounter(index); } |
| void TProgram::dumpReflection() { if (reflection != nullptr) reflection->dump(); } |
| |
| // |
| // I/O mapping implementation. |
| // |
| bool TProgram::mapIO(TIoMapResolver* pResolver, TIoMapper* pIoMapper) |
| { |
| if (! linked) |
| return false; |
| TIoMapper* ioMapper = nullptr; |
| TIoMapper defaultIOMapper; |
| if (pIoMapper == nullptr) |
| ioMapper = &defaultIOMapper; |
| else |
| ioMapper = pIoMapper; |
| for (int s = 0; s < EShLangCount; ++s) { |
| if (intermediate[s]) { |
| if (! ioMapper->addStage((EShLanguage)s, *intermediate[s], *infoSink, pResolver)) |
| return false; |
| } |
| } |
| |
| return ioMapper->doMap(pResolver, *infoSink); |
| } |
| |
| #endif // GLSLANG_WEB |
| |
| } // end namespace glslang |