// // 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 #include #include #include #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" // Build-time generated includes #include "../Include/build_info.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 = 4; // index range in MapSpvVersionToIndex int MapSpvVersionToIndex(const SpvVersion& spvVersion) { int index = 0; if (spvVersion.openGl > 0) index = 1; else if (spvVersion.vulkan > 0) { if (!spvVersion.vulkanRelaxed) index = 2; else index = 3; } 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 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; #elif defined(GLSLANG_ANGLE) profile = ECoreProfile; version = 450; #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; #elif defined(GLSLANG_ANGLE) profile = ECoreProfile; version = 450; #endif std::unique_ptr 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); // check for compute if ((profile != EEsProfile && version >= 420) || (profile == EEsProfile && version >= 310)) InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangCompute, source, infoSink, commonTable, symbolTables); #ifndef GLSLANG_ANGLE // 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); #endif // !GLSLANG_ANGLE #endif // !GLSLANG_WEB 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 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) { #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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; } #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) // 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; spvVersion.vulkanRelaxed = environment->input.vulkanRulesRelaxed; 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(messages & ~EShMsgReadHlsl); break; case EShSourceHlsl: source = EShSourceHlsl; messages = static_cast(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 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 lengths(new size_t[numTotal]); std::unique_ptr strings(new const char*[numTotal]); std::unique_ptr 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) { #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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; #elif defined(GLSLANG_ANGLE) profile = ECoreProfile; version = 450; #endif bool versionWillBeError = (versionNotFound || (profile == EEsProfile && version >= 300 && versionNotFirst)); #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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 symbolTable(new TSymbolTable); if (cachedTable) symbolTable->adoptLevels(*cachedTable); if (intermediate.getUniqueId() != 0) symbolTable->overwriteUniqueId(intermediate.getUniqueId()); // 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 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(); #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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); intermediate.setUniqueId(symbolTable->getMaxSymbolId()); return success; } #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) // 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& 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 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& 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"; } #ifndef GLSLANG_ANGLE if (messages & EShMsgAST) intermediate.output(parseContext.infoSink, true); #endif return success; } }; #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) // 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, TEnvironment* environment = nullptr) { DoPreprocessing parser(outputString); return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, stringNames, preamble, optLevel, resources, defaultVersion, defaultProfile, forceDefaultVersionAndProfile, forwardCompatible, messages, intermediate, parser, false, includer, "", environment); } #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(ConstructCompiler(language, debugOptions)); return reinterpret_cast(base); } ShHandle ShConstructLinker(const EShExecutable executable, int debugOptions) { if (!InitThread()) return 0; TShHandleBase* base = static_cast(ConstructLinker(executable, debugOptions)); return reinterpret_cast(base); } ShHandle ShConstructUniformMap() { if (!InitThread()) return 0; TShHandleBase* base = static_cast(ConstructUniformMap()); return reinterpret_cast(base); } void ShDestruct(ShHandle handle) { if (handle == 0) return; TShHandleBase* base = static_cast(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(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(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(linkHandle); TLinker* linker = static_cast(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(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(handle); TLinker* linker = static_cast(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(handle); TLinker* linker = static_cast(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(handle); TLinker* linker = static_cast(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(handle); TLinker* linker = static_cast(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(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 { Version GetVersion() { Version version; version.major = GLSLANG_VERSION_MAJOR; version.minor = GLSLANG_VERSION_MINOR; version.patch = GLSLANG_VERSION_PATCH; version.flavor = GLSLANG_VERSION_FLAVOR; return version; } #define QUOTE(s) #s #define STR(n) QUOTE(n) const char* GetEsslVersionString() { return "OpenGL ES GLSL 3.20 glslang Khronos. " STR(GLSLANG_VERSION_MAJOR) "." STR(GLSLANG_VERSION_MINOR) "." STR( GLSLANG_VERSION_PATCH) GLSLANG_VERSION_FLAVOR; } const char* GetGlslVersionString() { return "4.60 glslang Khronos. " STR(GLSLANG_VERSION_MAJOR) "." STR(GLSLANG_VERSION_MINOR) "." STR( GLSLANG_VERSION_PATCH) GLSLANG_VERSION_FLAVOR; } 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.input.vulkanRulesRelaxed = false; 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& p) { intermediate->addProcesses(p); } void TShader::setUniqueId(unsigned long long id) { intermediate->setUniqueId(id); } 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& base) { intermediate->setResourceSetBinding(base); } void TShader::setTextureSamplerTransformMode(EShTextureSamplerTransformMode mode) { intermediate->setTextureSamplerTransformMode(mode); } #endif void TShader::addBlockStorageOverride(const char* nameStr, TBlockStorageClass backing) { intermediate->addBlockStorageOverride(nameStr, backing); } void TShader::setGlobalUniformBlockName(const char* name) { intermediate->setGlobalUniformBlockName(name); } void TShader::setGlobalUniformSet(unsigned int set) { intermediate->setGlobalUniformSet(set); } void TShader::setGlobalUniformBinding(unsigned int binding) { intermediate->setGlobalUniformBinding(binding); } void TShader::setAtomicCounterBlockName(const char* name) { intermediate->setAtomicCounterBlockName(name); } void TShader::setAtomicCounterBlockSet(unsigned int set) { intermediate->setAtomicCounterBlockSet(set); } #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); } #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) // 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, &environment); } #endif const char* TShader::getInfoLog() { return infoSink->info.c_str(); } const char* TShader::getInfoDebugLog() { return infoSink->debug.c_str(); } TProgram::TProgram() : #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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; #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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; } if (!error) { if (! crossStageCheck(messages)) error = true; } 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; #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) 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()); intermediate[stage]->setLimits(firstIntermediate->getLimits()); // 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::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); #ifndef GLSLANG_ANGLE if (messages & EShMsgAST) intermediate[stage]->output(*infoSink, true); #endif return intermediate[stage]->getNumErrors() == 0; } // // Check that there are no errors in linker objects accross stages // // Return true if no errors. // bool TProgram::crossStageCheck(EShMessages) { // make temporary intermediates to hold the linkage symbols for each linking interface // while we do the checks // Independent interfaces are: // all uniform variables and blocks // all buffer blocks // all in/out on a stage boundary TVector activeStages; for (int s = 0; s < EShLangCount; ++s) { if (intermediate[s]) activeStages.push_back(intermediate[s]); } // no extra linking if there is only one stage if (! (activeStages.size() > 1)) return true; // setup temporary tree to hold unfirom objects from different stages TIntermediate* firstIntermediate = activeStages.front(); TIntermediate uniforms(EShLangCount, firstIntermediate->getVersion(), firstIntermediate->getProfile()); uniforms.setSpv(firstIntermediate->getSpv()); TIntermAggregate uniformObjects(EOpLinkerObjects); TIntermAggregate root(EOpSequence); root.getSequence().push_back(&uniformObjects); uniforms.setTreeRoot(&root); bool error = false; // merge uniforms from all stages into a single intermediate for (unsigned int i = 0; i < activeStages.size(); ++i) { uniforms.mergeUniformObjects(*infoSink, *activeStages[i]); } error |= uniforms.getNumErrors() != 0; // copy final definition of global block back into each stage for (unsigned int i = 0; i < activeStages.size(); ++i) { // We only want to merge into already existing global uniform blocks. // A stage that doesn't already know about the global doesn't care about it's content. // Otherwise we end up pointing to the same object between different stages // and that will break binding/set remappings bool mergeExistingOnly = true; activeStages[i]->mergeGlobalUniformBlocks(*infoSink, uniforms, mergeExistingOnly); } // compare cross stage symbols for each stage boundary for (unsigned int i = 1; i < activeStages.size(); ++i) { activeStages[i - 1]->checkStageIO(*infoSink, *activeStages[i]); error |= (activeStages[i - 1]->getNumErrors() != 0); } return !error; } const char* TProgram::getInfoLog() { return infoSink->info.c_str(); } const char* TProgram::getInfoDebugLog() { return infoSink->debug.c_str(); } #if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE) // // 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 && !GLSLANG_ANGLE } // end namespace glslang