mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	gl_shader_gen: generate programmable vs/gs and fixed gs
This commit is contained in:
		
							parent
							
								
									8186820d16
								
							
						
					
					
						commit
						191b29e402
					
				
					 5 changed files with 632 additions and 25 deletions
				
			
		|  | @ -1265,7 +1265,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( | |||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SetShader() { | ||||
|     auto config = GLShader::PicaShaderConfig::BuildFromRegs(Pica::g_state.regs); | ||||
|     auto config = GLShader::PicaFSConfig::BuildFromRegs(Pica::g_state.regs); | ||||
|     shader_program_manager->UseFragmentShader(config); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include <cstring> | ||||
| #include "common/assert.h" | ||||
| #include "common/bit_field.h" | ||||
| #include "common/bit_set.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "video_core/regs_framebuffer.h" | ||||
|  | @ -14,6 +15,7 @@ | |||
| #include "video_core/regs_rasterizer.h" | ||||
| #include "video_core/regs_texturing.h" | ||||
| #include "video_core/renderer_opengl/gl_rasterizer.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_decompiler.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_util.h" | ||||
| 
 | ||||
|  | @ -22,6 +24,7 @@ using Pica::LightingRegs; | |||
| using Pica::RasterizerRegs; | ||||
| using Pica::TexturingRegs; | ||||
| using TevStageConfig = TexturingRegs::TevStageConfig; | ||||
| using VSOutputAttributes = RasterizerRegs::VSOutputAttributes; | ||||
| 
 | ||||
| namespace GLShader { | ||||
| 
 | ||||
|  | @ -92,8 +95,8 @@ out gl_PerVertex { | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { | ||||
|     PicaShaderConfig res; | ||||
| PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) { | ||||
|     PicaFSConfig res; | ||||
| 
 | ||||
|     auto& state = res.state; | ||||
| 
 | ||||
|  | @ -219,6 +222,59 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { | |||
|     return res; | ||||
| } | ||||
| 
 | ||||
| void PicaShaderConfigCommon::Init(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup) { | ||||
|     program_hash = setup.GetProgramCodeHash(); | ||||
|     swizzle_hash = setup.GetSwizzleDataHash(); | ||||
|     main_offset = regs.main_offset; | ||||
|     sanitize_mul = false; // TODO (wwylele): stubbed now. Should sync with user settings
 | ||||
| 
 | ||||
|     num_outputs = 0; | ||||
|     output_map.fill(16); | ||||
| 
 | ||||
|     for (int reg : Common::BitSet<u32>(regs.output_mask)) { | ||||
|         output_map[reg] = num_outputs++; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PicaGSConfigCommonRaw::Init(const Pica::Regs& regs) { | ||||
|     vs_output_attributes = Common::BitSet<u32>(regs.vs.output_mask).Count(); | ||||
|     gs_output_attributes = vs_output_attributes; | ||||
| 
 | ||||
|     semantic_maps.fill({16, 0}); | ||||
|     for (u32 attrib = 0; attrib < regs.rasterizer.vs_output_total; ++attrib) { | ||||
|         std::array<VSOutputAttributes::Semantic, 4> semantics = { | ||||
|             regs.rasterizer.vs_output_attributes[attrib].map_x, | ||||
|             regs.rasterizer.vs_output_attributes[attrib].map_y, | ||||
|             regs.rasterizer.vs_output_attributes[attrib].map_z, | ||||
|             regs.rasterizer.vs_output_attributes[attrib].map_w}; | ||||
|         for (u32 comp = 0; comp < 4; ++comp) { | ||||
|             const auto semantic = semantics[comp]; | ||||
|             if (static_cast<size_t>(semantic) < 24) { | ||||
|                 semantic_maps[static_cast<size_t>(semantic)] = {attrib, comp}; | ||||
|             } else if (semantic != VSOutputAttributes::INVALID) { | ||||
|                 NGLOG_ERROR(Render_OpenGL, "Invalid/unknown semantic id: {}", | ||||
|                             static_cast<u32>(semantic)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PicaGSConfigRaw::Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) { | ||||
|     PicaShaderConfigCommon::Init(regs.gs, setup); | ||||
|     PicaGSConfigCommonRaw::Init(regs); | ||||
| 
 | ||||
|     num_inputs = regs.gs.max_input_attribute_index + 1; | ||||
|     input_map.fill(16); | ||||
| 
 | ||||
|     for (u32 attr = 0; attr < num_inputs; ++attr) { | ||||
|         input_map[regs.gs.GetRegisterForAttribute(attr)] = attr; | ||||
|     } | ||||
| 
 | ||||
|     attributes_per_vertex = regs.pipeline.vs_outmap_total_minus_1_a + 1; | ||||
| 
 | ||||
|     gs_output_attributes = num_outputs; | ||||
| } | ||||
| 
 | ||||
| /// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code)
 | ||||
| static bool IsPassThroughTevStage(const TevStageConfig& stage) { | ||||
|     return (stage.color_op == TevStageConfig::Operation::Replace && | ||||
|  | @ -230,7 +286,7 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) { | |||
|             stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1); | ||||
| } | ||||
| 
 | ||||
| static std::string SampleTexture(const PicaShaderConfig& config, unsigned texture_unit) { | ||||
| static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_unit) { | ||||
|     const auto& state = config.state; | ||||
|     switch (texture_unit) { | ||||
|     case 0: | ||||
|  | @ -274,7 +330,7 @@ static std::string SampleTexture(const PicaShaderConfig& config, unsigned textur | |||
| } | ||||
| 
 | ||||
| /// Writes the specified TEV stage source component(s)
 | ||||
| static void AppendSource(std::string& out, const PicaShaderConfig& config, | ||||
| static void AppendSource(std::string& out, const PicaFSConfig& config, | ||||
|                          TevStageConfig::Source source, const std::string& index_name) { | ||||
|     const auto& state = config.state; | ||||
|     using Source = TevStageConfig::Source; | ||||
|  | @ -317,7 +373,7 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config, | |||
| } | ||||
| 
 | ||||
| /// Writes the color components to use for the specified TEV stage color modifier
 | ||||
| static void AppendColorModifier(std::string& out, const PicaShaderConfig& config, | ||||
| static void AppendColorModifier(std::string& out, const PicaFSConfig& config, | ||||
|                                 TevStageConfig::ColorModifier modifier, | ||||
|                                 TevStageConfig::Source source, const std::string& index_name) { | ||||
|     using ColorModifier = TevStageConfig::ColorModifier; | ||||
|  | @ -375,7 +431,7 @@ static void AppendColorModifier(std::string& out, const PicaShaderConfig& config | |||
| } | ||||
| 
 | ||||
| /// Writes the alpha component to use for the specified TEV stage alpha modifier
 | ||||
| static void AppendAlphaModifier(std::string& out, const PicaShaderConfig& config, | ||||
| static void AppendAlphaModifier(std::string& out, const PicaFSConfig& config, | ||||
|                                 TevStageConfig::AlphaModifier modifier, | ||||
|                                 TevStageConfig::Source source, const std::string& index_name) { | ||||
|     using AlphaModifier = TevStageConfig::AlphaModifier; | ||||
|  | @ -540,7 +596,7 @@ static void AppendAlphaTestCondition(std::string& out, FramebufferRegs::CompareF | |||
| } | ||||
| 
 | ||||
| /// Writes the code to emulate the specified TEV stage
 | ||||
| static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) { | ||||
| static void WriteTevStage(std::string& out, const PicaFSConfig& config, unsigned index) { | ||||
|     const auto stage = | ||||
|         static_cast<const TexturingRegs::TevStageConfig>(config.state.tev_stages[index]); | ||||
|     if (!IsPassThroughTevStage(stage)) { | ||||
|  | @ -598,7 +654,7 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi | |||
| } | ||||
| 
 | ||||
| /// Writes the code to emulate fragment lighting
 | ||||
| static void WriteLighting(std::string& out, const PicaShaderConfig& config) { | ||||
| static void WriteLighting(std::string& out, const PicaFSConfig& config) { | ||||
|     const auto& lighting = config.state.lighting; | ||||
| 
 | ||||
|     // Define lighting globals
 | ||||
|  | @ -994,7 +1050,7 @@ void AppendProcTexCombineAndMap(std::string& out, ProcTexCombiner combiner, | |||
|     out += "ProcTexLookupLUT(" + map_lut + ", " + combined + ")"; | ||||
| } | ||||
| 
 | ||||
| void AppendProcTexSampler(std::string& out, const PicaShaderConfig& config) { | ||||
| void AppendProcTexSampler(std::string& out, const PicaFSConfig& config) { | ||||
|     // 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
 | ||||
|  | @ -1121,7 +1177,7 @@ float ProcTexNoiseCoef(vec2 x) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string GenerateFragmentShader(const PicaShaderConfig& config, bool separable_shader) { | ||||
| std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader) { | ||||
|     const auto& state = config.state; | ||||
| 
 | ||||
|     std::string out = "#version 330 core\n"; | ||||
|  | @ -1327,4 +1383,296 @@ void main() { | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, | ||||
|                                                   const PicaVSConfig& config, | ||||
|                                                   bool separable_shader) { | ||||
|     std::string out = "#version 330 core\n"; | ||||
|     if (separable_shader) { | ||||
|         out += "#extension GL_ARB_separate_shader_objects : enable\n"; | ||||
|     } | ||||
| 
 | ||||
|     out += Pica::Shader::Decompiler::GetCommonDeclarations(); | ||||
| 
 | ||||
|     std::array<bool, 16> used_regs{}; | ||||
|     auto get_input_reg = [&](u32 reg) -> std::string { | ||||
|         ASSERT(reg < 16); | ||||
|         used_regs[reg] = true; | ||||
|         return "vs_in_reg" + std::to_string(reg); | ||||
|     }; | ||||
| 
 | ||||
|     auto get_output_reg = [&](u32 reg) -> std::string { | ||||
|         ASSERT(reg < 16); | ||||
|         if (config.state.output_map[reg] < config.state.num_outputs) { | ||||
|             return "vs_out_attr" + std::to_string(config.state.output_map[reg]); | ||||
|         } | ||||
|         return ""; | ||||
|     }; | ||||
| 
 | ||||
|     auto program_source_opt = Pica::Shader::Decompiler::DecompileProgram( | ||||
|         setup.program_code, setup.swizzle_data, config.state.main_offset, get_input_reg, | ||||
|         get_output_reg, config.state.sanitize_mul, false); | ||||
| 
 | ||||
|     if (!program_source_opt) | ||||
|         return boost::none; | ||||
| 
 | ||||
|     std::string& program_source = program_source_opt.get(); | ||||
| 
 | ||||
|     out += R"( | ||||
| #define uniforms vs_uniforms | ||||
| layout (std140) uniform vs_config { | ||||
|     pica_uniforms uniforms; | ||||
| }; | ||||
| 
 | ||||
| )"; | ||||
|     // input attributes declaration
 | ||||
|     for (std::size_t i = 0; i < used_regs.size(); ++i) { | ||||
|         if (used_regs[i]) { | ||||
|             out += "layout(location = " + std::to_string(i) + ") in vec4 vs_in_reg" + | ||||
|                    std::to_string(i) + ";\n"; | ||||
|         } | ||||
|     } | ||||
|     out += "\n"; | ||||
| 
 | ||||
|     // output attributes declaration
 | ||||
|     for (u32 i = 0; i < config.state.num_outputs; ++i) { | ||||
|         out += (separable_shader ? "layout(location = " + std::to_string(i) + ")" : std::string{}) + | ||||
|                " out vec4 vs_out_attr" + std::to_string(i) + ";\n"; | ||||
|     } | ||||
| 
 | ||||
|     out += "\nvoid main() {\n"; | ||||
|     for (u32 i = 0; i < config.state.num_outputs; ++i) { | ||||
|         out += "    vs_out_attr" + std::to_string(i) + " = vec4(0.0, 0.0, 0.0, 1.0);\n"; | ||||
|     } | ||||
|     out += "\n    exec_shader();\n}\n\n"; | ||||
| 
 | ||||
|     out += program_source; | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| static std::string GetGSCommonSource(const PicaGSConfigCommonRaw& config, bool separable_shader) { | ||||
|     std::string out = GetVertexInterfaceDeclaration(true, separable_shader); | ||||
|     out += UniformBlockDef; | ||||
|     out += Pica::Shader::Decompiler::GetCommonDeclarations(); | ||||
| 
 | ||||
|     out += '\n'; | ||||
|     for (u32 i = 0; i < config.vs_output_attributes; ++i) { | ||||
|         out += (separable_shader ? "layout(location = " + std::to_string(i) + ")" : std::string{}) + | ||||
|                " in vec4 vs_out_attr" + std::to_string(i) + "[];\n"; | ||||
|     } | ||||
| 
 | ||||
|     out += R"( | ||||
| #define uniforms gs_uniforms | ||||
| layout (std140) uniform gs_config { | ||||
|     pica_uniforms uniforms; | ||||
| }; | ||||
| 
 | ||||
| struct Vertex { | ||||
| )"; | ||||
|     out += "    vec4 attributes[" + std::to_string(config.gs_output_attributes) + "];\n"; | ||||
|     out += "};\n\n"; | ||||
| 
 | ||||
|     auto semantic = [&config](VSOutputAttributes::Semantic slot_semantic) -> std::string { | ||||
|         u32 slot = static_cast<u32>(slot_semantic); | ||||
|         u32 attrib = config.semantic_maps[slot].attribute_index; | ||||
|         u32 comp = config.semantic_maps[slot].component_index; | ||||
|         if (attrib < config.gs_output_attributes) { | ||||
|             return "vtx.attributes[" + std::to_string(attrib) + "]." + "xyzw"[comp]; | ||||
|         } | ||||
|         return "0.0"; | ||||
|     }; | ||||
| 
 | ||||
|     out += "vec4 GetVertexQuaternion(Vertex vtx) {\n"; | ||||
|     out += "    return vec4(" + semantic(VSOutputAttributes::QUATERNION_X) + ", " + | ||||
|            semantic(VSOutputAttributes::QUATERNION_Y) + ", " + | ||||
|            semantic(VSOutputAttributes::QUATERNION_Z) + ", " + | ||||
|            semantic(VSOutputAttributes::QUATERNION_W) + ");\n"; | ||||
|     out += "}\n\n"; | ||||
| 
 | ||||
|     out += "void EmitVtx(Vertex vtx, bool quats_opposite) {\n"; | ||||
|     out += "    vec4 vtx_pos = vec4(" + semantic(VSOutputAttributes::POSITION_X) + ", " + | ||||
|            semantic(VSOutputAttributes::POSITION_Y) + ", " + | ||||
|            semantic(VSOutputAttributes::POSITION_Z) + ", " + | ||||
|            semantic(VSOutputAttributes::POSITION_W) + ");\n"; | ||||
|     out += "    gl_Position = vtx_pos;\n"; | ||||
|     out += "    gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0
 | ||||
|     out += "    gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n\n"; | ||||
| 
 | ||||
|     out += "    vec4 vtx_quat = GetVertexQuaternion(vtx);\n"; | ||||
|     out += "    normquat = mix(vtx_quat, -vtx_quat, bvec4(quats_opposite));\n\n"; | ||||
| 
 | ||||
|     out += "    vec4 vtx_color = vec4(" + semantic(VSOutputAttributes::COLOR_R) + ", " + | ||||
|            semantic(VSOutputAttributes::COLOR_G) + ", " + semantic(VSOutputAttributes::COLOR_B) + | ||||
|            ", " + semantic(VSOutputAttributes::COLOR_A) + ");\n"; | ||||
|     out += "    primary_color = min(abs(vtx_color), vec4(1.0));\n\n"; | ||||
| 
 | ||||
|     out += "    texcoord0 = vec2(" + semantic(VSOutputAttributes::TEXCOORD0_U) + ", " + | ||||
|            semantic(VSOutputAttributes::TEXCOORD0_V) + ");\n"; | ||||
|     out += "    texcoord1 = vec2(" + semantic(VSOutputAttributes::TEXCOORD1_U) + ", " + | ||||
|            semantic(VSOutputAttributes::TEXCOORD1_V) + ");\n\n"; | ||||
| 
 | ||||
|     out += "    texcoord0_w = " + semantic(VSOutputAttributes::TEXCOORD0_W) + ";\n"; | ||||
|     out += "    view = vec3(" + semantic(VSOutputAttributes::VIEW_X) + ", " + | ||||
|            semantic(VSOutputAttributes::VIEW_Y) + ", " + semantic(VSOutputAttributes::VIEW_Z) + | ||||
|            ");\n\n"; | ||||
| 
 | ||||
|     out += "    texcoord2 = vec2(" + semantic(VSOutputAttributes::TEXCOORD2_U) + ", " + | ||||
|            semantic(VSOutputAttributes::TEXCOORD2_V) + ");\n\n"; | ||||
| 
 | ||||
|     out += "    EmitVertex();\n"; | ||||
|     out += "}\n"; | ||||
| 
 | ||||
|     out += R"( | ||||
| bool AreQuaternionsOpposite(vec4 qa, vec4 qb) { | ||||
|     return (dot(qa, qb) < 0.0); | ||||
| } | ||||
| 
 | ||||
| void EmitPrim(Vertex vtx0, Vertex vtx1, Vertex vtx2) { | ||||
|     EmitVtx(vtx0, false); | ||||
|     EmitVtx(vtx1, AreQuaternionsOpposite(GetVertexQuaternion(vtx0), GetVertexQuaternion(vtx1))); | ||||
|     EmitVtx(vtx2, AreQuaternionsOpposite(GetVertexQuaternion(vtx0), GetVertexQuaternion(vtx2))); | ||||
|     EndPrimitive(); | ||||
| } | ||||
| )"; | ||||
| 
 | ||||
|     return out; | ||||
| }; | ||||
| 
 | ||||
| std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) { | ||||
|     std::string out = "#version 330 core\n"; | ||||
|     if (separable_shader) { | ||||
|         out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; | ||||
|     } | ||||
| 
 | ||||
|     out += R"( | ||||
| layout(triangles) in; | ||||
| layout(triangle_strip, max_vertices = 3) out; | ||||
| 
 | ||||
| )"; | ||||
| 
 | ||||
|     out += GetGSCommonSource(config.state, separable_shader); | ||||
| 
 | ||||
|     out += R"( | ||||
| void main() { | ||||
|     Vertex prim_buffer[3]; | ||||
| )"; | ||||
|     for (u32 vtx = 0; vtx < 3; ++vtx) { | ||||
|         out += "    prim_buffer[" + std::to_string(vtx) + "].attributes = vec4[" + | ||||
|                std::to_string(config.state.gs_output_attributes) + "]("; | ||||
|         for (u32 i = 0; i < config.state.vs_output_attributes; ++i) { | ||||
|             out += std::string(i == 0 ? "" : ", ") + "vs_out_attr" + std::to_string(i) + "[" + | ||||
|                    std::to_string(vtx) + "]"; | ||||
|         } | ||||
|         out += ");\n"; | ||||
|     } | ||||
|     out += "    EmitPrim(prim_buffer[0], prim_buffer[1], prim_buffer[2]);\n"; | ||||
|     out += "}\n"; | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup, | ||||
|                                                     const PicaGSConfig& config, | ||||
|                                                     bool separable_shader) { | ||||
|     std::string out = "#version 330 core\n"; | ||||
|     if (separable_shader) { | ||||
|         out += "#extension GL_ARB_separate_shader_objects : enable\n"; | ||||
|     } | ||||
| 
 | ||||
|     if (config.state.num_inputs % config.state.attributes_per_vertex != 0) | ||||
|         return boost::none; | ||||
| 
 | ||||
|     switch (config.state.num_inputs / config.state.attributes_per_vertex) { | ||||
|     case 1: | ||||
|         out += "layout(points) in;\n"; | ||||
|         break; | ||||
|     case 2: | ||||
|         out += "layout(lines) in;\n"; | ||||
|         break; | ||||
|     case 4: | ||||
|         out += "layout(lines_adjacency) in;\n"; | ||||
|         break; | ||||
|     case 3: | ||||
|         out += "layout(triangles) in;\n"; | ||||
|         break; | ||||
|     case 6: | ||||
|         out += "layout(triangles_adjacency) in;\n"; | ||||
|         break; | ||||
|     default: | ||||
|         return boost::none; | ||||
|     } | ||||
|     out += "layout(triangle_strip, max_vertices = 30) out;\n\n"; | ||||
| 
 | ||||
|     out += GetGSCommonSource(config.state, separable_shader); | ||||
| 
 | ||||
|     auto get_input_reg = [&](u32 reg) -> std::string { | ||||
|         ASSERT(reg < 16); | ||||
|         u32 attr = config.state.input_map[reg]; | ||||
|         if (attr < config.state.num_inputs) { | ||||
|             return "vs_out_attr" + std::to_string(attr % config.state.attributes_per_vertex) + "[" + | ||||
|                    std::to_string(attr / config.state.attributes_per_vertex) + "]"; | ||||
|         } | ||||
|         return "vec4(0.0, 0.0, 0.0, 1.0)"; | ||||
|     }; | ||||
| 
 | ||||
|     auto get_output_reg = [&](u32 reg) -> std::string { | ||||
|         ASSERT(reg < 16); | ||||
|         if (config.state.output_map[reg] < config.state.num_outputs) { | ||||
|             return "output_buffer.attributes[" + std::to_string(config.state.output_map[reg]) + "]"; | ||||
|         } | ||||
|         return ""; | ||||
|     }; | ||||
| 
 | ||||
|     auto program_source_opt = Pica::Shader::Decompiler::DecompileProgram( | ||||
|         setup.program_code, setup.swizzle_data, config.state.main_offset, get_input_reg, | ||||
|         get_output_reg, config.state.sanitize_mul, true); | ||||
| 
 | ||||
|     if (!program_source_opt) | ||||
|         return boost::none; | ||||
| 
 | ||||
|     std::string& program_source = program_source_opt.get(); | ||||
| 
 | ||||
|     out += R"( | ||||
| Vertex output_buffer; | ||||
| Vertex prim_buffer[3]; | ||||
| uint vertex_id = 0u; | ||||
| bool prim_emit = false; | ||||
| bool winding = false; | ||||
| 
 | ||||
| void setemit(uint vertex_id_, bool prim_emit_, bool winding_) { | ||||
|     vertex_id = vertex_id_; | ||||
|     prim_emit = prim_emit_; | ||||
|     winding = winding_; | ||||
| } | ||||
| 
 | ||||
| void emit() { | ||||
|     prim_buffer[vertex_id] = output_buffer; | ||||
| 
 | ||||
|     if (prim_emit) { | ||||
|         if (winding) { | ||||
|             EmitPrim(prim_buffer[1], prim_buffer[0], prim_buffer[2]); | ||||
|             winding = false; | ||||
|         } else { | ||||
|             EmitPrim(prim_buffer[0], prim_buffer[1], prim_buffer[2]); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void main() { | ||||
| )"; | ||||
|     for (u32 i = 0; i < config.state.num_outputs; ++i) { | ||||
|         out += | ||||
|             "    output_buffer.attributes[" + std::to_string(i) + "] = vec4(0.0, 0.0, 0.0, 1.0);\n"; | ||||
|     } | ||||
| 
 | ||||
|     // execute shader
 | ||||
|     out += "\n    exec_shader();\n\n"; | ||||
| 
 | ||||
|     out += "}\n\n"; | ||||
| 
 | ||||
|     out += program_source; | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| } // namespace GLShader
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <functional> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <boost/optional.hpp> | ||||
| #include "common/hash.h" | ||||
| #include "video_core/regs.h" | ||||
| #include "video_core/shader/shader.h" | ||||
|  | @ -47,7 +48,7 @@ struct TevStageConfigRaw { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct PicaShaderConfigState { | ||||
| struct PicaFSConfigState { | ||||
|     Pica::FramebufferRegs::CompareFunc alpha_test_func; | ||||
|     Pica::RasterizerRegs::ScissorMode scissor_test_mode; | ||||
|     Pica::TexturingRegs::TextureConfig::TextureType texture0_type; | ||||
|  | @ -112,17 +113,17 @@ struct PicaShaderConfigState { | |||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * This struct contains all state used to generate the GLSL shader program that emulates the current | ||||
|  * Pica register configuration. This struct is used as a cache key for generated GLSL shader | ||||
|  * 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 PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> { | ||||
| struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> { | ||||
| 
 | ||||
|     /// Construct a PicaShaderConfig with the given Pica register configuration.
 | ||||
|     static PicaShaderConfig BuildFromRegs(const Pica::Regs& regs); | ||||
|     /// Construct a PicaFSConfig with the given Pica register configuration.
 | ||||
|     static PicaFSConfig BuildFromRegs(const Pica::Regs& regs); | ||||
| 
 | ||||
|     bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { | ||||
|         return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); | ||||
|  | @ -133,6 +134,79 @@ struct PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * This struct contains common information to identify a GL vertex/geometry shader generated from | ||||
|  * PICA vertex/geometry shader. | ||||
|  */ | ||||
| struct PicaShaderConfigCommon { | ||||
|     void Init(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup); | ||||
| 
 | ||||
|     u64 program_hash; | ||||
|     u64 swizzle_hash; | ||||
|     u32 main_offset; | ||||
|     bool sanitize_mul; | ||||
| 
 | ||||
|     u32 num_outputs; | ||||
| 
 | ||||
|     // output_map[output register index] -> output attribute index
 | ||||
|     std::array<u32, 16> output_map; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * This struct contains information to identify a GL vertex shader generated from PICA vertex | ||||
|  * shader. | ||||
|  */ | ||||
| struct PicaVSConfig : Common::HashableStruct<PicaShaderConfigCommon> { | ||||
|     explicit PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) { | ||||
|         state.Init(regs.vs, setup); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 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) { | ||||
|         state.Init(regs); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct PicaGSConfigRaw : PicaShaderConfigCommon, PicaGSConfigCommonRaw { | ||||
|     void Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup); | ||||
| 
 | ||||
|     u32 num_inputs; | ||||
|     u32 attributes_per_vertex; | ||||
| 
 | ||||
|     // input_map[input register index] -> input attribute index
 | ||||
|     std::array<u32, 16> input_map; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * This struct contains information to identify a GL geometry shader generated from PICA geometry | ||||
|  * shader. | ||||
|  */ | ||||
| struct PicaGSConfig : Common::HashableStruct<PicaGSConfigRaw> { | ||||
|     explicit PicaGSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setups) { | ||||
|         state.Init(regs, setups); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Generates the GLSL vertex shader program source code that accepts vertices from software shader | ||||
|  * and directly passes them to the fragment shader. | ||||
|  | @ -141,6 +215,29 @@ struct PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> { | |||
|  */ | ||||
| std::string GenerateTrivialVertexShader(bool separable_shader); | ||||
| 
 | ||||
| /**
 | ||||
|  * Generates the GLSL vertex shader program source code for the given VS program | ||||
|  * @returns String of the shader source code; boost::none on failure | ||||
|  */ | ||||
| boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, | ||||
|                                                   const PicaVSConfig& config, | ||||
|                                                   bool separable_shader); | ||||
| 
 | ||||
| /*
 | ||||
|  * 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, bool separable_shader); | ||||
| 
 | ||||
| /**
 | ||||
|  * Generates the GLSL geometry shader program source code for the given GS program and its | ||||
|  * configuration | ||||
|  * @returns String of the shader source code; boost::none on failure | ||||
|  */ | ||||
| boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup, | ||||
|                                                     const PicaGSConfig& config, | ||||
|                                                     bool separable_shader); | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 | ||||
|  | @ -148,14 +245,35 @@ std::string GenerateTrivialVertexShader(bool separable_shader); | |||
|  * @param separable_shader generates shader that can be used for separate shader object | ||||
|  * @returns String of the shader source code | ||||
|  */ | ||||
| std::string GenerateFragmentShader(const PicaShaderConfig& config, bool separable_shader); | ||||
| std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader); | ||||
| 
 | ||||
| } // namespace GLShader
 | ||||
| 
 | ||||
| namespace std { | ||||
| template <> | ||||
| struct hash<GLShader::PicaShaderConfig> { | ||||
|     size_t operator()(const GLShader::PicaShaderConfig& k) const { | ||||
| struct hash<GLShader::PicaFSConfig> { | ||||
|     size_t operator()(const GLShader::PicaFSConfig& k) const { | ||||
|         return k.Hash(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <> | ||||
| struct hash<GLShader::PicaVSConfig> { | ||||
|     size_t operator()(const GLShader::PicaVSConfig& k) const { | ||||
|         return k.Hash(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <> | ||||
| struct hash<GLShader::PicaFixedGSConfig> { | ||||
|     size_t operator()(const GLShader::PicaFixedGSConfig& k) const { | ||||
|         return k.Hash(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <> | ||||
| struct hash<GLShader::PicaGSConfig> { | ||||
|     size_t operator()(const GLShader::PicaGSConfig& k) const { | ||||
|         return k.Hash(); | ||||
|     } | ||||
| }; | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <unordered_map> | ||||
| #include <boost/functional/hash.hpp> | ||||
| #include <boost/variant.hpp> | ||||
|  | @ -23,6 +24,8 @@ static void SetShaderUniformBlockBinding(GLuint shader, const char* name, Unifor | |||
| static void SetShaderUniformBlockBindings(GLuint shader) { | ||||
|     SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common, | ||||
|                                  sizeof(UniformData)); | ||||
|     SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS, sizeof(VSUniformData)); | ||||
|     SetShaderUniformBlockBinding(shader, "gs_config", UniformBindings::GS, sizeof(GSUniformData)); | ||||
| } | ||||
| 
 | ||||
| static void SetShaderSamplerBinding(GLuint shader, const char* name, | ||||
|  | @ -57,6 +60,21 @@ static void SetShaderSamplerBindings(GLuint shader) { | |||
|     cur_state.Apply(); | ||||
| } | ||||
| 
 | ||||
| void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs, | ||||
|                                    const Pica::Shader::ShaderSetup& setup) { | ||||
|     std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools), | ||||
|                    [](bool value) -> BoolAligned { return {value ? GL_TRUE : GL_FALSE}; }); | ||||
|     std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i), | ||||
|                    [](const auto& value) -> GLuvec4 { | ||||
|                        return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()}; | ||||
|                    }); | ||||
|     std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f), | ||||
|                    [](const auto& value) -> GLvec4 { | ||||
|                        return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(), | ||||
|                                value.w.ToFloat32()}; | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * An object representing a shader program staging. It can be either a shader object or a program | ||||
|  * object, depending on whether separable program is used. | ||||
|  | @ -128,13 +146,70 @@ private: | |||
|     std::unordered_map<KeyConfigType, OGLShaderStage> shaders; | ||||
| }; | ||||
| 
 | ||||
| // This is a cache designed for shaders translated from PICA shaders. The first cache matches the
 | ||||
| // config structure like a normal cache does. On cache miss, the second cache matches the generated
 | ||||
| // GLSL code. The configuration is like this because there might be leftover code in the PICA shader
 | ||||
| // program buffer from the previous shader, which is hashed into the config, resulting several
 | ||||
| // different config values from the same shader program.
 | ||||
| template <typename KeyConfigType, | ||||
|           boost::optional<std::string> (*CodeGenerator)(const Pica::Shader::ShaderSetup&, | ||||
|                                                         const KeyConfigType&, bool), | ||||
|           GLenum ShaderType> | ||||
| class ShaderDoubleCache { | ||||
| public: | ||||
|     explicit ShaderDoubleCache(bool separable) : separable(separable) {} | ||||
|     GLuint Get(const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) { | ||||
|         auto map_it = shader_map.find(key); | ||||
|         if (map_it == shader_map.end()) { | ||||
|             auto program_opt = CodeGenerator(setup, key, separable); | ||||
|             if (!program_opt) { | ||||
|                 shader_map[key] = nullptr; | ||||
|                 return 0; | ||||
|             } | ||||
| 
 | ||||
|             std::string& program = program_opt.get(); | ||||
|             auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable}); | ||||
|             OGLShaderStage& cached_shader = iter->second; | ||||
|             if (new_shader) { | ||||
|                 cached_shader.Create(program.c_str(), ShaderType); | ||||
|             } | ||||
|             shader_map[key] = &cached_shader; | ||||
|             return cached_shader.GetHandle(); | ||||
|         } | ||||
| 
 | ||||
|         if (map_it->second == nullptr) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         return map_it->second->GetHandle(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     bool separable; | ||||
|     std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map; | ||||
|     std::unordered_map<std::string, OGLShaderStage> shader_cache; | ||||
| }; | ||||
| 
 | ||||
| using ProgrammableVertexShaders = | ||||
|     ShaderDoubleCache<GLShader::PicaVSConfig, &GLShader::GenerateVertexShader, GL_VERTEX_SHADER>; | ||||
| 
 | ||||
| using ProgrammableGeometryShaders = | ||||
|     ShaderDoubleCache<GLShader::PicaGSConfig, &GLShader::GenerateGeometryShader, | ||||
|                       GL_GEOMETRY_SHADER>; | ||||
| 
 | ||||
| using FixedGeometryShaders = | ||||
|     ShaderCache<GLShader::PicaFixedGSConfig, &GLShader::GenerateFixedGeometryShader, | ||||
|                 GL_GEOMETRY_SHADER>; | ||||
| 
 | ||||
| using FragmentShaders = | ||||
|     ShaderCache<GLShader::PicaShaderConfig, &GLShader::GenerateFragmentShader, GL_FRAGMENT_SHADER>; | ||||
|     ShaderCache<GLShader::PicaFSConfig, &GLShader::GenerateFragmentShader, GL_FRAGMENT_SHADER>; | ||||
| 
 | ||||
| class ShaderProgramManager::Impl { | ||||
| public: | ||||
|     explicit Impl(bool separable) | ||||
|         : separable(separable), trivial_vertex_shader(separable), fragment_shaders(separable) { | ||||
|         : separable(separable), programmable_vertex_shaders(separable), | ||||
|           trivial_vertex_shader(separable), programmable_geometry_shaders(separable), | ||||
|           fixed_geometry_shaders(separable), fragment_shaders(separable) { | ||||
|         if (separable) | ||||
|             pipeline.Create(); | ||||
|     } | ||||
|  | @ -165,8 +240,12 @@ public: | |||
| 
 | ||||
|     ShaderTuple current; | ||||
| 
 | ||||
|     ProgrammableVertexShaders programmable_vertex_shaders; | ||||
|     TrivialVertexShader trivial_vertex_shader; | ||||
| 
 | ||||
|     ProgrammableGeometryShaders programmable_geometry_shaders; | ||||
|     FixedGeometryShaders fixed_geometry_shaders; | ||||
| 
 | ||||
|     FragmentShaders fragment_shaders; | ||||
| 
 | ||||
|     bool separable; | ||||
|  | @ -179,15 +258,37 @@ ShaderProgramManager::ShaderProgramManager(bool separable) | |||
| 
 | ||||
| ShaderProgramManager::~ShaderProgramManager() = default; | ||||
| 
 | ||||
| bool ShaderProgramManager::UseProgrammableVertexShader(const GLShader::PicaVSConfig& config, | ||||
|                                                        const Pica::Shader::ShaderSetup setup) { | ||||
|     GLuint handle = impl->programmable_vertex_shaders.Get(config, setup); | ||||
|     if (handle == 0) | ||||
|         return false; | ||||
|     impl->current.vs = handle; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void ShaderProgramManager::UseTrivialVertexShader() { | ||||
|     impl->current.vs = impl->trivial_vertex_shader.Get(); | ||||
| } | ||||
| 
 | ||||
| bool ShaderProgramManager::UseProgrammableGeometryShader(const GLShader::PicaGSConfig& config, | ||||
|                                                          const Pica::Shader::ShaderSetup setup) { | ||||
|     GLuint handle = impl->programmable_geometry_shaders.Get(config, setup); | ||||
|     if (handle == 0) | ||||
|         return false; | ||||
|     impl->current.gs = handle; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void ShaderProgramManager::UseFixedGeometryShader(const GLShader::PicaFixedGSConfig& config) { | ||||
|     impl->current.gs = impl->fixed_geometry_shaders.Get(config); | ||||
| } | ||||
| 
 | ||||
| void ShaderProgramManager::UseTrivialGeometryShader() { | ||||
|     impl->current.gs = 0; | ||||
| } | ||||
| 
 | ||||
| void ShaderProgramManager::UseFragmentShader(const GLShader::PicaShaderConfig& config) { | ||||
| void ShaderProgramManager::UseFragmentShader(const GLShader::PicaFSConfig& config) { | ||||
|     impl->current.fs = impl->fragment_shaders.Get(config); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||
| #include "video_core/renderer_opengl/pica_to_gl.h" | ||||
| 
 | ||||
| enum class UniformBindings : GLuint { Common }; | ||||
| enum class UniformBindings : GLuint { Common, VS, GS }; | ||||
| 
 | ||||
| struct LightSrc { | ||||
|     alignas(16) GLvec3 specular_0; | ||||
|  | @ -53,17 +53,57 @@ static_assert( | |||
| static_assert(sizeof(UniformData) < 16384, | ||||
|               "UniformData structure must be less than 16kb as per the OpenGL spec"); | ||||
| 
 | ||||
| /// Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms.
 | ||||
| // NOTE: the same rule from UniformData also applies here.
 | ||||
| struct PicaUniformsData { | ||||
|     void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup); | ||||
| 
 | ||||
|     struct BoolAligned { | ||||
|         alignas(16) GLint b; | ||||
|     }; | ||||
| 
 | ||||
|     std::array<BoolAligned, 16> bools; | ||||
|     alignas(16) std::array<GLuvec4, 4> i; | ||||
|     alignas(16) std::array<GLvec4, 96> f; | ||||
| }; | ||||
| 
 | ||||
| struct VSUniformData { | ||||
|     PicaUniformsData uniforms; | ||||
| }; | ||||
| static_assert( | ||||
|     sizeof(VSUniformData) == 1856, | ||||
|     "The size of the VSUniformData structure has changed, update the structure in the shader"); | ||||
| static_assert(sizeof(VSUniformData) < 16384, | ||||
|               "VSUniformData structure must be less than 16kb as per the OpenGL spec"); | ||||
| 
 | ||||
| struct GSUniformData { | ||||
|     PicaUniformsData uniforms; | ||||
| }; | ||||
| static_assert( | ||||
|     sizeof(GSUniformData) == 1856, | ||||
|     "The size of the GSUniformData structure has changed, update the structure in the shader"); | ||||
| static_assert(sizeof(GSUniformData) < 16384, | ||||
|               "GSUniformData structure must be less than 16kb as per the OpenGL spec"); | ||||
| 
 | ||||
| /// A class that manage different shader stages and configures them with given config data.
 | ||||
| class ShaderProgramManager { | ||||
| public: | ||||
|     explicit ShaderProgramManager(bool separable); | ||||
|     ~ShaderProgramManager(); | ||||
| 
 | ||||
|     bool UseProgrammableVertexShader(const GLShader::PicaVSConfig& config, | ||||
|                                      const Pica::Shader::ShaderSetup setup); | ||||
| 
 | ||||
|     void UseTrivialVertexShader(); | ||||
| 
 | ||||
|     bool UseProgrammableGeometryShader(const GLShader::PicaGSConfig& config, | ||||
|                                        const Pica::Shader::ShaderSetup setup); | ||||
| 
 | ||||
|     void UseFixedGeometryShader(const GLShader::PicaFixedGSConfig& config); | ||||
| 
 | ||||
|     void UseTrivialGeometryShader(); | ||||
| 
 | ||||
|     void UseFragmentShader(const GLShader::PicaShaderConfig& config); | ||||
|     void UseFragmentShader(const GLShader::PicaFSConfig& config); | ||||
| 
 | ||||
|     void ApplyTo(OpenGLState& state); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue