// slang-glslang.cpp #include "slang-glslang.h" #include "StandAlone/ResourceLimits.h" #include "StandAlone/Worklist.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 "OGLCompilersDLL/InitializeDll.h" #include "../../slang.h" #include "spirv-tools/optimizer.hpp" #include "spirv-tools/libspirv.h" #ifdef _WIN32 # include #endif #include #include // This is a wrapper to allow us to run the `glslang` compiler // in a controlled fashion. #define UNLIMITED 9999 static TBuiltInResource _calcBuiltinResources() { // NOTE! This is a bit of a hack - to set all the fields to true/UNLIMITED. // Care must be taken if new variables are introduced, the default may not be appropriate. // We are relying on limits being after the other fields. SLANG_COMPILE_TIME_ASSERT(SLANG_OFFSET_OF(TBuiltInResource, limits) > 0); // We are relying on maxLights being the first parameter, and all values will have the same type SLANG_COMPILE_TIME_ASSERT(SLANG_OFFSET_OF(TBuiltInResource, maxLights) == 0); TBuiltInResource resource; // Set up all the integer values. { auto* dst = &resource.maxLights; const size_t count = SLANG_OFFSET_OF(TBuiltInResource, limits) / sizeof(*dst); for (size_t i = 0; i < count; ++i) { dst[i] = UNLIMITED; } } // In the sea of variables there is a min value resource.minProgramTexelOffset = -UNLIMITED; // Set up the bools { TLimits* limits = &resource.limits; bool* dst = (bool*)limits; const size_t count = sizeof(TLimits) / sizeof(bool); for (size_t i = 0; i < count; ++i) { dst[i] = true; } } return resource; } static TBuiltInResource gResources = _calcBuiltinResources(); static void dump( void const* data, size_t size, glslang_OutputFunc outputFunc, void* outputUserData, FILE* fallbackStream) { if( outputFunc ) { outputFunc(data, size, outputUserData); } else { fwrite(data, 1, size, fallbackStream); // also output it for debug purposes std::string str((char const*)data, size); #ifdef _WIN32 OutputDebugStringA(str.c_str()); #else fprintf(stderr, "%s\n", str.c_str());; #endif } } static void dumpDiagnostics( const glslang_CompileRequest_1_1& request, std::string const& log) { dump(log.c_str(), log.length(), request.diagnosticFunc, request.diagnosticUserData, stderr); } struct SPIRVOptimizationDiagnostic { std::string toString() const { std::ostringstream out; switch (level) { case SPV_MSG_FATAL: case SPV_MSG_INTERNAL_ERROR: case SPV_MSG_ERROR: out << "error: "; break; case SPV_MSG_WARNING: out << "warning: "; break; case SPV_MSG_INFO: case SPV_MSG_DEBUG: out << "info: "; break; default: break; } if (source.length()) { out << source << ":"; } out << position.line << ":" << position.column << ":" << position.index << ":"; if (message.length()) { out << " " << message; } return out.str(); } spv_message_level_t level; std::string source; spv_position_t position; std::string message; }; // Apply the SPIRV-Tools optimizer to generated SPIR-V based on the desired optimization level // TODO: add flag for optimizing SPIR-V size as well static void glslang_optimizeSPIRV(spv_target_env targetEnv, const glslang_CompileRequest_1_1& request, std::vector& outDiags, std::vector& ioSpirv) { const auto optimizationLevel = request.optimizationLevel; // If there is no optimization then we are done if (optimizationLevel == SLANG_OPTIMIZATION_LEVEL_NONE) { return; } const auto debugInfoType = request.debugInfoType; spvtools::Optimizer optimizer(targetEnv); optimizer.SetMessageConsumer( [&](spv_message_level_t level, const char* source, const spv_position_t& position, const char* message) { SPIRVOptimizationDiagnostic diag; diag.level = level; if (source) { diag.source = source; } diag.position = position; if (message) { diag.message = message; } outDiags.push_back(diag); }); // If debug info is being generated, propagate // line information into all SPIR-V instructions. This avoids loss of // information when instructions are deleted or moved. Later, remove // redundant information to minimize final SPRIR-V size. if (debugInfoType != SLANG_DEBUG_INFO_LEVEL_NONE) { optimizer.RegisterPass(spvtools::CreatePropagateLineInfoPass()); } spvtools::OptimizerOptions spvOptOptions; // To compile some large shaders the default is not enough. // That although this limit is exceeded, the final optimized output is typically well // within the range. // // See kDefaultMaxIdBound for description of this limit. // // If a compilation produces a warning like // `0:0: ID overflow. Try running compact-ids.` // it might be fixable by raising the multiplier to a larger value. spvOptOptions.set_max_id_bound(kDefaultMaxIdBound * 4); // TODO confirm which passes we want to invoke for each level switch (optimizationLevel) { default: case SLANG_OPTIMIZATION_LEVEL_DEFAULT: { // Use a minimal set of performance settings // If we run CreateInlineExhaustivePass, We need to run CreateMergeReturnPass first. #if 0 // This is the previous 'default optimization' passes setting for glslang optimizer.RegisterPass(spvtools::CreateMergeReturnPass()); optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass()); optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(100)); optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); #elif 1 // 6Mb 27 secs (all passes up to 9) // 9Mb 25 secs (all passes up to 7) // 8Mb 15 secs (all passes) -(5,6,7) // 6Mb 15 secs (all passes) -(6,7) // This list of passes takes the previous 'default optimization' // passes (as listed above) and tries to combine them in order with the 'new' passes below. // The issue with the passes below is that although it produces smaller SPIR-V fairly quickly // it can cause serious problem on some drivers. // // Across a wide range of compilations this produced SPIR-V that is less than half size of the // previous -O1 passes above. optimizer.RegisterPass(spvtools::CreateWrapOpKillPass()); // 1 optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 2 optimizer.RegisterPass(spvtools::CreateMergeReturnPass()); optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass()); optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); // 3 optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass()); optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(100)); optimizer.RegisterPass(spvtools::CreateCCPPass()); // 4 * optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 5 //optimizer.RegisterPass(spvtools::CreateIfConversionPass()); // 6 //optimizer.RegisterPass(spvtools::CreateBlockMergePass()); // 7 * optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); // 8 optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateVectorDCEPass()); // 9 #else // The following selection of passes was created by // 1) Taking the list of passes from optimizer.RegisterSizePasses // 2) Disable/enable passes to try to produce some reasonable combination of low SPIR-V output size and compilation speed // // For a particularly difficult glsl shader this produced 1/3 SPIR-V code (against previous -O1), in around 13th the time (against -O3 option) // Over a wide range of compiles the SPIR-V is around 6% larger than -O3 // The following comments describe the path to finding this combination. The original compilation produces 18Mb SPIR-V binaries // in around 3 1/2 mins. The integer number increases with the ordering of the test. // // With 5 47s // With 6 we have 6Mb, and 38 seconds // With 7 we have 6Mb and 26 seconds // With 8 we have 6Mb in 18 seconds // 9 didn't improve perf or size // With 10 we have 6Mb in 16.8 // With 11 we have 6Mb in 16.1 // With 12 we have 6Mb in 15.6 // With 13 didn't improve // With 14 slightly larger, slightly smaller, so leave // Try 15 - Adding one and removing the other, makes things much worse // Without any SSA rewrite we are up to 6Mb. 48 // // So (for test case) approximately 13x compilation speed. // Binary twice the size of smallest SPIR-V size and 1/3 the size of the previous -O size optimizer.RegisterPass(spvtools::CreateWrapOpKillPass()); optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 15 optimizer.RegisterPass(spvtools::CreateMergeReturnPass()); optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass()); optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); // 9 optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass()); //optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(0)); // 12 //optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass()); optimizer.RegisterPass(spvtools::CreateCCPPass()); //optimizer.RegisterPass(spvtools::CreateLoopUnrollPass(true)); // 1 //optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 4 //optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 11 optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(0)); //optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass()); //optimizer.RegisterPass(spvtools::CreateIfConversionPass()); // 7 optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 13 //optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); // 10 //optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 6 + 15 //optimizer.RegisterPass(spvtools::CreateBlockMergePass()); // 8 optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); // 5 //optimizer.RegisterPass(spvtools::CreateCopyPropagateArraysPass()); // 1 optimizer.RegisterPass(spvtools::CreateVectorDCEPass()); optimizer.RegisterPass(spvtools::CreateDeadInsertElimPass()); optimizer.RegisterPass(spvtools::CreateEliminateDeadMembersPass()); //optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass()); //optimizer.RegisterPass(spvtools::CreateBlockMergePass()); // 3 //optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass()); // 2 //optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass()); optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 14 optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateCFGCleanupPass()); #endif break; } // TODO(JS): It would be better if we had some distinction here where 'high' meant optimize 'in a reasonable time' for // a better optimization, and 'maximal' meant compilation might take a really long time... so only use it if it's really // needed. // // Currently we just have high have the same meaning as 'maximal'. case SLANG_OPTIMIZATION_LEVEL_HIGH: case SLANG_OPTIMIZATION_LEVEL_MAXIMAL: { // Use the same passes when specifying the "-O" flag in spirv-opt // Roughly equivalent to `RegisterPerformancePasses` optimizer.RegisterPass(spvtools::CreateWrapOpKillPass()); optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); optimizer.RegisterPass(spvtools::CreateMergeReturnPass()); optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass()); optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateScalarReplacementPass()); optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); // We run CompactIdsPass here, because CreateLocalMultiStoreElimPass can explode // id usage (by a factor of 10), and compacting ids here has been shown to half // id usage with a complex shader. optimizer.RegisterPass(spvtools::CreateCompactIdsPass()); // Note that CreateLocalMultiStoreElimPass really just does a SSARewritePass optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateCCPPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateLoopUnrollPass(true)); optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass()); optimizer.RegisterPass(spvtools::CreateCombineAccessChainsPass()); optimizer.RegisterPass(spvtools::CreateSimplificationPass()); optimizer.RegisterPass(spvtools::CreateScalarReplacementPass()); optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateSSARewritePass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateVectorDCEPass()); optimizer.RegisterPass(spvtools::CreateDeadInsertElimPass()); optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); optimizer.RegisterPass(spvtools::CreateSimplificationPass()); optimizer.RegisterPass(spvtools::CreateIfConversionPass()); optimizer.RegisterPass(spvtools::CreateCopyPropagateArraysPass()); optimizer.RegisterPass(spvtools::CreateReduceLoadSizePass()); optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); optimizer.RegisterPass(spvtools::CreateBlockMergePass()); optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass()); optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); optimizer.RegisterPass(spvtools::CreateBlockMergePass()); optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // We again run compaction to try and ensure the final output uses ids that are in range. // On a complex shader, this reduced the amount ids by 5. optimizer.RegisterPass(spvtools::CreateCompactIdsPass()); break; } } if (debugInfoType != SLANG_DEBUG_INFO_LEVEL_NONE) { optimizer.RegisterPass(spvtools::CreateRedundantLineInfoElimPass()); } spvOptOptions.set_run_validator(false); // Don't run the validator by default { // Put the output optimized spirv into optSpirv std::vector optSpirv; // Optimize if (optimizer.Run(ioSpirv.data(), ioSpirv.size(), &optSpirv, spvOptOptions)) { assert(optSpirv.size() > 0); // Make the ioSpirv the optimized spirv ioSpirv.swap(optSpirv); } } } static glslang::EShTargetLanguageVersion _makeTargetLanguageVersion(int majorVersion, int minorVersion) { return glslang::EShTargetLanguageVersion((uint32_t(majorVersion) << 16) | (uint32_t(minorVersion) << 8)); } static glsl_SPIRVVersion _toSPIRVVersion(glslang::EShTargetLanguageVersion version) { glsl_SPIRVVersion ver; ver.patch = 0; ver.major = uint8_t(uint32_t(version) >> 16); ver.minor = uint8_t(uint32_t(version) >> 8); return ver; } // For working out the targets based on SPIR-V target strings namespace { // anonymous struct SPRIVTargetInfo { const char* name; spv_target_env targetEnv; }; } // anonymous static const SPRIVTargetInfo kSpirvTargetInfos[] = { {"1.0", SPV_ENV_UNIVERSAL_1_0}, {"vk1.0", SPV_ENV_VULKAN_1_0}, {"1.1", SPV_ENV_UNIVERSAL_1_1}, {"cl2.1", SPV_ENV_OPENCL_2_1}, {"cl2.2", SPV_ENV_OPENCL_2_2}, {"gl4.0", SPV_ENV_OPENGL_4_0}, {"gl4.1", SPV_ENV_OPENGL_4_1}, {"gl4.2", SPV_ENV_OPENGL_4_2}, {"gl4.3", SPV_ENV_OPENGL_4_3}, {"gl4.5", SPV_ENV_OPENGL_4_5}, {"1.2", SPV_ENV_UNIVERSAL_1_2}, {"cl1.2", SPV_ENV_OPENCL_1_2}, {"cl_emb1.2", SPV_ENV_OPENCL_EMBEDDED_1_2}, {"cl2.0", SPV_ENV_OPENCL_2_0}, {"cl_emb2.0", SPV_ENV_OPENCL_EMBEDDED_2_0}, {"cl_emb2.1", SPV_ENV_OPENCL_EMBEDDED_2_1}, {"cl_emb2.2", SPV_ENV_OPENCL_EMBEDDED_2_2}, {"1.3", SPV_ENV_UNIVERSAL_1_3}, {"vk1.1", SPV_ENV_VULKAN_1_1}, {"web_gpu1.0", SPV_ENV_WEBGPU_0}, {"1.4", SPV_ENV_UNIVERSAL_1_4}, {"vk1.1_spirv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4}, {"1.5", SPV_ENV_UNIVERSAL_1_5}, }; static int _findTargetIndex(const char* name) { const int count = int(sizeof(kSpirvTargetInfos) / sizeof(kSpirvTargetInfos[0])); for (int i = 0; i < count; ++i) { const SPRIVTargetInfo& info = kSpirvTargetInfos[i]; if (::strcmp(info.name, name) == 0) { return i; } } return -1; } static spv_target_env _getUniversalTargetEnv(glslang::EShTargetLanguageVersion inVersion) { glsl_SPIRVVersion spirvVersion = _toSPIRVVersion(inVersion); uint32_t ver = (uint32_t(spirvVersion.major) << 8) | spirvVersion.minor; switch (ver) { case 0x100: return SPV_ENV_UNIVERSAL_1_0; case 0x101: return SPV_ENV_UNIVERSAL_1_1; case 0x102: return SPV_ENV_UNIVERSAL_1_2; case 0x103: return SPV_ENV_UNIVERSAL_1_3; case 0x104: return SPV_ENV_UNIVERSAL_1_4; case 0x105: return SPV_ENV_UNIVERSAL_1_5; default: { if (ver > 0x105) { // This is the highest we known for now..., so try that return SPV_ENV_UNIVERSAL_1_5; } break; } } // Just use the default... return SPV_ENV_UNIVERSAL_1_2; } static int glslang_compileGLSLToSPIRV(const glslang_CompileRequest_1_1& request) { // Check that the encoding matches assert(glslang::EShTargetSpv_1_4 == _makeTargetLanguageVersion(1, 4)); EShLanguage glslangStage; switch( request.slangStage ) { #define CASE(SP, GL) case SLANG_STAGE_##SP: glslangStage = EShLang##GL; break CASE(VERTEX, Vertex); CASE(FRAGMENT, Fragment); CASE(GEOMETRY, Geometry); CASE(HULL, TessControl); CASE(DOMAIN, TessEvaluation); CASE(COMPUTE, Compute); CASE(RAY_GENERATION, RayGenNV); CASE(INTERSECTION, IntersectNV); CASE(ANY_HIT, AnyHitNV); CASE(CLOSEST_HIT, ClosestHitNV); CASE(MISS, MissNV); CASE(CALLABLE, CallableNV); #undef CASE default: dumpDiagnostics(request, "internal error: stage unsupported by glslang\n"); return 1; } spv_target_env targetEnv = SPV_ENV_UNIVERSAL_1_2; glslang::EShTargetLanguageVersion targetLanguage = glslang::EShTargetLanguageVersion(0); int spirvTargetIndex = -1; if (request.spirvTargetName) { spirvTargetIndex = _findTargetIndex(request.spirvTargetName); if (spirvTargetIndex < 0) { dumpDiagnostics(request, "warning: unknown SPIR-V version\n"); } else { targetEnv = kSpirvTargetInfos[spirvTargetIndex].targetEnv; } } // If a version is specified, and no target language is specified, set to universal version of that SPIR-V version if (request.spirvVersion.major != 0 && targetLanguage == glslang::EShTargetLanguageVersion(0)) { targetLanguage = _makeTargetLanguageVersion(request.spirvVersion.major, request.spirvVersion.minor); } // If we don't have a target, but do have a language, use that to determine a universal target if (spirvTargetIndex < 0 && targetLanguage != glslang::EShTargetLanguageVersion(0)) { // We can just use the appropriate universal based on the target language targetEnv = _getUniversalTargetEnv(targetLanguage); } // TODO: compute glslang stage to use glslang::TShader* shader = new glslang::TShader(glslangStage); auto shaderPtr = std::unique_ptr(shader); // Only set the target language if one is determined if (targetLanguage != glslang::EShTargetLanguageVersion(0)) { shader->setEnvTarget(glslang::EShTargetSpv, targetLanguage); } glslang::TProgram* program = new glslang::TProgram(); auto programPtr = std::unique_ptr(program); char const* sourceText = (char const*)request.inputBegin; char const* sourceTextEnd = (char const*)request.inputEnd; int sourceTextLength = (int)(sourceTextEnd - sourceText); shader->setPreamble("#extension GL_GOOGLE_cpp_style_line_directive : require\n"); shader->setStringsWithLengthsAndNames( &sourceText, &sourceTextLength, &request.sourcePath, 1); { const EShMessages messages = EShMessages(EShMsgSpvRules | EShMsgVulkanRules); if (!shader->parse(&gResources, 110, false, messages)) { dumpDiagnostics(request, shader->getInfoLog()); return 1; } program->addShader(shader); if (!program->link(messages)) { dumpDiagnostics(request, program->getInfoLog()); return 1; } if (!program->mapIO()) { dumpDiagnostics(request, program->getInfoLog()); return 1; } } for(int stage = 0; stage < EShLangCount; ++stage) { auto stageIntermediate = program->getIntermediate((EShLanguage)stage); if(!stageIntermediate) continue; std::vector spirv; spv::SpvBuildLogger logger; glslang::GlslangToSpv(*stageIntermediate, spirv, &logger); int optErrorCount = 0; if (request.optimizationLevel != SLANG_OPTIMIZATION_LEVEL_NONE) { std::vector optDiags; glslang_optimizeSPIRV(targetEnv, request, optDiags, spirv); { for (const auto& diag : optDiags) { // Count the number of errors optErrorCount += int(diag.level <= SPV_MSG_ERROR); // Note this string does not have \n. std::string diagString = diag.toString(); // Dump dump(diagString.c_str(), diagString.length(), request.diagnosticFunc, request.diagnosticUserData, stderr); } } } dumpDiagnostics(request, logger.getAllMessages()); dump(spirv.data(), spirv.size() * sizeof(unsigned int), request.outputFunc, request.outputUserData, stdout); if (optErrorCount > 0) { // It's an error... return 1; } } return 0; } static int glslang_dissassembleSPIRV(const glslang_CompileRequest_1_1& request) { typedef unsigned int SPIRVWord; SPIRVWord const* spirvBegin = (SPIRVWord const*)request.inputBegin; SPIRVWord const* spirvEnd = (SPIRVWord const*)request.inputEnd; std::vector spirv(spirvBegin, spirvEnd); std::stringstream spirvAsmStream; spv::Disassemble(spirvAsmStream, spirv); std::string result = spirvAsmStream.str(); dump(result.c_str(), result.length(), request.outputFunc, request.outputUserData, stdout); return 0; } // We need a per process initialization class ProcessInitializer { public: ProcessInitializer() { m_isInitialized = false; } bool init() { std::lock_guard guard(m_mutex); if (!m_isInitialized) { if (!glslang::InitializeProcess()) { return false; } m_isInitialized = true; } return true; } ~ProcessInitializer() { // We *assume* will only be called once dll is detatched and that will be on a single thread if (m_isInitialized) { glslang::FinalizeProcess(); } } std::mutex m_mutex; bool m_isInitialized = false; }; static int _compile(const glslang_CompileRequest_1_1& request) { int result = 0; switch (request.action) { default: result = 1; break; case GLSLANG_ACTION_COMPILE_GLSL_TO_SPIRV: result = glslang_compileGLSLToSPIRV(request); break; case GLSLANG_ACTION_DISSASSEMBLE_SPIRV: result = glslang_dissassembleSPIRV(request); break; } return result; } extern "C" #ifdef _MSC_VER _declspec(dllexport) #else __attribute__((__visibility__("default"))) #endif int glslang_compile_1_1(glslang_CompileRequest_1_1* inRequest) { static ProcessInitializer g_processInitializer; if (!g_processInitializer.init()) { // Failed return 1; } if (!glslang::InitThread()) { // Failed return 1; } // If it's the right size just use it if (inRequest->sizeInBytes == sizeof(glslang_CompileRequest_1_1)) { return _compile(*inRequest); } else { // NOTE! It could be larger, but here we'll assume thats ok, and copy and use. // Try to ensure some binary compatibility, by using sizeInBytes member, and copying glslang_CompileRequest_1_1 request; // Copy into request const size_t copySize = (inRequest->sizeInBytes > sizeof(request)) ? sizeof(request) : inRequest->sizeInBytes; ::memcpy(&request, inRequest, copySize); // Zero any remaining members memset(((uint8_t*)&request) + copySize, 0, sizeof(request) - copySize); return _compile(request); } } extern "C" #ifdef _MSC_VER _declspec(dllexport) #else __attribute__((__visibility__("default"))) #endif int glslang_compile(glslang_CompileRequest_1_0* inRequest) { glslang_CompileRequest_1_1 request; memset(&request, 0, sizeof(request)); request.sizeInBytes = sizeof(request); request.set(*inRequest); return glslang_compile_1_1(&request); }