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