// // Copyright (C) 2002-2005 3Dlabs Inc. Ltd. // Copyright (C) 2013-2016 LunarG, Inc. // Copyright (C) 2016-2020 Google, Inc. // Modifications Copyright(C) 2021 Advanced Micro Devices, Inc.All rights reserved. // // 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. // // this only applies to the standalone wrapper, not the front end in general #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include "ResourceLimits.h" #include "Worklist.h" #include "DirStackFileIncluder.h" #include "./../glslang/Include/ShHandle.h" #include "./../glslang/Public/ShaderLang.h" #include "../SPIRV/GlslangToSpv.h" #include "../SPIRV/GLSL.std.450.h" #include "../SPIRV/doc.h" #include "../SPIRV/disassemble.h" #include #include #include #include #include #include #include #include #include #include "../glslang/OSDependent/osinclude.h" // Build-time generated includes #include "glslang/build_info.h" #include "glslang/glsl_intrinsic_header.h" extern "C" { GLSLANG_EXPORT void ShOutputHtml(); } // Command-line options enum TOptions { EOptionNone = 0, EOptionIntermediate = (1 << 0), EOptionSuppressInfolog = (1 << 1), EOptionMemoryLeakMode = (1 << 2), EOptionRelaxedErrors = (1 << 3), EOptionGiveWarnings = (1 << 4), EOptionLinkProgram = (1 << 5), EOptionMultiThreaded = (1 << 6), EOptionDumpConfig = (1 << 7), EOptionDumpReflection = (1 << 8), EOptionSuppressWarnings = (1 << 9), EOptionDumpVersions = (1 << 10), EOptionSpv = (1 << 11), EOptionHumanReadableSpv = (1 << 12), EOptionVulkanRules = (1 << 13), EOptionDefaultDesktop = (1 << 14), EOptionOutputPreprocessed = (1 << 15), EOptionOutputHexadecimal = (1 << 16), EOptionReadHlsl = (1 << 17), EOptionCascadingErrors = (1 << 18), EOptionAutoMapBindings = (1 << 19), EOptionFlattenUniformArrays = (1 << 20), EOptionNoStorageFormat = (1 << 21), EOptionKeepUncalled = (1 << 22), EOptionHlslOffsets = (1 << 23), EOptionHlslIoMapping = (1 << 24), EOptionAutoMapLocations = (1 << 25), EOptionDebug = (1 << 26), EOptionStdin = (1 << 27), EOptionOptimizeDisable = (1 << 28), EOptionOptimizeSize = (1 << 29), EOptionInvertY = (1 << 30), EOptionDumpBareVersion = (1 << 31), }; bool targetHlslFunctionality1 = false; bool SpvToolsDisassembler = false; bool SpvToolsValidate = false; bool NaNClamp = false; bool stripDebugInfo = false; bool beQuiet = false; bool VulkanRulesRelaxed = false; bool autoSampledTextures = false; // // Return codes from main/exit(). // enum TFailCode { ESuccess = 0, EFailUsage, EFailCompile, EFailLink, EFailCompilerCreate, EFailThreadCreate, EFailLinkerCreate }; // // Forward declarations. // EShLanguage FindLanguage(const std::string& name, bool parseSuffix=true); void CompileFile(const char* fileName, ShHandle); void usage(); char* ReadFileData(const char* fileName); void FreeFileData(char* data); void InfoLogMsg(const char* msg, const char* name, const int num); // Globally track if any compile or link failure. bool CompileFailed = false; bool LinkFailed = false; // array of unique places to leave the shader names and infologs for the asynchronous compiles std::vector> WorkItems; TBuiltInResource Resources; std::string ConfigFile; // // Parse either a .conf file provided by the user or the default from glslang::DefaultTBuiltInResource // void ProcessConfigFile() { if (ConfigFile.size() == 0) Resources = glslang::DefaultTBuiltInResource; #ifndef GLSLANG_WEB else { char* configString = ReadFileData(ConfigFile.c_str()); glslang::DecodeResourceLimits(&Resources, configString); FreeFileData(configString); } #endif } int ReflectOptions = EShReflectionDefault; int Options = 0; const char* ExecutableName = nullptr; const char* binaryFileName = nullptr; const char* depencyFileName = nullptr; const char* entryPointName = nullptr; const char* sourceEntryPointName = nullptr; const char* shaderStageName = nullptr; const char* variableName = nullptr; bool HlslEnable16BitTypes = false; bool HlslDX9compatible = false; bool HlslDxPositionW = false; bool EnhancedMsgs = false; bool DumpBuiltinSymbols = false; std::vector IncludeDirectoryList; // Source environment // (source 'Client' is currently the same as target 'Client') int ClientInputSemanticsVersion = 100; // Target environment glslang::EShClient Client = glslang::EShClientNone; // will stay EShClientNone if only validating glslang::EShTargetClientVersion ClientVersion; // not valid until Client is set glslang::EShTargetLanguage TargetLanguage = glslang::EShTargetNone; glslang::EShTargetLanguageVersion TargetVersion; // not valid until TargetLanguage is set // GLSL version int GlslVersion = 0; // GLSL version specified on CLI, overrides #version in shader source std::vector Processes; // what should be recorded by OpModuleProcessed, or equivalent // Per descriptor-set binding base data typedef std::map TPerSetBaseBinding; std::vector> uniformLocationOverrides; int uniformBase = 0; std::array, glslang::EResCount> baseBinding; std::array, glslang::EResCount> baseBindingForSet; std::array, EShLangCount> baseResourceSetBinding; std::vector> blockStorageOverrides; bool setGlobalUniformBlock = false; std::string globalUniformName; unsigned int globalUniformBinding; unsigned int globalUniformSet; bool setGlobalBufferBlock = false; std::string atomicCounterBlockName; unsigned int atomicCounterBlockSet; // Add things like "#define ..." to a preamble to use in the beginning of the shader. class TPreamble { public: TPreamble() { } bool isSet() const { return text.size() > 0; } const char* get() const { return text.c_str(); } // #define... void addDef(std::string def) { text.append("#define "); fixLine(def); Processes.push_back("define-macro "); Processes.back().append(def); // The first "=" needs to turn into a space const size_t equal = def.find_first_of("="); if (equal != def.npos) def[equal] = ' '; text.append(def); text.append("\n"); } // #undef... void addUndef(std::string undef) { text.append("#undef "); fixLine(undef); Processes.push_back("undef-macro "); Processes.back().append(undef); text.append(undef); text.append("\n"); } protected: void fixLine(std::string& line) { // Can't go past a newline in the line const size_t end = line.find_first_of("\n"); if (end != line.npos) line = line.substr(0, end); } std::string text; // contents of preamble }; // Track the user's #define and #undef from the command line. TPreamble UserPreamble; std::string PreambleString; // // Create the default name for saving a binary if -o is not provided. // const char* GetBinaryName(EShLanguage stage) { const char* name; if (binaryFileName == nullptr) { switch (stage) { case EShLangVertex: name = "vert.spv"; break; case EShLangTessControl: name = "tesc.spv"; break; case EShLangTessEvaluation: name = "tese.spv"; break; case EShLangGeometry: name = "geom.spv"; break; case EShLangFragment: name = "frag.spv"; break; case EShLangCompute: name = "comp.spv"; break; case EShLangRayGen: name = "rgen.spv"; break; case EShLangIntersect: name = "rint.spv"; break; case EShLangAnyHit: name = "rahit.spv"; break; case EShLangClosestHit: name = "rchit.spv"; break; case EShLangMiss: name = "rmiss.spv"; break; case EShLangCallable: name = "rcall.spv"; break; case EShLangMeshNV: name = "mesh.spv"; break; case EShLangTaskNV: name = "task.spv"; break; default: name = "unknown"; break; } } else name = binaryFileName; return name; } // // *.conf => this is a config file that can set limits/resources // bool SetConfigFile(const std::string& name) { if (name.size() < 5) return false; if (name.compare(name.size() - 5, 5, ".conf") == 0) { ConfigFile = name; return true; } return false; } // // Give error and exit with failure code. // void Error(const char* message, const char* detail = nullptr) { fprintf(stderr, "%s: Error: ", ExecutableName); if (detail != nullptr) fprintf(stderr, "%s: ", detail); fprintf(stderr, "%s (use -h for usage)\n", message); exit(EFailUsage); } // // Process an optional binding base of one the forms: // --argname [stage] base // base for stage (if given) or all stages (if not) // --argname [stage] [base set]... // set/base pairs: set the base for given binding set. // Where stage is one of the forms accepted by FindLanguage, and base is an integer // void ProcessBindingBase(int& argc, char**& argv, glslang::TResourceType res) { if (argc < 2) usage(); EShLanguage lang = EShLangCount; int singleBase = 0; TPerSetBaseBinding perSetBase; int arg = 1; // Parse stage, if given if (!isdigit(argv[arg][0])) { if (argc < 3) // this form needs one more argument usage(); lang = FindLanguage(argv[arg++], false); } if ((argc - arg) >= 2 && isdigit(argv[arg+0][0]) && isdigit(argv[arg+1][0])) { // Parse a per-set binding base do { const int baseNum = atoi(argv[arg++]); const int setNum = atoi(argv[arg++]); perSetBase[setNum] = baseNum; } while ((argc - arg) >= 2 && isdigit(argv[arg + 0][0]) && isdigit(argv[arg + 1][0])); } else { // Parse single binding base singleBase = atoi(argv[arg++]); } argc -= (arg-1); argv += (arg-1); // Set one or all languages const int langMin = (lang < EShLangCount) ? lang+0 : 0; const int langMax = (lang < EShLangCount) ? lang+1 : EShLangCount; for (int lang = langMin; lang < langMax; ++lang) { if (!perSetBase.empty()) baseBindingForSet[res][lang].insert(perSetBase.begin(), perSetBase.end()); else baseBinding[res][lang] = singleBase; } } void ProcessResourceSetBindingBase(int& argc, char**& argv, std::array, EShLangCount>& base) { if (argc < 2) usage(); if (!isdigit(argv[1][0])) { if (argc < 3) // this form needs one more argument usage(); // Parse form: --argname stage [regname set base...], or: // --argname stage set const EShLanguage lang = FindLanguage(argv[1], false); argc--; argv++; while (argc > 1 && argv[1] != nullptr && argv[1][0] != '-') { base[lang].push_back(argv[1]); argc--; argv++; } // Must have one arg, or a multiple of three (for [regname set binding] triples) if (base[lang].size() != 1 && (base[lang].size() % 3) != 0) usage(); } else { // Parse form: --argname set for (int lang=0; lang>& storage) { if (argc < 3) usage(); glslang::TBlockStorageClass blockStorage = glslang::EbsNone; std::string strBacking(argv[2]); if (strBacking == "uniform") blockStorage = glslang::EbsUniform; else if (strBacking == "buffer") blockStorage = glslang::EbsStorageBuffer; else if (strBacking == "push_constant") blockStorage = glslang::EbsPushConstant; else { printf("%s: invalid block storage\n", strBacking.c_str()); usage(); } storage.push_back(std::make_pair(std::string(argv[1]), blockStorage)); argc -= 2; argv += 2; } inline bool isNonDigit(char c) { // a non-digit character valid in a glsl identifier return (c == '_') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } // whether string isa valid identifier to be used in glsl bool isValidIdentifier(const char* str) { std::string idn(str); if (idn.length() == 0) { return false; } if (idn.length() >= 3 && idn.substr(0, 3) == "gl_") { // identifiers startin with "gl_" are reserved return false; } if (!isNonDigit(idn[0])) { return false; } for (unsigned int i = 1; i < idn.length(); ++i) { if (!(isdigit(idn[i]) || isNonDigit(idn[i]))) { return false; } } return true; } // Process settings for either the global buffer block or global unfirom block // of the form: // --argname name set binding void ProcessGlobalBlockSettings(int& argc, char**& argv, std::string* name, unsigned int* set, unsigned int* binding) { if (argc < 4) usage(); unsigned int curArg = 1; assert(name || set || binding); if (name) { if (!isValidIdentifier(argv[curArg])) { printf("%s: invalid identifier\n", argv[curArg]); usage(); } *name = argv[curArg]; curArg++; } if (set) { errno = 0; int setVal = ::strtol(argv[curArg], NULL, 10); if (errno || setVal < 0) { printf("%s: invalid set\n", argv[curArg]); usage(); } *set = setVal; curArg++; } if (binding) { errno = 0; int bindingVal = ::strtol(argv[curArg], NULL, 10); if (errno || bindingVal < 0) { printf("%s: invalid binding\n", argv[curArg]); usage(); } *binding = bindingVal; curArg++; } argc -= (curArg - 1); argv += (curArg - 1); } // // Do all command-line argument parsing. This includes building up the work-items // to be processed later, and saving all the command-line options. // // Does not return (it exits) if command-line is fatally flawed. // void ProcessArguments(std::vector>& workItems, int argc, char* argv[]) { for (int res = 0; res < glslang::EResCount; ++res) baseBinding[res].fill(0); ExecutableName = argv[0]; workItems.reserve(argc); const auto bumpArg = [&]() { if (argc > 0) { argc--; argv++; } }; // read a string directly attached to a single-letter option const auto getStringOperand = [&](const char* desc) { if (argv[0][2] == 0) { printf("%s must immediately follow option (no spaces)\n", desc); exit(EFailUsage); } return argv[0] + 2; }; // read a number attached to a single-letter option const auto getAttachedNumber = [&](const char* desc) { int num = atoi(argv[0] + 2); if (num == 0) { printf("%s: expected attached non-0 number\n", desc); exit(EFailUsage); } return num; }; // minimum needed (without overriding something else) to target Vulkan SPIR-V const auto setVulkanSpv = []() { if (Client == glslang::EShClientNone) ClientVersion = glslang::EShTargetVulkan_1_0; Client = glslang::EShClientVulkan; Options |= EOptionSpv; Options |= EOptionVulkanRules; Options |= EOptionLinkProgram; }; // minimum needed (without overriding something else) to target OpenGL SPIR-V const auto setOpenGlSpv = []() { if (Client == glslang::EShClientNone) ClientVersion = glslang::EShTargetOpenGL_450; Client = glslang::EShClientOpenGL; Options |= EOptionSpv; Options |= EOptionLinkProgram; // undo a -H default to Vulkan Options &= ~EOptionVulkanRules; }; const auto getUniformOverride = [getStringOperand]() { const char *arg = getStringOperand("-u:"); const char *split = strchr(arg, ':'); if (split == NULL) { printf("%s: missing location\n", arg); exit(EFailUsage); } errno = 0; int location = ::strtol(split + 1, NULL, 10); if (errno) { printf("%s: invalid location\n", arg); exit(EFailUsage); } return std::make_pair(std::string(arg, split - arg), location); }; for (bumpArg(); argc >= 1; bumpArg()) { if (argv[0][0] == '-') { switch (argv[0][1]) { case '-': { std::string lowerword(argv[0]+2); std::transform(lowerword.begin(), lowerword.end(), lowerword.begin(), ::tolower); // handle --word style options if (lowerword == "auto-map-bindings" || // synonyms lowerword == "auto-map-binding" || lowerword == "amb") { Options |= EOptionAutoMapBindings; } else if (lowerword == "auto-map-locations" || // synonyms lowerword == "aml") { Options |= EOptionAutoMapLocations; } else if (lowerword == "uniform-base") { if (argc <= 1) Error("no provided", lowerword.c_str()); uniformBase = ::strtol(argv[1], NULL, 10); bumpArg(); break; } else if (lowerword == "client") { if (argc > 1) { if (strcmp(argv[1], "vulkan100") == 0) setVulkanSpv(); else if (strcmp(argv[1], "opengl100") == 0) setOpenGlSpv(); else Error("expects vulkan100 or opengl100", lowerword.c_str()); } else Error("expects vulkan100 or opengl100", lowerword.c_str()); bumpArg(); } else if (lowerword == "define-macro" || lowerword == "d") { if (argc > 1) UserPreamble.addDef(argv[1]); else Error("expects ", argv[0]); bumpArg(); } else if (lowerword == "dump-builtin-symbols") { DumpBuiltinSymbols = true; } else if (lowerword == "entry-point") { entryPointName = argv[1]; if (argc <= 1) Error("no provided", lowerword.c_str()); bumpArg(); } else if (lowerword == "flatten-uniform-arrays" || // synonyms lowerword == "flatten-uniform-array" || lowerword == "fua") { Options |= EOptionFlattenUniformArrays; } else if (lowerword == "glsl-version") { if (argc > 1) { if (strcmp(argv[1], "100") == 0) { GlslVersion = 100; } else if (strcmp(argv[1], "110") == 0) { GlslVersion = 110; } else if (strcmp(argv[1], "120") == 0) { GlslVersion = 120; } else if (strcmp(argv[1], "130") == 0) { GlslVersion = 130; } else if (strcmp(argv[1], "140") == 0) { GlslVersion = 140; } else if (strcmp(argv[1], "150") == 0) { GlslVersion = 150; } else if (strcmp(argv[1], "300es") == 0) { GlslVersion = 300; } else if (strcmp(argv[1], "310es") == 0) { GlslVersion = 310; } else if (strcmp(argv[1], "320es") == 0) { GlslVersion = 320; } else if (strcmp(argv[1], "330") == 0) { GlslVersion = 330; } else if (strcmp(argv[1], "400") == 0) { GlslVersion = 400; } else if (strcmp(argv[1], "410") == 0) { GlslVersion = 410; } else if (strcmp(argv[1], "420") == 0) { GlslVersion = 420; } else if (strcmp(argv[1], "430") == 0) { GlslVersion = 430; } else if (strcmp(argv[1], "440") == 0) { GlslVersion = 440; } else if (strcmp(argv[1], "450") == 0) { GlslVersion = 450; } else if (strcmp(argv[1], "460") == 0) { GlslVersion = 460; } else Error("--glsl-version expected one of: 100, 110, 120, 130, 140, 150,\n" "300es, 310es, 320es, 330\n" "400, 410, 420, 430, 440, 450, 460"); } bumpArg(); } else if (lowerword == "hlsl-offsets") { Options |= EOptionHlslOffsets; } else if (lowerword == "hlsl-iomap" || lowerword == "hlsl-iomapper" || lowerword == "hlsl-iomapping") { Options |= EOptionHlslIoMapping; } else if (lowerword == "hlsl-enable-16bit-types") { HlslEnable16BitTypes = true; } else if (lowerword == "hlsl-dx9-compatible") { HlslDX9compatible = true; } else if (lowerword == "hlsl-dx-position-w") { HlslDxPositionW = true; } else if (lowerword == "enhanced-msgs") { EnhancedMsgs = true; } else if (lowerword == "auto-sampled-textures") { autoSampledTextures = true; } else if (lowerword == "invert-y" || // synonyms lowerword == "iy") { Options |= EOptionInvertY; } else if (lowerword == "keep-uncalled" || // synonyms lowerword == "ku") { Options |= EOptionKeepUncalled; } else if (lowerword == "nan-clamp") { NaNClamp = true; } else if (lowerword == "no-storage-format" || // synonyms lowerword == "nsf") { Options |= EOptionNoStorageFormat; } else if (lowerword == "relaxed-errors") { Options |= EOptionRelaxedErrors; } else if (lowerword == "reflect-strict-array-suffix") { ReflectOptions |= EShReflectionStrictArraySuffix; } else if (lowerword == "reflect-basic-array-suffix") { ReflectOptions |= EShReflectionBasicArraySuffix; } else if (lowerword == "reflect-intermediate-io") { ReflectOptions |= EShReflectionIntermediateIO; } else if (lowerword == "reflect-separate-buffers") { ReflectOptions |= EShReflectionSeparateBuffers; } else if (lowerword == "reflect-all-block-variables") { ReflectOptions |= EShReflectionAllBlockVariables; } else if (lowerword == "reflect-unwrap-io-blocks") { ReflectOptions |= EShReflectionUnwrapIOBlocks; } else if (lowerword == "reflect-all-io-variables") { ReflectOptions |= EShReflectionAllIOVariables; } else if (lowerword == "reflect-shared-std140-ubo") { ReflectOptions |= EShReflectionSharedStd140UBO; } else if (lowerword == "reflect-shared-std140-ssbo") { ReflectOptions |= EShReflectionSharedStd140SSBO; } else if (lowerword == "resource-set-bindings" || // synonyms lowerword == "resource-set-binding" || lowerword == "rsb") { ProcessResourceSetBindingBase(argc, argv, baseResourceSetBinding); } else if (lowerword == "set-block-storage" || lowerword == "sbs") { ProcessBlockStorage(argc, argv, blockStorageOverrides); } else if (lowerword == "set-atomic-counter-block" || lowerword == "sacb") { ProcessGlobalBlockSettings(argc, argv, &atomicCounterBlockName, &atomicCounterBlockSet, nullptr); setGlobalBufferBlock = true; } else if (lowerword == "set-default-uniform-block" || lowerword == "sdub") { ProcessGlobalBlockSettings(argc, argv, &globalUniformName, &globalUniformSet, &globalUniformBinding); setGlobalUniformBlock = true; } else if (lowerword == "shift-image-bindings" || // synonyms lowerword == "shift-image-binding" || lowerword == "sib") { ProcessBindingBase(argc, argv, glslang::EResImage); } else if (lowerword == "shift-sampler-bindings" || // synonyms lowerword == "shift-sampler-binding" || lowerword == "ssb") { ProcessBindingBase(argc, argv, glslang::EResSampler); } else if (lowerword == "shift-uav-bindings" || // synonyms lowerword == "shift-uav-binding" || lowerword == "suavb") { ProcessBindingBase(argc, argv, glslang::EResUav); } else if (lowerword == "shift-texture-bindings" || // synonyms lowerword == "shift-texture-binding" || lowerword == "stb") { ProcessBindingBase(argc, argv, glslang::EResTexture); } else if (lowerword == "shift-ubo-bindings" || // synonyms lowerword == "shift-ubo-binding" || lowerword == "shift-cbuffer-bindings" || lowerword == "shift-cbuffer-binding" || lowerword == "sub" || lowerword == "scb") { ProcessBindingBase(argc, argv, glslang::EResUbo); } else if (lowerword == "shift-ssbo-bindings" || // synonyms lowerword == "shift-ssbo-binding" || lowerword == "sbb") { ProcessBindingBase(argc, argv, glslang::EResSsbo); } else if (lowerword == "source-entrypoint" || // synonyms lowerword == "sep") { if (argc <= 1) Error("no provided", lowerword.c_str()); sourceEntryPointName = argv[1]; bumpArg(); break; } else if (lowerword == "spirv-dis") { SpvToolsDisassembler = true; } else if (lowerword == "spirv-val") { SpvToolsValidate = true; } else if (lowerword == "stdin") { Options |= EOptionStdin; shaderStageName = argv[1]; } else if (lowerword == "suppress-warnings") { Options |= EOptionSuppressWarnings; } else if (lowerword == "target-env") { if (argc > 1) { if (strcmp(argv[1], "vulkan1.0") == 0) { setVulkanSpv(); ClientVersion = glslang::EShTargetVulkan_1_0; } else if (strcmp(argv[1], "vulkan1.1") == 0) { setVulkanSpv(); ClientVersion = glslang::EShTargetVulkan_1_1; } else if (strcmp(argv[1], "vulkan1.2") == 0) { setVulkanSpv(); ClientVersion = glslang::EShTargetVulkan_1_2; } else if (strcmp(argv[1], "vulkan1.3") == 0) { setVulkanSpv(); ClientVersion = glslang::EShTargetVulkan_1_3; } else if (strcmp(argv[1], "opengl") == 0) { setOpenGlSpv(); ClientVersion = glslang::EShTargetOpenGL_450; } else if (strcmp(argv[1], "spirv1.0") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_0; } else if (strcmp(argv[1], "spirv1.1") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_1; } else if (strcmp(argv[1], "spirv1.2") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_2; } else if (strcmp(argv[1], "spirv1.3") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_3; } else if (strcmp(argv[1], "spirv1.4") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_4; } else if (strcmp(argv[1], "spirv1.5") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_5; } else if (strcmp(argv[1], "spirv1.6") == 0) { TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_6; } else Error("--target-env expected one of: vulkan1.0, vulkan1.1, vulkan1.2,\n" "vulkan1.3, opengl, spirv1.0, spirv1.1, spirv1.2, spirv1.3,\n" "spirv1.4, spirv1.5 or spirv1.6"); } bumpArg(); } else if (lowerword == "undef-macro" || lowerword == "u") { if (argc > 1) UserPreamble.addUndef(argv[1]); else Error("expects ", argv[0]); bumpArg(); } else if (lowerword == "variable-name" || // synonyms lowerword == "vn") { Options |= EOptionOutputHexadecimal; if (argc <= 1) Error("no provided", lowerword.c_str()); variableName = argv[1]; bumpArg(); break; } else if (lowerword == "quiet") { beQuiet = true; } else if (lowerword == "depfile") { if (argc <= 1) Error("no provided", lowerword.c_str()); depencyFileName = argv[1]; bumpArg(); } else if (lowerword == "version") { Options |= EOptionDumpVersions; } else if (lowerword == "help") { usage(); break; } else { Error("unrecognized command-line option", argv[0]); } } break; case 'C': Options |= EOptionCascadingErrors; break; case 'D': if (argv[0][2] == 0) Options |= EOptionReadHlsl; else UserPreamble.addDef(getStringOperand("-D")); break; case 'u': uniformLocationOverrides.push_back(getUniformOverride()); break; case 'E': Options |= EOptionOutputPreprocessed; break; case 'G': // OpenGL client setOpenGlSpv(); if (argv[0][2] != 0) ClientInputSemanticsVersion = getAttachedNumber("-G client input semantics"); if (ClientInputSemanticsVersion != 100) Error("unknown client version for -G, should be 100"); break; case 'H': Options |= EOptionHumanReadableSpv; if ((Options & EOptionSpv) == 0) { // default to Vulkan setVulkanSpv(); } break; case 'I': IncludeDirectoryList.push_back(getStringOperand("-I include path")); break; case 'O': if (argv[0][2] == 'd') Options |= EOptionOptimizeDisable; else if (argv[0][2] == 's') #if ENABLE_OPT Options |= EOptionOptimizeSize; #else Error("-Os not available; optimizer not linked"); #endif else Error("unknown -O option"); break; case 'R': VulkanRulesRelaxed = true; break; case 'S': if (argc <= 1) Error("no specified for -S"); shaderStageName = argv[1]; bumpArg(); break; case 'U': UserPreamble.addUndef(getStringOperand("-U")); break; case 'V': setVulkanSpv(); if (argv[0][2] != 0) ClientInputSemanticsVersion = getAttachedNumber("-V client input semantics"); if (ClientInputSemanticsVersion != 100) Error("unknown client version for -V, should be 100"); break; case 'c': Options |= EOptionDumpConfig; break; case 'd': if (strncmp(&argv[0][1], "dumpversion", strlen(&argv[0][1]) + 1) == 0 || strncmp(&argv[0][1], "dumpfullversion", strlen(&argv[0][1]) + 1) == 0) Options |= EOptionDumpBareVersion; else Options |= EOptionDefaultDesktop; break; case 'e': entryPointName = argv[1]; if (argc <= 1) Error("no provided for -e"); bumpArg(); break; case 'f': if (strcmp(&argv[0][2], "hlsl_functionality1") == 0) targetHlslFunctionality1 = true; else Error("-f: expected hlsl_functionality1"); break; case 'g': // Override previous -g or -g0 argument stripDebugInfo = false; Options &= ~EOptionDebug; if (argv[0][2] == '0') stripDebugInfo = true; else Options |= EOptionDebug; break; case 'h': usage(); break; case 'i': Options |= EOptionIntermediate; break; case 'l': Options |= EOptionLinkProgram; break; case 'm': Options |= EOptionMemoryLeakMode; break; case 'o': if (argc <= 1) Error("no provided for -o"); binaryFileName = argv[1]; bumpArg(); break; case 'q': Options |= EOptionDumpReflection; break; case 'r': Options |= EOptionRelaxedErrors; break; case 's': Options |= EOptionSuppressInfolog; break; case 't': Options |= EOptionMultiThreaded; break; case 'v': Options |= EOptionDumpVersions; break; case 'w': Options |= EOptionSuppressWarnings; break; case 'x': Options |= EOptionOutputHexadecimal; break; default: Error("unrecognized command-line option", argv[0]); break; } } else { std::string name(argv[0]); if (! SetConfigFile(name)) { workItems.push_back(std::unique_ptr(new glslang::TWorkItem(name))); } } } // Make sure that -S is always specified if --stdin is specified if ((Options & EOptionStdin) && shaderStageName == nullptr) Error("must provide -S when --stdin is given"); // Make sure that -E is not specified alongside linking (which includes SPV generation) // Or things that require linking if (Options & EOptionOutputPreprocessed) { if (Options & EOptionLinkProgram) Error("can't use -E when linking is selected"); if (Options & EOptionDumpReflection) Error("reflection requires linking, which can't be used when -E when is selected"); } // reflection requires linking if ((Options & EOptionDumpReflection) && !(Options & EOptionLinkProgram)) Error("reflection requires -l for linking"); // -o or -x makes no sense if there is no target binary if (binaryFileName && (Options & EOptionSpv) == 0) Error("no binary generation requested (e.g., -V)"); if ((Options & EOptionFlattenUniformArrays) != 0 && (Options & EOptionReadHlsl) == 0) Error("uniform array flattening only valid when compiling HLSL source."); if ((Options & EOptionReadHlsl) && (Client == glslang::EShClientOpenGL)) { Error("Using HLSL input under OpenGL semantics is not currently supported."); } // rationalize client and target language if (TargetLanguage == glslang::EShTargetNone) { switch (ClientVersion) { case glslang::EShTargetVulkan_1_0: TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_0; break; case glslang::EShTargetVulkan_1_1: TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_3; break; case glslang::EShTargetVulkan_1_2: TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_5; break; case glslang::EShTargetVulkan_1_3: TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_6; break; case glslang::EShTargetOpenGL_450: TargetLanguage = glslang::EShTargetSpv; TargetVersion = glslang::EShTargetSpv_1_0; break; default: break; } } if (TargetLanguage != glslang::EShTargetNone && Client == glslang::EShClientNone) Error("To generate SPIR-V, also specify client semantics. See -G and -V."); } // // Translate the meaningful subset of command-line options to parser-behavior options. // void SetMessageOptions(EShMessages& messages) { if (Options & EOptionRelaxedErrors) messages = (EShMessages)(messages | EShMsgRelaxedErrors); if (Options & EOptionIntermediate) messages = (EShMessages)(messages | EShMsgAST); if (Options & EOptionSuppressWarnings) messages = (EShMessages)(messages | EShMsgSuppressWarnings); if (Options & EOptionSpv) messages = (EShMessages)(messages | EShMsgSpvRules); if (Options & EOptionVulkanRules) messages = (EShMessages)(messages | EShMsgVulkanRules); if (Options & EOptionOutputPreprocessed) messages = (EShMessages)(messages | EShMsgOnlyPreprocessor); if (Options & EOptionReadHlsl) messages = (EShMessages)(messages | EShMsgReadHlsl); if (Options & EOptionCascadingErrors) messages = (EShMessages)(messages | EShMsgCascadingErrors); if (Options & EOptionKeepUncalled) messages = (EShMessages)(messages | EShMsgKeepUncalled); if (Options & EOptionHlslOffsets) messages = (EShMessages)(messages | EShMsgHlslOffsets); if (Options & EOptionDebug) messages = (EShMessages)(messages | EShMsgDebugInfo); if (HlslEnable16BitTypes) messages = (EShMessages)(messages | EShMsgHlslEnable16BitTypes); if ((Options & EOptionOptimizeDisable) || !ENABLE_OPT) messages = (EShMessages)(messages | EShMsgHlslLegalization); if (HlslDX9compatible) messages = (EShMessages)(messages | EShMsgHlslDX9Compatible); if (DumpBuiltinSymbols) messages = (EShMessages)(messages | EShMsgBuiltinSymbolTable); if (EnhancedMsgs) messages = (EShMessages)(messages | EShMsgEnhanced); } // // Thread entry point, for non-linking asynchronous mode. // void CompileShaders(glslang::TWorklist& worklist) { if (Options & EOptionDebug) Error("cannot generate debug information unless linking to generate code"); glslang::TWorkItem* workItem; if (Options & EOptionStdin) { if (worklist.remove(workItem)) { ShHandle compiler = ShConstructCompiler(FindLanguage("stdin"), Options); if (compiler == nullptr) return; CompileFile("stdin", compiler); if (! (Options & EOptionSuppressInfolog)) workItem->results = ShGetInfoLog(compiler); ShDestruct(compiler); } } else { while (worklist.remove(workItem)) { ShHandle compiler = ShConstructCompiler(FindLanguage(workItem->name), Options); if (compiler == 0) return; CompileFile(workItem->name.c_str(), compiler); if (! (Options & EOptionSuppressInfolog)) workItem->results = ShGetInfoLog(compiler); ShDestruct(compiler); } } } // Outputs the given string, but only if it is non-null and non-empty. // This prevents erroneous newlines from appearing. void PutsIfNonEmpty(const char* str) { if (str && str[0]) { puts(str); } } // Outputs the given string to stderr, but only if it is non-null and non-empty. // This prevents erroneous newlines from appearing. void StderrIfNonEmpty(const char* str) { if (str && str[0]) fprintf(stderr, "%s\n", str); } // Simple bundling of what makes a compilation unit for ease in passing around, // and separation of handling file IO versus API (programmatic) compilation. struct ShaderCompUnit { EShLanguage stage; static const int maxCount = 1; int count; // live number of strings/names const char* text[maxCount]; // memory owned/managed externally std::string fileName[maxCount]; // hold's the memory, but... const char* fileNameList[maxCount]; // downstream interface wants pointers ShaderCompUnit(EShLanguage stage) : stage(stage), count(0) { } ShaderCompUnit(const ShaderCompUnit& rhs) { stage = rhs.stage; count = rhs.count; for (int i = 0; i < count; ++i) { fileName[i] = rhs.fileName[i]; text[i] = rhs.text[i]; fileNameList[i] = rhs.fileName[i].c_str(); } } void addString(std::string& ifileName, const char* itext) { assert(count < maxCount); fileName[count] = ifileName; text[count] = itext; fileNameList[count] = fileName[count].c_str(); ++count; } }; // Writes a string into a depfile, escaping some special characters following the Makefile rules. static void writeEscapedDepString(std::ofstream& file, const std::string& str) { for (char c : str) { switch (c) { case ' ': case ':': case '#': case '[': case ']': case '\\': file << '\\'; break; case '$': file << '$'; break; } file << c; } } // Writes a depfile similar to gcc -MMD foo.c bool writeDepFile(std::string depfile, std::vector& binaryFiles, const std::vector& sources) { std::ofstream file(depfile); if (file.fail()) return false; for (auto binaryFile = binaryFiles.begin(); binaryFile != binaryFiles.end(); binaryFile++) { writeEscapedDepString(file, *binaryFile); file << ":"; for (auto sourceFile = sources.begin(); sourceFile != sources.end(); sourceFile++) { file << " "; writeEscapedDepString(file, *sourceFile); } file << std::endl; } return true; } // // For linking mode: Will independently parse each compilation unit, but then put them // in the same program and link them together, making at most one linked module per // pipeline stage. // // Uses the new C++ interface instead of the old handle-based interface. // void CompileAndLinkShaderUnits(std::vector compUnits) { // keep track of what to free std::list shaders; EShMessages messages = EShMsgDefault; SetMessageOptions(messages); DirStackFileIncluder includer; std::for_each(IncludeDirectoryList.rbegin(), IncludeDirectoryList.rend(), [&includer](const std::string& dir) { includer.pushExternalLocalDirectory(dir); }); std::vector sources; // // Per-shader processing... // glslang::TProgram& program = *new glslang::TProgram; for (auto it = compUnits.cbegin(); it != compUnits.cend(); ++it) { const auto &compUnit = *it; for (int i = 0; i < compUnit.count; i++) { sources.push_back(compUnit.fileNameList[i]); } glslang::TShader* shader = new glslang::TShader(compUnit.stage); shader->setStringsWithLengthsAndNames(compUnit.text, NULL, compUnit.fileNameList, compUnit.count); if (entryPointName) shader->setEntryPoint(entryPointName); if (sourceEntryPointName) { if (entryPointName == nullptr) printf("Warning: Changing source entry point name without setting an entry-point name.\n" "Use '-e '.\n"); shader->setSourceEntryPoint(sourceEntryPointName); } shader->setOverrideVersion(GlslVersion); std::string intrinsicString = getIntrinsic(compUnit.text, compUnit.count); PreambleString = ""; if (UserPreamble.isSet()) PreambleString.append(UserPreamble.get()); if (!intrinsicString.empty()) PreambleString.append(intrinsicString); shader->setPreamble(PreambleString.c_str()); shader->addProcesses(Processes); #ifndef GLSLANG_WEB // Set IO mapper binding shift values for (int r = 0; r < glslang::EResCount; ++r) { const glslang::TResourceType res = glslang::TResourceType(r); // Set base bindings shader->setShiftBinding(res, baseBinding[res][compUnit.stage]); // Set bindings for particular resource sets // TODO: use a range based for loop here, when available in all environments. for (auto i = baseBindingForSet[res][compUnit.stage].begin(); i != baseBindingForSet[res][compUnit.stage].end(); ++i) shader->setShiftBindingForSet(res, i->second, i->first); } shader->setNoStorageFormat((Options & EOptionNoStorageFormat) != 0); shader->setResourceSetBinding(baseResourceSetBinding[compUnit.stage]); if (autoSampledTextures) shader->setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler); if (Options & EOptionAutoMapBindings) shader->setAutoMapBindings(true); if (Options & EOptionAutoMapLocations) shader->setAutoMapLocations(true); for (auto& uniOverride : uniformLocationOverrides) { shader->addUniformLocationOverride(uniOverride.first.c_str(), uniOverride.second); } shader->setUniformLocationBase(uniformBase); #endif if (VulkanRulesRelaxed) { for (auto& storageOverride : blockStorageOverrides) { shader->addBlockStorageOverride(storageOverride.first.c_str(), storageOverride.second); } if (setGlobalBufferBlock) { shader->setAtomicCounterBlockName(atomicCounterBlockName.c_str()); shader->setAtomicCounterBlockSet(atomicCounterBlockSet); } if (setGlobalUniformBlock) { shader->setGlobalUniformBlockName(globalUniformName.c_str()); shader->setGlobalUniformSet(globalUniformSet); shader->setGlobalUniformBinding(globalUniformBinding); } } shader->setNanMinMaxClamp(NaNClamp); #ifdef ENABLE_HLSL shader->setFlattenUniformArrays((Options & EOptionFlattenUniformArrays) != 0); if (Options & EOptionHlslIoMapping) shader->setHlslIoMapping(true); #endif if (Options & EOptionInvertY) shader->setInvertY(true); if (HlslDxPositionW) shader->setDxPositionW(true); if (EnhancedMsgs) shader->setEnhancedMsgs(); // Set up the environment, some subsettings take precedence over earlier // ways of setting things. if (Options & EOptionSpv) { shader->setEnvInput((Options & EOptionReadHlsl) ? glslang::EShSourceHlsl : glslang::EShSourceGlsl, compUnit.stage, Client, ClientInputSemanticsVersion); shader->setEnvClient(Client, ClientVersion); shader->setEnvTarget(TargetLanguage, TargetVersion); #ifdef ENABLE_HLSL if (targetHlslFunctionality1) shader->setEnvTargetHlslFunctionality1(); #endif if (VulkanRulesRelaxed) shader->setEnvInputVulkanRulesRelaxed(); } shaders.push_back(shader); const int defaultVersion = Options & EOptionDefaultDesktop ? 110 : 100; #ifndef GLSLANG_WEB if (Options & EOptionOutputPreprocessed) { std::string str; if (shader->preprocess(&Resources, defaultVersion, ENoProfile, false, false, messages, &str, includer)) { PutsIfNonEmpty(str.c_str()); } else { CompileFailed = true; } StderrIfNonEmpty(shader->getInfoLog()); StderrIfNonEmpty(shader->getInfoDebugLog()); continue; } #endif if (! shader->parse(&Resources, defaultVersion, false, messages, includer)) CompileFailed = true; program.addShader(shader); if (! (Options & EOptionSuppressInfolog) && ! (Options & EOptionMemoryLeakMode)) { if (!beQuiet) PutsIfNonEmpty(compUnit.fileName[0].c_str()); PutsIfNonEmpty(shader->getInfoLog()); PutsIfNonEmpty(shader->getInfoDebugLog()); } } // // Program-level processing... // // Link if (! (Options & EOptionOutputPreprocessed) && ! program.link(messages)) LinkFailed = true; #ifndef GLSLANG_WEB // Map IO if (Options & EOptionSpv) { if (!program.mapIO()) LinkFailed = true; } #endif // Report if (! (Options & EOptionSuppressInfolog) && ! (Options & EOptionMemoryLeakMode)) { PutsIfNonEmpty(program.getInfoLog()); PutsIfNonEmpty(program.getInfoDebugLog()); } #ifndef GLSLANG_WEB // Reflect if (Options & EOptionDumpReflection) { program.buildReflection(ReflectOptions); program.dumpReflection(); } #endif std::vector outputFiles; // Dump SPIR-V if (Options & EOptionSpv) { if (CompileFailed || LinkFailed) printf("SPIR-V is not generated for failed compile or link\n"); else { for (int stage = 0; stage < EShLangCount; ++stage) { if (program.getIntermediate((EShLanguage)stage)) { std::vector spirv; spv::SpvBuildLogger logger; glslang::SpvOptions spvOptions; if (Options & EOptionDebug) spvOptions.generateDebugInfo = true; else if (stripDebugInfo) spvOptions.stripDebugInfo = true; spvOptions.disableOptimizer = (Options & EOptionOptimizeDisable) != 0; spvOptions.optimizeSize = (Options & EOptionOptimizeSize) != 0; spvOptions.disassemble = SpvToolsDisassembler; spvOptions.validate = SpvToolsValidate; glslang::GlslangToSpv(*program.getIntermediate((EShLanguage)stage), spirv, &logger, &spvOptions); // Dump the spv to a file or stdout, etc., but only if not doing // memory/perf testing, as it's not internal to programmatic use. if (! (Options & EOptionMemoryLeakMode)) { printf("%s", logger.getAllMessages().c_str()); if (Options & EOptionOutputHexadecimal) { glslang::OutputSpvHex(spirv, GetBinaryName((EShLanguage)stage), variableName); } else { glslang::OutputSpvBin(spirv, GetBinaryName((EShLanguage)stage)); } outputFiles.push_back(GetBinaryName((EShLanguage)stage)); #ifndef GLSLANG_WEB if (!SpvToolsDisassembler && (Options & EOptionHumanReadableSpv)) spv::Disassemble(std::cout, spirv); #endif } } } } } if (depencyFileName && !(CompileFailed || LinkFailed)) { std::set includedFiles = includer.getIncludedFiles(); sources.insert(sources.end(), includedFiles.begin(), includedFiles.end()); writeDepFile(depencyFileName, outputFiles, sources); } // Free everything up, program has to go before the shaders // because it might have merged stuff from the shaders, and // the stuff from the shaders has to have its destructors called // before the pools holding the memory in the shaders is freed. delete &program; while (shaders.size() > 0) { delete shaders.back(); shaders.pop_back(); } } // // Do file IO part of compile and link, handing off the pure // API/programmatic mode to CompileAndLinkShaderUnits(), which can // be put in a loop for testing memory footprint and performance. // // This is just for linking mode: meaning all the shaders will be put into the // the same program linked together. // // This means there are a limited number of work items (not multi-threading mode) // and that the point is testing at the linking level. Hence, to enable // performance and memory testing, the actual compile/link can be put in // a loop, independent of processing the work items and file IO. // void CompileAndLinkShaderFiles(glslang::TWorklist& Worklist) { std::vector compUnits; // If this is using stdin, we can't really detect multiple different file // units by input type. We need to assume that we're just being given one // file of a certain type. if ((Options & EOptionStdin) != 0) { ShaderCompUnit compUnit(FindLanguage("stdin")); std::istreambuf_iterator begin(std::cin), end; std::string tempString(begin, end); char* fileText = strdup(tempString.c_str()); std::string fileName = "stdin"; compUnit.addString(fileName, fileText); compUnits.push_back(compUnit); } else { // Transfer all the work items from to a simple list of // of compilation units. (We don't care about the thread // work-item distribution properties in this path, which // is okay due to the limited number of shaders, know since // they are all getting linked together.) glslang::TWorkItem* workItem; while (Worklist.remove(workItem)) { ShaderCompUnit compUnit(FindLanguage(workItem->name)); char* fileText = ReadFileData(workItem->name.c_str()); if (fileText == nullptr) usage(); compUnit.addString(workItem->name, fileText); compUnits.push_back(compUnit); } } // Actual call to programmatic processing of compile and link, // in a loop for testing memory and performance. This part contains // all the perf/memory that a programmatic consumer will care about. for (int i = 0; i < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++i) { for (int j = 0; j < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++j) CompileAndLinkShaderUnits(compUnits); if (Options & EOptionMemoryLeakMode) glslang::OS_DumpMemoryCounters(); } // free memory from ReadFileData, which got stored in a const char* // as the first string above for (auto it = compUnits.begin(); it != compUnits.end(); ++it) FreeFileData(const_cast(it->text[0])); } int singleMain() { glslang::TWorklist workList; std::for_each(WorkItems.begin(), WorkItems.end(), [&workList](std::unique_ptr& item) { assert(item); workList.add(item.get()); }); #ifndef GLSLANG_WEB if (Options & EOptionDumpConfig) { printf("%s", glslang::GetDefaultTBuiltInResourceString().c_str()); if (workList.empty()) return ESuccess; } #endif if (Options & EOptionDumpBareVersion) { printf("%d:%d.%d.%d%s\n", glslang::GetSpirvGeneratorVersion(), GLSLANG_VERSION_MAJOR, GLSLANG_VERSION_MINOR, GLSLANG_VERSION_PATCH, GLSLANG_VERSION_FLAVOR); if (workList.empty()) return ESuccess; } else if (Options & EOptionDumpVersions) { printf("Glslang Version: %d:%d.%d.%d%s\n", glslang::GetSpirvGeneratorVersion(), GLSLANG_VERSION_MAJOR, GLSLANG_VERSION_MINOR, GLSLANG_VERSION_PATCH, GLSLANG_VERSION_FLAVOR); printf("ESSL Version: %s\n", glslang::GetEsslVersionString()); printf("GLSL Version: %s\n", glslang::GetGlslVersionString()); std::string spirvVersion; glslang::GetSpirvVersion(spirvVersion); printf("SPIR-V Version %s\n", spirvVersion.c_str()); printf("GLSL.std.450 Version %d, Revision %d\n", GLSLstd450Version, GLSLstd450Revision); printf("Khronos Tool ID %d\n", glslang::GetKhronosToolId()); printf("SPIR-V Generator Version %d\n", glslang::GetSpirvGeneratorVersion()); printf("GL_KHR_vulkan_glsl version %d\n", 100); printf("ARB_GL_gl_spirv version %d\n", 100); if (workList.empty()) return ESuccess; } if (workList.empty() && ((Options & EOptionStdin) == 0)) { usage(); } if (Options & EOptionStdin) { WorkItems.push_back(std::unique_ptr{new glslang::TWorkItem("stdin")}); workList.add(WorkItems.back().get()); } ProcessConfigFile(); if ((Options & EOptionReadHlsl) && !((Options & EOptionOutputPreprocessed) || (Options & EOptionSpv))) Error("HLSL requires SPIR-V code generation (or preprocessing only)"); // // Two modes: // 1) linking all arguments together, single-threaded, new C++ interface // 2) independent arguments, can be tackled by multiple asynchronous threads, for testing thread safety, using the old handle interface // if (Options & (EOptionLinkProgram | EOptionOutputPreprocessed)) { glslang::InitializeProcess(); glslang::InitializeProcess(); // also test reference counting of users glslang::InitializeProcess(); // also test reference counting of users glslang::FinalizeProcess(); // also test reference counting of users glslang::FinalizeProcess(); // also test reference counting of users CompileAndLinkShaderFiles(workList); glslang::FinalizeProcess(); } else { ShInitialize(); ShInitialize(); // also test reference counting of users ShFinalize(); // also test reference counting of users bool printShaderNames = workList.size() > 1; if (Options & EOptionMultiThreaded) { std::array threads; for (unsigned int t = 0; t < threads.size(); ++t) { threads[t] = std::thread(CompileShaders, std::ref(workList)); if (threads[t].get_id() == std::thread::id()) { fprintf(stderr, "Failed to create thread\n"); return EFailThreadCreate; } } std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); }); } else CompileShaders(workList); // Print out all the resulting infologs for (size_t w = 0; w < WorkItems.size(); ++w) { if (WorkItems[w]) { if (printShaderNames || WorkItems[w]->results.size() > 0) PutsIfNonEmpty(WorkItems[w]->name.c_str()); PutsIfNonEmpty(WorkItems[w]->results.c_str()); } } ShFinalize(); } if (CompileFailed) return EFailCompile; if (LinkFailed) return EFailLink; return 0; } int C_DECL main(int argc, char* argv[]) { ProcessArguments(WorkItems, argc, argv); int ret = 0; // Loop over the entire init/finalize cycle to watch memory changes const int iterations = 1; if (iterations > 1) glslang::OS_DumpMemoryCounters(); for (int i = 0; i < iterations; ++i) { ret = singleMain(); if (iterations > 1) glslang::OS_DumpMemoryCounters(); } return ret; } // // Deduce the language from the filename. Files must end in one of the // following extensions: // // .vert = vertex // .tesc = tessellation control // .tese = tessellation evaluation // .geom = geometry // .frag = fragment // .comp = compute // .rgen = ray generation // .rint = ray intersection // .rahit = ray any hit // .rchit = ray closest hit // .rmiss = ray miss // .rcall = ray callable // .mesh = mesh // .task = task // Additionally, the file names may end in ..glsl and ..hlsl // where is one of the stages listed above. // EShLanguage FindLanguage(const std::string& name, bool parseStageName) { std::string stageName; if (shaderStageName) stageName = shaderStageName; else if (parseStageName) { // Note: "first" extension means "first from the end", i.e. // if the file is named foo.vert.glsl, then "glsl" is first, // "vert" is second. size_t firstExtStart = name.find_last_of("."); bool hasFirstExt = firstExtStart != std::string::npos; size_t secondExtStart = hasFirstExt ? name.find_last_of(".", firstExtStart - 1) : std::string::npos; bool hasSecondExt = secondExtStart != std::string::npos; std::string firstExt = name.substr(firstExtStart + 1, std::string::npos); bool usesUnifiedExt = hasFirstExt && (firstExt == "glsl" || firstExt == "hlsl"); if (usesUnifiedExt && firstExt == "hlsl") Options |= EOptionReadHlsl; if (hasFirstExt && !usesUnifiedExt) stageName = firstExt; else if (usesUnifiedExt && hasSecondExt) stageName = name.substr(secondExtStart + 1, firstExtStart - secondExtStart - 1); else { usage(); return EShLangVertex; } } else stageName = name; if (stageName == "vert") return EShLangVertex; else if (stageName == "tesc") return EShLangTessControl; else if (stageName == "tese") return EShLangTessEvaluation; else if (stageName == "geom") return EShLangGeometry; else if (stageName == "frag") return EShLangFragment; else if (stageName == "comp") return EShLangCompute; else if (stageName == "rgen") return EShLangRayGen; else if (stageName == "rint") return EShLangIntersect; else if (stageName == "rahit") return EShLangAnyHit; else if (stageName == "rchit") return EShLangClosestHit; else if (stageName == "rmiss") return EShLangMiss; else if (stageName == "rcall") return EShLangCallable; else if (stageName == "mesh") return EShLangMeshNV; else if (stageName == "task") return EShLangTaskNV; usage(); return EShLangVertex; } // // Read a file's data into a string, and compile it using the old interface ShCompile, // for non-linkable results. // void CompileFile(const char* fileName, ShHandle compiler) { int ret = 0; char* shaderString; if ((Options & EOptionStdin) != 0) { std::istreambuf_iterator begin(std::cin), end; std::string tempString(begin, end); shaderString = strdup(tempString.c_str()); } else { shaderString = ReadFileData(fileName); } // move to length-based strings, rather than null-terminated strings int* lengths = new int[1]; lengths[0] = (int)strlen(shaderString); EShMessages messages = EShMsgDefault; SetMessageOptions(messages); if (UserPreamble.isSet()) Error("-D and -U options require -l (linking)\n"); for (int i = 0; i < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++i) { for (int j = 0; j < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++j) { // ret = ShCompile(compiler, shaderStrings, NumShaderStrings, lengths, EShOptNone, &Resources, Options, (Options & EOptionDefaultDesktop) ? 110 : 100, false, messages); ret = ShCompile(compiler, &shaderString, 1, nullptr, EShOptNone, &Resources, Options, (Options & EOptionDefaultDesktop) ? 110 : 100, false, messages); // const char* multi[12] = { "# ve", "rsion", " 300 e", "s", "\n#err", // "or should be l", "ine 1", "string 5\n", "float glo", "bal", // ";\n#error should be line 2\n void main() {", "global = 2.3;}" }; // const char* multi[7] = { "/", "/", "\\", "\n", "\n", "#", "version 300 es" }; // ret = ShCompile(compiler, multi, 7, nullptr, EShOptNone, &Resources, Options, (Options & EOptionDefaultDesktop) ? 110 : 100, false, messages); } if (Options & EOptionMemoryLeakMode) glslang::OS_DumpMemoryCounters(); } delete [] lengths; FreeFileData(shaderString); if (ret == 0) CompileFailed = true; } // // print usage to stdout // void usage() { printf("Usage: glslangValidator [option]... [file]...\n" "\n" "'file' can end in . for auto-stage classification, where is:\n" " .conf to provide a config file that replaces the default configuration\n" " (see -c option below for generating a template)\n" " .vert for a vertex shader\n" " .tesc for a tessellation control shader\n" " .tese for a tessellation evaluation shader\n" " .geom for a geometry shader\n" " .frag for a fragment shader\n" " .comp for a compute shader\n" " .mesh for a mesh shader\n" " .task for a task shader\n" " .rgen for a ray generation shader\n" " .rint for a ray intersection shader\n" " .rahit for a ray any hit shader\n" " .rchit for a ray closest hit shader\n" " .rmiss for a ray miss shader\n" " .rcall for a ray callable shader\n" " .glsl for .vert.glsl, .tesc.glsl, ..., .comp.glsl compound suffixes\n" " .hlsl for .vert.hlsl, .tesc.hlsl, ..., .comp.hlsl compound suffixes\n" "\n" "Options:\n" " -C cascading errors; risk crash from accumulation of error recoveries\n" " -D input is HLSL (this is the default when any suffix is .hlsl)\n" " -D | --define-macro | --D \n" " define a pre-processor macro\n" " -E print pre-processed GLSL; cannot be used with -l;\n" " errors will appear on stderr\n" " -G[ver] create SPIR-V binary, under OpenGL semantics; turns on -l;\n" " default file name is .spv (-o overrides this);\n" " 'ver', when present, is the version of the input semantics,\n" " which will appear in #define GL_SPIRV ver;\n" " '--client opengl100' is the same as -G100;\n" " a '--target-env' for OpenGL will also imply '-G';\n" " currently only supports GLSL\n" " -H print human readable form of SPIR-V; turns on -V\n" " -I add dir to the include search path; includer's directory\n" " is searched first, followed by left-to-right order of -I\n" " -Od disables optimization; may cause illegal SPIR-V for HLSL\n" " -Os optimizes SPIR-V to minimize size\n" " -R use relaxed verification rules for generating Vulkan SPIR-V,\n" " allowing the use of default uniforms, atomic_uints, and\n" " gl_VertexID and gl_InstanceID keywords.\n" " -S uses specified stage rather than parsing the file extension\n" " choices for are vert, tesc, tese, geom, frag, or comp\n" " -U | --undef-macro | --U \n" " undefine a pre-processor macro\n" " -V[ver] create SPIR-V binary, under Vulkan semantics; turns on -l;\n" " default file name is .spv (-o overrides this)\n" " 'ver', when present, is the version of the input semantics,\n" " which will appear in #define VULKAN ver\n" " '--client vulkan100' is the same as -V100\n" " a '--target-env' for Vulkan will also imply '-V'\n" " -c configuration dump;\n" " creates the default configuration file (redirect to a .conf file)\n" " -d default to desktop (#version 110) when there is no shader #version\n" " (default is ES version 100)\n" " -e | --entry-point \n" " specify as the entry-point function name\n" " -f{hlsl_functionality1}\n" " 'hlsl_functionality1' enables use of the\n" " SPV_GOOGLE_hlsl_functionality1 extension\n" " -g generate debug information\n" " -g0 strip debug information\n" " -h print this usage message\n" " -i intermediate tree (glslang AST) is printed out\n" " -l link all input files together to form a single module\n" " -m memory leak mode\n" " -o save binary to , requires a binary option (e.g., -V)\n" " -q dump reflection query database; requires -l for linking\n" " -r | --relaxed-errors" " relaxed GLSL semantic error-checking mode\n" " -s silence syntax and semantic error reporting\n" " -t multi-threaded mode\n" " -v | --version\n" " print version strings\n" " -w | --suppress-warnings\n" " suppress GLSL warnings, except as required by \"#extension : warn\"\n" " -x save binary output as text-based 32-bit hexadecimal numbers\n" " -u: specify a uniform location override for --aml\n" " --uniform-base set a base to use for generated uniform locations\n" " --auto-map-bindings | --amb automatically bind uniform variables\n" " without explicit bindings\n" " --auto-map-locations | --aml automatically locate input/output lacking\n" " 'location' (fragile, not cross stage)\n" " --auto-sampled-textures Removes sampler variables and converts\n" " existing textures to sampled textures\n" " --client {vulkan|opengl} see -V and -G\n" " --depfile writes depfile for build systems\n" " --dump-builtin-symbols prints builtin symbol table prior each compile\n" " -dumpfullversion | -dumpversion print bare major.minor.patchlevel\n" " --flatten-uniform-arrays | --fua flatten uniform texture/sampler arrays to\n" " scalars\n" " --glsl-version {100 | 110 | 120 | 130 | 140 | 150 |\n" " 300es | 310es | 320es | 330\n" " 400 | 410 | 420 | 430 | 440 | 450 | 460}\n" " set GLSL version, overrides #version\n" " in shader sourcen\n" " --hlsl-offsets allow block offsets to follow HLSL rules\n" " works independently of source language\n" " --hlsl-iomap perform IO mapping in HLSL register space\n" " --hlsl-enable-16bit-types allow 16-bit types in SPIR-V for HLSL\n" " --hlsl-dx9-compatible interprets sampler declarations as a\n" " texture/sampler combo like DirectX9 would,\n" " and recognizes DirectX9-specific semantics\n" " --hlsl-dx-position-w W component of SV_Position in HLSL fragment\n" " shaders compatible with DirectX\n" " --invert-y | --iy invert position.Y output in vertex shader\n" " --enhanced-msgs print more readable error messages (GLSL only)\n" " --keep-uncalled | --ku don't eliminate uncalled functions\n" " --nan-clamp favor non-NaN operand in min, max, and clamp\n" " --no-storage-format | --nsf use Unknown image format\n" " --quiet do not print anything to stdout, unless\n" " requested by another option\n" " --reflect-strict-array-suffix use strict array suffix rules when\n" " reflecting\n" " --reflect-basic-array-suffix arrays of basic types will have trailing [0]\n" " --reflect-intermediate-io reflection includes inputs/outputs of linked\n" " shaders rather than just vertex/fragment\n" " --reflect-separate-buffers reflect buffer variables and blocks\n" " separately to uniforms\n" " --reflect-all-block-variables reflect all variables in blocks, whether\n" " inactive or active\n" " --reflect-unwrap-io-blocks unwrap input/output blocks the same as\n" " uniform blocks\n" " --resource-set-binding [stage] name set binding\n" " set descriptor set and binding for\n" " individual resources\n" " --resource-set-binding [stage] set\n" " set descriptor set for all resources\n" " --rsb synonym for --resource-set-binding\n" " --set-block-backing name {uniform|buffer|push_constant}\n" " changes the backing type of a uniform, buffer,\n" " or push_constant block declared in\n" " in the program, when using -R option.\n" " This can be used to change the backing\n" " for existing blocks as well as implicit ones\n" " such as 'gl_DefaultUniformBlock'.\n" " --sbs synonym for set-block-storage\n" " --set-atomic-counter-block name set\n" " set name, and descriptor set for\n" " atomic counter blocks, with -R opt\n" " --sacb synonym for set-atomic-counter-block\n" " --set-default-uniform-block name set binding\n" " set name, descriptor set, and binding for\n" " global default-uniform-block, with -R opt\n" " --sdub synonym for set-default-uniform-block\n" " --shift-image-binding [stage] num\n" " base binding number for images (uav)\n" " --shift-image-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --sib synonym for --shift-image-binding\n" " --shift-sampler-binding [stage] num\n" " base binding number for samplers\n" " --shift-sampler-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --ssb synonym for --shift-sampler-binding\n" " --shift-ssbo-binding [stage] num base binding number for SSBOs\n" " --shift-ssbo-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --sbb synonym for --shift-ssbo-binding\n" " --shift-texture-binding [stage] num\n" " base binding number for textures\n" " --shift-texture-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --stb synonym for --shift-texture-binding\n" " --shift-uav-binding [stage] num base binding number for UAVs\n" " --shift-uav-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --suavb synonym for --shift-uav-binding\n" " --shift-UBO-binding [stage] num base binding number for UBOs\n" " --shift-UBO-binding [stage] [num set]...\n" " per-descriptor-set shift values\n" " --sub synonym for --shift-UBO-binding\n" " --shift-cbuffer-binding | --scb synonyms for --shift-UBO-binding\n" " --spirv-dis output standard-form disassembly; works only\n" " when a SPIR-V generation option is also used\n" " --spirv-val execute the SPIRV-Tools validator\n" " --source-entrypoint the given shader source function is\n" " renamed to be the given in -e\n" " --sep synonym for --source-entrypoint\n" " --stdin read from stdin instead of from a file;\n" " requires providing the shader stage using -S\n" " --target-env {vulkan1.0 | vulkan1.1 | vulkan1.2 | vulkan1.3 | opengl |\n" " spirv1.0 | spirv1.1 | spirv1.2 | spirv1.3 | spirv1.4 |\n" " spirv1.5 | spirv1.6}\n" " Set the execution environment that the\n" " generated code will be executed in.\n" " Defaults to:\n" " * vulkan1.0 under --client vulkan\n" " * opengl under --client opengl\n" " * spirv1.0 under --target-env vulkan1.0\n" " * spirv1.3 under --target-env vulkan1.1\n" " * spirv1.5 under --target-env vulkan1.2\n" " * spirv1.6 under --target-env vulkan1.3\n" " Multiple --target-env can be specified.\n" " --variable-name \n" " --vn creates a C header file that contains a\n" " uint32_t array named \n" " initialized with the shader binary code\n" ); exit(EFailUsage); } #if !defined _MSC_VER && !defined MINGW_HAS_SECURE_API #include int fopen_s( FILE** pFile, const char* filename, const char* mode ) { if (!pFile || !filename || !mode) { return EINVAL; } FILE* f = fopen(filename, mode); if (! f) { if (errno != 0) { return errno; } else { return ENOENT; } } *pFile = f; return 0; } #endif // // Malloc a string of sufficient size and read a string into it. // char* ReadFileData(const char* fileName) { FILE *in = nullptr; int errorCode = fopen_s(&in, fileName, "r"); if (errorCode || in == nullptr) Error("unable to open input file"); int count = 0; while (fgetc(in) != EOF) count++; fseek(in, 0, SEEK_SET); char* return_data = (char*)malloc(count + 1); // freed in FreeFileData() if ((int)fread(return_data, 1, count, in) != count) { free(return_data); Error("can't read input file"); } return_data[count] = '\0'; fclose(in); return return_data; } void FreeFileData(char* data) { free(data); } void InfoLogMsg(const char* msg, const char* name, const int num) { if (num >= 0 ) printf("#### %s %s %d INFO LOG ####\n", msg, name, num); else printf("#### %s %s INFO LOG ####\n", msg, name); }