0001: Composite guest frames by GPU-copying them into Godot-owned textures
- Date: 2026-10-02
- Status: accepted (step 0.9)
Context¶
Guests render into shared D3D12 textures (color + linear depth). Fusion (Godot 4.7.2, D3D12) has to draw them inside its scene. The plan was to wrap each shared texture with RenderingDevice.texture_create_from_extension and sample it directly through a Texture2DRD.
What we found:
texture_create_from_extensionworks on D3D12. It takes anID3D12Resource*and doesn't add a reference, so we have to keep the resource alive ourselves.Texture2DRDalways creates a shared view of the texture it wraps (TextureStorage::texture_rd_initialize).- In debug and editor builds, Godot's D3D12 driver refuses views of textures it didn't allocate (
ERR_FAIL_COND_V(!owner_tex_info->owner_info.allocation)inside#ifdef DEBUG_ENABLED). The rest of that function would work; the check is only a sanity check. The material then silently falls back to a white texture, which covered the whole screen in our first test. - Godot does expose its main D3D12 command queue:
get_driver_resource(DRIVER_RESOURCE_COMMAND_QUEUE, RID(), 0).
Decision¶
Each frame, when a new guest frame is ready, Fusion GPU-copies it with RenderingDevice.texture_copy from the shared texture (wrapped with texture_create_from_extension, usage CAN_COPY_FROM) into two textures Godot created itself. The compositing material samples those through Texture2DRD.
After each frame (frame_post_draw), Fusion signals its own fence on Godot's queue with the frame number. A guest buffer goes back to the guest once that fence passes the frame whose copy used it.
Consequences¶
- Works in the editor and in exported games; no Godot patch needed.
- Costs one GPU copy of color + depth per new guest frame. At 1600×900 on an RTX 3050 Laptop it was below measurement noise (about ±0.5 ms).
- Guest buffers return after about one frame, instead of staying held while shown.
- Godot's render graph orders the copy and issues its barriers.
- Pitfall found while testing: Godot skips drawing (and so doesn't send recorded GPU commands) while its window can't be drawn, e.g. minimized or screen off. A fence signaled at shutdown can therefore pass before recorded copies are submitted. Releasing the guest's D3D12 objects then crashed Godot on exit (0xC0000005). Fix: release D3D12 objects only after Godot has drawn a few more frames (the
DEFERREDlist infusion-godot/src/guest_view.rs); anything left at exit is freed by Windows. - If Godot ever allows views of external textures in debug builds, direct sampling (zero-copy) becomes possible. The copy is cheap enough that this isn't a priority.
Verification¶
godot/scenes/tests/compositor_test.tscn with -- --auto:
- freezes the guest;
- draws Godot's own cube at the pose the guest reports;
- compares the outlines: 100% overlap in 4 of 4 runs, including where Godot boxes hide parts of the cube.