[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
# 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

View File

@ -30,7 +30,6 @@ namespace dxvk {
this->deferSurfaceCreation = config.getOption<bool>("dxgi.deferSurfaceCreation", false);
this->numBackBuffers = config.getOption<int32_t>("dxgi.numBackBuffers", 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->longMad = config.getOption<bool>("d3d11.longMad", 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.
int32_t maxFrameLatency;
/// Limit frame rate
int32_t maxFrameRate;
/// Limit discardable resource size
VkDeviceSize maxImplicitDiscardSize;

View File

@ -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);
}

View File

@ -13,7 +13,7 @@ namespace dxvk {
class D3D11Device;
class D3D11DXGIDevice;
class D3D11SwapChain : public ComObject<IDXGIVkSwapChain1> {
class D3D11SwapChain : public ComObject<IDXGIVkSwapChain2> {
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<Rc<DxvkImageView>> 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<sync::CallbackFence> 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<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;
bool m_dirtyHdrMetadata = true;
bool m_dirtyHdrMetadata = true;
double m_targetFrameRate = 0.0;
dxvk::mutex m_frameStatisticsLock;
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
*/
@ -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

View File

@ -93,6 +93,7 @@ namespace dxvk {
this->maxDeviceMemory = VkDeviceSize(config.getOption<int32_t>("dxgi.maxDeviceMemory", 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);
// Expose Nvidia GPUs properly if NvAPI is enabled in environment

View File

@ -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;

View File

@ -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<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
m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast<void**>(&m_monitorInfo));
@ -328,6 +331,7 @@ namespace dxvk {
SyncInterval = options->syncInterval;
UpdateGlobalHDRState();
UpdateTargetFrameRate(SyncInterval);
std::lock_guard<dxvk::recursive_mutex> 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);
}
}
}

View File

@ -187,12 +187,17 @@ namespace dxvk {
Com<IDXGIVkSwapChain> m_presenter;
Com<IDXGIVkSwapChain1> m_presenter1;
Com<IDXGIVkSwapChain2> 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,