Context
Two of the four D3D12 primitives need the bridge's attention beyond a straight port:
- Watermark (GO, simpler than Vulkan): D3D12 fences are already monotonic timeline counters. With our own
ID3D12Fence, call queue.as_raw().Signal(fence, frame_no) AFTER wgpu's submit, read lock-free via GetCompletedValue(). No add_signal_semaphore plumbing (unlike the Vulkan M2b path).
- Resource state (ADAPT): rive issues its own ResourceBarriers and leaves the target in
D3D12_RESOURCE_STATE_COMMON at end of flush (render_context_d3d12_impl.cpp:2027-2030) — not a shader-read state. Also setTargetTexture seeds the tracked state to PRESENT (:433), so rive's first barrier is PRESENT -> RENDER_TARGET; for a wgpu-owned (non-swapchain) texture the real prior state differs.
Scope
- Implement the post-submit
Signal watermark using an own ID3D12Fence; recycle frames when GetCompletedValue() reaches the frame number. Reuse the safe/current-frame-number watermark already in the external-frame ABI.
- Tell wgpu's dx12 state tracker the wrapped texture is in
COMMON after rive's submit (COMMON's implicit promotion/decay is friendly). Reconcile the PRESENT-seeded initial state: hand the texture to rive decayed to COMMON, or extend wrap_d3d12_resource to set the initial tracked state. Pin the exact state values (the spike confirmed the mechanism, not the values).
Acceptance criteria
- Frames recycle exactly when their GPU work completes (no over-/under-hold), verified under load.
- No D3D12 debug-layer resource-state errors across rive<->wgpu handoff; the texture samples correctly in the subsequent wgpu pass.
Refs
Context
Two of the four D3D12 primitives need the bridge's attention beyond a straight port:
ID3D12Fence, callqueue.as_raw().Signal(fence, frame_no)AFTER wgpu's submit, read lock-free viaGetCompletedValue(). Noadd_signal_semaphoreplumbing (unlike the Vulkan M2b path).D3D12_RESOURCE_STATE_COMMONat end of flush (render_context_d3d12_impl.cpp:2027-2030) — not a shader-read state. AlsosetTargetTextureseeds the tracked state toPRESENT(:433), so rive's first barrier isPRESENT -> RENDER_TARGET; for a wgpu-owned (non-swapchain) texture the real prior state differs.Scope
Signalwatermark using an ownID3D12Fence; recycle frames whenGetCompletedValue()reaches the frame number. Reuse the safe/current-frame-number watermark already in the external-frame ABI.COMMONafter rive's submit (COMMON's implicit promotion/decay is friendly). Reconcile the PRESENT-seeded initial state: hand the texture to rive decayed toCOMMON, or extendwrap_d3d12_resourceto set the initial tracked state. Pin the exact state values (the spike confirmed the mechanism, not the values).Acceptance criteria
Refs
the D3D12 feasibility spike (summarized in ROADMAP.md)(primitives M3a: D3D12 completion watermark + resource-state handoff to wgpu's tracker #3, Roadmap decision: D3D11 is OUT OF SCOPE (document, do not implement) #4)vendor/rive-runtime/renderer/src/d3d12/render_context_d3d12_impl.cpp:409,433,2027-2030wgpu-hal-27.0.4/src/dx12/mod.rs:739(Queue::as_raw), device fence create/GetCompletedValue