From e3f4233cefff611e03a2031c6194a118d946a5d9 Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Sat, 25 Jul 2015 20:13:11 -0500
Subject: [PATCH 01/19] Initial implementation of fragment shader generation
 with caching.

---
 src/video_core/pica.h                         |   4 +
 .../renderer_opengl/gl_rasterizer.cpp         | 351 +++++++-----------
 .../renderer_opengl/gl_rasterizer.h           | 110 ++++--
 .../renderer_opengl/gl_shader_util.cpp        | 349 +++++++++++++++++
 .../renderer_opengl/gl_shader_util.h          |   6 +
 src/video_core/renderer_opengl/gl_shaders.h   |   8 +-
 src/video_core/renderer_opengl/gl_state.h     |   1 +
 7 files changed, 568 insertions(+), 261 deletions(-)

diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index ff81b409d..18fdc8c85 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -317,6 +317,7 @@ struct Regs {
         };
 
         union {
+            u32 source_raw;
             BitField< 0, 4, Source> color_source1;
             BitField< 4, 4, Source> color_source2;
             BitField< 8, 4, Source> color_source3;
@@ -326,6 +327,7 @@ struct Regs {
         };
 
         union {
+            u32 modifier_raw;
             BitField< 0, 4, ColorModifier> color_modifier1;
             BitField< 4, 4, ColorModifier> color_modifier2;
             BitField< 8, 4, ColorModifier> color_modifier3;
@@ -335,6 +337,7 @@ struct Regs {
         };
 
         union {
+            u32 op_raw;
             BitField< 0, 4, Operation> color_op;
             BitField<16, 4, Operation> alpha_op;
         };
@@ -348,6 +351,7 @@ struct Regs {
         };
 
         union {
+            u32 scale_raw;
             BitField< 0, 2, u32> color_scale;
             BitField<16, 2, u32> alpha_scale;
         };
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a613fe136..45329d561 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -8,6 +8,7 @@
 #include <glad/glad.h>
 
 #include "common/color.h"
+#include "common/file_util.h"
 #include "common/math_util.h"
 #include "common/microprofile.h"
 #include "common/profiler.h"
@@ -38,36 +39,6 @@ RasterizerOpenGL::RasterizerOpenGL() : last_fb_color_addr(0), last_fb_depth_addr
 RasterizerOpenGL::~RasterizerOpenGL() { }
 
 void RasterizerOpenGL::InitObjects() {
-    // Create the hardware shader program and get attrib/uniform locations
-    shader.Create(GLShaders::g_vertex_shader_hw, GLShaders::g_fragment_shader_hw);
-    attrib_position = glGetAttribLocation(shader.handle, "vert_position");
-    attrib_color = glGetAttribLocation(shader.handle, "vert_color");
-    attrib_texcoords = glGetAttribLocation(shader.handle, "vert_texcoords");
-
-    uniform_alphatest_enabled = glGetUniformLocation(shader.handle, "alphatest_enabled");
-    uniform_alphatest_func = glGetUniformLocation(shader.handle, "alphatest_func");
-    uniform_alphatest_ref = glGetUniformLocation(shader.handle, "alphatest_ref");
-
-    uniform_tex = glGetUniformLocation(shader.handle, "tex");
-
-    uniform_tev_combiner_buffer_color = glGetUniformLocation(shader.handle, "tev_combiner_buffer_color");
-
-    const auto tev_stages = Pica::g_state.regs.GetTevStages();
-    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
-        auto& uniform_tev_cfg = uniform_tev_cfgs[tev_stage_index];
-
-        std::string tev_ref_str = "tev_cfgs[" + std::to_string(tev_stage_index) + "]";
-        uniform_tev_cfg.enabled = glGetUniformLocation(shader.handle, (tev_ref_str + ".enabled").c_str());
-        uniform_tev_cfg.color_sources = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_sources").c_str());
-        uniform_tev_cfg.alpha_sources = glGetUniformLocation(shader.handle, (tev_ref_str + ".alpha_sources").c_str());
-        uniform_tev_cfg.color_modifiers = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_modifiers").c_str());
-        uniform_tev_cfg.alpha_modifiers = glGetUniformLocation(shader.handle, (tev_ref_str + ".alpha_modifiers").c_str());
-        uniform_tev_cfg.color_alpha_op = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_alpha_op").c_str());
-        uniform_tev_cfg.color_alpha_multiplier = glGetUniformLocation(shader.handle, (tev_ref_str + ".color_alpha_multiplier").c_str());
-        uniform_tev_cfg.const_color = glGetUniformLocation(shader.handle, (tev_ref_str + ".const_color").c_str());
-        uniform_tev_cfg.updates_combiner_buffer_color_alpha = glGetUniformLocation(shader.handle, (tev_ref_str + ".updates_combiner_buffer_color_alpha").c_str());
-    }
-
     // Create sampler objects
     for (size_t i = 0; i < texture_samplers.size(); ++i) {
         texture_samplers[i].Create();
@@ -78,29 +49,25 @@ void RasterizerOpenGL::InitObjects() {
     vertex_buffer.Create();
     vertex_array.Create();
 
-    // Update OpenGL state
     state.draw.vertex_array = vertex_array.handle;
     state.draw.vertex_buffer = vertex_buffer.handle;
-    state.draw.shader_program = shader.handle;
-
     state.Apply();
 
-    // Set the texture samplers to correspond to different texture units
-    glUniform1i(uniform_tex, 0);
-    glUniform1i(uniform_tex + 1, 1);
-    glUniform1i(uniform_tex + 2, 2);
-
     // Set vertex attributes
-    glVertexAttribPointer(attrib_position, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position));
-    glVertexAttribPointer(attrib_color, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color));
-    glVertexAttribPointer(attrib_texcoords, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
-    glVertexAttribPointer(attrib_texcoords + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
-    glVertexAttribPointer(attrib_texcoords + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
-    glEnableVertexAttribArray(attrib_position);
-    glEnableVertexAttribArray(attrib_color);
-    glEnableVertexAttribArray(attrib_texcoords);
-    glEnableVertexAttribArray(attrib_texcoords + 1);
-    glEnableVertexAttribArray(attrib_texcoords + 2);
+    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position));
+    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_POSITION);
+
+    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color));
+    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_COLOR);
+
+    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
+    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
+    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
+    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS);
+    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS + 1);
+    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS + 2);
+
+    RegenerateShaders();
 
     // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation
     fb_color_texture.texture.Create();
@@ -156,55 +123,11 @@ void RasterizerOpenGL::Reset() {
     SyncBlendEnabled();
     SyncBlendFuncs();
     SyncBlendColor();
-    SyncAlphaTest();
     SyncLogicOp();
     SyncStencilTest();
     SyncDepthTest();
 
-    // TEV stage 0
-    SyncTevSources(0, regs.tev_stage0);
-    SyncTevModifiers(0, regs.tev_stage0);
-    SyncTevOps(0, regs.tev_stage0);
-    SyncTevColor(0, regs.tev_stage0);
-    SyncTevMultipliers(0, regs.tev_stage0);
-
-    // TEV stage 1
-    SyncTevSources(1, regs.tev_stage1);
-    SyncTevModifiers(1, regs.tev_stage1);
-    SyncTevOps(1, regs.tev_stage1);
-    SyncTevColor(1, regs.tev_stage1);
-    SyncTevMultipliers(1, regs.tev_stage1);
-
-    // TEV stage 2
-    SyncTevSources(2, regs.tev_stage2);
-    SyncTevModifiers(2, regs.tev_stage2);
-    SyncTevOps(2, regs.tev_stage2);
-    SyncTevColor(2, regs.tev_stage2);
-    SyncTevMultipliers(2, regs.tev_stage2);
-
-    // TEV stage 3
-    SyncTevSources(3, regs.tev_stage3);
-    SyncTevModifiers(3, regs.tev_stage3);
-    SyncTevOps(3, regs.tev_stage3);
-    SyncTevColor(3, regs.tev_stage3);
-    SyncTevMultipliers(3, regs.tev_stage3);
-
-    // TEV stage 4
-    SyncTevSources(4, regs.tev_stage4);
-    SyncTevModifiers(4, regs.tev_stage4);
-    SyncTevOps(4, regs.tev_stage4);
-    SyncTevColor(4, regs.tev_stage4);
-    SyncTevMultipliers(4, regs.tev_stage4);
-
-    // TEV stage 5
-    SyncTevSources(5, regs.tev_stage5);
-    SyncTevModifiers(5, regs.tev_stage5);
-    SyncTevOps(5, regs.tev_stage5);
-    SyncTevColor(5, regs.tev_stage5);
-    SyncTevMultipliers(5, regs.tev_stage5);
-
-    SyncCombinerColor();
-    SyncCombinerWriteFlags();
+    RegenerateShaders();
 
     res_cache.FullFlush();
 }
@@ -217,10 +140,88 @@ void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0,
     vertex_batch.emplace_back(v2);
 }
 
+namespace ShaderCache {
+extern std::string GenerateFragmentShader(const ShaderCacheKey& config);
+}
+
+void RasterizerOpenGL::RegenerateShaders() {
+    const auto& regs = Pica::g_state.regs;
+
+    ShaderCacheKey config;
+    config.alpha_test_func = regs.output_merger.alpha_test.enable ?
+            regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
+    config.tev_stages = regs.GetTevStages();
+    for (auto& tev : config.tev_stages) {
+        tev.const_r = 0;
+        tev.const_g = 0;
+        tev.const_b = 0;
+        tev.const_a = 0;
+    }
+    config.combiner_buffer_input =
+            regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
+            regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
+
+    auto cached_shader = shader_cache.find(config);
+    if (cached_shader != shader_cache.end()) {
+        current_shader = &cached_shader->second;
+        state.draw.shader_program = current_shader->shader.handle;
+        state.Apply();
+    } else {
+        LOG_CRITICAL(Render_OpenGL, "Creating new shader: %08X", hash(config));
+
+        TEVShader shader;
+
+        std::string fragShader = ShaderCache::GenerateFragmentShader(config);
+        shader.shader.Create(GLShaders::g_vertex_shader_hw, fragShader.c_str());
+
+        shader.uniform_alphatest_ref = glGetUniformLocation(shader.shader.handle, "alphatest_ref");
+        shader.uniform_tex = glGetUniformLocation(shader.shader.handle, "tex");
+        shader.uniform_tev_combiner_buffer_color = glGetUniformLocation(shader.shader.handle, "tev_combiner_buffer_color");
+        shader.uniform_tev_const_colors = glGetUniformLocation(shader.shader.handle, "const_color");
+
+        current_shader = &shader_cache.emplace(config, std::move(shader)).first->second;
+
+        state.draw.shader_program = current_shader->shader.handle;
+        state.Apply();
+
+        // Set the texture samplers to correspond to different texture units
+        if (shader.uniform_tex != -1) {
+            glUniform1i(shader.uniform_tex, 0);
+            glUniform1i(shader.uniform_tex + 1, 1);
+            glUniform1i(shader.uniform_tex + 2, 2);
+        }
+    }
+
+
+    // Sync alpha reference
+    if (current_shader->uniform_alphatest_ref != -1)
+        glUniform1f(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
+
+    // Sync combiner buffer color
+    if (current_shader->uniform_tev_combiner_buffer_color != -1) {
+        auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
+        glUniform4fv(current_shader->uniform_tev_combiner_buffer_color, 1, combiner_color.data());
+    }
+
+    // Sync TEV const colors
+    if (current_shader->uniform_tev_const_colors != -1) {
+        auto& tev_stages = Pica::g_state.regs.GetTevStages();
+        for (int tev_index = 0; tev_index < tev_stages.size(); ++tev_index) {
+            auto const_color = PicaToGL::ColorRGBA8(tev_stages[tev_index].const_color);
+            glUniform4fv(current_shader->uniform_tev_const_colors + tev_index, 1, const_color.data());
+        }
+    }
+}
+
 void RasterizerOpenGL::DrawTriangles() {
     SyncFramebuffer();
     SyncDrawState();
 
+    if (state.draw.shader_dirty) {
+        RegenerateShaders();
+        state.draw.shader_dirty = false;
+    }
+
     glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW);
     glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size());
 
@@ -272,6 +273,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
     // Alpha test
     case PICA_REG_INDEX(output_merger.alpha_test):
         SyncAlphaTest();
+        state.draw.shader_dirty = true;
         break;
 
     // Stencil test
@@ -290,117 +292,57 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
         SyncLogicOp();
         break;
 
-    // TEV stage 0
+    // TEV stages
     case PICA_REG_INDEX(tev_stage0.color_source1):
-        SyncTevSources(0, regs.tev_stage0);
-        break;
     case PICA_REG_INDEX(tev_stage0.color_modifier1):
-        SyncTevModifiers(0, regs.tev_stage0);
-        break;
     case PICA_REG_INDEX(tev_stage0.color_op):
-        SyncTevOps(0, regs.tev_stage0);
-        break;
-    case PICA_REG_INDEX(tev_stage0.const_r):
-        SyncTevColor(0, regs.tev_stage0);
-        break;
     case PICA_REG_INDEX(tev_stage0.color_scale):
-        SyncTevMultipliers(0, regs.tev_stage0);
-        break;
-
-    // TEV stage 1
     case PICA_REG_INDEX(tev_stage1.color_source1):
-        SyncTevSources(1, regs.tev_stage1);
-        break;
     case PICA_REG_INDEX(tev_stage1.color_modifier1):
-        SyncTevModifiers(1, regs.tev_stage1);
-        break;
     case PICA_REG_INDEX(tev_stage1.color_op):
-        SyncTevOps(1, regs.tev_stage1);
-        break;
-    case PICA_REG_INDEX(tev_stage1.const_r):
-        SyncTevColor(1, regs.tev_stage1);
-        break;
     case PICA_REG_INDEX(tev_stage1.color_scale):
-        SyncTevMultipliers(1, regs.tev_stage1);
-        break;
-
-    // TEV stage 2
     case PICA_REG_INDEX(tev_stage2.color_source1):
-        SyncTevSources(2, regs.tev_stage2);
-        break;
     case PICA_REG_INDEX(tev_stage2.color_modifier1):
-        SyncTevModifiers(2, regs.tev_stage2);
-        break;
     case PICA_REG_INDEX(tev_stage2.color_op):
-        SyncTevOps(2, regs.tev_stage2);
-        break;
-    case PICA_REG_INDEX(tev_stage2.const_r):
-        SyncTevColor(2, regs.tev_stage2);
-        break;
     case PICA_REG_INDEX(tev_stage2.color_scale):
-        SyncTevMultipliers(2, regs.tev_stage2);
-        break;
-
-    // TEV stage 3
     case PICA_REG_INDEX(tev_stage3.color_source1):
-        SyncTevSources(3, regs.tev_stage3);
-        break;
     case PICA_REG_INDEX(tev_stage3.color_modifier1):
-        SyncTevModifiers(3, regs.tev_stage3);
-        break;
     case PICA_REG_INDEX(tev_stage3.color_op):
-        SyncTevOps(3, regs.tev_stage3);
-        break;
-    case PICA_REG_INDEX(tev_stage3.const_r):
-        SyncTevColor(3, regs.tev_stage3);
-        break;
     case PICA_REG_INDEX(tev_stage3.color_scale):
-        SyncTevMultipliers(3, regs.tev_stage3);
-        break;
-
-    // TEV stage 4
     case PICA_REG_INDEX(tev_stage4.color_source1):
-        SyncTevSources(4, regs.tev_stage4);
-        break;
     case PICA_REG_INDEX(tev_stage4.color_modifier1):
-        SyncTevModifiers(4, regs.tev_stage4);
-        break;
     case PICA_REG_INDEX(tev_stage4.color_op):
-        SyncTevOps(4, regs.tev_stage4);
+    case PICA_REG_INDEX(tev_stage4.color_scale):
+    case PICA_REG_INDEX(tev_stage5.color_source1):
+    case PICA_REG_INDEX(tev_stage5.color_modifier1):
+    case PICA_REG_INDEX(tev_stage5.color_op):
+    case PICA_REG_INDEX(tev_stage5.color_scale):
+    case PICA_REG_INDEX(tev_combiner_buffer_input):
+        state.draw.shader_dirty = true;
         break;
-    case PICA_REG_INDEX(tev_stage4.const_r):
-        SyncTevColor(4, regs.tev_stage4);
+    case PICA_REG_INDEX(tev_stage0.const_r):
+        SyncTevConstColor(0, regs.tev_stage0);
         break;
-    case PICA_REG_INDEX(tev_stage4.color_scale):
-        SyncTevMultipliers(4, regs.tev_stage4);
+    case PICA_REG_INDEX(tev_stage1.const_r):
+        SyncTevConstColor(1, regs.tev_stage0);
         break;
-
-    // TEV stage 5
-    case PICA_REG_INDEX(tev_stage5.color_source1):
-        SyncTevSources(5, regs.tev_stage5);
+    case PICA_REG_INDEX(tev_stage2.const_r):
+        SyncTevConstColor(2, regs.tev_stage0);
         break;
-    case PICA_REG_INDEX(tev_stage5.color_modifier1):
-        SyncTevModifiers(5, regs.tev_stage5);
+    case PICA_REG_INDEX(tev_stage3.const_r):
+        SyncTevConstColor(3, regs.tev_stage0);
         break;
-    case PICA_REG_INDEX(tev_stage5.color_op):
-        SyncTevOps(5, regs.tev_stage5);
+    case PICA_REG_INDEX(tev_stage4.const_r):
+        SyncTevConstColor(4, regs.tev_stage0);
         break;
     case PICA_REG_INDEX(tev_stage5.const_r):
-        SyncTevColor(5, regs.tev_stage5);
-        break;
-    case PICA_REG_INDEX(tev_stage5.color_scale):
-        SyncTevMultipliers(5, regs.tev_stage5);
+        SyncTevConstColor(5, regs.tev_stage0);
         break;
 
     // TEV combiner buffer color
     case PICA_REG_INDEX(tev_combiner_buffer_color):
         SyncCombinerColor();
         break;
-
-    // TEV combiner buffer write flags
-    case PICA_REG_INDEX(tev_combiner_buffer_input):
-        SyncCombinerWriteFlags();
-        break;
     }
 }
 
