Skip to content

split/gpu interop#2078

Open
jcelerier wants to merge 101 commits into
masterfrom
split/gpu-interop
Open

split/gpu interop#2078
jcelerier wants to merge 101 commits into
masterfrom
split/gpu-interop

Conversation

@jcelerier

Copy link
Copy Markdown
Member
  • isf: TEX_DIMENSIONS_3D / IMG_SIZE_3D aliases for 3D samplers
  • isf: point3d_input AS_COLOR flag for color-swatch display
  • isf: parse-time warning on unknown csf_image_input FORMAT
  • isf: parser rework — top-level descriptors, uniform inputs, geometry AUXILIARY, vertex inputs
  • 3rdparty: add OffsetAllocator submodule
  • gfx: add AssetTable + TextureLoader for shared decoded-asset cache
  • gfx: add GpuResourceRegistry — slab-allocated GPU arenas backed by OffsetAllocator
  • gfx: add CameraMath / GpuTiming / VertexFallback helpers
  • gfx: add IsfBindingsBuilder + PipelineStateHelpers
  • gfx: add SceneGPUState — FlatScene + scene packer
  • gfx: add OffscreenDevice + RhiPreviewWidget + Metal buffer-copy backend
  • gfx: extend ISF / SimpleRenderedISF nodes for new ISF features
  • csf: rework for scene-aware compute pipeline
  • gfx: rework raw raster pipeline (MRT, AUXILIARY, EXECUTION_MODEL) and VSA
  • gfx: refresh shared graph plumbing for incremental scene pipeline
  • gfx: add ScenePreprocessorNode — scene_spec to flat draws + arena uploads
  • gfx: add SceneFilterNode + FlattenedSceneFilterNode + MergeGeometriesNode
  • avnd: split CpuFilter / CpuAnalysis lifecycle into init / initState / release
  • avnd: add scene_port concept
  • avnd: split GpuNode lifecycle and add scene_port storage helpers
  • gfx: extend ShaderProgram for new ISF features
  • gfx: refresh Filter / GeometryFilter / TexturePort for new pipeline
  • gfx: refresh WindowDevice / Spout / Syphon / WindowCapture
  • gfx: refresh GfxContext + window / screen / multiwindow nodes for incremental scene rebuild
  • js: gpu node lifecycle rework with deterministic teardown
  • wip: many bugfixes across the board
  • build: more build fixes
  • ci: fixes for older qt versions
  • rhi: populate caps
  • ci: try windows fixes
  • gfx: restore PhongNode, still used by score-vfx-template
  • gfx: keep TextureRenderTarget compatible with addons' aggregate init
  • gfx: do not include mmsystem.h in CommonUBOs.hpp
  • gfx: fix build with Qt < 6.9
  • gfx: fix designator order after TextureRenderTarget reorder
  • gfx: fix build with Qt 6.4
  • gfx: more Qt 6.4 compatibility guards
  • xcb: use EGL instead of GLX by default
  • desktop: enforce desktop GL (Qt may pick GLES when we use a EGL surface)
  • gpu: try to land a generalized abstraction for interop & RDMA

@jcelerier jcelerier force-pushed the split/gpu-interop branch 3 times, most recently from 851f361 to b1c6d32 Compare June 26, 2026 03:15
jcelerier added 27 commits July 6, 2026 21:02
…AUXILIARY, vertex inputs

Top-level descriptors (PIPELINE_STATE, MULTIVIEW, EXECUTION_MODEL, EXTENSIONS,
CLIP_DISTANCES / CULL_DISTANCES, DEPTH_LAYOUT). New input kinds: uniform_input
(UBO INPUTS), per-input sampler_config (WRAP / FILTER / COMPARE / MIPS),
audio_sampler_config. Storage / image / cubemap input extensions: PERSISTENT,
IS_ARRAY, GENERATE_MIPS, cube/3D textures. Geometry input AUXILIARY: storage +
sampled, persistent, is_uniform, depth companion, INDIRECT block. Per-pass:
LAYER, Z, FORMAT, PIPELINE_STATE override. OUTPUTS: LAYERS, DEPTH, FORMAT,
SAMPLES, CUBEMAP, GENERATE_MIPS, WIDTH/HEIGHT expressions. Vertex inputs:
REQUIRED, DEFAULT, SEMANTIC, INTERPOLATION (with auto-flat for int/bool
varyings).

