renderer_vulkan: Initial vertex-shader SPIR-V generation

This commit is contained in:
Wunk 2024-07-26 03:12:12 +02:00 committed by Gamer64ytb
parent 52536765cc
commit fd3bb123f4
12 changed files with 480 additions and 32 deletions

View File

@ -6,6 +6,7 @@
#include <cstddef>
#include <cstring>
#include <span>
#include "common/cityhash.h"
#include "common/common_types.h"
@ -21,6 +22,15 @@ static inline u64 ComputeHash64(const void* data, std::size_t len) noexcept {
return CityHash64(static_cast<const char*>(data), len);
}
/**
* Computes a 64-bit hash over the specified block of data
* @param data Block of data to compute hash over
* @returns 64-bit hash value that was computed over the data block
*/
static inline u64 ComputeHash64(std::span<const std::byte> data) noexcept {
return ComputeHash64(data.data(), data.size());
}
/**
* Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical
* that either the struct includes no padding, or that any padding is initialized to a known value

View File

@ -195,6 +195,8 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_texture_runtime.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
)
target_link_libraries(video_core PRIVATE vulkan-headers vma sirit SPIRV glslang)
endif()

View File

@ -56,6 +56,11 @@ Shader::Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::str
MarkDone();
}
Shader::Shader(const Instance& instance, std::span<const u32> code) : Shader{instance} {
module = CompileSPV(code, instance.GetDevice());
MarkDone();
}
Shader::~Shader() {
if (device && module) {
device.destroyShaderModule(module);

View File

@ -152,6 +152,7 @@ struct PipelineInfo {
struct Shader : public Common::AsyncHandle {
explicit Shader(const Instance& instance);
explicit Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code);
explicit Shader(const Instance& instance, std::span<const u32> code);
~Shader();
[[nodiscard]] vk::ShaderModule Handle() const noexcept {
@ -160,7 +161,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

@ -20,6 +20,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;
@ -86,8 +87,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
DescriptorHeap{instance, scheduler.GetMasterSemaphore(), TEXTURE_BINDINGS<1>},
DescriptorHeap{instance, scheduler.GetMasterSemaphore(), UTILITY_BINDINGS, 32}},
trivial_vertex_shader{
instance, vk::ShaderStageFlagBits::eVertex,
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
instance, SPIRV::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported())} {
scheduler.RegisterOnDispatch([this] { update_queue.Flush(); });
profile = Pica::Shader::Profile{
.has_separable_shaders = true,
@ -356,21 +356,36 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
const 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;
if (use_spirv && false) {
// TODO: Generate vertex shader SPIRV from the given VS program
// 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)));
const 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

@ -118,7 +118,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

@ -159,8 +159,14 @@ bool InitializeCompiler() {
}
} // Anonymous namespace
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable) {
/**
* @brief Compiles GLSL into SPIRV
* @param code The string containing GLSL code.
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
*/
std::vector<u32> CompileGLSLtoSPIRV(std::string_view code, vk::ShaderStageFlagBits stage,
vk::Device device, std::string_view premable) {
if (!InitializeCompiler()) {
return {};
}
@ -216,7 +222,12 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages);
}
return CompileSPV(out_code, device);
return out_code;
}
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable) {
return CompileSPV(CompileGLSLtoSPIRV(code, stage, device, premable), device);
}
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {

View File

@ -10,11 +10,22 @@
namespace Vulkan {
/**
* @brief Compiles GLSL into SPIRV
* @param code The string containing GLSL code.
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
* @param premable Code that occurs before the main code block
*/
std::vector<u32> CompileGLSLtoSPIRV(std::string_view code, vk::ShaderStageFlagBits stage,
vk::Device device, std::string_view premable = "");
/**
* @brief Creates a vulkan shader module from GLSL by converting it to SPIR-V using glslang.
* @param code The string containing GLSL code.
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
* @param premable Code that occurs before the main code block
*/
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::string_view premable = "");

View File

@ -15,8 +15,6 @@ using Pica::TexturingRegs;
using TevStageConfig = TexturingRegs::TevStageConfig;
using TextureType = TexturingRegs::TextureConfig::TextureType;
constexpr u32 SPIRV_VERSION_1_3 = 0x00010300;
FragmentModule::FragmentModule(const FSConfig& config_, const Profile& profile_)
: Sirit::Module{SPIRV_VERSION_1_3}, config{config_}, profile{profile_},
use_fragment_shader_barycentric{profile.has_fragment_shader_barycentric &&

View File

@ -7,27 +7,15 @@
#include <array>
#include <sirit/sirit.h>
#include "common/common_types.h"
#include "spv_shader_gen.h"
#include "video_core/pica/regs_framebuffer.h"
#include "video_core/pica/regs_texturing.h"
namespace Pica::Shader {
struct FSConfig;
struct Profile;
} // namespace Pica::Shader
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,273 @@
// 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_gen.h"
using VSOutputAttributes = Pica::RasterizerRegs::VSOutputAttributes;
namespace Pica::Shader::Generator::SPIRV {
VertexModule::VertexModule() : Sirit::Module{SPIRV_VERSION_1_3} {
DefineArithmeticTypes();
DefineInterface();
ids.sanitize_vertex = WriteFuncSanitizeVertex();
DefineEntryPoint();
}
VertexModule::~VertexModule() = default;
void VertexModule::DefineArithmeticTypes() {
ids.void_ = Name(TypeVoid(), "void_id");
ids.bool_ = Name(TypeBool(), "bool_id");
ids.f32 = Name(TypeFloat(32), "f32_id");
ids.i32 = Name(TypeSInt(32), "i32_id");
ids.u32 = Name(TypeUInt(32), "u32_id");
for (u32 size = 2; size <= 4; size++) {
const u32 i = size - 2;
ids.bvec.ids[i] = Name(TypeVector(ids.bool_, size), fmt::format("bvec{}_id", size));
ids.vec.ids[i] = Name(TypeVector(ids.f32, size), fmt::format("vec{}_id", size));
ids.ivec.ids[i] = Name(TypeVector(ids.i32, size), fmt::format("ivec{}_id", size));
ids.uvec.ids[i] = Name(TypeVector(ids.u32, size), fmt::format("uvec{}_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)};
const Id interface_ids[] = {
// Inputs
ids.vert_in_position,
ids.vert_in_color,
ids.vert_in_texcoord0,
ids.vert_in_texcoord1,
ids.vert_in_texcoord2,
ids.vert_in_texcoord0_w,
ids.vert_in_normquat,
ids.vert_in_view,
// Outputs
ids.gl_position,
ids.gl_clip_distance,
ids.vert_out_color,
ids.vert_out_texcoord0,
ids.vert_out_texcoord1,
ids.vert_out_texcoord2,
ids.vert_out_texcoord0_w,
ids.vert_out_normquat,
ids.vert_out_view,
};
AddEntryPoint(spv::ExecutionModel::Vertex, main_func, "main", interface_ids);
}
void VertexModule::DefineInterface() {
// Define interface block
/// Inputs
ids.vert_in_position =
Name(DefineInput(ids.vec.Get(4), ATTRIBUTE_POSITION), "vert_in_position");
ids.vert_in_color = Name(DefineInput(ids.vec.Get(4), ATTRIBUTE_COLOR), "vert_in_color");
ids.vert_in_texcoord0 =
Name(DefineInput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD0), "vert_in_texcoord0");
ids.vert_in_texcoord1 =
Name(DefineInput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD1), "vert_in_texcoord1");
ids.vert_in_texcoord2 =
Name(DefineInput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD2), "vert_in_texcoord2");
ids.vert_in_texcoord0_w =
Name(DefineInput(ids.f32, ATTRIBUTE_TEXCOORD0_W), "vert_in_texcoord0_w");
ids.vert_in_normquat =
Name(DefineInput(ids.vec.Get(4), ATTRIBUTE_NORMQUAT), "vert_in_normquat");
ids.vert_in_view = Name(DefineInput(ids.vec.Get(3), ATTRIBUTE_VIEW), "vert_in_view");
/// Outputs
ids.vert_out_color = Name(DefineOutput(ids.vec.Get(4), ATTRIBUTE_COLOR), "vert_out_color");
ids.vert_out_texcoord0 =
Name(DefineOutput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD0), "vert_out_texcoord0");
ids.vert_out_texcoord1 =
Name(DefineOutput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD1), "vert_out_texcoord1");
ids.vert_out_texcoord2 =
Name(DefineOutput(ids.vec.Get(2), ATTRIBUTE_TEXCOORD2), "vert_out_texcoord2");
ids.vert_out_texcoord0_w =
Name(DefineOutput(ids.f32, ATTRIBUTE_TEXCOORD0_W), "vert_out_texcoord0_w");
ids.vert_out_normquat =
Name(DefineOutput(ids.vec.Get(4), ATTRIBUTE_NORMQUAT), "vert_out_normquat");
ids.vert_out_view = Name(DefineOutput(ids.vec.Get(3), ATTRIBUTE_VIEW), "vert_out_view");
/// Uniforms
// vs_data
const Id type_vs_data = Name(TypeStruct(ids.u32, ids.vec.Get(4)), "vs_data");
Decorate(type_vs_data, spv::Decoration::Block);
ids.ptr_vs_data = AddGlobalVariable(TypePointer(spv::StorageClass::Uniform, type_vs_data),
spv::StorageClass::Uniform);
Decorate(ids.ptr_vs_data, spv::Decoration::DescriptorSet, 0);
Decorate(ids.ptr_vs_data, spv::Decoration::Binding, 1);
MemberName(type_vs_data, 0, "enable_clip1");
MemberName(type_vs_data, 1, "clip_coef");
MemberDecorate(type_vs_data, 0, spv::Decoration::Offset, 0);
MemberDecorate(type_vs_data, 1, spv::Decoration::Offset, 16);
/// Built-ins
ids.gl_position = DefineVar(ids.vec.Get(4), spv::StorageClass::Output);
Decorate(ids.gl_position, spv::Decoration::BuiltIn, spv::BuiltIn::Position);
ids.gl_clip_distance =
DefineVar(TypeArray(ids.f32, Constant(ids.u32, 2)), spv::StorageClass::Output);
Decorate(ids.gl_clip_distance, spv::Decoration::BuiltIn, spv::BuiltIn::ClipDistance);
}
Id VertexModule::WriteFuncSanitizeVertex() {
const Id func_type = TypeFunction(ids.vec.Get(4), ids.vec.Get(4));
const Id func = Name(OpFunction(ids.vec.Get(4), spv::FunctionControlMask::MaskNone, func_type),
"SanitizeVertex");
const Id arg_pos = OpFunctionParameter(ids.vec.Get(4));
AddLabel(OpLabel());
const Id result = AddLocalVariable(TypePointer(spv::StorageClass::Function, ids.vec.Get(4)),
spv::StorageClass::Function);
OpStore(result, arg_pos);
const Id pos_z = OpCompositeExtract(ids.f32, arg_pos, 2);
const Id pos_w = OpCompositeExtract(ids.f32, arg_pos, 3);
const Id ndc_z = OpFDiv(ids.f32, pos_z, pos_w);
// if (ndc_z > 0.f && ndc_z < 0.000001f)
const Id test_1 =
OpLogicalAnd(ids.bool_, OpFOrdGreaterThan(ids.bool_, ndc_z, Constant(ids.f32, 0.0f)),
OpFOrdLessThan(ids.bool_, ndc_z, Constant(ids.f32, 0.000001f)));
{
const Id true_label = OpLabel();
const Id end_label = OpLabel();
OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(test_1, true_label, end_label);
AddLabel(true_label);
// .z = 0.0f;
OpStore(result, OpCompositeInsert(ids.vec.Get(4), ConstantNull(ids.f32), arg_pos, 2));
OpBranch(end_label);
AddLabel(end_label);
}
// if (ndc_z < -1.f && ndc_z > -1.00001f)
const Id test_2 =
OpLogicalAnd(ids.bool_, OpFOrdLessThan(ids.bool_, ndc_z, Constant(ids.f32, -1.0f)),
OpFOrdGreaterThan(ids.bool_, ndc_z, Constant(ids.f32, -1.00001f)));
{
const Id true_label = OpLabel();
const Id end_label = OpLabel();
OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(test_2, true_label, end_label);
AddLabel(true_label);
// .z = -.w;
const Id neg_w = OpFNegate(ids.f32, OpCompositeExtract(ids.f32, arg_pos, 3));
OpStore(result, OpCompositeInsert(ids.vec.Get(4), neg_w, arg_pos, 2));
OpBranch(end_label);
AddLabel(end_label);
}
OpReturnValue(OpLoad(ids.vec.Get(4), result));
OpFunctionEnd();
return func;
}
void VertexModule::Generate(Common::UniqueFunction<void, Sirit::Module&, const ModuleIds&> proc) {
AddLabel(OpLabel());
ids.ptr_enable_clip1 = OpAccessChain(TypePointer(spv::StorageClass::Uniform, ids.u32),
ids.ptr_vs_data, Constant(ids.u32, 0));
ids.ptr_clip_coef = OpAccessChain(TypePointer(spv::StorageClass::Uniform, ids.vec.Get(4)),
ids.ptr_vs_data, Constant(ids.u32, 1));
proc(*this, ids);
OpReturn();
OpFunctionEnd();
}
std::vector<u32> GenerateTrivialVertexShader(bool use_clip_planes) {
VertexModule module;
module.Generate([use_clip_planes](Sirit::Module& spv,
const VertexModule::ModuleIds& ids) -> void {
const Id pos_sanitized = spv.OpFunctionCall(
ids.vec.Get(4), ids.sanitize_vertex, spv.OpLoad(ids.vec.Get(4), ids.vert_in_position));
// Negate Z
const Id neg_z = spv.OpFNegate(ids.f32, spv.OpCompositeExtract(ids.f32, pos_sanitized, 2));
const Id negated_z = spv.OpCompositeInsert(ids.vec.Get(4), neg_z, pos_sanitized, 2);
spv.OpStore(ids.gl_position, negated_z);
// Pass-through
spv.OpStore(ids.vert_out_color, spv.OpLoad(ids.vec.Get(4), ids.vert_in_color));
spv.OpStore(ids.vert_out_texcoord0, spv.OpLoad(ids.vec.Get(2), ids.vert_in_texcoord0));
spv.OpStore(ids.vert_out_texcoord1, spv.OpLoad(ids.vec.Get(2), ids.vert_in_texcoord1));
spv.OpStore(ids.vert_out_texcoord2, spv.OpLoad(ids.vec.Get(2), ids.vert_in_texcoord2));
spv.OpStore(ids.vert_out_texcoord0_w, spv.OpLoad(ids.f32, ids.vert_in_texcoord0_w));
spv.OpStore(ids.vert_out_normquat, spv.OpLoad(ids.vec.Get(4), ids.vert_in_normquat));
spv.OpStore(ids.vert_out_view, spv.OpLoad(ids.vec.Get(3), ids.vert_in_view));
if (use_clip_planes) {
spv.OpStore(spv.OpAccessChain(spv.TypePointer(spv::StorageClass::Output, ids.f32),
ids.gl_clip_distance, spv.Constant(ids.u32, 0)),
neg_z);
const Id enable_clip1 = spv.OpINotEqual(
ids.bool_, spv.OpLoad(ids.u32, ids.ptr_enable_clip1), spv.Constant(ids.u32, 0));
{
const Id true_label = spv.OpLabel();
const Id false_label = spv.OpLabel();
const Id end_label = spv.OpLabel();
spv.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
spv.OpBranchConditional(enable_clip1, true_label, false_label);
{
spv.AddLabel(true_label);
spv.OpStore(
spv.OpAccessChain(spv.TypePointer(spv::StorageClass::Output, ids.f32),
ids.gl_clip_distance, spv.Constant(ids.u32, 1)),
spv.OpDot(ids.f32, spv.OpLoad(ids.vec.Get(4), ids.ptr_clip_coef),
pos_sanitized));
spv.OpBranch(end_label);
}
{
spv.AddLabel(false_label);
spv.OpStore(
spv.OpAccessChain(spv.TypePointer(spv::StorageClass::Output, ids.f32),
ids.gl_clip_distance, spv.Constant(ids.u32, 1)),
spv.ConstantNull(ids.f32));
spv.OpBranch(end_label);
}
spv.AddLabel(end_label);
}
}
});
return module.Assemble();
}
} // namespace Pica::Shader::Generator::SPIRV