@@ -712,9 +654,8 @@ void RasterizerOpenGL::SyncBlendColor() {
 
 void RasterizerOpenGL::SyncAlphaTest() {
     const auto& regs = Pica::g_state.regs;
-    glUniform1i(uniform_alphatest_enabled, regs.output_merger.alpha_test.enable);
-    glUniform1i(uniform_alphatest_func, (GLint)regs.output_merger.alpha_test.func.Value());
-    glUniform1f(uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
+    if (current_shader->uniform_alphatest_ref != -1)
+        glUniform1f(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
 }
 
 void RasterizerOpenGL::SyncLogicOp() {
@@ -744,55 +685,17 @@ void RasterizerOpenGL::SyncDepthTest() {
     state.depth.write_mask = regs.output_merger.depth_write_enable ? GL_TRUE : GL_FALSE;
 }
 
-void RasterizerOpenGL::SyncTevSources(unsigned stage_index, const Pica::Regs::TevStageConfig& config) {
-    GLint color_srcs[3] = { (GLint)config.color_source1.Value(),
-                            (GLint)config.color_source2.Value(),
-                            (GLint)config.color_source3.Value() };
-    GLint alpha_srcs[3] = { (GLint)config.alpha_source1.Value(),
-                            (GLint)config.alpha_source2.Value(),
-                            (GLint)config.alpha_source3.Value() };
-
-    glUniform3iv(uniform_tev_cfgs[stage_index].color_sources, 1, color_srcs);
-    glUniform3iv(uniform_tev_cfgs[stage_index].alpha_sources, 1, alpha_srcs);
-}
-
-void RasterizerOpenGL::SyncTevModifiers(unsigned stage_index, const Pica::Regs::TevStageConfig& config) {
-    GLint color_mods[3] = { (GLint)config.color_modifier1.Value(),
-                            (GLint)config.color_modifier2.Value(),
-                            (GLint)config.color_modifier3.Value() };
-    GLint alpha_mods[3] = { (GLint)config.alpha_modifier1.Value(),
-                            (GLint)config.alpha_modifier2.Value(),
-                            (GLint)config.alpha_modifier3.Value() };
-
-    glUniform3iv(uniform_tev_cfgs[stage_index].color_modifiers, 1, color_mods);
-    glUniform3iv(uniform_tev_cfgs[stage_index].alpha_modifiers, 1, alpha_mods);
-}
-
-void RasterizerOpenGL::SyncTevOps(unsigned stage_index, const Pica::Regs::TevStageConfig& config) {
-    glUniform2i(uniform_tev_cfgs[stage_index].color_alpha_op, (GLint)config.color_op.Value(), (GLint)config.alpha_op.Value());
-}
-
-void RasterizerOpenGL::SyncTevColor(unsigned stage_index, const Pica::Regs::TevStageConfig& config) {
-    auto const_color = PicaToGL::ColorRGBA8(config.const_color);
-    glUniform4fv(uniform_tev_cfgs[stage_index].const_color, 1, const_color.data());
-}
-
-void RasterizerOpenGL::SyncTevMultipliers(unsigned stage_index, const Pica::Regs::TevStageConfig& config) {
-    glUniform2i(uniform_tev_cfgs[stage_index].color_alpha_multiplier, config.GetColorMultiplier(), config.GetAlphaMultiplier());
-}
-
 void RasterizerOpenGL::SyncCombinerColor() {
-    auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
-    glUniform4fv(uniform_tev_combiner_buffer_color, 1, combiner_color.data());
+    if (current_shader->uniform_tev_combiner_buffer_color != -1) {
+        auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
+        glUniform4fv(current_shader->uniform_tev_combiner_buffer_color, 1, combiner_color.data());
+    }
 }
 
-void RasterizerOpenGL::SyncCombinerWriteFlags() {
-    const auto& regs = Pica::g_state.regs;
-    const auto tev_stages = regs.GetTevStages();
-    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
-        glUniform2i(uniform_tev_cfgs[tev_stage_index].updates_combiner_buffer_color_alpha,
-                    regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor(tev_stage_index),
-                    regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha(tev_stage_index));
+void RasterizerOpenGL::SyncTevConstColor(int stage_index, const Pica::Regs::TevStageConfig& tev_stage) {
+    if (current_shader->uniform_tev_const_colors != -1) {
+        auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color);
+        glUniform4fv(current_shader->uniform_tev_const_colors + stage_index, 1, const_color.data());
     }
 }
 
@@ -824,12 +727,6 @@ void RasterizerOpenGL::SyncDrawState() {
         }
     }
 
-    // Skip processing TEV stages that simply pass the previous stage results through
-    const auto tev_stages = regs.GetTevStages();
-    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
-        glUniform1i(uniform_tev_cfgs[tev_stage_index].enabled, !IsPassThroughTevStage(tev_stages[tev_stage_index]));
-    }
-
     state.Apply();
 }
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 1fe307846..19e8db69a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <vector>
+#include <unordered_map>
 
 #include "common/common_types.h"
 
@@ -13,6 +14,60 @@
 #include "video_core/renderer_opengl/gl_state.h"
 #include "video_core/shader/shader_interpreter.h"
 
+template <typename T>
+inline size_t hash(const T& o) {
+    return std::hash<T>()(o);
+}
+
+template <typename T>
+inline size_t combine_hash(const T& o) {
+    return hash(o);
+}
+
+template <typename T, typename... Args>
+inline size_t combine_hash(const T& o, const Args&... args) {
+    return hash(o) * 3 + combine_hash(args...);
+}
+
+struct ShaderCacheKey {
+    using Regs = Pica::Regs;
+
+    bool operator ==(const ShaderCacheKey& o) const {
+        return hash(*this) == hash(o);
+    };
+
+    Regs::CompareFunc alpha_test_func;
+    std::array<Regs::TevStageConfig, 6> tev_stages;
+    u8 combiner_buffer_input;
+
+    bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
+        return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index));
+    }
+
+    bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
+        return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
+    }
+};
+
+namespace std {
+
+template<> struct hash<::Pica::Regs::TevStageConfig> {
+    size_t operator()(const ::Pica::Regs::TevStageConfig& o) {
+        return ::combine_hash(
+            ::hash(o.source_raw), ::hash(o.modifier_raw),
+            ::hash(o.op_raw), ::hash(o.scale_raw));
+    }
+};
+
+template<> struct hash<::ShaderCacheKey> {
+    size_t operator()(const ::ShaderCacheKey& o) const {
+        return ::combine_hash(o.alpha_test_func, o.combiner_buffer_input,
+            o.tev_stages[0], o.tev_stages[1], o.tev_stages[2],
+            o.tev_stages[3], o.tev_stages[4], o.tev_stages[5]);
+    }
+};
+}
+
 class RasterizerOpenGL : public HWRasterizer {
 public:
 
@@ -33,6 +88,8 @@ public:
     /// Draw the current batch of triangles
     void DrawTriangles() override;
 
+    void RegenerateShaders();
+
     /// Commit the rasterizer's framebuffer contents immediately to the current 3DS memory framebuffer
     void CommitFramebuffer() override;
 
@@ -59,6 +116,22 @@ private:
         GLuint updates_combiner_buffer_color_alpha;
     };
 
+    struct TEVShader {
+        OGLShader shader;
+
+        // Hardware fragment shader
+        GLuint uniform_alphatest_ref;
+        GLuint uniform_tex;
+        GLuint uniform_tev_combiner_buffer_color;
+        GLuint uniform_tev_const_colors;
+
+        TEVShader() = default;
+        TEVShader(TEVShader&& o) : shader(std::move(o.shader)),
+            uniform_alphatest_ref(o.uniform_alphatest_ref), uniform_tex(o.uniform_tex),
+            uniform_tev_combiner_buffer_color(o.uniform_tev_combiner_buffer_color),
+            uniform_tev_const_colors(o.uniform_tev_const_colors) {}
+    };
+
     /// Structure used for storing information about color textures
     struct TextureInfo {
         OGLTexture texture;
@@ -156,27 +229,12 @@ private:
     /// Syncs the depth test states to match the PICA register
     void SyncDepthTest();
 
-    /// Syncs the specified TEV stage's color and alpha sources to match the PICA register
-    void SyncTevSources(unsigned stage_index, const Pica::Regs::TevStageConfig& config);
-
-    /// Syncs the specified TEV stage's color and alpha modifiers to match the PICA register
-    void SyncTevModifiers(unsigned stage_index, const Pica::Regs::TevStageConfig& config);
-
-    /// Syncs the specified TEV stage's color and alpha combiner operations to match the PICA register
-    void SyncTevOps(unsigned stage_index, const Pica::Regs::TevStageConfig& config);
-
-    /// Syncs the specified TEV stage's constant color to match the PICA register
-    void SyncTevColor(unsigned stage_index, const Pica::Regs::TevStageConfig& config);
-
-    /// Syncs the specified TEV stage's color and alpha multipliers to match the PICA register
-    void SyncTevMultipliers(unsigned stage_index, const Pica::Regs::TevStageConfig& config);
+    /// Syncs the TEV constant color to match the PICA register
+    void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage);
 
     /// Syncs the TEV combiner color buffer to match the PICA register
     void SyncCombinerColor();
 
-    /// Syncs the TEV combiner write flags to match the PICA register
-    void SyncCombinerWriteFlags();
-
     /// Syncs the remaining OpenGL drawing state to match the current PICA state
     void SyncDrawState();
 
@@ -213,21 +271,11 @@ private:
     std::array<SamplerInfo, 3> texture_samplers;
     TextureInfo fb_color_texture;
     DepthTextureInfo fb_depth_texture;
-    OGLShader shader;
+
+    std::unordered_map<ShaderCacheKey, TEVShader> shader_cache;
+    TEVShader* current_shader = nullptr;
+
     OGLVertexArray vertex_array;
     OGLBuffer vertex_buffer;
     OGLFramebuffer framebuffer;
-
-    // Hardware vertex shader
-    GLuint attrib_position;
-    GLuint attrib_color;
-    GLuint attrib_texcoords;
-
-    // Hardware fragment shader
-    GLuint uniform_alphatest_enabled;
-    GLuint uniform_alphatest_func;
-    GLuint uniform_alphatest_ref;
-    GLuint uniform_tex;
-    GLuint uniform_tev_combiner_buffer_color;
-    TEVConfigUniforms uniform_tev_cfgs[6];
 };
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 4cf246c06..ee32f6a31 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -2,6 +2,13 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+
+#include "gl_shader_util.h"
+#include "gl_rasterizer.h"
+#include "common/logging/log.h"
+
+#include "video_core/pica.h"
+
 #include <algorithm>
 #include <vector>
 
@@ -65,6 +72,13 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) {
     GLuint program_id = glCreateProgram();
     glAttachShader(program_id, vertex_shader_id);
     glAttachShader(program_id, fragment_shader_id);
+
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_POSITION, "vert_position");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_COLOR, "vert_color");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 0, "vert_texcoords0");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 1, "vert_texcoords1");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 2, "vert_texcoords2");
+
     glLinkProgram(program_id);
 
     // Check the program
@@ -88,3 +102,338 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) {
 }
 
 }
+
+namespace ShaderCache
+{
+
+static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
+    return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace &&
+            stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace &&
+            stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
+            stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
+            stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor &&
+            stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha &&
+            stage.GetColorMultiplier() == 1 &&
+            stage.GetAlphaMultiplier() == 1);
+}
+
+void AppendSource(std::string& shader, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using Source = Pica::Regs::TevStageConfig::Source;
+    switch (source) {
+    case Source::PrimaryColor:
+        shader += "o[2]";
+        break;
+    case Source::PrimaryFragmentColor:
+        // HACK: Until we implement fragment lighting, use primary_color
+        shader += "o[2]";
+        break;
+    case Source::SecondaryFragmentColor:
+        // HACK: Until we implement fragment lighting, use zero
+        shader += "vec4(0.0, 0.0, 0.0, 0.0)";
+        break;
+    case Source::Texture0:
+        shader += "texture(tex[0], o[3].xy)";
+        break;
+    case Source::Texture1:
+        shader += "texture(tex[1], o[3].zw)";
+        break;
+    case Source::Texture2: // TODO: Unverified
+        shader += "texture(tex[2], o[5].zw)";
+        break;
+    case Source::PreviousBuffer:
+        shader += "g_combiner_buffer";
+        break;
+    case Source::Constant:
+        shader += "const_color[" + index_name + "]";
+        break;
+    case Source::Previous:
+        shader += "g_last_tex_env_out";
+        break;
+    default:
+        shader += "vec4(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown source op %u", source);
+        break;
+    }
+}
+
+void AppendColorModifier(std::string& shader, Pica::Regs::TevStageConfig::ColorModifier modifier, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
+    switch (modifier) {
+        case ColorModifier::SourceColor:
+            AppendSource(shader, source, index_name);
+            shader += ".rgb";
+            break;
+        case ColorModifier::OneMinusSourceColor:
+            shader += "vec3(1.0) - ";
+            AppendSource(shader, source, index_name);
+            shader += ".rgb";
+            break;
+        case ColorModifier::SourceAlpha:
+            AppendSource(shader, source, index_name);
+            shader += ".aaa";
+            break;
+        case ColorModifier::OneMinusSourceAlpha:
+            shader += "vec3(1.0) - ";
+            AppendSource(shader, source, index_name);
+            shader += ".aaa";
+            break;
+        case ColorModifier::SourceRed:
+            AppendSource(shader, source, index_name);
+            shader += ".rrr";
+            break;
+        case ColorModifier::OneMinusSourceRed:
+            shader += "vec3(1.0) - ";
+            AppendSource(shader, source, index_name);
+            shader += ".rrr";
+            break;
+        case ColorModifier::SourceGreen:
+            AppendSource(shader, source, index_name);
+            shader += ".ggg";
+            break;
+        case ColorModifier::OneMinusSourceGreen:
+            shader += "vec3(1.0) - ";
+            AppendSource(shader, source, index_name);
+            shader += ".ggg";
+            break;
+        case ColorModifier::SourceBlue:
+            AppendSource(shader, source, index_name);
+            shader += ".bbb";
+            break;
+        case ColorModifier::OneMinusSourceBlue:
+            shader += "vec3(1.0) - ";
+            AppendSource(shader, source, index_name);
+            shader += ".bbb";
+            break;
+        default:
+            shader += "vec3(0.0)";
+            LOG_CRITICAL(Render_OpenGL, "Unknown color modifier op %u", modifier);
+            break;
+    }
+}
+
+void AppendAlphaModifier(std::string& shader, Pica::Regs::TevStageConfig::AlphaModifier modifier, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
+    switch (modifier) {
+        case AlphaModifier::SourceAlpha:
+            AppendSource(shader, source, index_name);
+            shader += ".a";
+            break;
+        case AlphaModifier::OneMinusSourceAlpha:
+            shader += "1.0 - ";
+            AppendSource(shader, source, index_name);
+            shader += ".a";
+            break;
+        case AlphaModifier::SourceRed:
+            AppendSource(shader, source, index_name);
+            shader += ".r";
+            break;
+        case AlphaModifier::OneMinusSourceRed:
+            shader += "1.0 - ";
+            AppendSource(shader, source, index_name);
+            shader += ".r";
+            break;
+        case AlphaModifier::SourceGreen:
+            AppendSource(shader, source, index_name);
+            shader += ".g";
+            break;
+        case AlphaModifier::OneMinusSourceGreen:
+            shader += "1.0 - ";
+            AppendSource(shader, source, index_name);
+            shader += ".g";
+            break;
+        case AlphaModifier::SourceBlue:
+            AppendSource(shader, source, index_name);
+            shader += ".b";
+            break;
+        case AlphaModifier::OneMinusSourceBlue:
+            shader += "1.0 - ";
+            AppendSource(shader, source, index_name);
+            shader += ".b";
+            break;
+        default:
+            shader += "vec3(0.0)";
+            LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier);
+            break;
+    }
+}
+
+void AppendColorCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation, const std::string& variable_name) {
+    using Operation = Pica::Regs::TevStageConfig::Operation;
+
+    switch (operation) {
+        case Operation::Replace:
+            shader += variable_name + "[0]";
+            break;
+        case Operation::Modulate:
+            shader += variable_name + "[0] * " + variable_name + "[1]";
+            break;
+        case Operation::Add:
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+            break;
+        case Operation::AddSigned:
+            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), 0.0, 1.0)";
+            break;
+        case Operation::Lerp:
+            shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
+            break;
+        case Operation::Subtract:
+            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+            break;
+        case Operation::MultiplyThenAdd:
+            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+            break;
+        case Operation::AddThenMultiply:
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
+            break;
+        default:
+            shader += "0.0";
+            LOG_CRITICAL(Render_OpenGL, "Unknown color comb op %u", operation);
+            break;
+    }
+}
+
+void AppendAlphaCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation, const std::string& variable_name) {
+    using Operation = Pica::Regs::TevStageConfig::Operation;
+    switch (operation) {
+        case Operation::Replace:
+            shader += variable_name + "[0]";
+            break;
+        case Operation::Modulate:
+            shader += variable_name + "[0] * " + variable_name + "[1]";
+            break;
+        case Operation::Add:
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+            break;
+        case Operation::AddSigned:
+            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
+            break;
+        case Operation::Lerp:
+            shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
+            break;
+        case Operation::Subtract:
+            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+            break;
+        case Operation::MultiplyThenAdd:
+            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+            break;
+        case Operation::AddThenMultiply:
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
+            break;
+        default:
+            shader += "0.0";
+            LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner op %u", operation);
+            break;
+    }
+}
+
+void AppendAlphaTestCondition(std::string& shader, Pica::Regs::CompareFunc func) {
+    using CompareFunc = Pica::Regs::CompareFunc;
+    switch (func) {
+        case CompareFunc::Never:
+            shader += "true";
+            break;
+        case CompareFunc::Always:
+            shader += "false";
+            break;
+        case CompareFunc::Equal:
+            shader += "g_last_tex_env_out.a != alphatest_ref";
+            break;
+        case CompareFunc::NotEqual:
+            shader += "g_last_tex_env_out.a == alphatest_ref";
+            break;
+        case CompareFunc::LessThan:
+            shader += "g_last_tex_env_out.a >= alphatest_ref";
+            break;
+        case CompareFunc::LessThanOrEqual:
+            shader += "g_last_tex_env_out.a > alphatest_ref";
+            break;
+        case CompareFunc::GreaterThan:
+            shader += "g_last_tex_env_out.a <= alphatest_ref";
+            break;
+        case CompareFunc::GreaterThanOrEqual:
+            shader += "g_last_tex_env_out.a < alphatest_ref";
+            break;
+        default:
+            shader += "false";
+            LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func);
+            break;
+    }
+}
+
+std::string GenerateFragmentShader(const ShaderCacheKey& config) {
+    std::string shader = R"(
+#version 150 core
+
+#define NUM_VTX_ATTR 7
+#define NUM_TEV_STAGES 6
+
+in vec4 o[NUM_VTX_ATTR];
+out vec4 color;
+
+uniform float alphatest_ref;
+uniform vec4 const_color[NUM_TEV_STAGES];
+uniform sampler2D tex[3];
+
+uniform vec4 tev_combiner_buffer_color;
+
+void main(void) {
+    vec4 g_combiner_buffer = tev_combiner_buffer_color;
+    vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
+)";
+
+    // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
+    if (config.alpha_test_func == Pica::Regs::CompareFunc::Never) {
+        shader += "discard;";
+        return shader;
+    }
+
+    auto& tev_stages = config.tev_stages;
+    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
+        auto& tev_stage = tev_stages[tev_stage_index];
+        if (!IsPassThroughTevStage(tev_stage)) {
+            std::string index_name = std::to_string(tev_stage_index);
+
+            shader += "vec3 color_results_" + index_name + "[3] = vec3[3](";
+            AppendColorModifier(shader, tev_stage.color_modifier1, tev_stage.color_source1, index_name);
+            shader += ", ";
+            AppendColorModifier(shader, tev_stage.color_modifier2, tev_stage.color_source2, index_name);
+            shader += ", ";
+            AppendColorModifier(shader, tev_stage.color_modifier3, tev_stage.color_source3, index_name);
+            shader += ");\n";
+
+            shader += "vec3 color_output_" + index_name + " = ";
+            AppendColorCombiner(shader, tev_stage.color_op, "color_results_" + index_name);
+            shader += ";\n";
+
+            shader += "float alpha_results_" + index_name + "[3] = float[3](";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier1, tev_stage.alpha_source1, index_name);
+            shader += ", ";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier2, tev_stage.alpha_source2, index_name);
+            shader += ", ";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier3, tev_stage.alpha_source3, index_name);
+            shader += ");\n";
+
+            shader += "float alpha_output_" + index_name + " = ";
+            AppendAlphaCombiner(shader, tev_stage.alpha_op, "alpha_results_" + index_name);
+            shader += ";\n";
+
+            shader += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " + std::to_string(tev_stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " + std::to_string(tev_stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
+        }
+
+        if (config.TevStageUpdatesCombinerBufferColor(tev_stage_index))
+            shader += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
+
+        if (config.TevStageUpdatesCombinerBufferAlpha(tev_stage_index))
+            shader += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
+    }
+
+    if (config.alpha_test_func != Pica::Regs::CompareFunc::Always) {
+        shader += "if (";
+        AppendAlphaTestCondition(shader, config.alpha_test_func);
+        shader += ") {\n discard;\n }\n";
+    }
+
+    shader += "color = g_last_tex_env_out;\n}";
+    return shader;
+}
+}
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index c9d7cc380..ca62c83ba 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -8,6 +8,12 @@
 
 namespace ShaderUtil {
 
+enum Attributes {
+    ATTRIBUTE_POSITION  = 0,
+    ATTRIBUTE_COLOR     = 1,
+    ATTRIBUTE_TEXCOORDS = 2,
+};
+
 GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path);
 
 }
diff --git a/src/video_core/renderer_opengl/gl_shaders.h b/src/video_core/renderer_opengl/gl_shaders.h
index a8cb2f595..2ba2c6b0f 100644
--- a/src/video_core/renderer_opengl/gl_shaders.h
+++ b/src/video_core/renderer_opengl/gl_shaders.h
@@ -49,14 +49,16 @@ const char g_vertex_shader_hw[] = R"(
 
 in vec4 vert_position;
 in vec4 vert_color;