Bug fixes: mat3/mat4 attribute slot count, TYPES struct emission in BOTH
stages, drop incorrect std430 padding heuristic that was breaking RawLight
stride.
jcelerier and others added 29 commits July 7, 2026 12:27
The encoder's Swizzle enum denotes the in-memory byte order of the RGBA8
output. Add the [A,R,G,B] order (needed for NTV2_FBF_RGBA, whose actual
memory layout is A,R,G,B despite the name) and clarify that the names are
memory orders, not AJA FBF names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
New GPUVideoEncoder implementations for AJA framebuffer formats beyond the
8-bit RGB / UYVY / v210 / 10-bit-422-planar set:

- PackedRGB.hpp: generic packed-pixel encoder generalising the V210 "one
  RGBA8 output texel == 4 destination row bytes" trick to any byte layout via
  a per-format rowByte(b) GLSL function. Factories for NTV2_FBF_10BIT_RGB,
  10BIT_DPX (BE) / 10BIT_DPX_LE, 48BIT_RGB (full 16-bit), 12BIT_RGB_PACKED,
  24BIT_RGB / 24BIT_BGR, and 10BIT_ARGB. Bit layouts verified against the SDK
  PackRGB10Bit* / Convert16BitARGBTo* / ConvertLine_8bitABGR_* helpers.
- YUY2.hpp: 8-bit 4:2:2 YUY2 byte order (Y0,Cb,Y1,Cr).
- YUVPlanar.hpp: generic 3-plane planar YCbCr, 8/10-bit x 4:2:2/4:2:0
  (R8/R16 plane targets, Qt>=6.10).

