[dxgi] Implement frame statistics based on IDXGIVkSwapChain1

This commit is contained in:
Philip Rebohle 2023-06-16 16:07:48 +02:00
parent 28f48f9fdc
commit a287566c65
2 changed files with 89 additions and 39 deletions

View File

@ -16,12 +16,16 @@ namespace dxvk {
m_window (hWnd), m_window (hWnd),
m_desc (*pDesc), m_desc (*pDesc),
m_descFs (*pFullscreenDesc), m_descFs (*pFullscreenDesc),
m_presentCount(0u), m_presentId (0u),
m_presenter (pPresenter), m_presenter (pPresenter),
m_monitor (wsi::getWindowMonitor(m_window)) { m_monitor (wsi::getWindowMonitor(m_window)) {
if (FAILED(m_presenter->GetAdapter(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&m_adapter)))) if (FAILED(m_presenter->GetAdapter(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&m_adapter))))
throw DxvkError("DXGI: Failed to get adapter for present device"); 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<void**>(&m_presenter1));
// 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));
@ -183,15 +187,24 @@ namespace dxvk {
// Populate frame statistics with local present count and current time // Populate frame statistics with local present count and current time
auto t1Counter = dxvk::high_resolution_clock::get_counter(); 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->PresentRefreshCount = 0;
pStats->SyncRefreshCount = 0; pStats->SyncRefreshCount = 0;
pStats->SyncQPCTime.QuadPart = t1Counter; pStats->SyncQPCTime.QuadPart = t1Counter;
pStats->SyncGPUTime.QuadPart = 0; 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))) { if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorData))) {
auto refreshPeriod = computeRefreshPeriod( auto refreshPeriod = computeRefreshPeriod(
monitorData->LastMode.RefreshRate.Numerator, 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 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 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->PresentRefreshCount = m_presenter1 != nullptr
pStats->SyncRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); ? monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t2, refreshPeriod)
: monitorData->FrameStats.PresentRefreshCount;
pStats->SyncRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod);
ReleaseMonitorData(); 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) { HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetLastPresentCount(UINT* pLastPresentCount) {
if (pLastPresentCount == nullptr) if (pLastPresentCount == nullptr)
return E_INVALIDARG; return E_INVALIDARG;
*pLastPresentCount = m_presentCount; UINT64 presentId = m_presentId;
if (m_presenter1 != nullptr)
m_presenter1->GetLastPresentCount(&presentId);
*pLastPresentCount = UINT(presentId);
return S_OK; return S_OK;
} }
@ -281,43 +309,44 @@ namespace dxvk {
UINT PresentFlags, UINT PresentFlags,
const DXGI_PRESENT_PARAMETERS* pPresentParameters) { const DXGI_PRESENT_PARAMETERS* pPresentParameters) {
if (!wsi::isWindow(m_window))
return S_OK;
if (SyncInterval > 4) if (SyncInterval > 4)
return DXGI_ERROR_INVALID_CALL; return DXGI_ERROR_INVALID_CALL;
std::lock_guard<dxvk::recursive_mutex> lockWin(m_lockWindow); std::lock_guard<dxvk::recursive_mutex> lockWin(m_lockWindow);
HRESULT hr = S_OK;
try { if (wsi::isWindow(m_window)) {
std::lock_guard<dxvk::mutex> lockBuf(m_lockBuffer); std::lock_guard<dxvk::mutex> lockBuf(m_lockBuffer);
HRESULT hr = m_presenter->Present(SyncInterval, PresentFlags, nullptr); 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;
} }
// Update frame statistics if (PresentFlags & DXGI_PRESENT_TEST)
DXGI_VK_MONITOR_DATA* monitorData = nullptr; return hr;
if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorData))) { if (hr == S_OK) {
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); m_presentId += 1;
auto t1 = dxvk::high_resolution_clock::now();
monitorData->FrameStats.PresentCount += 1; // Update monitor frame statistics. This is not consistent with swap chain
monitorData->FrameStats.PresentRefreshCount = monitorData->FrameStats.SyncRefreshCount + computeRefreshCount(t0, t1, refreshPeriod); // frame statistics at all, but we want to ensure that all presents become
ReleaseMonitorData(); // 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 hr;
return S_OK;
} }
@ -804,9 +833,27 @@ namespace dxvk {
HRESULT DxgiSwapChain::AcquireMonitorData( HRESULT DxgiSwapChain::AcquireMonitorData(
HMONITOR hMonitor, HMONITOR hMonitor,
DXGI_VK_MONITOR_DATA** ppData) { DXGI_VK_MONITOR_DATA** ppData) {
return m_monitorInfo != nullptr if (m_monitorInfo == nullptr || !hMonitor)
? m_monitorInfo->AcquireMonitorData(hMonitor, ppData) return E_NOINTERFACE;
: 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<IDXGIOutput1> output;
if (SUCCEEDED(GetOutputFromMonitor(hMonitor, &output)))
hr = m_monitorInfo->AcquireMonitorData(hMonitor, ppData);
}
if (hMonitor == m_monitor)
m_monitorHasOutput = SUCCEEDED(hr);
return hr;
} }

View File

@ -183,11 +183,14 @@ namespace dxvk {
HWND m_window; HWND m_window;
DXGI_SWAP_CHAIN_DESC1 m_desc; DXGI_SWAP_CHAIN_DESC1 m_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC m_descFs; DXGI_SWAP_CHAIN_FULLSCREEN_DESC m_descFs;
UINT m_presentCount; UINT m_presentId;
Com<IDXGIVkSwapChain> m_presenter; Com<IDXGIVkSwapChain> m_presenter;
Com<IDXGIVkSwapChain1> m_presenter1;
HMONITOR m_monitor; HMONITOR m_monitor;
bool m_monitorHasOutput = true;
bool m_frameStatisticsDisjoint = true;
wsi::DxvkWindowState m_windowState; wsi::DxvkWindowState m_windowState;
HRESULT EnterFullscreenMode( HRESULT EnterFullscreenMode(