-in vec2 vert_texcoords[3];
+in vec2 vert_texcoords0;
+in vec2 vert_texcoords1;
+in vec2 vert_texcoords2;
 
 out vec4 o[NUM_VTX_ATTR];
 
 void main() {
     o[2] = vert_color;
-    o[3] = vec4(vert_texcoords[0].xy, vert_texcoords[1].xy);
-    o[5] = vec4(0.0, 0.0, vert_texcoords[2].xy);
+    o[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
+    o[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
 
     gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
 }
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 6ecbedbb4..668b04259 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -65,6 +65,7 @@ public:
         GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING
         GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
         GLuint shader_program; // GL_CURRENT_PROGRAM
+        bool shader_dirty;
     } draw;
 
     OpenGLState();

From 82f3e6dc69d23c3e70c1ad2805e2e15397cd9156 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Thu, 1 Oct 2015 18:34:10 -0400
Subject: [PATCH 02/19] gl_shader_util: Fix precision bug with alpha testing. -
 Alpha testing is not done with float32 precision, this makes the HW renderer
 match the SW renderer.

---
 src/video_core/renderer_opengl/gl_rasterizer.cpp  |  4 ++--
 src/video_core/renderer_opengl/gl_shader_util.cpp | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 45329d561..38d184ae2 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -195,7 +195,7 @@ void RasterizerOpenGL::RegenerateShaders() {
 
     // Sync alpha reference
     if (current_shader->uniform_alphatest_ref != -1)
-        glUniform1f(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
+        glUniform1i(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref);
 
     // Sync combiner buffer color
     if (current_shader->uniform_tev_combiner_buffer_color != -1) {
@@ -655,7 +655,7 @@ void RasterizerOpenGL::SyncBlendColor() {
 void RasterizerOpenGL::SyncAlphaTest() {
     const auto& regs = Pica::g_state.regs;
     if (current_shader->uniform_alphatest_ref != -1)
-        glUniform1f(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
+        glUniform1i(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref);
 }
 
 void RasterizerOpenGL::SyncLogicOp() {
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index ee32f6a31..2c049e940 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -336,22 +336,22 @@ void AppendAlphaTestCondition(std::string& shader, Pica::Regs::CompareFunc func)
             shader += "false";
             break;
         case CompareFunc::Equal:
-            shader += "g_last_tex_env_out.a != alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
             break;
         case CompareFunc::NotEqual:
-            shader += "g_last_tex_env_out.a == alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
             break;
         case CompareFunc::LessThan:
-            shader += "g_last_tex_env_out.a >= alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
             break;
         case CompareFunc::LessThanOrEqual:
-            shader += "g_last_tex_env_out.a > alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
             break;
         case CompareFunc::GreaterThan:
-            shader += "g_last_tex_env_out.a <= alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
             break;
         case CompareFunc::GreaterThanOrEqual:
-            shader += "g_last_tex_env_out.a < alphatest_ref";
+            shader += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
             break;
         default:
             shader += "false";
@@ -370,7 +370,7 @@ std::string GenerateFragmentShader(const ShaderCacheKey& config) {
 in vec4 o[NUM_VTX_ATTR];
 out vec4 color;
 
-uniform float alphatest_ref;
+uniform int alphatest_ref;
 uniform vec4 const_color[NUM_TEV_STAGES];
 uniform sampler2D tex[3];
 

From 37b0aa5af74dc01dadff5b173523580d03db2f02 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Thu, 1 Oct 2015 22:12:44 -0400
Subject: [PATCH 03/19] gl_rasterizer: Fix typo in uploading TEV const color
 uniforms.

---
 src/video_core/renderer_opengl/gl_rasterizer.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 38d184ae2..a69e09188 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -324,19 +324,19 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
         SyncTevConstColor(0, regs.tev_stage0);
         break;
     case PICA_REG_INDEX(tev_stage1.const_r):
-        SyncTevConstColor(1, regs.tev_stage0);
+        SyncTevConstColor(1, regs.tev_stage1);
         break;
     case PICA_REG_INDEX(tev_stage2.const_r):
-        SyncTevConstColor(2, regs.tev_stage0);
+        SyncTevConstColor(2, regs.tev_stage2);
         break;
     case PICA_REG_INDEX(tev_stage3.const_r):
-        SyncTevConstColor(3, regs.tev_stage0);
+        SyncTevConstColor(3, regs.tev_stage3);
         break;
     case PICA_REG_INDEX(tev_stage4.const_r):
-        SyncTevConstColor(4, regs.tev_stage0);
+        SyncTevConstColor(4, regs.tev_stage4);
         break;
     case PICA_REG_INDEX(tev_stage5.const_r):
-        SyncTevConstColor(5, regs.tev_stage0);
+        SyncTevConstColor(5, regs.tev_stage5);
         break;
 
     // TEV combiner buffer color

From b02a533d946fbf98897ee42334a343d2aa4d7bf4 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Fri, 2 Oct 2015 00:12:50 -0400
Subject: [PATCH 04/19] gl_shader_util: Use vec3 constants for
 AppendColorCombiner.

---
 src/video_core/renderer_opengl/gl_shader_util.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 2c049e940..8a6a51ad4 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -268,25 +268,25 @@ void AppendColorCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operat
             shader += variable_name + "[0] * " + variable_name + "[1]";
             break;
         case Operation::Add:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
             break;
         case Operation::AddSigned:
-            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), 0.0, 1.0)";
+            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
             break;
         case Operation::Lerp:
             shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
             break;
         case Operation::Subtract:
-            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
             break;
         case Operation::MultiplyThenAdd:
-            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
             break;
         case Operation::AddThenMultiply:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
+            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
             break;
         default:
-            shader += "0.0";
+            shader += "vec3(0.0)";
             LOG_CRITICAL(Render_OpenGL, "Unknown color comb op %u", operation);
             break;
     }

From 3c057bd3d80b049720b11d0b44391c18870c28e8 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Mon, 5 Oct 2015 20:52:04 -0400
Subject: [PATCH 05/19] gl_rasterizer: Move logic for creating ShaderCacheKey
 to a static function.

---
 src/video_core/pica.h                         |  8 ++--
 .../renderer_opengl/gl_rasterizer.cpp         | 19 +-------
 .../renderer_opengl/gl_rasterizer.h           | 45 ++++++++++++++++++-
 3 files changed, 50 insertions(+), 22 deletions(-)

diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index 18fdc8c85..2f1b2dec4 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -317,7 +317,7 @@ struct Regs {
         };
 
         union {
-            u32 source_raw;
+            u32 sources_raw;
             BitField< 0, 4, Source> color_source1;
             BitField< 4, 4, Source> color_source2;
             BitField< 8, 4, Source> color_source3;
@@ -327,7 +327,7 @@ struct Regs {
         };
 
         union {
-            u32 modifier_raw;
+            u32 modifiers_raw;
             BitField< 0, 4, ColorModifier> color_modifier1;
             BitField< 4, 4, ColorModifier> color_modifier2;
             BitField< 8, 4, ColorModifier> color_modifier3;
@@ -337,7 +337,7 @@ struct Regs {
         };
 
         union {
-            u32 op_raw;
+            u32 ops_raw;
             BitField< 0, 4, Operation> color_op;
             BitField<16, 4, Operation> alpha_op;
         };
@@ -351,7 +351,7 @@ struct Regs {
         };
 
         union {
-            u32 scale_raw;
+            u32 scales_raw;
             BitField< 0, 2, u32> color_scale;
             BitField<16, 2, u32> alpha_scale;
         };
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a69e09188..01b9c91c6 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -145,21 +145,7 @@ extern std::string GenerateFragmentShader(const ShaderCacheKey& config);
 }
 
 void RasterizerOpenGL::RegenerateShaders() {
-    const auto& regs = Pica::g_state.regs;
-
-    ShaderCacheKey config;
-    config.alpha_test_func = regs.output_merger.alpha_test.enable ?
-            regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
-    config.tev_stages = regs.GetTevStages();
-    for (auto& tev : config.tev_stages) {
-        tev.const_r = 0;
-        tev.const_g = 0;
-        tev.const_b = 0;
-        tev.const_a = 0;
-    }
-    config.combiner_buffer_input =
-            regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
-            regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
+    ShaderCacheKey config = ShaderCacheKey::CurrentShaderConfig();
 
     auto cached_shader = shader_cache.find(config);
     if (cached_shader != shader_cache.end()) {
@@ -192,10 +178,9 @@ void RasterizerOpenGL::RegenerateShaders() {
         }
     }
 
-
     // Sync alpha reference
     if (current_shader->uniform_alphatest_ref != -1)
-        glUniform1i(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref);
+        glUniform1i(current_shader->uniform_alphatest_ref, Pica::g_state.regs.output_merger.alpha_test.ref);
 
     // Sync combiner buffer color
     if (current_shader->uniform_tev_combiner_buffer_color != -1) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 19e8db69a..5bc4a319f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -9,6 +9,7 @@
 
 #include "common/common_types.h"
 
+#include "video_core/pica.h"
 #include "video_core/hwrasterizer_base.h"
 #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
 #include "video_core/renderer_opengl/gl_state.h"
@@ -37,7 +38,7 @@ struct ShaderCacheKey {
     };
 
     Regs::CompareFunc alpha_test_func;
-    std::array<Regs::TevStageConfig, 6> tev_stages;
+    std::array<Regs::TevStageConfig, 6> tev_stages = {};
     u8 combiner_buffer_input;
 
     bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
@@ -47,6 +48,48 @@ struct ShaderCacheKey {
     bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
         return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
     }
+
+    static ShaderCacheKey CurrentShaderConfig() {
+        const auto& regs = Pica::g_state.regs;
+        ShaderCacheKey config;
+
+        config.alpha_test_func = regs.output_merger.alpha_test.enable ?
+            regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
+
+        config.tev_stages[0].source_raw = regs.tev_stage0.source_raw;
+        config.tev_stages[1].source_raw = regs.tev_stage1.source_raw;
+        config.tev_stages[2].source_raw = regs.tev_stage2.source_raw;
+        config.tev_stages[3].source_raw = regs.tev_stage3.source_raw;
+        config.tev_stages[4].source_raw = regs.tev_stage4.source_raw;
+        config.tev_stages[5].source_raw = regs.tev_stage5.source_raw;
+
+        config.tev_stages[0].modifier_raw = regs.tev_stage0.modifier_raw;
+        config.tev_stages[1].modifier_raw = regs.tev_stage1.modifier_raw;
+        config.tev_stages[2].modifier_raw = regs.tev_stage2.modifier_raw;
+        config.tev_stages[3].modifier_raw = regs.tev_stage3.modifier_raw;
+        config.tev_stages[4].modifier_raw = regs.tev_stage4.modifier_raw;
+        config.tev_stages[5].modifier_raw = regs.tev_stage5.modifier_raw;
+
+        config.tev_stages[0].op_raw = regs.tev_stage0.op_raw;
+        config.tev_stages[1].op_raw = regs.tev_stage1.op_raw;
+        config.tev_stages[2].op_raw = regs.tev_stage2.op_raw;
+        config.tev_stages[3].op_raw = regs.tev_stage3.op_raw;
+        config.tev_stages[4].op_raw = regs.tev_stage4.op_raw;
+        config.tev_stages[5].op_raw = regs.tev_stage5.op_raw;
+
+        config.tev_stages[0].scale_raw = regs.tev_stage0.scale_raw;
+        config.tev_stages[1].scale_raw = regs.tev_stage1.scale_raw;
+        config.tev_stages[2].scale_raw = regs.tev_stage2.scale_raw;
+        config.tev_stages[3].scale_raw = regs.tev_stage3.scale_raw;
+        config.tev_stages[4].scale_raw = regs.tev_stage4.scale_raw;
+        config.tev_stages[5].scale_raw = regs.tev_stage5.scale_raw;
+
+        config.combiner_buffer_input =
+            regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
+            regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
+
+        return config;
+    }
 };
 
 namespace std {

From c86b9d42423b5a83ccba40f828b7ad9dafe3e317 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Mon, 5 Oct 2015 22:33:47 -0400
Subject: [PATCH 06/19] renderer_opengl: Refactor shader generation/caching to
 be more organized + various cleanups.

---
 src/common/common_funcs.h                     |  18 +
 src/video_core/CMakeLists.txt                 |   3 +-
 .../renderer_opengl/gl_rasterizer.cpp         | 131 +++----
 .../renderer_opengl/gl_rasterizer.h           |  41 +-
 .../renderer_opengl/gl_resource_manager.h     |   2 +-
 .../renderer_opengl/gl_shader_gen.cpp         | 371 ++++++++++++++++++
 .../renderer_opengl/gl_shader_gen.h           |  17 +
 .../renderer_opengl/gl_shader_util.cpp        | 348 +---------------
 .../renderer_opengl/gl_shader_util.h          |   6 +-
 src/video_core/renderer_opengl/gl_shaders.h   | 339 ----------------
 .../renderer_opengl/renderer_opengl.cpp       |  39 +-
 11 files changed, 527 insertions(+), 788 deletions(-)
 create mode 100644 src/video_core/renderer_opengl/gl_shader_gen.cpp
 create mode 100644 src/video_core/renderer_opengl/gl_shader_gen.h
 delete mode 100644 src/video_core/renderer_opengl/gl_shaders.h

diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index ed20c3629..7a8dd39a0 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -4,6 +4,9 @@
 
 #pragma once
 
+#include <cstddef>
+#include <functional>
+
 #include "common_types.h"
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
@@ -95,3 +98,18 @@ inline u64 _rotr64(u64 x, unsigned int shift){
 // This function might change the error code.
 // Defined in Misc.cpp.
 const char* GetLastErrorMsg();
+
+template <typename T>
+inline std::size_t hash(const T& o) {
+    return std::hash<T>()(o);
+}
+
+template <typename T>
+inline std::size_t combine_hash(const T& o) {
+    return hash(o);
+}
+
+template <typename T, typename... Args>
+inline std::size_t combine_hash(const T& o, const Args&... args) {
+    return hash(o) * 3 + combine_hash(args...);
+}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 8c9d76ab4..2a924f4ad 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(SRCS
             renderer_opengl/gl_rasterizer.cpp
             renderer_opengl/gl_rasterizer_cache.cpp
+            renderer_opengl/gl_shader_gen.cpp
             renderer_opengl/gl_shader_util.cpp
             renderer_opengl/gl_state.cpp
             renderer_opengl/renderer_opengl.cpp
@@ -21,8 +22,8 @@ set(HEADERS
             renderer_opengl/gl_rasterizer.h
             renderer_opengl/gl_rasterizer_cache.h
             renderer_opengl/gl_resource_manager.h
+            renderer_opengl/gl_shader_gen.h
             renderer_opengl/gl_shader_util.h
-            renderer_opengl/gl_shaders.h
             renderer_opengl/gl_state.h
             renderer_opengl/pica_to_gl.h
             renderer_opengl/renderer_opengl.h
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 01b9c91c6..4f9865230 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -9,6 +9,7 @@
 
 #include "common/color.h"
 #include "common/file_util.h"
+#include "common/make_unique.h"
 #include "common/math_util.h"
 #include "common/microprofile.h"
 #include "common/profiler.h"
@@ -20,7 +21,7 @@
 #include "video_core/pica.h"
 #include "video_core/utils.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
-#include "video_core/renderer_opengl/gl_shaders.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
 #include "video_core/renderer_opengl/gl_shader_util.h"
 #include "video_core/renderer_opengl/pica_to_gl.h"
 
@@ -54,20 +55,20 @@ void RasterizerOpenGL::InitObjects() {
     state.Apply();
 
     // Set vertex attributes
-    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position));
-    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_POSITION);
+    glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position));
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_POSITION);
 
-    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color));
-    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_COLOR);
+    glVertexAttribPointer(GLShader::ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color));
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_COLOR);
 
-    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
-    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
-    glVertexAttribPointer(ShaderUtil::ATTRIBUTE_TEXCOORDS + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
-    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS);
-    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS + 1);
-    glEnableVertexAttribArray(ShaderUtil::ATTRIBUTE_TEXCOORDS + 2);
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 0, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 0);
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 1);
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 2);
 
-    RegenerateShaders();
+    SetShader();
 
     // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation
     fb_color_texture.texture.Create();
@@ -117,8 +118,6 @@ void RasterizerOpenGL::InitObjects() {
 }
 
 void RasterizerOpenGL::Reset() {
-    const auto& regs = Pica::g_state.regs;
-
     SyncCullMode();
     SyncBlendEnabled();
     SyncBlendFuncs();
@@ -127,7 +126,7 @@ void RasterizerOpenGL::Reset() {
     SyncStencilTest();
     SyncDepthTest();
 
-    RegenerateShaders();
+    SetShader();
 
     res_cache.FullFlush();
 }
@@ -140,70 +139,12 @@ void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0,
     vertex_batch.emplace_back(v2);
 }
 
-namespace ShaderCache {
-extern std::string GenerateFragmentShader(const ShaderCacheKey& config);
-}
-
-void RasterizerOpenGL::RegenerateShaders() {
-    ShaderCacheKey config = ShaderCacheKey::CurrentShaderConfig();
-
-    auto cached_shader = shader_cache.find(config);
-    if (cached_shader != shader_cache.end()) {
-        current_shader = &cached_shader->second;
-        state.draw.shader_program = current_shader->shader.handle;
-        state.Apply();
-    } else {
-        LOG_CRITICAL(Render_OpenGL, "Creating new shader: %08X", hash(config));
-
-        TEVShader shader;
-
-        std::string fragShader = ShaderCache::GenerateFragmentShader(config);
-        shader.shader.Create(GLShaders::g_vertex_shader_hw, fragShader.c_str());
-
-        shader.uniform_alphatest_ref = glGetUniformLocation(shader.shader.handle, "alphatest_ref");
-        shader.uniform_tex = glGetUniformLocation(shader.shader.handle, "tex");
-        shader.uniform_tev_combiner_buffer_color = glGetUniformLocation(shader.shader.handle, "tev_combiner_buffer_color");
-        shader.uniform_tev_const_colors = glGetUniformLocation(shader.shader.handle, "const_color");
-
-        current_shader = &shader_cache.emplace(config, std::move(shader)).first->second;
-
-        state.draw.shader_program = current_shader->shader.handle;
-        state.Apply();
-
-        // Set the texture samplers to correspond to different texture units
-        if (shader.uniform_tex != -1) {
-            glUniform1i(shader.uniform_tex, 0);
-            glUniform1i(shader.uniform_tex + 1, 1);
-            glUniform1i(shader.uniform_tex + 2, 2);
-        }
-    }
-
-    // Sync alpha reference
-    if (current_shader->uniform_alphatest_ref != -1)
-        glUniform1i(current_shader->uniform_alphatest_ref, Pica::g_state.regs.output_merger.alpha_test.ref);
-
-    // Sync combiner buffer color
-    if (current_shader->uniform_tev_combiner_buffer_color != -1) {
-        auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
-        glUniform4fv(current_shader->uniform_tev_combiner_buffer_color, 1, combiner_color.data());
-    }
-
-    // Sync TEV const colors
-    if (current_shader->uniform_tev_const_colors != -1) {
-        auto& tev_stages = Pica::g_state.regs.GetTevStages();
-        for (int tev_index = 0; tev_index < tev_stages.size(); ++tev_index) {
-            auto const_color = PicaToGL::ColorRGBA8(tev_stages[tev_index].const_color);
-            glUniform4fv(current_shader->uniform_tev_const_colors + tev_index, 1, const_color.data());
-        }
-    }
-}
-
 void RasterizerOpenGL::DrawTriangles() {
     SyncFramebuffer();
     SyncDrawState();
 
     if (state.draw.shader_dirty) {
-        RegenerateShaders();
+        SetShader();
         state.draw.shader_dirty = false;
     }
 
@@ -519,6 +460,48 @@ void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::
     state.Apply();
 }
 
