From 97347a5846d7209eea82dfe700bd82bad81c3bd9 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 11 Apr 2024 17:23:09 +0200 Subject: [PATCH 1/2] [d3d9] Implement GetFrontBufferData using GDI --- src/d3d9/d3d9_device.cpp | 4 ++ src/d3d9/d3d9_swapchain.cpp | 82 +++++++++++++++++++++++++++++++++++++ src/d3d9/d3d9_swapchain.h | 4 ++ 3 files changed, 90 insertions(+) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index e99ad38374b..10ed13fe7a5 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1093,6 +1093,10 @@ namespace dxvk { if (unlikely(iSwapChain != 0)) return D3DERR_INVALIDCALL; + #ifdef _WIN32 + return D3D9SwapChainEx::GetFrontBufferDataGDI(pDestSurface); + #endif + D3D9DeviceLock lock = LockDevice(); // In windowed mode, GetFrontBufferData takes a screenshot of the entire screen. diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 5772433069d..107a92b59fd 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -241,9 +241,91 @@ namespace dxvk { return S_OK; } + + HRESULT D3D9SwapChainEx::GetFrontBufferDataGDI(IDirect3DSurface9* pDestSurface) { + D3D9Surface* dst = static_cast(pDestSurface); + + if (unlikely(dst == nullptr)) + return D3DERR_INVALIDCALL; + + D3D9CommonTexture* dstTexInfo = dst->GetCommonTexture(); + VkExtent3D dstTexExtent = dstTexInfo->GetExtentMip(dst->GetMipLevel()); + + if (unlikely(dstTexInfo->Desc()->Format != D3D9Format::A8R8G8B8)) { + return D3DERR_INVALIDCALL; + } + + if (unlikely(dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM && dstTexInfo->Desc()->Pool != D3DPOOL_SCRATCH)) + return D3DERR_INVALIDCALL; + + const POINT ptZero = { 0, 0 }; + HMONITOR primaryMonitor = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY); + + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(MONITORINFO); + if (!GetMonitorInfo(primaryMonitor, &monitorInfo)) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to query window size."); + return D3DERR_INVALIDCALL; + } + VkExtent2D monitorExtent = { uint32_t(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left), uint32_t(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top) }; + VkExtent2D blitRegionExtent = { std::min(monitorExtent.width, dstTexExtent.width), std::min(monitorExtent.height, dstTexExtent.height) }; + + HDC srcDC = GetDC(nullptr); + HDC dstDC = CreateCompatibleDC(nullptr); + HBITMAP hbitmap = CreateCompatibleBitmap(srcDC, dstTexExtent.width, dstTexExtent.height); + + if (unlikely(hbitmap == nullptr)) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to create bitmap."); + return D3DERR_INVALIDCALL; + } + + HBITMAP oldBitmap = static_cast(SelectObject(dstDC, hbitmap)); + if (unlikely(oldBitmap == nullptr)) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to select bitmap for DC."); + return D3DERR_INVALIDCALL; + } + + // We always want the primary monitor which conveniently always sits at 0,0. + if (unlikely(!BitBlt(dstDC, 0, 0, blitRegionExtent.width, blitRegionExtent.height, srcDC, 0, 0, SRCCOPY))) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to create blit window."); + return D3DERR_INVALIDCALL; + } + + D3DLOCKED_RECT lockedRect = {}; + if (unlikely(pDestSurface->LockRect(&lockedRect, nullptr, D3DLOCK_DISCARD) != D3D_OK)) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to lock dst surface."); + return D3DERR_INVALIDCALL; + } + + BITMAPINFO info = { + sizeof(BITMAPINFOHEADER), int32_t(dstTexExtent.width), int32_t(-blitRegionExtent.height), 1, 32, BI_RGB, 0, 0, 0, 0, 0, + 0,0,0,0 + }; + + // For uncompressed RGB formats, the minimum stride is always the image width in bytes, rounded up to the nearest DWORD + // Our pitch is always aligned to 4 and GetFrontBufferData only supports A8R8G8B8. So we can avoid another copy to repack the data. + + uint32_t lines = uint32_t(blitRegionExtent.height); + int scanlinesCopied = GetDIBits(dstDC, hbitmap, 0, lines, lockedRect.pBits, &info, DIB_RGB_COLORS); + if (unlikely(scanlinesCopied != int32_t(lines))) { + Logger::err("D3D9SwapChainEx::GetFrontBufferData: Failed to read hbitmap data."); + return D3DERR_INVALIDCALL; + } + + pDestSurface->UnlockRect(); + + SelectObject(dstDC, oldBitmap); + return D3D_OK; + } #endif HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetFrontBufferData(IDirect3DSurface9* pDestSurface) { + #ifdef _WIN32 + if (m_presentParams.Windowed || !HasFrontBuffer()) { + return D3D9SwapChainEx::GetFrontBufferDataGDI(pDestSurface); + } + #endif + D3D9DeviceLock lock = m_parent->LockDevice(); // This function can do absolutely everything! diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index c6cfef27d90..446e56b61c1 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -134,6 +134,10 @@ namespace dxvk { void UpdateWindowCtx(); +#ifdef _WIN32 + static HRESULT GetFrontBufferDataGDI(IDirect3DSurface9* pDestSurface); +#endif + private: enum BindingIds : uint32_t { From e4e89db23c1d4b463f2fae5b5668be8014068708 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 11 Apr 2024 17:56:13 +0200 Subject: [PATCH 2/2] [d3d9] Only use GDI GetFrontBufferData when Gamescope WSI isn't enabled --- src/d3d9/d3d9_device.cpp | 6 +++++- src/d3d9/d3d9_device.h | 6 ++++++ src/d3d9/d3d9_swapchain.cpp | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 10ed13fe7a5..46089035fd8 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -183,6 +183,8 @@ namespace dxvk { m_activeRTsWhichAreTextures = 0; m_alphaSwizzleRTs = 0; m_lastHazardsRT = 0; + + m_gamescopeWSI = dxvk::env::getEnvVar("ENABLE_GAMESCOPE_WSI") == "1"; } @@ -1094,7 +1096,9 @@ namespace dxvk { return D3DERR_INVALIDCALL; #ifdef _WIN32 - return D3D9SwapChainEx::GetFrontBufferDataGDI(pDestSurface); + if (!IsGamescopeWSIEnabled()) { + return D3D9SwapChainEx::GetFrontBufferDataGDI(pDestSurface); + } #endif D3D9DeviceLock lock = LockDevice(); diff --git a/src/d3d9/d3d9_device.h b/src/d3d9/d3d9_device.h index 114a0b9bb6c..79c757edea2 100644 --- a/src/d3d9/d3d9_device.h +++ b/src/d3d9/d3d9_device.h @@ -1273,6 +1273,10 @@ namespace dxvk { m_mostRecentlyUsedSwapchain = m_implicitSwapchain.ptr(); } + bool IsGamescopeWSIEnabled() const { + return m_gamescopeWSI; + } + Com m_parent; D3DDEVTYPE m_deviceType; HWND m_window; @@ -1439,6 +1443,8 @@ namespace dxvk { D3D9SwapChainEx* m_mostRecentlyUsedSwapchain = nullptr; + bool m_gamescopeWSI; + #ifdef D3D9_ALLOW_UNMAPPING lru_list m_mappedTextures; #endif diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 107a92b59fd..6ef582eef0d 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -321,7 +321,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetFrontBufferData(IDirect3DSurface9* pDestSurface) { #ifdef _WIN32 - if (m_presentParams.Windowed || !HasFrontBuffer()) { + if (!m_parent->IsGamescopeWSIEnabled() && (m_presentParams.Windowed || !HasFrontBuffer())) { return D3D9SwapChainEx::GetFrontBufferDataGDI(pDestSurface); } #endif