From 77a128ab8707659a531029a21a2e874108eedf38 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 13 Aug 2016 22:57:50 +1000 Subject: Implement experimental Vulkan backend --- .../Core/VideoBackends/Vulkan/ShaderCompiler.cpp | 334 +++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp (limited to 'Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp') 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 +#include + +// 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 shader = std::make_unique(stage); + std::unique_ptr program; + glslang::TShader::ForbidInclude includer; + EProfile profile = ECoreProfile; + EShMessages messages = + static_cast(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(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(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(); + 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 -- cgit v1.2.3