+void RasterizerOpenGL::SetShader() {
+    ShaderCacheKey config = ShaderCacheKey::CurrentConfig();
+
+    // Find (or generate) the GLSL shader for the current TEV state
+    auto cached_shader = shader_cache.find(config);
+    if (cached_shader != shader_cache.end()) {
+        current_shader = cached_shader->second.get();
+
+        state.draw.shader_program = current_shader->shader.handle;
+        state.Apply();
+    } else {
+        LOG_DEBUG(Render_OpenGL, "Creating new shader: %08X", hash(config));
+
+        std::unique_ptr<TEVShader> shader = Common::make_unique<TEVShader>();
+
+        shader->shader.Create(GLShader::GenerateVertexShader().c_str(), GLShader::GenerateFragmentShader(config).c_str());
+        shader->uniform_alphatest_ref = glGetUniformLocation(shader->shader.handle, "alphatest_ref");
+        shader->uniform_tex = glGetUniformLocation(shader->shader.handle, "tex");
+        shader->uniform_tev_combiner_buffer_color = glGetUniformLocation(shader->shader.handle, "tev_combiner_buffer_color");
+        shader->uniform_tev_const_colors = glGetUniformLocation(shader->shader.handle, "const_color");
+
+        state.draw.shader_program = shader->shader.handle;
+        state.Apply();
+
+        // Set the texture samplers to correspond to different texture units
+        if (shader->uniform_tex != -1) {
+            glUniform1i(shader->uniform_tex, 0);
+            glUniform1i(shader->uniform_tex + 1, 1);
+            glUniform1i(shader->uniform_tex + 2, 2);
+        }
+
+        current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get();
+    }
+
+    // Update uniforms
+    SyncAlphaTest();
+    SyncCombinerColor();
+    auto& tev_stages = Pica::g_state.regs.GetTevStages();
+    for (int index = 0; index < tev_stages.size(); ++index)
+        SyncTevConstColor(index, tev_stages[index]);
+}
+
 void RasterizerOpenGL::SyncFramebuffer() {
     const auto& regs = Pica::g_state.regs;
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 5bc4a319f..de9e4d22e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <cstddef>
+#include <memory>
 #include <vector>
 #include <unordered_map>
 
@@ -15,21 +17,6 @@
 #include "video_core/renderer_opengl/gl_state.h"
 #include "video_core/shader/shader_interpreter.h"
 
-template <typename T>
-inline size_t hash(const T& o) {
-    return std::hash<T>()(o);
-}
-
-template <typename T>
-inline size_t combine_hash(const T& o) {
-    return hash(o);
-}
-
-template <typename T, typename... Args>
-inline size_t combine_hash(const T& o, const Args&... args) {
-    return hash(o) * 3 + combine_hash(args...);
-}
-
 struct ShaderCacheKey {
     using Regs = Pica::Regs;
 
@@ -49,7 +36,7 @@ struct ShaderCacheKey {
         return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
     }
 
-    static ShaderCacheKey CurrentShaderConfig() {
+    static ShaderCacheKey CurrentConfig() {
         const auto& regs = Pica::g_state.regs;
         ShaderCacheKey config;
 
@@ -94,8 +81,14 @@ struct ShaderCacheKey {
 
 namespace std {
 
+template<> struct hash<::Pica::Regs::CompareFunc> {
+    std::size_t operator()(const ::Pica::Regs::CompareFunc& o) {
+        return ::hash((unsigned)o);
+    }
+};
+
 template<> struct hash<::Pica::Regs::TevStageConfig> {
-    size_t operator()(const ::Pica::Regs::TevStageConfig& o) {
+    std::size_t operator()(const ::Pica::Regs::TevStageConfig& o) {
         return ::combine_hash(
             ::hash(o.source_raw), ::hash(o.modifier_raw),
             ::hash(o.op_raw), ::hash(o.scale_raw));
@@ -103,13 +96,14 @@ template<> struct hash<::Pica::Regs::TevStageConfig> {
 };
 
 template<> struct hash<::ShaderCacheKey> {
-    size_t operator()(const ::ShaderCacheKey& o) const {
+    std::size_t operator()(const ::ShaderCacheKey& o) const {
         return ::combine_hash(o.alpha_test_func, o.combiner_buffer_input,
             o.tev_stages[0], o.tev_stages[1], o.tev_stages[2],
             o.tev_stages[3], o.tev_stages[4], o.tev_stages[5]);
     }
 };
-}
+
+} // namespace std
 
 class RasterizerOpenGL : public HWRasterizer {
 public:
@@ -131,8 +125,6 @@ public:
     /// Draw the current batch of triangles
     void DrawTriangles() override;
 
-    void RegenerateShaders();
-
     /// Commit the rasterizer's framebuffer contents immediately to the current 3DS memory framebuffer
     void CommitFramebuffer() override;
 
@@ -245,6 +237,9 @@ private:
     /// Reconfigure the OpenGL depth texture to use the given format and dimensions
     void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height);
 
+    /// Sets the OpenGL shader in accordance with the current PICA register state
+    void SetShader();
+
     /// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer
     void SyncFramebuffer();
 
@@ -315,8 +310,8 @@ private:
     TextureInfo fb_color_texture;
     DepthTextureInfo fb_depth_texture;
 
-    std::unordered_map<ShaderCacheKey, TEVShader> shader_cache;
-    TEVShader* current_shader = nullptr;
+    std::unordered_map<ShaderCacheKey, std::unique_ptr<TEVShader>> shader_cache;
+    const TEVShader* current_shader = nullptr;
 
     OGLVertexArray vertex_array;
     OGLBuffer vertex_buffer;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 65034d40d..eb128966c 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -71,7 +71,7 @@ public:
     /// Creates a new internal OpenGL resource and stores the handle
     void Create(const char* vert_shader, const char* frag_shader) {
         if (handle != 0) return;
-        handle = ShaderUtil::LoadShaders(vert_shader, frag_shader);
+        handle = GLShader::LoadProgram(vert_shader, frag_shader);
     }
 
     /// Deletes the internal OpenGL resource
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
new file mode 100644
index 000000000..059f127af
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -0,0 +1,371 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/pica.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+
+namespace GLShader {
+
+static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
+    return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace &&
+        stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace &&
+        stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
+        stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
+        stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor &&
+        stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha &&
+        stage.GetColorMultiplier() == 1 &&
+        stage.GetAlphaMultiplier() == 1);
+}
+
+static void AppendSource(std::string& shader, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using Source = Pica::Regs::TevStageConfig::Source;
+    switch (source) {
+    case Source::PrimaryColor:
+        shader += "o[2]";
+        break;
+    case Source::PrimaryFragmentColor:
+        // HACK: Until we implement fragment lighting, use primary_color
+        shader += "o[2]";
+        break;
+    case Source::SecondaryFragmentColor:
+        // HACK: Until we implement fragment lighting, use zero
+        shader += "vec4(0.0, 0.0, 0.0, 0.0)";
+        break;
+    case Source::Texture0:
+        shader += "texture(tex[0], o[3].xy)";
+        break;
+    case Source::Texture1:
+        shader += "texture(tex[1], o[3].zw)";
+        break;
+    case Source::Texture2: // TODO: Unverified
+        shader += "texture(tex[2], o[5].zw)";
+        break;
+    case Source::PreviousBuffer:
+        shader += "g_combiner_buffer";
+        break;
+    case Source::Constant:
+        shader += "const_color[" + index_name + "]";
+        break;
+    case Source::Previous:
+        shader += "g_last_tex_env_out";
+        break;
+    default:
+        shader += "vec4(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown source op %u", source);
+        break;
+    }
+}
+
+static void AppendColorModifier(std::string& shader, Pica::Regs::TevStageConfig::ColorModifier modifier,
+        Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
+    switch (modifier) {
+    case ColorModifier::SourceColor:
+        AppendSource(shader, source, index_name);
+        shader += ".rgb";
+        break;
+    case ColorModifier::OneMinusSourceColor:
+        shader += "vec3(1.0) - ";
+        AppendSource(shader, source, index_name);
+        shader += ".rgb";
+        break;
+    case ColorModifier::SourceAlpha:
+        AppendSource(shader, source, index_name);
+        shader += ".aaa";
+        break;
+    case ColorModifier::OneMinusSourceAlpha:
+        shader += "vec3(1.0) - ";
+        AppendSource(shader, source, index_name);
+        shader += ".aaa";
+        break;
+    case ColorModifier::SourceRed:
+        AppendSource(shader, source, index_name);
+        shader += ".rrr";
+        break;
+    case ColorModifier::OneMinusSourceRed:
+        shader += "vec3(1.0) - ";
+        AppendSource(shader, source, index_name);
+        shader += ".rrr";
+        break;
+    case ColorModifier::SourceGreen:
+        AppendSource(shader, source, index_name);
+        shader += ".ggg";
+        break;
+    case ColorModifier::OneMinusSourceGreen:
+        shader += "vec3(1.0) - ";
+        AppendSource(shader, source, index_name);
+        shader += ".ggg";
+        break;
+    case ColorModifier::SourceBlue:
+        AppendSource(shader, source, index_name);
+        shader += ".bbb";
+        break;
+    case ColorModifier::OneMinusSourceBlue:
+        shader += "vec3(1.0) - ";
+        AppendSource(shader, source, index_name);
+        shader += ".bbb";
+        break;
+    default:
+        shader += "vec3(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown color modifier op %u", modifier);
+        break;
+    }
+}
+
+static void AppendAlphaModifier(std::string& shader, Pica::Regs::TevStageConfig::AlphaModifier modifier,
+        Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
+    using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
+    switch (modifier) {
+    case AlphaModifier::SourceAlpha:
+        AppendSource(shader, source, index_name);
+        shader += ".a";
+        break;
+    case AlphaModifier::OneMinusSourceAlpha:
+        shader += "1.0 - ";
+        AppendSource(shader, source, index_name);
+        shader += ".a";
+        break;
+    case AlphaModifier::SourceRed:
+        AppendSource(shader, source, index_name);
+        shader += ".r";
+        break;
+    case AlphaModifier::OneMinusSourceRed:
+        shader += "1.0 - ";
+        AppendSource(shader, source, index_name);
+        shader += ".r";
+        break;
+    case AlphaModifier::SourceGreen:
+        AppendSource(shader, source, index_name);
+        shader += ".g";
+        break;
+    case AlphaModifier::OneMinusSourceGreen:
+        shader += "1.0 - ";
+        AppendSource(shader, source, index_name);
+        shader += ".g";
+        break;
+    case AlphaModifier::SourceBlue:
+        AppendSource(shader, source, index_name);
+        shader += ".b";
+        break;
+    case AlphaModifier::OneMinusSourceBlue:
+        shader += "1.0 - ";
+        AppendSource(shader, source, index_name);
+        shader += ".b";
+        break;
+    default:
+        shader += "vec3(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier);
+        break;
+    }
+}
+
+static void AppendColorCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation,
+        const std::string& variable_name) {
+    using Operation = Pica::Regs::TevStageConfig::Operation;
+
+    switch (operation) {
+    case Operation::Replace:
+        shader += variable_name + "[0]";
+        break;
+    case Operation::Modulate:
+        shader += variable_name + "[0] * " + variable_name + "[1]";
+        break;
+    case Operation::Add:
+        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
+        break;
+    case Operation::AddSigned:
+        shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
+        break;
+    case Operation::Lerp:
+        shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
+        break;
+    case Operation::Subtract:
+        shader += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
+        break;
+    case Operation::MultiplyThenAdd:
+        shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
+        break;
+    case Operation::AddThenMultiply:
+        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
+        break;
+    default:
+        shader += "vec3(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown color comb op %u", operation);
+        break;
+    }
+}
+
+static void AppendAlphaCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation,
+        const std::string& variable_name) {
+    using Operation = Pica::Regs::TevStageConfig::Operation;
+    switch (operation) {
+    case Operation::Replace:
+        shader += variable_name + "[0]";
+        break;
+    case Operation::Modulate:
+        shader += variable_name + "[0] * " + variable_name + "[1]";
+        break;
+    case Operation::Add:
+        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+        break;
+    case Operation::AddSigned:
+        shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
+        break;
+    case Operation::Lerp:
+        shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
+        break;
+    case Operation::Subtract:
+        shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+        break;
+    case Operation::MultiplyThenAdd:
+        shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+        break;
+    case Operation::AddThenMultiply:
+        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
+        break;
+    default:
+        shader += "0.0";
+        LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner op %u", operation);
+        break;
+    }
+}
+
+static void AppendAlphaTestCondition(std::string& shader, Pica::Regs::CompareFunc func) {
+    using CompareFunc = Pica::Regs::CompareFunc;
+    switch (func) {
+    case CompareFunc::Never:
+        shader += "true";
+        break;
+    case CompareFunc::Always:
+        shader += "false";
+        break;
+    case CompareFunc::Equal:
+        shader += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
+        break;
+    case CompareFunc::NotEqual:
+        shader += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
+        break;
+    case CompareFunc::LessThan:
+        shader += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
+        break;
+    case CompareFunc::LessThanOrEqual:
+        shader += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
+        break;
+    case CompareFunc::GreaterThan:
+        shader += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
+        break;
+    case CompareFunc::GreaterThanOrEqual:
+        shader += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
+        break;
+    default:
+        shader += "false";
+        LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func);
+        break;
+    }
+}
+
+std::string GenerateFragmentShader(const ShaderCacheKey& config) {
+    std::string shader = R"(
+#version 150 core
+
+#define NUM_VTX_ATTR 7
+#define NUM_TEV_STAGES 6
+
+in vec4 o[NUM_VTX_ATTR];
+out vec4 color;
+
+uniform int alphatest_ref;
+uniform vec4 const_color[NUM_TEV_STAGES];
+uniform sampler2D tex[3];
+
+uniform vec4 tev_combiner_buffer_color;
+
+void main(void) {
+vec4 g_combiner_buffer = tev_combiner_buffer_color;
+vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
+)";
+
+    // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
+    if (config.alpha_test_func == Pica::Regs::CompareFunc::Never) {
+        shader += "discard;";
+        return shader;
+    }
+
+    auto& tev_stages = config.tev_stages;
+    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
+        auto& tev_stage = tev_stages[tev_stage_index];
+        if (!IsPassThroughTevStage(tev_stage)) {
+            std::string index_name = std::to_string(tev_stage_index);
+
+            shader += "vec3 color_results_" + index_name + "[3] = vec3[3](";
+            AppendColorModifier(shader, tev_stage.color_modifier1, tev_stage.color_source1, index_name);
+            shader += ", ";
+            AppendColorModifier(shader, tev_stage.color_modifier2, tev_stage.color_source2, index_name);
+            shader += ", ";
+            AppendColorModifier(shader, tev_stage.color_modifier3, tev_stage.color_source3, index_name);
+            shader += ");\n";
+
+            shader += "vec3 color_output_" + index_name + " = ";
+            AppendColorCombiner(shader, tev_stage.color_op, "color_results_" + index_name);
+            shader += ";\n";
+
+            shader += "float alpha_results_" + index_name + "[3] = float[3](";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier1, tev_stage.alpha_source1, index_name);
+            shader += ", ";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier2, tev_stage.alpha_source2, index_name);
+            shader += ", ";
+            AppendAlphaModifier(shader, tev_stage.alpha_modifier3, tev_stage.alpha_source3, index_name);
+            shader += ");\n";
+
+            shader += "float alpha_output_" + index_name + " = ";
+            AppendAlphaCombiner(shader, tev_stage.alpha_op, "alpha_results_" + index_name);
+            shader += ";\n";
+
+            shader += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " + std::to_string(tev_stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " + std::to_string(tev_stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
+        }
+
+        if (config.TevStageUpdatesCombinerBufferColor(tev_stage_index))
+            shader += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
+
+        if (config.TevStageUpdatesCombinerBufferAlpha(tev_stage_index))
+            shader += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
+    }
+
+    if (config.alpha_test_func != Pica::Regs::CompareFunc::Always) {
+        shader += "if (";
+        AppendAlphaTestCondition(shader, config.alpha_test_func);
+        shader += ") {\n discard;\n }\n";
+    }
+
+    shader += "color = g_last_tex_env_out;\n}";
+    return shader;
+}
+
+std::string GenerateVertexShader() {
+    static const std::string shader_str = R"(
+#version 150 core
+
+#define NUM_VTX_ATTR 7
+
+in vec4 vert_position;
+in vec4 vert_color;
+in vec2 vert_texcoords0;
+in vec2 vert_texcoords1;
+in vec2 vert_texcoords2;
+
+out vec4 o[NUM_VTX_ATTR];
+
+void main() {
+    o[2] = vert_color;
+    o[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
+    o[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
+
+    gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
+}
+)";
+    return shader_str;
+}
+
+} // namespace GLShaderGen
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
new file mode 100644
index 000000000..7fd18de6d
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -0,0 +1,17 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include "video_core/renderer_opengl/gl_rasterizer.h"
+
+namespace GLShader {
+
+std::string GenerateVertexShader();
+
+std::string GenerateFragmentShader(const ShaderCacheKey& config);
+
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 8a6a51ad4..ce218b857 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -2,22 +2,15 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-
-#include "gl_shader_util.h"
-#include "gl_rasterizer.h"
-#include "common/logging/log.h"
-
-#include "video_core/pica.h"
-
 #include <algorithm>
 #include <vector>
 
 #include "common/logging/log.h"
 #include "video_core/renderer_opengl/gl_shader_util.h"
 
-namespace ShaderUtil {
+namespace GLShader {
 
-GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) {
+GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
 
     // Create the shaders
     GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
@@ -101,339 +94,4 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) {
     return program_id;
 }
 
-}
-
-namespace ShaderCache
-{
-
-static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
-    return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-            stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-            stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-            stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-            stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor &&
-            stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha &&
-            stage.GetColorMultiplier() == 1 &&
-            stage.GetAlphaMultiplier() == 1);
-}
-
-void AppendSource(std::string& shader, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using Source = Pica::Regs::TevStageConfig::Source;
-    switch (source) {
-    case Source::PrimaryColor:
-        shader += "o[2]";
-        break;
-    case Source::PrimaryFragmentColor:
-        // HACK: Until we implement fragment lighting, use primary_color
-        shader += "o[2]";
-        break;
-    case Source::SecondaryFragmentColor:
-        // HACK: Until we implement fragment lighting, use zero
-        shader += "vec4(0.0, 0.0, 0.0, 0.0)";
-        break;
-    case Source::Texture0:
-        shader += "texture(tex[0], o[3].xy)";
-        break;
-    case Source::Texture1:
-        shader += "texture(tex[1], o[3].zw)";
-        break;
-    case Source::Texture2: // TODO: Unverified
-        shader += "texture(tex[2], o[5].zw)";
-        break;
-    case Source::PreviousBuffer:
-        shader += "g_combiner_buffer";
-        break;
-    case Source::Constant:
-        shader += "const_color[" + index_name + "]";
-        break;
-    case Source::Previous:
-        shader += "g_last_tex_env_out";
-        break;
-    default:
-        shader += "vec4(0.0)";
-        LOG_CRITICAL(Render_OpenGL, "Unknown source op %u", source);
-        break;
-    }
-}
-
-void AppendColorModifier(std::string& shader, Pica::Regs::TevStageConfig::ColorModifier modifier, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
-    switch (modifier) {
-        case ColorModifier::SourceColor:
-            AppendSource(shader, source, index_name);
-            shader += ".rgb";
-            break;
-        case ColorModifier::OneMinusSourceColor:
-            shader += "vec3(1.0) - ";
-            AppendSource(shader, source, index_name);
-            shader += ".rgb";
-            break;
-        case ColorModifier::SourceAlpha:
-            AppendSource(shader, source, index_name);
-            shader += ".aaa";
-            break;
-        case ColorModifier::OneMinusSourceAlpha:
-            shader += "vec3(1.0) - ";
-            AppendSource(shader, source, index_name);
-            shader += ".aaa";
-            break;
-        case ColorModifier::SourceRed:
-            AppendSource(shader, source, index_name);
-            shader += ".rrr";
-            break;
-        case ColorModifier::OneMinusSourceRed:
-            shader += "vec3(1.0) - ";
-            AppendSource(shader, source, index_name);
-            shader += ".rrr";
-            break;
-        case ColorModifier::SourceGreen:
-            AppendSource(shader, source, index_name);
-            shader += ".ggg";
-            break;
-        case ColorModifier::OneMinusSourceGreen:
-            shader += "vec3(1.0) - ";
-            AppendSource(shader, source, index_name);
-            shader += ".ggg";
-            break;
-        case ColorModifier::SourceBlue:
-            AppendSource(shader, source, index_name);
-            shader += ".bbb";
-            break;
-        case ColorModifier::OneMinusSourceBlue:
-            shader += "vec3(1.0) - ";
-            AppendSource(shader, source, index_name);
-            shader += ".bbb";
-            break;
-        default:
-            shader += "vec3(0.0)";
-            LOG_CRITICAL(Render_OpenGL, "Unknown color modifier op %u", modifier);
-            break;
-    }
-}
-
-void AppendAlphaModifier(std::string& shader, Pica::Regs::TevStageConfig::AlphaModifier modifier, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
-    switch (modifier) {
-        case AlphaModifier::SourceAlpha:
-            AppendSource(shader, source, index_name);
-            shader += ".a";
-            break;
-        case AlphaModifier::OneMinusSourceAlpha:
-            shader += "1.0 - ";
-            AppendSource(shader, source, index_name);
-            shader += ".a";
-            break;
-        case AlphaModifier::SourceRed:
-            AppendSource(shader, source, index_name);
-            shader += ".r";
-            break;
-        case AlphaModifier::OneMinusSourceRed:
-            shader += "1.0 - ";
-            AppendSource(shader, source, index_name);
-            shader += ".r";
-            break;
-        case AlphaModifier::SourceGreen:
-            AppendSource(shader, source, index_name);
-            shader += ".g";
-            break;
-        case AlphaModifier::OneMinusSourceGreen:
-            shader += "1.0 - ";
-            AppendSource(shader, source, index_name);
-            shader += ".g";
-            break;
-        case AlphaModifier::SourceBlue:
-            AppendSource(shader, source, index_name);
-            shader += ".b";
-            break;
-        case AlphaModifier::OneMinusSourceBlue:
-            shader += "1.0 - ";
-            AppendSource(shader, source, index_name);
-            shader += ".b";
-            break;
-        default:
-            shader += "vec3(0.0)";
-            LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier);
-            break;
-    }
-}
-
-void AppendColorCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation, const std::string& variable_name) {
-    using Operation = Pica::Regs::TevStageConfig::Operation;
-
-    switch (operation) {
-        case Operation::Replace:
-            shader += variable_name + "[0]";
-            break;
-        case Operation::Modulate:
-            shader += variable_name + "[0] * " + variable_name + "[1]";
-            break;
-        case Operation::Add:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
-            break;
-        case Operation::AddSigned:
-            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
-            break;
-        case Operation::Lerp:
-            shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
-            break;
-        case Operation::Subtract:
-            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
-            break;
-        case Operation::MultiplyThenAdd:
-            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
-            break;
-        case Operation::AddThenMultiply:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
-            break;
-        default:
-            shader += "vec3(0.0)";
-            LOG_CRITICAL(Render_OpenGL, "Unknown color comb op %u", operation);
-            break;
-    }
-}
-
-void AppendAlphaCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation, const std::string& variable_name) {
-    using Operation = Pica::Regs::TevStageConfig::Operation;
-    switch (operation) {
-        case Operation::Replace:
-            shader += variable_name + "[0]";
-            break;
-        case Operation::Modulate:
-            shader += variable_name + "[0] * " + variable_name + "[1]";
-            break;
-        case Operation::Add:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
-            break;
-        case Operation::AddSigned:
-            shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
-            break;
-        case Operation::Lerp:
-            shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
-            break;
-        case Operation::Subtract:
-            shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
-            break;
-        case Operation::MultiplyThenAdd:
-            shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
-            break;
-        case Operation::AddThenMultiply:
-            shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
-            break;
-        default:
-            shader += "0.0";
-            LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner op %u", operation);
-            break;
-    }
-}
-
-void AppendAlphaTestCondition(std::string& shader, Pica::Regs::CompareFunc func) {
-    using CompareFunc = Pica::Regs::CompareFunc;
-    switch (func) {
-        case CompareFunc::Never:
-            shader += "true";
-            break;
-        case CompareFunc::Always:
-            shader += "false";
-            break;
-        case CompareFunc::Equal:
-            shader += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
-            break;
-        case CompareFunc::NotEqual:
-            shader += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
-            break;
-        case CompareFunc::LessThan:
-            shader += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
-            break;
-        case CompareFunc::LessThanOrEqual:
-            shader += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
-            break;
-        case CompareFunc::GreaterThan:
-            shader += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
-            break;
-        case CompareFunc::GreaterThanOrEqual:
-            shader += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
-            break;
-        default:
-            shader += "false";
-            LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func);
-            break;
-    }
-}
-
-std::string GenerateFragmentShader(const ShaderCacheKey& config) {
-    std::string shader = R"(
-#version 150 core
-
-#define NUM_VTX_ATTR 7
-#define NUM_TEV_STAGES 6
-
-in vec4 o[NUM_VTX_ATTR];
-out vec4 color;
-
-uniform int alphatest_ref;
-uniform vec4 const_color[NUM_TEV_STAGES];
-uniform sampler2D tex[3];
-
-uniform vec4 tev_combiner_buffer_color;
-
-void main(void) {
-    vec4 g_combiner_buffer = tev_combiner_buffer_color;
-    vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
-)";
-
-    // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
-    if (config.alpha_test_func == Pica::Regs::CompareFunc::Never) {
-        shader += "discard;";
-        return shader;
-    }
-
-    auto& tev_stages = config.tev_stages;
-    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
-        auto& tev_stage = tev_stages[tev_stage_index];
-        if (!IsPassThroughTevStage(tev_stage)) {
-            std::string index_name = std::to_string(tev_stage_index);
-
-            shader += "vec3 color_results_" + index_name + "[3] = vec3[3](";
-            AppendColorModifier(shader, tev_stage.color_modifier1, tev_stage.color_source1, index_name);
-            shader += ", ";
-            AppendColorModifier(shader, tev_stage.color_modifier2, tev_stage.color_source2, index_name);
-            shader += ", ";
-            AppendColorModifier(shader, tev_stage.color_modifier3, tev_stage.color_source3, index_name);
-            shader += ");\n";
-
-            shader += "vec3 color_output_" + index_name + " = ";
-            AppendColorCombiner(shader, tev_stage.color_op, "color_results_" + index_name);
-            shader += ";\n";
-
-            shader += "float alpha_results_" + index_name + "[3] = float[3](";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier1, tev_stage.alpha_source1, index_name);
-            shader += ", ";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier2, tev_stage.alpha_source2, index_name);
-            shader += ", ";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier3, tev_stage.alpha_source3, index_name);
-            shader += ");\n";
-
-            shader += "float alpha_output_" + index_name + " = ";
-            AppendAlphaCombiner(shader, tev_stage.alpha_op, "alpha_results_" + index_name);
-            shader += ";\n";
-
-            shader += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " + std::to_string(tev_stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " + std::to_string(tev_stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
-        }
-
-        if (config.TevStageUpdatesCombinerBufferColor(tev_stage_index))
-            shader += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
-
-        if (config.TevStageUpdatesCombinerBufferAlpha(tev_stage_index))
-            shader += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
-    }
-
-    if (config.alpha_test_func != Pica::Regs::CompareFunc::Always) {
-        shader += "if (";
-        AppendAlphaTestCondition(shader, config.alpha_test_func);
-        shader += ") {\n discard;\n }\n";
-    }
-
-    shader += "color = g_last_tex_env_out;\n}";
-    return shader;
-}
-}
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index ca62c83ba..6e2d007f8 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -6,7 +6,7 @@
 
 #include <glad/glad.h>
 
