diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 459ab32c2..66483a900 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -137,6 +137,56 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin
 
 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();
@@ -354,17 +404,10 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
         source_image_view = smaa->Draw(scheduler, image_index, source_image, source_image_view);
     }
     if (fsr) {
-        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);
-        VkExtent2D fsr_input_size{
-            .width = Settings::values.resolution_info.ScaleUp(framebuffer.width),
-            .height = Settings::values.resolution_info.ScaleUp(framebuffer.height),
+        const auto crop_rect = NormalizeCrop(framebuffer, screen_info);
+        const VkExtent2D fsr_input_size{
+            .width = Settings::values.resolution_info.ScaleUp(screen_info.width),
+            .height = Settings::values.resolution_info.ScaleUp(screen_info.height),
         };
         VkImageView fsr_image_view =
             fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
@@ -1395,60 +1438,27 @@ void BlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayou
         MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height));
 }
 
-static Common::Rectangle<f32> NormalizeCrop(Common::Rectangle<int> crop,
-                                            const Tegra::FramebufferConfig& framebuffer) {
-    f32 left, top, right, bottom;
-
-    if (!crop.IsEmpty()) {
-        // If crop rectangle is not empty, apply properties from rectangle.
-        left = static_cast<f32>(crop.left);
-        top = static_cast<f32>(crop.top);
-        right = static_cast<f32>(crop.right);
-        bottom = static_cast<f32>(crop.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));
-    }
-
-    return Common::Rectangle<f32>(left, top, right, bottom);
-}
-
 void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
                                const Layout::FramebufferLayout layout) const {
-    // Get the normalized crop rectangle.
-    const auto crop = NormalizeCrop(framebuffer.crop_rect, framebuffer);
+    f32 left, top, right, bottom;
 
-    // Get the screen properties.
-    const f32 screen_width = static_cast<f32>(screen_info.width);
-    const f32 screen_height = static_cast<f32>(screen_info.height);
+    if (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 {
+        // Get the normalized crop rectangle.
+        const auto crop = NormalizeCrop(framebuffer, screen_info);
 
-    // Apply the crop.
-    const f32 left = crop.left / screen_width;
-    const f32 top = crop.top / screen_height;
-    const f32 right = crop.right / screen_width;
-    const f32 bottom = crop.bottom / screen_height;
+        // Apply the crop.
+        left = crop.left;
+        top = crop.top;
+        right = crop.right;
+        bottom = crop.bottom;
+    }
 
     // Map the coordinates to the screen.
     const auto& screen = layout.screen;
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index ce8f3f3c2..f7a05fbc0 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -34,7 +34,7 @@ FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image
 }
 
 VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
-                      VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect) {
+                      VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect) {
 
     UpdateDescriptorSet(image_index, image_view);
 
@@ -61,15 +61,21 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView imag
 
         cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *easu_pipeline);
 
-        std::array<u32, 4 * 4> push_constants;
-        FsrEasuConOffset(
-            push_constants.data() + 0, push_constants.data() + 4, push_constants.data() + 8,
-            push_constants.data() + 12,
+        const f32 input_image_width = static_cast<f32>(input_image_extent.width);
+        const f32 input_image_height = static_cast<f32>(input_image_extent.height);
+        const f32 output_image_width = static_cast<f32>(output_size.width);
+        const f32 output_image_height = static_cast<f32>(output_size.height);
+        const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
+        const f32 viewport_x = crop_rect.left * input_image_width;
+        const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
+        const f32 viewport_y = crop_rect.top * input_image_height;
 
-            static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()),
-            static_cast<f32>(input_image_extent.width), static_cast<f32>(input_image_extent.height),
-            static_cast<f32>(output_size.width), static_cast<f32>(output_size.height),
-            static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top));
+        std::array<u32, 4 * 4> push_constants;
+        FsrEasuConOffset(push_constants.data() + 0, push_constants.data() + 4,
+                         push_constants.data() + 8, push_constants.data() + 12,
+
+                         viewport_width, viewport_height, input_image_width, input_image_height,
+                         output_image_width, output_image_height, viewport_x, viewport_y);
         cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, push_constants);
 
         {
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h
index 8bb9fc23a..3505c1416 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.h
+++ b/src/video_core/renderer_vulkan/vk_fsr.h
@@ -17,7 +17,7 @@ public:
     explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count,
                  VkExtent2D output_size);
     VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
-                     VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect);
+                     VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect);
 
 private:
     void CreateDescriptorPool();