From 8186820d1674753abaa8ea04d569e905f0a6c0fd Mon Sep 17 00:00:00 2001
From: MerryMage <MerryMage@users.noreply.github.com>
Date: Wed, 27 Dec 2017 13:10:15 +0000
Subject: [PATCH 1/2] pica_to_gl: Add GLuvec{2,3,4} aliases

To allow for transfer for integers into shaders.
---
 src/video_core/renderer_opengl/pica_to_gl.h | 4 ++++
 1 file changed, 4 insertions(+)

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<GLfloat, 2>;
 using GLvec3 = std::array<GLfloat, 3>;
 using GLvec4 = std::array<GLfloat, 4>;
 
+using GLuvec2 = std::array<GLuint, 2>;
+using GLuvec3 = std::array<GLuint, 3>;
+using GLuvec4 = std::array<GLuint, 4>;
+
 namespace PicaToGL {
 
 inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) {

From 191b29e402a5937f36ea7fcaa2b92f2b74d805d8 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Mon, 23 Apr 2018 22:20:30 +0300
Subject: [PATCH 2/2] gl_shader_gen: generate programmable vs/gs and fixed gs

---
 .../renderer_opengl/gl_rasterizer.cpp         |   2 +-
 .../renderer_opengl/gl_shader_gen.cpp         | 368 +++++++++++++++++-
 .../renderer_opengl/gl_shader_gen.h           | 136 ++++++-
 .../renderer_opengl/gl_shader_manager.cpp     | 107 ++++-
 .../renderer_opengl/gl_shader_manager.h       |  44 ++-
 5 files changed, 632 insertions(+), 25 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8b3a4aa84..23348b74d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -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);
 }
 
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 <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
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 <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();
     }
 };
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 <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);
 }
 
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<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);