diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8dd120977..7c44a77cb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1266,7 +1266,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); } diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 79a034d6a..16f4556c8 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -7,6 +7,7 @@ #include #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(regs.output_mask)) { + output_map[reg] = num_outputs++; + } +} + +void PicaGSConfigCommonRaw::Init(const Pica::Regs& regs) { + vs_output_attributes = Common::BitSet(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 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(semantic) < 24) { + semantic_maps[static_cast(semantic)] = {attrib, comp}; + } else if (semantic != VSOutputAttributes::INVALID) { + NGLOG_ERROR(Render_OpenGL, "Invalid/unknown semantic id: {}", + static_cast(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(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 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 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(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 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 diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index f900e3091..ada4060e2 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -9,6 +9,7 @@ #include #include #include +#include #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 { +struct PicaFSConfig : Common::HashableStruct { - /// 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 { } }; +/** + * 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 output_map; +}; + +/** + * This struct contains information to identify a GL vertex shader generated from PICA vertex + * shader. + */ +struct PicaVSConfig : Common::HashableStruct { + 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 semantic_maps; +}; + +/** + * This struct contains information to identify a GL geometry shader generated from PICA no-geometry + * shader pipeline + */ +struct PicaFixedGSConfig : Common::HashableStruct { + 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 input_map; +}; + +/** + * This struct contains information to identify a GL geometry shader generated from PICA geometry + * shader. + */ +struct PicaGSConfig : Common::HashableStruct { + 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 { */ 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 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 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 { - size_t operator()(const GLShader::PicaShaderConfig& k) const { +struct hash { + size_t operator()(const GLShader::PicaFSConfig& k) const { + return k.Hash(); + } +}; + +template <> +struct hash { + size_t operator()(const GLShader::PicaVSConfig& k) const { + return k.Hash(); + } +}; + +template <> +struct hash { + size_t operator()(const GLShader::PicaFixedGSConfig& k) const { + return k.Hash(); + } +}; + +template <> +struct hash { + size_t operator()(const GLShader::PicaGSConfig& k) const { return k.Hash(); } }; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 30bc2d49d..71acdc5ff 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -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 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 (*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 shader_map; + std::unordered_map shader_cache; +}; + +using ProgrammableVertexShaders = + ShaderDoubleCache; + +using ProgrammableGeometryShaders = + ShaderDoubleCache; + +using FixedGeometryShaders = + ShaderCache; + using FragmentShaders = - ShaderCache; + ShaderCache; 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); } diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 1e59b74aa..364b8090c 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -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 bools; + alignas(16) std::array i; + alignas(16) std::array 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); diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 6005c6148..45d4bc4bb 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -19,6 +19,10 @@ using GLvec2 = std::array; using GLvec3 = std::array; using GLvec4 = std::array; +using GLuvec2 = std::array; +using GLuvec3 = std::array; +using GLuvec4 = std::array; + namespace PicaToGL { inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) {