-namespace ShaderUtil {
+namespace GLShader {
 
 enum Attributes {
     ATTRIBUTE_POSITION  = 0,
@@ -14,6 +14,6 @@ enum Attributes {
     ATTRIBUTE_TEXCOORDS = 2,
 };
 
-GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path);
+GLuint LoadProgram(const char* vertex_file_path, const char* fragment_file_path);
 
-}
+} // namespace
diff --git a/src/video_core/renderer_opengl/gl_shaders.h b/src/video_core/renderer_opengl/gl_shaders.h
deleted file mode 100644
index 2ba2c6b0f..000000000
--- a/src/video_core/renderer_opengl/gl_shaders.h
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-namespace GLShaders {
-
-const char g_vertex_shader[] = R"(
-#version 150 core
-
-in vec2 vert_position;
-in vec2 vert_tex_coord;
-out vec2 frag_tex_coord;
-
-// This is a truncated 3x3 matrix for 2D transformations:
-// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
-// The third column performs translation.
-// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
-// implicitly be [0, 0, 1]
-uniform mat3x2 modelview_matrix;
-
-void main() {
-    // Multiply input position by the rotscale part of the matrix and then manually translate by
-    // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
-    // to `vec3(vert_position.xy, 1.0)`
-    gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
-    frag_tex_coord = vert_tex_coord;
-}
-)";
-
-const char g_fragment_shader[] = R"(
-#version 150 core
-
-in vec2 frag_tex_coord;
-out vec4 color;
-
-uniform sampler2D color_texture;
-
-void main() {
-    color = texture(color_texture, frag_tex_coord);
-}
-)";
-
-const char g_vertex_shader_hw[] = R"(
-#version 150 core
-
-#define NUM_VTX_ATTR 7
-
-in vec4 vert_position;
-in vec4 vert_color;
-in vec2 vert_texcoords0;
-in vec2 vert_texcoords1;
-in vec2 vert_texcoords2;
-
-out vec4 o[NUM_VTX_ATTR];
-
-void main() {
-    o[2] = vert_color;
-    o[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
-    o[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
-
-    gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
-}
-)";
-
-// TODO: Create a shader constructor and cache that builds this program with minimal conditionals instead of using tev_cfg uniforms
-const char g_fragment_shader_hw[] = R"(
-#version 150 core
-
-#define NUM_VTX_ATTR 7
-#define NUM_TEV_STAGES 6
-
-#define SOURCE_PRIMARYCOLOR           0x0
-#define SOURCE_PRIMARYFRAGMENTCOLOR   0x1
-#define SOURCE_SECONDARYFRAGMENTCOLOR 0x2
-#define SOURCE_TEXTURE0               0x3
-#define SOURCE_TEXTURE1               0x4
-#define SOURCE_TEXTURE2               0x5
-#define SOURCE_TEXTURE3               0x6
-#define SOURCE_PREVIOUSBUFFER         0xd
-#define SOURCE_CONSTANT               0xe
-#define SOURCE_PREVIOUS               0xf
-
-#define COLORMODIFIER_SOURCECOLOR         0x0
-#define COLORMODIFIER_ONEMINUSSOURCECOLOR 0x1
-#define COLORMODIFIER_SOURCEALPHA         0x2
-#define COLORMODIFIER_ONEMINUSSOURCEALPHA 0x3
-#define COLORMODIFIER_SOURCERED           0x4
-#define COLORMODIFIER_ONEMINUSSOURCERED   0x5
-#define COLORMODIFIER_SOURCEGREEN         0x8
-#define COLORMODIFIER_ONEMINUSSOURCEGREEN 0x9
-#define COLORMODIFIER_SOURCEBLUE          0xc
-#define COLORMODIFIER_ONEMINUSSOURCEBLUE  0xd
-
-#define ALPHAMODIFIER_SOURCEALPHA         0x0
-#define ALPHAMODIFIER_ONEMINUSSOURCEALPHA 0x1
-#define ALPHAMODIFIER_SOURCERED           0x2
-#define ALPHAMODIFIER_ONEMINUSSOURCERED   0x3
-#define ALPHAMODIFIER_SOURCEGREEN         0x4
-#define ALPHAMODIFIER_ONEMINUSSOURCEGREEN 0x5
-#define ALPHAMODIFIER_SOURCEBLUE          0x6
-#define ALPHAMODIFIER_ONEMINUSSOURCEBLUE  0x7
-
-#define OPERATION_REPLACE         0
-#define OPERATION_MODULATE        1
-#define OPERATION_ADD             2
-#define OPERATION_ADDSIGNED       3
-#define OPERATION_LERP            4
-#define OPERATION_SUBTRACT        5
-#define OPERATION_MULTIPLYTHENADD 8
-#define OPERATION_ADDTHENMULTIPLY 9
-
-#define COMPAREFUNC_NEVER              0
-#define COMPAREFUNC_ALWAYS             1
-#define COMPAREFUNC_EQUAL              2
-#define COMPAREFUNC_NOTEQUAL           3
-#define COMPAREFUNC_LESSTHAN           4
-#define COMPAREFUNC_LESSTHANOREQUAL    5
-#define COMPAREFUNC_GREATERTHAN        6
-#define COMPAREFUNC_GREATERTHANOREQUAL 7
-
-in vec4 o[NUM_VTX_ATTR];
-out vec4 color;
-
-uniform bool alphatest_enabled;
-uniform int alphatest_func;
-uniform float alphatest_ref;
-
-uniform sampler2D tex[3];
-
-uniform vec4 tev_combiner_buffer_color;
-
-struct TEVConfig
-{
-    bool enabled;
-    ivec3 color_sources;
-    ivec3 alpha_sources;
-    ivec3 color_modifiers;
-    ivec3 alpha_modifiers;
-    ivec2 color_alpha_op;
-    ivec2 color_alpha_multiplier;
-    vec4 const_color;
-    bvec2 updates_combiner_buffer_color_alpha;
-};
-
-uniform TEVConfig tev_cfgs[NUM_TEV_STAGES];
-
-vec4 g_combiner_buffer;
-vec4 g_last_tex_env_out;
-vec4 g_const_color;
-
-vec4 GetSource(int source) {
-    if (source == SOURCE_PRIMARYCOLOR) {
-        return o[2];
-    } else if (source == SOURCE_PRIMARYFRAGMENTCOLOR) {
-        // HACK: Until we implement fragment lighting, use primary_color
-        return o[2];
-    } else if (source == SOURCE_SECONDARYFRAGMENTCOLOR) {
-        // HACK: Until we implement fragment lighting, use zero
-        return vec4(0.0, 0.0, 0.0, 0.0);
-    } else if (source == SOURCE_TEXTURE0) {
-        return texture(tex[0], o[3].xy);
-    } else if (source == SOURCE_TEXTURE1) {
-        return texture(tex[1], o[3].zw);
-    } else if (source == SOURCE_TEXTURE2) {
-        // TODO: Unverified
-        return texture(tex[2], o[5].zw);
-    } else if (source == SOURCE_TEXTURE3) {
-        // TODO: no 4th texture?
-    } else if (source == SOURCE_PREVIOUSBUFFER) {
-        return g_combiner_buffer;
-    } else if (source == SOURCE_CONSTANT) {
-        return g_const_color;
-    } else if (source == SOURCE_PREVIOUS) {
-        return g_last_tex_env_out;
-    }
-
-    return vec4(0.0);
-}
-
-vec3 GetColorModifier(int factor, vec4 color) {
-    if (factor == COLORMODIFIER_SOURCECOLOR) {
-        return color.rgb;
-    } else if (factor == COLORMODIFIER_ONEMINUSSOURCECOLOR) {
-        return vec3(1.0) - color.rgb;
-    } else if (factor == COLORMODIFIER_SOURCEALPHA) {
-        return color.aaa;
-    } else if (factor == COLORMODIFIER_ONEMINUSSOURCEALPHA) {
-        return vec3(1.0) - color.aaa;
-    } else if (factor == COLORMODIFIER_SOURCERED) {
-        return color.rrr;
-    } else if (factor == COLORMODIFIER_ONEMINUSSOURCERED) {
-        return vec3(1.0) - color.rrr;
-    } else if (factor == COLORMODIFIER_SOURCEGREEN) {
-        return color.ggg;
-    } else if (factor == COLORMODIFIER_ONEMINUSSOURCEGREEN) {
-        return vec3(1.0) - color.ggg;
-    } else if (factor == COLORMODIFIER_SOURCEBLUE) {
-        return color.bbb;
-    } else if (factor == COLORMODIFIER_ONEMINUSSOURCEBLUE) {
-        return vec3(1.0) - color.bbb;
-    }
-
-    return vec3(0.0);
-}
-
-float GetAlphaModifier(int factor, vec4 color) {
-    if (factor == ALPHAMODIFIER_SOURCEALPHA) {
-        return color.a;
-    } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEALPHA) {
-        return 1.0 - color.a;
-    } else if (factor == ALPHAMODIFIER_SOURCERED) {
-        return color.r;
-    } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCERED) {
-        return 1.0 - color.r;
-    } else if (factor == ALPHAMODIFIER_SOURCEGREEN) {
-        return color.g;
-    } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEGREEN) {
-        return 1.0 - color.g;
-    } else if (factor == ALPHAMODIFIER_SOURCEBLUE) {
-        return color.b;
-    } else if (factor == ALPHAMODIFIER_ONEMINUSSOURCEBLUE) {
-        return 1.0 - color.b;
-    }
-
-    return 0.0;
-}
-
-vec3 ColorCombine(int op, vec3 color[3]) {
-    if (op == OPERATION_REPLACE) {
-        return color[0];
-    } else if (op == OPERATION_MODULATE) {
-        return color[0] * color[1];
-    } else if (op == OPERATION_ADD) {
-        return min(color[0] + color[1], 1.0);
-    } else if (op == OPERATION_ADDSIGNED) {
-        return clamp(color[0] + color[1] - vec3(0.5), 0.0, 1.0);
-    } else if (op == OPERATION_LERP) {
-        return color[0] * color[2] + color[1] * (vec3(1.0) - color[2]);
-    } else if (op == OPERATION_SUBTRACT) {
-        return max(color[0] - color[1], 0.0);
-    } else if (op == OPERATION_MULTIPLYTHENADD) {
-        return min(color[0] * color[1] + color[2], 1.0);
-    } else if (op == OPERATION_ADDTHENMULTIPLY) {
-        return min(color[0] + color[1], 1.0) * color[2];
-    }
-
-    return vec3(0.0);
-}
-
-float AlphaCombine(int op, float alpha[3]) {
-    if (op == OPERATION_REPLACE) {
-        return alpha[0];
-    } else if (op == OPERATION_MODULATE) {
-        return alpha[0] * alpha[1];
-    } else if (op == OPERATION_ADD) {
-        return min(alpha[0] + alpha[1], 1.0);
-    } else if (op == OPERATION_ADDSIGNED) {
-        return clamp(alpha[0] + alpha[1] - 0.5, 0.0, 1.0);
-    } else if (op == OPERATION_LERP) {
-        return alpha[0] * alpha[2] + alpha[1] * (1.0 - alpha[2]);
-    } else if (op == OPERATION_SUBTRACT) {
-        return max(alpha[0] - alpha[1], 0.0);
-    } else if (op == OPERATION_MULTIPLYTHENADD) {
-        return min(alpha[0] * alpha[1] + alpha[2], 1.0);
-    } else if (op == OPERATION_ADDTHENMULTIPLY) {
-        return min(alpha[0] + alpha[1], 1.0) * alpha[2];
-    }
-
-    return 0.0;
-}
-
-void main(void) {
-    g_combiner_buffer = tev_combiner_buffer_color;
-
-    for (int tex_env_idx = 0; tex_env_idx < NUM_TEV_STAGES; ++tex_env_idx) {
-        if (tev_cfgs[tex_env_idx].enabled) {
-            g_const_color = tev_cfgs[tex_env_idx].const_color;
-
-            vec3 color_results[3] = vec3[3](GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.x, GetSource(tev_cfgs[tex_env_idx].color_sources.x)),
-                                            GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.y, GetSource(tev_cfgs[tex_env_idx].color_sources.y)),
-                                            GetColorModifier(tev_cfgs[tex_env_idx].color_modifiers.z, GetSource(tev_cfgs[tex_env_idx].color_sources.z)));
-            vec3 color_output = ColorCombine(tev_cfgs[tex_env_idx].color_alpha_op.x, color_results);
-
-            float alpha_results[3] = float[3](GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.x, GetSource(tev_cfgs[tex_env_idx].alpha_sources.x)),
-                                              GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.y, GetSource(tev_cfgs[tex_env_idx].alpha_sources.y)),
-                                              GetAlphaModifier(tev_cfgs[tex_env_idx].alpha_modifiers.z, GetSource(tev_cfgs[tex_env_idx].alpha_sources.z)));
-            float alpha_output = AlphaCombine(tev_cfgs[tex_env_idx].color_alpha_op.y, alpha_results);
-
-            g_last_tex_env_out = vec4(min(color_output * tev_cfgs[tex_env_idx].color_alpha_multiplier.x, 1.0), min(alpha_output * tev_cfgs[tex_env_idx].color_alpha_multiplier.y, 1.0));
-        }
-
-        if (tev_cfgs[tex_env_idx].updates_combiner_buffer_color_alpha.x) {
-            g_combiner_buffer.rgb = g_last_tex_env_out.rgb;
-        }
-
-        if (tev_cfgs[tex_env_idx].updates_combiner_buffer_color_alpha.y) {
-            g_combiner_buffer.a = g_last_tex_env_out.a;
-        }
-    }
-
-    if (alphatest_enabled) {
-        if (alphatest_func == COMPAREFUNC_NEVER) {
-            discard;
-        } else if (alphatest_func == COMPAREFUNC_ALWAYS) {
-
-        } else if (alphatest_func == COMPAREFUNC_EQUAL) {
-            if (g_last_tex_env_out.a != alphatest_ref) {
-                discard;
-            }
-        } else if (alphatest_func == COMPAREFUNC_NOTEQUAL) {
-            if (g_last_tex_env_out.a == alphatest_ref) {
-                discard;
-            }
-        } else if (alphatest_func == COMPAREFUNC_LESSTHAN) {
-            if (g_last_tex_env_out.a >= alphatest_ref) {
-                discard;
-            }
-        } else if (alphatest_func == COMPAREFUNC_LESSTHANOREQUAL) {
-            if (g_last_tex_env_out.a > alphatest_ref) {
-                discard;
-            }
-        } else if (alphatest_func == COMPAREFUNC_GREATERTHAN) {
-            if (g_last_tex_env_out.a <= alphatest_ref) {
-                discard;
-            }
-        } else if (alphatest_func == COMPAREFUNC_GREATERTHANOREQUAL) {
-            if (g_last_tex_env_out.a < alphatest_ref) {
-                discard;
-            }
-        }
-    }
-
-    color = g_last_tex_env_out;
-}
-)";
-
-}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index f1313b54f..ac0a058db 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -21,9 +21,44 @@
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_util.h"
-#include "video_core/renderer_opengl/gl_shaders.h"
 #include "video_core/renderer_opengl/renderer_opengl.h"
 
+static const char vertex_shader[] = R"(
+#version 150 core
+
+in vec2 vert_position;
+in vec2 vert_tex_coord;
+out vec2 frag_tex_coord;
+
+// This is a truncated 3x3 matrix for 2D transformations:
+// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
+// The third column performs translation.
+// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
+// implicitly be [0, 0, 1]
+uniform mat3x2 modelview_matrix;
+
+void main() {
+    // Multiply input position by the rotscale part of the matrix and then manually translate by
+    // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
+    // to `vec3(vert_position.xy, 1.0)`
+    gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
+    frag_tex_coord = vert_tex_coord;
+}
+)";
+
+static const char fragment_shader[] = R"(
+#version 150 core
+
+in vec2 frag_tex_coord;
+out vec4 color;
+
+uniform sampler2D color_texture;
+
+void main() {
+    color = texture(color_texture, frag_tex_coord);
+}
+)";
+
 /**
  * Vertex structure that the drawn screen rectangles are composed of.
  */
@@ -207,7 +242,7 @@ void RendererOpenGL::InitOpenGLObjects() {
     glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f);
 
     // Link shaders and get variable locations
-    program_id = ShaderUtil::LoadShaders(GLShaders::g_vertex_shader, GLShaders::g_fragment_shader);
+    program_id = GLShader::LoadProgram(vertex_shader, fragment_shader);
     uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix");
     uniform_color_texture = glGetUniformLocation(program_id, "color_texture");
     attrib_position = glGetAttribLocation(program_id, "vert_position");

From a74774257eafd37f2efae5b745930c3950a3aa04 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Tue, 6 Oct 2015 18:01:53 -0400
Subject: [PATCH 07/19] gl_shader_gen: Various cleanups + moved TEV stage
 generation to its own function.

---
 .../renderer_opengl/gl_shader_gen.cpp         | 331 +++++++++---------
 1 file changed, 170 insertions(+), 161 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 059f127af..4854c347e 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -6,268 +6,313 @@
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_gen.h"
 
