From 35e5a67a839c5b1b5f12d21b2d4e3911e1af33b7 Mon Sep 17 00:00:00 2001
From: ameerj <52414509+ameerj@users.noreply.github.com>
Date: Sun, 12 Sep 2021 20:32:23 -0400
Subject: [PATCH] vk_swapchain: Use immediate present mode when mailbox is
 unavailable and FPS is unlocked

Allows drivers that do not support VK_PRESENT_MODE_MAILBOX_KHR the ability to present at a framerate higher than the monitor's refresh rate when the FPS is unlocked.
---
 .../renderer_vulkan/renderer_vulkan.cpp       |  2 +-
 .../renderer_vulkan/vk_swapchain.cpp          | 29 +++++++++++++++++--
 src/video_core/renderer_vulkan/vk_swapchain.h | 11 +++++++
 3 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 9ff0a28cd..adb6b7a3b 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -149,7 +149,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
         const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
         swapchain.Create(layout.width, layout.height, is_srgb);
     };
-    if (swapchain.IsSubOptimal() || swapchain.HasColorSpaceChanged(is_srgb)) {
+    if (swapchain.NeedsRecreation(is_srgb)) {
         recreate_swapchain();
     }
     bool is_outdated;
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index aadf03cb0..8972a6921 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -9,6 +9,7 @@
 
 #include "common/assert.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/frontend/framebuffer_layout.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -36,8 +37,19 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats)
 
 VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) {
     // Mailbox doesn't lock the application like fifo (vsync), prefer it
-    const auto found = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR);
-    return found != modes.end() ? *found : VK_PRESENT_MODE_FIFO_KHR;
+    const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR);
+    if (found_mailbox != modes.end()) {
+        return VK_PRESENT_MODE_MAILBOX_KHR;
+    }
+    if (Settings::values.disable_fps_limit.GetValue()) {
+        // FIFO present mode locks the framerate to the monitor's refresh rate,
+        // Find an alternative to surpass this limitation if FPS is unlocked.
+        const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR);
+        if (found_imm != modes.end()) {
+            return VK_PRESENT_MODE_IMMEDIATE_KHR;
+        }
+    }
+    return VK_PRESENT_MODE_FIFO_KHR;
 }
 
 VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height) {
@@ -143,7 +155,7 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
     const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};
 
     const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
-    const VkPresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
+    present_mode = ChooseSwapPresentMode(present_modes);
 
     u32 requested_image_count{capabilities.minImageCount + 1};
     if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
@@ -196,6 +208,7 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
 
     extent = swapchain_ci.imageExtent;
     current_srgb = srgb;
+    current_fps_unlocked = Settings::values.disable_fps_limit.GetValue();
 
     images = swapchain.GetImages();
     image_count = static_cast<u32>(images.size());
@@ -248,4 +261,14 @@ void VKSwapchain::Destroy() {
     swapchain.reset();
 }
 
+bool VKSwapchain::HasFpsUnlockChanged() const {
+    return current_fps_unlocked != Settings::values.disable_fps_limit.GetValue();
+}
+
+bool VKSwapchain::NeedsPresentModeUpdate() const {
+    // Mailbox present mode is the ideal for all scenarios. If it is not available,
+    // A different present mode is needed to support unlocked FPS above the monitor's refresh rate.
+    return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged();
+}
+
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index 5bce41e21..61a6d959e 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -33,6 +33,11 @@ public:
     /// Presents the rendered image to the swapchain.
     void Present(VkSemaphore render_semaphore);
 
+    /// Returns true when the swapchain needs to be recreated.
+    bool NeedsRecreation(bool is_srgb) const {
+        return HasColorSpaceChanged(is_srgb) || IsSubOptimal() || NeedsPresentModeUpdate();
+    }
+
     /// Returns true when the color space has changed.
     bool HasColorSpaceChanged(bool is_srgb) const {
         return current_srgb != is_srgb;
@@ -84,6 +89,10 @@ private:
 
     void Destroy();
 
+    bool HasFpsUnlockChanged() const;
+
+    bool NeedsPresentModeUpdate() const;
+
     const VkSurfaceKHR surface;
     const Device& device;
     VKScheduler& scheduler;
@@ -102,8 +111,10 @@ private:
 
     VkFormat image_view_format{};
     VkExtent2D extent{};
+    VkPresentModeKHR present_mode{};
 
     bool current_srgb{};
+    bool current_fps_unlocked{};
     bool is_outdated{};
     bool is_suboptimal{};
 };