From bc8b09103624970965aa1d8c4cb7c4617231e1e0 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 12 Jan 2025 23:26:56 +0100 Subject: [PATCH] [dxvk] Move Vulkan swapchain management to backend Massive cleanup reduce code duplication between D3D11 and D3D9, and introduce a sane path to pass data around. Implicit swap chain recreation is now entirely transparent to the frontends. --- src/d3d11/d3d11_swapchain.cpp | 152 ++++-------- src/d3d11/d3d11_swapchain.h | 13 +- src/d3d9/d3d9_swapchain.cpp | 165 +++++-------- src/d3d9/d3d9_swapchain.h | 16 +- src/dxvk/dxvk_device.cpp | 2 - src/dxvk/dxvk_device.h | 2 - src/dxvk/dxvk_presenter.cpp | 445 +++++++++++++++++++++++----------- src/dxvk/dxvk_presenter.h | 165 +++++++------ src/dxvk/dxvk_queue.cpp | 5 +- src/dxvk/dxvk_queue.h | 1 - 10 files changed, 514 insertions(+), 452 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 2fc84ab49..df3e99879 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -174,11 +174,11 @@ namespace dxvk { const DXGI_SWAP_CHAIN_DESC1* pDesc, const UINT* pNodeMasks, IUnknown* const* ppPresentQueues) { - m_dirty |= m_desc.Format != pDesc->Format - || m_desc.Width != pDesc->Width - || m_desc.Height != pDesc->Height - || m_desc.BufferCount != pDesc->BufferCount - || m_desc.Flags != pDesc->Flags; + if (m_desc.Format != pDesc->Format) + m_presenter->setSurfaceFormat(GetSurfaceFormat(pDesc->Format)); + + if (m_desc.Width != pDesc->Width || m_desc.Height != pDesc->Height) + m_presenter->setSurfaceExtent({ m_desc.Width, m_desc.Height }); m_desc = *pDesc; CreateBackBuffers(); @@ -251,33 +251,32 @@ namespace dxvk { UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS* pPresentParameters) { - if (!(PresentFlags & DXGI_PRESENT_TEST)) - m_dirty |= m_presenter->setSyncInterval(SyncInterval) != VK_SUCCESS; - HRESULT hr = S_OK; - if (!m_presenter->hasSwapChain()) { - RecreateSwapChain(); - m_dirty = false; - } - - if (!m_presenter->hasSwapChain()) - hr = DXGI_STATUS_OCCLUDED; - if (m_device->getDeviceStatus() != VK_SUCCESS) hr = DXGI_ERROR_DEVICE_RESET; - if (PresentFlags & DXGI_PRESENT_TEST) - return hr; + if (PresentFlags & DXGI_PRESENT_TEST) { + if (hr != S_OK) + return hr; + + // If the current present status is NOT_READY, we have a present + // in flight, which means that we can most likely present again. + // This avoids an expensive sync point. + VkResult status = m_presentStatus.result.load(); + + if (status == VK_NOT_READY) + return S_OK; + + status = m_presenter->checkSwapChainStatus(); + return status == VK_SUCCESS ? S_OK : DXGI_STATUS_OCCLUDED; + } if (hr != S_OK) { SyncFrameLatency(); return hr; } - if (std::exchange(m_dirty, false)) - RecreateSwapChain(); - try { hr = PresentImage(SyncInterval); } catch (const DxvkError& e) { @@ -298,7 +297,8 @@ namespace dxvk { DXGI_COLOR_SPACE_TYPE ColorSpace) { UINT supportFlags = 0; - const VkColorSpaceKHR vkColorSpace = ConvertColorSpace(ColorSpace); + VkColorSpaceKHR vkColorSpace = ConvertColorSpace(ColorSpace); + if (m_presenter->supportsColorSpace(vkColorSpace)) supportFlags |= DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT; @@ -308,13 +308,14 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D11SwapChain::SetColorSpace( DXGI_COLOR_SPACE_TYPE ColorSpace) { - if (!(CheckColorSpaceSupport(ColorSpace) & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) + VkColorSpaceKHR colorSpace = ConvertColorSpace(ColorSpace); + + if (!m_presenter->supportsColorSpace(colorSpace)) return E_INVALIDARG; - const VkColorSpaceKHR vkColorSpace = ConvertColorSpace(ColorSpace); - m_dirty |= vkColorSpace != m_colorspace; - m_colorspace = vkColorSpace; + m_colorSpace = colorSpace; + m_presenter->setSurfaceFormat(GetSurfaceFormat(m_desc.Format)); return S_OK; } @@ -378,27 +379,19 @@ namespace dxvk { SynchronizePresent(); - if (!m_presenter->hasSwapChain()) - return DXGI_STATUS_OCCLUDED; + m_presenter->setSyncInterval(SyncInterval); // Presentation semaphores and WSI swap chain image PresenterSync sync; - Rc backBuffer; VkResult status = m_presenter->acquireNextImage(sync, backBuffer); - while (status != VK_SUCCESS) { - RecreateSwapChain(); + if (status < 0) + return E_FAIL; - if (!m_presenter->hasSwapChain()) - return DXGI_STATUS_OCCLUDED; - - status = m_presenter->acquireNextImage(sync, backBuffer); - - if (status == VK_SUBOPTIMAL_KHR) - break; - } + if (status == VK_NOT_READY) + return DXGI_STATUS_OCCLUDED; m_frameId += 1; @@ -425,7 +418,7 @@ namespace dxvk { cSync = sync, cHud = m_hud, cPresenter = m_presenter, - cColorSpace = m_colorspace, + cColorSpace = m_colorSpace, cFrameId = m_frameId ] (DxvkContext* ctx) { // Blit the D3D back buffer onto the actual Vulkan @@ -448,7 +441,6 @@ namespace dxvk { ctx->flushCommandList(nullptr); cDevice->presentImage(cPresenter, - cPresenter->info().presentMode, cFrameId, cPresentStatus); }); @@ -480,30 +472,7 @@ namespace dxvk { void D3D11SwapChain::SynchronizePresent() { - // Recreate swap chain if the previous present call failed - VkResult status = m_device->waitForSubmission(&m_presentStatus); - - if (status != VK_SUCCESS) - RecreateSwapChain(); - } - - - void D3D11SwapChain::RecreateSwapChain() { - // Ensure that we can safely destroy the swap chain m_device->waitForSubmission(&m_presentStatus); - m_device->waitForIdle(); - - m_presentStatus.result = VK_SUCCESS; - - PresenterDesc presenterDesc; - presenterDesc.imageExtent = { m_desc.Width, m_desc.Height }; - presenterDesc.imageCount = PickImageCount(m_desc.BufferCount + 1); - presenterDesc.numFormats = PickFormats(m_desc.Format, presenterDesc.formats); - - VkResult vr = m_presenter->recreateSwapChain(presenterDesc); - - if (vr) - throw DxvkError(str::format("D3D11SwapChain: Failed to recreate swap chain: ", vr)); } @@ -516,10 +485,7 @@ namespace dxvk { void D3D11SwapChain::CreatePresenter() { - PresenterDesc presenterDesc; - presenterDesc.imageExtent = { m_desc.Width, m_desc.Height }; - presenterDesc.imageCount = PickImageCount(m_desc.BufferCount + 1); - presenterDesc.numFormats = PickFormats(m_desc.Format, presenterDesc.formats); + PresenterDesc presenterDesc = { }; presenterDesc.deferSurfaceCreation = m_parent->GetOptions()->deferSurfaceCreation; m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc, [ @@ -531,6 +497,8 @@ namespace dxvk { cAdapter->handle(), surface); }); + m_presenter->setSurfaceFormat(GetSurfaceFormat(m_desc.Format)); + m_presenter->setSurfaceExtent({ m_desc.Width, m_desc.Height }); m_presenter->setFrameRateLimit(m_targetFrameRate, GetActualFrameLatency()); } @@ -658,46 +626,26 @@ namespace dxvk { } - uint32_t D3D11SwapChain::PickFormats( - DXGI_FORMAT Format, - VkSurfaceFormatKHR* pDstFormats) { - uint32_t n = 0; - + VkSurfaceFormatKHR D3D11SwapChain::GetSurfaceFormat(DXGI_FORMAT Format) { switch (Format) { default: Logger::warn(str::format("D3D11SwapChain: Unexpected format: ", m_desc.Format)); - [[fallthrough]]; - + [[fallthrough]]; + case DXGI_FORMAT_R8G8B8A8_UNORM: - case DXGI_FORMAT_B8G8R8A8_UNORM: { - pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, m_colorspace }; - } break; - + case DXGI_FORMAT_B8G8R8A8_UNORM: + return { VK_FORMAT_R8G8B8A8_UNORM, m_colorSpace }; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: - case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: { - pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_SRGB, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_SRGB, m_colorspace }; - } break; - - case DXGI_FORMAT_R10G10B10A2_UNORM: { - pDstFormats[n++] = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_A2R10G10B10_UNORM_PACK32, m_colorspace }; - } break; - - case DXGI_FORMAT_R16G16B16A16_FLOAT: { - pDstFormats[n++] = { VK_FORMAT_R16G16B16A16_SFLOAT, m_colorspace }; - } break; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return { VK_FORMAT_R8G8B8A8_SRGB, m_colorSpace }; + + case DXGI_FORMAT_R10G10B10A2_UNORM: + return { VK_FORMAT_A2B10G10R10_UNORM_PACK32, m_colorSpace }; + + case DXGI_FORMAT_R16G16B16A16_FLOAT: + return { VK_FORMAT_R16G16B16A16_SFLOAT, m_colorSpace }; } - - return n; - } - - - uint32_t D3D11SwapChain::PickImageCount( - UINT Preferred) { - int32_t option = m_parent->GetOptions()->numBackBuffers; - return option > 0 ? uint32_t(option) : uint32_t(Preferred); } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 80e8dfb81..74149f9bf 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -119,9 +119,7 @@ namespace dxvk { HANDLE m_frameLatencyEvent = nullptr; Rc m_frameLatencySignal; - bool m_dirty = true; - - VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + VkColorSpaceKHR m_colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; double m_targetFrameRate = 0.0; @@ -136,8 +134,6 @@ namespace dxvk { void SynchronizePresent(); - void RecreateSwapChain(); - void CreateFrameLatencyEvent(); void CreatePresenter(); @@ -154,12 +150,7 @@ namespace dxvk { uint32_t GetActualFrameLatency(); - uint32_t PickFormats( - DXGI_FORMAT Format, - VkSurfaceFormatKHR* pDstFormats); - - uint32_t PickImageCount( - UINT Preferred); + VkSurfaceFormatKHR GetSurfaceFormat(DXGI_FORMAT Format); std::string GetApiName() const; diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index c302c493e..e0a0218ab 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -153,18 +153,16 @@ namespace dxvk { UpdateWindowCtx(); - bool recreate = false; - recreate |= m_wctx->presenter == nullptr; + bool recreate = !m_wctx->presenter; + if (options->deferSurfaceCreation) recreate |= m_parent->IsDeviceReset(); - if (m_wctx->presenter != nullptr) { - m_dirty |= m_wctx->presenter->setSyncInterval(presentInterval) != VK_SUCCESS; - m_dirty |= !m_wctx->presenter->hasSwapChain(); - } + if (m_wctx->presenter) + m_wctx->presenter->setSyncInterval(presentInterval); - m_dirty |= UpdatePresentRegion(pSourceRect, pDestRect); - m_dirty |= recreate; + UpdatePresentRegion(pSourceRect, pDestRect); + UpdatePresentParameters(); #ifdef _WIN32 const bool useGDIFallback = m_partialCopy && !HasFrontBuffer(); @@ -174,17 +172,11 @@ namespace dxvk { try { if (recreate) - CreatePresenter(); - - if (std::exchange(m_dirty, false)) - RecreateSwapChain(); + RecreateSurface(); // We aren't going to device loss simply because // 99% of D3D9 games don't handle this properly and // just end up crashing (like with alt-tab loss) - if (!m_wctx->presenter->hasSwapChain()) - return D3D_OK; - UpdateTargetFrameRate(presentInterval); PresentImage(presentInterval); return D3D_OK; @@ -606,9 +598,6 @@ namespace dxvk { this->SynchronizePresent(); this->NormalizePresentParameters(pPresentParams); - m_dirty |= m_presentParams.BackBufferFormat != pPresentParams->BackBufferFormat - || m_presentParams.BackBufferCount != pPresentParams->BackBufferCount; - bool changeFullscreen = m_presentParams.Windowed != pPresentParams->Windowed; if (pPresentParams->Windowed) { @@ -640,6 +629,8 @@ namespace dxvk { if (changeFullscreen) SetGammaRamp(0, &m_ramp); + UpdatePresentParameters(); + hr = CreateBackBuffers(m_presentParams.BackBufferCount, m_presentParams.Flags); if (FAILED(hr)) return hr; @@ -826,27 +817,13 @@ namespace dxvk { SynchronizePresent(); // Presentation semaphores and WSI swap chain image - PresenterInfo info = m_wctx->presenter->info(); PresenterSync sync = { }; - Rc backBuffer; VkResult status = m_wctx->presenter->acquireNextImage(sync, backBuffer); - while (status != VK_SUCCESS) { - RecreateSwapChain(); - - info = m_wctx->presenter->info(); - status = m_wctx->presenter->acquireNextImage(sync, backBuffer); - - if (status == VK_SUBOPTIMAL_KHR) - break; - } - - if (m_hdrMetadata && m_dirtyHdrMetadata) { - m_wctx->presenter->setHdrMetadata(*m_hdrMetadata); - m_dirtyHdrMetadata = false; - } + if (status < 0 || status == VK_NOT_READY) + break; VkRect2D srcRect = { { int32_t(m_srcRect.left), int32_t(m_srcRect.top) }, @@ -912,7 +889,6 @@ namespace dxvk { uint64_t frameId = cRepeat ? 0 : cFrameId; cDevice->presentImage(cPresenter, - cPresenter->info().presentMode, frameId, cPresentStatus); }); @@ -931,29 +907,15 @@ namespace dxvk { void D3D9SwapChainEx::SynchronizePresent() { - // Recreate swap chain if the previous present call failed - VkResult status = m_device->waitForSubmission(&m_presentStatus); - - if (status != VK_SUCCESS) - RecreateSwapChain(); + m_device->waitForSubmission(&m_presentStatus); } - void D3D9SwapChainEx::RecreateSwapChain() { - // Ensure that we can safely destroy the swap chain - m_device->waitForSubmission(&m_presentStatus); - m_device->waitForIdle(); - m_presentStatus.result = VK_SUCCESS; - - PresenterDesc presenterDesc; - presenterDesc.imageExtent = GetPresentExtent(); - presenterDesc.imageCount = PickImageCount(m_presentParams.BackBufferCount + 1); - presenterDesc.numFormats = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats); - - VkResult vr = m_wctx->presenter->recreateSwapChain(presenterDesc); - - if (vr) - throw DxvkError(str::format("D3D9SwapChainEx: Failed to recreate swap chain: ", vr)); + void D3D9SwapChainEx::RecreateSurface() { + if (m_wctx->presenter) + m_wctx->presenter->invalidateSurface(); + else + CreatePresenter(); } @@ -965,9 +927,6 @@ namespace dxvk { m_presentStatus.result = VK_SUCCESS; PresenterDesc presenterDesc; - presenterDesc.imageExtent = GetPresentExtent(); - presenterDesc.imageCount = PickImageCount(m_presentParams.BackBufferCount + 1); - presenterDesc.numFormats = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats); presenterDesc.deferSurfaceCreation = m_parent->GetOptions()->deferSurfaceCreation; m_wctx->presenter = new Presenter(m_device, @@ -982,6 +941,12 @@ namespace dxvk { vki->instance(), surface); }); + + m_wctx->presenter->setSurfaceExtent(m_swapchainExtent); + m_wctx->presenter->setSurfaceFormat(GetSurfaceFormat()); + + if (m_hdrMetadata) + m_wctx->presenter->setHdrMetadata(*m_hdrMetadata); } @@ -1138,60 +1103,44 @@ namespace dxvk { } - uint32_t D3D9SwapChainEx::PickFormats( - D3D9Format Format, - VkSurfaceFormatKHR* pDstFormats) { - uint32_t n = 0; + VkSurfaceFormatKHR D3D9SwapChainEx::GetSurfaceFormat() { + D3D9Format format = EnumerateFormat(m_presentParams.BackBufferFormat); - switch (Format) { + switch (format) { default: - Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", Format)); - [[fallthrough]]; + Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", format)); + [[fallthrough]]; case D3D9Format::A8R8G8B8: case D3D9Format::X8R8G8B8: + return { VK_FORMAT_B8G8R8A8_UNORM, m_colorspace }; + case D3D9Format::A8B8G8R8: - case D3D9Format::X8B8G8R8: { - pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, m_colorspace }; - } break; + case D3D9Format::X8B8G8R8: + return { VK_FORMAT_R8G8B8A8_UNORM, m_colorspace }; case D3D9Format::A2R10G10B10: - case D3D9Format::A2B10G10R10: { - pDstFormats[n++] = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_A2R10G10B10_UNORM_PACK32, m_colorspace }; - } break; + return { VK_FORMAT_A2R10G10B10_UNORM_PACK32, m_colorspace }; + + case D3D9Format::A2B10G10R10: + return { VK_FORMAT_A2B10G10R10_UNORM_PACK32, m_colorspace }; case D3D9Format::X1R5G5B5: - case D3D9Format::A1R5G5B5: { - pDstFormats[n++] = { VK_FORMAT_B5G5R5A1_UNORM_PACK16, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_R5G5B5A1_UNORM_PACK16, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_A1R5G5B5_UNORM_PACK16, m_colorspace }; - } break; + case D3D9Format::A1R5G5B5: + return { VK_FORMAT_B5G5R5A1_UNORM_PACK16, m_colorspace }; - case D3D9Format::R5G6B5: { - pDstFormats[n++] = { VK_FORMAT_B5G6R5_UNORM_PACK16, m_colorspace }; - pDstFormats[n++] = { VK_FORMAT_R5G6B5_UNORM_PACK16, m_colorspace }; - } break; + case D3D9Format::R5G6B5: + return { VK_FORMAT_B5G6R5_UNORM_PACK16, m_colorspace }; case D3D9Format::A16B16G16R16F: { - if (m_unlockAdditionalFormats) { - pDstFormats[n++] = { VK_FORMAT_R16G16B16A16_SFLOAT, m_colorspace }; - } else { - Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", Format)); + if (!m_unlockAdditionalFormats) { + Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", format)); + return VkSurfaceFormatKHR { }; } - break; + + return { VK_FORMAT_R16G16B16A16_SFLOAT, m_colorspace }; } } - - return n; - } - - - uint32_t D3D9SwapChainEx::PickImageCount( - UINT Preferred) { - int32_t option = m_parent->GetOptions()->numBackBuffers; - return option > 0 ? uint32_t(option) : uint32_t(Preferred); } @@ -1297,7 +1246,7 @@ namespace dxvk { return D3D_OK; } - bool D3D9SwapChainEx::UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect) { + void D3D9SwapChainEx::UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect) { const bool isWindowed = m_presentParams.Windowed; // Tests show that present regions are ignored in fullscreen @@ -1333,15 +1282,15 @@ namespace dxvk { || dstRect.right - dstRect.left != LONG(width) || dstRect.bottom - dstRect.top != LONG(height); - bool recreate = m_wctx != nullptr - && (m_wctx->presenter == nullptr - || m_wctx->presenter->info().imageExtent.width != width - || m_wctx->presenter->info().imageExtent.height != height); - m_swapchainExtent = { width, height }; m_dstRect = dstRect; + } - return recreate; + void D3D9SwapChainEx::UpdatePresentParameters() { + if (m_wctx && m_wctx->presenter) { + m_wctx->presenter->setSurfaceExtent(m_swapchainExtent); + m_wctx->presenter->setSurfaceFormat(GetSurfaceFormat()); + } } VkExtent2D D3D9SwapChainEx::GetPresentExtent() { @@ -1386,9 +1335,11 @@ namespace dxvk { if (!CheckColorSpaceSupport(ColorSpace)) return D3DERR_INVALIDCALL; - m_swapchain->m_dirty |= ColorSpace != m_swapchain->m_colorspace; m_swapchain->m_colorspace = ColorSpace; + if (m_swapchain->m_wctx && m_swapchain->m_wctx->presenter) + m_swapchain->m_wctx->presenter->setSurfaceFormat(m_swapchain->GetSurfaceFormat()); + return S_OK; } @@ -1397,8 +1348,10 @@ namespace dxvk { if (!pHDRMetadata) return D3DERR_INVALIDCALL; - m_swapchain->m_hdrMetadata = *pHDRMetadata; - m_swapchain->m_dirtyHdrMetadata = true; + m_swapchain->m_hdrMetadata = *pHDRMetadata; + + if (m_swapchain->m_wctx && m_swapchain->m_wctx->presenter) + m_swapchain->m_wctx->presenter->setHdrMetadata(*pHDRMetadata); return S_OK; } diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index 1ae3c6b8a..c413a73c0 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -169,8 +169,6 @@ namespace dxvk { uint32_t m_frameLatencyCap = 0; - bool m_dirty = true; - HWND m_window = nullptr; HMONITOR m_monitor = nullptr; @@ -185,7 +183,6 @@ namespace dxvk { VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; std::optional m_hdrMetadata; - bool m_dirtyHdrMetadata = true; bool m_unlockAdditionalFormats = false; D3D9VkExtSwapchain m_swapchainExt; @@ -194,7 +191,7 @@ namespace dxvk { void SynchronizePresent(); - void RecreateSwapChain(); + void RecreateSurface(); void CreatePresenter(); @@ -212,13 +209,8 @@ namespace dxvk { uint32_t GetActualFrameLatency(); - uint32_t PickFormats( - D3D9Format Format, - VkSurfaceFormatKHR* pDstFormats); + VkSurfaceFormatKHR GetSurfaceFormat(); - uint32_t PickImageCount( - UINT Preferred); - void NormalizePresentParameters(D3DPRESENT_PARAMETERS* pPresentParams); void NotifyDisplayRefreshRate( @@ -236,7 +228,9 @@ namespace dxvk { HRESULT RestoreDisplayMode(HMONITOR hMonitor); - bool UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect); + void UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect); + + void UpdatePresentParameters(); VkExtent2D GetPresentExtent(); diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 86c789dd8..36275e2a3 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -307,14 +307,12 @@ namespace dxvk { void DxvkDevice::presentImage( const Rc& presenter, - VkPresentModeKHR presentMode, uint64_t frameId, DxvkSubmitStatus* status) { status->result = VK_NOT_READY; DxvkPresentInfo presentInfo = { }; presentInfo.presenter = presenter; - presentInfo.presentMode = presentMode; presentInfo.frameId = frameId; m_submissionQueue.present(presentInfo, status); diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index 219655fa9..a40a25fec 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -485,13 +485,11 @@ namespace dxvk { * the submission thread. The status of this operation * can be retrieved with \ref waitForSubmission. * \param [in] presenter The presenter - * \param [in] presenteMode Present mode * \param [in] frameId Optional frame ID * \param [out] status Present status */ void presentImage( const Rc& presenter, - VkPresentModeKHR presentMode, uint64_t frameId, DxvkSubmitStatus* status); diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index a97c60200..e5016ebcc 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -52,26 +52,51 @@ namespace dxvk { } - PresenterInfo Presenter::info() const { - return m_info; + VkResult Presenter::checkSwapChainStatus() { + std::lock_guard lock(m_surfaceMutex); + + if (!m_swapchain) + return recreateSwapChain(); + + return VK_SUCCESS; } VkResult Presenter::acquireNextImage(PresenterSync& sync, Rc& image) { - PresenterSync& semaphores = m_semaphores.at(m_frameIndex); - sync = semaphores; + std::lock_guard lock(m_surfaceMutex); + + // Ensure that the swap chain gets recreated if it is dirty + updateSwapChain(); // Don't acquire more than one image at a time - if (m_acquireStatus == VK_NOT_READY) { - waitForSwapchainFence(semaphores); + if (m_acquireStatus == VK_NOT_READY && m_swapchain) { + PresenterSync sync = m_semaphores.at(m_frameIndex); + + waitForSwapchainFence(sync); m_acquireStatus = m_vkd->vkAcquireNextImageKHR(m_vkd->device(), m_swapchain, std::numeric_limits::max(), sync.acquire, VK_NULL_HANDLE, &m_imageIndex); } - if (m_acquireStatus != VK_SUCCESS && m_acquireStatus != VK_SUBOPTIMAL_KHR) - return m_acquireStatus; + // If the swap chain is out of date, recreate it and retry. It + // is possible that we do not get a new swap chain here, e.g. + // because the window is minimized. + if (m_acquireStatus != VK_SUCCESS || !m_swapchain) { + VkResult vr = recreateSwapChain(); + + if (vr != VK_SUCCESS) + return vr; + + PresenterSync sync = m_semaphores.at(m_frameIndex); + + m_acquireStatus = m_vkd->vkAcquireNextImageKHR(m_vkd->device(), + m_swapchain, std::numeric_limits::max(), + sync.acquire, VK_NULL_HANDLE, &m_imageIndex); + + if (m_acquireStatus < 0) + return m_acquireStatus; + } // Update HDR metadata after a successful acquire. We know // that there won't be a present in flight at this point. @@ -84,14 +109,19 @@ namespace dxvk { } } + // Set dynamic present mode for the next frame if possible + if (!m_dynamicModes.empty()) + m_presentMode = m_dynamicModes.at(m_preferredSyncInterval ? 1u : 0u); + + // Return relevant Vulkan objects for the acquired image + sync = m_semaphores.at(m_frameIndex); image = m_images.at(m_imageIndex); + return m_acquireStatus; } - VkResult Presenter::presentImage( - VkPresentModeKHR mode, - uint64_t frameId) { + VkResult Presenter::presentImage(uint64_t frameId) { PresenterSync& currSync = m_semaphores.at(m_frameIndex); VkPresentIdKHR presentId = { VK_STRUCTURE_TYPE_PRESENT_ID_KHR }; @@ -104,7 +134,7 @@ namespace dxvk { VkSwapchainPresentModeInfoEXT modeInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT }; modeInfo.swapchainCount = 1; - modeInfo.pPresentModes = &mode; + modeInfo.pPresentModes = &m_presentMode; VkPresentInfoKHR info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; info.waitSemaphoreCount = 1; @@ -146,10 +176,7 @@ namespace dxvk { } - void Presenter::signalFrame( - VkResult result, - VkPresentModeKHR mode, - uint64_t frameId) { + void Presenter::signalFrame(VkResult result, uint64_t frameId) { if (m_signal == nullptr || !frameId) return; @@ -158,7 +185,7 @@ namespace dxvk { PresenterFrame frame = { }; frame.result = result; - frame.mode = mode; + frame.mode = m_presentMode; frame.frameId = frameId; m_frameQueue.push(frame); @@ -172,14 +199,102 @@ namespace dxvk { } - VkResult Presenter::recreateSwapChain(const PresenterDesc& desc) { + bool Presenter::supportsColorSpace(VkColorSpaceKHR colorspace) { + std::lock_guard lock(m_surfaceMutex); + + if (!m_surface) { + VkResult vr = createSurface(); + + if (vr != VK_SUCCESS) + return false; + } + + std::vector surfaceFormats; + getSupportedFormats(surfaceFormats); + + for (const auto& surfaceFormat : surfaceFormats) { + if (surfaceFormat.colorSpace == colorspace) + return true; + } + + return false; + } + + + void Presenter::invalidateSurface() { + std::lock_guard lock(m_surfaceMutex); + + m_dirtySurface = true; + } + + + void Presenter::setSyncInterval(uint32_t syncInterval) { + std::lock_guard lock(m_surfaceMutex); + + // Normalize sync interval for present modes. We currently + // cannot support anything other than 1 natively anyway. + syncInterval = std::min(syncInterval, 1u); + + if (m_preferredSyncInterval != syncInterval) { + m_preferredSyncInterval = syncInterval; + + if (m_dynamicModes.empty()) + m_dirtySwapchain = true; + } + } + + + void Presenter::setFrameRateLimit(double frameRate, uint32_t maxLatency) { + m_fpsLimiter.setTargetFrameRate(frameRate, maxLatency); + } + + + void Presenter::setSurfaceFormat(VkSurfaceFormatKHR format) { + std::lock_guard lock(m_surfaceMutex); + + if (m_preferredFormat.format != format.format || m_preferredFormat.colorSpace != format.colorSpace) { + m_preferredFormat = format; + m_dirtySwapchain = true; + } + } + + + void Presenter::setSurfaceExtent(VkExtent2D extent) { + std::lock_guard lock(m_surfaceMutex); + + if (m_preferredExtent != extent) { + m_preferredExtent = extent; + m_dirtySwapchain = true; + } + } + + + void Presenter::setHdrMetadata(VkHdrMetadataEXT hdrMetadata) { + std::lock_guard lock(m_surfaceMutex); + + if (m_hdrMetadata->sType != VK_STRUCTURE_TYPE_HDR_METADATA_EXT) { + m_hdrMetadata = std::nullopt; + return; + } + + if (hdrMetadata.pNext) + Logger::warn("HDR metadata extensions not currently supported."); + + m_hdrMetadata = hdrMetadata; + m_hdrMetadata->pNext = nullptr; + + m_hdrMetadataDirty = true; + } + + + VkResult Presenter::recreateSwapChain() { VkResult vr; if (m_swapchain) destroySwapchain(); if (m_surface) { - vr = createSwapChain(desc); + vr = createSwapChain(); if (vr == VK_ERROR_SURFACE_LOST_KHR) destroySurface(); @@ -189,14 +304,27 @@ namespace dxvk { vr = createSurface(); if (vr == VK_SUCCESS) - vr = createSwapChain(desc); + vr = createSwapChain(); } return vr; } - VkResult Presenter::createSwapChain(const PresenterDesc& desc) { + void Presenter::updateSwapChain() { + if (m_dirtySurface || m_dirtySwapchain) { + destroySwapchain(); + m_dirtySwapchain = false; + } + + if (m_dirtySurface) { + destroySurface(); + m_dirtySurface = false; + } + } + + + VkResult Presenter::createSwapChain() { VkSurfaceFullScreenExclusiveInfoEXT fullScreenExclusiveInfo = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; fullScreenExclusiveInfo.fullScreenExclusive = m_fullscreenMode; @@ -229,25 +357,22 @@ namespace dxvk { // Select image extent based on current surface capabilities, and return // immediately if we cannot create an actual swap chain. - m_info.imageExtent = pickImageExtent(caps.surfaceCapabilities, desc.imageExtent); + VkExtent2D imageExtent = pickImageExtent(caps.surfaceCapabilities, m_preferredExtent); - if (!m_info.imageExtent.width || !m_info.imageExtent.height) { - m_info.imageCount = 0; - m_info.format = { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; - return VK_SUCCESS; - } + if (!imageExtent.width || !imageExtent.height) + return VK_NOT_READY; // Select format based on swap chain properties if ((status = getSupportedFormats(formats))) return status; - m_info.format = pickFormat(formats.size(), formats.data(), desc.numFormats, desc.formats); + VkSurfaceFormatKHR surfaceFormat = pickSurfaceFormat(formats.size(), formats.data(), m_preferredFormat); // Select a present mode for the current sync interval if ((status = getSupportedPresentModes(modes))) return status; - m_info.presentMode = pickPresentMode(modes.size(), modes.data(), m_info.syncInterval); + m_presentMode = pickPresentMode(modes.size(), modes.data(), m_preferredSyncInterval); // Check whether we can change present modes dynamically. This may // influence the image count as well as further swap chain creation. @@ -268,7 +393,7 @@ namespace dxvk { VkSurfacePresentModeEXT presentModeInfo = { VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT }; presentModeInfo.pNext = const_cast(std::exchange(surfaceInfo.pNext, &presentModeInfo)); - presentModeInfo.presentMode = m_info.presentMode; + presentModeInfo.presentMode = m_presentMode; caps.pNext = &compatibleModeInfo; @@ -324,8 +449,6 @@ namespace dxvk { } // Compute swap chain image count based on available info - m_info.imageCount = pickImageCount(minImageCount, maxImageCount, desc.imageCount); - VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; fullScreenInfo.fullScreenExclusive = m_fullscreenMode; @@ -335,17 +458,17 @@ namespace dxvk { VkSwapchainCreateInfoKHR swapInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; swapInfo.surface = m_surface; - swapInfo.minImageCount = m_info.imageCount; - swapInfo.imageFormat = m_info.format.format; - swapInfo.imageColorSpace = m_info.format.colorSpace; - swapInfo.imageExtent = m_info.imageExtent; + swapInfo.minImageCount = pickImageCount(minImageCount, maxImageCount); + swapInfo.imageFormat = surfaceFormat.format; + swapInfo.imageColorSpace = surfaceFormat.colorSpace; + swapInfo.imageExtent = imageExtent; swapInfo.imageArrayLayers = 1; swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; swapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; swapInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - swapInfo.presentMode = m_info.presentMode; + swapInfo.presentMode = m_presentMode; swapInfo.clipped = VK_TRUE; if (m_device->features().extFullScreenExclusive) @@ -356,26 +479,23 @@ namespace dxvk { Logger::info(str::format( "Presenter: Actual swap chain properties:" - "\n Format: ", m_info.format.format, - "\n Color space: ", m_info.format.colorSpace, - "\n Present mode: ", m_info.presentMode, " (dynamic: ", (dynamicModes.empty() ? "no)" : "yes)"), - "\n Buffer size: ", m_info.imageExtent.width, "x", m_info.imageExtent.height, - "\n Image count: ", m_info.imageCount)); + "\n Format: ", swapInfo.imageFormat, + "\n Color space: ", swapInfo.imageColorSpace, + "\n Present mode: ", swapInfo.presentMode, " (dynamic: ", (dynamicModes.empty() ? "no)" : "yes)"), + "\n Buffer size: ", swapInfo.imageExtent.width, "x", swapInfo.imageExtent.height, + "\n Image count: ", swapInfo.minImageCount)); if ((status = m_vkd->vkCreateSwapchainKHR(m_vkd->device(), &swapInfo, nullptr, &m_swapchain))) return status; - // Acquire images and create views + // Import actual swap chain images std::vector images; if ((status = getSwapImages(images))) return status; - // Update actual image count - m_info.imageCount = images.size(); - - for (uint32_t i = 0; i < m_info.imageCount; i++) { + for (uint32_t i = 0; i < images.size(); i++) { std::string debugName = str::format("Vulkan swap image ", i); DxvkImageCreateInfo imageInfo = { }; @@ -397,7 +517,7 @@ namespace dxvk { // Create one set of semaphores per swap image, as well as a fence // that we use to ensure that semaphores are safe to access. - uint32_t semaphoreCount = m_info.imageCount; + uint32_t semaphoreCount = images.size(); if (!m_device->features().extSwapchainMaintenance1.swapchainMaintenance1) { // Without support for present fences, just give up and allocate extra @@ -437,61 +557,6 @@ namespace dxvk { } - bool Presenter::supportsColorSpace(VkColorSpaceKHR colorspace) { - if (!m_surface) - return false; - - std::vector surfaceFormats; - getSupportedFormats(surfaceFormats); - - for (const auto& surfaceFormat : surfaceFormats) { - if (surfaceFormat.colorSpace == colorspace) - return true; - } - - return false; - } - - - VkResult Presenter::setSyncInterval(uint32_t syncInterval) { - // Normalize sync interval for present modes. We currently - // cannot support anything other than 1 natively anyway. - syncInterval = std::min(syncInterval, 1u); - - if (syncInterval == m_info.syncInterval) - return VK_SUCCESS; - - m_info.syncInterval = syncInterval; - - if (syncInterval >= m_dynamicModes.size()) - return VK_ERROR_OUT_OF_DATE_KHR; - - m_info.presentMode = m_dynamicModes[syncInterval]; - return VK_SUCCESS; - } - - - void Presenter::setFrameRateLimit(double frameRate, uint32_t maxLatency) { - m_fpsLimiter.setTargetFrameRate(frameRate, maxLatency); - } - - - void Presenter::setHdrMetadata(const VkHdrMetadataEXT& hdrMetadata) { - if (m_hdrMetadata->sType != VK_STRUCTURE_TYPE_HDR_METADATA_EXT) { - m_hdrMetadata = std::nullopt; - return; - } - - if (hdrMetadata.pNext) - Logger::warn("HDR metadata extensions not currently supported."); - - m_hdrMetadata = hdrMetadata; - m_hdrMetadata->pNext = nullptr; - - m_hdrMetadataDirty = true; - } - - VkResult Presenter::getSupportedFormats(std::vector& formats) const { uint32_t numFormats = 0; @@ -586,42 +651,148 @@ namespace dxvk { } - VkSurfaceFormatKHR Presenter::pickFormat( + VkSurfaceFormatKHR Presenter::pickSurfaceFormat( uint32_t numSupported, const VkSurfaceFormatKHR* pSupported, - uint32_t numDesired, - const VkSurfaceFormatKHR* pDesired) { - if (numDesired > 0) { - // If the implementation allows us to freely choose - // the format, we'll just use the preferred format. - if (numSupported == 1 && pSupported[0].format == VK_FORMAT_UNDEFINED) - return pDesired[0]; - - // If the preferred format is explicitly listed in - // the array of supported surface formats, use it - for (uint32_t i = 0; i < numDesired; i++) { - for (uint32_t j = 0; j < numSupported; j++) { - if (pSupported[j].format == pDesired[i].format - && pSupported[j].colorSpace == pDesired[i].colorSpace) - return pSupported[j]; - } - } + const VkSurfaceFormatKHR& desired) { + VkSurfaceFormatKHR result = { }; + result.colorSpace = pickColorSpace(numSupported, pSupported, desired.colorSpace); + result.format = pickFormat(numSupported, pSupported, result.colorSpace, desired.format); + return result; + } - // If that didn't work, we'll fall back to a format - // which has similar properties to the preferred one - DxvkFormatFlags prefFlags = lookupFormatInfo(pDesired[0].format)->flags; - for (uint32_t j = 0; j < numSupported; j++) { - auto currFlags = lookupFormatInfo(pSupported[j].format)->flags; + VkColorSpaceKHR Presenter::pickColorSpace( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + VkColorSpaceKHR desired) { + static const std::array, 2> fallbacks = {{ + { VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, VK_COLOR_SPACE_HDR10_ST2084_EXT }, - if ((currFlags & DxvkFormatFlag::ColorSpaceSrgb) - == (prefFlags & DxvkFormatFlag::ColorSpaceSrgb)) - return pSupported[j]; + { VK_COLOR_SPACE_HDR10_ST2084_EXT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT }, + }}; + + for (uint32_t i = 0; i < numSupported; i++) { + if (pSupported[i].colorSpace == desired) + return desired; + } + + for (const auto& f : fallbacks) { + if (f.first != desired) + continue; + + for (uint32_t i = 0; i < numSupported; i++) { + if (pSupported[i].colorSpace == f.second) + return f.second; } } - - // Otherwise, fall back to the first supported format - return pSupported[0]; + + Logger::warn(str::format("No fallback color space found for ", desired, ", using ", pSupported[0].colorSpace)); + return pSupported[0].colorSpace; + } + + + VkFormat Presenter::pickFormat( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + VkColorSpaceKHR colorSpace, + VkFormat format) { + static const std::array srgbFormatList = { + VK_FORMAT_B5G5R5A1_UNORM_PACK16, + VK_FORMAT_R5G5B5A1_UNORM_PACK16, + VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR, + VK_FORMAT_R5G6B5_UNORM_PACK16, + VK_FORMAT_B5G6R5_UNORM_PACK16, + VK_FORMAT_R8G8B8A8_SRGB, + VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_A8B8G8R8_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, + }; + + static const std::array hdr10FormatList = { + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, + }; + + static const std::array scRGBFormatList = { + VK_FORMAT_R16G16B16A16_SFLOAT, + }; + + static const std::array compatLists = {{ + { VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + srgbFormatList.size(), srgbFormatList.data() }, + { VK_COLOR_SPACE_HDR10_ST2084_EXT, + hdr10FormatList.size(), hdr10FormatList.data() }, + { VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, + scRGBFormatList.size(), scRGBFormatList.data() }, + }}; + + // If the desired format is supported natively, use it + VkFormat fallback = VK_FORMAT_UNDEFINED; + + for (uint32_t i = 0; i < numSupported; i++) { + if (pSupported[i].colorSpace == colorSpace) { + if (pSupported[i].format == format) + return pSupported[i].format; + + if (!fallback) + fallback = pSupported[i].format; + } + } + + // Otherwise, find a supported format for the color space + const PresenterFormatList* compatList = nullptr; + + for (const auto& l : compatLists) { + if (l.colorSpace == colorSpace) + compatList = &l; + } + + if (!compatList) + return fallback; + + // If the desired format is linear, ignore sRGB formats. We can do + // this because sRGB and linear formats must be supported in pairs. + // sRGB to linear fallbacks need to be allowed though in order to + // be able to select a format with a higher bit depth than requested. + bool desiredIsSrgb = lookupFormatInfo(format)->flags.test(DxvkFormatFlag::ColorSpaceSrgb); + bool desiredFound = false; + + for (uint32_t i = 0; i < compatList->formatCount; i++) { + bool formatIsSrgb = lookupFormatInfo(compatList->formats[i])->flags.test(DxvkFormatFlag::ColorSpaceSrgb); + + if (!desiredIsSrgb && formatIsSrgb) + continue; + + bool isSupported = false; + + if (compatList->formats[i] == format) + desiredFound = true; + + for (uint32_t j = 0; j < numSupported && !isSupported; j++) + isSupported = pSupported[j].colorSpace == colorSpace && pSupported[j].format == compatList->formats[i]; + + if (isSupported) { + fallback = compatList->formats[i]; + + if (desiredFound) + break; + } + } + + if (!desiredFound) + Logger::warn(str::format("Desired format ", format, " not in compatibility list for ", colorSpace, ", using ", fallback)); + + return fallback; } @@ -671,16 +842,12 @@ namespace dxvk { uint32_t Presenter::pickImageCount( uint32_t minImageCount, - uint32_t maxImageCount, - uint32_t desired) { + uint32_t maxImageCount) { uint32_t count = minImageCount + 1; - - if (count < desired) - count = desired; - + if (count > maxImageCount && maxImageCount != 0) count = maxImageCount; - + return count; } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 2da3a78ca..3222634ed 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -33,25 +33,7 @@ namespace dxvk { * an input during swap chain creation. */ struct PresenterDesc { - VkExtent2D imageExtent = { }; - uint32_t imageCount = 0u; - uint32_t numFormats = 0u; - VkSurfaceFormatKHR formats[4] = { }; - bool deferSurfaceCreation = false; - }; - - /** - * \brief Presenter properties - * - * Contains the actual properties - * of the underlying swap chain. - */ - struct PresenterInfo { - VkSurfaceFormatKHR format; - VkPresentModeKHR presentMode; - VkExtent2D imageExtent; - uint32_t imageCount; - uint32_t syncInterval; + bool deferSurfaceCreation = false; }; /** @@ -78,6 +60,15 @@ namespace dxvk { VkResult result = VK_NOT_READY; }; + /** + * \brief Format compatibility list + */ + struct PresenterFormatList { + VkColorSpaceKHR colorSpace; + size_t formatCount; + const VkFormat* formats; + }; + /** * \brief Vulkan presenter * @@ -98,21 +89,29 @@ namespace dxvk { ~Presenter(); /** - * \brief Actual presenter info - * \returns Swap chain properties + * \brief Tests swap chain status + * + * If no swapchain currently exists, this method may create + * one so that presentation can subsequently be performed. + * \returns One of the following return codes: + * - \c VK_SUCCESS if a valid swapchain exists + * - \c VK_NOT_READY if no swap chain can be created + * - Any other error code if swap chain creation failed. */ - PresenterInfo info() const; + VkResult checkSwapChainStatus(); /** * \brief Acquires next image - * - * Potentially blocks the calling thread. - * If this returns an error, the swap chain - * must be recreated and a new image must - * be acquired before proceeding. + * + * Tries to acquire an image from the underlying Vulkan + * swapchain. May recreate the swapchain if any surface + * properties or user-specified parameters have changed. + * Potentially blocks the calling thread, and must not be + * called if any present call is currently in flight. * \param [out] sync Synchronization semaphores * \param [out] image Acquired swap chain image - * \returns Status of the operation + * \returns Status of the operation. May return + * \c VK_NOT_READY if no swap chain exists. */ VkResult acquireNextImage( PresenterSync& sync, @@ -121,17 +120,12 @@ namespace dxvk { /** * \brief Presents current image * - * Presents the current image. If this returns - * an error, the swap chain must be recreated, - * but do not present before acquiring an image. - * \param [in] mode Present mode + * Presents the last successfuly acquired image. * \param [in] frameId Frame number. * Must increase monotonically. * \returns Status of the operation */ - VkResult presentImage( - VkPresentModeKHR mode, - uint64_t frameId); + VkResult presentImage(uint64_t frameId); /** * \brief Signals a given frame @@ -141,34 +135,17 @@ namespace dxvk { * called before GPU work prior to the present submission has * completed in order to maintain consistency. * \param [in] result Presentation result - * \param [in] mode Present mode * \param [in] frameId Frame number */ - void signalFrame( - VkResult result, - VkPresentModeKHR mode, - uint64_t frameId); - - /** - * \brief Changes presenter properties - * - * Recreates the swap chain immediately. Note that - * no swap chain resources must be in use by the - * GPU at the time this is called. - * \param [in] desc Swap chain description - * \param [in] surface New Vulkan surface - */ - VkResult recreateSwapChain( - const PresenterDesc& desc); + void signalFrame(VkResult result, uint64_t frameId); /** * \brief Changes sync interval * - * If this returns an error, the swap chain must - * be recreated. + * Changes the Vulkan present mode as necessary. * \param [in] syncInterval New sync interval */ - VkResult setSyncInterval(uint32_t syncInterval); + void setSyncInterval(uint32_t syncInterval); /** * \brief Changes maximum frame rate @@ -179,16 +156,32 @@ namespace dxvk { void setFrameRateLimit(double frameRate, uint32_t maxLatency); /** - * \brief Checks whether a Vulkan swap chain exists + * \brief Sets preferred color space and format * - * On Windows, there are situations where we cannot create - * a swap chain as the surface size can reach zero, and no - * presentation can be performed. - * \returns \c true if the presenter has a swap chain. + * If the Vulkan surface does not natively support the given + * parameter combo, it will try to select a format and color + * space with similar properties. + * \param [in] format Preferred surface format */ - bool hasSwapChain() const { - return m_swapchain; - } + void setSurfaceFormat(VkSurfaceFormatKHR format); + + /** + * \brief Sets preferred surface extent + * + * The preferred surface extent is only relevant if the Vulkan + * surface itself does not have a fixed size. Should match the + * back buffer size of the application. + * \param [in] extent Preferred surface extent + */ + void setSurfaceExtent(VkExtent2D extent); + + /** + * \brief Sets HDR metadata + * + * Updated HDR metadata will be applied on the next \c acquire. + * \param [in] hdrMetadata HDR Metadata + */ + void setHdrMetadata(VkHdrMetadataEXT hdrMetadata); /** * \brief Checks support for a Vulkan color space @@ -199,12 +192,13 @@ namespace dxvk { bool supportsColorSpace(VkColorSpaceKHR colorspace); /** - * \brief Sets HDR metadata + * \brief Invalidates Vulkan surface * - * Updated HDR metadata will be applied on the next \c acquire. - * \param [in] hdrMetadata HDR Metadata + * This will cause the Vulkan surface to be destroyed and + * recreated on the next \c acquire call. This is a hacky + * workaround to support windows with multiple surfaces. */ - void setHdrMetadata(const VkHdrMetadataEXT& hdrMetadata); + void invalidateSurface(); private: @@ -214,7 +208,7 @@ namespace dxvk { Rc m_vki; Rc m_vkd; - PresenterInfo m_info = { }; + dxvk::mutex m_surfaceMutex; PresenterSurfaceProc m_surfaceProc; VkSurfaceKHR m_surface = VK_NULL_HANDLE; @@ -227,6 +221,15 @@ namespace dxvk { std::vector m_dynamicModes; + VkExtent2D m_preferredExtent = { }; + VkSurfaceFormatKHR m_preferredFormat = { }; + uint32_t m_preferredSyncInterval = 1u; + + bool m_dirtySwapchain = false; + bool m_dirtySurface = false; + + VkPresentModeKHR m_presentMode = VK_PRESENT_MODE_FIFO_KHR; + uint32_t m_imageIndex = 0; uint32_t m_frameIndex = 0; @@ -246,8 +249,11 @@ namespace dxvk { alignas(CACHE_LINE_SIZE) FpsLimiter m_fpsLimiter; - VkResult createSwapChain( - const PresenterDesc& desc); + void updateSwapChain(); + + VkResult recreateSwapChain(); + + VkResult createSwapChain(); VkResult getSupportedFormats( std::vector& formats) const; @@ -258,11 +264,21 @@ namespace dxvk { VkResult getSwapImages( std::vector& images); - VkSurfaceFormatKHR pickFormat( + VkSurfaceFormatKHR pickSurfaceFormat( uint32_t numSupported, const VkSurfaceFormatKHR* pSupported, - uint32_t numDesired, - const VkSurfaceFormatKHR* pDesired); + const VkSurfaceFormatKHR& desired); + + VkColorSpaceKHR pickColorSpace( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + VkColorSpaceKHR desired); + + VkFormat pickFormat( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + VkColorSpaceKHR colorSpace, + VkFormat format); VkPresentModeKHR pickPresentMode( uint32_t numSupported, @@ -275,8 +291,7 @@ namespace dxvk { uint32_t pickImageCount( uint32_t minImageCount, - uint32_t maxImageCount, - uint32_t desired); + uint32_t maxImageCount); VkResult createSurface(); diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index 533bd30fd..d9d8b6c30 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -145,7 +145,7 @@ namespace dxvk { entry.result = entry.submit.cmdList->submit(m_semaphores, m_timelines); entry.timelines = m_timelines; } else if (entry.present.presenter != nullptr) { - entry.result = entry.present.presenter->presentImage(entry.present.presentMode, entry.present.frameId); + entry.result = entry.present.presenter->presentImage(entry.present.frameId); } if (m_callback) @@ -235,8 +235,7 @@ namespace dxvk { // Signal the frame and then immediately destroy the reference. // This is necessary since the front-end may want to explicitly // destroy the presenter object. - entry.present.presenter->signalFrame(entry.result, - entry.present.presentMode, entry.present.frameId); + entry.present.presenter->signalFrame(entry.result, entry.present.frameId); entry.present.presenter = nullptr; } diff --git a/src/dxvk/dxvk_queue.h b/src/dxvk/dxvk_queue.h index b7b9114b9..8ef6a7148 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -43,7 +43,6 @@ namespace dxvk { */ struct DxvkPresentInfo { Rc presenter; - VkPresentModeKHR presentMode; uint64_t frameId; };