+using Pica::Regs;
+using TevStageConfig = Regs::TevStageConfig;
+
 namespace GLShader {
 
-static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
-    return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-        stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-        stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-        stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-        stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor &&
-        stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha &&
-        stage.GetColorMultiplier() == 1 &&
-        stage.GetAlphaMultiplier() == 1);
+static bool IsPassThroughTevStage(const TevStageConfig& stage) {
+    return (stage.color_op             == TevStageConfig::Operation::Replace &&
+            stage.alpha_op             == TevStageConfig::Operation::Replace &&
+            stage.color_source1        == TevStageConfig::Source::Previous &&
+            stage.alpha_source1        == TevStageConfig::Source::Previous &&
+            stage.color_modifier1      == TevStageConfig::ColorModifier::SourceColor &&
+            stage.alpha_modifier1      == TevStageConfig::AlphaModifier::SourceAlpha &&
+            stage.GetColorMultiplier() == 1 &&
+            stage.GetAlphaMultiplier() == 1);
 }
 
-static void AppendSource(std::string& shader, Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using Source = Pica::Regs::TevStageConfig::Source;
+static void AppendSource(std::string& out, TevStageConfig::Source source,
+        const std::string& index_name) {
+    using Source = TevStageConfig::Source;
     switch (source) {
     case Source::PrimaryColor:
-        shader += "o[2]";
+        out += "o[2]";
         break;
     case Source::PrimaryFragmentColor:
         // HACK: Until we implement fragment lighting, use primary_color
-        shader += "o[2]";
+        out += "o[2]";
         break;
     case Source::SecondaryFragmentColor:
         // HACK: Until we implement fragment lighting, use zero
-        shader += "vec4(0.0, 0.0, 0.0, 0.0)";
+        out += "vec4(0.0, 0.0, 0.0, 0.0)";
         break;
     case Source::Texture0:
-        shader += "texture(tex[0], o[3].xy)";
+        out += "texture(tex[0], o[3].xy)";
         break;
     case Source::Texture1:
-        shader += "texture(tex[1], o[3].zw)";
+        out += "texture(tex[1], o[3].zw)";
         break;
     case Source::Texture2: // TODO: Unverified
-        shader += "texture(tex[2], o[5].zw)";
+        out += "texture(tex[2], o[5].zw)";
         break;
     case Source::PreviousBuffer:
-        shader += "g_combiner_buffer";
+        out += "g_combiner_buffer";
         break;
     case Source::Constant:
-        shader += "const_color[" + index_name + "]";
+        out += "const_color[" + index_name + "]";
         break;
     case Source::Previous:
-        shader += "g_last_tex_env_out";
+        out += "g_last_tex_env_out";
         break;
     default:
-        shader += "vec4(0.0)";
+        out += "vec4(0.0)";
         LOG_CRITICAL(Render_OpenGL, "Unknown source op %u", source);
         break;
     }
 }
 
-static void AppendColorModifier(std::string& shader, Pica::Regs::TevStageConfig::ColorModifier modifier,
-        Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
+static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier modifier,
+        TevStageConfig::Source source, const std::string& index_name) {
+    using ColorModifier = TevStageConfig::ColorModifier;
     switch (modifier) {
     case ColorModifier::SourceColor:
-        AppendSource(shader, source, index_name);
-        shader += ".rgb";
+        AppendSource(out, source, index_name);
+        out += ".rgb";
         break;
     case ColorModifier::OneMinusSourceColor:
-        shader += "vec3(1.0) - ";
-        AppendSource(shader, source, index_name);
-        shader += ".rgb";
+        out += "vec3(1.0) - ";
+        AppendSource(out, source, index_name);
+        out += ".rgb";
         break;
     case ColorModifier::SourceAlpha:
-        AppendSource(shader, source, index_name);
-        shader += ".aaa";
+        AppendSource(out, source, index_name);
+        out += ".aaa";
         break;
     case ColorModifier::OneMinusSourceAlpha:
-        shader += "vec3(1.0) - ";
-        AppendSource(shader, source, index_name);
-        shader += ".aaa";
+        out += "vec3(1.0) - ";
+        AppendSource(out, source, index_name);
+        out += ".aaa";
         break;
     case ColorModifier::SourceRed:
-        AppendSource(shader, source, index_name);
-        shader += ".rrr";
+        AppendSource(out, source, index_name);
+        out += ".rrr";
         break;
     case ColorModifier::OneMinusSourceRed:
-        shader += "vec3(1.0) - ";
-        AppendSource(shader, source, index_name);
-        shader += ".rrr";
+        out += "vec3(1.0) - ";
+        AppendSource(out, source, index_name);
+        out += ".rrr";
         break;
     case ColorModifier::SourceGreen:
-        AppendSource(shader, source, index_name);
-        shader += ".ggg";
+        AppendSource(out, source, index_name);
+        out += ".ggg";
         break;
     case ColorModifier::OneMinusSourceGreen:
-        shader += "vec3(1.0) - ";
-        AppendSource(shader, source, index_name);
-        shader += ".ggg";
+        out += "vec3(1.0) - ";
+        AppendSource(out, source, index_name);
+        out += ".ggg";
         break;
     case ColorModifier::SourceBlue:
-        AppendSource(shader, source, index_name);
-        shader += ".bbb";
+        AppendSource(out, source, index_name);
+        out += ".bbb";
         break;
     case ColorModifier::OneMinusSourceBlue:
-        shader += "vec3(1.0) - ";
-        AppendSource(shader, source, index_name);
-        shader += ".bbb";
+        out += "vec3(1.0) - ";
+        AppendSource(out, source, index_name);
+        out += ".bbb";
         break;
     default:
-        shader += "vec3(0.0)";
+        out += "vec3(0.0)";
         LOG_CRITICAL(Render_OpenGL, "Unknown color modifier op %u", modifier);
         break;
     }
 }
 
-static void AppendAlphaModifier(std::string& shader, Pica::Regs::TevStageConfig::AlphaModifier modifier,
-        Pica::Regs::TevStageConfig::Source source, const std::string& index_name) {
-    using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
+static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier modifier,
+        TevStageConfig::Source source, const std::string& index_name) {
+    using AlphaModifier = TevStageConfig::AlphaModifier;
     switch (modifier) {
     case AlphaModifier::SourceAlpha:
-        AppendSource(shader, source, index_name);
-        shader += ".a";
+        AppendSource(out, source, index_name);
+        out += ".a";
         break;
     case AlphaModifier::OneMinusSourceAlpha:
-        shader += "1.0 - ";
-        AppendSource(shader, source, index_name);
-        shader += ".a";
+        out += "1.0 - ";
+        AppendSource(out, source, index_name);
+        out += ".a";
         break;
     case AlphaModifier::SourceRed:
-        AppendSource(shader, source, index_name);
-        shader += ".r";
+        AppendSource(out, source, index_name);
+        out += ".r";
         break;
     case AlphaModifier::OneMinusSourceRed:
-        shader += "1.0 - ";
-        AppendSource(shader, source, index_name);
-        shader += ".r";
+        out += "1.0 - ";
+        AppendSource(out, source, index_name);
+        out += ".r";
         break;
     case AlphaModifier::SourceGreen:
-        AppendSource(shader, source, index_name);
-        shader += ".g";
+        AppendSource(out, source, index_name);
+        out += ".g";
         break;
     case AlphaModifier::OneMinusSourceGreen:
-        shader += "1.0 - ";
-        AppendSource(shader, source, index_name);
-        shader += ".g";
+        out += "1.0 - ";
+        AppendSource(out, source, index_name);
+        out += ".g";
         break;
     case AlphaModifier::SourceBlue:
-        AppendSource(shader, source, index_name);
-        shader += ".b";
+        AppendSource(out, source, index_name);
+        out += ".b";
         break;
     case AlphaModifier::OneMinusSourceBlue:
-        shader += "1.0 - ";
-        AppendSource(shader, source, index_name);
-        shader += ".b";
+        out += "1.0 - ";
+        AppendSource(out, source, index_name);
+        out += ".b";
         break;
     default:
-        shader += "vec3(0.0)";
+        out += "vec3(0.0)";
         LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier);
         break;
     }
 }
 
-static void AppendColorCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation,
+static void AppendColorCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
-    using Operation = Pica::Regs::TevStageConfig::Operation;
+    using Operation = TevStageConfig::Operation;
 
     switch (operation) {
     case Operation::Replace:
-        shader += variable_name + "[0]";
+        out += variable_name + "[0]";
         break;
     case Operation::Modulate:
-        shader += variable_name + "[0] * " + variable_name + "[1]";
+        out += variable_name + "[0] * " + variable_name + "[1]";
         break;
     case Operation::Add:
-        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
+        out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
         break;
     case Operation::AddSigned:
-        shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
+        out += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
         break;
     case Operation::Lerp:
-        shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
+        out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
         break;
     case Operation::Subtract:
-        shader += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
+        out += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
         break;
     case Operation::MultiplyThenAdd:
-        shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
+        out += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
         break;
     case Operation::AddThenMultiply:
-        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
+        out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
         break;
     default:
-        shader += "vec3(0.0)";
-        LOG_CRITICAL(Render_OpenGL, "Unknown color comb op %u", operation);
+        out += "vec3(0.0)";
+        LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation);
         break;
     }
 }
 
-static void AppendAlphaCombiner(std::string& shader, Pica::Regs::TevStageConfig::Operation operation,
+static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
-    using Operation = Pica::Regs::TevStageConfig::Operation;
+    using Operation = TevStageConfig::Operation;
     switch (operation) {
     case Operation::Replace:
-        shader += variable_name + "[0]";
+        out += variable_name + "[0]";
         break;
     case Operation::Modulate:
-        shader += variable_name + "[0] * " + variable_name + "[1]";
+        out += variable_name + "[0] * " + variable_name + "[1]";
         break;
     case Operation::Add:
-        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+        out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
         break;
     case Operation::AddSigned:
-        shader += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
+        out += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
         break;
     case Operation::Lerp:
-        shader += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
+        out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
         break;
     case Operation::Subtract:
-        shader += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+        out += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
         break;
     case Operation::MultiplyThenAdd:
-        shader += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+        out += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
         break;
     case Operation::AddThenMultiply:
-        shader += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
+        out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
         break;
     default:
-        shader += "0.0";
-        LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner op %u", operation);
+        out += "0.0";
+        LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner operation: %u", operation);
         break;
     }
 }
 
-static void AppendAlphaTestCondition(std::string& shader, Pica::Regs::CompareFunc func) {
-    using CompareFunc = Pica::Regs::CompareFunc;
+static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
+    using CompareFunc = Regs::CompareFunc;
     switch (func) {
     case CompareFunc::Never:
-        shader += "true";
+        out += "true";
         break;
     case CompareFunc::Always:
-        shader += "false";
+        out += "false";
         break;
     case CompareFunc::Equal:
-        shader += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
         break;
     case CompareFunc::NotEqual:
-        shader += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
         break;
     case CompareFunc::LessThan:
-        shader += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
         break;
     case CompareFunc::LessThanOrEqual:
-        shader += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
         break;
     case CompareFunc::GreaterThan:
-        shader += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
         break;
     case CompareFunc::GreaterThanOrEqual:
-        shader += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
+        out += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
         break;
     default:
-        shader += "false";
+        out += "false";
         LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func);
         break;
     }
 }
 
+static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsigned index) {
+    auto& stage = config.tev_stages[index];
+    if (!IsPassThroughTevStage(stage)) {
+        std::string index_name = std::to_string(index);
+
+        out += "vec3 color_results_" + index_name + "[3] = vec3[3](";
+        AppendColorModifier(out, stage.color_modifier1, stage.color_source1, index_name);
+        out += ", ";
+        AppendColorModifier(out, stage.color_modifier2, stage.color_source2, index_name);
+        out += ", ";
+        AppendColorModifier(out, stage.color_modifier3, stage.color_source3, index_name);
+        out += ");\n";
+
+        out += "vec3 color_output_" + index_name + " = ";
+        AppendColorCombiner(out, stage.color_op, "color_results_" + index_name);
+        out += ";\n";
+
+        out += "float alpha_results_" + index_name + "[3] = float[3](";
+        AppendAlphaModifier(out, stage.alpha_modifier1, stage.alpha_source1, index_name);
+        out += ", ";
+        AppendAlphaModifier(out, stage.alpha_modifier2, stage.alpha_source2, index_name);
+        out += ", ";
+        AppendAlphaModifier(out, stage.alpha_modifier3, stage.alpha_source3, index_name);
+        out += ");\n";
+
+        out += "float alpha_output_" + index_name + " = ";
+        AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name);
+        out += ";\n";
+
+        out += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " +
+            std::to_string(stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " +
+            std::to_string(stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
+    }
+
+    if (config.TevStageUpdatesCombinerBufferColor(index))
+        out += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
+
+    if (config.TevStageUpdatesCombinerBufferAlpha(index))
+        out += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
+}
+
 std::string GenerateFragmentShader(const ShaderCacheKey& config) {
-    std::string shader = R"(
+    std::string out = R"(
 #version 150 core
 
 #define NUM_VTX_ATTR 7
@@ -288,63 +333,27 @@ vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
 )";
 
     // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
-    if (config.alpha_test_func == Pica::Regs::CompareFunc::Never) {
-        shader += "discard;";
-        return shader;
+    if (config.alpha_test_func == Regs::CompareFunc::Never) {
+        out += "discard;";
+        return out;
     }
 
-    auto& tev_stages = config.tev_stages;
-    for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); ++tev_stage_index) {
-        auto& tev_stage = tev_stages[tev_stage_index];
-        if (!IsPassThroughTevStage(tev_stage)) {
-            std::string index_name = std::to_string(tev_stage_index);
-
-            shader += "vec3 color_results_" + index_name + "[3] = vec3[3](";
-            AppendColorModifier(shader, tev_stage.color_modifier1, tev_stage.color_source1, index_name);
-            shader += ", ";
-            AppendColorModifier(shader, tev_stage.color_modifier2, tev_stage.color_source2, index_name);
-            shader += ", ";
-            AppendColorModifier(shader, tev_stage.color_modifier3, tev_stage.color_source3, index_name);
-            shader += ");\n";
-
-            shader += "vec3 color_output_" + index_name + " = ";
-            AppendColorCombiner(shader, tev_stage.color_op, "color_results_" + index_name);
-            shader += ";\n";
-
-            shader += "float alpha_results_" + index_name + "[3] = float[3](";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier1, tev_stage.alpha_source1, index_name);
-            shader += ", ";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier2, tev_stage.alpha_source2, index_name);
-            shader += ", ";
-            AppendAlphaModifier(shader, tev_stage.alpha_modifier3, tev_stage.alpha_source3, index_name);
-            shader += ");\n";
-
-            shader += "float alpha_output_" + index_name + " = ";
-            AppendAlphaCombiner(shader, tev_stage.alpha_op, "alpha_results_" + index_name);
-            shader += ";\n";
-
-            shader += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " + std::to_string(tev_stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " + std::to_string(tev_stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
-        }
-
-        if (config.TevStageUpdatesCombinerBufferColor(tev_stage_index))
-            shader += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
-
-        if (config.TevStageUpdatesCombinerBufferAlpha(tev_stage_index))
-            shader += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
-    }
+    for (std::size_t index = 0; index < config.tev_stages.size(); ++index)
+        WriteTevStage(out, config, (unsigned)index);
 
-    if (config.alpha_test_func != Pica::Regs::CompareFunc::Always) {
-        shader += "if (";
-        AppendAlphaTestCondition(shader, config.alpha_test_func);
-        shader += ") {\n discard;\n }\n";
+    if (config.alpha_test_func != Regs::CompareFunc::Always) {
+        out += "if (";
+        AppendAlphaTestCondition(out, config.alpha_test_func);
+        out += ") {\n discard;\n }\n";
     }
 
-    shader += "color = g_last_tex_env_out;\n}";
-    return shader;
+    out += "color = g_last_tex_env_out;\n}";
+
+    return out;
 }
 
 std::string GenerateVertexShader() {
-    static const std::string shader_str = R"(
+    static const std::string out = R"(
 #version 150 core
 
 #define NUM_VTX_ATTR 7
@@ -365,7 +374,7 @@ void main() {
     gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
 }
 )";
-    return shader_str;
+    return out;
 }
 
-} // namespace GLShaderGen
+} // namespace GLShader

From 2a0a86f62998fa2a7a76ea3862b1de245132f85a Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Tue, 6 Oct 2015 18:10:32 -0400
Subject: [PATCH 08/19] gl_shader_util: Cleanup header file + add docstring.

---
 src/video_core/renderer_opengl/gl_shader_util.h | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 6e2d007f8..4d3791d50 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -14,6 +14,12 @@ enum Attributes {
     ATTRIBUTE_TEXCOORDS = 2,
 };
 
-GLuint LoadProgram(const char* vertex_file_path, const char* fragment_file_path);
+/**
+ * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)
+ * @param vertex_shader String of the GLSL vertex shader program
+ * @param fragment_shader String of the GLSL fragment shader program
+ * @returns Handle of the newly created OpenGL shader object
+ */
+GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader);
 
 } // namespace

From 4b5141954ebb2416c9de1996f155641cea46f60a Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Tue, 6 Oct 2015 18:21:28 -0400
Subject: [PATCH 09/19] gl_shader_gen: Add additional function documentation.

---
 src/video_core/renderer_opengl/gl_shader_gen.cpp |  8 ++++++++
 src/video_core/renderer_opengl/gl_shader_gen.h   | 10 ++++++++++
 2 files changed, 18 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 4854c347e..5093710d4 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -11,6 +11,7 @@ using TevStageConfig = Regs::TevStageConfig;
 
 namespace GLShader {
 
+/// 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 &&
             stage.alpha_op             == TevStageConfig::Operation::Replace &&
@@ -22,6 +23,7 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {
             stage.GetAlphaMultiplier() == 1);
 }
 
+/// Writes the specified TEV stage source component(s)
 static void AppendSource(std::string& out, TevStageConfig::Source source,
         const std::string& index_name) {
     using Source = TevStageConfig::Source;
@@ -62,6 +64,7 @@ static void AppendSource(std::string& out, TevStageConfig::Source source,
     }
 }
 
+/// Writes the color components to use for the specified TEV stage color modifier
 static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier modifier,
         TevStageConfig::Source source, const std::string& index_name) {
     using ColorModifier = TevStageConfig::ColorModifier;
@@ -118,6 +121,7 @@ static void AppendColorModifier(std::string& out, TevStageConfig::ColorModifier
     }
 }
 
+/// Writes the alpha component to use for the specified TEV stage alpha modifier
 static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier modifier,
         TevStageConfig::Source source, const std::string& index_name) {
     using AlphaModifier = TevStageConfig::AlphaModifier;
@@ -165,6 +169,7 @@ static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier
     }
 }
 
+/// Writes the combiner function for the color components for the specified TEV stage operation
 static void AppendColorCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
     using Operation = TevStageConfig::Operation;
@@ -201,6 +206,7 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper
     }
 }
 
+/// Writes the combiner function for the alpha component for the specified TEV stage operation
 static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
     using Operation = TevStageConfig::Operation;
@@ -236,6 +242,7 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper
     }
 }
 
+/// Writes the if-statement condition used to evaluate alpha testing
 static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
     using CompareFunc = Regs::CompareFunc;
     switch (func) {
@@ -270,6 +277,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
     }
 }
 