Validated round-trip on Kona5 12-bit firmware across GL/Vulkan/D3D11/D3D12:
10-bit RGB, both DPX, 12-bit (48-bit + packed), and 24-bit RGB/BGR all pass
(42.8-43.6 dB). YUY2/10-bit-ARGB/planar encoders are correct but the Kona5
does not drive those framestore formats to SDI (card output limitation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
The CPU-staging counterpart to GpuDirectOutput (and the output-direction
counterpart to HostPinnedRing): the path used whenever GPU-direct DMA isn't
available - the common case on consumer GPUs. It owns the full
"encode on GPU -> read back -> hand a host pointer to the card's DMA"
pipeline:
  - the two double-buffered GPUVideoEncoders,
  - a page-locked staging ring (vendor-pinned via VendorDmaRegistrar),
  - the direct-DMA-from-readback fast path (page-lock the encoder readback
    buffer when it's already byte-identical to the framestore, skipping the
    per-frame memcpy),
  - single-plane / planar staging, plus an optional vendor customStage hook
    for special packing (e.g. v210 CPU pack for non-mod-6 widths).

Vendors supply only pin/unpin callbacks + per-frame submit/pacing, so
DeckLink / Magewell / Bluefish output can reuse this instead of
re-implementing the ring + readback + direct-DMA. VendorDmaRegistrar moved
to its own header so both output helpers share it without the GPU-direct
header's CUDA deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Two shared seams lifted out of the AJA addon so DeckLink/Bluefish/
Magewell/Deltacast addons reuse them instead of re-deriving:

- makeWireEncoder(VideoPixelFormat) / makeWireComputeEncoder /
  wireComputeSupports (encoders/WireEncoderFactory.hpp): the
  RGBA->wire encoder switch, keyed on the neutral VideoPixelFormat
  enum instead of a vendor format enum. Extended VideoPixelFormat
  with the formats the existing encoders already produce (RGB24/BGR24,
  ARGB10, DPX10/DPX10LE, RGB12P, RGB48, YUV420P10).

- PacedFramePump (interop/PacedFramePump.{hpp,cpp}): the lock-free
  SPSC ring + clock-paced consumer thread + drain-to-newest +
  drop/underrun accounting, generalised from AJAConsumerThread. The
  vendor supplies three hooks: waitForTick / canAccept / submit.

Also adds the multi-vendor implementation map document.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
selectGpuDirectStrategy(cfg, candidates, onEngaged, onFailed) owns the
DVP->Tier3->fallback init/log/release loop every output addon repeats.
The per-vendor strategy classes stay in the addon (they hold the card
handle + format); the addon passes factory thunks for the active backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Shared base for professional capture-card INPUT nodes (AJA, and future
DeckLink/Bluefish/Deltacast/Magewell). DMACaptureRenderer owns all the
generic machinery — mesh/UBO setup, decoder init, GpuDirectCaptureStrategy
init with CPU-staging fallback, the zero-copy texture swap, defaultPassesInit,
the per-frame slot-ring poll + acquire/release bracket, and teardown.

The vendor supplies a DMACaptureBackend: open the device, report geometry +
colour metadata, build the decoder for its wire format, pick the GPU-direct
strategy (+ CPU fallback), and run the capture thread feeding the slot ring.

This is the input-side counterpart to the output seams (HostStagedOutput /
GpuDirectStrategy / PacedFramePump). It is deliberately NOT a universal
"video input" base: the AVFrame-upload path (camera/NDI, VideoNodeRenderer)
and the shared-texture path (Spout/Syphon) are different frame-delivery
models; this base is specifically the DMA-capture-card family.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Record the implemented shared seams (#3 makeWireEncoder, #2 PacedFramePump,
#4 selectGpuDirectStrategy, DMACaptureInputNode/DMACaptureBackend), the
naming/scoping decisions (DMACaptureInputNode not VideoInputNode; no
monolithic VendorIoBackend), and the vendor authoring surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Single source of truth for the overlap between the capture-card wire-format
enum and libav's AVPixelFormat. toAVPixelFormat / fromAVPixelFormat map the
buffer formats that have byte-identical twins (planar/semi-planar YUV, packed
8-bit + 16-bit RGB, grey, UYVY/YUYV/YVYU) and return NONE/Unknown for the
wire-only packed formats FFmpeg models as codecs rather than pixel formats
(v210, v216, r210, DPX 10/12, 12-bit packed RGB, A2-ARGB10).

Lets the capture path derive a VideoPixelFormat from the AVPixelFormat that
Video::ImageFormat already carries, and lets vendor tables interop with the
libav-based decoder/encoder paths without each site re-deriving the mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
…innedFormat

- decoders/WireDecoderFactory.hpp: makeWireDecoder(VideoPixelFormat, ImageFormat&)
  — the capture-side symmetric of makeWireEncoder, over the real GPUVideoDecoder
  classes. Vendor capture backends stop hand-rolling the format->decoder switch.
- Remove decoders/FormatDecoders.{hpp,cpp}: an orphan (bare GLSL strings, called
  nowhere) superseded by the GPUVideoDecoder-based factory.
- HostPinnedRing: replace the 4-value HostPinnedFormat enum (a strict subset
  duplicate) with the shared VideoPixelFormat; stride now comes from
  interop::defaultStride (identical values), toDvpFormat keyed on VideoPixelFormat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Plan to add DeckLink/Magewell/Bluefish/Deltacast as backends behind a single
unified "Direct Video I/O" device that branches on the detected card, with AJA
folded in as the reference backend. Covers the shared output-backend extraction,
the vendor registry, per-vendor SDK wiring (enum/output/input/GPU-direct tiers/
formats/signal/HDR/ANC), the full options matrix, CMake SDK gating, testing, and
phased sequencing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
The output-side counterpart to DMACaptureBackend. A vendor implements it; the
(forthcoming) shared DirectVideoOutputNode composes the already-generic pieces
around it: makeWireEncoder(wireFormat), selectGpuDirectStrategy(gpuDirect
Candidates), HostStagedOutput(planes/registrar/customStage), PacedFramePump(
pacingHooks). This is the generalisation of AJANode::createOutput; AJA will be
ported onto it next as the reference backend (Phase A of DIRECT_VIDEO_IO_PLAN).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
…colorConversion

Lets the shared output node stay vendor-neutral: encoderFormat() (differs from
wireFormat when a CPU repack bridges them, e.g. v210 non-mod-6 -> UYVY),
prefersFloatRender() (RGBA16F intermediate for >8-bit/HDR), colorConversion()
(the RGB->wire colour-matrix shader).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
The vendor-neutral OutputNode for capture-card playout: owns the QRhi + render
loop + createOutput orchestration and drives everything through a
DirectVideoOutputBackend (makeWireEncoder(encoderFormat), selectGpuDirect
Strategy(gpuDirectCandidates), HostStagedOutput(planes/registrar/customStage),
PacedFramePump(pacingHooks)). Backend gains frameRate()/isOpen(); open() drops
the (pre-QRhi) rhi arg. Public diagnostics: activeStrategyName(), pacing*().

A vendor output node is now a thin subclass that constructs the node with its
backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Per design decision: only a handful of vendors, so the unified device hardcodes
the compiled-in vendor list behind #if SCORE_HAS_<VENDOR> and dispatches with a
plain switch on a vendor tag (no registry / registration indirection). The
unified protocol/device/settings + the dispatch live in score-addon-videoio
alongside the vendor backends; score-plugin-gfx keeps only the interfaces + nodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Add a no-regression, opt-in GPU-direct output download to the shared host-
staged path, reusing the existing (until now unconsumed) HostPinnedRing
TextureToBuffer + DVP backend instead of reinventing a multi-slot output ring.

- GPUVideoEncoder gains two additive, default-preserving virtuals:
  outputTexture() (nullptr by default) and setReadbackEnabled(bool) (no-op).
  UYVY/V210/BGRA (single-plane) override them: expose m_outTexture and gate the
  exec() readback on a flag. Defaults => byte-identical existing behavior.
- HostStagedOutputConfig gains preferGpuDownload + caps. When both are set and
  both encoders expose an output texture, init() builds a HostPinnedRing
  configured to the ENCODER TEXTURE geometry (RGBA8, texW*4 stride) — so the
  ring's DVP registration (sysmem + source texture) matches the texture, no
  HostPinnedRing change needed. Engages only when a GPU-direct backend is
  selected (not CpuStaging/None) and slot bytes == frameByteSize; then it
  registers each slot via the vendor registrar, disables the encoder readback
  (no double transfer), and skips the CPU std::vector ring. Any failure falls
  back to the unchanged CPU path. prepareNextFrame() DVP-copies the encoder
  texture into the next ring slot (synchronous) and returns its host pointer.
- DirectVideoOutputBackend gains prefersGpuDownload() (default false, AJA
  unchanged). DirectVideoOutputNode probes GpuCapabilities once and passes it +
  the opt-in into HostStagedOutputConfig.

AJA and the CPU readback path are byte-identical (default-off). Compile-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
The vendor-neutral NVIDIA-DVP shim templates (Dvp{Capture,Output}{Gl,D3D11} +
the DmaLockPolicy concept/NoDmaLock) were living in the score-addon-videoio
addon, but they're vendor-neutral gfx infrastructure — parameterized by a
DmaLockPolicy and depending only on nv_dvp_bridge + the GpuDirect*Strategy
interfaces, all of which already live here. Relocate them to Graph/interop/
alongside HostPinnedRing/HostStagedOutput and rename the namespace
Gfx::gpudirect -> score::gfx::interop to match the folder. Header-only; gfx
doesn't compile them itself (no consumer yet), it just provides them to the
capture-card addons. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
…pers

Seam B from the gpudirect taxonomy: a vendor-neutral allocator of RDMA-capable
GPU VRAM for cards that DMA straight to/from VRAM (e.g. Deltacast
VHD_CreateSlotEx(RDMAEnabled=TRUE)). Counterpart to HostPinnedRing's host-buffer
tier. No consumer yet — this is the foundation a vendor backend will use.

- interop/RdmaGpuBuffer.{hpp,cpp}: allocate N slots, each exposing the per-API
  handoff {gpuVA, size, opaque}. Backends:
    * Cuda  — reuses CudaVmmAllocator (CUdeviceptr, gpuDirectRDMACapable). REAL.
    * Vulkan (QT_HAS_VULKAN) — vkinterop::createRdmaBuffer (VkRemoteAddressNV). REAL.
    * HostFallback — 64 KiB-aligned host memory (non-RDMA app-buffer path). REAL.
    * D3D12 — scaffolded + gated (needs SCORE_HAS_NVAPI + the NvAPI SDK, which
      isn't part of the score build); returns unavailable for now.
- vkinterop: createRdmaBuffer / getMemoryRemoteAddress / destroyRdma, built on
  VK_NV_external_memory_rdma (RDMA_CAPABLE memory + vkGetMemoryRemoteAddressNV,
  via the existing devFuncs/resolveDeviceFn conventions). Compile out to a safe
  nullopt when the extension isn't in the Vulkan headers.

Compile-only (no Deltacast/Quadro here). gfx builds clean; videoio still links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
Symmetric inverse of cuda_p2p_copy_buffer_to_array: copies a CUDA array (level 0
of a CUDA-imported VkImage) into a flat device buffer (an RDMA-capable CUDA VMM
slot the card DMAs out). The per-frame texture->VRAM copy for GPU-direct OUTPUT,
the counterpart to the buffer->texture copy used by capture. Same cuMemcpy2DAsync
+ stream-sync shape; additive (new function, nothing else changed).

First consumer: the Deltacast RDMA output strategy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
CudaVmmAllocator now sets CU_POINTER_ATTRIBUTE_SYNC_MEMOPS on each mapped VMM
range (via a newly-loaded cuPointerSetAttribute in the CudaFunctions shim).
GPUDirect-RDMA engines like Deltacast's VHD_CreateSlotEx(RDMAEnabled=TRUE) and
Rivermax require this attribute to DMA into the buffer; without it the RDMA
capture/output path would be rejected at runtime on real hardware. Best-effort
(null-checked, non-fatal) — honoured on RDMA-capable GPUs, ignored elsewhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
…ams) + CUDA binary-semaphore import

Replaces the Vulkan InteropFence stub with a working implementation for the
GPU-direct RDMA output path (Deltacast), so the CUDA array->buffer copy never
reads a half-written VkImage.

- CudaP2PBridge: add cuda_p2p_import_vulkan_semaphore_binary(), a sibling of
  the existing timeline importer that passes CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_
  OPAQUE_WIN32 (/_OPAQUE_FD) to importSemaphore(). cuda_p2p_wait_semaphore
  already works for binary (value ignored) — waited with value 0.
- InteropFence: InteropFenceVulkan holds a ring of 3 exportable BINARY
  VkSemaphores. signalAfterEncode() asks QRhi to signal m_vkSem[value%3] at its
  next queue submit via setQueueSubmitParams (consumed by endOffscreenFrame);
  waitOnCuda() schedules a CUDA-stream wait on the matching imported semaphore.
  Export fn resolved via QVulkanInstance::getInstanceProcAddr
  (vkGetSemaphoreWin32HandleKHR / vkGetSemaphoreFdKHR). Any failure -> release()
  + init returns false so the caller uses its finish() fallback (no partial
  state). Kept InteropFenceVulkanStub for the no-Vulkan build. The device
  extensions this needs (VK_KHR_external_semaphore[_win32/_fd]) are already
  enabled in both QRhi Vulkan device-creation paths (ScreenNode.cpp and
  VulkanVideoDevice.hpp sharedVulkanDeviceExtensions()).

Compile-only (no Quadro/RDMA hardware here); degrades gracefully when the
semaphore path is unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
The Vulkan InteropFence had re-rolled vkCreateSemaphore + VkExportSemaphoreCreateInfo
+ vkGetSemaphoreWin32HandleKHR/FdKHR + CUDA import inline — exactly the boilerplate
VkCudaTimelineSemaphore already centralizes. Add a `binary` mode to that helper
(default VkSemaphoreCreateInfo is BINARY; imports via the new binary CUDA entry
point) and have InteropFenceVulkan hold a ring of them, dropping ~120 duplicated
lines. Also fix a latent per-semaphore Win32 HANDLE leak: the exported handle is
owned by us (CUDA duplicates it during import), so CloseHandle after import on
Win32; on Linux OPAQUE_FD ownership transfers to CUDA. Compile-only; no API change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FnqYUYn7AVxBQFZywarDNT
…iesce()

Two seam-level fixes from the multi-vendor review:

- PacedFramePump now parks on a frames-available semaphore BEFORE calling
  the vendor waitForTick() hook. Guarantees: waitForTick() never fires on
  an empty ring (a hook that consumes a scarce per-call resource - e.g.
  DeckLink's free-output-slot permits - can no longer lose it to an idle
  tick), an always-true waitForTick() no longer busy-spins a core while
  idle (Deltacast), and a tick timeout re-queues the pending frame. AJA
  is unaffected: its hook is a pure WaitForOutputVerticalInterrupt, so
  submits stay VBI-aligned with identical drain-to-newest semantics.

- New DirectVideoOutputBackend::quiesce() (default no-op), called by
  DirectVideoOutputNode::destroyOutput() after the pump stops but before
  the GPU-direct strategy / host-staged ring are released. Vendors whose
  submit hands buffers to a retained queue (DeckLink ScheduleVideoFrame,
  Deltacast RDMA application-buffer slots) stop streaming there so the
  card never DMA-reads freed memory; synchronous vendors (AJA) keep the
  no-op and see the exact same teardown order as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTaNKeBdVCyacJ6H2YGPsk
The release() detach guard compared samplers[0].texture against
strategy->outputTexture() — true for every strategy, since CPU/DVP
strategies report the decoder's own texture. Detaching in that case
leaked one QRhiTexture per redock (GPUVideoDecoder::release() is what
deletes it). Record at init whether the Vulkan tier-3 texture swap
actually happened and gate the detach on that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTaNKeBdVCyacJ6H2YGPsk
TextureShare's factory used #if defined(QT_HAS_VULKAN) but the macro is
always defined (0 or 1) by score/gfx/Vulkan.hpp; test its value like the
class definition does. Rename RdmaGpuBuffer's local roundUp which collides
with CudaVmmAllocator's in the same anonymous namespace under unity builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8dWKnm8kgkuDaMMjapgFT
Don't build EncoderTester with SCORE_STATIC_PLUGINS: score_init_static_plugins()
references every configured addon which the tester doesn't link. Use bridged
casts for the ObjC handle pointers in TextureShareMetal under ARC.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8dWKnm8kgkuDaMMjapgFT
- PipewireInputDevice: DRM_PRIME frame release used a dangling
  pw_stream after stop() — the weak_ptr guarded the process-wide
  context, not the stream. Frames now hold a shared stream token that
  is nulled on the loop thread before pw_stream_destroy.
- PipewireInputDevice: pooled frames recycled across a format
  renegotiation kept their old geometry, so the next capture copied
  the new size through the old allocation (heap overflow); reallocate
  stale frames at pick time. Lock the pool in init_frames(), which
  runs on the loop thread concurrently with release_frame.
- DMACaptureInputNode: implement addOutputPass/removeOutputPass; the
  renderer holds per-edge passes, so the empty overrides meant a UAF
  when a downstream render-target spec changed and no pass at all for
  edges connected after init.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8dWKnm8kgkuDaMMjapgFT
- PipewireFormats: RGBA8/BGRA8 mapped to nonexistent fourccs ('RGBA'/
  'BGRA' ASCII); use DRM_FORMAT_ABGR8888/ARGB8888 (AB24/AR24, verified
  against drm_fourcc.h) so the zero-copy import stops failing.
- PipewireInputDevice: stop() no longer early-returns when
  on_node_removed already cleared m_running (leaked the stream +
  listener); start() tears down any such leftover stream first.
- PipewireInputDevice: advertise the full planar frame size (NV12 1.5x,
  P010 3x, P210 4x) in SPA_PARAM_BUFFERS_size, and validate incoming
  chunk sizes against av_image_get_buffer_size before the plane copies.
- PipewireOutputDevice: don't hold the pw thread-loop lock across
  render + endOffscreenFrame (multi-ms GPU wait stalling the data
  thread); buffers removed while checked out are parked and destroyed
  at queue time instead.
- TextureShare (GL): flush QRhi's deferred command list via
  beginExternal before creating the producer fence — resourceUpdate
  alone doesn't reach the GL stream, so the fence guarded nothing.
- HWCUDA: on partial interop-setup failure, recreate the sampler
  textures with their own storage; they could keep wrapping a VkImage
  cleanup() had just destroyed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8dWKnm8kgkuDaMMjapgFT
@jcelerier jcelerier force-pushed the split/gpu-interop branch from b445d5c to 6e0ed28 Compare July 7, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants