Roadmap: Vulkan mock done; capture remaining (read-back approach + test caveat)
Reflect that the Vulkan mock backend + submodules are done, and scope the remaining Vulkan capture honestly: hook vkGetInstance/DeviceProcAddr + intercept device/swapchain creation, and read the presented image back with vkCmdCopyImageToBuffer (the same read-back pattern as D3D10/D3D9/OpenGL -- simpler/robuster than a VK_KHR_external_memory keyed-mutex blit). Note testing needs the early-load path (late-inject can't catch a Vulkan present), and Vulkan games mirror via WGC meanwhile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
60
README.md
60
README.md
@@ -147,41 +147,41 @@ Conventions for every milestone below:
|
||||
nothing clipped/overlapping, colours/labels correct. A green unit test is not a substitute
|
||||
for looking at the product; this real-screenshot check is required, not optional.
|
||||
|
||||
- **M2 — Vulkan (mock → capture).** The largest.
|
||||
1. **Mock backend** (`render_vk.cpp`). New submodules **Vulkan-Headers**
|
||||
(`KhronosGroup/Vulkan-Headers`, official) + **volk** (`zeux/volk`); raw
|
||||
`vkCreateWin32SurfaceKHR`, swap chain, per-frame acquire → clear → present. Background via
|
||||
`vkCmdClearColorImage`, bar + block via `vkCmdClearAttachments` clear-rects (no
|
||||
pipeline/shaders → **no SPIR-V toolchain**). Loads Vulkan via **volk** on purpose — that's
|
||||
the loader-bypass case the capture hook must handle. Validation layers used when a Vulkan
|
||||
SDK is present, skipped otherwise.
|
||||
2. **Capture.** Hook `vkQueuePresentKHR`; import a D3D11 keyed-mutex shared texture into Vulkan
|
||||
(`VK_KHR_external_memory_win32` + `VK_KHR_win32_keyed_mutex`) and `vkCmdBlitImage` the
|
||||
swap-chain image into it each present; the host samples it as usual. Because Vulkan caches
|
||||
its present pointer at init, the hook **cannot be placed by late injection** — it must be
|
||||
present before `vkCreateInstance`:
|
||||
- **Attach via auto-attach — best-effort by default, opt-in layer for reliability.** By
|
||||
- **M2 — Vulkan capture.** The Vulkan **mock backend is done** (`render_vk.cpp`, selectable `vk`):
|
||||
volk + Vulkan-Headers submodules, a real instance / device / swap chain, and a per-frame
|
||||
`vkCmdClearColorImage` of the frame-counter colour (no pipeline/shaders → **no SPIR-V
|
||||
toolchain**); it loads Vulkan via **volk** on purpose (the loader-bypass case the capture hook
|
||||
must handle). `mock_game_test` runs a **liveness** check on it — its present pointer is cached
|
||||
at init, so late injection can't hook it, which is exactly why the capture below is the hard
|
||||
part. The remaining **capture hook** (Vulkan games mirror via **WGC** meanwhile — the default):
|
||||
1. **Present interception (needs early presence).** Hook `vkGetInstanceProcAddr` /
|
||||
`vkGetDeviceProcAddr` (the `vulkan-1.dll` exports) so a volk-using app resolves *our*
|
||||
wrappers, and intercept `vkCreateDevice` / `vkCreateSwapchainKHR` to capture the
|
||||
device/queue/images/format. Read the presented image back with `vkCmdCopyImageToBuffer` into
|
||||
a host-visible buffer (the same read-back pattern as D3D10/D3D9/OpenGL — simpler and more
|
||||
robust than a `VK_KHR_external_memory_win32` + keyed-mutex blit), swizzle if BGRA, and upload
|
||||
via the hook-owned D3D11 device. **Testing needs the early-load path** (the mock loads the
|
||||
hook before `vkCreateInstance`, e.g. via an env-var or the layer); the normal late-inject
|
||||
`mock_game_test` flow can't catch a Vulkan present.
|
||||
2. **Attach via auto-attach — best-effort by default, opt-in layer for reliability.** By
|
||||
default, arming Auto-attach for a detected Vulkan game injects as early as possible on
|
||||
relaunch (the existing poll-and-inject) — enough for games that initialize Vulkan a little
|
||||
into startup. For games that init Vulkan immediately, an **opt-in** Injection-panel
|
||||
checkbox ("Set up Vulkan layer") registers a per-user (HKCU, no admin) implicit Vulkan
|
||||
layer (thin `coop_vk_layer.dll` + JSON manifest) scoped to that game's image name; on
|
||||
relaunch the loader loads it at `vkCreateInstance` — guaranteed before init — and it wires
|
||||
up present capture + IPC. The layer is removed when the option is unticked or the host
|
||||
exits, and self-deactivates for any non-target app, so a stale registration (e.g. after a
|
||||
host crash) is harmless.
|
||||
- **Too-late detection + red prompt.** A late-injected hook that finds `vulkan-1.dll` loaded
|
||||
into startup. For games that init Vulkan immediately, an **opt-in** Injection-panel checkbox
|
||||
("Set up Vulkan layer") registers a per-user (HKCU, no admin) implicit Vulkan layer (thin
|
||||
`coop_vk_layer.dll` + JSON manifest) scoped to that game's image name; on relaunch the loader
|
||||
loads it at `vkCreateInstance` — guaranteed before init — and it wires up present capture +
|
||||
IPC. The layer is removed when the option is unticked or the host exits, and self-deactivates
|
||||
for any non-target app, so a stale registration (e.g. after a host crash) is harmless.
|
||||
3. **Too-late detection + red banner.** A late-injected hook that finds `vulkan-1.dll` loaded
|
||||
but no working hooked-present reports a *"Vulkan, injected too late"* status; the host
|
||||
overlays a **red** banner on the mirror window: "Detected a Vulkan game — the hook attached
|
||||
too late to mirror it with low latency. Enable Auto-attach (and, if it persists, 'Set up
|
||||
Vulkan layer') and relaunch the game." **WGC keeps mirroring meanwhile** so the session
|
||||
stays usable; the banner clears once the hooked path goes live. (New end-user UI → keep
|
||||
M1's fit test green.)
|
||||
- Sub-steps (separate commits): present-hook placement (best-effort inject + the implicit
|
||||
layer), external-memory image import + keyed-mutex sync, the too-late status code, the
|
||||
host red-banner UI, and the Auto-attach / Vulkan-layer Injection-panel controls. Test the
|
||||
early (layer) path against the mock (decode frames) and the too-late path (assert the
|
||||
status + banner fire).
|
||||
Vulkan layer') and relaunch the game." **WGC keeps mirroring meanwhile** so the session stays
|
||||
usable; the banner clears once the hooked path goes live. This is new end-user UI, so it must
|
||||
keep M1's fit test (and the visual inspection) green.
|
||||
|
||||
Each of 1–3 is its own commit; test the early/layer path against the mock (decode frames) and
|
||||
the too-late path (assert the status + banner fire).
|
||||
|
||||
### Future work
|
||||
|
||||
|
||||
Reference in New Issue
Block a user