summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp334
1 files changed, 334 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
new file mode 100644
index 0000000000..1b4f134688
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
@@ -0,0 +1,334 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <fstream>
+#include <string>
+
+// glslang includes
+#include "GlslangToSpv.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 "VideoBackends/Vulkan/ShaderCompiler.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace Vulkan
+{
+namespace ShaderCompiler
+{
+// Registers itself for cleanup via atexit
+bool InitializeGlslang();
+
+// Resource limits used when compiling shaders
+static const TBuiltInResource* GetCompilerResourceLimits();
+
+// Compile a shader to SPIR-V via glslang
+static bool CompileShaderToSPV(SPIRVCodeVector* out_code, EShLanguage stage,
+ const char* stage_filename, const char* source_code,
+ size_t source_code_length, bool prepend_header);
+
+// Regarding the UBO bind points, we subtract one from the binding index because
+// the OpenGL backend requires UBO #0 for non-block uniforms (at least on NV).
+// This allows us to share the same shaders but use bind point #0 in the Vulkan
+// backend. None of the Vulkan-specific shaders use UBOs, instead they use push
+// constants, so when/if the GL backend moves to uniform blocks completely this
+// subtraction can be removed.
+static const char SHADER_HEADER[] = R"(
+ // Target GLSL 4.5.
+ #version 450 core
+ #define ATTRIBUTE_LOCATION(x) layout(location = x)
+ #define FRAGMENT_OUTPUT_LOCATION(x) layout(location = x)
+ #define FRAGMENT_OUTPUT_LOCATION_INDEXED(x, y) layout(location = x, index = y)
+ #define UBO_BINDING(packing, x) layout(packing, set = 0, binding = (x - 1))
+ #define SAMPLER_BINDING(x) layout(set = 1, binding = x)
+ #define SSBO_BINDING(x) layout(set = 2, binding = x)
+ #define VARYING_LOCATION(x) layout(location = x)
+ #define FORCE_EARLY_Z layout(early_fragment_tests) in
+
+ // hlsl to glsl function translation
+ #define float2 vec2
+ #define float3 vec3
+ #define float4 vec4
+ #define uint2 uvec2
+ #define uint3 uvec3
+ #define uint4 uvec4
+ #define int2 ivec2
+ #define int3 ivec3
+ #define int4 ivec4
+ #define frac fract
+ #define lerp mix
+
+ // These were changed in Vulkan
+ #define gl_VertexID gl_VertexIndex
+ #define gl_InstanceID gl_InstanceIndex
+)";
+
+bool CompileShaderToSPV(SPIRVCodeVector* out_code, EShLanguage stage, const char* stage_filename,
+ const char* source_code, size_t source_code_length, bool prepend_header)
+{
+ if (!InitializeGlslang())
+ return false;
+
+ std::unique_ptr<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage);
+ std::unique_ptr<glslang::TProgram> program;
+ glslang::TShader::ForbidInclude includer;
+ EProfile profile = ECoreProfile;
+ EShMessages messages =
+ static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
+ int default_version = 450;
+
+ std::string full_source_code;
+ const char* pass_source_code = source_code;
+ int pass_source_code_length = static_cast<int>(source_code_length);
+ if (prepend_header)
+ {
+ full_source_code.reserve(sizeof(SHADER_HEADER) + source_code_length);
+ full_source_code.append(SHADER_HEADER, sizeof(SHADER_HEADER) - 1);
+ full_source_code.append(source_code, source_code_length);
+ pass_source_code = full_source_code.c_str();
+ pass_source_code_length = static_cast<int>(full_source_code.length());
+ }
+
+ shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1);
+
+ auto DumpBadShader = [&](const char* msg) {
+ static int counter = 0;
+ std::string filename = StringFromFormat(
+ "%sbad_%s_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), stage_filename, counter++);
+
+ std::ofstream stream;
+ OpenFStream(stream, filename, std::ios_base::out);
+ if (stream.good())
+ {
+ stream << full_source_code << 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;
+ }
+ }
+
+ PanicAlert("%s (written to %s)", msg, filename.c_str());
+ };
+
+ if (!shader->parse(GetCompilerResourceLimits(), default_version, profile, false, true, messages,
+ includer))
+ {
+ DumpBadShader("Failed to parse shader");
+ return false;
+ }
+
+ // 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 false;
+ }
+
+ glslang::TIntermediate* intermediate = program->getIntermediate(stage);
+ if (!intermediate)
+ {
+ DumpBadShader("Failed to generate SPIR-V");
+ return false;
+ }
+
+ spv::SpvBuildLogger logger;
+ glslang::GlslangToSpv(*intermediate, *out_code, &logger);
+
+ // 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(VIDEO, "Shader info log: %s", shader->getInfoLog());
+ if (strlen(shader->getInfoDebugLog()) > 0)
+ WARN_LOG(VIDEO, "Shader debug info log: %s", shader->getInfoDebugLog());
+ if (strlen(program->getInfoLog()) > 25)
+ WARN_LOG(VIDEO, "Program info log: %s", program->getInfoLog());
+ if (strlen(program->getInfoDebugLog()) > 0)
+ WARN_LOG(VIDEO, "Program debug info log: %s", program->getInfoDebugLog());
+ std::string spv_messages = logger.getAllMessages();
+ if (!spv_messages.empty())
+ WARN_LOG(VIDEO, "SPIR-V conversion messages: %s", spv_messages.c_str());
+
+ // 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;
+ OpenFStream(stream, filename, std::ios_base::out);
+ if (stream.good())
+ {
+ stream << full_source_code << 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 true;
+}
+
+bool InitializeGlslang()
+{
+ static bool glslang_initialized = false;
+ if (glslang_initialized)
+ return true;
+
+ if (!glslang::InitializeProcess())
+ {
+ PanicAlert("Failed to initialize glslang shader compiler");
+ return false;
+ }
+
+ std::atexit([]() { glslang::FinalizeProcess(); });
+
+ glslang_initialized = true;
+ return true;
+}
+
+const TBuiltInResource* GetCompilerResourceLimits()
+{
+ static const TBuiltInResource limits = {/* .MaxLights = */ 32,
+ /* .MaxClipPlanes = */ 6,
+ /* .MaxTextureUnits = */ 32,
+ /* .MaxTextureCoords = */ 32,
+ /* .MaxVertexAttribs = */ 64,
+ /* .MaxVertexUniformComponents = */ 4096,
+ /* .MaxVaryingFloats = */ 64,
+ /* .MaxVertexTextureImageUnits = */ 32,
+ /* .MaxCombinedTextureImageUnits = */ 80,
+ /* .MaxTextureImageUnits = */ 32,
+ /* .MaxFragmentUniformComponents = */ 4096,
+ /* .MaxDrawBuffers = */ 32,
+ /* .MaxVertexUniformVectors = */ 128,
+ /* .MaxVaryingVectors = */ 8,
+ /* .MaxFragmentUniformVectors = */ 16,
+ /* .MaxVertexOutputVectors = */ 16,
+ /* .MaxFragmentInputVectors = */ 15,
+ /* .MinProgramTexelOffset = */ -8,
+ /* .MaxProgramTexelOffset = */ 7,
+ /* .MaxClipDistances = */ 8,
+ /* .MaxComputeWorkGroupCountX = */ 65535,
+ /* .MaxComputeWorkGroupCountY = */ 65535,
+ /* .MaxComputeWorkGroupCountZ = */ 65535,
+ /* .MaxComputeWorkGroupSizeX = */ 1024,
+ /* .MaxComputeWorkGroupSizeY = */ 1024,
+ /* .MaxComputeWorkGroupSizeZ = */ 64,
+ /* .MaxComputeUniformComponents = */ 1024,
+ /* .MaxComputeTextureImageUnits = */ 16,
+ /* .MaxComputeImageUniforms = */ 8,
+ /* .MaxComputeAtomicCounters = */ 8,
+ /* .MaxComputeAtomicCounterBuffers = */ 1,
+ /* .MaxVaryingComponents = */ 60,
+ /* .MaxVertexOutputComponents = */ 64,
+ /* .MaxGeometryInputComponents = */ 64,
+ /* .MaxGeometryOutputComponents = */ 128,
+ /* .MaxFragmentInputComponents = */ 128,
+ /* .MaxImageUnits = */ 8,
+ /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
+ /* .MaxCombinedShaderOutputResources = */ 8,
+ /* .MaxImageSamples = */ 0,
+ /* .MaxVertexImageUniforms = */ 0,
+ /* .MaxTessControlImageUniforms = */ 0,
+ /* .MaxTessEvaluationImageUniforms = */ 0,
+ /* .MaxGeometryImageUniforms = */ 0,
+ /* .MaxFragmentImageUniforms = */ 8,
+ /* .MaxCombinedImageUniforms = */ 8,
+ /* .MaxGeometryTextureImageUnits = */ 16,
+ /* .MaxGeometryOutputVertices = */ 256,
+ /* .MaxGeometryTotalOutputComponents = */ 1024,
+ /* .MaxGeometryUniformComponents = */ 1024,
+ /* .MaxGeometryVaryingComponents = */ 64,
+ /* .MaxTessControlInputComponents = */ 128,
+ /* .MaxTessControlOutputComponents = */ 128,
+ /* .MaxTessControlTextureImageUnits = */ 16,
+ /* .MaxTessControlUniformComponents = */ 1024,
+ /* .MaxTessControlTotalOutputComponents = */ 4096,
+ /* .MaxTessEvaluationInputComponents = */ 128,
+ /* .MaxTessEvaluationOutputComponents = */ 128,
+ /* .MaxTessEvaluationTextureImageUnits = */ 16,
+ /* .MaxTessEvaluationUniformComponents = */ 1024,
+ /* .MaxTessPatchComponents = */ 120,
+ /* .MaxPatchVertices = */ 32,
+ /* .MaxTessGenLevel = */ 64,
+ /* .MaxViewports = */ 16,
+ /* .MaxVertexAtomicCounters = */ 0,
+ /* .MaxTessControlAtomicCounters = */ 0,
+ /* .MaxTessEvaluationAtomicCounters = */ 0,
+ /* .MaxGeometryAtomicCounters = */ 0,
+ /* .MaxFragmentAtomicCounters = */ 8,
+ /* .MaxCombinedAtomicCounters = */ 8,
+ /* .MaxAtomicCounterBindings = */ 1,
+ /* .MaxVertexAtomicCounterBuffers = */ 0,
+ /* .MaxTessControlAtomicCounterBuffers = */ 0,
+ /* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
+ /* .MaxGeometryAtomicCounterBuffers = */ 0,
+ /* .MaxFragmentAtomicCounterBuffers = */ 1,
+ /* .MaxCombinedAtomicCounterBuffers = */ 1,
+ /* .MaxAtomicCounterBufferSize = */ 16384,
+ /* .MaxTransformFeedbackBuffers = */ 4,
+ /* .MaxTransformFeedbackInterleavedComponents = */ 64,
+ /* .MaxCullDistances = */ 8,
+ /* .MaxCombinedClipAndCullDistances = */ 8,
+ /* .MaxSamples = */ 4,
+ /* .limits = */ {
+ /* .nonInductiveForLoops = */ 1,
+ /* .whileLoops = */ 1,
+ /* .doWhileLoops = */ 1,
+ /* .generalUniformIndexing = */ 1,
+ /* .generalAttributeMatrixVectorIndexing = */ 1,
+ /* .generalVaryingIndexing = */ 1,
+ /* .generalSamplerIndexing = */ 1,
+ /* .generalVariableIndexing = */ 1,
+ /* .generalConstantMatrixVectorIndexing = */ 1,
+ }};
+
+ return &limits;
+}
+
+bool CompileVertexShader(SPIRVCodeVector* out_code, const char* source_code,
+ size_t source_code_length, bool prepend_header)
+{
+ return CompileShaderToSPV(out_code, EShLangVertex, "vs", source_code, source_code_length,
+ prepend_header);
+}
+
+bool CompileGeometryShader(SPIRVCodeVector* out_code, const char* source_code,
+ size_t source_code_length, bool prepend_header)
+{
+ return CompileShaderToSPV(out_code, EShLangGeometry, "gs", source_code, source_code_length,
+ prepend_header);
+}
+
+bool CompileFragmentShader(SPIRVCodeVector* out_code, const char* source_code,
+ size_t source_code_length, bool prepend_header)
+{
+ return CompileShaderToSPV(out_code, EShLangFragment, "ps", source_code, source_code_length,
+ prepend_header);
+}
+
+} // namespace ShaderCompiler
+} // namespace Vulkan