Skip to content

Fix D3D12 swapchain hook for Wine/D3DMetal (CrossOver on macOS)#1786

Closed
AlexGRDev wants to merge 8 commits into
praydog:masterfrom
AlexGRDev:d3dmetal-fix
Closed

Fix D3D12 swapchain hook for Wine/D3DMetal (CrossOver on macOS)#1786
AlexGRDev wants to merge 8 commits into
praydog:masterfrom
AlexGRDev:d3dmetal-fix

Conversation

@AlexGRDev

Copy link
Copy Markdown
  • Add adapter enumeration fallback when D3D12CreateDevice(nullptr) fails (D3DMetal may reject the nullptr adapter used for dummy device creation)
  • Hook all three IDXGIFactory2 swapchain creation methods [15/16/24]: CreateSwapChainForHwnd, CreateSwapChainForCoreWindow, CreateSwapChainForComposition so the hook fires regardless of which method D3DMetal uses internally
  • When any factory hook fires, bootstrap s_swapchain_vtable and s_command_queue_offset directly from the real game swapchain instead of requiring a successful dummy swapchain creation
  • Fallback to factory hooks (instead of returning false) when dummy swapchain creation fails, allowing Present to hook on next recreation

Fixes REFramework overlay not appearing in RE4R under CrossOver 26.2 with CX_GRAPHICS_BACKEND=d3dmetal on macOS.

AlexGRDev and others added 8 commits June 28, 2026 17:52
- Add adapter enumeration fallback when D3D12CreateDevice(nullptr) fails
  (D3DMetal may reject the nullptr adapter used for dummy device creation)
- Hook all three IDXGIFactory2 swapchain creation methods [15/16/24]:
  CreateSwapChainForHwnd, CreateSwapChainForCoreWindow, CreateSwapChainForComposition
  so the hook fires regardless of which method D3DMetal uses internally
- When any factory hook fires, bootstrap s_swapchain_vtable and
  s_command_queue_offset directly from the real game swapchain instead
  of requiring a successful dummy swapchain creation
- Fallback to factory hooks (instead of returning false) when dummy
  swapchain creation fails, allowing Present to hook on next recreation

Fixes REFramework overlay not appearing in RE4R under CrossOver 26.2
with CX_GRAPHICS_BACKEND=d3dmetal on macOS.
MSVC requires explicit cast — void* cannot implicitly convert to void**.
All three bootstrap locations (create_swapchain, for_corewindow, for_composition)
now use *(void***)(*swap_chain) consistent with the rest of the codebase.
DXMT hooks D3D12CreateDevice to translate calls to Metal. When REFramework
temporarily restores original bytes and calls the raw function, there is no
native D3D12 on macOS — the call crashes/hangs.

Detect Wine via wine_get_version in ntdll.dll. When Wine is present, keep
DXMT's hook intact and call D3D12CreateDevice through it. DXMT handles
the translation and device creation can succeed normally.
…n fails

DXMT wraps D3D12CommandQueue internally and does not store the passed
COM pointer directly in the swapchain object, causing the memory scan
to find nothing. Detect this case (Wine + offset == 0) and instead
of returning false, keep a ref to the dummy queue we created and use
it in present() — both are backed by the same Metal GPU so rendering
commands reach the same hardware.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dummy queue was created from the dummy device used for vtable
scanning, not from the game's actual D3D12 device. DXMT routes each
D3D12 device to its own Metal command queue, so executing REFramework's
command lists (recorded on the game's device) via the dummy device's
queue causes them to submit to a different Metal command buffer —
appearing behind or not composited with the game frame.

Fix: on the first present() call we have the game's real ID3D12Device4
from GetDevice(). Create a new queue from that device and replace the
dummy queue. All subsequent overlay rendering now submits to the correct
Metal command queue and is composited in the right order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three improvements to fix overlay z-ordering in fullscreen/borderless:

1. CPU fence wait in present(): after m_on_present() executes ImGui commands
   on our separate Metal queue, Signal+WaitForSingleObjectEx forces CPU-side
   synchronization before DXMT's Present blit. Metal hazard tracking should
   then see our completed writes to the backbuffer texture.

2. Vtable-matching scan (3rd pass): if direct pointer scan fails (DXMT stores
   queue via internal pointer not COM pointer), scan for an object in the
   swapchain whose vtable matches the command queue vtable. Extends search
   to 1024 pointers.

3. Fence+event creation when queue is upgraded to game-device queue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… queue

The previous scans all targeted the DUMMY swapchain created in hook().
DXMT may store the command queue differently in real vs dummy swapc hains.

On the first present() call we have the REAL game swapchain (swap_chain param).
Scan it immediately after the queue upgrade using vtable matching — look for
any object whose vtable matches our DXMT ID3D12CommandQueue vtable.

If found, replace s_wine_fallback_queue with the game's actual original queue.
DXMT uses this queue for Present synchronization, so rendering on it guarantees
our ImGui commands are included in the Metal presentation ordering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@AlexGRDev AlexGRDev closed this by deleting the head repository Jul 5, 2026
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