diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index bf51fcf7f4..0c85440471 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -71,6 +71,7 @@ const char *VulkanPresentModeToString(VkPresentModeKHR presentMode) { case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return "FIFO_RELAXED"; case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: return "SHARED_DEMAND_REFRESH_KHR"; case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: return "SHARED_CONTINUOUS_REFRESH_KHR"; + case VK_PRESENT_MODE_FIFO_LATEST_READY_KHR: return "FIFO_LATEST_READY"; default: return "UNKNOWN"; } } @@ -710,6 +711,12 @@ VkResult VulkanContext::CreateDevice(int physical_device) { extensionsLookup_.EXT_provoking_vertex = EnableDeviceExtension(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, 0); + extensionsLookup_.KHR_present_mode_fifo_latest_ready = EnableDeviceExtension(VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, 0); + if (!extensionsLookup_.KHR_present_mode_fifo_latest_ready) { + // Enable the EXT extension instead if available, it's equivalent (was promoted). + extensionsLookup_.KHR_present_mode_fifo_latest_ready = EnableDeviceExtension(VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, 0); + } + // Optional features if (extensionsLookup_.KHR_get_physical_device_properties2 && vkGetPhysicalDeviceFeatures2) { VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR }; @@ -1397,13 +1404,9 @@ bool VulkanContext::InitSwapchain() { availablePresentModes_.push_back(presentModes[i]); } + // Kind of silly logic now, but at least it performs a final sanity check of the chosen value. for (size_t i = 0; i < presentModeCount; i++) { - bool match = false; - match = match || ((createInfo_.flags & VulkanInitFlags::PRESENT_MAILBOX) && presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR); - match = match || ((createInfo_.flags & VulkanInitFlags::PRESENT_IMMEDIATE) && presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR); - match = match || ((createInfo_.flags & VulkanInitFlags::PRESENT_FIFO_RELAXED) && presentModes[i] == VK_PRESENT_MODE_FIFO_RELAXED_KHR); - match = match || ((createInfo_.flags & VulkanInitFlags::PRESENT_FIFO) && presentModes[i] == VK_PRESENT_MODE_FIFO_KHR); - + bool match = presentModes[i] == createInfo_.presentMode; // Default to the first present mode from the list. if (match || swapchainPresentMode == VK_PRESENT_MODE_MAX_ENUM_KHR) { swapchainPresentMode = presentModes[i]; diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h index 46535dc3b8..cbadbe3506 100644 --- a/Common/GPU/Vulkan/VulkanContext.h +++ b/Common/GPU/Vulkan/VulkanContext.h @@ -25,10 +25,6 @@ enum class VulkanInitFlags : uint32_t { VALIDATE = (1 << 0), - PRESENT_MAILBOX = (1 << 1), - PRESENT_IMMEDIATE = (1 << 2), - PRESENT_FIFO_RELAXED = (1 << 3), - PRESENT_FIFO = (1 << 4), DISABLE_IMPLICIT_LAYERS = (1 << 5), }; ENUM_CLASS_BITOPS(VulkanInitFlags); @@ -185,6 +181,7 @@ public: int app_ver; VulkanInitFlags flags; std::string customDriver; + VkPresentModeKHR presentMode; }; VkResult CreateInstance(const CreateInfo &info); @@ -417,6 +414,15 @@ public: return availablePresentModes_; } + bool PresentModeSupported(VkPresentModeKHR mode) const { + for (const auto &m : availablePresentModes_) { + if (m == mode) { + return true; + } + } + return false; + } + int GetLastDeleteCount() const { return frame_[curFrame_].deleteList.GetLastDeleteCount(); } diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h index 02771f5534..83836b6d1c 100644 --- a/Common/GPU/Vulkan/VulkanLoader.h +++ b/Common/GPU/Vulkan/VulkanLoader.h @@ -270,6 +270,7 @@ struct VulkanExtensions { bool KHR_present_wait; // Same bool GOOGLE_display_timing; bool EXT_provoking_vertex; + bool KHR_present_mode_fifo_latest_ready; // bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers. }; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 8f7afd9f24..86b503638a 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -430,9 +430,10 @@ public: PresentMode GetPresentMode() const { switch (vulkan_->GetPresentMode()) { case VK_PRESENT_MODE_FIFO_KHR: return PresentMode::FIFO; - case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return PresentMode::FIFO; // We treat is as FIFO for now (and won't ever enable it anyway...) + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return PresentMode::FIFO_RELAXED; // We treat is as FIFO for now (and won't ever enable it anyway...) case VK_PRESENT_MODE_IMMEDIATE_KHR: return PresentMode::IMMEDIATE; case VK_PRESENT_MODE_MAILBOX_KHR: return PresentMode::MAILBOX; + case VK_PRESENT_MODE_FIFO_LATEST_READY_KHR: return PresentMode::FIFO_LATEST_READY; default: return PresentMode::FIFO; } } @@ -937,6 +938,8 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) case VK_PRESENT_MODE_FIFO_KHR: caps_.presentModesSupported |= PresentMode::FIFO; break; case VK_PRESENT_MODE_IMMEDIATE_KHR: caps_.presentModesSupported |= PresentMode::IMMEDIATE; break; case VK_PRESENT_MODE_MAILBOX_KHR: caps_.presentModesSupported |= PresentMode::MAILBOX; break; + case VK_PRESENT_MODE_FIFO_LATEST_READY_KHR: caps_.presentModesSupported |= PresentMode::FIFO_LATEST_READY; break; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: caps_.presentModesSupported |= PresentMode::FIFO_RELAXED; break; default: break; // Ignore any other modes. } } diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index e68b3ed972..cf2838f36c 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -581,6 +581,8 @@ enum class PresentMode { FIFO = 1, IMMEDIATE = 2, MAILBOX = 4, + FIFO_RELAXED = 8, // Vulkan only + FIFO_LATEST_READY = 16, // Vulkan only }; ENUM_CLASS_BITOPS(PresentMode); @@ -627,7 +629,7 @@ struct DeviceCaps { // Old style, for older GL or Direct3D 9. u32 clipPlanesSupported; - // Presentation caps + // Presentation caps (simplified). Note: Vulkan supports more, we handle that separately now. int presentMaxInterval; // 1 on many backends bool presentInstantModeChange; PresentMode presentModesSupported; diff --git a/Core/Config.cpp b/Core/Config.cpp index d6cc0ee25f..6c9c1830e1 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -727,6 +727,7 @@ static const ConfigSetting graphicsSettings[] = { ConfigSetting("TexDeposterize", &g_Config.bTexDeposterize, false, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("TexHardwareScaling", &g_Config.bTexHardwareScaling, false, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("VSync", &g_Config.bVSync, &DefaultVSync, CfgFlag::PER_GAME), + ConfigSetting("VulkanPresentMode", &g_Config.iVulkanPresentationMode, (int)PresentMode::Mailbox, CfgFlag::PER_GAME), ConfigSetting("BloomHack", &g_Config.iBloomHack, 0, CfgFlag::PER_GAME | CfgFlag::REPORT), // Not really a graphics setting... diff --git a/Core/Config.h b/Core/Config.h index 105ce8b296..1852e6c9bc 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -168,6 +168,8 @@ public: bool bCameraMirrorHorizontal; int iDisplayFramerateMode; // enum DisplayFramerateMode. Android-only. int iDisplayRefreshRate = 60; + int iVulkanPresentationMode; + bool bVSync; bool bSoftwareRendering; bool bSoftwareRenderingJit; @@ -196,7 +198,6 @@ public: bool bImmersiveMode; // Mode on Android Kitkat 4.4 and later that hides the back button etc. bool bSustainedPerformanceMode; // Android: Slows clocks down to avoid overheating/speed fluctuations. bool bIgnoreScreenInsets; // Android: Center screen disregarding insets if this is enabled. - bool bVSync; bool bShowImDebugger; diff --git a/Core/ConfigValues.h b/Core/ConfigValues.h index ecba313e22..ce6f30c369 100644 --- a/Core/ConfigValues.h +++ b/Core/ConfigValues.h @@ -142,6 +142,16 @@ ENUM_CLASS_BITOPS(DisableHLEFlags); std::string GPUBackendToString(GPUBackend backend); GPUBackend GPUBackendFromString(std::string_view backend); +// Vulkan present modes, linearized. Currently in order of lowest to highest latency, hopefully won't change in the future. +// NOTE: These values DO NOT match the flags in DrawContext caps - these are not used as a bitfield. +enum class PresentMode { + Immediate = 0, // VK_PRESENT_MODE_IMMEDIATE_KHR + Mailbox = 1, // VK_PRESENT_MODE_MAILBOX_KHR + FifoLatestReady = 2, // VK_PRESENT_MODE_FIFO_LATEST_READY_KHR + FifoRelaxed = 3, // VK_PRESENT_MODE_FIFO_RELAXED_KHR + Fifo = 4, // VK_PRESENT_MODE_FIFO_KHR +}; + // For iIOTimingMethod. enum IOTimingMethods { IOTIMING_FAST = 0, diff --git a/GPU/Vulkan/VulkanUtil.cpp b/GPU/Vulkan/VulkanUtil.cpp index 6f5f24784c..09b18d7d76 100644 --- a/GPU/Vulkan/VulkanUtil.cpp +++ b/GPU/Vulkan/VulkanUtil.cpp @@ -36,9 +36,25 @@ const VkComponentMapping VULKAN_1555_SWIZZLE = { VK_COMPONENT_SWIZZLE_B, VK_COMP const VkComponentMapping VULKAN_565_SWIZZLE = { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_IDENTITY }; const VkComponentMapping VULKAN_8888_SWIZZLE = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; +VkPresentModeKHR ConfigPresentModeToVulkan(PresentMode presentMode) { + switch (presentMode) { + case PresentMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case PresentMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case PresentMode::FifoLatestReady: + return VK_PRESENT_MODE_FIFO_LATEST_READY_KHR; + case PresentMode::FifoRelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + case PresentMode::Fifo: + default: + return VK_PRESENT_MODE_FIFO_KHR; + } +} + // TODO: Share this between backends. static VulkanInitFlags VulkanInitFlagsFromConfig() { - VulkanInitFlags flags = g_Config.bVSync ? VulkanInitFlags::PRESENT_FIFO : VulkanInitFlags::PRESENT_MAILBOX; + VulkanInitFlags flags = (VulkanInitFlags)0; if (g_Validate) { flags |= VulkanInitFlags::VALIDATE; } @@ -54,6 +70,7 @@ void InitVulkanCreateInfoFromConfig(VulkanContext::CreateInfo *info) { info->app_ver = gitVer.ToInteger(); info->flags = VulkanInitFlagsFromConfig(); info->customDriver = g_Config.sCustomDriver; + info->presentMode = ConfigPresentModeToVulkan((PresentMode)g_Config.iVulkanPresentationMode); } VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error) { diff --git a/GPU/Vulkan/VulkanUtil.h b/GPU/Vulkan/VulkanUtil.h index e456154f7c..d033d12d4d 100644 --- a/GPU/Vulkan/VulkanUtil.h +++ b/GPU/Vulkan/VulkanUtil.h @@ -26,6 +26,7 @@ #include "Common/GPU/Vulkan/VulkanLoader.h" #include "Common/GPU/Vulkan/VulkanDescSet.h" #include "Common/GPU/thin3d.h" +#include "Core/ConfigValues.h" extern const VkComponentMapping VULKAN_4444_SWIZZLE; extern const VkComponentMapping VULKAN_1555_SWIZZLE; @@ -88,4 +89,5 @@ private: VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error); +VkPresentModeKHR ConfigPresentModeToVulkan(PresentMode presentMode); void InitVulkanCreateInfoFromConfig(VulkanContext::CreateInfo *info); diff --git a/UI/DebugOverlay.cpp b/UI/DebugOverlay.cpp index 9946fd639e..e59a8b8ebd 100644 --- a/UI/DebugOverlay.cpp +++ b/UI/DebugOverlay.cpp @@ -136,8 +136,9 @@ static void DrawFrameTiming(UIContext *ctx, const Bounds &bounds) { ctx->BindFontTexture(); ctx->Draw()->SetFontScale(0.5f, 0.5f); + // NOTE: This is not necessarily the same as the actual present mode. snprintf(statBuf, sizeof(statBuf), - "Mode (interval): %s (%d)", + "Timing mode (interval): %s (%d)", Draw::PresentModeToString(g_frameTiming.presentMode), g_frameTiming.presentInterval); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index fa751f25cd..b600e75f3c 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -364,13 +364,45 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) } #endif - // All backends support FIFO. Check if any immediate modes are supported, if so we can allow the user to choose. - if (draw->GetDeviceCaps().presentModesSupported & (Draw::PresentMode::IMMEDIATE | Draw::PresentMode::MAILBOX)) { - CheckBox *vSync = graphicsSettings->Add(new CheckBox(&g_Config.bVSync, gr->T("VSync"))); - vSync->OnClick.Add([=](EventParams &e) { + if (GetGPUBackend() == GPUBackend::VULKAN) { + // In Vulkan, we can now explicitly let the user choose the presentation mode. + static const char *presentationModes[] = { + "Immediate (low latency, tearing)", + "Mailbox (low latency, recommended)", + "FIFO latest ready", + "FIFO relaxed", + "FIFO (high latency, low power)", + }; + UI::PopupMultiChoice *presentationMode = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iVulkanPresentationMode, gr->T("Frame presentation mode"), presentationModes, 0, ARRAY_SIZE(presentationModes), I18NCat::GRAPHICS, screenManager())); + presentationMode->SetChoiceIcon(0, ImageID("I_WARNING")); + if (!(draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE)) { + presentationMode->HideChoice(0); + } + if (!(draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::MAILBOX)) { + presentationMode->HideChoice(1); + } + if (!(draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::FIFO_LATEST_READY)) { + presentationMode->HideChoice(2); + } + if (!(draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::FIFO_RELAXED)) { + presentationMode->HideChoice(3); + } + if (!(draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::FIFO)) { + presentationMode->HideChoice(4); + } + presentationMode->OnChoice.Add([=](EventParams &e) { NativeResized(); return UI::EVENT_CONTINUE; }); + } else { + // All backends support FIFO. Check if any immediate modes are supported, if so we can allow the user to choose. + if (draw->GetDeviceCaps().presentModesSupported & (Draw::PresentMode::IMMEDIATE | Draw::PresentMode::MAILBOX)) { + CheckBox *vSync = graphicsSettings->Add(new CheckBox(&g_Config.bVSync, gr->T("VSync"))); + vSync->OnClick.Add([=](EventParams &e) { + NativeResized(); + return UI::EVENT_CONTINUE; + }); + } } #if PPSSPP_PLATFORM(ANDROID)