From 80de01a5b4a7f57ec7850079fbd38fac76b9d08f Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Wed, 3 Jan 2024 22:46:59 -0500
Subject: [PATCH] video_core: simplify accelerated surface fetch and crop
 handling between APIs

---
 src/video_core/CMakeLists.txt                 |   1 +
 src/video_core/framebuffer_config.cpp         |  55 ++++
 src/video_core/framebuffer_config.h           |   3 +
 src/video_core/rasterizer_interface.h         |   6 -
 .../renderer_null/null_rasterizer.cpp         |   4 -
 .../renderer_null/null_rasterizer.h           |   2 -
 src/video_core/renderer_opengl/gl_fsr.cpp     |  21 +-
 src/video_core/renderer_opengl/gl_fsr.h       |   2 +-
 .../renderer_opengl/gl_rasterizer.cpp         |  28 +--
 .../renderer_opengl/gl_rasterizer.h           |  13 +-
 .../renderer_opengl/renderer_opengl.cpp       | 235 ++++++++----------
 .../renderer_opengl/renderer_opengl.h         |  32 +--
 .../renderer_vulkan/renderer_vulkan.cpp       |  22 +-
 .../renderer_vulkan/renderer_vulkan.h         |   4 +-
 .../renderer_vulkan/vk_blit_screen.cpp        |  89 ++-----
 .../renderer_vulkan/vk_blit_screen.h          |  20 +-
 .../renderer_vulkan/vk_rasterizer.cpp         |  27 +-
 .../renderer_vulkan/vk_rasterizer.h           |  14 +-
 18 files changed, 262 insertions(+), 316 deletions(-)
 create mode 100644 src/video_core/framebuffer_config.cpp

diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 0755ba772a..36aa7bb66f 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -55,6 +55,7 @@ add_library(video_core STATIC
     engines/maxwell_dma.h
     engines/puller.cpp
     engines/puller.h
+    framebuffer_config.cpp
     framebuffer_config.h
     fsr.cpp
     fsr.h
diff --git a/src/video_core/framebuffer_config.cpp b/src/video_core/framebuffer_config.cpp
new file mode 100644
index 0000000000..e28d41f84c
--- /dev/null
+++ b/src/video_core/framebuffer_config.cpp
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "video_core/framebuffer_config.h"
+
+namespace Tegra {
+
+Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
+                                     u32 texture_height) {
+    f32 left, top, right, bottom;
+
+    if (!framebuffer.crop_rect.IsEmpty()) {
+        // If crop rectangle is not empty, apply properties from rectangle.
+        left = static_cast<f32>(framebuffer.crop_rect.left);
+        top = static_cast<f32>(framebuffer.crop_rect.top);
+        right = static_cast<f32>(framebuffer.crop_rect.right);
+        bottom = static_cast<f32>(framebuffer.crop_rect.bottom);
+    } else {
+        // Otherwise, fall back to framebuffer dimensions.
+        left = 0;
+        top = 0;
+        right = static_cast<f32>(framebuffer.width);
+        bottom = static_cast<f32>(framebuffer.height);
+    }
+
+    // Apply transformation flags.
+    auto framebuffer_transform_flags = framebuffer.transform_flags;
+
+    if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipH)) {
+        // Switch left and right.
+        std::swap(left, right);
+    }
+    if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipV)) {
+        // Switch top and bottom.
+        std::swap(top, bottom);
+    }
+
+    framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipH;
+    framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipV;
+    if (True(framebuffer_transform_flags)) {
+        UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}",
+                          static_cast<u32>(framebuffer_transform_flags));
+    }
+
+    // Normalize coordinate space.
+    left /= static_cast<f32>(texture_width);
+    top /= static_cast<f32>(texture_height);
+    right /= static_cast<f32>(texture_width);
+    bottom /= static_cast<f32>(texture_height);
+
+    return Common::Rectangle<f32>(left, top, right, bottom);
+}
+
+} // namespace Tegra
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index 856f4bd529..10ddc75a71 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -24,4 +24,7 @@ struct FramebufferConfig {
     Common::Rectangle<int> crop_rect;
 };
 
+Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
+                                     u32 texture_height);
+
 } // namespace Tegra
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 8fa4e4d9a2..6e2eccfbf0 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -155,12 +155,6 @@ public:
     virtual void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
                                           std::span<const u8> memory) = 0;
 
-    /// Attempt to use a faster method to display the framebuffer to screen
-    [[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config,
-                                                 DAddr framebuffer_addr, u32 pixel_stride) {
-        return false;
-    }
-
     /// Initialize disk cached resources for the game being emulated
     virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                                    const DiskResourceLoadCallback& callback) {}
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index abfabb65bb..a5cda0f389 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -92,10 +92,6 @@ bool RasterizerNull::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surfac
 }
 void RasterizerNull::AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
                                               std::span<const u8> memory) {}
-bool RasterizerNull::AccelerateDisplay(const Tegra::FramebufferConfig& config,
-                                       DAddr framebuffer_addr, u32 pixel_stride) {
-    return true;
-}
 void RasterizerNull::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                                        const VideoCore::DiskResourceLoadCallback& callback) {}
 void RasterizerNull::InitializeChannel(Tegra::Control::ChannelState& channel) {
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index a5789604f9..c7f5849c75 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -77,8 +77,6 @@ public:
     Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
     void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
                                   std::span<const u8> memory) override;
-    bool AccelerateDisplay(const Tegra::FramebufferConfig& config, DAddr framebuffer_addr,
-                           u32 pixel_stride) override;
     void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                            const VideoCore::DiskResourceLoadCallback& callback) override;
     void InitializeChannel(Tegra::Control::ChannelState& channel) override;
diff --git a/src/video_core/renderer_opengl/gl_fsr.cpp b/src/video_core/renderer_opengl/gl_fsr.cpp
index 77262dcf16..429dcdc6ca 100644
--- a/src/video_core/renderer_opengl/gl_fsr.cpp
+++ b/src/video_core/renderer_opengl/gl_fsr.cpp
@@ -25,7 +25,7 @@ FSR::~FSR() = default;
 
 void FSR::Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen,
                u32 input_image_width, u32 input_image_height,
-               const Common::Rectangle<int>& crop_rect) {
+               const Common::Rectangle<f32>& crop_rect) {
 
     const auto output_image_width = screen.GetWidth();
     const auto output_image_height = screen.GetHeight();
@@ -57,14 +57,19 @@ void FSR::Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& sc
     glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(output_image_width),
                        static_cast<GLfloat>(output_image_height));
 
-    FsrConstants constants;
-    FsrEasuConOffset(
-        constants.data() + 0, constants.data() + 4, constants.data() + 8, constants.data() + 12,
+    const f32 input_width = static_cast<f32>(input_image_width);
+    const f32 input_height = static_cast<f32>(input_image_height);
+    const f32 output_width = static_cast<f32>(screen.GetWidth());
+    const f32 output_height = static_cast<f32>(screen.GetHeight());
+    const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_width;
+    const f32 viewport_x = crop_rect.left * input_width;
+    const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_height;
+    const f32 viewport_y = crop_rect.top * input_height;
 
-        static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()),
-        static_cast<f32>(input_image_width), static_cast<f32>(input_image_height),
-        static_cast<f32>(output_image_width), static_cast<f32>(output_image_height),
-        static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top));
+    FsrConstants constants;
+    FsrEasuConOffset(constants.data() + 0, constants.data() + 4, constants.data() + 8,
+                     constants.data() + 12, viewport_width, viewport_height, input_width,
+                     input_height, output_width, output_height, viewport_x, viewport_y);
 
     glProgramUniform4uiv(fsr_easu_frag.handle, 0, sizeof(constants), std::data(constants));
 
diff --git a/src/video_core/renderer_opengl/gl_fsr.h b/src/video_core/renderer_opengl/gl_fsr.h
index 1f6ae3115f..a5092e3969 100644
--- a/src/video_core/renderer_opengl/gl_fsr.h
+++ b/src/video_core/renderer_opengl/gl_fsr.h
@@ -22,7 +22,7 @@ public:
 
     void Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen,
               u32 input_image_width, u32 input_image_height,
-              const Common::Rectangle<int>& crop_rect);
+              const Common::Rectangle<f32>& crop_rect);
 
     void InitBuffers();
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index d5354ef2d6..050a74cca1 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -71,10 +71,10 @@ std::optional<VideoCore::QueryType> MaxwellToVideoCoreQuery(VideoCommon::QueryTy
 
 RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
                                    Tegra::MaxwellDeviceMemoryManager& device_memory_,
-                                   const Device& device_, ScreenInfo& screen_info_,
-                                   ProgramManager& program_manager_, StateTracker& state_tracker_)
-    : gpu(gpu_), device_memory(device_memory_), device(device_), screen_info(screen_info_),
-      program_manager(program_manager_), state_tracker(state_tracker_),
+                                   const Device& device_, ProgramManager& program_manager_,
+                                   StateTracker& state_tracker_)
+    : gpu(gpu_), device_memory(device_memory_), device(device_), program_manager(program_manager_),
+      state_tracker(state_tracker_),
       texture_cache_runtime(device, program_manager, state_tracker, staging_buffer_pool),
       texture_cache(texture_cache_runtime, device_memory_),
       buffer_cache_runtime(device, staging_buffer_pool),
@@ -739,10 +739,10 @@ void RasterizerOpenGL::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si
     query_cache.InvalidateRegion(*cpu_addr, copy_size);
 }
 
-bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
-                                         DAddr framebuffer_addr, u32 pixel_stride) {
+std::optional<FramebufferTextureInfo> RasterizerOpenGL::AccelerateDisplay(
+    const Tegra::FramebufferConfig& config, DAddr framebuffer_addr, u32 pixel_stride) {
     if (framebuffer_addr == 0) {
-        return false;
+        return {};
     }
     MICROPROFILE_SCOPE(OpenGL_CacheManagement);
 
@@ -750,16 +750,14 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
     ImageView* const image_view{
         texture_cache.TryFindFramebufferImageView(config, framebuffer_addr)};
     if (!image_view) {
-        return false;
+        return {};
     }
-    // Verify that the cached surface is the same size and format as the requested framebuffer
-    // ASSERT_MSG(image_view->size.width == config.width, "Framebuffer width is different");
-    // ASSERT_MSG(image_view->size.height == config.height, "Framebuffer height is different");
 
-    screen_info.texture.width = image_view->size.width;
-    screen_info.texture.height = image_view->size.height;
-    screen_info.display_texture = image_view->Handle(Shader::TextureType::Color2D);
-    return true;
+    FramebufferTextureInfo info{};
+    info.display_texture = image_view->Handle(Shader::TextureType::Color2D);
+    info.width = image_view->size.width;
+    info.height = image_view->size.height;
+    return info;
 }
 
 void RasterizerOpenGL::SyncState() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 34aa735263..ee82d9f3a6 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -37,7 +37,7 @@ class MemoryManager;
 
 namespace OpenGL {
 
-struct ScreenInfo;
+struct FramebufferTextureInfo;
 struct ShaderEntries;
 
 struct BindlessSSBO {
@@ -76,8 +76,8 @@ class RasterizerOpenGL : public VideoCore::RasterizerInterface,
 public:
     explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
                               Tegra::MaxwellDeviceMemoryManager& device_memory_,
-                              const Device& device_, ScreenInfo& screen_info_,
-                              ProgramManager& program_manager_, StateTracker& state_tracker_);
+                              const Device& device_, ProgramManager& program_manager_,
+                              StateTracker& state_tracker_);
     ~RasterizerOpenGL() override;
 
     void Draw(bool is_indexed, u32 instance_count) override;
@@ -122,8 +122,6 @@ public:
     Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
     void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
                                   std::span<const u8> memory) override;
