Add spirv compiler wrapper

This commit is contained in:
Stenzek 2023-08-06 01:53:29 +10:00
parent 51aa5c5760
commit 61fbf7a533
3 changed files with 226 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com>
// 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 <cstring>
#include <memory>
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<SPIRVCodeVector> 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::SPIRVCodeVector>
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<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage);
std::unique_ptr<glslang::TProgram> program;
glslang::TShader::ForbidIncluder includer;
const EProfile profile = ECoreProfile;
const EShMessages messages =
static_cast<EShMessages>(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<int>(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<glslang::TProgram>();
program->addShader(shader.get());
if (!program->link(messages))
{
DumpBadShader("Failed to link program");
return std::nullopt;
}
glslang::TIntermediate* intermediate = program->getIntermediate(static_cast<EShLanguage>(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::SPIRVCodeVector> SPIRVCompiler::CompileVertexShader(std::string_view source_code,
u32 options)
{
return CompileShaderToSPV(EShLangVertex, "vs", source_code, options);
}
std::optional<SPIRVCompiler::SPIRVCodeVector> SPIRVCompiler::CompileFragmentShader(std::string_view source_code,
u32 options)
{
return CompileShaderToSPV(EShLangFragment, "ps", source_code, options);
}
std::optional<SPIRVCompiler::SPIRVCodeVector> SPIRVCompiler::CompileComputeShader(std::string_view source_code,
u32 options)
{
return CompileShaderToSPV(EShLangCompute, "cs", source_code, options);
}
std::optional<SPIRVCompiler::SPIRVCodeVector> 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<std::string> SPIRVCompiler::CompileSPIRVToMSL(gsl::span<const SPIRVCodeType> spv)
{
spirv_cross::CompilerMSL compiler(spv.data(), spv.size());
std::string msl = compiler.compile();
return (msl.empty()) ? std::optional<std::string>() : std::optional<std::string>(std::move(msl));
}

View File

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/types.h"
#include "gsl/span"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
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<SPIRVCodeType>;
// Compile a vertex shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileVertexShader(std::string_view source_code, u32 options);
// Compile a fragment shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileFragmentShader(std::string_view source_code, u32 options);
// Compile a compute shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileComputeShader(std::string_view source_code, u32 options);
std::optional<SPIRVCodeVector> CompileShader(GPUShaderStage stage, std::string_view source_code, u32 options);
#ifdef __APPLE__
// Converts a SPIR-V shader into MSL.
std::optional<std::string> CompileSPIRVToMSL(gsl::span<const SPIRVCodeType> spv);
#endif
} // namespace SPIRVCompiler