mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	renderer_vulkan: Add shader generators (#6630)
This commit is contained in:
		
							parent
							
								
									7edc86a9bc
								
							
						
					
					
						commit
						2e479fcec5
					
				
					 7 changed files with 4238 additions and 1 deletions
				
			
		|  | @ -104,6 +104,12 @@ add_library(video_core STATIC | |||
|     renderer_vulkan/vk_instance.h | ||||
|     renderer_vulkan/vk_platform.cpp | ||||
|     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/shader.cpp | ||||
|     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 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}) | ||||
| 
 | ||||
| if ("x86_64" IN_LIST ARCHITECTURE) | ||||
|  |  | |||
							
								
								
									
										1880
									
								
								src/video_core/renderer_vulkan/vk_shader_gen.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1880
									
								
								src/video_core/renderer_vulkan/vk_shader_gen.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										274
									
								
								src/video_core/renderer_vulkan/vk_shader_gen.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								src/video_core/renderer_vulkan/vk_shader_gen.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
							
								
								
									
										1516
									
								
								src/video_core/renderer_vulkan/vk_shader_gen_spv.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1516
									
								
								src/video_core/renderer_vulkan/vk_shader_gen_spv.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										300
									
								
								src/video_core/renderer_vulkan/vk_shader_gen_spv.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								src/video_core/renderer_vulkan/vk_shader_gen_spv.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
							
								
								
									
										233
									
								
								src/video_core/renderer_vulkan/vk_shader_util.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/video_core/renderer_vulkan/vk_shader_util.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
							
								
								
									
										28
									
								
								src/video_core/renderer_vulkan/vk_shader_util.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/video_core/renderer_vulkan/vk_shader_util.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue