renderer_vulkan: Initialize SPIRV vertex shaders

Defines shaders as an array of `u32` rather than a GLSL string.
Uses a hash of the `u32` bytes rather than `std::string`
This commit is contained in:
Wunkolo 2024-03-02 11:42:36 -08:00
parent d24722ba96
commit 882f75c02c
7 changed files with 175 additions and 20 deletions

View file

@ -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

View file

@ -160,7 +160,7 @@ struct Shader : public Common::AsyncHandle {
vk::ShaderModule module;
vk::Device device;
std::string program;
std::vector<u32> program;
};
class GraphicsPipeline : public Common::AsyncHandle {

View file

@ -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<u32> 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();
});
}

View file

@ -119,7 +119,7 @@ private:
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
std::unordered_map<Pica::Shader::Generator::PicaVSConfig, Shader*> programmable_vertex_map;
std::unordered_map<std::string, Shader> programmable_vertex_cache;
std::unordered_map<u64, Shader> programmable_vertex_cache;
std::unordered_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
std::unordered_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
Shader trivial_vertex_shader;

View file

@ -7,6 +7,7 @@
#include <array>
#include <sirit/sirit.h>
#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<Id, 3> ids;
};
class FragmentModule : public Sirit::Module {
static constexpr u32 NUM_TEV_STAGES = 6;
static constexpr u32 NUM_LIGHTS = 8;

View file

@ -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<u32> GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& config,
const Profile& profile) {
VertexModule module(config, profile);
module.Generate();
return module.Assemble();
}
} // namespace Pica::Shader::Generator::SPIRV

View file

@ -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 <sirit/sirit.h>
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<Id, 3> 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<u32> GenerateVertexShader(const Pica::ShaderSetup& setup, const PicaVSConfig& config,
const Profile& profile);
} // namespace Pica::Shader::Generator::SPIRV