From a32050374c6960a74a35cb55cd75c2270d96b55d Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 29 Apr 2018 23:03:27 +0200 Subject: [PATCH] [dxgi] Implement display mode changes Allows games to change the screen resolution in fullscreen mode. This is currently in a rough shape and some games may not work as expected when selecting fullscreen mode. --- src/dxgi/dxgi_output.cpp | 61 ++++++++++++++- src/dxgi/dxgi_output.h | 7 ++ src/dxgi/dxgi_swapchain.cpp | 115 ++++++++++++++++++++++++---- src/dxgi/dxgi_swapchain.h | 7 ++ tests/d3d11/test_d3d11_triangle.cpp | 2 +- 5 files changed, 170 insertions(+), 22 deletions(-) diff --git a/src/dxgi/dxgi_output.cpp b/src/dxgi/dxgi_output.cpp index 1bdd628c6..571145dfe 100644 --- a/src/dxgi/dxgi_output.cpp +++ b/src/dxgi/dxgi_output.cpp @@ -79,11 +79,10 @@ namespace dxvk { if (targetFormat == DXGI_FORMAT_UNKNOWN) targetFormat = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; - + UINT targetRefreshRate = 0; - if (pModeToMatch->RefreshRate.Denominator != 0 - && pModeToMatch->RefreshRate.Numerator != 0) { + if (pModeToMatch->RefreshRate.Denominator != 0) { targetRefreshRate = pModeToMatch->RefreshRate.Numerator / pModeToMatch->RefreshRate.Denominator; } @@ -129,7 +128,7 @@ namespace dxvk { UINT currDifference = std::abs(int(pModeToMatch->Width - mode.Width)) + std::abs(int(pModeToMatch->Height - mode.Height)); - if (currDifference < minDifference) { + if (currDifference <= minDifference) { minDifference = currDifference; *pClosestMatch = mode; } @@ -326,6 +325,60 @@ namespace dxvk { } + HRESULT DxgiOutput::GetDisplayMode(DXGI_MODE_DESC* pMode, DWORD ModeNum) { + ::MONITORINFOEXW monInfo; + monInfo.cbSize = sizeof(monInfo); + + if (!::GetMonitorInfoW(m_monitor, reinterpret_cast(&monInfo))) { + Logger::err("DXGI: Failed to query monitor info"); + return E_FAIL; + } + + DEVMODEW devMode = { }; + devMode.dmSize = sizeof(devMode); + + if (!::EnumDisplaySettingsW(monInfo.szDevice, ModeNum, &devMode)) + return DXGI_ERROR_NOT_FOUND; + + pMode->Width = devMode.dmPelsWidth; + pMode->Height = devMode.dmPelsHeight; + pMode->RefreshRate = { devMode.dmDisplayFrequency, 1 }; + pMode->Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; // FIXME + pMode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + pMode->Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + return S_OK; + } + + + HRESULT DxgiOutput::SetDisplayMode(const DXGI_MODE_DESC* pMode) { + ::MONITORINFOEXW monInfo; + monInfo.cbSize = sizeof(monInfo); + + if (!::GetMonitorInfoW(m_monitor, reinterpret_cast(&monInfo))) { + Logger::err("DXGI: Failed to query monitor info"); + return E_FAIL; + } + + DEVMODEW devMode = { }; + devMode.dmSize = sizeof(devMode); + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + devMode.dmPelsWidth = pMode->Width; + devMode.dmPelsHeight = pMode->Height; + devMode.dmBitsPerPel = GetFormatBpp(pMode->Format); + + if (pMode->RefreshRate.Numerator != 0) { + devMode.dmFields |= DM_DISPLAYFREQUENCY; + devMode.dmDisplayFrequency = pMode->RefreshRate.Numerator + / pMode->RefreshRate.Denominator; + } + + LONG status = ::ChangeDisplaySettingsExW( + monInfo.szDevice, &devMode, nullptr, CDS_FULLSCREEN, nullptr); + + return status == DISP_CHANGE_SUCCESSFUL ? S_OK : DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;; + } + + uint32_t DxgiOutput::GetFormatBpp(DXGI_FORMAT Format) const { DXGI_VK_FORMAT_INFO formatInfo = m_adapter->LookupFormat(Format, DXGI_VK_FORMAT_MODE_ANY); return imageFormatInfo(formatInfo.Format)->elementSize * 8; diff --git a/src/dxgi/dxgi_output.h b/src/dxgi/dxgi_output.h index a4e5ec544..4b23032a1 100644 --- a/src/dxgi/dxgi_output.h +++ b/src/dxgi/dxgi_output.h @@ -77,6 +77,13 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE WaitForVBlank() final; + HRESULT GetDisplayMode( + DXGI_MODE_DESC* pMode, + DWORD ModeNum); + + HRESULT SetDisplayMode( + const DXGI_MODE_DESC* pMode); + private: Com m_adapter = nullptr; diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 92aec639b..492c92311 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -60,7 +60,8 @@ namespace dxvk { DxgiSwapChain::~DxgiSwapChain() { - + if (IsWindow(m_desc.OutputWindow) && !m_desc.Windowed) + LeaveFullscreenMode(); } @@ -269,19 +270,49 @@ namespace dxvk { if (!IsWindow(m_desc.OutputWindow)) return DXGI_ERROR_INVALID_CALL; - // TODO support fullscreen mode - RECT newRect = { 0, 0, 0, 0 }; - RECT oldRect = { 0, 0, 0, 0 }; + // Update the swap chain description + if (pNewTargetParameters->RefreshRate.Numerator != 0) + m_desc.BufferDesc.RefreshRate = pNewTargetParameters->RefreshRate; + + m_desc.BufferDesc.ScanlineOrdering = pNewTargetParameters->ScanlineOrdering; + m_desc.BufferDesc.Scaling = pNewTargetParameters->Scaling; + + if (m_desc.Windowed) { + // Adjust window position and size + RECT newRect = { 0, 0, 0, 0 }; + RECT oldRect = { 0, 0, 0, 0 }; + + ::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); + } else { + Com output; + + if (FAILED(m_adapter->GetOutputFromMonitor(m_monitor, &output))) { + Logger::err("DXGI: ResizeTarget: Failed to query containing output"); + return E_FAIL; + } + + // If the swap chain allows it, change the display mode + if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) + ChangeDisplayMode(output.ptr(), pNewTargetParameters); + + // Resize and reposition the window to + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + + const RECT newRect = desc.DesktopCoordinates; + + ::MoveWindow(m_desc.OutputWindow, newRect.left, newRect.top, + newRect.right - newRect.left, newRect.bottom - newRect.top, TRUE); + } - ::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); return S_OK; } @@ -295,9 +326,6 @@ namespace dxvk { if (!IsWindow(m_desc.OutputWindow)) return DXGI_ERROR_INVALID_CALL; - if (Fullscreen) - Logger::warn("DxgiSwapChain: Display mode changes not implemented"); - if (m_desc.Windowed && Fullscreen) return this->EnterFullscreenMode(pTarget); else if (!m_desc.Windowed && !Fullscreen) @@ -404,6 +432,22 @@ namespace dxvk { } } + // Find a display mode that matches what we need + ::GetWindowRect(m_desc.OutputWindow, &m_windowState.rect); + + if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) { + auto windowRect = m_windowState.rect; + + DXGI_MODE_DESC displayMode = m_desc.BufferDesc; + displayMode.Width = windowRect.right - windowRect.left; + displayMode.Height = windowRect.bottom - windowRect.top; + + if (FAILED(ChangeDisplayMode(output.ptr(), &displayMode))) { + Logger::err("DXGI: EnterFullscreenMode: Failed to change display mode"); + return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; + } + } + // Update swap chain description m_desc.Windowed = FALSE; @@ -413,7 +457,6 @@ namespace dxvk { m_windowState.style = style; m_windowState.exstyle = exstyle; - ::GetWindowRect(m_desc.OutputWindow, &m_windowState.rect); style |= WS_POPUP | WS_SYSMENU; style &= ~(WS_CAPTION | WS_THICKFRAME); @@ -439,6 +482,13 @@ namespace dxvk { HRESULT DxgiSwapChain::LeaveFullscreenMode() { + Com output; + + if (FAILED(m_adapter->GetOutputFromMonitor(m_monitor, &output)) + || FAILED(RestoreDisplayMode(output.ptr()))) + Logger::warn("DXGI: LeaveFullscreenMode: Failed to restore display mode"); + + // Restore internal state m_desc.Windowed = TRUE; m_monitor = nullptr; @@ -463,6 +513,37 @@ namespace dxvk { } + HRESULT DxgiSwapChain::ChangeDisplayMode( + IDXGIOutput* pOutput, + const DXGI_MODE_DESC* pDisplayMode) { + auto output = static_cast(pOutput); + + DXGI_MODE_DESC selectedMode; + + // Find a close mode that the output supports + HRESULT hr = output->FindClosestMatchingMode( + pDisplayMode, &selectedMode, nullptr); + + if (FAILED(hr)) + return hr; + + return output->SetDisplayMode(&selectedMode); + } + + + HRESULT DxgiSwapChain::RestoreDisplayMode(IDXGIOutput* pOutput) { + auto output = static_cast(pOutput); + DXGI_MODE_DESC mode; + + HRESULT hr = output->GetDisplayMode(&mode, ENUM_REGISTRY_SETTINGS); + + if (FAILED(hr)) + return hr; + + return output->SetDisplayMode(&mode); + } + + HRESULT DxgiSwapChain::GetSampleCount(UINT Count, VkSampleCountFlagBits* pCount) const { switch (Count) { case 1: *pCount = VK_SAMPLE_COUNT_1_BIT; return S_OK; diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index c58003720..98af070d8 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -120,6 +120,13 @@ namespace dxvk { HRESULT LeaveFullscreenMode(); + HRESULT ChangeDisplayMode( + IDXGIOutput* pOutput, + const DXGI_MODE_DESC* pDisplayMode); + + HRESULT RestoreDisplayMode( + IDXGIOutput* pOutput); + HRESULT GetSampleCount( UINT Count, VkSampleCountFlagBits* pCount) const; diff --git a/tests/d3d11/test_d3d11_triangle.cpp b/tests/d3d11/test_d3d11_triangle.cpp index 74465a9e6..71d6e8f1d 100644 --- a/tests/d3d11/test_d3d11_triangle.cpp +++ b/tests/d3d11/test_d3d11_triangle.cpp @@ -91,7 +91,7 @@ public: swapDesc.OutputWindow = window; swapDesc.Windowed = true; swapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - swapDesc.Flags = 0; + swapDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; if (FAILED(m_factory->CreateSwapChain(m_device.ptr(), &swapDesc, &m_swapChain))) throw DxvkError("Failed to create DXGI swap chain");