From 61fbf7a53336f48d4507ff10f611fa1a72814cd3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 6 Aug 2023 01:53:29 +1000 Subject: [PATCH] Add spirv compiler wrapper --- src/core/CMakeLists.txt | 11 +++ src/core/gpu/spirv_compiler.cpp | 168 ++++++++++++++++++++++++++++++++ src/core/gpu/spirv_compiler.h | 47 +++++++++ 3 files changed, 226 insertions(+) create mode 100644 src/core/gpu/spirv_compiler.cpp create mode 100644 src/core/gpu/spirv_compiler.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9a88bfd4d..265b2e87a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -283,6 +283,17 @@ if(ENABLE_OPENGL) endif() endif() +if(ENABLE_VULKAN OR APPLE) + target_sources(core PRIVATE + gpu/spirv_compiler.cpp + gpu/spirv_compiler.h + ) + target_link_libraries(core PRIVATE glslang) + if(APPLE) + target_link_libraries(core PRIVATE spirv-cross) + endif() +endif() + if(ENABLE_VULKAN) target_sources(core PRIVATE gpu/vulkan/builders.cpp diff --git a/src/core/gpu/spirv_compiler.cpp b/src/core/gpu/spirv_compiler.cpp new file mode 100644 index 000000000..b45d42e46 --- /dev/null +++ b/src/core/gpu/spirv_compiler.cpp @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "spirv_compiler.h" +#include "gpu_device.h" + +#include "../settings.h" + +#include "common/assert.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/path.h" +#include "common/string_util.h" + +#include +#include +Log_SetChannel(SPIRVCompiler); + +// glslang includes +#include "SPIRV/GlslangToSpv.h" +#include "StandAlone/ResourceLimits.h" +#include "glslang/Public/ShaderLang.h" + +#ifdef __APPLE__ +#include "spirv-cross/spirv_cross.hpp" +#include "spirv-cross/spirv_msl.hpp" +#endif + +namespace SPIRVCompiler { +static std::optional CompileShaderToSPV(EShLanguage stage, const char* stage_filename, + std::string_view source, u32 options); + +static unsigned s_next_bad_shader_id = 1; +} // namespace SPIRVCompiler + +std::optional +SPIRVCompiler::CompileShaderToSPV(EShLanguage stage, const char* stage_filename, std::string_view source, u32 options) +{ + static bool glslang_initialized = false; + if (!glslang_initialized) + { + if (!glslang::InitializeProcess()) + { + Panic("Failed to initialize glslang shader compiler"); + return std::nullopt; + } + + std::atexit(&glslang::FinalizeProcess); + glslang_initialized = true; + } + + 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 | ((options & VulkanRules) ? EShMsgVulkanRules : 0) | + ((options & DebugInfo) ? 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) { + const std::string filename = + Path::Combine(EmuFolders::DataRoot, fmt::format("bad_shader_{}.txt", s_next_bad_shader_id++)); + Log_ErrorPrintf("CompileShaderToSPV: %s, writing to %s", msg, filename.c_str()); + + auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb"); + if (fp) + { + std::fwrite(source.data(), source.size(), 1, fp.get()); + std::fprintf(fp.get(), "\n\n%s\n", msg); + std::fprintf(fp.get(), "Shader Info Log:\n%s\n%s\n", shader->getInfoLog(), shader->getInfoDebugLog()); + if (program) + std::fprintf(fp.get(), "Program Info Log:%s\n%s\n", program->getInfoLog(), program->getInfoDebugLog()); + } + }; + + 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(static_cast(stage)); + if (!intermediate) + { + DumpBadShader("Failed to generate SPIR-V"); + return std::nullopt; + } + + SPIRVCodeVector out_code; + spv::SpvBuildLogger logger; + glslang::SpvOptions spvoptions; + spvoptions.generateDebugInfo = (options & DebugInfo) != 0; + glslang::GlslangToSpv(*intermediate, out_code, &logger, &spvoptions); + + // Write out messages + if (std::strlen(shader->getInfoLog()) > 0) + Log_WarningPrintf("Shader info log: %s", shader->getInfoLog()); + if (std::strlen(shader->getInfoDebugLog()) > 0) + Log_WarningPrintf("Shader debug info log: %s", shader->getInfoDebugLog()); + if (std::strlen(program->getInfoLog()) > 0) + Log_WarningPrintf("Program info log: %s", program->getInfoLog()); + if (std::strlen(program->getInfoDebugLog()) > 0) + Log_WarningPrintf("Program debug info log: %s", program->getInfoDebugLog()); + std::string spv_messages = logger.getAllMessages(); + if (!spv_messages.empty()) + Log_WarningPrintf("SPIR-V conversion messages: %s", spv_messages.c_str()); + + return out_code; +} + +std::optional SPIRVCompiler::CompileVertexShader(std::string_view source_code, + u32 options) +{ + return CompileShaderToSPV(EShLangVertex, "vs", source_code, options); +} + +std::optional SPIRVCompiler::CompileFragmentShader(std::string_view source_code, + u32 options) +{ + return CompileShaderToSPV(EShLangFragment, "ps", source_code, options); +} + +std::optional SPIRVCompiler::CompileComputeShader(std::string_view source_code, + u32 options) +{ + return CompileShaderToSPV(EShLangCompute, "cs", source_code, options); +} + +std::optional SPIRVCompiler::CompileShader(GPUShaderStage type, + std::string_view source_code, u32 options) +{ + switch (type) + { + case GPUShaderStage::Vertex: + return CompileShaderToSPV(EShLangVertex, "vs", source_code, options); + + case GPUShaderStage::Fragment: + return CompileShaderToSPV(EShLangFragment, "ps", source_code, options); + + case GPUShaderStage::Compute: + return CompileShaderToSPV(EShLangCompute, "cs", source_code, options); + + default: + return std::nullopt; + } +} + +std::optional SPIRVCompiler::CompileSPIRVToMSL(gsl::span spv) +{ + spirv_cross::CompilerMSL compiler(spv.data(), spv.size()); + std::string msl = compiler.compile(); + return (msl.empty()) ? std::optional() : std::optional(std::move(msl)); +} diff --git a/src/core/gpu/spirv_compiler.h b/src/core/gpu/spirv_compiler.h new file mode 100644 index 000000000..8361c0862 --- /dev/null +++ b/src/core/gpu/spirv_compiler.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +#include "common/types.h" + +#include "gsl/span" + +#include +#include +#include +#include + +enum class GPUShaderStage : u8; + +namespace SPIRVCompiler { + +enum CompileOptions +{ + DebugInfo = (1 << 0), + VulkanRules = (1 << 1), +}; + +// SPIR-V compiled code type +using SPIRVCodeType = u32; +using SPIRVCodeVector = std::vector; + +// Compile a vertex shader to SPIR-V. +std::optional CompileVertexShader(std::string_view source_code, u32 options); + +// Compile a fragment shader to SPIR-V. +std::optional CompileFragmentShader(std::string_view source_code, u32 options); + +// Compile a compute shader to SPIR-V. +std::optional CompileComputeShader(std::string_view source_code, u32 options); + +std::optional CompileShader(GPUShaderStage stage, std::string_view source_code, u32 options); + +#ifdef __APPLE__ + +// Converts a SPIR-V shader into MSL. +std::optional CompileSPIRVToMSL(gsl::span spv); + +#endif + +} // namespace SPIRVCompiler