/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2021 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "common/Vulkan/ShaderCompiler.h" #include "common/Vulkan/Util.h" #include "common/Assertions.h" #include "common/Console.h" #include "common/StringUtil.h" #include #include #include // glslang includes #include "SPIRV/GlslangToSpv.h" #include "StandAlone/ResourceLimits.h" #include "glslang/Public/ShaderLang.h" namespace Vulkan::ShaderCompiler { // Registers itself for cleanup via atexit bool InitializeGlslang(); static unsigned s_next_bad_shader_id = 1; static bool glslang_initialized = false; static std::optional CompileShaderToSPV( EShLanguage stage, const char* stage_filename, std::string_view source, bool debug) { if (!InitializeGlslang()) return std::nullopt; std::unique_ptr shader = std::make_unique(stage); std::unique_ptr program; glslang::TShader::ForbidIncluder includer; const EProfile profile = ECoreProfile; const EShMessages messages = static_cast(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules | (debug ? EShMsgDebugInfo : 0)); const int default_version = 450; std::string full_source_code; const char* pass_source_code = source.data(); int pass_source_code_length = static_cast(source.size()); shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); auto DumpBadShader = [&](const char* msg) { std::string filename = StringUtil::StdStringFromFormat("pcsx2_bad_shader_%u.txt", s_next_bad_shader_id++); Console.Error("CompileShaderToSPV: %s, writing to %s", msg, filename.c_str()); std::ofstream ofs(filename.c_str(), std::ofstream::out | std::ofstream::binary); if (ofs.is_open()) { ofs << source; ofs << "\n"; ofs << msg << std::endl; ofs << "Shader Info Log:" << std::endl; ofs << shader->getInfoLog() << std::endl; ofs << shader->getInfoDebugLog() << std::endl; if (program) { ofs << "Program Info Log:" << std::endl; ofs << program->getInfoLog() << std::endl; ofs << program->getInfoDebugLog() << std::endl; } ofs.close(); } }; if (!shader->parse( &glslang::DefaultTBuiltInResource, default_version, profile, false, true, messages, includer)) { DumpBadShader("Failed to parse shader"); return std::nullopt; } // Even though there's only a single shader, we still need to link it to generate SPV program = std::make_unique(); program->addShader(shader.get()); if (!program->link(messages)) { DumpBadShader("Failed to link program"); return std::nullopt; } glslang::TIntermediate* intermediate = program->getIntermediate(stage); if (!intermediate) { DumpBadShader("Failed to generate SPIR-V"); return std::nullopt; } SPIRVCodeVector out_code; spv::SpvBuildLogger logger; glslang::SpvOptions options; options.generateDebugInfo = debug; glslang::GlslangToSpv(*intermediate, out_code, &logger, &options); // Write out messages if (std::strlen(shader->getInfoLog()) > 0) Console.Warning("Shader info log: %s", shader->getInfoLog()); if (std::strlen(shader->getInfoDebugLog()) > 0) Console.Warning("Shader debug info log: %s", shader->getInfoDebugLog()); if (std::strlen(program->getInfoLog()) > 0) Console.Warning("Program info log: %s", program->getInfoLog()); if (std::strlen(program->getInfoDebugLog()) > 0) Console.Warning("Program debug info log: %s", program->getInfoDebugLog()); std::string spv_messages = logger.getAllMessages(); if (!spv_messages.empty()) Console.Warning("SPIR-V conversion messages: %s", spv_messages.c_str()); return out_code; } bool InitializeGlslang() { if (glslang_initialized) return true; if (!glslang::InitializeProcess()) { pxFailRel("Failed to initialize glslang shader compiler"); return false; } std::atexit(DeinitializeGlslang); glslang_initialized = true; return true; } void DeinitializeGlslang() { if (!glslang_initialized) return; glslang::FinalizeProcess(); glslang_initialized = false; } std::optional CompileVertexShader(std::string_view source_code, bool debug) { return CompileShaderToSPV(EShLangVertex, "vs", source_code, debug); } std::optional CompileGeometryShader(std::string_view source_code, bool debug) { return CompileShaderToSPV(EShLangGeometry, "gs", source_code, debug); } std::optional CompileFragmentShader(std::string_view source_code, bool debug) { return CompileShaderToSPV(EShLangFragment, "ps", source_code, debug); } std::optional CompileComputeShader(std::string_view source_code, bool debug) { return CompileShaderToSPV(EShLangCompute, "cs", source_code, debug); } std::optional CompileShader(Type type, std::string_view source_code, bool debug) { switch (type) { case Type::Vertex: return CompileShaderToSPV(EShLangVertex, "vs", source_code, debug); case Type::Geometry: return CompileShaderToSPV(EShLangGeometry, "gs", source_code, debug); case Type::Fragment: return CompileShaderToSPV(EShLangFragment, "ps", source_code, debug); case Type::Compute: return CompileShaderToSPV(EShLangCompute, "cs", source_code, debug); default: return std::nullopt; } } } // namespace Vulkan::ShaderCompiler