diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 4a78d38f..adfa667e 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -16,12 +16,16 @@ namespace dxvk { m_window (hWnd), m_desc (*pDesc), m_descFs (*pFullscreenDesc), - m_presentCount(0u), + m_presentId (0u), m_presenter (pPresenter), m_monitor (wsi::getWindowMonitor(m_window)) { if (FAILED(m_presenter->GetAdapter(__uuidof(IDXGIAdapter), reinterpret_cast(&m_adapter)))) throw DxvkError("DXGI: Failed to get adapter for present device"); - + + // 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)); + // Query monitor info form DXVK's DXGI factory, if available m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast(&m_monitorInfo)); @@ -183,15 +187,24 @@ namespace dxvk { // Populate frame statistics with local present count and current time auto t1Counter = dxvk::high_resolution_clock::get_counter(); - pStats->PresentCount = m_presentCount; + DXGI_VK_FRAME_STATISTICS frameStatistics = { }; + frameStatistics.PresentCount = m_presentId; + frameStatistics.PresentQPCTime = t1Counter; + + if (m_presenter1 != nullptr) + m_presenter1->GetFrameStatistics(&frameStatistics); + + // Fill in actual DXGI statistics, using monitor data to help compute + // vblank counts if possible. This is not fully accurate, especially on + // displays with variable refresh rates, but it's the best we can do. + DXGI_VK_MONITOR_DATA* monitorData = nullptr; + + pStats->PresentCount = frameStatistics.PresentCount; pStats->PresentRefreshCount = 0; pStats->SyncRefreshCount = 0; pStats->SyncQPCTime.QuadPart = t1Counter; pStats->SyncGPUTime.QuadPart = 0; - // If possible, use the monitor's frame statistics for vblank stats - DXGI_VK_MONITOR_DATA* monitorData = nullptr; - if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorData))) { auto refreshPeriod = computeRefreshPeriod( monitorData->LastMode.RefreshRate.Numerator, @@ -199,14 +212,24 @@ namespace dxvk { auto t0 = dxvk::high_resolution_clock::get_time_from_counter(monitorData->FrameStats.SyncQPCTime.QuadPart); auto t1 = dxvk::high_resolution_clock::get_time_from_counter(t1Counter); + auto t2 = dxvk::high_resolution_clock::get_time_from_counter(frameStatistics.PresentQPCTime); - pStats->PresentRefreshCount = monitorData->FrameStats.PresentRefreshCount; - pStats->SyncRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); + pStats->PresentRefreshCount = m_presenter1 != nullptr + ? monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t2, refreshPeriod) + : monitorData->FrameStats.PresentRefreshCount; + pStats->SyncRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); ReleaseMonitorData(); } - return S_OK; + // Docs say that DISJOINT is returned on the first call and around + // mode changes. Just make this swap chain state for now. + HRESULT hr = S_OK; + + if (std::exchange(m_frameStatisticsDisjoint, false)) + hr = DXGI_ERROR_FRAME_STATISTICS_DISJOINT; + + return hr; } @@ -258,8 +281,13 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetLastPresentCount(UINT* pLastPresentCount) { if (pLastPresentCount == nullptr) return E_INVALIDARG; - - *pLastPresentCount = m_presentCount; + + UINT64 presentId = m_presentId; + + if (m_presenter1 != nullptr) + m_presenter1->GetLastPresentCount(&presentId); + + *pLastPresentCount = UINT(presentId); return S_OK; } @@ -281,43 +309,44 @@ namespace dxvk { UINT PresentFlags, const DXGI_PRESENT_PARAMETERS* pPresentParameters) { - if (!wsi::isWindow(m_window)) - return S_OK; - if (SyncInterval > 4) return DXGI_ERROR_INVALID_CALL; std::lock_guard lockWin(m_lockWindow); + HRESULT hr = S_OK; - try { + if (wsi::isWindow(m_window)) { std::lock_guard lockBuf(m_lockBuffer); - HRESULT hr = m_presenter->Present(SyncInterval, PresentFlags, nullptr); - - if (hr != S_OK || (PresentFlags & DXGI_PRESENT_TEST)) - return hr; - } catch (const DxvkError& err) { - Logger::err(err.message()); - return DXGI_ERROR_DRIVER_INTERNAL_ERROR; + hr = m_presenter->Present(SyncInterval, PresentFlags, nullptr); } - // Update frame statistics - DXGI_VK_MONITOR_DATA* monitorData = nullptr; + if (PresentFlags & DXGI_PRESENT_TEST) + return hr; - if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorData))) { - auto refreshPeriod = computeRefreshPeriod( - monitorData->LastMode.RefreshRate.Numerator, - monitorData->LastMode.RefreshRate.Denominator); + if (hr == S_OK) { - auto t0 = dxvk::high_resolution_clock::get_time_from_counter(monitorData->FrameStats.SyncQPCTime.QuadPart); - auto t1 = dxvk::high_resolution_clock::now(); + m_presentId += 1; - monitorData->FrameStats.PresentCount += 1; - monitorData->FrameStats.PresentRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); - ReleaseMonitorData(); + // Update monitor frame statistics. This is not consistent with swap chain + // frame statistics at all, but we want to ensure that all presents become + // visible to the IDXGIOutput in case applications rely on that behaviour. + DXGI_VK_MONITOR_DATA* monitorData = nullptr; + + if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorData))) { + auto refreshPeriod = computeRefreshPeriod( + monitorData->LastMode.RefreshRate.Numerator, + monitorData->LastMode.RefreshRate.Denominator); + + auto t0 = dxvk::high_resolution_clock::get_time_from_counter(monitorData->FrameStats.SyncQPCTime.QuadPart); + auto t1 = dxvk::high_resolution_clock::now(); + + monitorData->FrameStats.PresentCount += 1; + monitorData->FrameStats.PresentRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); + ReleaseMonitorData(); + } } - m_presentCount += 1; - return S_OK; + return hr; } @@ -804,9 +833,27 @@ namespace dxvk { HRESULT DxgiSwapChain::AcquireMonitorData( HMONITOR hMonitor, DXGI_VK_MONITOR_DATA** ppData) { - return m_monitorInfo != nullptr - ? m_monitorInfo->AcquireMonitorData(hMonitor, ppData) - : E_NOINTERFACE; + if (m_monitorInfo == nullptr || !hMonitor) + return E_NOINTERFACE; + + HRESULT hr = m_monitorInfo->AcquireMonitorData(hMonitor, ppData); + + if (FAILED(hr)) { + // We may need to initialize a DXGI output to populate monitor data. + // If acquiring monitor data has failed previously, do not try again. + if (hMonitor == m_monitor && !m_monitorHasOutput) + return E_NOINTERFACE; + + Com output; + + if (SUCCEEDED(GetOutputFromMonitor(hMonitor, &output))) + hr = m_monitorInfo->AcquireMonitorData(hMonitor, ppData); + } + + if (hMonitor == m_monitor) + m_monitorHasOutput = SUCCEEDED(hr); + + return hr; } diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index c32c2c79..b87b71e4 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -183,11 +183,14 @@ namespace dxvk { HWND m_window; DXGI_SWAP_CHAIN_DESC1 m_desc; DXGI_SWAP_CHAIN_FULLSCREEN_DESC m_descFs; - UINT m_presentCount; + UINT m_presentId; Com m_presenter; + Com m_presenter1; HMONITOR m_monitor; + bool m_monitorHasOutput = true; + bool m_frameStatisticsDisjoint = true; wsi::DxvkWindowState m_windowState; HRESULT EnterFullscreenMode(