Phase 2: Present-hook video path (shared-texture mirror)
Add an injected IDXGISwapChain::Present / Present1 hook as a lower-latency, border-free alternative to WGC. The hook copies the swapchain backbuffer into a shared keyed-mutex texture (coop_video_<pid>); the host opens it by name and samples it. New opt-in HookSubsys_Video (protocol v6 -> v7); the Video mirror panel gains a WGC vs Hooked source toggle that installs/removes the subsystem. Verified by present_hook_test (drives a real D3D11 swapchain end-to-end and reads the rendered pixels back through the shared texture) and against Phantom Brave (D3D9: hook installs cleanly and stays idle, WGC fallback). All 5 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
27
README.md
27
README.md
@@ -23,6 +23,7 @@ XInput game becomes Remote-Play-Together-able.
|
||||
| Forward input to game | DLL injection + XInput hook (SafetyHook) — game sees *only* our pad | `coop_hook.dll` | done |
|
||||
| Keep game running unfocused | Hook spoofs focus so the game polls while the host holds OS focus | `coop_hook.dll` | done |
|
||||
| Mirror video | Windows Graphics Capture of the game window, letterboxed into the host window | `coop_host.exe` | done |
|
||||
| Mirror video (alt) | Injected Present-hook copies the DXGI backbuffer into a shared keyed-mutex texture the host samples (lower latency, no capture border) | `coop_hook.dll` + `coop_host.exe` | done |
|
||||
| Mirror audio | Injected render-hook copies the game's WASAPI frames into a shared ring and silences the game locally (no echo); WASAPI process loopback is the automatic fallback | `coop_hook.dll` + `coop_host.exe` | done |
|
||||
| Host ↔ hook IPC | Named shared memory (seqlock for input, status back-channel) | `common/` | done |
|
||||
|
||||
@@ -131,11 +132,19 @@ Done:
|
||||
Replaces tailing `%TEMP%\coop_hook.log` (which stays as an opt-in file mirror).
|
||||
`coop_audio_probe` drains and prints the same stream headless.
|
||||
|
||||
- **Present-hook video path. ✅** The injected DLL hooks `IDXGISwapChain::Present`
|
||||
(and `Present1`) and copies the swapchain backbuffer into a shared keyed-mutex
|
||||
texture (`coop_video_<pid>`); the host opens it by name and samples it — a
|
||||
lower-latency, border-free alternative to WGC. It's an opt-in subsystem (the
|
||||
**Video Present-hook** checkbox in the Injection panel, or just pick **Source:
|
||||
Hooked (Present)** in the Video mirror panel, which installs it). Only DXGI
|
||||
swapchains with an `ID3D11Texture2D` backbuffer are caught (the common D3D11
|
||||
case); D3D9 / pure-D3D12 games keep WGC. Validated by `present_hook_test`
|
||||
(drives a real D3D11 swapchain end-to-end and reads the pixels back through the
|
||||
shared texture).
|
||||
|
||||
Future work, roughly in priority order:
|
||||
|
||||
- **Present-hook video path:** capture the game's frames by hooking
|
||||
`IDXGISwapChain::Present` in the injected DLL and sharing the backbuffer via a
|
||||
shared D3D11 texture, as a lower-latency / more stable alternative to WGC.
|
||||
- **Steam Input:** consume guest input through the Steam Input API directly
|
||||
rather than XInput.
|
||||
- **x86 support:** add an x86 build of `coop_hook.dll` plus an x86 injector
|
||||
@@ -185,6 +194,12 @@ ctest --test-dir build -C Debug --output-on-failure
|
||||
COM vtables were discovered, the frames reached the ring (non-silent), the
|
||||
primary stream was silenced, and exactly one render stream was counted. Skips
|
||||
cleanly if the machine has no audio endpoint.
|
||||
- **`present_hook_test`** — in-process self-test of the Present-hook video path:
|
||||
installs the hook, drives a real D3D11 swapchain in the same process (clears the
|
||||
backbuffer to a known color and calls `Present`), and asserts the detour fired,
|
||||
the backbuffer reached the shared keyed-mutex texture, and a second device can
|
||||
open it by name and read the exact pixels back. Skips cleanly if the machine has
|
||||
no D3D11 device.
|
||||
- **`audio_loopback_test`** — spawns `coop_tone.exe` (a standalone WASAPI
|
||||
sine-wave source under [`tools/audio_tone`](tools/audio_tone)) and verifies the
|
||||
shipping process-loopback capture (the fallback path) receives its audio by
|
||||
@@ -223,7 +238,11 @@ person/account to receive the stream.
|
||||
**Attached**, a non-zero **XInput polled: N/s**, and **Focus spoof: active**.
|
||||
|
||||
3. **Mirror video:** in the **Video mirror** panel, tick **Mirror game window** —
|
||||
the host window now shows a live, letterboxed copy of the game.
|
||||
the host window now shows a live, letterboxed copy of the game. **Source**
|
||||
picks how the frames are grabbed: **WGC** (default, Windows Graphics Capture —
|
||||
works for any window) or **Hooked (Present)** (the injected Present-hook's
|
||||
shared texture — lower latency and no capture border, but only for DXGI /
|
||||
D3D11 games; selecting it installs the video subsystem in the game).
|
||||
|
||||
4. **Mirror audio:** in the **Audio mirror** panel, tick **Mirror game audio**.
|
||||
With the hook injected, **Source** shows **Hooked (no echo)** and the game's
|
||||
|
||||
Reference in New Issue
Block a user