From 65a91900f5f306b89828a515f9ba5a43fd259b6b Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Wed, 10 Apr 2024 23:24:51 +0200 Subject: [PATCH] [d3d9] Add option for extra frame buffer to fix GetFrameBufferData --- dxvk.conf | 9 +++++++- src/d3d9/d3d9_options.cpp | 1 + src/d3d9/d3d9_options.h | 3 +++ src/d3d9/d3d9_swapchain.cpp | 41 +++++++++++++++++++++++++++++++------ src/d3d9/d3d9_swapchain.h | 6 ++++-- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/dxvk.conf b/dxvk.conf index 2bf0d23eb..96e686ac9 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -765,6 +765,14 @@ # d3d9.countLosableResources = True +# Add an extra frame buffer when necessary +# +# Some games create a swapchain with only 1 buffer and still expect GetFrontBufferData() to correctly return back the data of the last present. +# To make that work correctly, we add a second buffer and copy to it every single frame. +# This is unnecessary for all but a single modded game (Silent Hill 2 Enhanced Edition), that's why it's a config option. + +# d3d9.extraFrontbuffer = False + # Dref scaling for DXS0/FVF # # Some early D3D8 games expect Dref (depth texcoord Z) to be on the range of @@ -843,4 +851,3 @@ # - True/False # d3d8.forceLegacyDiscard = False - diff --git a/src/d3d9/d3d9_options.cpp b/src/d3d9/d3d9_options.cpp index f33e94c8a..a16a2fc1a 100644 --- a/src/d3d9/d3d9_options.cpp +++ b/src/d3d9/d3d9_options.cpp @@ -75,6 +75,7 @@ namespace dxvk { this->clampNegativeLodBias = config.getOption ("d3d9.clampNegativeLodBias", false); this->countLosableResources = config.getOption ("d3d9.countLosableResources", true); this->reproducibleCommandStream = config.getOption ("d3d9.reproducibleCommandStream", false); + this->extraFrontbuffer = config.getOption ("d3d9.extraFrontbuffer", false); // D3D8 options this->drefScaling = config.getOption ("d3d8.scaleDref", 0); diff --git a/src/d3d9/d3d9_options.h b/src/d3d9/d3d9_options.h index 8fee48361..5255cb793 100644 --- a/src/d3d9/d3d9_options.h +++ b/src/d3d9/d3d9_options.h @@ -151,6 +151,9 @@ namespace dxvk { /// Enable depth texcoord Z (Dref) scaling (D3D8 quirk) int32_t drefScaling; + + /// Add an extra front buffer to make GetFrontBufferData() work correctly when the swapchain only has a single buffer + bool extraFrontbuffer; }; } diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index e22665400..18dd2ce78 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -167,8 +167,27 @@ namespace dxvk { UpdatePresentRegion(pSourceRect, pDestRect); UpdatePresentParameters(); + if (!SwapWithFrontBuffer() && m_parent->GetOptions()->extraFrontbuffer) { + // We never actually rotate in the front buffer. + // Just blit to it for GetFrontBufferData. + + // When we have multiple buffers, the last buffer always acts as the front buffer. + // (See comment in PresentImage for an explaination why.) + // Games with a buffer count of 1 rely on the contents of the previous frame still + // being there, so we can't just add another buffer to the rotation. + // At the same time, they could call GetFrontBufferData after already rendering to the backbuffer. + // So we have to do a copy of the backbuffer that will be copied to the Vulkan backbuffer + // and keep that around for the next frame. + + const auto& backbuffer = m_backBuffers[0]; + const auto& frontbuffer = GetFrontBuffer(); + if (FAILED(m_parent->StretchRect(backbuffer.ptr(), nullptr, frontbuffer.ptr(), nullptr, D3DTEXF_NONE))) { + Logger::err("Failed to blit to front buffer"); + } + } + #ifdef _WIN32 - const bool useGDIFallback = m_partialCopy && !HasFrontBuffer(); + const bool useGDIFallback = m_partialCopy && !SwapWithFrontBuffer(); if (useGDIFallback) return PresentImageGDI(m_window); #endif @@ -916,7 +935,17 @@ namespace dxvk { // Rotate swap chain buffers so that the back // buffer at index 0 becomes the front buffer. - for (uint32_t i = 1; i < m_backBuffers.size(); i++) + uint32_t rotatingBufferCount = m_backBuffers.size(); + if (!SwapWithFrontBuffer() && m_parent->GetOptions()->extraFrontbuffer) { + // The front buffer only exists for GetFrontBufferData + // and the application cannot obserse buffer swapping in GetBackBuffer() + rotatingBufferCount -= 1; + } + + // Backbuffer 0 is the one that gets copied to the Vulkan swapchain backbuffer. + // => m_backBuffers[1] is the next one that gets presented + // and the currente m_backBuffers[0] ends up at the end of the vector. + for (uint32_t i = 1; i < rotatingBufferCount; i++) m_backBuffers[i]->Swap(m_backBuffers[i - 1].ptr()); m_parent->m_flags.set(D3D9DeviceFlag::DirtyFramebuffer); @@ -986,10 +1015,10 @@ namespace dxvk { // creating a new one to free up resources DestroyBackBuffers(); - int NumFrontBuffer = HasFrontBuffer() ? 1 : 0; - const uint32_t NumBuffers = NumBackBuffers + NumFrontBuffer; + int frontBufferCount = (SwapWithFrontBuffer() || m_parent->GetOptions()->extraFrontbuffer) ? 1 : 0; + const uint32_t bufferCount = NumBackBuffers + frontBufferCount; - m_backBuffers.reserve(NumBuffers); + m_backBuffers.reserve(bufferCount); // Create new back buffer D3D9_COMMON_TEXTURE_DESC desc; @@ -1010,7 +1039,7 @@ namespace dxvk { // we might need to lock for the BlitGDI fallback path desc.IsLockable = true; - for (uint32_t i = 0; i < NumBuffers; i++) { + for (uint32_t i = 0; i < bufferCount; i++) { D3D9Surface* surface; try { surface = new D3D9Surface(m_parent, &desc, m_parent->IsExtended(), this, nullptr); diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index 6ea0d96cb..169bb5978 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -240,17 +240,19 @@ namespace dxvk { bool IsDeviceReset(D3D9WindowContext* wctx); const Com& GetFrontBuffer() const { + // Buffer 0 is the one that gets copied to the Vulkan backbuffer. + // We rotate buffers after presenting, so buffer 0 becomes the last buffer in the vector. return m_backBuffers.back(); } - bool HasFrontBuffer() const { + bool SwapWithFrontBuffer() const { if (m_presentParams.SwapEffect == D3DSWAPEFFECT_COPY) return false; if (m_presentParams.SwapEffect == D3DSWAPEFFECT_COPY_VSYNC) return false; - // Tests show that SWAPEEFFECT_DISCARD + 1 backbuffer in windowed mode behaves identically to SWAPEFFECT_COPY + // Tests show that SWAPEEFFECT_DISCARD with 1 backbuffer in windowed mode behaves identically to SWAPEFFECT_COPY // For SWAPEFFECT_COPY we don't swap buffers but do another blit to the front buffer instead. if (m_presentParams.SwapEffect == D3DSWAPEFFECT_DISCARD && m_presentParams.BackBufferCount == 1 && m_presentParams.Windowed) return false;