+/// Writes the code to emulate the specified TEV stage
 static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsigned index) {
     auto& stage = config.tev_stages[index];
     if (!IsPassThroughTevStage(stage)) {
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index 7fd18de6d..c8295e9e0 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -10,8 +10,18 @@
 
 namespace GLShader {
 
+/**
+ * Generates the GLSL vertex shader program source code for the current Pica state
+ * @returns String of the shader source code
+ */
 std::string GenerateVertexShader();
 
+/**
+ * Generates the GLSL fragment shader program source code for the current Pica state
+ * @param config ShaderCacheKey object generated for the current Pica state, used for the shader
+ *               configuration (NOTE: Use state in this struct only, not the Pica registers!)
+ * @returns String of the shader source code
+ */
 std::string GenerateFragmentShader(const ShaderCacheKey& config);
 
 } // namespace GLShader

From f2e7f7e1019eeffc0aee438f788449fc4e0c118f Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Tue, 6 Oct 2015 18:34:50 -0400
Subject: [PATCH 10/19] gl_rasterizer: Add documentation to ShaderCacheKey.

---
 src/video_core/renderer_opengl/gl_rasterizer.h | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index de9e4d22e..484579d82 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -17,6 +17,14 @@
 #include "video_core/renderer_opengl/gl_state.h"
 #include "video_core/shader/shader_interpreter.h"
 
+/**
+ * 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
+ * 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 ShaderCacheKey {
     using Regs = Pica::Regs;
 
@@ -36,6 +44,11 @@ struct ShaderCacheKey {
         return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
     }
 
+    /**
+     * This function is used to construct a ShaderCacheKey with the current Pica register
+     * configuration. Don't construct a ShaderCacheKey manually, instead call this function (and
+     * extend it as additional state needs to be captured to generate shaders).
+     */
     static ShaderCacheKey CurrentConfig() {
         const auto& regs = Pica::g_state.regs;
         ShaderCacheKey config;
@@ -43,6 +56,9 @@ struct ShaderCacheKey {
         config.alpha_test_func = regs.output_merger.alpha_test.enable ?
             regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
 
+        // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling
+        // the GetTevStages() function) because BitField explicitly disables copies.
+
         config.tev_stages[0].source_raw = regs.tev_stage0.source_raw;
         config.tev_stages[1].source_raw = regs.tev_stage1.source_raw;
         config.tev_stages[2].source_raw = regs.tev_stage2.source_raw;

From bd833b8dd8b872833a6867266da4d61f058e340f Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 7 Oct 2015 21:01:28 -0400
Subject: [PATCH 11/19] gl_shader_gen: Fix bug where TEV stage outputs should
 be clamped.

---
 src/video_core/renderer_opengl/gl_shader_gen.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 5093710d4..74f0d4805 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -307,9 +307,9 @@ static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsign
         AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name);
         out += ";\n";
 
-        out += "g_last_tex_env_out = vec4(min(color_output_" + index_name + " * " +
-            std::to_string(stage.GetColorMultiplier()) + ".0, 1.0), min(alpha_output_" + index_name + " * " +
-            std::to_string(stage.GetAlphaMultiplier()) + ".0, 1.0));\n";
+        out += "g_last_tex_env_out = vec4("
+            "clamp(color_output_" + index_name + " * " + std::to_string(stage.GetColorMultiplier()) + ".0, 0.0, 1.0),"
+            "clamp(alpha_output_" + index_name + " * " + std::to_string(stage.GetAlphaMultiplier()) + ".0, 0.0, 1.0));\n";
     }
 
     if (config.TevStageUpdatesCombinerBufferColor(index))

From c2c4faef4c03d0c7b026672f2862b0022e5dc9cd Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 7 Oct 2015 21:02:50 -0400
Subject: [PATCH 12/19] gl_shader_gen: AppendAlphaModifier default should be
 0.0, not vec4(0.0).

---
 src/video_core/renderer_opengl/gl_shader_gen.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 74f0d4805..b24c763f2 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -163,7 +163,7 @@ static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier
         out += ".b";
         break;
     default:
-        out += "vec3(0.0)";
+        out += "0.0";
         LOG_CRITICAL(Render_OpenGL, "Unknown alpha modifier op %u", modifier);
         break;
     }

From 5ef2df056d0c765093b00932ae240093543708fc Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 7 Oct 2015 21:11:32 -0400
Subject: [PATCH 13/19] gl_shader_gen: Rename 'o' to 'attr' in vertex/fragment
 shaders.

---
 .../renderer_opengl/gl_shader_gen.cpp         | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index b24c763f2..79c690e76 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -29,24 +29,24 @@ static void AppendSource(std::string& out, TevStageConfig::Source source,
     using Source = TevStageConfig::Source;
     switch (source) {
     case Source::PrimaryColor:
-        out += "o[2]";
+        out += "attr[2]";
         break;
     case Source::PrimaryFragmentColor:
         // HACK: Until we implement fragment lighting, use primary_color
-        out += "o[2]";
+        out += "attr[2]";
         break;
     case Source::SecondaryFragmentColor:
         // HACK: Until we implement fragment lighting, use zero
         out += "vec4(0.0, 0.0, 0.0, 0.0)";
         break;
     case Source::Texture0:
-        out += "texture(tex[0], o[3].xy)";
+        out += "texture(tex[0], attr[3].xy)";
         break;
     case Source::Texture1:
-        out += "texture(tex[1], o[3].zw)";
+        out += "texture(tex[1], attr[3].zw)";
         break;
     case Source::Texture2: // TODO: Unverified
-        out += "texture(tex[2], o[5].zw)";
+        out += "texture(tex[2], attr[5].zw)";
         break;
     case Source::PreviousBuffer:
         out += "g_combiner_buffer";
@@ -326,7 +326,7 @@ std::string GenerateFragmentShader(const ShaderCacheKey& config) {
 #define NUM_VTX_ATTR 7
 #define NUM_TEV_STAGES 6
 
-in vec4 o[NUM_VTX_ATTR];
+in vec4 attr[NUM_VTX_ATTR];
 out vec4 color;
 
 uniform int alphatest_ref;
@@ -342,7 +342,7 @@ vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
 
     // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
     if (config.alpha_test_func == Regs::CompareFunc::Never) {
-        out += "discard;";
+        out += "discard; }";
         return out;
     }
 
@@ -372,12 +372,12 @@ in vec2 vert_texcoords0;
 in vec2 vert_texcoords1;
 in vec2 vert_texcoords2;
 
-out vec4 o[NUM_VTX_ATTR];
+out vec4 attr[NUM_VTX_ATTR];
 
 void main() {
-    o[2] = vert_color;
-    o[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
-    o[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
+    attr[2] = vert_color;
+    attr[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
+    attr[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
 
     gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
 }

From 71edb55114af129d6f580e271ad5196043342abe Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Fri, 9 Oct 2015 19:32:38 -0400
Subject: [PATCH 14/19] gl_shader_gen: Require explicit uniform locations.

- Fixes uniform issue on AMD.
---
 .../renderer_opengl/gl_rasterizer.cpp         | 27 ++++---------
 .../renderer_opengl/gl_rasterizer.h           | 40 +++++++------------
 .../renderer_opengl/gl_shader_gen.cpp         | 23 ++++++-----
 3 files changed, 34 insertions(+), 56 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 4f9865230..64639ed26 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -476,20 +476,14 @@ void RasterizerOpenGL::SetShader() {
         std::unique_ptr<TEVShader> shader = Common::make_unique<TEVShader>();
 
         shader->shader.Create(GLShader::GenerateVertexShader().c_str(), GLShader::GenerateFragmentShader(config).c_str());
-        shader->uniform_alphatest_ref = glGetUniformLocation(shader->shader.handle, "alphatest_ref");
-        shader->uniform_tex = glGetUniformLocation(shader->shader.handle, "tex");
-        shader->uniform_tev_combiner_buffer_color = glGetUniformLocation(shader->shader.handle, "tev_combiner_buffer_color");
-        shader->uniform_tev_const_colors = glGetUniformLocation(shader->shader.handle, "const_color");
 
         state.draw.shader_program = shader->shader.handle;
         state.Apply();
 
         // Set the texture samplers to correspond to different texture units
-        if (shader->uniform_tex != -1) {
-            glUniform1i(shader->uniform_tex, 0);
-            glUniform1i(shader->uniform_tex + 1, 1);
-            glUniform1i(shader->uniform_tex + 2, 2);
-        }
+        glUniform1i(PicaShader::Uniform::Texture0, 0);
+        glUniform1i(PicaShader::Uniform::Texture1, 1);
+        glUniform1i(PicaShader::Uniform::Texture2, 2);
 
         current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get();
     }
@@ -622,8 +616,7 @@ void RasterizerOpenGL::SyncBlendColor() {
 
 void RasterizerOpenGL::SyncAlphaTest() {
     const auto& regs = Pica::g_state.regs;
-    if (current_shader->uniform_alphatest_ref != -1)
-        glUniform1i(current_shader->uniform_alphatest_ref, regs.output_merger.alpha_test.ref);
+    glUniform1i(PicaShader::Uniform::AlphaTestRef, regs.output_merger.alpha_test.ref);
 }
 
 void RasterizerOpenGL::SyncLogicOp() {
@@ -654,17 +647,13 @@ void RasterizerOpenGL::SyncDepthTest() {
 }
 
 void RasterizerOpenGL::SyncCombinerColor() {
-    if (current_shader->uniform_tev_combiner_buffer_color != -1) {
-        auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
-        glUniform4fv(current_shader->uniform_tev_combiner_buffer_color, 1, combiner_color.data());
-    }
+    auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
+    glUniform4fv(PicaShader::Uniform::TevCombinerBufferColor, 1, combiner_color.data());
 }
 
 void RasterizerOpenGL::SyncTevConstColor(int stage_index, const Pica::Regs::TevStageConfig& tev_stage) {
-    if (current_shader->uniform_tev_const_colors != -1) {
-        auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color);
-        glUniform4fv(current_shader->uniform_tev_const_colors + stage_index, 1, const_color.data());
-    }
+    auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color);
+    glUniform4fv(PicaShader::Uniform::TevConstColors + stage_index, 1, const_color.data());
 }
 
 void RasterizerOpenGL::SyncDrawState() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 484579d82..79c34944a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -153,36 +153,24 @@ public:
     /// Notify rasterizer that a 3DS memory region has been changed
     void NotifyFlush(PAddr addr, u32 size) override;
 
-private:
-    /// Structure used for managing texture environment states
-    struct TEVConfigUniforms {
-        GLuint enabled;
-        GLuint color_sources;
-        GLuint alpha_sources;
-        GLuint color_modifiers;
-        GLuint alpha_modifiers;
-        GLuint color_alpha_op;
-        GLuint color_alpha_multiplier;
-        GLuint const_color;
-        GLuint updates_combiner_buffer_color_alpha;
-    };
-
-    struct TEVShader {
+    /// OpenGL shader generated for a given Pica register state
+    struct PicaShader {
+        /// OpenGL shader resource
         OGLShader shader;
 
-        // Hardware fragment shader
-        GLuint uniform_alphatest_ref;
-        GLuint uniform_tex;
-        GLuint uniform_tev_combiner_buffer_color;
-        GLuint uniform_tev_const_colors;
-
-        TEVShader() = default;
-        TEVShader(TEVShader&& o) : shader(std::move(o.shader)),
-            uniform_alphatest_ref(o.uniform_alphatest_ref), uniform_tex(o.uniform_tex),
-            uniform_tev_combiner_buffer_color(o.uniform_tev_combiner_buffer_color),
-            uniform_tev_const_colors(o.uniform_tev_const_colors) {}
+        /// Fragment shader uniforms
+        enum Uniform : GLuint {
+            AlphaTestRef = 0,
+            TevConstColors = 1,
+            Texture0 = 7,
+            Texture1 = 8,
+            Texture2 = 9,
+            TevCombinerBufferColor = 10,
+        };
     };
 
+private:
+
     /// Structure used for storing information about color textures
     struct TextureInfo {
         OGLTexture texture;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 79c690e76..50bb2e3cc 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -321,24 +321,25 @@ static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsign
 
 std::string GenerateFragmentShader(const ShaderCacheKey& config) {
     std::string out = R"(
-#version 150 core
+#version 330
+#extension GL_ARB_explicit_uniform_location : require
 
 #define NUM_VTX_ATTR 7
 #define NUM_TEV_STAGES 6
 
 in vec4 attr[NUM_VTX_ATTR];
 out vec4 color;
+)";
 
-uniform int alphatest_ref;
-uniform vec4 const_color[NUM_TEV_STAGES];
-uniform sampler2D tex[3];
-
-uniform vec4 tev_combiner_buffer_color;
+    using Uniform = RasterizerOpenGL::PicaShader::Uniform;
+    out += "layout(location = " + std::to_string(Uniform::AlphaTestRef) + ") uniform int alphatest_ref;\n";
+    out += "layout(location = " + std::to_string(Uniform::TevConstColors) + ") uniform vec4 const_color[NUM_TEV_STAGES];\n";
+    out += "layout(location = " + std::to_string(Uniform::Texture0) + ") uniform sampler2D tex[3];\n";
+    out += "layout(location = " + std::to_string(Uniform::TevCombinerBufferColor) + ") uniform vec4 tev_combiner_buffer_color;\n";
 
-void main(void) {
-vec4 g_combiner_buffer = tev_combiner_buffer_color;
-vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
-)";
+    out += "void main() {\n";
+    out += "vec4 combiner_buffer = tev_combiner_buffer_color;\n";
+    out += "vec4 last_tex_env_out = vec4(0.0);\n";
 
     // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
     if (config.alpha_test_func == Regs::CompareFunc::Never) {
@@ -362,7 +363,7 @@ vec4 g_last_tex_env_out = vec4(0.0, 0.0, 0.0, 0.0);
 
 std::string GenerateVertexShader() {
     static const std::string out = R"(
-#version 150 core
+#version 330
 
 #define NUM_VTX_ATTR 7
 

From 240a3b80d970b56b4ed3671536489eb0e32532ae Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Fri, 9 Oct 2015 22:46:47 -0400
Subject: [PATCH 15/19] gl_rasterizer: Use MMH3 hash for shader cache hey.

- Includes a check to confirm no hash collisions.
---
 src/common/common_funcs.h                     |  18 ---
 .../renderer_opengl/gl_rasterizer.cpp         |   7 +-
 .../renderer_opengl/gl_rasterizer.h           | 133 ++++++++----------
 .../renderer_opengl/gl_shader_gen.cpp         |   4 +-
 .../renderer_opengl/gl_shader_gen.h           |   2 +-
 5 files changed, 63 insertions(+), 101 deletions(-)

diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 7a8dd39a0..ed20c3629 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -4,9 +4,6 @@
 
 #pragma once
 
-#include <cstddef>
-#include <functional>
-
 #include "common_types.h"
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
@@ -98,18 +95,3 @@ inline u64 _rotr64(u64 x, unsigned int shift){
 // This function might change the error code.
 // Defined in Misc.cpp.
 const char* GetLastErrorMsg();
-
-template <typename T>
-inline std::size_t hash(const T& o) {
-    return std::hash<T>()(o);
-}
-
-template <typename T>
-inline std::size_t combine_hash(const T& o) {
-    return hash(o);
-}
-
-template <typename T, typename... Args>
-inline std::size_t combine_hash(const T& o, const Args&... args) {
-    return hash(o) * 3 + combine_hash(args...);
-}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 64639ed26..4ae42f226 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -461,7 +461,8 @@ void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::
 }
 
 void RasterizerOpenGL::SetShader() {
-    ShaderCacheKey config = ShaderCacheKey::CurrentConfig();
+    PicaShaderConfig config = PicaShaderConfig::CurrentConfig();
+    std::unique_ptr<PicaShader> shader = Common::make_unique<PicaShader>();
 
     // Find (or generate) the GLSL shader for the current TEV state
     auto cached_shader = shader_cache.find(config);
@@ -471,9 +472,7 @@ void RasterizerOpenGL::SetShader() {
         state.draw.shader_program = current_shader->shader.handle;
         state.Apply();
     } else {
-        LOG_DEBUG(Render_OpenGL, "Creating new shader: %08X", hash(config));
-
-        std::unique_ptr<TEVShader> shader = Common::make_unique<TEVShader>();
+        LOG_DEBUG(Render_OpenGL, "Creating new shader");
 
         shader->shader.Create(GLShader::GenerateVertexShader().c_str(), GLShader::GenerateFragmentShader(config).c_str());
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 79c34944a..cf8df8d9c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -5,11 +5,13 @@
 #pragma once
 
 #include <cstddef>
+#include <cstring>
 #include <memory>
 #include <vector>
 #include <unordered_map>
 
 #include "common/common_types.h"
+#include "common/hash.h"
 
 #include "video_core/pica.h"
 #include "video_core/hwrasterizer_base.h"
@@ -25,97 +27,76 @@
  * 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 ShaderCacheKey {
-    using Regs = Pica::Regs;
-
-    bool operator ==(const ShaderCacheKey& o) const {
-        return hash(*this) == hash(o);
-    };
-
-    Regs::CompareFunc alpha_test_func;
-    std::array<Regs::TevStageConfig, 6> tev_stages = {};
-    u8 combiner_buffer_input;
-
-    bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
-        return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index));
-    }
-
-    bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
-        return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
-    }
-
-    /**
-     * This function is used to construct a ShaderCacheKey with the current Pica register
-     * configuration. Don't construct a ShaderCacheKey manually, instead call this function (and
-     * extend it as additional state needs to be captured to generate shaders).
-     */
-    static ShaderCacheKey CurrentConfig() {
+struct PicaShaderConfig {
+    /// Construct a PicaShaderConfig with the current Pica register configuration.
+    static PicaShaderConfig CurrentConfig() {
+        PicaShaderConfig res;
         const auto& regs = Pica::g_state.regs;
-        ShaderCacheKey config;
 
-        config.alpha_test_func = regs.output_merger.alpha_test.enable ?
+        res.alpha_test_func = regs.output_merger.alpha_test.enable ?
             regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always;
 
         // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling
         // the GetTevStages() function) because BitField explicitly disables copies.
 
-        config.tev_stages[0].source_raw = regs.tev_stage0.source_raw;
-        config.tev_stages[1].source_raw = regs.tev_stage1.source_raw;
-        config.tev_stages[2].source_raw = regs.tev_stage2.source_raw;
-        config.tev_stages[3].source_raw = regs.tev_stage3.source_raw;
-        config.tev_stages[4].source_raw = regs.tev_stage4.source_raw;
-        config.tev_stages[5].source_raw = regs.tev_stage5.source_raw;
-
-        config.tev_stages[0].modifier_raw = regs.tev_stage0.modifier_raw;
-        config.tev_stages[1].modifier_raw = regs.tev_stage1.modifier_raw;
-        config.tev_stages[2].modifier_raw = regs.tev_stage2.modifier_raw;
-        config.tev_stages[3].modifier_raw = regs.tev_stage3.modifier_raw;
-        config.tev_stages[4].modifier_raw = regs.tev_stage4.modifier_raw;
-        config.tev_stages[5].modifier_raw = regs.tev_stage5.modifier_raw;
-
-        config.tev_stages[0].op_raw = regs.tev_stage0.op_raw;
-        config.tev_stages[1].op_raw = regs.tev_stage1.op_raw;
-        config.tev_stages[2].op_raw = regs.tev_stage2.op_raw;
-        config.tev_stages[3].op_raw = regs.tev_stage3.op_raw;
-        config.tev_stages[4].op_raw = regs.tev_stage4.op_raw;
-        config.tev_stages[5].op_raw = regs.tev_stage5.op_raw;
-
-        config.tev_stages[0].scale_raw = regs.tev_stage0.scale_raw;
-        config.tev_stages[1].scale_raw = regs.tev_stage1.scale_raw;
-        config.tev_stages[2].scale_raw = regs.tev_stage2.scale_raw;
-        config.tev_stages[3].scale_raw = regs.tev_stage3.scale_raw;
-        config.tev_stages[4].scale_raw = regs.tev_stage4.scale_raw;
-        config.tev_stages[5].scale_raw = regs.tev_stage5.scale_raw;
-
-        config.combiner_buffer_input =
+        res.tev_stages[0].sources_raw = regs.tev_stage0.sources_raw;
+        res.tev_stages[1].sources_raw = regs.tev_stage1.sources_raw;
+        res.tev_stages[2].sources_raw = regs.tev_stage2.sources_raw;
+        res.tev_stages[3].sources_raw = regs.tev_stage3.sources_raw;
+        res.tev_stages[4].sources_raw = regs.tev_stage4.sources_raw;
+        res.tev_stages[5].sources_raw = regs.tev_stage5.sources_raw;
+
+        res.tev_stages[0].modifiers_raw = regs.tev_stage0.modifiers_raw;
+        res.tev_stages[1].modifiers_raw = regs.tev_stage1.modifiers_raw;
+        res.tev_stages[2].modifiers_raw = regs.tev_stage2.modifiers_raw;
+        res.tev_stages[3].modifiers_raw = regs.tev_stage3.modifiers_raw;
+        res.tev_stages[4].modifiers_raw = regs.tev_stage4.modifiers_raw;
+        res.tev_stages[5].modifiers_raw = regs.tev_stage5.modifiers_raw;
+
+        res.tev_stages[0].ops_raw = regs.tev_stage0.ops_raw;
+        res.tev_stages[1].ops_raw = regs.tev_stage1.ops_raw;
+        res.tev_stages[2].ops_raw = regs.tev_stage2.ops_raw;
+        res.tev_stages[3].ops_raw = regs.tev_stage3.ops_raw;
+        res.tev_stages[4].ops_raw = regs.tev_stage4.ops_raw;
+        res.tev_stages[5].ops_raw = regs.tev_stage5.ops_raw;
+
+        res.tev_stages[0].scales_raw = regs.tev_stage0.scales_raw;
+        res.tev_stages[1].scales_raw = regs.tev_stage1.scales_raw;
+        res.tev_stages[2].scales_raw = regs.tev_stage2.scales_raw;
+        res.tev_stages[3].scales_raw = regs.tev_stage3.scales_raw;
+        res.tev_stages[4].scales_raw = regs.tev_stage4.scales_raw;
+        res.tev_stages[5].scales_raw = regs.tev_stage5.scales_raw;
+
+        res.combiner_buffer_input =
             regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
             regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
 
-        return config;
+        return res;
     }
-};
-
-namespace std {
 
-template<> struct hash<::Pica::Regs::CompareFunc> {
-    std::size_t operator()(const ::Pica::Regs::CompareFunc& o) {
-        return ::hash((unsigned)o);
+    bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
+        return (stage_index < 4) && (combiner_buffer_input & (1 << stage_index));
     }
-};
 
-template<> struct hash<::Pica::Regs::TevStageConfig> {
-    std::size_t operator()(const ::Pica::Regs::TevStageConfig& o) {
-        return ::combine_hash(
-            ::hash(o.source_raw), ::hash(o.modifier_raw),
-            ::hash(o.op_raw), ::hash(o.scale_raw));
+    bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
+        return (stage_index < 4) && ((combiner_buffer_input >> 4) & (1 << stage_index));
     }
+
+    bool operator ==(const PicaShaderConfig& o) const {
+        return std::memcmp(this, &o, sizeof(PicaShaderConfig)) == 0;
+    };
+
+    Pica::Regs::CompareFunc alpha_test_func;
+    std::array<Pica::Regs::TevStageConfig, 6> tev_stages = {};
+    u8 combiner_buffer_input;
 };
 
-template<> struct hash<::ShaderCacheKey> {
-    std::size_t operator()(const ::ShaderCacheKey& o) const {
-        return ::combine_hash(o.alpha_test_func, o.combiner_buffer_input,
-            o.tev_stages[0], o.tev_stages[1], o.tev_stages[2],
-            o.tev_stages[3], o.tev_stages[4], o.tev_stages[5]);
+namespace std {
+
+template <>
+struct hash<PicaShaderConfig> {
+    std::size_t operator()(const PicaShaderConfig& k) const {
+        return Common::ComputeHash64(&k, sizeof(PicaShaderConfig));
     }
 };
 
@@ -314,8 +295,8 @@ private:
     TextureInfo fb_color_texture;
     DepthTextureInfo fb_depth_texture;
 
-    std::unordered_map<ShaderCacheKey, std::unique_ptr<TEVShader>> shader_cache;
-    const TEVShader* current_shader = nullptr;
+    std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache;
+    const PicaShader* current_shader = nullptr;
 
     OGLVertexArray vertex_array;
     OGLBuffer vertex_buffer;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 50bb2e3cc..84883b483 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -278,7 +278,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
 }
 
 /// Writes the code to emulate the specified TEV stage
-static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsigned index) {
+static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) {
     auto& stage = config.tev_stages[index];
     if (!IsPassThroughTevStage(stage)) {
         std::string index_name = std::to_string(index);
@@ -319,7 +319,7 @@ static void WriteTevStage(std::string& out, const ShaderCacheKey& config, unsign
         out += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
 }
 
-std::string GenerateFragmentShader(const ShaderCacheKey& config) {
+std::string GenerateFragmentShader(const PicaShaderConfig& config) {
     std::string out = R"(
 #version 330
 #extension GL_ARB_explicit_uniform_location : require
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index c8295e9e0..0ca9d2879 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -22,6 +22,6 @@ std::string GenerateVertexShader();
  *               configuration (NOTE: Use state in this struct only, not the Pica registers!)
  * @returns String of the shader source code
  */
-std::string GenerateFragmentShader(const ShaderCacheKey& config);
+std::string GenerateFragmentShader(const PicaShaderConfig& config);
 
 } // namespace GLShader

From 0ebcff710e6b5d266158ec364cf08bd8f22ae74a Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Mon, 12 Oct 2015 22:23:26 -0400
Subject: [PATCH 16/19] gl_shader_gen: Various cleanups to shader generation.

---
 .../renderer_opengl/gl_rasterizer.h           |  2 +-
 .../renderer_opengl/gl_shader_gen.cpp         | 92 ++++++++++---------
 .../renderer_opengl/gl_shader_util.cpp        |  6 +-
 3 files changed, 52 insertions(+), 48 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index cf8df8d9c..872cae7da 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -95,7 +95,7 @@ namespace std {
 
 template <>
 struct hash<PicaShaderConfig> {
-    std::size_t operator()(const PicaShaderConfig& k) const {
+    size_t operator()(const PicaShaderConfig& k) const {
         return Common::ComputeHash64(&k, sizeof(PicaShaderConfig));
     }
 };
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 84883b483..7506cdc08 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -29,33 +29,33 @@ static void AppendSource(std::string& out, TevStageConfig::Source source,
     using Source = TevStageConfig::Source;
     switch (source) {
     case Source::PrimaryColor:
-        out += "attr[2]";
+        out += "primary_color";
         break;
     case Source::PrimaryFragmentColor:
         // HACK: Until we implement fragment lighting, use primary_color
-        out += "attr[2]";
+        out += "primary_color";
         break;
     case Source::SecondaryFragmentColor:
         // HACK: Until we implement fragment lighting, use zero
-        out += "vec4(0.0, 0.0, 0.0, 0.0)";
+        out += "vec4(0.0)";
         break;
     case Source::Texture0:
-        out += "texture(tex[0], attr[3].xy)";
+        out += "texture(tex[0], texcoord[0])";
         break;
     case Source::Texture1:
-        out += "texture(tex[1], attr[3].zw)";
+        out += "texture(tex[1], texcoord[1])";
         break;
-    case Source::Texture2: // TODO: Unverified
-        out += "texture(tex[2], attr[5].zw)";
+    case Source::Texture2:
+        out += "texture(tex[2], texcoord[2])";
         break;
     case Source::PreviousBuffer:
-        out += "g_combiner_buffer";
+        out += "combiner_buffer";
         break;
     case Source::Constant:
-        out += "const_color[" + index_name + "]";
+        ((out += "const_color[") += index_name) += ']';
         break;
     case Source::Previous:
-        out += "g_last_tex_env_out";
+        out += "last_tex_env_out";
         break;
     default:
         out += "vec4(0.0)";
@@ -172,8 +172,8 @@ static void AppendAlphaModifier(std::string& out, TevStageConfig::AlphaModifier
 /// Writes the combiner function for the color components for the specified TEV stage operation
 static void AppendColorCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
+    out += "clamp(";
     using Operation = TevStageConfig::Operation;
-
     switch (operation) {
     case Operation::Replace:
         out += variable_name + "[0]";
@@ -182,19 +182,20 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper
         out += variable_name + "[0] * " + variable_name + "[1]";
         break;
     case Operation::Add:
-        out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0))";
+        out += variable_name + "[0] + " + variable_name + "[1]";
         break;
     case Operation::AddSigned:
-        out += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - vec3(0.5), vec3(0.0), vec3(1.0))";
+        out += variable_name + "[0] + " + variable_name + "[1] - vec3(0.5)";
         break;
     case Operation::Lerp:
+        // TODO(bunnei): Verify if HW actually does this per-component, otherwise we can just use builtin lerp
         out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (vec3(1.0) - " + variable_name + "[2])";
         break;
     case Operation::Subtract:
-        out += "max(" + variable_name + "[0] - " + variable_name + "[1], vec3(0.0))";
+        out += variable_name + "[0] - " + variable_name + "[1]";
         break;
     case Operation::MultiplyThenAdd:
-        out += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], vec3(1.0))";
+        out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]";
         break;
     case Operation::AddThenMultiply:
         out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]";
@@ -204,11 +205,13 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper
         LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation);
         break;
     }
+    out += ", vec3(0.0), vec3(1.0))";
 }
 
 /// Writes the combiner function for the alpha component for the specified TEV stage operation
 static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation operation,
         const std::string& variable_name) {
+    out += "clamp(";
     using Operation = TevStageConfig::Operation;
     switch (operation) {
     case Operation::Replace:
@@ -218,19 +221,19 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper
         out += variable_name + "[0] * " + variable_name + "[1]";
         break;
     case Operation::Add:
-        out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0)";
+        out += variable_name + "[0] + " + variable_name + "[1]";
         break;
     case Operation::AddSigned:
-        out += "clamp(" + variable_name + "[0] + " + variable_name + "[1] - 0.5, 0.0, 1.0)";
+        out += variable_name + "[0] + " + variable_name + "[1] - 0.5";
         break;
     case Operation::Lerp:
         out += variable_name + "[0] * " + variable_name + "[2] + " + variable_name + "[1] * (1.0 - " + variable_name + "[2])";
         break;
     case Operation::Subtract:
-        out += "max(" + variable_name + "[0] - " + variable_name + "[1], 0.0)";
+        out += variable_name + "[0] - " + variable_name + "[1]";
         break;
     case Operation::MultiplyThenAdd:
-        out += "min(" + variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2], 1.0)";
+        out += variable_name + "[0] * " + variable_name + "[1] + " + variable_name + "[2]";
         break;
     case Operation::AddThenMultiply:
         out += "min(" + variable_name + "[0] + " + variable_name + "[1], 1.0) * " + variable_name + "[2]";
@@ -240,6 +243,7 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper
         LOG_CRITICAL(Render_OpenGL, "Unknown alpha combiner operation: %u", operation);
         break;
     }
+    out += ", 0.0, 1.0)";
 }
 
 /// Writes the if-statement condition used to evaluate alpha testing
@@ -253,22 +257,22 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
         out += "false";
         break;
     case CompareFunc::Equal:
-        out += "int(g_last_tex_env_out.a * 255.0f) != alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) != alphatest_ref";
         break;
     case CompareFunc::NotEqual:
-        out += "int(g_last_tex_env_out.a * 255.0f) == alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) == alphatest_ref";
         break;
     case CompareFunc::LessThan:
-        out += "int(g_last_tex_env_out.a * 255.0f) >= alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) >= alphatest_ref";
         break;
     case CompareFunc::LessThanOrEqual:
-        out += "int(g_last_tex_env_out.a * 255.0f) > alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) > alphatest_ref";
         break;
     case CompareFunc::GreaterThan:
-        out += "int(g_last_tex_env_out.a * 255.0f) <= alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) <= alphatest_ref";
         break;
     case CompareFunc::GreaterThanOrEqual:
-        out += "int(g_last_tex_env_out.a * 255.0f) < alphatest_ref";
+        out += "int(last_tex_env_out.a * 255.0f) < alphatest_ref";
         break;
     default:
         out += "false";
@@ -307,16 +311,16 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi
         AppendAlphaCombiner(out, stage.alpha_op, "alpha_results_" + index_name);
         out += ";\n";
 
-        out += "g_last_tex_env_out = vec4("
-            "clamp(color_output_" + index_name + " * " + std::to_string(stage.GetColorMultiplier()) + ".0, 0.0, 1.0),"
+        out += "last_tex_env_out = vec4("
+            "clamp(color_output_" + index_name + " * " + std::to_string(stage.GetColorMultiplier()) + ".0, vec3(0.0), vec3(1.0)),"
             "clamp(alpha_output_" + index_name + " * " + std::to_string(stage.GetAlphaMultiplier()) + ".0, 0.0, 1.0));\n";
     }
 
     if (config.TevStageUpdatesCombinerBufferColor(index))
-        out += "g_combiner_buffer.rgb = g_last_tex_env_out.rgb;\n";
+        out += "combiner_buffer.rgb = last_tex_env_out.rgb;\n";
 
     if (config.TevStageUpdatesCombinerBufferAlpha(index))
-        out += "g_combiner_buffer.a = g_last_tex_env_out.a;\n";
+        out += "combiner_buffer.a = last_tex_env_out.a;\n";
 }
 
 std::string GenerateFragmentShader(const PicaShaderConfig& config) {
@@ -324,10 +328,11 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
 #version 330
 #extension GL_ARB_explicit_uniform_location : require
 
-#define NUM_VTX_ATTR 7
 #define NUM_TEV_STAGES 6
 
-in vec4 attr[NUM_VTX_ATTR];
+in vec4 primary_color;
+in vec2 texcoord[3];
+
 out vec4 color;
 )";
 
@@ -347,16 +352,16 @@ out vec4 color;
         return out;
     }
 
-    for (std::size_t index = 0; index < config.tev_stages.size(); ++index)
+    for (size_t index = 0; index < config.tev_stages.size(); ++index)
         WriteTevStage(out, config, (unsigned)index);
 
     if (config.alpha_test_func != Regs::CompareFunc::Always) {
         out += "if (";
         AppendAlphaTestCondition(out, config.alpha_test_func);
-        out += ") {\n discard;\n }\n";
+        out += ") discard;\n";
     }
 
-    out += "color = g_last_tex_env_out;\n}";
+    out += "color = last_tex_env_out;\n}";
 
     return out;
 }
@@ -365,21 +370,20 @@ std::string GenerateVertexShader() {
     static const std::string out = R"(
 #version 330
 
-#define NUM_VTX_ATTR 7
-
 in vec4 vert_position;
 in vec4 vert_color;
-in vec2 vert_texcoords0;
-in vec2 vert_texcoords1;
-in vec2 vert_texcoords2;
+in vec2 vert_texcoord0;
+in vec2 vert_texcoord1;
+in vec2 vert_texcoord2;
 
-out vec4 attr[NUM_VTX_ATTR];
+out vec4 primary_color;
+out vec2 texcoord[3];
 
 void main() {
-    attr[2] = vert_color;
-    attr[3] = vec4(vert_texcoords0.xy, vert_texcoords1.xy);
-    attr[5] = vec4(0.0, 0.0, vert_texcoords2.xy);
-
+    primary_color = vert_color;
+    texcoord[0] = vert_texcoord0;
+    texcoord[1] = vert_texcoord1;
+    texcoord[2] = vert_texcoord2;
     gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
 }
 )";
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index ce218b857..2fa0ceb3e 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -68,9 +68,9 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
 
     glBindAttribLocation(program_id, Attributes::ATTRIBUTE_POSITION, "vert_position");
     glBindAttribLocation(program_id, Attributes::ATTRIBUTE_COLOR, "vert_color");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 0, "vert_texcoords0");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 1, "vert_texcoords1");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 2, "vert_texcoords2");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 0, "vert_texcoord0");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 1, "vert_texcoord1");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 2, "vert_texcoord2");
 
     glLinkProgram(program_id);
 

