1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-03-01 19:29:16 +01:00

[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.
This commit is contained in:
Philip Rebohle 2025-01-12 23:26:56 +01:00 committed by Philip Rebohle
parent 06b44c6237
commit 43838d3df8
10 changed files with 514 additions and 452 deletions

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

@ -119,9 +119,7 @@ namespace dxvk {
HANDLE m_frameLatencyEvent = nullptr;
Rc<sync::CallbackFence> 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;

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

@ -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<VkHdrMetadataEXT> 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();

@ -307,14 +307,12 @@ namespace dxvk {
void DxvkDevice::presentImage(
const Rc<Presenter>& 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);

@ -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>& presenter,
VkPresentModeKHR presentMode,
uint64_t frameId,
DxvkSubmitStatus* status);

@ -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<DxvkImage>& 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<uint64_t>::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<uint64_t>::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<VkSurfaceFormatKHR> 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<void*>(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<VkImage> 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<VkSurfaceFormatKHR> 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<VkSurfaceFormatKHR>& 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<std::pair<VkColorSpaceKHR, VkColorSpaceKHR>, 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<VkFormat, 15> 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<VkFormat, 5> 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<VkFormat, 1> scRGBFormatList = {
VK_FORMAT_R16G16B16A16_SFLOAT,
};
static const std::array<PresenterFormatList, 3> 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;
}

@ -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<vk::InstanceFn> m_vki;
Rc<vk::DeviceFn> 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<VkPresentModeKHR> 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<VkSurfaceFormatKHR>& formats) const;
@ -258,11 +264,21 @@ namespace dxvk {
VkResult getSwapImages(
std::vector<VkImage>& 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();

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

@ -43,7 +43,6 @@ namespace dxvk {
*/
struct DxvkPresentInfo {
Rc<Presenter> presenter;
VkPresentModeKHR presentMode;
uint64_t frameId;
};