diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a44bbfae8a..7b836cc94f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -79,6 +79,26 @@ struct DrawParameters {
     }
 };
 
+struct FramebufferCacheKey {
+    bool is_single_buffer = false;
+    bool stencil_enable = false;
+
+    std::array<GLenum, Maxwell::NumRenderTargets> color_attachments{};
+    std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors{};
+    u32 colors_count = 0;
+
+    GLuint zeta = 0;
+
+    auto Tie() const {
+        return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count,
+                        zeta);
+    }
+
+    bool operator<(const FramebufferCacheKey& rhs) const {
+        return Tie() < rhs.Tie();
+    }
+};
+
 RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
     : res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info},
       buffer_cache(*this, STREAM_BUFFER_SIZE) {
@@ -103,9 +123,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
 
     OpenGLState::ApplyDefaultState();
 
-    // Create render framebuffer
-    framebuffer.Create();
-
     shader_program_manager = std::make_unique<GLShader::ProgramManager>();
     state.draw.shader_program = 0;
     state.Apply();
@@ -363,6 +380,44 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
     }
 }
 
+void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
+                                              OpenGLState& current_state) {
+    const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey);
+    auto& framebuffer = entry->second;
+
+    if (is_cache_miss)
+        framebuffer.Create();
+
+    current_state.draw.draw_framebuffer = framebuffer.handle;
+    current_state.ApplyFramebufferState();
+
+    if (!is_cache_miss)
+        return;
+
+    if (fbkey.is_single_buffer) {
+        if (fbkey.color_attachments[0] != GL_NONE) {
+            glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0],
+                                 0);
+        }
+        glDrawBuffer(fbkey.color_attachments[0]);
+    } else {
+        for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
+            if (fbkey.colors[index]) {
+                glFramebufferTexture(GL_DRAW_FRAMEBUFFER,
+                                     GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
+                                     fbkey.colors[index], 0);
+            }
+        }
+        glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data());
+    }
+
+    if (fbkey.zeta) {
+        GLenum zeta_attachment =
+            fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
+        glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0);
+    }
+}
+
 std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
@@ -446,10 +501,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
     ASSERT_MSG(regs.rt_separate_frag_data == 0, "Unimplemented");
 
     // Bind the framebuffer surfaces
-    current_state.draw.draw_framebuffer = framebuffer.handle;
-    current_state.ApplyFramebufferState();
     current_state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0;
 
+    FramebufferCacheKey fbkey;
+
     if (using_color_fb) {
         if (single_color_target) {
             // Used when just a single color attachment is enabled, e.g. for clearing a color buffer
@@ -465,14 +520,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
                 state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
             }
 
-            glFramebufferTexture2D(
-                GL_DRAW_FRAMEBUFFER,
-                GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
-                color_surface != nullptr ? color_surface->Texture().handle : 0, 0);
-            glDrawBuffer(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target));
+            fbkey.is_single_buffer = true;
+            fbkey.color_attachments[0] =
+                GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target);
+            fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0;
         } else {
             // Multiple color attachments are enabled
-            std::array<GLenum, Maxwell::NumRenderTargets> buffers;
             for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
                 Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
 
@@ -487,22 +540,17 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
                         color_surface->GetSurfaceParams().srgb_conversion;
                 }
 
-                buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
-                glFramebufferTexture2D(
-                    GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
-                    GL_TEXTURE_2D, color_surface != nullptr ? color_surface->Texture().handle : 0,
-                    0);
+                fbkey.color_attachments[index] =
+                    GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
+                fbkey.colors[index] =
+                    color_surface != nullptr ? color_surface->Texture().handle : 0;
             }
-            glDrawBuffers(regs.rt_control.count, buffers.data());
+            fbkey.is_single_buffer = false;
+            fbkey.colors_count = regs.rt_control.count;
         }
     } else {
-        // No color attachments are enabled - zero out all of them
-        for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
-            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
-                                   GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), GL_TEXTURE_2D,
-                                   0, 0);
-        }
-        glDrawBuffer(GL_NONE);
+        // No color attachments are enabled - leave them as zero
+        fbkey.is_single_buffer = true;
     }
 
     if (depth_surface) {
@@ -510,22 +558,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
         // the shader doesn't actually write to it.
         depth_surface->MarkAsModified(true, res_cache);
 
-        if (regs.stencil_enable) {
-            // Attach both depth and stencil
-            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
-                                   depth_surface->Texture().handle, 0);
-        } else {
-            // Attach depth
-            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
-                                   depth_surface->Texture().handle, 0);
-            // Clear stencil attachment
-            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
-        }
-    } else {
-        // Clear both depth and stencil attachment
-        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
-                               0);
+        fbkey.zeta = depth_surface->Texture().handle;
+        fbkey.stencil_enable = regs.stencil_enable;
     }
+
+    SetupCachedFramebuffer(fbkey, current_state);
+
     SyncViewport(current_state);
 }
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 7ec9746b13..959e8df63f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -40,6 +40,7 @@ namespace OpenGL {
 
 struct ScreenInfo;
 struct DrawParameters;
+struct FramebufferCacheKey;
 
 class RasterizerOpenGL : public VideoCore::RasterizerInterface {
 public:
@@ -211,11 +212,12 @@ private:
              OGLVertexArray>
         vertex_array_cache;
 
+    std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache;
+
     std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
 
     static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
     OGLBufferCache buffer_cache;
-    OGLFramebuffer framebuffer;
     PrimitiveAssembler primitive_assembler{buffer_cache};
     GLint uniform_buffer_alignment;
 
@@ -230,6 +232,8 @@ private:
 
     void SetupShaders(GLenum primitive_mode);
 
+    void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state);
+
     enum class AccelDraw { Disabled, Arrays, Indexed };
     AccelDraw accelerate_draw = AccelDraw::Disabled;