diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 363b941f3..a70ff79d0 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -11,6 +11,7 @@
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_cache.h"
 #include "video_core/renderer_opengl/gl_shader_decompiler.h"
+#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
 #include "video_core/renderer_opengl/gl_shader_manager.h"
 #include "video_core/renderer_opengl/utils.h"
 #include "video_core/shader/shader_ir.h"
@@ -19,8 +20,19 @@ namespace OpenGL {
 
 using VideoCommon::Shader::ProgramCode;
 
+// One UBO is always reserved for emulation values
+constexpr u32 RESERVED_UBOS = 1;
+
+struct UnspecializedShader {
+    std::string code;
+    GLShader::ShaderEntries entries;
+    Maxwell::ShaderProgram program_type;
+};
+
+namespace {
+
 /// Gets the address for the specified shader stage program
-static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
+VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
     const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
     const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
     const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
@@ -30,7 +42,7 @@ static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
 }
 
 /// Gets the shader program code from memory for the specified address
-static ProgramCode GetShaderCode(VAddr addr) {
+ProgramCode GetShaderCode(VAddr addr) {
     ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
     Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
     return program_code;
@@ -51,38 +63,193 @@ constexpr GLenum GetShaderType(Maxwell::ShaderProgram program_type) {
     }
 }
 
-CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
-    : addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} {
+/// Gets if the current instruction offset is a scheduler instruction
+constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
+    // Sched instructions appear once every 4 instructions.
+    constexpr std::size_t SchedPeriod = 4;
+    const std::size_t absolute_offset = offset - main_offset;
+    return (absolute_offset % SchedPeriod) == 0;
+}
 
-    GLShader::ProgramResult program_result;
+/// Describes primitive behavior on geometry shaders
+constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) {
+    switch (primitive_mode) {
+    case GL_POINTS:
+        return {"points", "Points", 1};
+    case GL_LINES:
+    case GL_LINE_STRIP:
+        return {"lines", "Lines", 2};
+    case GL_LINES_ADJACENCY:
+    case GL_LINE_STRIP_ADJACENCY:
+        return {"lines_adjacency", "LinesAdj", 4};
+    case GL_TRIANGLES:
+    case GL_TRIANGLE_STRIP:
+    case GL_TRIANGLE_FAN:
+        return {"triangles", "Triangles", 3};
+    case GL_TRIANGLES_ADJACENCY:
+    case GL_TRIANGLE_STRIP_ADJACENCY:
+        return {"triangles_adjacency", "TrianglesAdj", 6};
+    default:
+        return {"points", "Invalid", 1};
+    }
+}
 
-    switch (program_type) {
-    case Maxwell::ShaderProgram::VertexA:
+/// Calculates the size of a program stream
+std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
+    constexpr std::size_t start_offset = 10;
+    std::size_t offset = start_offset;
+    std::size_t size = start_offset * sizeof(u64);
+    while (offset < program.size()) {
+        const u64 instruction = program[offset];
+        if (!IsSchedInstruction(offset, start_offset)) {
+            if (instruction == 0 || (instruction >> 52) == 0x50b) {
+                // End on Maxwell's "nop" instruction
+                break;
+            }
+        }
+        size += sizeof(u64);
+        offset++;
+    }
+    // The last instruction is included in the program size
+    return std::min(size + sizeof(u64), program.size() * sizeof(u64));
+}
+
+/// Hashes one (or two) program streams
+u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode& code,
+                        const ProgramCode& code_b) {
+    u64 unique_identifier =
+        Common::CityHash64(reinterpret_cast<const char*>(code.data()), CalculateProgramSize(code));
+    if (program_type != Maxwell::ShaderProgram::VertexA) {
+        return unique_identifier;
+    }
+    // VertexA programs include two programs
+
+    std::size_t seed = 0;
+    boost::hash_combine(seed, unique_identifier);
+
+    const u64 identifier_b = Common::CityHash64(reinterpret_cast<const char*>(code_b.data()),
+                                                CalculateProgramSize(code_b));
+    boost::hash_combine(seed, identifier_b);
+    return static_cast<u64>(seed);
+}
+
+/// Creates an unspecialized program from code streams
+GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, ProgramCode program_code,
+                                      ProgramCode program_code_b) {
+    GLShader::ShaderSetup setup(std::move(program_code));
+    if (program_type == Maxwell::ShaderProgram::VertexA) {
         // VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
         // Conventional HW does not support this, so we combine VertexA and VertexB into one
         // stage here.
-        setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
+        setup.SetProgramB(std::move(program_code_b));
+    }
+
+    switch (program_type) {
+    case Maxwell::ShaderProgram::VertexA:
     case Maxwell::ShaderProgram::VertexB:
-        CalculateProperties();
-        program_result = GLShader::GenerateVertexShader(setup);
-        break;
+        return GLShader::GenerateVertexShader(setup);
     case Maxwell::ShaderProgram::Geometry:
-        CalculateProperties();
-        program_result = GLShader::GenerateGeometryShader(setup);
-        break;
+        return GLShader::GenerateGeometryShader(setup);
     case Maxwell::ShaderProgram::Fragment:
-        CalculateProperties();
-        program_result = GLShader::GenerateFragmentShader(setup);
-        break;
+        return GLShader::GenerateFragmentShader(setup);
     default:
         LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type));
         UNREACHABLE();
+        return {};
+    }
+}
+
+CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
+                               Maxwell::ShaderProgram program_type, BaseBindings base_bindings,
+                               GLenum primitive_mode, bool hint_retrievable = false) {
+    std::string source = "#version 430 core\n";
+    source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
+
+    for (const auto& cbuf : entries.const_buffers) {
+        source +=
+            fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++);
+    }
+    for (const auto& gmem : entries.global_memory_entries) {
+        source += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(),
+                              gmem.GetCbufOffset(), base_bindings.gmem++);
+    }
+    for (const auto& sampler : entries.samplers) {
+        source += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(),
+                              base_bindings.sampler++);
+    }
+
+    if (program_type == Maxwell::ShaderProgram::Geometry) {
+        const auto [glsl_topology, _, max_vertices] = GetPrimitiveDescription(primitive_mode);
+
+        source += "layout (" + std::string(glsl_topology) + ") in;\n";
+        source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n';
+    }
+
+    source += code;
+
+    OGLShader shader;
+    shader.Create(source.c_str(), GetShaderType(program_type));
+
+    auto program = std::make_shared<OGLProgram>();
+    program->Create(true, hint_retrievable, shader.handle);
+    return program;
+}
+
+std::set<GLenum> GetSupportedFormats() {
+    std::set<GLenum> supported_formats;
+
+    GLint num_formats{};
+    glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
+
+    std::vector<GLint> formats(num_formats);
+    glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data());
+
+    for (const GLint format : formats)
+        supported_formats.insert(static_cast<GLenum>(format));
+    return supported_formats;
+}
+
+} // namespace
+
+CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
+                           ShaderDiskCacheOpenGL& disk_cache,
+                           const PrecompiledPrograms& precompiled_programs,
+                           ProgramCode&& program_code, ProgramCode&& program_code_b)
+    : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
+      disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
+
+    const std::size_t code_size = CalculateProgramSize(program_code);
+    const std::size_t code_size_b =
+        program_code_b.empty() ? 0 : CalculateProgramSize(program_code_b);
+
+    GLShader::ProgramResult program_result =
+        CreateProgram(program_type, program_code, program_code_b);
+    if (program_result.first.empty()) {
+        // TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now
         return;
     }
 
     code = program_result.first;
     entries = program_result.second;
     shader_length = entries.shader_length;
+
+    const ShaderDiskCacheRaw raw(unique_identifier, program_type,
+                                 static_cast<u32>(code_size / sizeof(u64)),
+                                 static_cast<u32>(code_size_b / sizeof(u64)),
+                                 std::move(program_code), std::move(program_code_b));
+    disk_cache.SaveRaw(raw);
+}
+
+CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
+                           ShaderDiskCacheOpenGL& disk_cache,
+                           const PrecompiledPrograms& precompiled_programs,
+                           GLShader::ProgramResult result)
+    : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
+      disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
+
+    code = std::move(result.first);
+    entries = result.second;
+    shader_length = entries.shader_length;
 }
 
 std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive_mode,
@@ -94,138 +261,195 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
         const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings);
         auto& program = entry->second;
         if (is_cache_miss) {
-            std::string source = AllocateBindings(base_bindings);
-            source += code;
+            program = TryLoadProgram(primitive_mode, base_bindings);
+            if (!program) {
+                program =
+                    SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
+                disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
+            }
 
-            OGLShader shader;
-            shader.Create(source.c_str(), GetShaderType(program_type));
-            program.Create(true, shader.handle);
-            LabelGLObject(GL_PROGRAM, program.handle, addr);
+            LabelGLObject(GL_PROGRAM, program->handle, addr);
         }
 
-        handle = program.handle;
+        handle = program->handle;
     }
 
-    // Add const buffer and samplers offset reserved by this shader. One UBO binding is reserved for
-    // emulation values
-    base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()) + 1;
+    base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()) + RESERVED_UBOS;
     base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size());
     base_bindings.sampler += static_cast<u32>(entries.samplers.size());
 
     return {handle, base_bindings};
 }
 
-std::string CachedShader::AllocateBindings(BaseBindings base_bindings) {
-    std::string code = "#version 430 core\n";
-    code += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
-
-    for (const auto& cbuf : entries.const_buffers) {
-        code += fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++);
-    }
-
-    for (const auto& gmem : entries.global_memory_entries) {
-        code += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(),
-                            gmem.GetCbufOffset(), base_bindings.gmem++);
-    }
-
-    for (const auto& sampler : entries.samplers) {
-        code += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(),
-                            base_bindings.sampler++);
-    }
-
-    return code;
-}
-
 GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) {
     const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings);
     auto& programs = entry->second;
 
     switch (primitive_mode) {
     case GL_POINTS:
-        return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints");
+        return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
     case GL_LINES:
     case GL_LINE_STRIP:
-        return LazyGeometryProgram(programs.lines, base_bindings, "lines", 2, "ShaderLines");
+        return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode);
     case GL_LINES_ADJACENCY:
     case GL_LINE_STRIP_ADJACENCY:
-        return LazyGeometryProgram(programs.lines_adjacency, base_bindings, "lines_adjacency", 4,
-                                   "ShaderLinesAdjacency");
+        return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode);
     case GL_TRIANGLES:
     case GL_TRIANGLE_STRIP:
     case GL_TRIANGLE_FAN:
-        return LazyGeometryProgram(programs.triangles, base_bindings, "triangles", 3,
-                                   "ShaderTriangles");
+        return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode);
     case GL_TRIANGLES_ADJACENCY:
     case GL_TRIANGLE_STRIP_ADJACENCY:
-        return LazyGeometryProgram(programs.triangles_adjacency, base_bindings,
-                                   "triangles_adjacency", 6, "ShaderTrianglesAdjacency");
+        return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode);
     default:
         UNREACHABLE_MSG("Unknown primitive mode.");
-        return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints");
+        return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
     }
 }
 
-GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings,
-                                         const std::string& glsl_topology, u32 max_vertices,
-                                         const std::string& debug_name) {
-    if (target_program.handle != 0) {
-        return target_program.handle;
+GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
+                                         GLenum primitive_mode) {
+    if (target_program) {
+        return target_program->handle;
+    }
+    const auto [_, debug_name, __] = GetPrimitiveDescription(primitive_mode);
+    target_program = TryLoadProgram(primitive_mode, base_bindings);
+    if (!target_program) {
+        target_program =
+            SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
+        disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
     }
-    std::string source = AllocateBindings(base_bindings);
-    source += "layout (" + glsl_topology + ") in;\n";
-    source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n';
-    source += code;
 
-    OGLShader shader;
-    shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
-    target_program.Create(true, shader.handle);
-    LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
-    return target_program.handle;
+    LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name);
+
+    return target_program->handle;
 };
 
-static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
-    // sched instructions appear once every 4 instructions.
-    static constexpr std::size_t SchedPeriod = 4;
-    const std::size_t absolute_offset = offset - main_offset;
-    return (absolute_offset % SchedPeriod) == 0;
+CachedProgram CachedShader::TryLoadProgram(GLenum primitive_mode,
+                                           BaseBindings base_bindings) const {
+    const auto found = precompiled_programs.find(GetUsage(primitive_mode, base_bindings));
+    if (found == precompiled_programs.end()) {
+        return {};
+    }
+    return found->second;
 }
 
-static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
-    constexpr std::size_t start_offset = 10;
-    std::size_t offset = start_offset;
-    std::size_t size = start_offset * sizeof(u64);
-    while (offset < program.size()) {
-        const u64 inst = program[offset];
-        if (!IsSchedInstruction(offset, start_offset)) {
-            if (inst == 0 || (inst >> 52) == 0x50b) {
-                break;
-            }
-        }
-        size += sizeof(inst);
-        offset++;
-    }
-    return size;
-}
-
-void CachedShader::CalculateProperties() {
-    setup.program.real_size = CalculateProgramSize(setup.program.code);
-    setup.program.real_size_b = 0;
-    setup.program.unique_identifier = Common::CityHash64(
-        reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size);
-    if (program_type == Maxwell::ShaderProgram::VertexA) {
-        std::size_t seed = 0;
-        boost::hash_combine(seed, setup.program.unique_identifier);
-        setup.program.real_size_b = CalculateProgramSize(setup.program.code_b);
-        const u64 identifier_b = Common::CityHash64(
-            reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b);
-        boost::hash_combine(seed, identifier_b);
-        setup.program.unique_identifier = static_cast<u64>(seed);
-    }
+ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
+                                            BaseBindings base_bindings) const {
+    return {unique_identifier, base_bindings, primitive_mode};
 }
 
 ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
 
