diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index f2c084017..46aa843e0 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -77,6 +77,10 @@ add_library(video_core STATIC shader/generator/shader_gen.h shader/generator/shader_uniforms.cpp shader/generator/shader_uniforms.h + shader/generator/spv_fs_shader_gen.cpp + shader/generator/spv_fs_shader_gen.h + shader/generator/spv_shader_gen.h + shader/generator/spv_shader_gen.cpp shader/shader.cpp shader/shader.h shader/shader_interpreter.cpp diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 93a4ebd48..38cd988d1 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -160,7 +160,7 @@ struct Shader : public Common::AsyncHandle { vk::ShaderModule module; vk::Device device; - std::string program; + std::vector program; }; class GraphicsPipeline : public Common::AsyncHandle { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 9668aed69..6682ec22e 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -19,6 +19,7 @@ #include "video_core/shader/generator/glsl_fs_shader_gen.h" #include "video_core/shader/generator/glsl_shader_gen.h" #include "video_core/shader/generator/spv_fs_shader_gen.h" +#include "video_core/shader/generator/spv_shader_gen.h" using namespace Pica::Shader::Generator; using Pica::Shader::FSConfig; @@ -404,21 +405,37 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs, auto [it, new_config] = programmable_vertex_map.try_emplace(config); if (new_config) { - auto program = GLSL::GenerateVertexShader(setup, config, true); - if (program.empty()) { - LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader"); - programmable_vertex_map[config] = nullptr; - return false; + const bool use_spirv = Settings::values.spirv_shader_gen.GetValue(); + const vk::Device device = instance.GetDevice(); + + std::vector code; + + // Disabled for programmable shaders for now + if (use_spirv && false) { + // Directly generate SPIRV + code = SPIRV::GenerateVertexShader(setup, config, profile); + } else { + // Generate GLSL + const std::string program = GLSL::GenerateVertexShader(setup, config, true); + if (program.empty()) { + LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader"); + programmable_vertex_map[config] = nullptr; + return false; + } + // Compile GLSL to SPIRV + code = CompileGLSLtoSPIRV(program, vk::ShaderStageFlagBits::eVertex, device); } - auto [iter, new_program] = programmable_vertex_cache.try_emplace(program, instance); + const u64 code_hash = Common::ComputeHash64(std::as_bytes(std::span(code))); + + auto [iter, new_program] = programmable_vertex_cache.try_emplace(code_hash, instance); auto& shader = iter->second; + // Queue worker thread to create shader module if (new_program) { - shader.program = std::move(program); - const vk::Device device = instance.GetDevice(); + shader.program = std::move(code); workers.QueueWork([device, &shader] { - shader.module = Compile(shader.program, vk::ShaderStageFlagBits::eVertex, device); + shader.module = CompileSPV(shader.program, device); shader.MarkDone(); }); } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 2c57d2d52..e9df4f9bc 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -119,7 +119,7 @@ private: std::array shader_hashes; std::array current_shaders; std::unordered_map programmable_vertex_map; - std::unordered_map programmable_vertex_cache; + std::unordered_map programmable_vertex_cache; std::unordered_map fixed_geometry_shaders; std::unordered_map fragment_shaders; Shader trivial_vertex_shader; diff --git a/src/video_core/shader/generator/spv_fs_shader_gen.h b/src/video_core/shader/generator/spv_fs_shader_gen.h index cbbede01f..ecca1d842 100644 --- a/src/video_core/shader/generator/spv_fs_shader_gen.h +++ b/src/video_core/shader/generator/spv_fs_shader_gen.h @@ -7,6 +7,7 @@ #include #include +#include "spv_shader_gen.h" #include "video_core/pica/regs_framebuffer.h" #include "video_core/pica/regs_texturing.h" @@ -19,15 +20,6 @@ namespace Pica::Shader::Generator::SPIRV { using Sirit::Id; -struct VectorIds { - /// Returns the type id of the vector with the provided size - [[nodiscard]] constexpr Id Get(u32 size) const { - return ids[size - 2]; - } - - std::array ids; -}; - class FragmentModule : public Sirit::Module { static constexpr u32 NUM_TEV_STAGES = 6; static constexpr u32 NUM_LIGHTS = 8; diff --git a/src/video_core/shader/generator/spv_shader_gen.cpp b/src/video_core/shader/generator/spv_shader_gen.cpp new file mode 100644 index 000000000..c3479f903 --- /dev/null +++ b/src/video_core/shader/generator/spv_shader_gen.cpp @@ -0,0 +1,68 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/pica/regs_rasterizer.h" +#include "video_core/shader/generator/shader_gen.h" +// #include "video_core/shader/generator/spv_shader_decompiler.h" +#include "video_core/shader/generator/spv_shader_gen.h" + +using VSOutputAttributes = Pica::RasterizerRegs::VSOutputAttributes; + +namespace Pica::Shader::Generator::SPIRV { + +constexpr u32 SPIRV_VERSION_1_3 = 0x00010300; + +VertexModule::VertexModule(const PicaVSConfig& config_, const Profile& profile_) + : Sirit::Module{SPIRV_VERSION_1_3}, config{config_}, profile{profile_} { + DefineArithmeticTypes(); + DefineInterface(); + DefineEntryPoint(); +} + +VertexModule::~VertexModule() = default; + +void VertexModule::Generate() { + AddLabel(OpLabel()); + OpReturn(); + OpFunctionEnd(); +} + +void VertexModule::DefineArithmeticTypes() { + void_id = Name(TypeVoid(), "void_id"); + bool_id = Name(TypeBool(), "bool_id"); + f32_id = Name(TypeFloat(32), "f32_id"); + i32_id = Name(TypeSInt(32), "i32_id"); + u32_id = Name(TypeUInt(32), "u32_id"); + + for (u32 size = 2; size <= 4; size++) { + const u32 i = size - 2; + vec_ids.ids[i] = Name(TypeVector(f32_id, size), fmt::format("vec{}_id", size)); + ivec_ids.ids[i] = Name(TypeVector(i32_id, size), fmt::format("ivec{}_id", size)); + uvec_ids.ids[i] = Name(TypeVector(u32_id, size), fmt::format("uvec{}_id", size)); + bvec_ids.ids[i] = Name(TypeVector(bool_id, size), fmt::format("bvec{}_id", size)); + } +} + +void VertexModule::DefineEntryPoint() { + AddCapability(spv::Capability::Shader); + SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + + const Id main_type{TypeFunction(TypeVoid())}; + const Id main_func{OpFunction(TypeVoid(), spv::FunctionControlMask::MaskNone, main_type)}; + + AddEntryPoint(spv::ExecutionModel::Vertex, main_func, "main"); +} + +void VertexModule::DefineInterface() { + // Define interface block +} + +std::vector GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& config, + const Profile& profile) { + VertexModule module(config, profile); + module.Generate(); + return module.Assemble(); +} + +} // namespace Pica::Shader::Generator::SPIRV \ No newline at end of file diff --git a/src/video_core/shader/generator/spv_shader_gen.h b/src/video_core/shader/generator/spv_shader_gen.h new file mode 100644 index 000000000..1f8fda8e0 --- /dev/null +++ b/src/video_core/shader/generator/spv_shader_gen.h @@ -0,0 +1,74 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Pica { +struct ShaderSetup; +} + +namespace Pica::Shader { +struct VSConfig; +struct Profile; +} // namespace Pica::Shader + +namespace Pica::Shader::Generator { +struct PicaVSConfig; +} // namespace Pica::Shader::Generator + +namespace Pica::Shader::Generator::SPIRV { + +using Sirit::Id; + +struct VectorIds { + /// Returns the type id of the vector with the provided size + [[nodiscard]] constexpr Id Get(u32 size) const { + return ids[size - 2]; + } + + std::array ids; +}; + +class VertexModule : public Sirit::Module { + +public: + explicit VertexModule(const PicaVSConfig& config, const Profile& profile); + ~VertexModule(); + + /// Emits SPIR-V bytecode corresponding to the provided pica vertex configuration + void Generate(); + +private: + void DefineArithmeticTypes(); + void DefineEntryPoint(); + void DefineInterface(); + +private: + const PicaVSConfig& config; + const Profile& profile; + + Id void_id{}; + Id bool_id{}; + Id f32_id{}; + Id i32_id{}; + Id u32_id{}; + + VectorIds vec_ids{}; + VectorIds ivec_ids{}; + VectorIds uvec_ids{}; + VectorIds bvec_ids{}; +}; + +/** + * Generates the SPIRV vertex shader program source code for the given VS program + * @param config ShaderCacheKey object generated for the current Pica state, used for the shader + * configuration (NOTE: Use state in this struct only, not the Pica registers!) + * @param separable_shader generates shader that can be used for separate shader object + * @returns String of the shader source code; empty on failure + */ +std::vector GenerateVertexShader(const Pica::ShaderSetup& setup, const PicaVSConfig& config, + const Profile& profile); +} // namespace Pica::Shader::Generator::SPIRV