Skip to content

Re-anchor D3D9 swapchain dimensions across ctor and Reset paths#141

Open
BinqAdams wants to merge 1 commit intoNVIDIAGameWorks:mainfrom
BinqAdams:fix/d3d9-swapchain-reanchor
Open

Re-anchor D3D9 swapchain dimensions across ctor and Reset paths#141
BinqAdams wants to merge 1 commit intoNVIDIAGameWorks:mainfrom
BinqAdams:fix/d3d9-swapchain-reanchor

Conversation

@BinqAdams
Copy link
Copy Markdown
Contributor

Summary

Re-anchor m_originalWidth / m_originalHeight in three swapchain code paths so m_widthScale / m_heightScale settle correctly when CreateDevice and a subsequent Reset use different backbuffer dims.

Motivation

D3D9SwapChainEx::m_originalWidth / m_originalHeight is captured from the first CreateDevice's BackBuffer dims (in the ctor init list at d3d9_swapchain.cpp:327-328) and never updated, while NormalizePresentParameters recomputes m_widthScale = pp->BackBufferWidth / m_originalWidth on every Reset. When the host process creates the device at one resolution and later Resets to another (a common pattern for engines that initialize at a default size, then apply the user-selected resolution from a settings menu), m_widthScale becomes non-1.0 (or Inf when BackBufferWidth=0 triggers D3D9 auto-derive).

That scale multiplies every D3DVIEWPORT9 rect inside D3D9DeviceEx::SetViewport, so:

  • Ratio mismatch (e.g. 640 → 1280 makes m_widthScale = 2.0): the Vulkan viewport overflows the backbuffer attachment — the visible result is the top-left quadrant of the rendered scene with the rest clipped.
  • BackBufferWidth=0 ctor path: m_widthScale = NEW_BB / 0 = Inf, which propagates into SetViewport's per-rect multiply and produces a NaN/Inf VkViewport that fails CreateDevice with D3DERR_NOTAVAILABLE.

The D3D9SwapchainExternal subclass overrides Reset, so a fix limited to D3D9SwapChainEx::Reset doesn't reach the bridge / RemixAPI path.

What Changed

src/d3d9/d3d9_swapchain.cpp: re-anchor m_originalWidth / m_originalHeight in three sites:

  1. D3D9SwapChainEx ctor BB=0 path. After NormalizePresentParameters fills in the auto-derived backbuffer dims, if the init list captured 0 from the caller, re-anchor to the post-Normalize values and reset m_widthScale / m_heightScale to 1.0.
  2. D3D9SwapChainEx::Reset. Re-anchor to the new backbuffer dims before NormalizePresentParameters runs, so m_widthScale settles at 1.0 for the new size.
  3. D3D9SwapchainExternal::Reset (bridge / RemixAPI path). Mirror the same fix; the override would otherwise leave m_originalWidth pinned to the first CreateDevice's value.

The DXVK_RESOLUTION_WIDTH / DXVK_RESOLUTION_HEIGHT upscale feature is preserved by gating each re-anchor on those env vars being empty — when they're set, the engine-supplied dims are intentionally overwritten by the env values to produce an upscale ratio, and re-anchoring would defeat that.

Net diff: src/d3d9/d3d9_swapchain.cpp only, +77.

Testing

Verified behavior on three reproducers:

  • Engine that creates at a hardcoded default resolution then Resets to the user-selected resolution: pre-fix produced the top-left-quadrant clipping symptom in 2D content (menus, video, HUD); post-fix renders correctly across the new resolution.
  • CreateDevice with BackBufferWidth=BackBufferHeight=0 (D3D9 auto-derive from HWND): pre-fix returned D3DERR_NOTAVAILABLE from the first SetViewport due to Inf/NaN; post-fix succeeds with m_widthScale = m_heightScale = 1.0.
  • DXVK_RESOLUTION_WIDTH / DXVK_RESOLUTION_HEIGHT env vars set: re-anchor is skipped, intentional upscale ratio is preserved.

D3D9SwapChainEx::m_originalWidth / m_originalHeight is captured from
the first CreateDevice's BackBuffer dims and never updated, while
NormalizePresentParameters computes m_widthScale = pp->BackBufferWidth
/ m_originalWidth on every Reset. When the host process creates the
device at one resolution and later Resets to another (a common
pattern for engines that initialize at a default size then apply the
user-selected resolution from a settings menu), m_widthScale becomes
non-1.0 (or Inf when BackBufferWidth=0 triggers D3D9 auto-derive).
That scale multiplies every D3DVIEWPORT9 rect inside
D3D9DeviceEx::SetViewport, producing a Vulkan viewport that overflows
the backbuffer attachment - the visible result is the top-left
quadrant of the rendered scene with the rest clipped, or
CreateDevice failing with D3DERR_NOTAVAILABLE for the BB=0 path
(NaN/Inf VkViewport rejection).

Re-anchor m_originalWidth/Height in three sites:

1. D3D9SwapChainEx ctor BB=0 path. After NormalizePresentParameters
   fills in the auto-derived BackBuffer dims, if the init list
   captured 0 from the caller, re-anchor to the post-Normalize values
   and reset m_widthScale/m_heightScale to 1.0.
2. D3D9SwapChainEx::Reset. Re-anchor to the new BackBuffer dims
   before NormalizePresentParameters runs, so m_widthScale settles
   at 1.0 for the new size.
3. D3D9SwapchainExternal::Reset (the bridge / RemixAPI path).
   Mirrors the same fix; the override would otherwise leave
   m_originalWidth pinned to the first CreateDevice's value.

The DXVK_RESOLUTION_WIDTH/HEIGHT upscale feature is preserved by
gating each re-anchor on the env vars being empty - when they're
set, the engine-supplied dims are intentionally overwritten by the
env values to produce an upscale ratio, and re-anchoring would
defeat that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant