gfx: scene render pipeline rework + GPU video I/O interop layer#2109
Draft
jcelerier wants to merge 24 commits into
Draft
gfx: scene render pipeline rework + GPU video I/O interop layer#2109jcelerier wants to merge 24 commits into
jcelerier wants to merge 24 commits into
Conversation
jcelerier
force-pushed
the
clean/gpu-interop
branch
from
July 12, 2026 18:32
562c054 to
c78e765
Compare
This was referenced Jul 12, 2026
Closed
Closed
Bring in OffsetAllocator to back the slab-allocated GPU arenas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ebuilt The core scene-GPU rework. Adds the supporting infrastructure — OffsetAllocator-backed GPU arenas (GpuResourceRegistry), a shared decoded-asset cache (AssetTable + TextureLoader), scene math and pipeline-state helpers, the flat scene state and packer (SceneGPUState), an offscreen RHI device and preview widget — then reworks the pipeline on top of it: the ISF parser and nodes gain 3D samplers, uniform inputs and geometry/vertex stages; the compute and raster pipelines become scene-aware with multiple render targets and auxiliary outputs; the render graph updates edges, render targets and passes in place instead of rebuilding wholesale; and the filter, texture-port, window, capture and video-output paths follow the new node and renderer interfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
ScenePreprocessorNode turns a scene spec into flat draw commands and arena uploads; SceneFilterNode, FlattenedSceneFilterNode and MergeGeometriesNode operate on flattened scenes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Split the CPU filter/analysis and GPU node lifecycles into init / initState / release and add the scene-port concept with its storage helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Request the xcb EGL integration on X11 so that dma-buf import can stay zero-copy, and pin a desktop-GL (not GLES) core-profile context with a stencil buffer on the desktop code path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
A PanelDelegateFactory may now legitimately return no widget. Don't store a null panel, guard the plugin-load instantiation path against it, and make the device-explorer panel lookup null-tolerant instead of dereferencing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Drain the event queue before deleting the presenter: deferred slots queued during plugin load read the presenter-owned application context, so running them after the presenter is gone is a use-after-free. Also take the settings models down while the QApplication is still alive, which Qt >= 6.11 requires. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
staticVulkanInstance() could hand back an instance whose create() had failed; return null instead so callers fall back cleanly rather than crashing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…tizer builds Honor SCORE_SANITIZE_SKIP_CHECKS in firstTimeLibraryDownload so that ASan and UBSan runs don't reach out to the network at startup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
A small static bridge that dlopens libdvp at runtime (the driver ships it; no CUDA toolkit install is required) and exposes its buffer/texture transfer API to the gfx plugin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
A backend-neutral pixel-format description plus conversions to and from ffmpeg's AVPixelFormat, shared by the video encoders, decoders and the GPU interop strategies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
P010, packed high-bit-depth RGB, and planar/packed YCbCr (YUY2, yuv422p10) GPU encoders; parameterize the packed-RGBA encoder for the RGBA, ABGR and ARGB byte orders; and add a factory that selects the encoder for a given wire format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Vendor-neutral primitives for moving frames between the GPU and capture cards without a CPU round-trip: - Vulkan external memory and cross-API texture sharing - a CUDA layer (function loader, VMM allocator, capability probing) - Vulkan<->CUDA binary semaphores and an interop fence - host-pinned, imported and RDMA GPU-buffer rings (NVIDIA and AMD) - dma-buf / DRM-PRIME import and export helpers - a paced frame pump and a stage profiler - CPU-staged and RDMA output strategies with a runtime selector Everything is compiled unconditionally but only activates when the matching driver and hardware are present at runtime. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
v210 and r210 wire decoders, with correct padded strides at widths that are not a multiple of six, plus a decoder factory and the DRM-PRIME, VAAPI, CUDA and Vulkan hardware-decode paths. Video inputs can now report a dynamic format change and carry the hardware surface's underlying software pixel format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
DirectVideoOutputNode drives a capture card's SDI/HDMI output through a shader encode plus the selected interop strategy, and DMACaptureInputNode ingests a card's input straight into GPU textures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Extract a RenderClock abstraction behind which the display-vsync and timer render clocks sit, and add an opt-in external clock so the render loop can be paced by a capture card's vertical interval. Off by default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Native PipeWire video input and output, with shared-memory and zero-copy dma-buf transport negotiated per pixel format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…kends Wire the high-bit-depth encoders into the Libav and GStreamer video outputs and the dma-buf import into the window-capture, shmdata and sh4lt inputs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
An offscreen self-test for the GPU video encoders and a PipeWire producer/consumer round-trip harness covering every pixel format and transport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
jcelerier
force-pushed
the
clean/gpu-interop
branch
from
July 13, 2026 02:53
dae2a5b to
ef992e4
Compare
The quad's vertex buffer is owned by RenderList::m_vertexBuffers and is raw-deleted by RenderList::release(), but the renderer kept a private copy of the handle. The `if(m_meshBuffer.buffers.empty())` guard in init() then skipped re-acquisition, so after a release()+init() cycle -- which maybeRebuild() runs on every window resize/expose -- the node drew with a freed buffer. init() allocates the process/material UBOs immediately after, so the allocator hands that block straight back and the stale handle points at a live UniformBuffer: a deterministic `buf->usage().testFlag(QRhiBuffer::VertexBuffer)` abort as soon as a real on-screen Window renders a Direct Video Input. Every other renderer using this caching idiom already resets it (VideoNodeRenderer, DirectVideoNodeRenderer, SimpleRenderedISFNode). Also give the renderer a defensive destructor mirroring release()'s documented teardown ordering, for deletion paths that never reach release(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
In VSync mode the on-screen render loop re-drives itself solely through QWindow::requestUpdate(). On xcb that is an unconditional timer, so the loop self-heals. On Wayland requestUpdate() is frame-callback gated: if the compositor withholds a frame callback past QT_WAYLAND_FRAME_CALLBACK_TIMEOUT (default 100ms) -- occluded window, blanked/DPMS output, another surface on top, or heavy per-frame work -- Qt marks the window inexposed and the loop, having no other driver, produces no frames. The render itself is fine: an offscreen readback of the same frame returns correct pixels, so the symptom is a black on-screen window despite correct rendering. Confirmed by flipping only the VSync setting on a KDE-Wayland session, and by a minimal QRhi repro whose requestUpdate loop dies under a forced 1ms frame-callback timeout while a timer-driven loop is unaffected. Gate the vsync loop off when the Qt platform is Wayland so it takes the timer path. The compositor supplies frame pacing there, so no tearing results from dropping swap-chain vsync. xcb, eglfs and the embedded backends are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
VideoCaptureSlotRing gains a Spout-style seqlock format channel
(formatGeneration + detected{Width,Height,PixelFormat,Rate}): publishFormat()
on the capture thread signals a wire change, loadFormat() reads it on the render
thread with a parity retry. DMACaptureInputNode::update() polls it beside
latestFrameId and, on a new generation, runs release()+init() to re-create its
size-dependent GPU resources (input texture, slot pool, pipelines,
VideoMaterialUBO.textureSize) at the new geometry. m_lastFormatGen is baselined
at the end of init() so the first frame doesn't spuriously rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This branch bundles two stacked features (merge as one), 22 commits on top of
master:1. Scene-aware GPU render pipeline (6 commits)
Reworks the gfx render graph around a flat, incrementally-rebuilt scene model:
2. GPU video I/O interop layer (16 commits)
Adds a vendor-neutral layer for capture-card and PipeWire video I/O without a CPU round-trip:
Validation
Every commit builds independently on Linux (clang/ninja;
score_plugin_gfxat each commit, consumer plugins from their adaptation commit, full tree at the tip). The interop paths were hardware-validated end-to-end (AJA Kona 5 SDI/HDMI, DeckLink, Magewell, PipeWire) and are ASan/UBSan-clean.🤖 Generated with Claude Code