From 3790c99a7d1f6377166049c61bbe03f93e2c3a60 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Wed, 4 May 2022 00:40:59 -0500 Subject: [PATCH] VideoCommon: add common spirv helper functions Co-authored-by: tellowkrinkle --- Source/Core/DolphinLib.props | 2 + Source/Core/VideoCommon/CMakeLists.txt | 13 ++ Source/Core/VideoCommon/Spirv.cpp | 203 +++++++++++++++++++++++++ Source/Core/VideoCommon/Spirv.h | 30 ++++ 4 files changed, 248 insertions(+) create mode 100644 Source/Core/VideoCommon/Spirv.cpp create mode 100644 Source/Core/VideoCommon/Spirv.h diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index ea6c9705dc..8fcfad4353 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -650,6 +650,7 @@ + @@ -1224,6 +1225,7 @@ + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index ec8f89c829..9751df621c 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -74,6 +74,8 @@ add_library(videocommon ShaderCache.h ShaderGenCommon.cpp ShaderGenCommon.h + Spirv.cpp + Spirv.h Statistics.cpp Statistics.h TextureCacheBase.cpp @@ -139,6 +141,7 @@ PRIVATE png xxhash imgui + glslang ) if(_M_X86) @@ -181,6 +184,16 @@ if(FFmpeg_FOUND) endif() endif() +# Silence warnings on glslang by flagging it as a system include +target_include_directories(videocommon +SYSTEM PUBLIC + ${CMAKE_SOURCE_DIR}/Externals/glslang/glslang/Public +SYSTEM PRIVATE + ${CMAKE_SOURCE_DIR}/Externals/glslang/StandAlone + ${CMAKE_SOURCE_DIR}/Externals/glslang/SPIRV + ${CMAKE_SOURCE_DIR}/Externals/glslang +) + if(MSVC) # Add precompiled header target_link_libraries(videocommon PRIVATE use_pch) diff --git a/Source/Core/VideoCommon/Spirv.cpp b/Source/Core/VideoCommon/Spirv.cpp new file mode 100644 index 0000000000..9819811367 --- /dev/null +++ b/Source/Core/VideoCommon/Spirv.cpp @@ -0,0 +1,203 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Spirv.h" + +// glslang includes +#include "GlslangToSpv.h" +#include "ResourceLimits.h" +#include "ShaderLang.h" +#include "disassemble.h" + +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Common/StringUtil.h" +#include "Common/Version.h" + +#include "VideoCommon/VideoBackendBase.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +bool InitializeGlslang() +{ + static bool glslang_initialized = false; + if (glslang_initialized) + return true; + + if (!glslang::InitializeProcess()) + { + PanicAlertFmt("Failed to initialize glslang shader compiler"); + return false; + } + + std::atexit([]() { glslang::FinalizeProcess(); }); + + glslang_initialized = true; + return true; +} + +const TBuiltInResource* GetCompilerResourceLimits() +{ + return &glslang::DefaultTBuiltInResource; +} + +std::optional +CompileShaderToSPV(EShLanguage stage, + std::optional language_version, + const char* stage_filename, std::string_view source) +{ + if (!InitializeGlslang()) + return std::nullopt; + + std::unique_ptr shader = std::make_unique(stage); + std::unique_ptr program; + glslang::TShader::ForbidIncluder includer; + EProfile profile = ECoreProfile; + EShMessages messages = static_cast(EShMsgDefault | EShMsgSpvRules); + int default_version = 450; + + const char* pass_source_code = source.data(); + int pass_source_code_length = static_cast(source.size()); + + if (language_version) + shader->setEnvTarget(glslang::EShTargetSpv, *language_version); + + shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); + + auto DumpBadShader = [&](const char* msg) { + static int counter = 0; + std::string filename = VideoBackendBase::BadShaderFilename(stage_filename, counter++); + std::ofstream stream; + File::OpenFStream(stream, filename, std::ios_base::out); + if (stream.good()) + { + stream << source << std::endl; + stream << msg << std::endl; + stream << "Shader Info Log:" << std::endl; + stream << shader->getInfoLog() << std::endl; + stream << shader->getInfoDebugLog() << std::endl; + if (program) + { + stream << "Program Info Log:" << std::endl; + stream << program->getInfoLog() << std::endl; + stream << program->getInfoDebugLog() << std::endl; + } + } + + stream << "\n"; + stream << "Dolphin Version: " + Common::GetScmRevStr() + "\n"; + stream << "Video Backend: " + g_video_backend->GetDisplayName(); + stream.close(); + + PanicAlertFmt("{} (written to {})\nDebug info:\n{}", msg, filename, shader->getInfoLog()); + }; + + if (!shader->parse(GetCompilerResourceLimits(), 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; + } + + SPIRV::CodeVector out_code; + spv::SpvBuildLogger logger; + glslang::SpvOptions options; + + if (g_ActiveConfig.bEnableValidationLayer) + { + // Attach the source code to the SPIR-V for tools like RenderDoc. + intermediate->addSourceText(pass_source_code, pass_source_code_length); + + options.generateDebugInfo = true; + options.disableOptimizer = true; + options.optimizeSize = false; + options.disassemble = false; + options.validate = true; + } + + glslang::GlslangToSpv(*intermediate, out_code, &logger, &options); + + // Write out messages + // Temporary: skip if it contains "Warning, version 450 is not yet complete; most version-specific + // features are present, but some are missing." + if (strlen(shader->getInfoLog()) > 108) + WARN_LOG_FMT(VIDEO, "Shader info log: {}", shader->getInfoLog()); + if (strlen(shader->getInfoDebugLog()) > 0) + WARN_LOG_FMT(VIDEO, "Shader debug info log: {}", shader->getInfoDebugLog()); + if (strlen(program->getInfoLog()) > 25) + WARN_LOG_FMT(VIDEO, "Program info log: {}", program->getInfoLog()); + if (strlen(program->getInfoDebugLog()) > 0) + WARN_LOG_FMT(VIDEO, "Program debug info log: {}", program->getInfoDebugLog()); + const std::string spv_messages = logger.getAllMessages(); + if (!spv_messages.empty()) + WARN_LOG_FMT(VIDEO, "SPIR-V conversion messages: {}", spv_messages); + + // Dump source code of shaders out to file if enabled. + if (g_ActiveConfig.iLog & CONF_SAVESHADERS) + { + static int counter = 0; + std::string filename = StringFromFormat("%s%s_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), + stage_filename, counter++); + + std::ofstream stream; + File::OpenFStream(stream, filename, std::ios_base::out); + if (stream.good()) + { + stream << source << std::endl; + stream << "Shader Info Log:" << std::endl; + stream << shader->getInfoLog() << std::endl; + stream << shader->getInfoDebugLog() << std::endl; + stream << "Program Info Log:" << std::endl; + stream << program->getInfoLog() << std::endl; + stream << program->getInfoDebugLog() << std::endl; + stream << "SPIR-V conversion messages: " << std::endl; + stream << spv_messages; + stream << "SPIR-V:" << std::endl; + spv::Disassemble(stream, out_code); + } + } + + return out_code; +} +} // namespace + +namespace SPIRV +{ +std::optional CompileVertexShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangVertex, std::nullopt, "vs", source_code); +} + +std::optional CompileGeometryShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangGeometry, std::nullopt, "gs", source_code); +} + +std::optional CompileFragmentShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangFragment, std::nullopt, "ps", source_code); +} + +std::optional CompileComputeShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangCompute, std::nullopt, "cs", source_code); +} +} // namespace SPIRV diff --git a/Source/Core/VideoCommon/Spirv.h b/Source/Core/VideoCommon/Spirv.h new file mode 100644 index 0000000000..81d1917eeb --- /dev/null +++ b/Source/Core/VideoCommon/Spirv.h @@ -0,0 +1,30 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +namespace SPIRV +{ +// SPIR-V compiled code type +using CodeType = u32; +using CodeVector = std::vector; + +// Compile a vertex shader to SPIR-V. +std::optional CompileVertexShader(std::string_view source_code); + +// Compile a geometry shader to SPIR-V. +std::optional CompileGeometryShader(std::string_view source_code); + +// Compile a fragment shader to SPIR-V. +std::optional CompileFragmentShader(std::string_view source_code); + +// Compile a compute shader to SPIR-V. +std::optional CompileComputeShader(std::string_view source_code); +} // namespace SPIRV