renderer_vulkan: Add shader generators (#6630)
This commit is contained in:
parent
7edc86a9bc
commit
2e479fcec5
|
@ -104,6 +104,12 @@ add_library(video_core STATIC
|
||||||
renderer_vulkan/vk_instance.h
|
renderer_vulkan/vk_instance.h
|
||||||
renderer_vulkan/vk_platform.cpp
|
renderer_vulkan/vk_platform.cpp
|
||||||
renderer_vulkan/vk_platform.h
|
renderer_vulkan/vk_platform.h
|
||||||
|
renderer_vulkan/vk_shader_gen.cpp
|
||||||
|
renderer_vulkan/vk_shader_gen.h
|
||||||
|
renderer_vulkan/vk_shader_gen_spv.cpp
|
||||||
|
renderer_vulkan/vk_shader_gen_spv.h
|
||||||
|
renderer_vulkan/vk_shader_util.cpp
|
||||||
|
renderer_vulkan/vk_shader_util.h
|
||||||
shader/debug_data.h
|
shader/debug_data.h
|
||||||
shader/shader.cpp
|
shader/shader.cpp
|
||||||
shader/shader.h
|
shader/shader.h
|
||||||
|
@ -133,7 +139,7 @@ create_target_directory_groups(video_core)
|
||||||
|
|
||||||
target_link_libraries(video_core PUBLIC citra_common citra_core)
|
target_link_libraries(video_core PUBLIC citra_common citra_core)
|
||||||
target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx json-headers nihstro-headers tsl::robin_map)
|
target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx json-headers nihstro-headers tsl::robin_map)
|
||||||
target_link_libraries(video_core PRIVATE vulkan-headers vma glad)
|
target_link_libraries(video_core PRIVATE vulkan-headers vma glad sirit SPIRV glslang)
|
||||||
set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||||
|
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
if ("x86_64" IN_LIST ARCHITECTURE)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,274 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include "common/hash.h"
|
||||||
|
#include "video_core/regs.h"
|
||||||
|
#include "video_core/shader/shader.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Instance;
|
||||||
|
|
||||||
|
enum Attributes {
|
||||||
|
ATTRIBUTE_POSITION,
|
||||||
|
ATTRIBUTE_COLOR,
|
||||||
|
ATTRIBUTE_TEXCOORD0,
|
||||||
|
ATTRIBUTE_TEXCOORD1,
|
||||||
|
ATTRIBUTE_TEXCOORD2,
|
||||||
|
ATTRIBUTE_TEXCOORD0_W,
|
||||||
|
ATTRIBUTE_NORMQUAT,
|
||||||
|
ATTRIBUTE_VIEW,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Doesn't include const_color because we don't sync it, see comment in BuildFromRegs()
|
||||||
|
struct TevStageConfigRaw {
|
||||||
|
u32 sources_raw;
|
||||||
|
u32 modifiers_raw;
|
||||||
|
u32 ops_raw;
|
||||||
|
u32 scales_raw;
|
||||||
|
explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept {
|
||||||
|
Pica::TexturingRegs::TevStageConfig stage;
|
||||||
|
stage.sources_raw = sources_raw;
|
||||||
|
stage.modifiers_raw = modifiers_raw;
|
||||||
|
stage.ops_raw = ops_raw;
|
||||||
|
stage.const_color = 0;
|
||||||
|
stage.scales_raw = scales_raw;
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PicaFSConfigState {
|
||||||
|
union {
|
||||||
|
BitField<0, 3, Pica::FramebufferRegs::CompareFunc> alpha_test_func;
|
||||||
|
BitField<3, 2, Pica::RasterizerRegs::ScissorMode> scissor_test_mode;
|
||||||
|
BitField<5, 3, Pica::TexturingRegs::TextureConfig::TextureType> texture0_type;
|
||||||
|
BitField<8, 1, u32> texture2_use_coord1;
|
||||||
|
BitField<9, 8, u32> combiner_buffer_input;
|
||||||
|
BitField<17, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable;
|
||||||
|
BitField<18, 3, Pica::TexturingRegs::FogMode> fog_mode;
|
||||||
|
BitField<21, 1, u32> fog_flip;
|
||||||
|
BitField<22, 1, u32> emulate_logic_op;
|
||||||
|
BitField<23, 4, Pica::FramebufferRegs::LogicOp> logic_op;
|
||||||
|
BitField<27, 1, u32> shadow_rendering;
|
||||||
|
BitField<28, 1, u32> shadow_texture_orthographic;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<TevStageConfigRaw, 6> tev_stages;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
BitField<0, 3, u16> num;
|
||||||
|
BitField<3, 1, u16> directional;
|
||||||
|
BitField<4, 1, u16> two_sided_diffuse;
|
||||||
|
BitField<5, 1, u16> dist_atten_enable;
|
||||||
|
BitField<6, 1, u16> spot_atten_enable;
|
||||||
|
BitField<7, 1, u16> geometric_factor_0;
|
||||||
|
BitField<8, 1, u16> geometric_factor_1;
|
||||||
|
BitField<9, 1, u16> shadow_enable;
|
||||||
|
} light[8];
|
||||||
|
|
||||||
|
union {
|
||||||
|
BitField<0, 1, u32> enable;
|
||||||
|
BitField<1, 4, u32> src_num;
|
||||||
|
BitField<5, 2, Pica::LightingRegs::LightingBumpMode> bump_mode;
|
||||||
|
BitField<7, 2, u32> bump_selector;
|
||||||
|
BitField<9, 1, u32> bump_renorm;
|
||||||
|
BitField<10, 1, u32> clamp_highlights;
|
||||||
|
BitField<11, 4, Pica::LightingRegs::LightingConfig> config;
|
||||||
|
BitField<15, 1, u32> enable_primary_alpha;
|
||||||
|
BitField<16, 1, u32> enable_secondary_alpha;
|
||||||
|
BitField<17, 1, u32> enable_shadow;
|
||||||
|
BitField<18, 1, u32> shadow_primary;
|
||||||
|
BitField<19, 1, u32> shadow_secondary;
|
||||||
|
BitField<20, 1, u32> shadow_invert;
|
||||||
|
BitField<21, 1, u32> shadow_alpha;
|
||||||
|
BitField<22, 2, u32> shadow_selector;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
BitField<0, 1, u32> enable;
|
||||||
|
BitField<1, 1, u32> abs_input;
|
||||||
|
BitField<2, 3, Pica::LightingRegs::LightingLutInput> type;
|
||||||
|
};
|
||||||
|
float scale;
|
||||||
|
} lut_d0, lut_d1, lut_sp, lut_fr, lut_rr, lut_rg, lut_rb;
|
||||||
|
} lighting;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
BitField<0, 1, u32> enable;
|
||||||
|
BitField<1, 2, u32> coord;
|
||||||
|
BitField<3, 3, Pica::TexturingRegs::ProcTexClamp> u_clamp;
|
||||||
|
BitField<6, 3, Pica::TexturingRegs::ProcTexClamp> v_clamp;
|
||||||
|
BitField<9, 4, Pica::TexturingRegs::ProcTexCombiner> color_combiner;
|
||||||
|
BitField<13, 4, Pica::TexturingRegs::ProcTexCombiner> alpha_combiner;
|
||||||
|
BitField<17, 3, Pica::TexturingRegs::ProcTexFilter> lut_filter;
|
||||||
|
BitField<20, 1, u32> separate_alpha;
|
||||||
|
BitField<21, 1, u32> noise_enable;
|
||||||
|
BitField<22, 2, Pica::TexturingRegs::ProcTexShift> u_shift;
|
||||||
|
BitField<24, 2, Pica::TexturingRegs::ProcTexShift> v_shift;
|
||||||
|
};
|
||||||
|
s32 lut_width;
|
||||||
|
s32 lut_offset0;
|
||||||
|
s32 lut_offset1;
|
||||||
|
s32 lut_offset2;
|
||||||
|
s32 lut_offset3;
|
||||||
|
u8 lod_min;
|
||||||
|
u8 lod_max;
|
||||||
|
} proctex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This struct contains all state used to generate the GLSL fragment shader that emulates the
|
||||||
|
* current Pica register configuration. This struct is used as a cache key for generated GLSL shader
|
||||||
|
* programs. The functions in gl_shader_gen.cpp should retrieve state from this struct only, not by
|
||||||
|
* directly accessing Pica registers. This should reduce the risk of bugs in shader generation where
|
||||||
|
* Pica state is not being captured in the shader cache key, thereby resulting in (what should be)
|
||||||
|
* two separate shaders sharing the same key.
|
||||||
|
*/
|
||||||
|
struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> {
|
||||||
|
PicaFSConfig(const Pica::Regs& regs, const Instance& instance);
|
||||||
|
|
||||||
|
bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
|
||||||
|
return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
|
||||||
|
return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AttribLoadFlags {
|
||||||
|
Float = 1 << 0,
|
||||||
|
Sint = 1 << 1,
|
||||||
|
Uint = 1 << 2,
|
||||||
|
ZeroW = 1 << 3,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AttribLoadFlags)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This struct contains common information to identify a GL vertex/geometry shader generated from
|
||||||
|
* PICA vertex/geometry shader.
|
||||||
|
*/
|
||||||
|
struct PicaShaderConfigCommon {
|
||||||
|
void Init(const Pica::RasterizerRegs& rasterizer, const Pica::ShaderRegs& regs,
|
||||||
|
Pica::Shader::ShaderSetup& setup);
|
||||||
|
|
||||||
|
u64 program_hash;
|
||||||
|
u64 swizzle_hash;
|
||||||
|
u32 main_offset;
|
||||||
|
bool sanitize_mul;
|
||||||
|
|
||||||
|
u32 num_outputs;
|
||||||
|
// Load operations to apply to the input vertex data
|
||||||
|
std::array<AttribLoadFlags, 16> load_flags;
|
||||||
|
|
||||||
|
// output_map[output register index] -> output attribute index
|
||||||
|
std::array<u32, 16> output_map;
|
||||||
|
|
||||||
|
bool use_geometry_shader;
|
||||||
|
u32 vs_output_attributes;
|
||||||
|
u32 gs_output_attributes;
|
||||||
|
|
||||||
|
struct SemanticMap {
|
||||||
|
u32 attribute_index;
|
||||||
|
u32 component_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// semantic_maps[semantic name] -> GS output attribute index + component index
|
||||||
|
std::array<SemanticMap, 24> semantic_maps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This struct contains information to identify a GL vertex shader generated from PICA vertex
|
||||||
|
* shader.
|
||||||
|
*/
|
||||||
|
struct PicaVSConfig : Common::HashableStruct<PicaShaderConfigCommon> {
|
||||||
|
explicit PicaVSConfig(const Pica::RasterizerRegs& rasterizer, const Pica::ShaderRegs& regs,
|
||||||
|
Pica::Shader::ShaderSetup& setup, const Instance& instance);
|
||||||
|
bool use_clip_planes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PicaGSConfigCommonRaw {
|
||||||
|
void Init(const Pica::Regs& regs);
|
||||||
|
|
||||||
|
u32 vs_output_attributes;
|
||||||
|
u32 gs_output_attributes;
|
||||||
|
|
||||||
|
struct SemanticMap {
|
||||||
|
u32 attribute_index;
|
||||||
|
u32 component_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// semantic_maps[semantic name] -> GS output attribute index + component index
|
||||||
|
std::array<SemanticMap, 24> semantic_maps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This struct contains information to identify a GL geometry shader generated from PICA no-geometry
|
||||||
|
* shader pipeline
|
||||||
|
*/
|
||||||
|
struct PicaFixedGSConfig : Common::HashableStruct<PicaGSConfigCommonRaw> {
|
||||||
|
explicit PicaFixedGSConfig(const Pica::Regs& regs, const Instance& instance);
|
||||||
|
bool use_clip_planes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the GLSL vertex shader program source code that accepts vertices from software shader
|
||||||
|
* and directly passes them to the fragment shader.
|
||||||
|
* @param separable_shader generates shader that can be used for separate shader object
|
||||||
|
* @returns String of the shader source code
|
||||||
|
*/
|
||||||
|
std::string GenerateTrivialVertexShader(bool use_clip_planes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the GLSL vertex shader program source code for the given VS program
|
||||||
|
* @returns String of the shader source code; boost::none on failure
|
||||||
|
*/
|
||||||
|
std::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
|
||||||
|
const PicaVSConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the GLSL fixed geometry shader program source code for non-GS PICA pipeline
|
||||||
|
* @returns String of the shader source code
|
||||||
|
*/
|
||||||
|
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the GLSL fragment shader program source code for the current Pica state
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
std::string GenerateFragmentShader(const PicaFSConfig& config);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<Vulkan::PicaFSConfig> {
|
||||||
|
std::size_t operator()(const Vulkan::PicaFSConfig& k) const noexcept {
|
||||||
|
return k.Hash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<Vulkan::PicaVSConfig> {
|
||||||
|
std::size_t operator()(const Vulkan::PicaVSConfig& k) const noexcept {
|
||||||
|
return k.Hash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<Vulkan::PicaFixedGSConfig> {
|
||||||
|
std::size_t operator()(const Vulkan::PicaFixedGSConfig& k) const noexcept {
|
||||||
|
return k.Hash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,300 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <sirit/sirit.h>
|
||||||
|
|
||||||
|
#include "video_core/renderer_vulkan/vk_shader_gen.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class TelemetrySession;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
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;
|
||||||
|
static constexpr u32 NUM_LIGHTING_SAMPLERS = 24;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FragmentModule(Core::TelemetrySession& telemetry, const PicaFSConfig& config);
|
||||||
|
~FragmentModule();
|
||||||
|
|
||||||
|
/// Emits SPIR-V bytecode corresponding to the provided pica fragment configuration
|
||||||
|
void Generate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Undos the vulkan perspective transformation and applies the PICA one
|
||||||
|
void WriteDepth();
|
||||||
|
|
||||||
|
/// Emits code to emulate the scissor rectangle
|
||||||
|
void WriteScissor();
|
||||||
|
|
||||||
|
/// Writes the code to emulate fragment lighting
|
||||||
|
void WriteLighting();
|
||||||
|
|
||||||
|
/// Writes the code to emulate fog
|
||||||
|
void WriteFog();
|
||||||
|
|
||||||
|
/// Writes the code to emulate gas rendering
|
||||||
|
void WriteGas();
|
||||||
|
|
||||||
|
/// Writes the code to emulate the specified TEV stage
|
||||||
|
void WriteTevStage(s32 index);
|
||||||
|
|
||||||
|
/// Defines the tex3 proctex sampling function
|
||||||
|
void DefineProcTexSampler();
|
||||||
|
|
||||||
|
/// Writes the if-statement condition used to evaluate alpha testing.
|
||||||
|
void WriteAlphaTestCondition(Pica::FramebufferRegs::CompareFunc func);
|
||||||
|
|
||||||
|
/// Samples the current fragment texel from the provided texture unit
|
||||||
|
[[nodiscard]] Id SampleTexture(u32 texture_unit);
|
||||||
|
|
||||||
|
/// Samples the current fragment texel from shadow plane
|
||||||
|
[[nodiscard]] Id SampleShadow();
|
||||||
|
|
||||||
|
[[nodiscard]] Id AppendProcTexShiftOffset(Id v, Pica::TexturingRegs::ProcTexShift mode,
|
||||||
|
Pica::TexturingRegs::ProcTexClamp clamp_mode);
|
||||||
|
|
||||||
|
[[nodiscard]] Id AppendProcTexClamp(Id var, Pica::TexturingRegs::ProcTexClamp mode);
|
||||||
|
|
||||||
|
[[nodiscard]] Id AppendProcTexCombineAndMap(Pica::TexturingRegs::ProcTexCombiner combiner, Id u,
|
||||||
|
Id v, Id offset);
|
||||||
|
|
||||||
|
/// Rounds the provided variable to the nearest 1/255th
|
||||||
|
[[nodiscard]] Id Byteround(Id variable_id, u32 size = 1);
|
||||||
|
|
||||||
|
/// LUT sampling uitlity
|
||||||
|
/// For NoiseLUT/ColorMap/AlphaMap, coord=0.0 is lut[0], coord=127.0/128.0 is lut[127] and
|
||||||
|
/// coord=1.0 is lut[127]+lut_diff[127]. For other indices, the result is interpolated using
|
||||||
|
/// value entries and difference entries.
|
||||||
|
[[nodiscard]] Id ProcTexLookupLUT(Id offset, Id coord);
|
||||||
|
|
||||||
|
/// Generates random noise with proctex
|
||||||
|
[[nodiscard]] Id ProcTexNoiseCoef(Id x);
|
||||||
|
|
||||||
|
/// Samples a color value from the rgba texture lut
|
||||||
|
[[nodiscard]] Id SampleProcTexColor(Id lut_coord, Id level);
|
||||||
|
|
||||||
|
/// Lookups the lighting LUT at the provided lut_index
|
||||||
|
[[nodiscard]] Id LookupLightingLUT(Id lut_index, Id index, Id delta);
|
||||||
|
|
||||||
|
/// Writes the specified TEV stage source component(s)
|
||||||
|
[[nodiscard]] Id AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||||
|
|
||||||
|
/// Writes the color components to use for the specified TEV stage color modifier
|
||||||
|
[[nodiscard]] Id AppendColorModifier(
|
||||||
|
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
|
||||||
|
Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||||
|
|
||||||
|
/// Writes the alpha component to use for the specified TEV stage alpha modifier
|
||||||
|
[[nodiscard]] Id AppendAlphaModifier(
|
||||||
|
Pica::TexturingRegs::TevStageConfig::AlphaModifier modifier,
|
||||||
|
Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||||
|
|
||||||
|
/// Writes the combiner function for the color components for the specified TEV stage operation
|
||||||
|
[[nodiscard]] Id AppendColorCombiner(Pica::TexturingRegs::TevStageConfig::Operation operation);
|
||||||
|
|
||||||
|
/// Writes the combiner function for the alpha component for the specified TEV stage operation
|
||||||
|
[[nodiscard]] Id AppendAlphaCombiner(Pica::TexturingRegs::TevStageConfig::Operation operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Creates a constant array of integers
|
||||||
|
template <typename... T>
|
||||||
|
void InitTableS32(Id table, T... elems) {
|
||||||
|
const Id table_const{ConstS32(elems...)};
|
||||||
|
OpStore(table, table_const);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Loads the member specified from the shader_data uniform struct
|
||||||
|
template <typename... Ids>
|
||||||
|
[[nodiscard]] Id GetShaderDataMember(Id type, Ids... ids) {
|
||||||
|
const Id uniform_ptr{TypePointer(spv::StorageClass::Uniform, type)};
|
||||||
|
return OpLoad(type, OpAccessChain(uniform_ptr, shader_data_id, ids...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pads the provided vector by inserting args at the end
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id PadVectorF32(Id vector, Id pad_type_id, Args&&... args) {
|
||||||
|
return OpCompositeConstruct(pad_type_id, vector, ConstF32(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a 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 a input 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a uniform constant variable
|
||||||
|
[[nodiscard]] Id DefineUniformConst(Id type, u32 set, u32 binding, bool readonly = false) {
|
||||||
|
const Id uniform_id{DefineVar(type, spv::StorageClass::UniformConstant)};
|
||||||
|
Decorate(uniform_id, spv::Decoration::DescriptorSet, set);
|
||||||
|
Decorate(uniform_id, spv::Decoration::Binding, binding);
|
||||||
|
if (readonly) {
|
||||||
|
Decorate(uniform_id, spv::Decoration::NonWritable);
|
||||||
|
}
|
||||||
|
return uniform_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the id of a signed integer constant of value
|
||||||
|
[[nodiscard]] Id ConstU32(u32 value) {
|
||||||
|
return Constant(u32_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstU32(Args&&... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(u32_id, values)...};
|
||||||
|
const Id type = size <= 4 ? uvec_ids.Get(size) : TypeArray(u32_id, ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the id of a signed integer constant of value
|
||||||
|
[[nodiscard]] Id ConstS32(s32 value) {
|
||||||
|
return Constant(i32_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstS32(Args&&... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(i32_id, values)...};
|
||||||
|
const Id type = size <= 4 ? ivec_ids.Get(size) : TypeArray(i32_id, ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the id of a float constant of value
|
||||||
|
[[nodiscard]] Id ConstF32(f32 value) {
|
||||||
|
return Constant(f32_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstF32(Args... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(f32_id, values)...};
|
||||||
|
const Id type = size <= 4 ? vec_ids.Get(size) : TypeArray(f32_id, ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineArithmeticTypes();
|
||||||
|
void DefineEntryPoint();
|
||||||
|
void DefineUniformStructs();
|
||||||
|
void DefineInterface();
|
||||||
|
Id CompareShadow(Id pixel, Id z);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::TelemetrySession& telemetry;
|
||||||
|
PicaFSConfig config;
|
||||||
|
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{};
|
||||||
|
|
||||||
|
Id image2d_id{};
|
||||||
|
Id image_cube_id{};
|
||||||
|
Id image_buffer_id{};
|
||||||
|
Id image_r32_id{};
|
||||||
|
Id sampler_id{};
|
||||||
|
Id shader_data_id{};
|
||||||
|
|
||||||
|
Id primary_color_id{};
|
||||||
|
Id texcoord0_id{};
|
||||||
|
Id texcoord1_id{};
|
||||||
|
Id texcoord2_id{};
|
||||||
|
Id texcoord0_w_id{};
|
||||||
|
Id normquat_id{};
|
||||||
|
Id view_id{};
|
||||||
|
Id color_id{};
|
||||||
|
|
||||||
|
Id gl_frag_coord_id{};
|
||||||
|
Id gl_frag_depth_id{};
|
||||||
|
Id depth{};
|
||||||
|
|
||||||
|
Id tex0_id{};
|
||||||
|
Id tex1_id{};
|
||||||
|
Id tex2_id{};
|
||||||
|
Id tex_cube_id{};
|
||||||
|
Id texture_buffer_lut_lf_id{};
|
||||||
|
Id texture_buffer_lut_rg_id{};
|
||||||
|
Id texture_buffer_lut_rgba_id{};
|
||||||
|
Id shadow_buffer_id{};
|
||||||
|
Id shadow_texture_px_id{};
|
||||||
|
Id shadow_texture_nx_id{};
|
||||||
|
Id shadow_texture_py_id{};
|
||||||
|
Id shadow_texture_ny_id{};
|
||||||
|
Id shadow_texture_pz_id{};
|
||||||
|
Id shadow_texture_nz_id{};
|
||||||
|
|
||||||
|
Id texture_buffer_lut_lf{};
|
||||||
|
Id texture_buffer_lut_rg{};
|
||||||
|
Id texture_buffer_lut_rgba{};
|
||||||
|
|
||||||
|
Id rounded_primary_color{};
|
||||||
|
Id primary_fragment_color{};
|
||||||
|
Id secondary_fragment_color{};
|
||||||
|
Id combiner_buffer{};
|
||||||
|
Id next_combiner_buffer{};
|
||||||
|
Id last_tex_env_out{};
|
||||||
|
|
||||||
|
Id color_results_1{};
|
||||||
|
Id color_results_2{};
|
||||||
|
Id color_results_3{};
|
||||||
|
Id alpha_results_1{};
|
||||||
|
Id alpha_results_2{};
|
||||||
|
Id alpha_results_3{};
|
||||||
|
|
||||||
|
Id proctex_func{};
|
||||||
|
Id noise1d_table{};
|
||||||
|
Id noise2d_table{};
|
||||||
|
Id lut_offsets{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the SPIR-V fragment shader program source code for the current Pica state
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
std::vector<u32> GenerateFragmentShaderSPV(const PicaFSConfig& config);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,233 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <SPIRV/GlslangToSpv.h>
|
||||||
|
#include <glslang/Include/ResourceLimits.h>
|
||||||
|
#include <glslang/Public/ShaderLang.h>
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/literals.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
using namespace Common::Literals;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr TBuiltInResource DefaultTBuiltInResource = {
|
||||||
|
.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,
|
||||||
|
.maxMeshOutputVerticesNV = 256,
|
||||||
|
.maxMeshOutputPrimitivesNV = 512,
|
||||||
|
.maxMeshWorkGroupSizeX_NV = 32,
|
||||||
|
.maxMeshWorkGroupSizeY_NV = 1,
|
||||||
|
.maxMeshWorkGroupSizeZ_NV = 1,
|
||||||
|
.maxTaskWorkGroupSizeX_NV = 32,
|
||||||
|
.maxTaskWorkGroupSizeY_NV = 1,
|
||||||
|
.maxTaskWorkGroupSizeZ_NV = 1,
|
||||||
|
.maxMeshViewCountNV = 4,
|
||||||
|
.maxDualSourceDrawBuffersEXT = 1,
|
||||||
|
.limits =
|
||||||
|
TLimits{
|
||||||
|
.nonInductiveForLoops = 1,
|
||||||
|
.whileLoops = 1,
|
||||||
|
.doWhileLoops = 1,
|
||||||
|
.generalUniformIndexing = 1,
|
||||||
|
.generalAttributeMatrixVectorIndexing = 1,
|
||||||
|
.generalVaryingIndexing = 1,
|
||||||
|
.generalSamplerIndexing = 1,
|
||||||
|
.generalVariableIndexing = 1,
|
||||||
|
.generalConstantMatrixVectorIndexing = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
EShLanguage ToEshShaderStage(vk::ShaderStageFlagBits stage) {
|
||||||
|
switch (stage) {
|
||||||
|
case vk::ShaderStageFlagBits::eVertex:
|
||||||
|
return EShLanguage::EShLangVertex;
|
||||||
|
case vk::ShaderStageFlagBits::eGeometry:
|
||||||
|
return EShLanguage::EShLangGeometry;
|
||||||
|
case vk::ShaderStageFlagBits::eFragment:
|
||||||
|
return EShLanguage::EShLangFragment;
|
||||||
|
case vk::ShaderStageFlagBits::eCompute:
|
||||||
|
return EShLanguage::EShLangCompute;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unkown shader stage {}", stage);
|
||||||
|
}
|
||||||
|
return EShLanguage::EShLangVertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InitializeCompiler() {
|
||||||
|
static bool glslang_initialized = false;
|
||||||
|
|
||||||
|
if (glslang_initialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!glslang::InitializeProcess()) {
|
||||||
|
LOG_CRITICAL(Render_Vulkan, "Failed to initialize glslang shader compiler");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atexit([]() { glslang::FinalizeProcess(); });
|
||||||
|
|
||||||
|
glslang_initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) {
|
||||||
|
if (!InitializeCompiler()) {
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EProfile profile = ECoreProfile;
|
||||||
|
EShMessages messages =
|
||||||
|
static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
|
||||||
|
EShLanguage lang = ToEshShaderStage(stage);
|
||||||
|
|
||||||
|
const int default_version = 450;
|
||||||
|
const char* pass_source_code = code.data();
|
||||||
|
int pass_source_code_length = static_cast<int>(code.size());
|
||||||
|
|
||||||
|
auto shader = std::make_unique<glslang::TShader>(lang);
|
||||||
|
shader->setEnvTarget(glslang::EShTargetSpv,
|
||||||
|
glslang::EShTargetLanguageVersion::EShTargetSpv_1_3);
|
||||||
|
shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1);
|
||||||
|
|
||||||
|
glslang::TShader::ForbidIncluder includer;
|
||||||
|
if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages,
|
||||||
|
includer)) [[unlikely]] {
|
||||||
|
LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(),
|
||||||
|
shader->getInfoDebugLog());
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even though there's only a single shader, we still need to link it to generate SPV
|
||||||
|
auto program = std::make_unique<glslang::TProgram>();
|
||||||
|
program->addShader(shader.get());
|
||||||
|
if (!program->link(messages)) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(),
|
||||||
|
program->getInfoDebugLog());
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
glslang::TIntermediate* intermediate = program->getIntermediate(lang);
|
||||||
|
std::vector<u32> out_code;
|
||||||
|
spv::SpvBuildLogger logger;
|
||||||
|
glslang::SpvOptions options;
|
||||||
|
|
||||||
|
// Enable optimizations on the generated SPIR-V code.
|
||||||
|
options.disableOptimizer = false;
|
||||||
|
options.validate = false;
|
||||||
|
options.optimizeSize = true;
|
||||||
|
|
||||||
|
out_code.reserve(8_KiB);
|
||||||
|
glslang::GlslangToSpv(*intermediate, out_code, &logger, &options);
|
||||||
|
|
||||||
|
const std::string spv_messages = logger.getAllMessages();
|
||||||
|
if (!spv_messages.empty()) {
|
||||||
|
LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompileSPV(out_code, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {
|
||||||
|
const vk::ShaderModuleCreateInfo shader_info = {
|
||||||
|
.codeSize = code.size() * sizeof(u32),
|
||||||
|
.pCode = code.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return device.createShaderModule(shader_info);
|
||||||
|
} catch (vk::SystemError& err) {
|
||||||
|
UNREACHABLE_MSG("{}", err.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a vulkan shader module from SPIR-V bytecode.
|
||||||
|
* @param code The SPIR-V bytecode data.
|
||||||
|
* @param device The vulkan device handle
|
||||||
|
*/
|
||||||
|
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
Reference in New Issue