From 4412f2f5ffd7a536cd66036df60fcfe52b3a57ed Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 31 Dec 2017 00:23:34 +0100 Subject: [PATCH] [dxgi] Reimplemented parts of DxgiSwapchain with win32 APIs This solves issues in applications that create more than one swap chain for a given window, and helps applications that don't like SDL interference in general. Fullscreen support is currently missing entirely. --- src/dxgi/dxgi_output.cpp | 29 ++--- src/dxgi/dxgi_presenter.cpp | 76 ++++++++----- src/dxgi/dxgi_presenter.h | 44 +++++--- src/dxgi/dxgi_swapchain.cpp | 212 +++++++++++++----------------------- src/dxgi/dxgi_swapchain.h | 18 ++- 5 files changed, 171 insertions(+), 208 deletions(-) diff --git a/src/dxgi/dxgi_output.cpp b/src/dxgi/dxgi_output.cpp index 545ad657..b94481eb 100644 --- a/src/dxgi/dxgi_output.cpp +++ b/src/dxgi/dxgi_output.cpp @@ -137,30 +137,23 @@ namespace dxvk { } hasMode = testMode.w == currMode.w - && testMode.h == currMode.h - && testMode.refresh_rate == currMode.refresh_rate; + && testMode.h == currMode.h + && testMode.refresh_rate == currMode.refresh_rate; } // Convert the SDL display mode to a DXGI display mode info // structure and filter out any unwanted modes based on the // supplied flags. if (!hasMode) { - bool isNativeMode = (currMode.w == desktopMode.w) - && (currMode.h == desktopMode.h); - - if (isNativeMode || (Flags & DXGI_ENUM_MODES_SCALING)) { - DXGI_MODE_DESC mode; - mode.Width = currMode.w; - mode.Height = currMode.h; - mode.RefreshRate.Numerator = currMode.refresh_rate; - mode.RefreshRate.Denominator = 1; - mode.Format = EnumFormat; - mode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; - mode.Scaling = isNativeMode - ? DXGI_MODE_SCALING_CENTERED - : DXGI_MODE_SCALING_STRETCHED; - modes.push_back(mode); - } + DXGI_MODE_DESC mode; + mode.Width = currMode.w; + mode.Height = currMode.h; + mode.RefreshRate.Numerator = currMode.refresh_rate; + mode.RefreshRate.Denominator = 1; + mode.Format = EnumFormat; + mode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + mode.Scaling = DXGI_MODE_SCALING_CENTERED; + modes.push_back(mode); } } diff --git a/src/dxgi/dxgi_presenter.cpp b/src/dxgi/dxgi_presenter.cpp index c209197e..2a96e63d 100644 --- a/src/dxgi/dxgi_presenter.cpp +++ b/src/dxgi/dxgi_presenter.cpp @@ -5,11 +5,8 @@ namespace dxvk { DxgiPresenter::DxgiPresenter( - const Rc& device, - HWND window, - uint32_t bufferWidth, - uint32_t bufferHeight, - DXGI_FORMAT bufferFormat) + const Rc& device, + HWND window) : m_device (device), m_context (device->createContext()) { @@ -19,15 +16,11 @@ namespace dxvk { m_surface = m_device->adapter()->createSurface(instance, window); - // Create swap chain for the surface - DxvkSwapchainProperties swapchainProperties; - swapchainProperties.preferredSurfaceFormat = this->pickFormat(bufferFormat); - swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; - swapchainProperties.preferredBufferSize.width = bufferWidth; - swapchainProperties.preferredBufferSize.height = bufferHeight; - - m_swapchain = m_device->createSwapchain( - m_surface, swapchainProperties); + // Reset options for the swap chain itself. We will + // create a swap chain object before presentation. + m_options.preferredSurfaceFormat = { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + m_options.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; + m_options.preferredBufferSize = { 0u, 0u }; // Sampler for presentation DxvkSamplerCreateInfo samplerInfo; @@ -46,8 +39,11 @@ namespace dxvk { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; samplerInfo.usePixelCoord = VK_FALSE; + m_samplerFitting = m_device->createSampler(samplerInfo); - m_sampler = m_device->createSampler(samplerInfo); + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + m_samplerScaling = m_device->createSampler(samplerInfo); // Set up context state. The shader bindings and the // constant state objects will never be modified. @@ -153,6 +149,10 @@ namespace dxvk { void DxgiPresenter::presentImage() { + const bool fitSize = + m_backBuffer->info().extent.width == m_options.preferredBufferSize.width + && m_backBuffer->info().extent.height == m_options.preferredBufferSize.height; + m_context->beginRecording( m_device->createCommandList()); @@ -191,8 +191,10 @@ namespace dxvk { m_context->setViewports(1, &viewport, &scissor); - m_context->bindResourceSampler(BindingIds::Sampler, m_sampler); - m_context->bindResourceImage (BindingIds::Texture, m_backBufferView); + m_context->bindResourceSampler(BindingIds::Sampler, + fitSize ? m_samplerFitting : m_samplerScaling); + + m_context->bindResourceImage(BindingIds::Texture, m_backBufferView); m_context->draw(4, 1, 0, 0); m_device->submitCommandList( @@ -256,21 +258,34 @@ namespace dxvk { } - void DxgiPresenter::recreateSwapchain( - uint32_t bufferWidth, - uint32_t bufferHeight, - DXGI_FORMAT bufferFormat) { - DxvkSwapchainProperties swapchainProperties; - swapchainProperties.preferredSurfaceFormat = this->pickFormat(bufferFormat); - swapchainProperties.preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; - swapchainProperties.preferredBufferSize.width = bufferWidth; - swapchainProperties.preferredBufferSize.height = bufferHeight; + void DxgiPresenter::recreateSwapchain(const DxvkSwapchainProperties& options) { + const bool doRecreate = + options.preferredSurfaceFormat.format != m_options.preferredSurfaceFormat.format + || options.preferredSurfaceFormat.colorSpace != m_options.preferredSurfaceFormat.colorSpace + || options.preferredPresentMode != m_options.preferredPresentMode + || options.preferredBufferSize.width != m_options.preferredBufferSize.width + || options.preferredBufferSize.height != m_options.preferredBufferSize.height; - m_swapchain->changeProperties(swapchainProperties); + if (doRecreate) { + Logger::info(str::format( + "DxgiPresenter: Recreating swap chain: ", + "\n Format: ", options.preferredSurfaceFormat.format, + "\n Present mode: ", options.preferredPresentMode, + "\n Buffer size: ", options.preferredBufferSize.width, "x", options.preferredBufferSize.height)); + + m_options = options; + + if (m_swapchain == nullptr) { + m_swapchain = m_device->createSwapchain( + m_surface, options); + } else { + m_swapchain->changeProperties(options); + } + } } - VkSurfaceFormatKHR DxgiPresenter::pickFormat(DXGI_FORMAT fmt) const { + VkSurfaceFormatKHR DxgiPresenter::pickSurfaceFormat(DXGI_FORMAT fmt) const { std::vector formats; switch (fmt) { @@ -295,6 +310,11 @@ namespace dxvk { } + VkPresentModeKHR DxgiPresenter::pickPresentMode(VkPresentModeKHR preferred) const { + return m_surface->pickPresentMode(1, &preferred); + } + + Rc DxgiPresenter::createVertexShader() { SpirvModule module; diff --git a/src/dxgi/dxgi_presenter.h b/src/dxgi/dxgi_presenter.h index 4f58ecee..a20604d6 100644 --- a/src/dxgi/dxgi_presenter.h +++ b/src/dxgi/dxgi_presenter.h @@ -22,11 +22,8 @@ namespace dxvk { public: DxgiPresenter( - const Rc& device, - HWND window, - uint32_t bufferWidth, - uint32_t bufferHeight, - DXGI_FORMAT bufferFormat); + const Rc& device, + HWND window); ~DxgiPresenter(); @@ -50,16 +47,36 @@ namespace dxvk { * \param [in] image Back buffer image */ void updateBackBuffer( - const Rc& image); + const Rc& image); /** - * \brief Renders image to the screen - * \param [in] view Source image view + * \brief Recreats Vulkan swap chain + * + * Only actually recreates the swap chain object + * if any of the properties have changed. If no + * properties have changed, this is a no-op. + * \param [in] options New swap chain options */ void recreateSwapchain( - uint32_t bufferWidth, - uint32_t bufferHeight, - DXGI_FORMAT bufferFormat); + const DxvkSwapchainProperties& options); + + /** + * \brief Picks a surface format based on a DXGI format + * + * This will return a supported format that, if possible, + * has properties similar to those of the DXGI format. + * \param [in] fmt The DXGI format + * \returns The Vulkan format + */ + VkSurfaceFormatKHR pickSurfaceFormat(DXGI_FORMAT fmt) const; + + /** + * \brief Picks a supported present mode + * + * \param [in] preferred Preferred present mode + * \returns An actually supported present mode + */ + VkPresentModeKHR pickPresentMode(VkPresentModeKHR preferred) const; private: @@ -74,13 +91,14 @@ namespace dxvk { Rc m_surface; Rc m_swapchain; - Rc m_sampler; + Rc m_samplerFitting; + Rc m_samplerScaling; Rc m_backBuffer; Rc m_backBufferResolve; Rc m_backBufferView; - VkSurfaceFormatKHR pickFormat(DXGI_FORMAT fmt) const; + DxvkSwapchainProperties m_options; Rc createVertexShader(); Rc createFragmentShader(); diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 272f4c3f..7ea824ec 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -36,36 +36,24 @@ namespace dxvk { m_stats.SyncQPCTime.QuadPart = 0; m_stats.SyncGPUTime.QuadPart = 0; - // Create SDL window handle - m_window = SDL_CreateWindowFrom(m_desc.OutputWindow); - - if (m_window == nullptr) { - throw DxvkError(str::format( - "DxgiSwapChain::DxgiSwapChain: Failed to create window:\n", - SDL_GetError())); - } - // Adjust initial back buffer size. If zero, these // shall be set to the current window size. - VkExtent2D windowSize = this->getWindowSize(); + const VkExtent2D windowSize = GetWindowSize(); if (m_desc.BufferDesc.Width == 0) m_desc.BufferDesc.Width = windowSize.width; if (m_desc.BufferDesc.Height == 0) m_desc.BufferDesc.Height = windowSize.height; // Set initial window mode and fullscreen state - if (FAILED(this->SetFullscreenState(!pDesc->Windowed, nullptr))) - throw DxvkError("DxgiSwapChain::DxgiSwapChain: Failed to set initial fullscreen state"); +// if (FAILED(this->SetFullscreenState(!pDesc->Windowed, nullptr))) +// throw DxvkError("DxgiSwapChain: Failed to set initial fullscreen state"); - this->createPresenter(); - this->createBackBuffer(); - TRACE(this); + if (FAILED(CreatePresenter()) || FAILED(CreateBackBuffer())) + throw DxvkError("DxgiSwapChain: Failed to create presenter or back buffer"); } DxgiSwapChain::~DxgiSwapChain() { - TRACE(this); - // We do not release the SDL window handle here since - // that would destroy the underlying window as well. + } @@ -106,18 +94,8 @@ namespace dxvk { if (ppOutput != nullptr) return DXGI_ERROR_INVALID_CALL; - // We can use the display index returned by SDL to query the - // containing output, since DxgiAdapter::EnumOutputs uses the - // same output IDs. - std::lock_guard lock(m_mutex); - int32_t displayId = SDL_GetWindowDisplayIndex(m_window); - - if (displayId < 0) { - Logger::err("DxgiSwapChain::GetContainingOutput: Failed to query window display index"); - return DXGI_ERROR_DRIVER_INTERNAL_ERROR; - } - - return m_adapter->EnumOutputs(displayId, ppOutput); + Logger::err("DxgiSwapChain::GetContainingOutput: Not implemented"); + return E_NOTIMPL; } @@ -175,10 +153,23 @@ namespace dxvk { // Submit pending rendering commands // before recording the present code. m_presentDevice->FlushRenderingCommands(); - - // TODO implement sync interval - // TODO implement flags - m_presenter->presentImage(); + + // Update swap chain properties. This will not only set + // up vertical synchronization properly, but also apply + // changes that were made to the window size even if the + // Vulkan swap chain itself remains valid. + DxvkSwapchainProperties swapchainProps; + swapchainProps.preferredSurfaceFormat + = m_presenter->pickSurfaceFormat(m_desc.BufferDesc.Format); + swapchainProps.preferredPresentMode = SyncInterval == 0 + ? m_presenter->pickPresentMode(VK_PRESENT_MODE_IMMEDIATE_KHR) + : m_presenter->pickPresentMode(VK_PRESENT_MODE_MAILBOX_KHR); + swapchainProps.preferredBufferSize = GetWindowSize(); + + m_presenter->recreateSwapchain(swapchainProps); + + for (uint32_t i = 0; i < SyncInterval || i < 1; i++) + m_presenter->presentImage(); return S_OK; } catch (const DxvkError& err) { Logger::err(err.message()); @@ -195,9 +186,9 @@ namespace dxvk { UINT SwapChainFlags) { std::lock_guard lock(m_mutex); - VkExtent2D windowSize = this->getWindowSize(); + const VkExtent2D windowSize = GetWindowSize(); - m_desc.BufferDesc.Width = Width != 0 ? Width : windowSize.width; + m_desc.BufferDesc.Width = Width != 0 ? Width : windowSize.width; m_desc.BufferDesc.Height = Height != 0 ? Height : windowSize.height; m_desc.Flags = SwapChainFlags; @@ -208,17 +199,7 @@ namespace dxvk { if (NewFormat != DXGI_FORMAT_UNKNOWN) m_desc.BufferDesc.Format = NewFormat; - try { - m_presenter->recreateSwapchain( - m_desc.BufferDesc.Width, - m_desc.BufferDesc.Height, - m_desc.BufferDesc.Format); - this->createBackBuffer(); - return S_OK; - } catch (const DxvkError& err) { - Logger::err(err.message()); - return DXGI_ERROR_DRIVER_INTERNAL_ERROR; - } + return CreateBackBuffer(); } @@ -228,39 +209,21 @@ namespace dxvk { std::lock_guard lock(m_mutex); - // Applies to windowed mode - SDL_SetWindowSize(m_window, - pNewTargetParameters->Width, - pNewTargetParameters->Height); + // TODO support fullscreen mode + RECT newRect = { 0, 0, 0, 0 }; + RECT oldRect = { 0, 0, 0, 0 }; - // Applies to fullscreen mode - SDL_DisplayMode displayMode; - displayMode.format = SDL_PIXELFORMAT_RGBA32; - displayMode.w = pNewTargetParameters->Width; - displayMode.h = pNewTargetParameters->Height; - displayMode.refresh_rate = pNewTargetParameters->RefreshRate.Numerator - / pNewTargetParameters->RefreshRate.Denominator; - displayMode.driverdata = nullptr; + ::GetWindowRect(m_desc.OutputWindow, &oldRect); + ::SetRect(&newRect, 0, 0, pNewTargetParameters->Width, pNewTargetParameters->Height); + ::AdjustWindowRectEx(&newRect, + ::GetWindowLongW(m_desc.OutputWindow, GWL_STYLE), FALSE, + ::GetWindowLongW(m_desc.OutputWindow, GWL_EXSTYLE)); + ::SetRect(&newRect, 0, 0, newRect.right - newRect.left, newRect.bottom - newRect.top); + ::OffsetRect(&newRect, oldRect.left, oldRect.top); + ::MoveWindow(m_desc.OutputWindow, newRect.left, newRect.top, + newRect.right - newRect.left, newRect.bottom - newRect.top, TRUE); - // TODO test mode change flag - - if (SDL_SetWindowDisplayMode(m_window, &displayMode)) { - Logger::err(str::format( - "DxgiSwapChain::ResizeTarget: Failed to set display mode:\n", - SDL_GetError())); - return DXGI_ERROR_DRIVER_INTERNAL_ERROR; - } - - try { - m_presenter->recreateSwapchain( - m_desc.BufferDesc.Width, - m_desc.BufferDesc.Height, - m_desc.BufferDesc.Format); - return S_OK; - } catch (const DxvkError& err) { - Logger::err(err.message()); - return DXGI_ERROR_DRIVER_INTERNAL_ERROR; - } + return S_OK; } @@ -269,81 +232,54 @@ namespace dxvk { IDXGIOutput* pTarget) { std::lock_guard lock(m_mutex); - // Unconditionally reset the swap chain to windowed mode first. - // This required if the application wants to move the window to - // a different display while remaining in fullscreen mode. - if (SDL_SetWindowFullscreen(m_window, 0)) { - Logger::err(str::format( - "DxgiSwapChain::SetFullscreenState: Failed to set windowed mode:\n", - SDL_GetError())); - return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; - } - - m_desc.Windowed = !Fullscreen; - - if (Fullscreen) { - // If a target output is specified, we need to move the - // window to that output first while in windowed mode. - if (pTarget != nullptr) { - DXGI_OUTPUT_DESC outputDesc; - - if (FAILED(pTarget->GetDesc(&outputDesc))) { - Logger::err("DxgiSwapChain::SetFullscreenState: Failed to query output properties"); - return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; - } - - SDL_SetWindowPosition(m_window, - outputDesc.DesktopCoordinates.left, - outputDesc.DesktopCoordinates.top); - } - - // Now that the window is located at the target location, - // SDL should fullscreen it on the requested display. We - // only use borderless fullscreen for now, may be changed. - if (SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP)) { - Logger::err(str::format( - "DxgiSwapChain::SetFullscreenState: Failed to set fullscreen mode:\n", - SDL_GetError())); - return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; - } - } - - return S_OK; + Logger::err("DxgiSwapChain::SetFullscreenState: Not implemented"); + return E_NOTIMPL; } - void DxgiSwapChain::createPresenter() { - m_presenter = new DxgiPresenter( - m_device->GetDXVKDevice(), - m_desc.OutputWindow, - m_desc.BufferDesc.Width, - m_desc.BufferDesc.Height, - m_desc.BufferDesc.Format); + HRESULT DxgiSwapChain::CreatePresenter() { + try { + m_presenter = new DxgiPresenter( + m_device->GetDXVKDevice(), + m_desc.OutputWindow); + return S_OK; + } catch (const DxvkError& e) { + Logger::err(e.message()); + return E_FAIL; + } } - void DxgiSwapChain::createBackBuffer() { + HRESULT DxgiSwapChain::CreateBackBuffer() { VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; - if (FAILED(GetSampleCount(m_desc.SampleDesc.Count, &sampleCount))) - throw DxvkError("DxgiSwapChain: Invalid sample count"); + if (FAILED(GetSampleCount(m_desc.SampleDesc.Count, &sampleCount))) { + Logger::err("DxgiSwapChain: Invalid sample count"); + return E_INVALIDARG; + } - if (FAILED(m_presentDevice->CreateSwapChainBackBuffer(&m_desc, &m_backBuffer))) - throw DxvkError("DxgiSwapChain: Failed to create back buffer"); + if (FAILED(m_presentDevice->CreateSwapChainBackBuffer(&m_desc, &m_backBuffer))) { + Logger::err("DxgiSwapChain: Failed to create back buffer"); + return E_FAIL; + } - m_presenter->updateBackBuffer(m_backBuffer->GetDXVKImage()); + try { + m_presenter->updateBackBuffer(m_backBuffer->GetDXVKImage()); + return S_OK; + } catch (const DxvkError& e) { + Logger::err(e.message()); + return E_FAIL; + } } - VkExtent2D DxgiSwapChain::getWindowSize() const { - int winWidth = 0; - int winHeight = 0; - - SDL_GetWindowSize(m_window, &winWidth, &winHeight); + VkExtent2D DxgiSwapChain::GetWindowSize() const { + RECT windowRect; + ::GetClientRect(m_desc.OutputWindow, &windowRect); VkExtent2D result; - result.width = winWidth; - result.height = winHeight; + result.width = windowRect.right; + result.height = windowRect.bottom; return result; } diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index d47395b7..61904da2 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -88,20 +88,16 @@ namespace dxvk { Com m_device; Com m_presentDevice; - DXGI_SWAP_CHAIN_DESC m_desc; - DXGI_FRAME_STATISTICS m_stats; + DXGI_SWAP_CHAIN_DESC m_desc; + DXGI_FRAME_STATISTICS m_stats; - SDL_Window* m_window = nullptr; + Rc m_presenter; + Com m_backBuffer; - Rc m_presenter; - Com m_backBuffer; + HRESULT CreatePresenter(); + HRESULT CreateBackBuffer(); - void createPresenter(); - void createBackBuffer(); - - void createContext(); - - VkExtent2D getWindowSize() const; + VkExtent2D GetWindowSize() const; HRESULT GetSampleCount( UINT Count,