From 1c198dcd48cf834029ad9bb2dff58b89269f2678 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 6 Jun 2024 10:32:02 +0200 Subject: [PATCH] [dxgi] Limit frame rate to display refresh as necessary --- dxvk.conf | 7 ++++++- src/d3d11/d3d11_options.cpp | 1 - src/d3d11/d3d11_options.h | 3 --- src/d3d11/d3d11_swapchain.cpp | 14 ++++++++++++-- src/d3d11/d3d11_swapchain.h | 23 ++++++++++++++--------- src/dxgi/dxgi_interfaces.h | 8 ++++++++ src/dxgi/dxgi_options.cpp | 1 + src/dxgi/dxgi_options.h | 3 +++ src/dxgi/dxgi_swapchain.cpp | 31 ++++++++++++++++++++++++++++++- src/dxgi/dxgi_swapchain.h | 8 ++++++++ 10 files changed, 82 insertions(+), 17 deletions(-) diff --git a/dxvk.conf b/dxvk.conf index 0fa96f215..10c95d442 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -43,7 +43,12 @@ # bugs in games that have physics or other simulation tied to their frame # rate, but do not provide their own limiter. # -# Supported values : Any non-negative integer +# Supported values +# -1: Always disables the limiter +# 0: Default behaviour. Limits the frame rate to the selected display +# refresh rate when vertical synchronization is enabled if the +# actual display mode does not match the game's one. +# n: Limit to n frames per second. # dxgi.maxFrameRate = 0 # d3d9.maxFrameRate = 0 diff --git a/src/d3d11/d3d11_options.cpp b/src/d3d11/d3d11_options.cpp index c6b41ecb7..835ce985b 100644 --- a/src/d3d11/d3d11_options.cpp +++ b/src/d3d11/d3d11_options.cpp @@ -30,7 +30,6 @@ namespace dxvk { this->deferSurfaceCreation = config.getOption("dxgi.deferSurfaceCreation", false); this->numBackBuffers = config.getOption("dxgi.numBackBuffers", 0); this->maxFrameLatency = config.getOption("dxgi.maxFrameLatency", 0); - this->maxFrameRate = config.getOption("dxgi.maxFrameRate", 0); this->exposeDriverCommandLists = config.getOption("d3d11.exposeDriverCommandLists", true); this->longMad = config.getOption("d3d11.longMad", false); this->reproducibleCommandStream = config.getOption("d3d11.reproducibleCommandStream", false); diff --git a/src/d3d11/d3d11_options.h b/src/d3d11/d3d11_options.h index a5e21e17e..e556a89d8 100644 --- a/src/d3d11/d3d11_options.h +++ b/src/d3d11/d3d11_options.h @@ -80,9 +80,6 @@ namespace dxvk { /// a higher value. May help with frame timing issues. int32_t maxFrameLatency; - /// Limit frame rate - int32_t maxFrameRate; - /// Limit discardable resource size VkDeviceSize maxImplicitDiscardSize; diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 73939e2c9..ce6faed53 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -99,7 +99,8 @@ namespace dxvk { if (riid == __uuidof(IUnknown) || riid == __uuidof(IDXGIVkSwapChain) - || riid == __uuidof(IDXGIVkSwapChain1)) { + || riid == __uuidof(IDXGIVkSwapChain1) + || riid == __uuidof(IDXGIVkSwapChain2)) { *ppvObject = ref(this); return S_OK; } @@ -347,6 +348,15 @@ namespace dxvk { } + void STDMETHODCALLTYPE D3D11SwapChain::SetTargetFrameRate( + double FrameRate) { + m_targetFrameRate = FrameRate; + + if (m_presenter != nullptr) + m_presenter->setFrameRateLimit(m_targetFrameRate); + } + + HRESULT D3D11SwapChain::PresentImage(UINT SyncInterval) { // Flush pending rendering commands before auto immediateContext = m_parent->GetContext(); @@ -496,7 +506,7 @@ namespace dxvk { presenterDesc.fullScreenExclusive = PickFullscreenMode(); m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc); - m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate); + m_presenter->setFrameRateLimit(m_targetFrameRate); } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 00073d769..9a2b0cac9 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -13,7 +13,7 @@ namespace dxvk { class D3D11Device; class D3D11DXGIDevice; - class D3D11SwapChain : public ComObject { + class D3D11SwapChain : public ComObject { constexpr static uint32_t DefaultFrameLatency = 1; public: @@ -86,6 +86,9 @@ namespace dxvk { void STDMETHODCALLTYPE GetFrameStatistics( DXGI_VK_FRAME_STATISTICS* pFrameStatistics); + void STDMETHODCALLTYPE SetTargetFrameRate( + double FrameRate); + private: enum BindingIds : uint32_t { @@ -116,18 +119,20 @@ namespace dxvk { std::vector> m_imageViews; - uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS; - uint32_t m_frameLatency = DefaultFrameLatency; - uint32_t m_frameLatencyCap = 0; - HANDLE m_frameLatencyEvent = nullptr; - Rc m_frameLatencySignal; + uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS; + uint32_t m_frameLatency = DefaultFrameLatency; + uint32_t m_frameLatencyCap = 0; + HANDLE m_frameLatencyEvent = nullptr; + Rc m_frameLatencySignal; - bool m_dirty = true; + bool m_dirty = true; - VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; std::optional m_hdrMetadata; - bool m_dirtyHdrMetadata = true; + bool m_dirtyHdrMetadata = true; + + double m_targetFrameRate = 0.0; dxvk::mutex m_frameStatisticsLock; DXGI_VK_FRAME_STATISTICS m_frameStatistics = { }; diff --git a/src/dxgi/dxgi_interfaces.h b/src/dxgi/dxgi_interfaces.h index 4c35bd527..390eee55a 100644 --- a/src/dxgi/dxgi_interfaces.h +++ b/src/dxgi/dxgi_interfaces.h @@ -137,6 +137,13 @@ IDXGIVkSwapChain1 : public IDXGIVkSwapChain { }; +MIDL_INTERFACE("aed91093-e02e-458c-bdef-a97da1a7e6d2") +IDXGIVkSwapChain2 : public IDXGIVkSwapChain1 { + virtual void STDMETHODCALLTYPE SetTargetFrameRate( + double FrameRate) = 0; +}; + + /** * \brief Private DXGI presenter factory */ @@ -471,5 +478,6 @@ __CRT_UUID_DECL(IDXGIVkInteropSurface, 0x5546cf8c,0x77e7,0x4341,0xb0,0x5d,0x __CRT_UUID_DECL(IDXGIVkSurfaceFactory, 0x1e7895a1,0x1bc3,0x4f9c,0xa6,0x70,0x29,0x0a,0x4b,0xc9,0x58,0x1a); __CRT_UUID_DECL(IDXGIVkSwapChain, 0xe4a9059e,0xb569,0x46ab,0x8d,0xe7,0x50,0x1b,0xd2,0xbc,0x7f,0x7a); __CRT_UUID_DECL(IDXGIVkSwapChain1, 0x785326d4,0xb77b,0x4826,0xae,0x70,0x8d,0x08,0x30,0x8e,0xe6,0xd1); +__CRT_UUID_DECL(IDXGIVkSwapChain2, 0xaed91093,0xe02e,0x458c,0xbd,0xef,0xa9,0x7d,0xa1,0xa7,0xe6,0xd2); __CRT_UUID_DECL(IDXGIVkSwapChainFactory, 0xe7d6c3ca,0x23a0,0x4e08,0x9f,0x2f,0xea,0x52,0x31,0xdf,0x66,0x33); #endif diff --git a/src/dxgi/dxgi_options.cpp b/src/dxgi/dxgi_options.cpp index d7d55d4e8..f352dfdbf 100644 --- a/src/dxgi/dxgi_options.cpp +++ b/src/dxgi/dxgi_options.cpp @@ -93,6 +93,7 @@ namespace dxvk { this->maxDeviceMemory = VkDeviceSize(config.getOption("dxgi.maxDeviceMemory", 0)) << 20; this->maxSharedMemory = VkDeviceSize(config.getOption("dxgi.maxSharedMemory", 0)) << 20; + this->maxFrameRate = config.getOption("dxgi.maxFrameRate", 0); this->syncInterval = config.getOption("dxgi.syncInterval", -1); // Expose Nvidia GPUs properly if NvAPI is enabled in environment diff --git a/src/dxgi/dxgi_options.h b/src/dxgi/dxgi_options.h index a22de94f1..95b223f1b 100644 --- a/src/dxgi/dxgi_options.h +++ b/src/dxgi/dxgi_options.h @@ -49,6 +49,9 @@ namespace dxvk { /// Enable HDR bool enableHDR; + /// Limit frame rate + int32_t maxFrameRate; + /// Sync interval. Overrides the value /// passed to IDXGISwapChain::Present. int32_t syncInterval; diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 9b1283a1b..a55278a66 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -25,6 +25,9 @@ namespace dxvk { // Query updated interface versions from presenter, this // may fail e.g. with older vkd3d-proton builds. m_presenter->QueryInterface(__uuidof(IDXGIVkSwapChain1), reinterpret_cast(&m_presenter1)); + m_presenter->QueryInterface(__uuidof(IDXGIVkSwapChain2), reinterpret_cast(&m_presenter2)); + + m_frameRateOption = m_factory->GetOptions()->maxFrameRate; // Query monitor info form DXVK's DXGI factory, if available m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast(&m_monitorInfo)); @@ -328,6 +331,7 @@ namespace dxvk { SyncInterval = options->syncInterval; UpdateGlobalHDRState(); + UpdateTargetFrameRate(SyncInterval); std::lock_guard lockWin(m_lockWindow); HRESULT hr = S_OK; @@ -767,7 +771,7 @@ namespace dxvk { HRESULT hr = pOutput->FindClosestMatchingMode1( &preferredMode, &selectedMode, nullptr); - + if (FAILED(hr)) { Logger::err(str::format( "DXGI: Failed to query closest mode:", @@ -777,6 +781,9 @@ namespace dxvk { return hr; } + if (!selectedMode.RefreshRate.Denominator) + selectedMode.RefreshRate.Denominator = 1; + if (!wsi::setWindowMode(outputDesc.Monitor, m_window, ConvertDisplayMode(selectedMode))) return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; @@ -798,6 +805,8 @@ namespace dxvk { ReleaseMonitorData(); } + m_frameRateRefresh = double(selectedMode.RefreshRate.Numerator) + / double(selectedMode.RefreshRate.Denominator); return S_OK; } @@ -809,6 +818,7 @@ namespace dxvk { if (!wsi::restoreDisplayMode()) return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; + m_frameRateRefresh = 0.0; return S_OK; } @@ -952,4 +962,23 @@ namespace dxvk { return hr; } + + void DxgiSwapChain::UpdateTargetFrameRate( + UINT SyncInterval) { + if (m_presenter2 == nullptr) + return; + + // Use a negative number to indicate that the limiter should only + // be engaged if the target frame rate is actually exceeded + double frameRate = std::max(m_frameRateOption, 0.0); + + if (SyncInterval && m_frameRateOption == 0.0) + frameRate = -m_frameRateRefresh / double(SyncInterval); + + if (m_frameRateLimit != frameRate) { + m_frameRateLimit = frameRate; + m_presenter2->SetTargetFrameRate(frameRate); + } + } + } diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index 8ec7d5014..4bdad9d0f 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -187,12 +187,17 @@ namespace dxvk { Com m_presenter; Com m_presenter1; + Com m_presenter2; HMONITOR m_monitor; bool m_monitorHasOutput = true; bool m_frameStatisticsDisjoint = true; wsi::DxvkWindowState m_windowState; + double m_frameRateOption = 0.0; + double m_frameRateRefresh = 0.0; + double m_frameRateLimit = 0.0; + DXGI_COLOR_SPACE_TYPE m_colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; uint32_t m_globalHDRStateSerial = 0; @@ -233,6 +238,9 @@ namespace dxvk { DXGI_FORMAT Format, DXGI_COLOR_SPACE_TYPE ColorSpace); + void UpdateTargetFrameRate( + UINT SyncInterval); + HRESULT STDMETHODCALLTYPE PresentBase( UINT SyncInterval, UINT PresentFlags,