-    bool AccelerateDisplay(const Tegra::FramebufferConfig& config, DAddr framebuffer_addr,
-                           u32 pixel_stride) override;
     void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                            const VideoCore::DiskResourceLoadCallback& callback) override;
 
@@ -144,6 +142,10 @@ public:
         return true;
     }
 
+    std::optional<FramebufferTextureInfo> AccelerateDisplay(const Tegra::FramebufferConfig& config,
+                                                            VAddr framebuffer_addr,
+                                                            u32 pixel_stride);
+
 private:
     static constexpr size_t MAX_TEXTURES = 192;
     static constexpr size_t MAX_IMAGES = 48;
@@ -237,7 +239,6 @@ private:
     Tegra::MaxwellDeviceMemoryManager& device_memory;
 
     const Device& device;
-    ScreenInfo& screen_info;
     ProgramManager& program_manager;
     StateTracker& state_tracker;
 
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index b75376fdbc..ea5ed3e2fc 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -148,8 +148,7 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
     : RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
       emu_window{emu_window_}, device_memory{device_memory_}, gpu{gpu_}, device{emu_window_},
       state_tracker{}, program_manager{device},
-      rasterizer(emu_window, gpu, device_memory, device, screen_info, program_manager,
-                 state_tracker) {
+      rasterizer(emu_window, gpu, device_memory, device, program_manager, state_tracker) {
     if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
         glEnable(GL_DEBUG_OUTPUT);
         glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
@@ -184,11 +183,11 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
     if (!framebuffer) {
         return;
     }
-    PrepareRendertarget(framebuffer);
-    RenderScreenshot();
+
+    RenderScreenshot(*framebuffer);
 
     state_tracker.BindFramebuffer(0);
-    DrawScreen(emu_window.GetFramebufferLayout());
+    DrawScreen(*framebuffer, emu_window.GetFramebufferLayout());
 
     ++m_current_frame;
 
@@ -199,41 +198,37 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
     render_window.OnFrameDisplayed();
 }
 
-void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
-    if (!framebuffer) {
-        return;
-    }
+FramebufferTextureInfo RendererOpenGL::PrepareRenderTarget(
+    const Tegra::FramebufferConfig& framebuffer) {
     // If framebuffer is provided, reload it from memory to a texture
-    if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
-        screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
-        screen_info.texture.pixel_format != framebuffer->pixel_format ||
+    if (framebuffer_texture.width != static_cast<GLsizei>(framebuffer.width) ||
+        framebuffer_texture.height != static_cast<GLsizei>(framebuffer.height) ||
+        framebuffer_texture.pixel_format != framebuffer.pixel_format ||
         gl_framebuffer_data.empty()) {
         // Reallocate texture if the framebuffer size has changed.
         // This is expected to not happen very often and hence should not be a
         // performance problem.
-        ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
+        ConfigureFramebufferTexture(framebuffer);
     }
 
-    // Load the framebuffer from memory, draw it to the screen, and swap buffers
-    LoadFBToScreenInfo(*framebuffer);
+    // Load the framebuffer from memory if needed
+    return LoadFBToScreenInfo(framebuffer);
 }
 
-void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
-    // Framebuffer orientation handling
-    framebuffer_transform_flags = framebuffer.transform_flags;
-    framebuffer_crop_rect = framebuffer.crop_rect;
-    framebuffer_width = framebuffer.width;
-    framebuffer_height = framebuffer.height;
-
+FramebufferTextureInfo RendererOpenGL::LoadFBToScreenInfo(
+    const Tegra::FramebufferConfig& framebuffer) {
     const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
-    screen_info.was_accelerated =
+    const auto accelerated_info =
         rasterizer.AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride);
-    if (screen_info.was_accelerated) {
-        return;
+    if (accelerated_info) {
+        return *accelerated_info;
     }
 
     // Reset the screen info's display texture to its own permanent texture
-    screen_info.display_texture = screen_info.texture.resource.handle;
+    FramebufferTextureInfo info{};
+    info.display_texture = framebuffer_texture.resource.handle;
+    info.width = framebuffer.width;
+    info.height = framebuffer.height;
 
     // TODO(Rodrigo): Read this from HLE
     constexpr u32 block_height_log2 = 4;
@@ -256,17 +251,13 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
     //       they differ from the LCD resolution.
     // TODO: Applications could theoretically crash yuzu here by specifying too large
     //       framebuffer sizes. We should make sure that this cannot happen.
-    glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width,
-                        framebuffer.height, screen_info.texture.gl_format,
-                        screen_info.texture.gl_type, gl_framebuffer_data.data());
+    glTextureSubImage2D(framebuffer_texture.resource.handle, 0, 0, 0, framebuffer.width,
+                        framebuffer.height, framebuffer_texture.gl_format,
+                        framebuffer_texture.gl_type, gl_framebuffer_data.data());
 
     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-}
 
