mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 13:57:57 -07:00
381 lines
17 KiB
C++
381 lines
17 KiB
C++
// Copyright 2016 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "VideoBackends/Vulkan/ShaderCompiler.h"
|
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#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 "Common/Version.h"
|
|
|
|
#include "VideoCommon/VideoBackendBase.h"
|
|
#include "VideoCommon/VideoConfig.h"
|
|
|
|
namespace Vulkan::ShaderCompiler
|
|
{
|
|
// Registers itself for cleanup via atexit
|
|
bool InitializeGlslang();
|
|
|
|
// Resource limits used when compiling shaders
|
|
static const TBuiltInResource* GetCompilerResourceLimits();
|
|
|
|
// 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 TEXEL_BUFFER_BINDING(x) layout(set = 1, binding = (x + 8))
|
|
#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 API_VULKAN 1
|
|
#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
|
|
)";
|
|
static const char COMPUTE_SHADER_HEADER[] = R"(
|
|
// Target GLSL 4.5.
|
|
#version 450 core
|
|
// All resources are packed into one descriptor set for compute.
|
|
#define UBO_BINDING(packing, x) layout(packing, set = 0, binding = (x - 1))
|
|
#define SAMPLER_BINDING(x) layout(set = 0, binding = (1 + x))
|
|
#define TEXEL_BUFFER_BINDING(x) layout(set = 0, binding = (3 + x))
|
|
#define IMAGE_BINDING(format, x) layout(format, set = 0, binding = (5 + x))
|
|
|
|
// hlsl to glsl function translation
|
|
#define API_VULKAN 1
|
|
#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
|
|
)";
|
|
static const char SUBGROUP_HELPER_HEADER[] = R"(
|
|
#extension GL_KHR_shader_subgroup_basic : enable
|
|
#extension GL_KHR_shader_subgroup_arithmetic : enable
|
|
#extension GL_KHR_shader_subgroup_ballot : enable
|
|
|
|
#define SUPPORTS_SUBGROUP_REDUCTION 1
|
|
#define CAN_USE_SUBGROUP_REDUCTION true
|
|
#define IS_HELPER_INVOCATION gl_HelperInvocation
|
|
#define IS_FIRST_ACTIVE_INVOCATION (gl_SubgroupInvocationID == subgroupBallotFindLSB(subgroupBallot(true)))
|
|
#define SUBGROUP_MIN(value) value = subgroupMin(value)
|
|
#define SUBGROUP_MAX(value) value = subgroupMax(value)
|
|
)";
|
|
|
|
std::optional<SPIRVCodeVector> CompileShaderToSPV(EShLanguage stage, const char* stage_filename,
|
|
std::string_view source, std::string_view header)
|
|
{
|
|
if (!InitializeGlslang())
|
|
return std::nullopt;
|
|
|
|
std::unique_ptr<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage);
|
|
std::unique_ptr<glslang::TProgram> program;
|
|
glslang::TShader::ForbidIncluder 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.data();
|
|
int pass_source_code_length = static_cast<int>(source.size());
|
|
if (!header.empty())
|
|
{
|
|
constexpr size_t subgroup_helper_header_length = std::size(SUBGROUP_HELPER_HEADER) - 1;
|
|
full_source_code.reserve(header.size() + subgroup_helper_header_length + source.size());
|
|
full_source_code.append(header);
|
|
if (g_vulkan_context->SupportsShaderSubgroupOperations())
|
|
full_source_code.append(SUBGROUP_HELPER_HEADER, subgroup_helper_header_length);
|
|
full_source_code.append(source);
|
|
pass_source_code = full_source_code.c_str();
|
|
pass_source_code_length = static_cast<int>(full_source_code.length());
|
|
}
|
|
|
|
// Sub-group operations require Vulkan 1.1 and SPIR-V 1.3.
|
|
if (g_vulkan_context->SupportsShaderSubgroupOperations())
|
|
shader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
|
|
|
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 << 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;
|
|
}
|
|
}
|
|
|
|
stream << "\n";
|
|
stream << "Dolphin Version: " + Common::scm_rev_str + "\n";
|
|
stream << "Video Backend: " + g_video_backend->GetDisplayName();
|
|
|
|
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 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(stage);
|
|
if (!intermediate)
|
|
{
|
|
DumpBadShader("Failed to generate SPIR-V");
|
|
return std::nullopt;
|
|
}
|
|
|
|
SPIRVCodeVector out_code;
|
|
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;
|
|
File::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 out_code;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
std::optional<SPIRVCodeVector> CompileVertexShader(std::string_view source_code)
|
|
{
|
|
return CompileShaderToSPV(EShLangVertex, "vs", source_code, SHADER_HEADER);
|
|
}
|
|
|
|
std::optional<SPIRVCodeVector> CompileGeometryShader(std::string_view source_code)
|
|
{
|
|
return CompileShaderToSPV(EShLangGeometry, "gs", source_code, SHADER_HEADER);
|
|
}
|
|
|
|
std::optional<SPIRVCodeVector> CompileFragmentShader(std::string_view source_code)
|
|
{
|
|
return CompileShaderToSPV(EShLangFragment, "ps", source_code, SHADER_HEADER);
|
|
}
|
|
|
|
std::optional<SPIRVCodeVector> CompileComputeShader(std::string_view source_code)
|
|
{
|
|
return CompileShaderToSPV(EShLangCompute, "cs", source_code, COMPUTE_SHADER_HEADER);
|
|
}
|
|
} // namespace Vulkan::ShaderCompiler
|