diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 164af9197..6d58bcdda 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -45,14 +45,10 @@ public:
 
     /**
      * Presentation thread calls this to get the latest frame available to present. If there is no
-     * frame available after timeout, returns nullptr
+     * frame available after timeout, returns the previous frame. If there is no previous frame it
+     * returns nullptr
      */
     virtual Frontend::Frame* TryGetPresentFrame(int timeout_ms) = 0;
-
-    /**
-     * Presentation thread calls this after swap to release the frame and add it back to the queue
-     */
-    virtual void ReleasePresentFrame(Frame* frame) = 0;
 };
 
 /**
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 1cffc8ea7..33552c6bd 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -15,6 +15,23 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R
 
 namespace OpenGL {
 
+void OGLRenderbuffer::Create() {
+    if (handle != 0)
+        return;
+
+    MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
+    glGenRenderbuffers(1, &handle);
+}
+
+void OGLRenderbuffer::Release() {
+    if (handle == 0)
+        return;
+
+    MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
+    glDeleteRenderbuffers(1, &handle);
+    handle = 0;
+}
+
 void OGLTexture::Create() {
     if (handle != 0)
         return;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index ce7f7fbfb..7f94c8a39 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -12,6 +12,31 @@
 
 namespace OpenGL {
 
+class OGLRenderbuffer : private NonCopyable {
+public:
+    OGLRenderbuffer() = default;
+
+    OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
+    ~OGLRenderbuffer() {
+        Release();
+    }
+
+    OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept {
+        Release();
+        handle = std::exchange(o.handle, 0);
+        return *this;
+    }
+
+    /// Creates a new internal OpenGL resource and stores the handle
+    void Create();
+
+    /// Deletes the internal OpenGL resource
+    void Release();
+
+    GLuint handle = 0;
+};
+
 class OGLTexture : private NonCopyable {
 public:
     OGLTexture() = default;
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 17bf2b2f7..83e6f1678 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -89,6 +89,8 @@ OpenGLState::OpenGLState() {
     viewport.height = 0;
 
     clip_distance = {};
+
+    renderbuffer = 0;
 }
 
 void OpenGLState::Apply() const {
@@ -337,6 +339,10 @@ void OpenGLState::Apply() const {
         }
     }
 
+    if (renderbuffer != cur_state.renderbuffer) {
+        glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+    }
+
     cur_state = *this;
 }
 
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index b1842a820..e2ccc9f51 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -144,6 +144,8 @@ public:
 
     std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
 
+    GLuint renderbuffer;
+
     OpenGLState();
 
     /// Get the currently active OpenGL state
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 1452cd6de..555f80130 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -38,7 +38,7 @@ struct Frame {
     u32 width{};                      /// Width of the frame (to detect resize)
     u32 height{};                     /// Height of the frame
     bool color_reloaded = false;      /// Texture attachment was recreated (ie: resized)
-    OpenGL::OGLTexture color{};       /// Texture shared between the render/present FBO
+    OpenGL::OGLRenderbuffer color{};  /// Buffer shared between the render/present FBO
     OpenGL::OGLFramebuffer render{};  /// FBO created on the render thread
     OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
     GLsync render_fence{};            /// Fence created on the render thread
@@ -48,7 +48,10 @@ struct Frame {
 
 namespace OpenGL {
 
-constexpr std::size_t SWAP_CHAIN_SIZE = 5;
+// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
+// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger
+// number but 8 seems to be a good trade off for now
+constexpr std::size_t SWAP_CHAIN_SIZE = 8;
 
 class OGLTextureMailbox : public Frontend::TextureMailbox {
 public:
@@ -58,6 +61,7 @@ public:
     std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
     std::deque<Frontend::Frame*> free_queue{};
     std::deque<Frontend::Frame*> present_queue{};
+    Frontend::Frame* previous_frame = nullptr;
 
     OGLTextureMailbox() {
         for (auto& frame : swap_chain) {
@@ -65,7 +69,10 @@ public:
         }
     }
 
-    ~OGLTextureMailbox() override = default;
+    ~OGLTextureMailbox() override {
+        // lock the mutex and clear out the present and free_queues and notify any people who are
+        // blocked to prevent deadlock on shutdown
+    }
 
     void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
         frame->present.Release();
@@ -73,12 +80,12 @@ public:
         GLint previous_draw_fbo{};
         glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
         glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
-        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-                               frame->color.handle, 0);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+                                  frame->color.handle);
         if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
             LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
         }
-        glBindFramebuffer(GL_FRAMEBUFFER, previous_draw_fbo);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
         frame->color_reloaded = false;
     }
 
@@ -89,14 +96,9 @@ public:
         // Recreate the color texture attachment
         frame->color.Release();
         frame->color.Create();
-        state.texture_units[0].texture_2d = frame->color.handle;
+        state.renderbuffer = frame->color.handle;
         state.Apply();
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, width, height);
 
         // Recreate the FBO for the render target
         frame->render.Release();
@@ -104,8 +106,8 @@ public:
         state.draw.read_framebuffer = frame->render.handle;
         state.draw.draw_framebuffer = frame->render.handle;
         state.Apply();
-        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-                               frame->color.handle, 0);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+                                  frame->color.handle);
         if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
             LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
         }
@@ -138,8 +140,15 @@ public:
                             [&] { return !present_queue.empty(); });
         if (present_queue.empty()) {
             // timed out waiting for a frame to draw so return nullptr
-            return nullptr;
+            return previous_frame;
         }
+
+        // free the previous frame and add it back to the free queue
+        if (previous_frame) {
+            free_queue.push_back(previous_frame);
+            free_cv.notify_one();
+        }
+
         // the newest entries are pushed to the front of the queue
         Frontend::Frame* frame = present_queue.front();
         present_queue.pop_front();
@@ -149,14 +158,9 @@ public:
             free_cv.notify_one();
         }
         present_queue.clear();
+        previous_frame = frame;
         return frame;
     }
-
-    void ReleasePresentFrame(Frontend::Frame* frame) override {
-        std::unique_lock<std::mutex> lock(swap_chain_lock);
-        free_queue.push_back(frame);
-        free_cv.notify_one();
-    }
 };
 
 static const char vertex_shader[] = R"(
@@ -838,12 +842,15 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
     const auto& layout = render_window.GetFramebufferLayout();
     auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
     if (!frame) {
-        LOG_CRITICAL(Render_OpenGL, "Try returned no frame to present");
+        LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
         return;
     }
+
+    glClear(GL_COLOR_BUFFER_BIT);
+
     // Recreate the presentation FBO if the color attachment was changed
     if (frame->color_reloaded) {
-        LOG_CRITICAL(Render_OpenGL, "Reloading present frame");
+        LOG_DEBUG(Render_OpenGL, "Reloading present frame");
         render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
     }
     glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
@@ -860,7 +867,6 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
     /* insert fence for the main thread to block on */
     frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
     glFlush();
-    render_window.mailbox->ReleasePresentFrame(frame);
 }
 
 void RendererOpenGL::PresentComplete() {