[dxgi] Limit frame rate to display refresh as necessary

This commit is contained in:
Philip Rebohle 2024-06-06 10:32:02 +02:00 committed by Philip Rebohle
parent 379346751a
commit 1c198dcd48
10 changed files with 82 additions and 17 deletions

View file

@ -43,7 +43,12 @@
# bugs in games that have physics or other simulation tied to their frame # bugs in games that have physics or other simulation tied to their frame
# rate, but do not provide their own limiter. # 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 # dxgi.maxFrameRate = 0
# d3d9.maxFrameRate = 0 # d3d9.maxFrameRate = 0

View file

@ -30,7 +30,6 @@ namespace dxvk {
this->deferSurfaceCreation = config.getOption<bool>("dxgi.deferSurfaceCreation", false); this->deferSurfaceCreation = config.getOption<bool>("dxgi.deferSurfaceCreation", false);
this->numBackBuffers = config.getOption<int32_t>("dxgi.numBackBuffers", 0); this->numBackBuffers = config.getOption<int32_t>("dxgi.numBackBuffers", 0);
this->maxFrameLatency = config.getOption<int32_t>("dxgi.maxFrameLatency", 0); this->maxFrameLatency = config.getOption<int32_t>("dxgi.maxFrameLatency", 0);
this->maxFrameRate = config.getOption<int32_t>("dxgi.maxFrameRate", 0);
this->exposeDriverCommandLists = config.getOption<bool>("d3d11.exposeDriverCommandLists", true); this->exposeDriverCommandLists = config.getOption<bool>("d3d11.exposeDriverCommandLists", true);
this->longMad = config.getOption<bool>("d3d11.longMad", false); this->longMad = config.getOption<bool>("d3d11.longMad", false);
this->reproducibleCommandStream = config.getOption<bool>("d3d11.reproducibleCommandStream", false); this->reproducibleCommandStream = config.getOption<bool>("d3d11.reproducibleCommandStream", false);

View file

@ -80,9 +80,6 @@ namespace dxvk {
/// a higher value. May help with frame timing issues. /// a higher value. May help with frame timing issues.
int32_t maxFrameLatency; int32_t maxFrameLatency;
/// Limit frame rate
int32_t maxFrameRate;
/// Limit discardable resource size /// Limit discardable resource size
VkDeviceSize maxImplicitDiscardSize; VkDeviceSize maxImplicitDiscardSize;

View file

@ -99,7 +99,8 @@ namespace dxvk {
if (riid == __uuidof(IUnknown) if (riid == __uuidof(IUnknown)
|| riid == __uuidof(IDXGIVkSwapChain) || riid == __uuidof(IDXGIVkSwapChain)
|| riid == __uuidof(IDXGIVkSwapChain1)) { || riid == __uuidof(IDXGIVkSwapChain1)
|| riid == __uuidof(IDXGIVkSwapChain2)) {
*ppvObject = ref(this); *ppvObject = ref(this);
return S_OK; 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) { HRESULT D3D11SwapChain::PresentImage(UINT SyncInterval) {
// Flush pending rendering commands before // Flush pending rendering commands before
auto immediateContext = m_parent->GetContext(); auto immediateContext = m_parent->GetContext();
@ -496,7 +506,7 @@ namespace dxvk {
presenterDesc.fullScreenExclusive = PickFullscreenMode(); presenterDesc.fullScreenExclusive = PickFullscreenMode();
m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc); m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc);
m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate); m_presenter->setFrameRateLimit(m_targetFrameRate);
} }

View file

@ -13,7 +13,7 @@ namespace dxvk {
class D3D11Device; class D3D11Device;
class D3D11DXGIDevice; class D3D11DXGIDevice;
class D3D11SwapChain : public ComObject<IDXGIVkSwapChain1> { class D3D11SwapChain : public ComObject<IDXGIVkSwapChain2> {
constexpr static uint32_t DefaultFrameLatency = 1; constexpr static uint32_t DefaultFrameLatency = 1;
public: public:
@ -86,6 +86,9 @@ namespace dxvk {
void STDMETHODCALLTYPE GetFrameStatistics( void STDMETHODCALLTYPE GetFrameStatistics(
DXGI_VK_FRAME_STATISTICS* pFrameStatistics); DXGI_VK_FRAME_STATISTICS* pFrameStatistics);
void STDMETHODCALLTYPE SetTargetFrameRate(
double FrameRate);
private: private:
enum BindingIds : uint32_t { enum BindingIds : uint32_t {
@ -116,18 +119,20 @@ namespace dxvk {
std::vector<Rc<DxvkImageView>> m_imageViews; std::vector<Rc<DxvkImageView>> m_imageViews;
uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS; uint64_t m_frameId = DXGI_MAX_SWAP_CHAIN_BUFFERS;
uint32_t m_frameLatency = DefaultFrameLatency; uint32_t m_frameLatency = DefaultFrameLatency;
uint32_t m_frameLatencyCap = 0; uint32_t m_frameLatencyCap = 0;
HANDLE m_frameLatencyEvent = nullptr; HANDLE m_frameLatencyEvent = nullptr;
Rc<sync::CallbackFence> m_frameLatencySignal; Rc<sync::CallbackFence> 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<VkHdrMetadataEXT> m_hdrMetadata; std::optional<VkHdrMetadataEXT> m_hdrMetadata;
bool m_dirtyHdrMetadata = true; bool m_dirtyHdrMetadata = true;
double m_targetFrameRate = 0.0;
dxvk::mutex m_frameStatisticsLock; dxvk::mutex m_frameStatisticsLock;
DXGI_VK_FRAME_STATISTICS m_frameStatistics = { }; DXGI_VK_FRAME_STATISTICS m_frameStatistics = { };

View file

@ -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 * \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(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(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(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); __CRT_UUID_DECL(IDXGIVkSwapChainFactory, 0xe7d6c3ca,0x23a0,0x4e08,0x9f,0x2f,0xea,0x52,0x31,0xdf,0x66,0x33);
#endif #endif

View file

@ -93,6 +93,7 @@ namespace dxvk {
this->maxDeviceMemory = VkDeviceSize(config.getOption<int32_t>("dxgi.maxDeviceMemory", 0)) << 20; this->maxDeviceMemory = VkDeviceSize(config.getOption<int32_t>("dxgi.maxDeviceMemory", 0)) << 20;
this->maxSharedMemory = VkDeviceSize(config.getOption<int32_t>("dxgi.maxSharedMemory", 0)) << 20; this->maxSharedMemory = VkDeviceSize(config.getOption<int32_t>("dxgi.maxSharedMemory", 0)) << 20;
this->maxFrameRate = config.getOption<int32_t>("dxgi.maxFrameRate", 0);
this->syncInterval = config.getOption<int32_t>("dxgi.syncInterval", -1); this->syncInterval = config.getOption<int32_t>("dxgi.syncInterval", -1);
// Expose Nvidia GPUs properly if NvAPI is enabled in environment // Expose Nvidia GPUs properly if NvAPI is enabled in environment

View file

@ -49,6 +49,9 @@ namespace dxvk {
/// Enable HDR /// Enable HDR
bool enableHDR; bool enableHDR;
/// Limit frame rate
int32_t maxFrameRate;
/// Sync interval. Overrides the value /// Sync interval. Overrides the value
/// passed to IDXGISwapChain::Present. /// passed to IDXGISwapChain::Present.
int32_t syncInterval; int32_t syncInterval;

View file

@ -25,6 +25,9 @@ namespace dxvk {
// Query updated interface versions from presenter, this // Query updated interface versions from presenter, this
// may fail e.g. with older vkd3d-proton builds. // may fail e.g. with older vkd3d-proton builds.
m_presenter->QueryInterface(__uuidof(IDXGIVkSwapChain1), reinterpret_cast<void**>(&m_presenter1)); m_presenter->QueryInterface(__uuidof(IDXGIVkSwapChain1), reinterpret_cast<void**>(&m_presenter1));
m_presenter->QueryInterface(__uuidof(IDXGIVkSwapChain2), reinterpret_cast<void**>(&m_presenter2));
m_frameRateOption = m_factory->GetOptions()->maxFrameRate;
// Query monitor info form DXVK's DXGI factory, if available // Query monitor info form DXVK's DXGI factory, if available
m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast<void**>(&m_monitorInfo)); m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast<void**>(&m_monitorInfo));
@ -328,6 +331,7 @@ namespace dxvk {
SyncInterval = options->syncInterval; SyncInterval = options->syncInterval;
UpdateGlobalHDRState(); UpdateGlobalHDRState();
UpdateTargetFrameRate(SyncInterval);
std::lock_guard<dxvk::recursive_mutex> lockWin(m_lockWindow); std::lock_guard<dxvk::recursive_mutex> lockWin(m_lockWindow);
HRESULT hr = S_OK; HRESULT hr = S_OK;
@ -767,7 +771,7 @@ namespace dxvk {
HRESULT hr = pOutput->FindClosestMatchingMode1( HRESULT hr = pOutput->FindClosestMatchingMode1(
&preferredMode, &selectedMode, nullptr); &preferredMode, &selectedMode, nullptr);
if (FAILED(hr)) { if (FAILED(hr)) {
Logger::err(str::format( Logger::err(str::format(
"DXGI: Failed to query closest mode:", "DXGI: Failed to query closest mode:",
@ -777,6 +781,9 @@ namespace dxvk {
return hr; return hr;
} }
if (!selectedMode.RefreshRate.Denominator)
selectedMode.RefreshRate.Denominator = 1;
if (!wsi::setWindowMode(outputDesc.Monitor, m_window, ConvertDisplayMode(selectedMode))) if (!wsi::setWindowMode(outputDesc.Monitor, m_window, ConvertDisplayMode(selectedMode)))
return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
@ -798,6 +805,8 @@ namespace dxvk {
ReleaseMonitorData(); ReleaseMonitorData();
} }
m_frameRateRefresh = double(selectedMode.RefreshRate.Numerator)
/ double(selectedMode.RefreshRate.Denominator);
return S_OK; return S_OK;
} }
@ -809,6 +818,7 @@ namespace dxvk {
if (!wsi::restoreDisplayMode()) if (!wsi::restoreDisplayMode())
return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
m_frameRateRefresh = 0.0;
return S_OK; return S_OK;
} }
@ -952,4 +962,23 @@ namespace dxvk {
return hr; 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);
}
}
} }

View file

@ -187,12 +187,17 @@ namespace dxvk {
Com<IDXGIVkSwapChain> m_presenter; Com<IDXGIVkSwapChain> m_presenter;
Com<IDXGIVkSwapChain1> m_presenter1; Com<IDXGIVkSwapChain1> m_presenter1;
Com<IDXGIVkSwapChain2> m_presenter2;
HMONITOR m_monitor; HMONITOR m_monitor;
bool m_monitorHasOutput = true; bool m_monitorHasOutput = true;
bool m_frameStatisticsDisjoint = true; bool m_frameStatisticsDisjoint = true;
wsi::DxvkWindowState m_windowState; 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; DXGI_COLOR_SPACE_TYPE m_colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
uint32_t m_globalHDRStateSerial = 0; uint32_t m_globalHDRStateSerial = 0;
@ -233,6 +238,9 @@ namespace dxvk {
DXGI_FORMAT Format, DXGI_FORMAT Format,
DXGI_COLOR_SPACE_TYPE ColorSpace); DXGI_COLOR_SPACE_TYPE ColorSpace);
void UpdateTargetFrameRate(
UINT SyncInterval);
HRESULT STDMETHODCALLTYPE PresentBase( HRESULT STDMETHODCALLTYPE PresentBase(
UINT SyncInterval, UINT SyncInterval,
UINT PresentFlags, UINT PresentFlags,