From 94a12ec56ba2398c164a2dd61b77d67957774010 Mon Sep 17 00:00:00 2001 From: Jon-Luke Biddle Date: Thu, 5 Nov 2026 04:59:19 -0600 Subject: [PATCH 1/4] bugfix(dx8): Use the display format for the fullscreen back buffer when a device reset does not last In fullscreen the back buffer is promoted to D3DFMT_A8R8G8B8 while the display mode is D3DFMT_X8R8G8B8. Because the formats differ, every IDirect3DDevice8::Reset() sets the display mode again. On systems where a display mode change loses the device (seen on Windows 11 with multiple monitors and GPUs), the device is lost again by its own Reset() before a single frame is presented, so the game resets forever while the screen keeps switching modes. When the previous fullscreen Reset() did not last until a frame was presented, Reset_Device now switches the back buffer to the display format, which lets Reset() restore the device without a display mode change. Destination alpha effects such as the soft water edge are then unavailable for the rest of the session. --- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 53 ++++++++++++++++++- .../Source/WWVegas/WW3D2/dx8wrapper.h | 3 ++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 5597ef73649..099d261359e 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -120,6 +120,8 @@ int DX8Wrapper::TextureBitDepth = DEFAULT_TEXTURE_BIT_DEPTH; bool DX8Wrapper::IsWindowed = false; D3DFORMAT DX8Wrapper::DisplayFormat = D3DFMT_UNKNOWN; D3DMULTISAMPLE_TYPE DX8Wrapper::MultiSampleAntiAliasing = DEFAULT_MSAA; +bool DX8Wrapper::FullscreenBackBufferUsesDisplayFormat = false; +unsigned DX8Wrapper::FullscreenResetsWithoutPresent = 0; // shader system additions KJM v DWORD DX8Wrapper::Vertex_Shader = 0; @@ -631,9 +633,24 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) HRESULT hr=_Get_D3D_Device8()->TestCooperativeLevel(); if (hr != D3DERR_DEVICELOST ) - { DX8CALL_HRES(Reset(&_PresentParameters),hr) + { + // TheSuperHackers @bugfix coconutbird 05/11/2026 A fullscreen back buffer with destination alpha does not + // match the display format, so every Reset() sets the display mode again. On systems where a display mode + // change loses the device, the device is lost again by its own Reset() before it presents a single frame, + // and the game resets forever while the screen keeps switching modes. When the previous Reset() did not + // last until a frame was presented, use the display format for the back buffer, so that Reset() can + // restore the device without changing the display mode. + if (!IsWindowed && FullscreenResetsWithoutPresent != 0 && _PresentParameters.BackBufferFormat != DisplayFormat) + { + Use_Display_Format_For_Fullscreen_Back_Buffer(); + } + + DX8CALL_HRES(Reset(&_PresentParameters),hr) if (hr != D3D_OK) return false; //reset failed. + + if (!IsWindowed) + ++FullscreenResetsWithoutPresent; } else return false; //device is lost and can't be reset. @@ -1485,7 +1502,9 @@ bool DX8Wrapper::Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,D3DFORMAT *set_backbuffer=*set_colorbuffer = format_table[format_index]; } - if (bitdepth==32 && *set_colorbuffer == D3DFMT_X8R8G8B8 && D3DInterface->CheckDeviceType(0,D3DDEVTYPE_HAL,*set_colorbuffer,D3DFMT_A8R8G8B8, TRUE) == D3D_OK) + // TheSuperHackers @bugfix coconutbird 05/11/2026 Keep the display format once Reset_Device found that a back buffer + // with destination alpha cannot be kept in fullscreen. + if (bitdepth==32 && *set_colorbuffer == D3DFMT_X8R8G8B8 && !FullscreenBackBufferUsesDisplayFormat && D3DInterface->CheckDeviceType(0,D3DDEVTYPE_HAL,*set_colorbuffer,D3DFMT_A8R8G8B8, TRUE) == D3D_OK) { //promote 32-bit modes to include destination alpha when supported *set_backbuffer = D3DFMT_A8R8G8B8; @@ -1624,6 +1643,34 @@ bool DX8Wrapper::Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORM return true; } +void DX8Wrapper::Use_Display_Format_For_Fullscreen_Back_Buffer() +{ + WWDEBUG_SAY(("DX8Wrapper::Reset_Device did not last until a frame was presented. Using the display format for the fullscreen back buffer.")); + FullscreenBackBufferUsesDisplayFormat = true; + _PresentParameters.BackBufferFormat = DisplayFormat; + + D3DFORMAT zmode; + if (Find_Z_Mode(DisplayFormat, DisplayFormat, &zmode)) + { + _PresentParameters.AutoDepthStencilFormat = zmode; + } + + if (_PresentParameters.MultiSampleType != D3DMULTISAMPLE_NONE) + { + const HRESULT hrBack = D3DInterface->CheckDeviceMultiSampleType(CurRenderDevice, D3DDEVTYPE_HAL, + _PresentParameters.BackBufferFormat, FALSE, _PresentParameters.MultiSampleType); + const HRESULT hrDepth = D3DInterface->CheckDeviceMultiSampleType(CurRenderDevice, D3DDEVTYPE_HAL, + _PresentParameters.AutoDepthStencilFormat, FALSE, _PresentParameters.MultiSampleType); + + if (FAILED(hrBack) || FAILED(hrDepth)) + { + WWDEBUG_SAY(("Requested MSAA Mode Not Supported")); + MultiSampleAntiAliasing = D3DMULTISAMPLE_NONE; + _PresentParameters.MultiSampleType = D3DMULTISAMPLE_NONE; + } + } +} + void DX8Wrapper::Reset_Statistics() { @@ -1692,6 +1739,7 @@ void DX8Wrapper::End_Scene(bool flip_frames) #endif IsDeviceLost=false; FrameCount++; + FullscreenResetsWithoutPresent = 0; } else { IsDeviceLost=true; @@ -1760,6 +1808,7 @@ void DX8Wrapper::Flip_To_Primary() if (SUCCEEDED(hr)) { IsDeviceLost=false; FrameCount++; + FullscreenResetsWithoutPresent = 0; WWDEBUG_SAY(("Flip to primary succeeded %ld", FrameCount)); } else { diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 2e929450484..35cf2f7caad 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -624,6 +624,7 @@ class DX8Wrapper static bool Find_Color_Mode(D3DFORMAT colorbuffer, int resx, int resy, UINT *mode); static bool Find_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT *zmode); static bool Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT zmode); + static void Use_Display_Format_For_Fullscreen_Back_Buffer(); static void Compute_Caps(WW3DFormat display_format); /* @@ -651,6 +652,8 @@ class DX8Wrapper static bool IsWindowed; static D3DFORMAT DisplayFormat; static D3DMULTISAMPLE_TYPE MultiSampleAntiAliasing; + static bool FullscreenBackBufferUsesDisplayFormat; + static unsigned FullscreenResetsWithoutPresent; // shader system updates KJM v From e4703825b4ecaf41987a6aed62087f6fbc7d4a76 Mon Sep 17 00:00:00 2001 From: Jon-Luke Biddle Date: Thu, 5 Nov 2026 06:49:25 -0600 Subject: [PATCH 2/4] bugfix(dx8): Render the scene into an A8R8G8B8 target when the fullscreen back buffer uses the display format --- .../GameClient/W3DProfilerFrameCapture.cpp | 4 +- .../Source/W3DDevice/GameClient/W3DSmudge.cpp | 6 +- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 227 ++++++++++++++++++ .../Source/WWVegas/WW3D2/dx8wrapper.h | 15 +- 4 files changed, 247 insertions(+), 5 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp index 4c2f9b40a39..57c4c68a893 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp @@ -103,8 +103,8 @@ void W3DProfilerFrameCapture::Capture(UnsignedInt displayWidth, UnsignedInt disp return; } - // get the backbuffer - SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + // get the surface the scene is rendered into + SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Scene_Surface(); if (!backBuffer) { REF_PTR_RELEASE(surfaceClass); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp index 37fc8609328..1d3c2318427 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp @@ -80,7 +80,8 @@ void W3DSmudgeManager::ReAcquireResources() { ReleaseResources(); - SurfaceClass *surface=DX8Wrapper::_Get_DX8_Back_Buffer(); + // TheSuperHackers @bugfix coconutbird 05/11/2026 Match the surface the scene is rendered into, which is not always the back buffer. + SurfaceClass *surface=DX8Wrapper::_Get_DX8_Scene_Surface(); SurfaceClass::SurfaceDescription surface_desc; surface->Get_Description(surface_desc); @@ -323,7 +324,8 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) return; } - SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + // TheSuperHackers @bugfix coconutbird 05/11/2026 Copy from the surface the scene is rendered into, which is not always the back buffer. + SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Scene_Surface(); if (!backBuffer) return; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 099d261359e..3f37730a9e8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -122,6 +122,12 @@ D3DFORMAT DX8Wrapper::DisplayFormat = D3DFMT_UNKNOWN; D3DMULTISAMPLE_TYPE DX8Wrapper::MultiSampleAntiAliasing = DEFAULT_MSAA; bool DX8Wrapper::FullscreenBackBufferUsesDisplayFormat = false; unsigned DX8Wrapper::FullscreenResetsWithoutPresent = 0; +bool DX8Wrapper::UseSceneAlphaTarget = false; +D3DMULTISAMPLE_TYPE DX8Wrapper::SceneAlphaMultiSampleType = D3DMULTISAMPLE_NONE; +IDirect3DTexture8 * DX8Wrapper::SceneAlphaTexture = nullptr; +IDirect3DSurface8 * DX8Wrapper::SceneAlphaTextureSurface = nullptr; +IDirect3DSurface8 * DX8Wrapper::SceneAlphaMultisampleSurface = nullptr; +IDirect3DSurface8 * DX8Wrapper::SceneAlphaDepthSurface = nullptr; // shader system additions KJM v DWORD DX8Wrapper::Vertex_Shader = 0; @@ -624,6 +630,7 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) DynamicIBAccessClass::_Deinit(); DX8TextureManagerClass::Release_Textures(); SHD_SHUTDOWN_SHADERS; + Release_Scene_Alpha_Target(); // Reset frame count to reflect the flipping chain being reset by Reset() FrameCount = 0; @@ -645,12 +652,16 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) Use_Display_Format_For_Fullscreen_Back_Buffer(); } + Prepare_Scene_Alpha_Target(); + DX8CALL_HRES(Reset(&_PresentParameters),hr) if (hr != D3D_OK) return false; //reset failed. if (!IsWindowed) ++FullscreenResetsWithoutPresent; + + Create_Scene_Alpha_Target(); } else return false; //device is lost and can't be reset. @@ -675,6 +686,7 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) void DX8Wrapper::Release_Device() { if (D3DDevice) { + Release_Scene_Alpha_Target(); for (int a=0;aCheckDeviceFormat(CurRenderDevice, D3DDEVTYPE_HAL, DisplayFormat, + D3DUSAGE_RENDERTARGET, D3DRTYPE_TEXTURE, D3DFMT_A8R8G8B8)) + || FAILED(D3DInterface->CheckDepthStencilMatch(CurRenderDevice, D3DDEVTYPE_HAL, DisplayFormat, + D3DFMT_A8R8G8B8, _PresentParameters.AutoDepthStencilFormat)) + || (multiSampleType != D3DMULTISAMPLE_NONE + && (FAILED(D3DInterface->CheckDeviceMultiSampleType(CurRenderDevice, D3DDEVTYPE_HAL, + D3DFMT_A8R8G8B8, FALSE, multiSampleType)) + || FAILED(D3DInterface->CheckDeviceMultiSampleType(CurRenderDevice, D3DDEVTYPE_HAL, + _PresentParameters.AutoDepthStencilFormat, FALSE, multiSampleType))))) + { + WWDEBUG_SAY(("DX8Wrapper::Prepare_Scene_Alpha_Target: A8R8G8B8 scene target is not supported.")); + return; + } + + UseSceneAlphaTarget = true; + SceneAlphaMultiSampleType = multiSampleType; + _PresentParameters.MultiSampleType = D3DMULTISAMPLE_NONE; +} + +void DX8Wrapper::Create_Scene_Alpha_Target() +{ + WWASSERT(SceneAlphaTexture == nullptr); + + if (!UseSceneAlphaTarget) + return; + + const UINT width = _PresentParameters.BackBufferWidth; + const UINT height = _PresentParameters.BackBufferHeight; + + HRESULT hr = D3DDevice->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &SceneAlphaTexture); + if (SUCCEEDED(hr)) + hr = SceneAlphaTexture->GetSurfaceLevel(0, &SceneAlphaTextureSurface); + if (SUCCEEDED(hr) && SceneAlphaMultiSampleType != D3DMULTISAMPLE_NONE) + { + hr = D3DDevice->CreateRenderTarget(width, height, D3DFMT_A8R8G8B8, SceneAlphaMultiSampleType, FALSE, &SceneAlphaMultisampleSurface); + if (SUCCEEDED(hr)) + hr = D3DDevice->CreateDepthStencilSurface(width, height, _PresentParameters.AutoDepthStencilFormat, SceneAlphaMultiSampleType, &SceneAlphaDepthSurface); + } + + IDirect3DSurface8 *depthBuffer = nullptr; + if (SUCCEEDED(hr)) + hr = D3DDevice->GetDepthStencilSurface(&depthBuffer); + if (SUCCEEDED(hr)) + hr = D3DDevice->SetRenderTarget(Get_Scene_Alpha_Render_Surface(), SceneAlphaDepthSurface ? SceneAlphaDepthSurface : depthBuffer); + if (depthBuffer) + depthBuffer->Release(); + + if (FAILED(hr)) + { + WWDEBUG_SAY(("DX8Wrapper::Create_Scene_Alpha_Target: failed with 0x%08X.", (unsigned)hr)); + Release_Scene_Alpha_Target(); + return; + } + + WWDEBUG_SAY(("DX8Wrapper::Create_Scene_Alpha_Target: rendering the scene into a %ux%u A8R8G8B8 target, multisample type %d.", + width, height, (int)SceneAlphaMultiSampleType)); +} + +void DX8Wrapper::Release_Scene_Alpha_Target() +{ + if (SceneAlphaTexture == nullptr) + return; + + // The device holds references to its render target and depth buffer, so give it the back buffer back first. + // The back buffer cannot use the multisampled depth buffer of the scene target, so in that case no depth buffer + // is set until Reset() restores the automatic one. + IDirect3DSurface8 *backBuffer = nullptr; + IDirect3DSurface8 *depthBuffer = nullptr; + D3DDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backBuffer); + if (SceneAlphaDepthSurface == nullptr) + D3DDevice->GetDepthStencilSurface(&depthBuffer); + if (backBuffer) + { + D3DDevice->SetRenderTarget(backBuffer, depthBuffer); + backBuffer->Release(); + } + if (depthBuffer) + depthBuffer->Release(); + + if (SceneAlphaDepthSurface) + { + SceneAlphaDepthSurface->Release(); + SceneAlphaDepthSurface = nullptr; + } + + if (SceneAlphaMultisampleSurface) + { + SceneAlphaMultisampleSurface->Release(); + SceneAlphaMultisampleSurface = nullptr; + } + if (SceneAlphaTextureSurface) + { + SceneAlphaTextureSurface->Release(); + SceneAlphaTextureSurface = nullptr; + } + SceneAlphaTexture->Release(); + SceneAlphaTexture = nullptr; +} + +void DX8Wrapper::Copy_Scene_Alpha_Target_To_Back_Buffer() +{ + WWPROFILE("DX8Wrapper::Copy_Scene_Alpha_Target_To_Back_Buffer"); + + if (SceneAlphaMultisampleSurface) + { + DX8CALL(CopyRects(SceneAlphaMultisampleSurface, nullptr, 0, SceneAlphaTextureSurface, nullptr)); + } + + IDirect3DSurface8 *backBuffer = nullptr; + DX8CALL(GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backBuffer)); + if (backBuffer == nullptr) + return; + + IDirect3DSurface8 *depthBuffer = nullptr; + DX8CALL(GetDepthStencilSurface(&depthBuffer)); + + // Restore every device state after the copy, so the render state cache stays valid. + DWORD stateBlock = 0; + const bool restoreStates = SUCCEEDED(D3DDevice->CreateStateBlock(D3DSBT_ALL, &stateBlock)); + + // The copy needs no depth buffer, and the back buffer cannot use the multisampled depth buffer of the scene target. + DX8CALL(SetRenderTarget(backBuffer, nullptr)); + DX8CALL(BeginScene()); + + DX8CALL(SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE)); + DX8CALL(SetRenderState(D3DRS_ZWRITEENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_ALPHATESTENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_STENCILENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_FOGENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE)); + DX8CALL(SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID)); + DX8CALL(SetRenderState(D3DRS_DITHERENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_LIGHTING, FALSE)); + DX8CALL(SetRenderState(D3DRS_SPECULARENABLE, FALSE)); + DX8CALL(SetRenderState(D3DRS_WRAP0, 0)); + DX8CALL(SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA)); + + DX8CALL(SetPixelShader(0)); + DX8CALL(SetVertexShader(D3DFVF_XYZRHW | D3DFVF_TEX1)); + DX8CALL(SetTexture(0, SceneAlphaTexture)); + DX8CALL(SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1)); + DX8CALL(SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE)); + DX8CALL(SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1)); + DX8CALL(SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE)); + DX8CALL(SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0)); + DX8CALL(SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE)); + DX8CALL(SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP)); + DX8CALL(SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP)); + DX8CALL(SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_POINT)); + DX8CALL(SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_POINT)); + DX8CALL(SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_NONE)); + DX8CALL(SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE)); + DX8CALL(SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE)); + + // Offset by half a pixel so that each texel maps onto exactly one pixel. + struct QuadVertex { float x, y, z, rhw, u, v; }; + const float right = (float)_PresentParameters.BackBufferWidth - 0.5f; + const float bottom = (float)_PresentParameters.BackBufferHeight - 0.5f; + const QuadVertex quad[4] = + { + { -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f }, + { right, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f }, + { -0.5f, bottom, 0.0f, 1.0f, 0.0f, 1.0f }, + { right, bottom, 0.0f, 1.0f, 1.0f, 1.0f }, + }; + DX8CALL(DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad, sizeof(QuadVertex))); + + DX8CALL(EndScene()); + DX8CALL(SetRenderTarget(Get_Scene_Alpha_Render_Surface(), depthBuffer)); + + if (restoreStates) + { + D3DDevice->ApplyStateBlock(stateBlock); + D3DDevice->DeleteStateBlock(stateBlock); + } + + backBuffer->Release(); + if (depthBuffer) + depthBuffer->Release(); +} + void DX8Wrapper::Reset_Statistics() { @@ -1719,6 +1929,9 @@ void DX8Wrapper::End_Scene(bool flip_frames) DX8_THREAD_ASSERT(); DX8CALL(EndScene()); + if (flip_frames && SceneAlphaTexture) + Copy_Scene_Alpha_Target_To_Back_Buffer(); + DX8WebBrowser::Render(0); if (flip_frames) { @@ -3195,6 +3408,17 @@ SurfaceClass * DX8Wrapper::_Get_DX8_Back_Buffer(unsigned int num) return surf; } +SurfaceClass * DX8Wrapper::_Get_DX8_Scene_Surface() +{ + DX8_THREAD_ASSERT(); + + IDirect3DSurface8 * surface = Get_Scene_Alpha_Render_Surface(); + if (surface == nullptr) + return _Get_DX8_Back_Buffer(); + + return NEW_REF(SurfaceClass,(surface)); +} + TextureClass * DX8Wrapper::Create_Render_Target (int width, int height, WW3DFormat format) @@ -4490,5 +4714,8 @@ const char* DX8Wrapper::Get_DX8_Blend_Op_Name(unsigned value) WW3DFormat DX8Wrapper::getBackBufferFormat() { + if (SceneAlphaTexture) + return WW3D_FORMAT_A8R8G8B8; + return D3DFormat_To_WW3DFormat( _PresentParameters.BackBufferFormat ); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 35cf2f7caad..d82b56fd921 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -423,6 +423,8 @@ class DX8Wrapper static IDirect3DSurface8 * _Create_DX8_Surface(const char *filename); static IDirect3DSurface8 * _Get_DX8_Front_Buffer(); static SurfaceClass * _Get_DX8_Back_Buffer(unsigned int num=0); + /// Returns the surface the scene is rendered into: the scene alpha target when there is one, otherwise the back buffer. + static SurfaceClass * _Get_DX8_Scene_Surface(); static void _Copy_DX8_Rects( IDirect3DSurface8* pSourceSurface, @@ -532,7 +534,7 @@ class DX8Wrapper static IDirect3DDevice8* _Get_D3D_Device8() { return D3DDevice; } static IDirect3D8* _Get_D3D8() { return D3DInterface; } - /// Returns the display format - added by TR for video playback - not part of W3D + /// Returns the format of the surface the scene is rendered into - added by TR for video playback - not part of W3D static WW3DFormat getBackBufferFormat(); static bool Reset_Device(bool reload_assets=true); @@ -625,6 +627,11 @@ class DX8Wrapper static bool Find_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT *zmode); static bool Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT zmode); static void Use_Display_Format_For_Fullscreen_Back_Buffer(); + static void Prepare_Scene_Alpha_Target(); + static void Create_Scene_Alpha_Target(); + static void Release_Scene_Alpha_Target(); + static void Copy_Scene_Alpha_Target_To_Back_Buffer(); + static IDirect3DSurface8 * Get_Scene_Alpha_Render_Surface() { return SceneAlphaMultisampleSurface ? SceneAlphaMultisampleSurface : SceneAlphaTextureSurface; } static void Compute_Caps(WW3DFormat display_format); /* @@ -654,6 +661,12 @@ class DX8Wrapper static D3DMULTISAMPLE_TYPE MultiSampleAntiAliasing; static bool FullscreenBackBufferUsesDisplayFormat; static unsigned FullscreenResetsWithoutPresent; + static bool UseSceneAlphaTarget; + static D3DMULTISAMPLE_TYPE SceneAlphaMultiSampleType; + static IDirect3DTexture8 * SceneAlphaTexture; + static IDirect3DSurface8 * SceneAlphaTextureSurface; + static IDirect3DSurface8 * SceneAlphaMultisampleSurface; + static IDirect3DSurface8 * SceneAlphaDepthSurface; // shader system updates KJM v From f49232121583baa721da3f79dae834b73ea53772 Mon Sep 17 00:00:00 2001 From: Jon-Luke Biddle Date: Thu, 5 Nov 2026 08:01:45 -0600 Subject: [PATCH 3/4] bugfix(dx8): Keep the depth buffer bound when the scene alpha target cannot be created - Release only unbinds the multisampled depth buffer of the scene target when it is the one that is set. - Report the multisampling the device has when the scene target cannot be created. - Create the state block once with the scene target instead of every frame. --- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 34 +++++++++++++------ .../Source/WWVegas/WW3D2/dx8wrapper.h | 1 + 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 3f37730a9e8..ae09aefef52 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -128,6 +128,7 @@ IDirect3DTexture8 * DX8Wrapper::SceneAlphaTexture = nullptr; IDirect3DSurface8 * DX8Wrapper::SceneAlphaTextureSurface = nullptr; IDirect3DSurface8 * DX8Wrapper::SceneAlphaMultisampleSurface = nullptr; IDirect3DSurface8 * DX8Wrapper::SceneAlphaDepthSurface = nullptr; +DWORD DX8Wrapper::SceneAlphaStateBlock = 0; // shader system additions KJM v DWORD DX8Wrapper::Vertex_Shader = 0; @@ -1738,6 +1739,9 @@ void DX8Wrapper::Create_Scene_Alpha_Target() hr = D3DDevice->CreateDepthStencilSurface(width, height, _PresentParameters.AutoDepthStencilFormat, SceneAlphaMultiSampleType, &SceneAlphaDepthSurface); } + if (SUCCEEDED(hr)) + hr = D3DDevice->CreateStateBlock(D3DSBT_ALL, &SceneAlphaStateBlock); + IDirect3DSurface8 *depthBuffer = nullptr; if (SUCCEEDED(hr)) hr = D3DDevice->GetDepthStencilSurface(&depthBuffer); @@ -1750,6 +1754,9 @@ void DX8Wrapper::Create_Scene_Alpha_Target() { WWDEBUG_SAY(("DX8Wrapper::Create_Scene_Alpha_Target: failed with 0x%08X.", (unsigned)hr)); Release_Scene_Alpha_Target(); + UseSceneAlphaTarget = false; + // The back buffer was reset without multisampling for the scene target, so report what the device has now. + MultiSampleAntiAliasing = _PresentParameters.MultiSampleType; return; } @@ -1763,13 +1770,17 @@ void DX8Wrapper::Release_Scene_Alpha_Target() return; // The device holds references to its render target and depth buffer, so give it the back buffer back first. - // The back buffer cannot use the multisampled depth buffer of the scene target, so in that case no depth buffer - // is set until Reset() restores the automatic one. + // The back buffer cannot use the multisampled depth buffer of the scene target, so when that one is set, no + // depth buffer is set until Reset() restores the automatic one. IDirect3DSurface8 *backBuffer = nullptr; IDirect3DSurface8 *depthBuffer = nullptr; D3DDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backBuffer); - if (SceneAlphaDepthSurface == nullptr) - D3DDevice->GetDepthStencilSurface(&depthBuffer); + D3DDevice->GetDepthStencilSurface(&depthBuffer); + if (depthBuffer != nullptr && depthBuffer == SceneAlphaDepthSurface) + { + depthBuffer->Release(); + depthBuffer = nullptr; + } if (backBuffer) { D3DDevice->SetRenderTarget(backBuffer, depthBuffer); @@ -1778,6 +1789,12 @@ void DX8Wrapper::Release_Scene_Alpha_Target() if (depthBuffer) depthBuffer->Release(); + if (SceneAlphaStateBlock) + { + D3DDevice->DeleteStateBlock(SceneAlphaStateBlock); + SceneAlphaStateBlock = 0; + } + if (SceneAlphaDepthSurface) { SceneAlphaDepthSurface->Release(); @@ -1816,8 +1833,7 @@ void DX8Wrapper::Copy_Scene_Alpha_Target_To_Back_Buffer() DX8CALL(GetDepthStencilSurface(&depthBuffer)); // Restore every device state after the copy, so the render state cache stays valid. - DWORD stateBlock = 0; - const bool restoreStates = SUCCEEDED(D3DDevice->CreateStateBlock(D3DSBT_ALL, &stateBlock)); + DX8CALL(CaptureStateBlock(SceneAlphaStateBlock)); // The copy needs no depth buffer, and the back buffer cannot use the multisampled depth buffer of the scene target. DX8CALL(SetRenderTarget(backBuffer, nullptr)); @@ -1870,11 +1886,7 @@ void DX8Wrapper::Copy_Scene_Alpha_Target_To_Back_Buffer() DX8CALL(EndScene()); DX8CALL(SetRenderTarget(Get_Scene_Alpha_Render_Surface(), depthBuffer)); - if (restoreStates) - { - D3DDevice->ApplyStateBlock(stateBlock); - D3DDevice->DeleteStateBlock(stateBlock); - } + DX8CALL(ApplyStateBlock(SceneAlphaStateBlock)); backBuffer->Release(); if (depthBuffer) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index d82b56fd921..7404e4fb44a 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -667,6 +667,7 @@ class DX8Wrapper static IDirect3DSurface8 * SceneAlphaTextureSurface; static IDirect3DSurface8 * SceneAlphaMultisampleSurface; static IDirect3DSurface8 * SceneAlphaDepthSurface; + static DWORD SceneAlphaStateBlock; // shader system updates KJM v From f1df1196a01ee93a1fd99f01682fc1e80a73237b Mon Sep 17 00:00:00 2001 From: Jon-Luke Biddle Date: Fri, 6 Nov 2026 06:59:47 -0600 Subject: [PATCH 4/4] bugfix(dx8): Keep the back buffer format when no depth buffer format matches the display format --- Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index ae09aefef52..86b82f736cf 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -1658,16 +1658,18 @@ bool DX8Wrapper::Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORM void DX8Wrapper::Use_Display_Format_For_Fullscreen_Back_Buffer() { - WWDEBUG_SAY(("DX8Wrapper::Reset_Device did not last until a frame was presented. Using the display format for the fullscreen back buffer.")); - FullscreenBackBufferUsesDisplayFormat = true; - _PresentParameters.BackBufferFormat = DisplayFormat; - D3DFORMAT zmode; - if (Find_Z_Mode(DisplayFormat, DisplayFormat, &zmode)) + if (!Find_Z_Mode(DisplayFormat, DisplayFormat, &zmode)) { - _PresentParameters.AutoDepthStencilFormat = zmode; + WWDEBUG_SAY(("DX8Wrapper::Reset_Device did not last until a frame was presented, but no depth buffer format matches the display format. Keeping the fullscreen back buffer format.")); + return; } + WWDEBUG_SAY(("DX8Wrapper::Reset_Device did not last until a frame was presented. Using the display format for the fullscreen back buffer.")); + FullscreenBackBufferUsesDisplayFormat = true; + _PresentParameters.BackBufferFormat = DisplayFormat; + _PresentParameters.AutoDepthStencilFormat = zmode; + if (_PresentParameters.MultiSampleType != D3DMULTISAMPLE_NONE) { const HRESULT hrBack = D3DInterface->CheckDeviceMultiSampleType(CurRenderDevice, D3DDEVTYPE_HAL,