-void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
-                                                const TextureInfo& texture) {
-    const u8 framebuffer_data[4] = {color_a, color_b, color_g, color_r};
-    glClearTexImage(texture.resource.handle, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data);
+    return info;
 }
 
 void RendererOpenGL::InitOpenGLObjects() {
@@ -343,15 +334,15 @@ void RendererOpenGL::InitOpenGLObjects() {
     glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
 
     // Allocate textures for the screen
-    screen_info.texture.resource.Create(GL_TEXTURE_2D);
+    framebuffer_texture.resource.Create(GL_TEXTURE_2D);
 
-    const GLuint texture = screen_info.texture.resource.handle;
+    const GLuint texture = framebuffer_texture.resource.handle;
     glTextureStorage2D(texture, 1, GL_RGBA8, 1, 1);
 
-    screen_info.display_texture = screen_info.texture.resource.handle;
-
     // Clear screen to black
-    LoadColorToActiveGLTexture(0, 0, 0, 0, screen_info.texture);
+    const u8 framebuffer_data[4] = {0, 0, 0, 0};
+    glClearTexImage(framebuffer_texture.resource.handle, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                    framebuffer_data);
 
     aa_framebuffer.Create();
 
@@ -380,60 +371,65 @@ void RendererOpenGL::AddTelemetryFields() {
     telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
 }
 
-void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
-                                                 const Tegra::FramebufferConfig& framebuffer) {
-    texture.width = framebuffer.width;
-    texture.height = framebuffer.height;
-    texture.pixel_format = framebuffer.pixel_format;
+void RendererOpenGL::ConfigureFramebufferTexture(const Tegra::FramebufferConfig& framebuffer) {
+    framebuffer_texture.width = framebuffer.width;
+    framebuffer_texture.height = framebuffer.height;
+    framebuffer_texture.pixel_format = framebuffer.pixel_format;
 
     const auto pixel_format{
         VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
     const u32 bytes_per_pixel{VideoCore::Surface::BytesPerBlock(pixel_format)};
-    gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel);
+    gl_framebuffer_data.resize(framebuffer_texture.width * framebuffer_texture.height *
+                               bytes_per_pixel);
 
     GLint internal_format;
     switch (framebuffer.pixel_format) {
     case Service::android::PixelFormat::Rgba8888:
         internal_format = GL_RGBA8;
-        texture.gl_format = GL_RGBA;
-        texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+        framebuffer_texture.gl_format = GL_RGBA;
+        framebuffer_texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
         break;
     case Service::android::PixelFormat::Rgb565:
         internal_format = GL_RGB565;
-        texture.gl_format = GL_RGB;
-        texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
+        framebuffer_texture.gl_format = GL_RGB;
+        framebuffer_texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
         break;
     default:
         internal_format = GL_RGBA8;
-        texture.gl_format = GL_RGBA;
-        texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+        framebuffer_texture.gl_format = GL_RGBA;
+        framebuffer_texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
         // UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
         //                   static_cast<u32>(framebuffer.pixel_format));
         break;
     }
 
-    texture.resource.Release();
-    texture.resource.Create(GL_TEXTURE_2D);
-    glTextureStorage2D(texture.resource.handle, 1, internal_format, texture.width, texture.height);
+    framebuffer_texture.resource.Release();
+    framebuffer_texture.resource.Create(GL_TEXTURE_2D);
+    glTextureStorage2D(framebuffer_texture.resource.handle, 1, internal_format,
+                       framebuffer_texture.width, framebuffer_texture.height);
     aa_texture.Release();
     aa_texture.Create(GL_TEXTURE_2D);
     glTextureStorage2D(aa_texture.handle, 1, GL_RGBA16F,
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.width),
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.height));
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.height));
     glNamedFramebufferTexture(aa_framebuffer.handle, GL_COLOR_ATTACHMENT0, aa_texture.handle, 0);
     smaa_edges_tex.Release();
     smaa_edges_tex.Create(GL_TEXTURE_2D);
     glTextureStorage2D(smaa_edges_tex.handle, 1, GL_RG16F,
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.width),
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.height));
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.height));
     smaa_blend_tex.Release();
     smaa_blend_tex.Create(GL_TEXTURE_2D);
     glTextureStorage2D(smaa_blend_tex.handle, 1, GL_RGBA16F,
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.width),
-                       Settings::values.resolution_info.ScaleUp(screen_info.texture.height));
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.width),
+                       Settings::values.resolution_info.ScaleUp(framebuffer_texture.height));
 }
 
-void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
+void RendererOpenGL::DrawScreen(const Tegra::FramebufferConfig& framebuffer,
+                                const Layout::FramebufferLayout& layout) {
+    FramebufferTextureInfo info = PrepareRenderTarget(framebuffer);
+    const auto crop = Tegra::NormalizeCrop(framebuffer, info.width, info.height);
+
     // TODO: Signal state tracker about these changes
     state_tracker.NotifyScreenDrawVertexArray();
     state_tracker.NotifyPolygonModes();
@@ -469,7 +465,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
     glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
     glDepthRangeIndexed(0, 0.0, 0.0);
 
-    glBindTextureUnit(0, screen_info.display_texture);
+    glBindTextureUnit(0, info.display_texture);
 
     auto anti_aliasing = Settings::values.anti_aliasing.GetValue();
     if (anti_aliasing >= Settings::AntiAliasing::MaxEnum) {
@@ -480,22 +476,22 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
 
     if (anti_aliasing != Settings::AntiAliasing::None) {
         glEnablei(GL_SCISSOR_TEST, 0);
-        auto viewport_width = screen_info.texture.width;
-        auto scissor_width = framebuffer_crop_rect.GetWidth();
+        auto viewport_width = info.width;
+        auto scissor_width = static_cast<u32>(crop.GetWidth());
         if (scissor_width <= 0) {
             scissor_width = viewport_width;
         }
-        auto viewport_height = screen_info.texture.height;
-        auto scissor_height = framebuffer_crop_rect.GetHeight();
+        auto viewport_height = info.height;
+        auto scissor_height = static_cast<u32>(crop.GetHeight());
         if (scissor_height <= 0) {
             scissor_height = viewport_height;
         }
-        if (screen_info.was_accelerated) {
-            viewport_width = Settings::values.resolution_info.ScaleUp(viewport_width);
-            scissor_width = Settings::values.resolution_info.ScaleUp(scissor_width);
-            viewport_height = Settings::values.resolution_info.ScaleUp(viewport_height);
-            scissor_height = Settings::values.resolution_info.ScaleUp(scissor_height);
-        }
+
+        viewport_width = Settings::values.resolution_info.ScaleUp(viewport_width);
+        scissor_width = Settings::values.resolution_info.ScaleUp(scissor_width);
+        viewport_height = Settings::values.resolution_info.ScaleUp(viewport_height);
+        scissor_height = Settings::values.resolution_info.ScaleUp(scissor_height);
+
         glScissorIndexed(0, 0, 0, scissor_width, scissor_height);
         glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(viewport_width),
                            static_cast<GLfloat>(viewport_height));
@@ -536,7 +532,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
                                                 smaa_blending_weight_calculation_frag.handle);
             glDrawArrays(GL_TRIANGLES, 0, 3);
 
-            glBindTextureUnit(0, screen_info.display_texture);
+            glBindTextureUnit(0, info.display_texture);
             glBindTextureUnit(1, smaa_blend_tex.handle);
             glNamedFramebufferTexture(aa_framebuffer.handle, GL_COLOR_ATTACHMENT0,
                                       aa_texture.handle, 0);
@@ -561,18 +557,10 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
             fsr->InitBuffers();
         }
 
-        auto crop_rect = framebuffer_crop_rect;
-        if (crop_rect.GetWidth() == 0) {
-            crop_rect.right = framebuffer_width;
-        }
-        if (crop_rect.GetHeight() == 0) {
-            crop_rect.bottom = framebuffer_height;
-        }
-        crop_rect = crop_rect.Scale(Settings::values.resolution_info.up_factor);
-        const auto fsr_input_width = Settings::values.resolution_info.ScaleUp(framebuffer_width);
-        const auto fsr_input_height = Settings::values.resolution_info.ScaleUp(framebuffer_height);
+        const auto fsr_input_width = Settings::values.resolution_info.ScaleUp(info.width);
+        const auto fsr_input_height = Settings::values.resolution_info.ScaleUp(info.height);
         glBindSampler(0, present_sampler.handle);
-        fsr->Draw(program_manager, layout.screen, fsr_input_width, fsr_input_height, crop_rect);
+        fsr->Draw(program_manager, layout.screen, fsr_input_width, fsr_input_height, crop);
     } else {
         if (fsr->AreBuffersInitialized()) {
             fsr->ReleaseBuffers();
@@ -603,61 +591,34 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
     glProgramUniformMatrix3x2fv(present_vertex.handle, ModelViewMatrixLocation, 1, GL_FALSE,
                                 ortho_matrix.data());
 
-    const auto& texcoords = screen_info.display_texcoords;
-    auto left = texcoords.left;
-    auto right = texcoords.right;
-    if (framebuffer_transform_flags != Service::android::BufferTransformFlags::Unset) {
-        if (framebuffer_transform_flags == Service::android::BufferTransformFlags::FlipV) {
-            // Flip the framebuffer vertically
-            left = texcoords.right;
-            right = texcoords.left;
-        } else {
-            // Other transformations are unsupported
-            LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}",
-                         framebuffer_transform_flags);
-            UNIMPLEMENTED();
-        }
-    }
-
-    ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented");
-
-    f32 left_start{};
-    if (framebuffer_crop_rect.Top() > 0) {
-        left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
-                     static_cast<f32>(framebuffer_crop_rect.Bottom());
-    }
-    f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width);
-    f32 scale_v =
-        static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height);
-
-    if (Settings::values.scaling_filter.GetValue() != Settings::ScalingFilter::Fsr) {
-        // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
-        // (e.g. handheld mode) on a 1920x1080 framebuffer.
-        if (framebuffer_crop_rect.GetWidth() > 0) {
-            scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) /
-                      static_cast<f32>(screen_info.texture.width);
-        }
-        if (framebuffer_crop_rect.GetHeight() > 0) {
-            scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) /
-                      static_cast<f32>(screen_info.texture.height);
-        }
-    }
-    if (Settings::values.anti_aliasing.GetValue() == Settings::AntiAliasing::Fxaa &&
-        !screen_info.was_accelerated) {
-        scale_u /= Settings::values.resolution_info.up_factor;
-        scale_v /= Settings::values.resolution_info.up_factor;
+    f32 left, top, right, bottom;
+    if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
+        // FSR has already applied the crop, so we just want to render the image
+        // it has produced.
+        left = 0;
+        top = 0;
+        right = 1;
+        bottom = 1;
+    } else {
+        // Apply the precomputed crop.
+        left = crop.left;
+        top = crop.top;
+        right = crop.right;
+        bottom = crop.bottom;
     }
 
+    // Map the coordinates to the screen.
     const auto& screen = layout.screen;
+    const auto x = screen.left;
+    const auto y = screen.top;
+    const auto w = screen.GetWidth();
+    const auto h = screen.GetHeight();
+
     const std::array vertices = {
-        ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u,
-                         left_start + left * scale_v),
-        ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u,
-                         left_start + left * scale_v),
-        ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u,
-                         left_start + right * scale_v),
-        ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u,
-                         left_start + right * scale_v),
+        ScreenRectVertex(x, y, left, top),
+        ScreenRectVertex(x + w, y, right, top),
+        ScreenRectVertex(x, y + h, left, bottom),
+        ScreenRectVertex(x + w, y + h, right, bottom),
     };
     glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices));
 
@@ -701,7 +662,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
     // program_manager.RestoreGuestPipeline();
 }
 
-void RendererOpenGL::RenderScreenshot() {
+void RendererOpenGL::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer) {
     if (!renderer_settings.screenshot_requested) {
         return;
     }
@@ -723,7 +684,7 @@ void RendererOpenGL::RenderScreenshot() {
     glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, layout.width, layout.height);
     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
 
-    DrawScreen(layout);
+    DrawScreen(framebuffer, layout);
 
     glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
     glPixelStorei(GL_PACK_ROW_LENGTH, 0);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 18699610a0..cde8c57026 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -50,11 +50,10 @@ struct TextureInfo {
 };
 
 /// Structure used for storing information about the display target for the Switch screen
-struct ScreenInfo {
+struct FramebufferTextureInfo {
     GLuint display_texture{};
-    bool was_accelerated = false;
-    const Common::Rectangle<float> display_texcoords{0.0f, 0.0f, 1.0f, 1.0f};
-    TextureInfo texture;
+    u32 width;
+    u32 height;
 };
 
 class RendererOpenGL final : public VideoCore::RendererBase {
@@ -81,23 +80,18 @@ private:
 
     void AddTelemetryFields();
 
-    void ConfigureFramebufferTexture(TextureInfo& texture,
-                                     const Tegra::FramebufferConfig& framebuffer);
+    void ConfigureFramebufferTexture(const Tegra::FramebufferConfig& framebuffer);
 
     /// Draws the emulated screens to the emulator window.
-    void DrawScreen(const Layout::FramebufferLayout& layout);
+    void DrawScreen(const Tegra::FramebufferConfig& framebuffer,
+                    const Layout::FramebufferLayout& layout);
 
-    void RenderScreenshot();
+    void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer);
 
     /// Loads framebuffer from emulated memory into the active OpenGL texture.
-    void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
+    FramebufferTextureInfo LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
 
-    /// Fills active OpenGL texture with the given RGB color.Since the color is solid, the texture
-    /// can be 1x1 but will stretch across whatever it's rendered on.
-    void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
-                                    const TextureInfo& texture);
-
-    void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
+    FramebufferTextureInfo PrepareRenderTarget(const Tegra::FramebufferConfig& framebuffer);
 
     Core::TelemetrySession& telemetry_session;
     Core::Frontend::EmuWindow& emu_window;
@@ -126,7 +120,7 @@ private:
     GLuint64EXT vertex_buffer_address = 0;
 
     /// Display information for Switch screen
-    ScreenInfo screen_info;
+    TextureInfo framebuffer_texture;
     OGLTexture aa_texture;
     OGLFramebuffer aa_framebuffer;
 
@@ -145,12 +139,6 @@ private:
 
     /// OpenGL framebuffer data
     std::vector<u8> gl_framebuffer_data;
-
-    /// Used for transforming the framebuffer orientation
-    Service::android::BufferTransformFlags framebuffer_transform_flags{};
-    Common::Rectangle<int> framebuffer_crop_rect;
-    u32 framebuffer_width;
-    u32 framebuffer_height;
 };
 
 } // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 1631276c6d..e1fe53bbd9 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -98,9 +98,9 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
       present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
                       surface),
       blit_screen(device_memory, render_window, device, memory_allocator, swapchain,
-                  present_manager, scheduler, screen_info),
-      rasterizer(render_window, gpu, device_memory, screen_info, device, memory_allocator,
-                 state_tracker, scheduler) {
+                  present_manager, scheduler),
+      rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
+                 scheduler) {
     if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
         turbo_mode.emplace(instance, dld);
         scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
@@ -124,17 +124,10 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
     if (!render_window.IsShown()) {
         return;
     }
-    // Update screen info if the framebuffer size has changed.
-    screen_info.width = framebuffer->width;
-    screen_info.height = framebuffer->height;
-
-    const DAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
-    const bool use_accelerated =
-        rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
-    RenderScreenshot(*framebuffer, use_accelerated);
 
+    RenderScreenshot(*framebuffer);
     Frame* frame = present_manager.GetRenderFrame();
-    blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated);
+    blit_screen.DrawToSwapchain(rasterizer, frame, *framebuffer);
     scheduler.Flush(*frame->render_ready);
     present_manager.Present(frame);
 
@@ -168,8 +161,7 @@ void RendererVulkan::Report() const {
     telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
 }
 
-void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer,
-                                              bool use_accelerated) {
+void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer) {
     if (!renderer_settings.screenshot_requested) {
         return;
     }
@@ -221,7 +213,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
     });
     const VkExtent2D render_area{.width = layout.width, .height = layout.height};
     const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area);
-    blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated);
+    blit_screen.Draw(rasterizer, framebuffer, *screenshot_fb, layout, render_area);
 
     const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);
     const VkBufferCreateInfo dst_buffer_info{
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 11c52287ad..d7d006b202 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -59,7 +59,7 @@ public:
 private:
     void Report() const;
 
-    void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated);
+    void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer);
 
     Core::TelemetrySession& telemetry_session;
     Tegra::MaxwellDeviceMemoryManager& device_memory;
@@ -72,8 +72,6 @@ private:
     vk::DebugUtilsMessenger debug_messenger;
     vk::SurfaceKHR surface;
 
-    ScreenInfo screen_info;
-
     Device device;
     MemoryAllocator memory_allocator;
     StateTracker state_tracker;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 610f27c846..c21a9c8fe7 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -124,11 +124,10 @@ struct BlitScreen::BufferData {
 BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_,
                        Core::Frontend::EmuWindow& render_window_, const Device& device_,
                        MemoryAllocator& memory_allocator_, Swapchain& swapchain_,
-                       PresentManager& present_manager_, Scheduler& scheduler_,
-                       const ScreenInfo& screen_info_)
+                       PresentManager& present_manager_, Scheduler& scheduler_)
     : device_memory{device_memory_}, render_window{render_window_}, device{device_},
       memory_allocator{memory_allocator_}, swapchain{swapchain_}, present_manager{present_manager_},