-void ShaderCacheOpenGL::LoadDiskCache() {}
+void ShaderCacheOpenGL::LoadDiskCache() {
+    std::vector<ShaderDiskCacheRaw> raws;
+    std::vector<ShaderDiskCacheUsage> usages;
+    if (!disk_cache.LoadTransferable(raws, usages)) {
+        return;
+    }
+
+    std::vector<ShaderDiskCachePrecompiledEntry> precompiled = disk_cache.LoadPrecompiled();
+    const auto SearchPrecompiled = [&precompiled](const ShaderDiskCacheUsage& usage) {
+        return std::find_if(
+            precompiled.begin(), precompiled.end(),
+            [&usage](const auto& precompiled_entry) { return precompiled_entry.usage == usage; });
+    };
+
+    const std::set<GLenum> supported_formats{GetSupportedFormats()};
+    const auto unspecialized{GenerateUnspecializedShaders(raws)};
+
+    // Build shaders
+    for (std::size_t i = 0; i < usages.size(); ++i) {
+        const auto& usage{usages[i]};
+        LOG_INFO(Render_OpenGL, "Building shader {:016x} ({} of {})", usage.unique_identifier,
+                 i + 1, usages.size());
+
+        const auto& unspec{unspecialized.at(usage.unique_identifier)};
+
+        const auto precompiled_it = SearchPrecompiled(usage);
+        const bool is_precompiled = precompiled_it != precompiled.end();
+
+        CachedProgram shader;
+        if (is_precompiled) {
+            shader = GeneratePrecompiledProgram(precompiled, *precompiled_it, supported_formats);
+        }
+        if (!shader) {
+            shader = SpecializeShader(unspec.code, unspec.entries, unspec.program_type,
+                                      usage.bindings, usage.primitive, true);
+        }
+        precompiled_programs.insert({usage, std::move(shader)});
+    }
+
+    // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before
+    // precompiling them
+
+    for (std::size_t i = 0; i < usages.size(); ++i) {
+        const auto& usage{usages[i]};
+        if (SearchPrecompiled(usage) == precompiled.end()) {
+            const auto& program = precompiled_programs.at(usage);
+            disk_cache.SavePrecompiled(usage, program->handle);
+        }
+    }
+}
+
+CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
+    std::vector<ShaderDiskCachePrecompiledEntry>& precompiled,
+    const ShaderDiskCachePrecompiledEntry& precompiled_entry,
+    const std::set<GLenum>& supported_formats) {
+
+    if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) {
+        LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing");
+        disk_cache.InvalidatePrecompiled();
+        precompiled.clear();
+        return {};
+    }
+
+    CachedProgram shader = std::make_shared<OGLProgram>();
+    shader->handle = glCreateProgram();
+    glProgramBinary(shader->handle, precompiled_entry.binary_format,
+                    precompiled_entry.binary.data(),
+                    static_cast<GLsizei>(precompiled_entry.binary.size()));
+
+    GLint link_status{};
+    glGetProgramiv(shader->handle, GL_LINK_STATUS, &link_status);
+    if (link_status == GL_FALSE) {
+        LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver - removing");
+        disk_cache.InvalidatePrecompiled();
+        precompiled.clear();
+
+        shader.reset();
+    }
+
+    return shader;
+}
+
+std::map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecializedShaders(
+    const std::vector<ShaderDiskCacheRaw>& raws) {
+
+    std::map<u64, UnspecializedShader> unspecialized;
+    for (const auto& raw : raws) {
+        const u64 calculated_hash =
+            GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
+        if (raw.GetUniqueIdentifier() != calculated_hash) {
+            LOG_ERROR(
+                Render_OpenGL,
+                "Invalid hash in entry={:016x} (obtained hash={:016x}) - removing shader cache",
+                raw.GetUniqueIdentifier(), calculated_hash);
+            disk_cache.InvalidateTransferable();
+            return {};
+        }
+
+        auto result =
+            CreateProgram(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
+
+        precompiled_shaders.insert({raw.GetUniqueIdentifier(), result});
+
+        unspecialized.insert(
+            {raw.GetUniqueIdentifier(),
+             {std::move(result.first), std::move(result.second), raw.GetProgramType()}});
+    }
+    return unspecialized;
+}
 
 Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
     if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
@@ -239,7 +463,23 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
 
     if (!shader) {
         // No shader found - create a new one
-        shader = std::make_shared<CachedShader>(program_addr, program);
+        ProgramCode program_code = GetShaderCode(program_addr);
+        ProgramCode program_code_b;
+        if (program == Maxwell::ShaderProgram::VertexA) {
+            program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB));
+        }
+        const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
+
+        const auto found = precompiled_shaders.find(unique_identifier);
+        if (found != precompiled_shaders.end()) {
+            shader =
+                std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
+                                               precompiled_programs, found->second);
+        } else {
+            shader = std::make_shared<CachedShader>(
+                program_addr, unique_identifier, program, disk_cache, precompiled_programs,
+                std::move(program_code), std::move(program_code_b));
+        }
         Register(shader);
     }
 
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 18fb80bcc..763a47bce 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -7,6 +7,7 @@
 #include <array>
 #include <map>
 #include <memory>
+#include <set>
 #include <tuple>
 
 #include <glad/glad.h>
@@ -23,13 +24,25 @@ namespace OpenGL {
 
 class CachedShader;
 class RasterizerOpenGL;
+struct UnspecializedShader;
 
 using Shader = std::shared_ptr<CachedShader>;
+using CachedProgram = std::shared_ptr<OGLProgram>;
 using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using PrecompiledPrograms = std::map<ShaderDiskCacheUsage, CachedProgram>;
+using PrecompiledShaders = std::map<u64, GLShader::ProgramResult>;
 
 class CachedShader final : public RasterizerCacheObject {
 public:
-    CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
+    explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
+                          ShaderDiskCacheOpenGL& disk_cache,
+                          const PrecompiledPrograms& precompiled_programs,
+                          ProgramCode&& program_code, ProgramCode&& program_code_b);
+
+    explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
+                          ShaderDiskCacheOpenGL& disk_cache,
+                          const PrecompiledPrograms& precompiled_programs,
+                          GLShader::ProgramResult result);
 
     VAddr GetAddr() const override {
         return addr;
@@ -56,33 +69,35 @@ private:
     // declared by the hardware. Workaround this issue by generating a different shader per input
     // topology class.
     struct GeometryPrograms {
-        OGLProgram points;
-        OGLProgram lines;
-        OGLProgram lines_adjacency;
-        OGLProgram triangles;
-        OGLProgram triangles_adjacency;
+        CachedProgram points;
+        CachedProgram lines;
+        CachedProgram lines_adjacency;
+        CachedProgram triangles;
+        CachedProgram triangles_adjacency;
     };
 
-    std::string AllocateBindings(BaseBindings base_bindings);
-
     GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings);
 
     /// Generates a geometry shader or returns one that already exists.
-    GLuint LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings,
-                               const std::string& glsl_topology, u32 max_vertices,
-                               const std::string& debug_name);
+    GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
+                               GLenum primitive_mode);
 
-    void CalculateProperties();
+    CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const;
+
+    ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
+
+    const VAddr addr;
+    const u64 unique_identifier;
+    const Maxwell::ShaderProgram program_type;
+    ShaderDiskCacheOpenGL& disk_cache;
+    const PrecompiledPrograms& precompiled_programs;
 
-    VAddr addr{};
     std::size_t shader_length{};
-    Maxwell::ShaderProgram program_type{};
-    GLShader::ShaderSetup setup;
     GLShader::ShaderEntries entries;
 
     std::string code;
 
-    std::map<BaseBindings, OGLProgram> programs;
+    std::map<BaseBindings, CachedProgram> programs;
     std::map<BaseBindings, GeometryPrograms> geometry_programs;
 
     std::map<u32, GLuint> cbuf_resource_cache;
@@ -101,7 +116,19 @@ public:
     Shader GetStageProgram(Maxwell::ShaderProgram program);
 
 private:
+    std::map<u64, UnspecializedShader> GenerateUnspecializedShaders(
+        const std::vector<ShaderDiskCacheRaw>& raws);
+
+    CachedProgram GeneratePrecompiledProgram(
+        std::vector<ShaderDiskCachePrecompiledEntry>& precompiled,
+        const ShaderDiskCachePrecompiledEntry& precompiled_entry,
+        const std::set<GLenum>& supported_formats);
+
     std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
+
+    ShaderDiskCacheOpenGL disk_cache;
+    PrecompiledShaders precompiled_shaders;
+    PrecompiledPrograms precompiled_programs;
 };
 
 } // namespace OpenGL