View File

@ -0,0 +1,134 @@
// 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>
#include "common/common_types.h"
#include "common/unique_function.h"
namespace Pica {
struct ShaderSetup;
}
namespace Pica::Shader {
struct VSConfig;
struct FSConfig;
struct Profile;
} // namespace Pica::Shader
namespace Pica::Shader::Generator {
struct PicaVSConfig;
} // namespace Pica::Shader::Generator
namespace Pica::Shader::Generator::SPIRV {
using Sirit::Id;
constexpr u32 SPIRV_VERSION_1_3 = 0x00010300;
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();
~VertexModule();
private:
template <bool global = true>
[[nodiscard]] Id DefineVar(Id type, spv::StorageClass storage_class) {
const Id pointer_type_id{TypePointer(storage_class, type)};
return global ? AddGlobalVariable(pointer_type_id, storage_class)
: AddLocalVariable(pointer_type_id, storage_class);
}
/// Defines an input variable
[[nodiscard]] Id DefineInput(Id type, u32 location) {
const Id input_id{DefineVar(type, spv::StorageClass::Input)};
Decorate(input_id, spv::Decoration::Location, location);
return input_id;
}
/// Defines an output variable
[[nodiscard]] Id DefineOutput(Id type, u32 location) {
const Id output_id{DefineVar(type, spv::StorageClass::Output)};
Decorate(output_id, spv::Decoration::Location, location);
return output_id;
}
void DefineArithmeticTypes();
void DefineEntryPoint();
void DefineInterface();
[[nodiscard]] Id WriteFuncSanitizeVertex();
public:
struct ModuleIds {
/// Types
Id void_{};
Id bool_{};
Id f32{};
Id i32{};
Id u32{};
VectorIds vec{};
VectorIds ivec{};
VectorIds uvec{};
VectorIds bvec{};
/// Input vertex attributes
Id vert_in_position{};
Id vert_in_color{};
Id vert_in_texcoord0{};
Id vert_in_texcoord1{};
Id vert_in_texcoord2{};
Id vert_in_texcoord0_w{};
Id vert_in_normquat{};
Id vert_in_view{};
/// Output vertex attributes
Id vert_out_color{};
Id vert_out_texcoord0{};
Id vert_out_texcoord1{};
Id vert_out_texcoord2{};
Id vert_out_texcoord0_w{};
Id vert_out_normquat{};
Id vert_out_view{};
/// Uniforms
// vs_data
Id ptr_vs_data;
Id ptr_enable_clip1;
Id ptr_clip_coef;
/// Built-ins
Id gl_position;
Id gl_clip_distance;
/// Functions
Id sanitize_vertex;
} ids;
/// Generate code using the provided SPIRV emitter context
void Generate(Common::UniqueFunction<void, Sirit::Module&, const ModuleIds&> proc);
};
/**
* Generates the SPIRV vertex shader program source code that accepts vertices from software shader
* and directly passes them to the fragment shader.
* @returns SPIRV shader assembly; empty on failure
*/
std::vector<u32> GenerateTrivialVertexShader(bool use_clip_planes);
} // namespace Pica::Shader::Generator::SPIRV