-      scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
+      scheduler{scheduler_}, image_count{swapchain.GetImageCount()} {
     resource_ticks.resize(image_count);
     swapchain_view_format = swapchain.GetImageViewFormat();
 
@@ -138,56 +137,6 @@ BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_,
 
 BlitScreen::~BlitScreen() = default;
 
-static Common::Rectangle<f32> NormalizeCrop(const Tegra::FramebufferConfig& framebuffer,
-                                            const ScreenInfo& screen_info) {
-    f32 left, top, right, bottom;
-
-    if (!framebuffer.crop_rect.IsEmpty()) {
-        // If crop rectangle is not empty, apply properties from rectangle.
-        left = static_cast<f32>(framebuffer.crop_rect.left);
-        top = static_cast<f32>(framebuffer.crop_rect.top);
-        right = static_cast<f32>(framebuffer.crop_rect.right);
-        bottom = static_cast<f32>(framebuffer.crop_rect.bottom);
-    } else {
-        // Otherwise, fall back to framebuffer dimensions.
-        left = 0;
-        top = 0;
-        right = static_cast<f32>(framebuffer.width);
-        bottom = static_cast<f32>(framebuffer.height);
-    }
-
-    // Apply transformation flags.
-    auto framebuffer_transform_flags = framebuffer.transform_flags;
-
-    if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipH)) {
-        // Switch left and right.
-        std::swap(left, right);
-    }
-    if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipV)) {
-        // Switch top and bottom.
-        std::swap(top, bottom);
-    }
-
-    framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipH;
-    framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipV;
-    if (True(framebuffer_transform_flags)) {
-        UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}",
-                          static_cast<u32>(framebuffer_transform_flags));
-    }
-
-    // Get the screen properties.
-    const f32 screen_width = static_cast<f32>(screen_info.width);
-    const f32 screen_height = static_cast<f32>(screen_info.height);
-
-    // Normalize coordinate space.
-    left /= screen_width;
-    top /= screen_height;
-    right /= screen_width;
-    bottom /= screen_height;
-
-    return Common::Rectangle<f32>(left, top, right, bottom);
-}
-
 void BlitScreen::Recreate() {
     present_manager.WaitPresent();
     scheduler.Finish();
@@ -195,9 +144,16 @@ void BlitScreen::Recreate() {
     CreateDynamicResources();
 }
 
-void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
+void BlitScreen::Draw(RasterizerVulkan& rasterizer, const Tegra::FramebufferConfig& framebuffer,
                       const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout,
-                      VkExtent2D render_area, bool use_accelerated) {
+                      VkExtent2D render_area) {
+
+    const auto texture_info = rasterizer.AccelerateDisplay(
+        framebuffer, framebuffer.address + framebuffer.offset, framebuffer.stride);
+    const u32 texture_width = texture_info ? texture_info->width : framebuffer.width;
+    const u32 texture_height = texture_info ? texture_info->height : framebuffer.height;
+    const bool use_accelerated = texture_info.has_value();
+
     RefreshResources(framebuffer);
 
     // Finish any pending renderpass
@@ -206,13 +162,13 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
     scheduler.Wait(resource_ticks[image_index]);
     resource_ticks[image_index] = scheduler.CurrentTick();
 
-    VkImage source_image = use_accelerated ? screen_info.image : *raw_images[image_index];
+    VkImage source_image = texture_info ? texture_info->image : *raw_images[image_index];
     VkImageView source_image_view =
-        use_accelerated ? screen_info.image_view : *raw_image_views[image_index];
+        texture_info ? texture_info->image_view : *raw_image_views[image_index];
 
     BufferData data;
     SetUniformData(data, layout);
-    SetVertexData(data, framebuffer, layout);
+    SetVertexData(data, framebuffer, layout, texture_width, texture_height);
 
     const std::span<u8> mapped_span = buffer.Mapped();
     std::memcpy(mapped_span.data(), &data, sizeof(data));
@@ -405,10 +361,10 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
         source_image_view = smaa->Draw(scheduler, image_index, source_image, source_image_view);
     }
     if (fsr) {
-        const auto crop_rect = NormalizeCrop(framebuffer, screen_info);
+        const auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
         const VkExtent2D fsr_input_size{
-            .width = Settings::values.resolution_info.ScaleUp(screen_info.width),
-            .height = Settings::values.resolution_info.ScaleUp(screen_info.height),
+            .width = Settings::values.resolution_info.ScaleUp(texture_width),
+            .height = Settings::values.resolution_info.ScaleUp(texture_height),
         };
         VkImageView fsr_image_view =
             fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
@@ -480,8 +436,8 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
     });
 }
 
-void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
-                                 bool use_accelerated) {
+void BlitScreen::DrawToSwapchain(RasterizerVulkan& rasterizer, Frame* frame,
+                                 const Tegra::FramebufferConfig& framebuffer) {
     // Recreate dynamic resources if the the image count or input format changed
     const VkFormat current_framebuffer_format =
         std::exchange(framebuffer_view_format, GetFormat(framebuffer));
@@ -500,7 +456,7 @@ void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& f
     }
 
     const VkExtent2D render_area{frame->width, frame->height};
-    Draw(framebuffer, *frame->framebuffer, layout, render_area, use_accelerated);
+    Draw(rasterizer, framebuffer, *frame->framebuffer, layout, render_area);
     if (++image_index >= image_count) {
         image_index = 0;
     }
@@ -1434,7 +1390,8 @@ void BlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayou
 }
 
 void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
-                               const Layout::FramebufferLayout layout) const {
+                               const Layout::FramebufferLayout layout, u32 texture_width,
+                               u32 texture_height) const {
     f32 left, top, right, bottom;
 
     if (fsr) {
@@ -1446,7 +1403,7 @@ void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig&
         bottom = 1;
     } else {
         // Get the normalized crop rectangle.
-        const auto crop = NormalizeCrop(framebuffer, screen_info);
+        const auto crop = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
 
         // Apply the crop.
         left = crop.left;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 3eff760092..40338886a2 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -32,8 +32,6 @@ enum class PixelFormat : u32;
 
 namespace Vulkan {
 
-struct ScreenInfo;
-
 class Device;
 class FSR;
 class RasterizerVulkan;
@@ -44,7 +42,7 @@ class PresentManager;
 
 struct Frame;
 
-struct ScreenInfo {
+struct FramebufferTextureInfo {
     VkImage image{};
     VkImageView image_view{};
     u32 width{};
@@ -56,17 +54,17 @@ public:
     explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory,
                         Core::Frontend::EmuWindow& render_window, const Device& device,
                         MemoryAllocator& memory_manager, Swapchain& swapchain,
-                        PresentManager& present_manager, Scheduler& scheduler,
-                        const ScreenInfo& screen_info);
+                        PresentManager& present_manager, Scheduler& scheduler);
     ~BlitScreen();
 
     void Recreate();
 
-    void Draw(const Tegra::FramebufferConfig& framebuffer, const VkFramebuffer& host_framebuffer,
-              const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated);
+    void Draw(RasterizerVulkan& rasterizer, const Tegra::FramebufferConfig& framebuffer,
+              const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout,
+              VkExtent2D render_area);
 
-    void DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
-                         bool use_accelerated);
+    void DrawToSwapchain(RasterizerVulkan& rasterizer, Frame* frame,
+                         const Tegra::FramebufferConfig& framebuffer);
 
     [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,
                                                     VkExtent2D extent);
@@ -99,7 +97,8 @@ private:
     void UpdateAADescriptorSet(VkImageView image_view, bool nn) const;
     void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;
     void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
-                       const Layout::FramebufferLayout layout) const;
+                       const Layout::FramebufferLayout layout, u32 texture_width,
+                       u32 texture_height) const;
 
     void CreateSMAA(VkExtent2D smaa_size);
     void CreateFSR();
@@ -116,7 +115,6 @@ private:
     Scheduler& scheduler;
     std::size_t image_count;
     std::size_t image_index{};
-    const ScreenInfo& screen_info;
 
     vk::ShaderModule vertex_shader;
     vk::ShaderModule fxaa_vertex_shader;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 5bf41b81f6..e593d7225d 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -165,10 +165,9 @@ DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances,
 
 RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
                                    Tegra::MaxwellDeviceMemoryManager& device_memory_,
-                                   ScreenInfo& screen_info_, const Device& device_,
-                                   MemoryAllocator& memory_allocator_, StateTracker& state_tracker_,
-                                   Scheduler& scheduler_)
-    : gpu{gpu_}, device_memory{device_memory_}, screen_info{screen_info_}, device{device_},
+                                   const Device& device_, MemoryAllocator& memory_allocator_,
+                                   StateTracker& state_tracker_, Scheduler& scheduler_)
+    : gpu{gpu_}, device_memory{device_memory_}, device{device_},
       memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_},
       staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
       guest_descriptor_queue(device, scheduler), compute_pass_descriptor_queue(device, scheduler),
@@ -783,23 +782,25 @@ void RasterizerVulkan::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si
     query_cache.InvalidateRegion(*cpu_addr, copy_size);
 }
 
-bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
-                                         DAddr framebuffer_addr, u32 pixel_stride) {
+std::optional<FramebufferTextureInfo> RasterizerVulkan::AccelerateDisplay(
+    const Tegra::FramebufferConfig& config, DAddr framebuffer_addr, u32 pixel_stride) {
     if (!framebuffer_addr) {
-        return false;
+        return {};
     }
     std::scoped_lock lock{texture_cache.mutex};
     ImageView* const image_view =
         texture_cache.TryFindFramebufferImageView(config, framebuffer_addr);
     if (!image_view) {
-        return false;
+        return {};
     }
     query_cache.NotifySegment(false);
-    screen_info.image = image_view->ImageHandle();
-    screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D);
-    screen_info.width = image_view->size.width;
-    screen_info.height = image_view->size.height;
-    return true;
+
+    FramebufferTextureInfo info{};
+    info.image = image_view->ImageHandle();
+    info.image_view = image_view->Handle(Shader::TextureType::Color2D);
+    info.width = image_view->size.width;
+    info.height = image_view->size.height;
+    return info;
 }
 
 void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 881ee0993e..0617b37f05 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -43,7 +43,7 @@ class Maxwell3D;
 
 namespace Vulkan {
 
-struct ScreenInfo;
+struct FramebufferTextureInfo;
 
 class StateTracker;
 
@@ -78,9 +78,8 @@ class RasterizerVulkan final : public VideoCore::RasterizerInterface,
 public:
     explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
                               Tegra::MaxwellDeviceMemoryManager& device_memory_,
-                              ScreenInfo& screen_info_, const Device& device_,
-                              MemoryAllocator& memory_allocator_, StateTracker& state_tracker_,
-                              Scheduler& scheduler_);
+                              const Device& device_, MemoryAllocator& memory_allocator_,
+                              StateTracker& state_tracker_, Scheduler& scheduler_);
     ~RasterizerVulkan() override;
 
     void Draw(bool is_indexed, u32 instance_count) override;
@@ -126,8 +125,6 @@ public:
     Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
     void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size,
                                   std::span<const u8> memory) override;
-    bool AccelerateDisplay(const Tegra::FramebufferConfig& config, DAddr framebuffer_addr,
-                           u32 pixel_stride) override;
     void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                            const VideoCore::DiskResourceLoadCallback& callback) override;
 
@@ -137,6 +134,10 @@ public:
 
     void ReleaseChannel(s32 channel_id) override;
 
+    std::optional<FramebufferTextureInfo> AccelerateDisplay(const Tegra::FramebufferConfig& config,
+                                                            VAddr framebuffer_addr,
+                                                            u32 pixel_stride);
+
 private:
     static constexpr size_t MAX_TEXTURES = 192;
     static constexpr size_t MAX_IMAGES = 48;
@@ -182,7 +183,6 @@ private:
     Tegra::GPU& gpu;
     Tegra::MaxwellDeviceMemoryManager& device_memory;
 
-    ScreenInfo& screen_info;
     const Device& device;
     MemoryAllocator& memory_allocator;
     StateTracker& state_tracker;