From e7b1f2ae0af304abea3fb9a5b658abb92737caaa Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 21 Oct 2015 00:03:22 -0400
Subject: [PATCH 17/19] gl_rasterizer: Define enum types for each vertex
 texcoord attribute.

---
 src/video_core/renderer_opengl/gl_rasterizer.cpp  | 12 ++++++------
 src/video_core/renderer_opengl/gl_shader_util.cpp |  6 +++---
 src/video_core/renderer_opengl/gl_shader_util.h   |  8 +++++---
 3 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 4ae42f226..d1def2f3b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -61,12 +61,12 @@ void RasterizerOpenGL::InitObjects() {
     glVertexAttribPointer(GLShader::ATTRIBUTE_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, color));
     glEnableVertexAttribArray(GLShader::ATTRIBUTE_COLOR);
 
-    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 0, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
-    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
-    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORDS + 2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
-    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 0);
-    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 1);
-    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORDS + 2);
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD0, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord0));
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD1, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord1));
+    glVertexAttribPointer(GLShader::ATTRIBUTE_TEXCOORD2, 2, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, tex_coord2));
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD0);
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD1);
+    glEnableVertexAttribArray(GLShader::ATTRIBUTE_TEXCOORD2);
 
     SetShader();
 
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 2fa0ceb3e..735c86d22 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -68,9 +68,9 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
 
     glBindAttribLocation(program_id, Attributes::ATTRIBUTE_POSITION, "vert_position");
     glBindAttribLocation(program_id, Attributes::ATTRIBUTE_COLOR, "vert_color");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 0, "vert_texcoord0");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 1, "vert_texcoord1");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORDS + 2, "vert_texcoord2");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD0, "vert_texcoord0");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD1, "vert_texcoord1");
+    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD2, "vert_texcoord2");
 
     glLinkProgram(program_id);
 
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 4d3791d50..046aae14f 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -9,9 +9,11 @@
 namespace GLShader {
 
 enum Attributes {
-    ATTRIBUTE_POSITION  = 0,
-    ATTRIBUTE_COLOR     = 1,
-    ATTRIBUTE_TEXCOORDS = 2,
+    ATTRIBUTE_POSITION,
+    ATTRIBUTE_COLOR,
+    ATTRIBUTE_TEXCOORD0,
+    ATTRIBUTE_TEXCOORD1,
+    ATTRIBUTE_TEXCOORD2,
 };
 
 /**

From e663f5c91450ac5b36358195718edddbde25cd75 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 21 Oct 2015 00:04:02 -0400
Subject: [PATCH 18/19] gl_shader_gen: Optimize code for
 AppendAlphaTestCondition.

- Also add a comment to AppendColorCombiner.
---
 .../renderer_opengl/gl_shader_gen.cpp         | 27 ++++++++-----------
 1 file changed, 11 insertions(+), 16 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 7506cdc08..e456f5847 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -205,7 +205,7 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper
         LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation);
         break;
     }
-    out += ", vec3(0.0), vec3(1.0))";
+    out += ", vec3(0.0), vec3(1.0))"; // Clamp result to 0.0, 1.0
 }
 
 /// Writes the combiner function for the alpha component for the specified TEV stage operation
@@ -257,23 +257,18 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
         out += "false";
         break;
     case CompareFunc::Equal:
-        out += "int(last_tex_env_out.a * 255.0f) != alphatest_ref";
-        break;
     case CompareFunc::NotEqual:
-        out += "int(last_tex_env_out.a * 255.0f) == alphatest_ref";
-        break;
     case CompareFunc::LessThan:
-        out += "int(last_tex_env_out.a * 255.0f) >= alphatest_ref";
-        break;
     case CompareFunc::LessThanOrEqual:
-        out += "int(last_tex_env_out.a * 255.0f) > alphatest_ref";
-        break;
     case CompareFunc::GreaterThan:
-        out += "int(last_tex_env_out.a * 255.0f) <= alphatest_ref";
-        break;
     case CompareFunc::GreaterThanOrEqual:
-        out += "int(last_tex_env_out.a * 255.0f) < alphatest_ref";
+    {
+        static const char* op[] = { "!=", "==", ">=", ">", "<=", "<", };
+        unsigned index = (unsigned)func - (unsigned)CompareFunc::Equal;
+        out += "int(last_tex_env_out.a * 255.0f) " + std::string(op[index]) + " alphatest_ref";
         break;
+    }
+
     default:
         out += "false";
         LOG_CRITICAL(Render_OpenGL, "Unknown alpha test condition %u", func);
@@ -337,10 +332,10 @@ out vec4 color;
 )";
 
     using Uniform = RasterizerOpenGL::PicaShader::Uniform;
-    out += "layout(location = " + std::to_string(Uniform::AlphaTestRef) + ") uniform int alphatest_ref;\n";
-    out += "layout(location = " + std::to_string(Uniform::TevConstColors) + ") uniform vec4 const_color[NUM_TEV_STAGES];\n";
-    out += "layout(location = " + std::to_string(Uniform::Texture0) + ") uniform sampler2D tex[3];\n";
-    out += "layout(location = " + std::to_string(Uniform::TevCombinerBufferColor) + ") uniform vec4 tev_combiner_buffer_color;\n";
+    out += "layout(location = " + std::to_string((int)Uniform::AlphaTestRef) + ") uniform int alphatest_ref;\n";
+    out += "layout(location = " + std::to_string((int)Uniform::TevConstColors) + ") uniform vec4 const_color[NUM_TEV_STAGES];\n";
+    out += "layout(location = " + std::to_string((int)Uniform::Texture0) + ") uniform sampler2D tex[3];\n";
+    out += "layout(location = " + std::to_string((int)Uniform::TevCombinerBufferColor) + ") uniform vec4 tev_combiner_buffer_color;\n";
 
     out += "void main() {\n";
     out += "vec4 combiner_buffer = tev_combiner_buffer_color;\n";

From 74186a5f016a72f28f2d227bbe524787cd5b685f Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Wed, 21 Oct 2015 21:50:55 -0400
Subject: [PATCH 19/19] gl_shader_gen: Use explicit locations for vertex shader
 attributes.

---
 .../renderer_opengl/gl_shader_gen.cpp          | 18 +++++++++---------
 .../renderer_opengl/gl_shader_util.cpp         |  6 ------
 2 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index e456f5847..d19d15e75 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -362,15 +362,14 @@ out vec4 color;
 }
 
 std::string GenerateVertexShader() {
-    static const std::string out = R"(
-#version 330
-
-in vec4 vert_position;
-in vec4 vert_color;
-in vec2 vert_texcoord0;
-in vec2 vert_texcoord1;
-in vec2 vert_texcoord2;
-
+    std::string out = "#version 330\n";
+    out += "layout(location = " + std::to_string((int)ATTRIBUTE_POSITION)  + ") in vec4 vert_position;\n";
+    out += "layout(location = " + std::to_string((int)ATTRIBUTE_COLOR)     + ") in vec4 vert_color;\n";
+    out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD0) + ") in vec2 vert_texcoord0;\n";
+    out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD1) + ") in vec2 vert_texcoord1;\n";
+    out += "layout(location = " + std::to_string((int)ATTRIBUTE_TEXCOORD2) + ") in vec2 vert_texcoord2;\n";
+
+    out += R"(
 out vec4 primary_color;
 out vec2 texcoord[3];
 
@@ -382,6 +381,7 @@ void main() {
     gl_Position = vec4(vert_position.x, -vert_position.y, -vert_position.z, vert_position.w);
 }
 )";
+
     return out;
 }
 
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 735c86d22..e3f7a5868 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -66,12 +66,6 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
     glAttachShader(program_id, vertex_shader_id);
     glAttachShader(program_id, fragment_shader_id);
 
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_POSITION, "vert_position");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_COLOR, "vert_color");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD0, "vert_texcoord0");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD1, "vert_texcoord1");
-    glBindAttribLocation(program_id, Attributes::ATTRIBUTE_TEXCOORD2, "vert_texcoord2");
-
     glLinkProgram(program_id);
 
     // Check the program