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

[d3d9] Add option for extra frame buffer to fix GetFrameBufferData

This commit is contained in:
Robin Kertels 2024-04-10 23:24:51 +02:00
parent 3f7460931a
commit 65a91900f5
No known key found for this signature in database
GPG Key ID: 3824904F14D40757
5 changed files with 51 additions and 9 deletions

View File

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

View File

@ -75,6 +75,7 @@ namespace dxvk {
this->clampNegativeLodBias = config.getOption<bool> ("d3d9.clampNegativeLodBias", false);
this->countLosableResources = config.getOption<bool> ("d3d9.countLosableResources", true);
this->reproducibleCommandStream = config.getOption<bool> ("d3d9.reproducibleCommandStream", false);
this->extraFrontbuffer = config.getOption<bool> ("d3d9.extraFrontbuffer", false);
// D3D8 options
this->drefScaling = config.getOption<int32_t> ("d3d8.scaleDref", 0);

View File

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

View File

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

View File

@ -240,17 +240,19 @@ namespace dxvk {
bool IsDeviceReset(D3D9WindowContext* wctx);
const Com<D3D9Surface, false>& 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;