split/gpu interop#2078
Closed
jcelerier wants to merge 156 commits into
Closed
Conversation
jcelerier
commented
Jun 14, 2026
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
851f361 to
b1c6d32
Compare
…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.
…remental scene rebuild
…oundtrip - New per-cell `trans`/`wire` columns backed by the device testability accessors: a dmabuf-requested cell that runs on the readback fallback reports `dma!fb -> shm` and PASS(fallback) instead of masquerading as zero-copy; the transport is sampled before stop() (which resets it). - Vulkan DMA-BUF output cells enabled (were GL-only) — this is the configuration that proves true zero-copy on Qt >= 6.6. - Match production startup: QT_XCB_GL_INTEGRATION=xcb_egl on Linux (src/app/main.cpp does the same; MinimalGUIApplication bypasses it), respecting a pre-set environment override. - SCORE_PWRT_DUMP=<prefix> dumps the first verified frame per cell for visual diffing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…vendor pin nvidia_p2p_get_pages only pins CUDA-allocator memory; pinning CUDA-imported GL buffers can never work. Give the vendor a CUDA-owned bounce buffer per slot (cuda_p2p_alloc_buffer + SYNC_MEMOPS) and bridge to the GL storage ring with one on-GPU DtoD copy per frame — frames never touch sysmem. Because pin return codes lie on IOMMU-translated platforms (writes silently drop), VendorDmaRegistrar grows an optional verifyTransfer content probe; GpuDirectOutput runs it at init and falls back honestly. Also: GL register-only mode in GpuRingBuffer for the capture direction, and a teardown-order fix in DMACaptureInputNode (release the strategy — which unpins through the card — before destroying the backend that owns the card). Validated end-to-end on 2x Kona 5 + Quadro RTX 4000 (iommu=pt): RDMA-GL/T3 engages both directions, 3-firmware sweep, 44-52 dB, ~87 ms vs ~150 ms CPU. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
A factory returning nullptr (missing hardware, headless environment) put a null unique_ptr in ApplicationComponents::panels, crashing every later panels() iteration (e.g. Explorer::findDeviceExplorerWidgetInstance from Scenario::SearchWidget). Warn and skip instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…null Same defect as registerPanel: a declining PanelDelegateFactory put a null unique_ptr in ApplicationComponents::panels and setModel dereferenced it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
ctx.panels()'s indirect iteration dereferences every slot unconditionally, which aborts on hardened stdlibs (_GLIBCXX_ASSERTIONS) when a slot is empty; findPanel<T>() does the same lookup null-safely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ownload The first-run library-download question is modal; in non-interactive sessions (tests/CI) it blocks the main thread forever. Same escape hatch refresh() already uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…pplication The test scaffolding deletes the QApplication in the destructor BODY while score::Settings is a value member destroyed after it — its QObject settings models then hit freed application state (SEGV in QObject::~QObject on Qt 6.11; silently tolerated on older Qt). Settings grows an idempotent teardownModels() that both destructors invoke before deleting the app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
staticVulkanInstance() set the invalid flag on create() failure but still returned the pointer to the first caller — Graph's Vulkan->GL fallback check passed and QRhi::create crashed on the dead instance (hit on any platform where the QPA lacks Vulkan, e.g. QT_QPA_PLATFORM=offscreen). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…t current dvpCloseGLContext poisons the GL context it was bound to. When init failed AFTER binding DVP (e.g. no fragment encoder for the wire format), release() closed DVP and the caller kept rendering on the same context — SEGV in QRhi::endOffscreenFrame a few frames later. Validate encoder/sizes/native handles before touching DVP so failure paths have no side effects, and make the owning context current in release() (required by DVP, previously only accidentally true). Repro: AJARoundtrip --interop dvp with a DVP-capable cell followed by an RGBA8 cell on the Quadro; 12-cell stress now exits with a full matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
- New R210Decoder (DeckLink bmdFormat10BitRGB SDI 4:4:4 wire), exact inverse of PackedRGBEncoder::r210be(); wired into makeWireDecoder. - v210 encode+decode now size rows on the padded wire stride (((w+47)/48)*128 bytes) instead of a tight (w/6)*4 texel row: at 1280 and 2048-DCI widths the old math truncated the tail group and skewed every row against the wire, collapsing roundtrip PSNR to ~31 dB. Compute encoder edge-clamps tail-group fetches (texelFetch does not). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
… sequence Three findings from a standalone two-context GLX probe against libdvp 1.69: - dvpInitGLContext(0) makes libdvp create its own internal GL context; only the FIRST init'd context in a process can transfer — a second one (output node + capture node, each with their own QRhi) gets DVP_STATUS_ERROR from every dvpMemcpyLined while setup calls all succeed. Init with DVP_DEVICE_FLAGS_SHARE_APP_CONTEXT (1) instead. - Texture-destination copies follow Blender/UPBGE's shape: EndAPI before the DVP scope, buffer sync as CPU-signalled source acquire, a per-texture sync signalled on completion, WaitAPI after. Textures now get their own sync slot at registration. - The upload runs on the render thread via the SPSC slot publisher (capture thread only publishes); the strategy allocates its own RenderTarget-flagged texture and hands it to the node via the strategy-owned texture protocol. AJA roundtrip --interop dvp --rx gpu: DVP-GL engaged both directions, 1080p5994 YCbCr8/v210 PASS 51.28/51.92 dB, zero transfer errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
PacedFramePump waited one vendor tick before every submit, serialising tick-period + DMA: throughput capped at 1/(VBI + transfer) — measured 46 fps at UHD60 and 24 fps at 8K on Kona 5 while every stage had head- room. When the vendor provides canAccept (AJA AutoCirculate), stuff the card's ring back-to-back and block on the tick only under back-pressure; vendors with null canAccept (DeckLink free-pool, Bluefish sync, Deltacast blocking submit) keep the one-tick-per-submit contract. Also stop discarding an already-drained frame on a canAccept blip. StageProfiler: SCORE_AJA_PROFILE=1 wall-time attribution per pipeline stage (paint/upload/encoder-sync/DtoD/AC-transfer); instruments GpuDirectOutput's sync + bounce copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
VulkanCudaBounce pairs, per slot, a Vulkan-owned exportable buffer (CUDA-imported through the SUPPORTED interop direction) with a pinned cuMemAlloc bounce the vendor DMAs; flushToBounce/flushFromBounce bridge the two with one DtoD. Hard-won constraints, all measured on hardware: - a CUDA-VMM fd imported INTO Vulkan allocates cleanly but does not alias — writes land in private memory (wrong interop direction); - vkGetMemoryFdPropertiesKHR is spec-forbidden for OPAQUE_FD handles (VUID 00674) — the import helper now skips the query for opaque types; - multi-GPU: the Vulkan physical device must be the CUDA device; createSharedVulkanDevice now honours QT_VK_PHYSICAL_DEVICE_INDEX (QRhi's own env) instead of hardcoding physDevs[0]. Also: GpuRingBuffer Vulkan plain mode (QRhi storage buffers + native VkBuffer, no bridge import), cuda_p2p_copy_dtod_2d (pitched flat DtoD for row-padded linear images), and CUDA VMM fd export plumbing (cuMemExportToShareableHandle + CudaVmmAllocation::exportPosixFd, probe-verified pinnable — kept as API surface for future seams). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Replace the finish()+finish() double drain in prepareNextFrame with GPU ordering: the copy frame's pre-barrier covers all earlier submission-order work (no host wait after encode), and an empty queue submit signals an exported timeline semaphore that CUDA waits on-stream — the single host block left is the final DtoD stream sync. 1080p5994 Vulkan rx-gpu latency: 105.8 -> 85.6 ms (GL parity), zero lost/repeated frames. Gated on the device having been created WITH timelineSemaphore enabled: score's shared-device path (Qt >= 6.6) enables every queried feature and now reports it (SharedVulkanDevice::timelineSemaphores -> vkinterop::setDeviceTimelineSemaphoresEnabled). QRhi-created devices (Qt 6.4) never enable the feature — the validation layer confirmed using it there is a spec violation the driver merely tolerates — so they keep the finish() fallback. Queue-drain before semaphore destroy fixes the teardown VUID. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…llapse (d) Adds the CUDA driver-API surface for exporting a mapped device VA range as a dma-buf fd — the cross-API handle type Vulkan/GL can import as an aliasing image, i.e. the export half of AJA "capture-collapse" option (d) (make the AutoCirculate DMA target itself the sampled resource, deleting the bounce). - CudaFunctions: resolve cuMemGetHandleForAddressRange; add CU_MEM_RANGE_HANDLE_TYPE_DMA_BUF_FD + the PCIE mapping flag + CU_DEVICE_ATTRIBUTE_DMA_BUF_SUPPORTED; add dmaBufExportSupported(device). - CudaVmmAllocation::exportDmaBufFd(): dma-buf export of the mapped range, distinct from exportPosixFd() (opaque fd, which does not alias into Vulkan). Gated + honest: option (d) is NOT wired into the capture strategies. The rdmaprobe4 spike showed CU_DEVICE_ATTRIBUTE_DMA_BUF_SUPPORTED = 0 on this rig's Quadro RTX 4000 and GeForce RTX 4090 (driver 595.71.05), so cuMemGetHandleForAddressRange(DMA_BUF_FD) returns CUDA_ERROR_NOT_SUPPORTED for both VMM and legacy cuMemAlloc. The Quadro is the only GPU here with GPUDIRECT_RDMA=1 (required for the AJA pin) yet cannot export a dma-buf, so the both-pinnable-and-exportable requirement is unsatisfiable and the bounce path is retained. This plumbing lets a future DMA_BUF-capable GPU wire up (d) mechanically, gated on dmaBufExportSupported(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Adds the plumbing for AJA capture-collapse option (a) and the Vulkan double-buffer correctness fix (see aja-capture-collapse-analysis §7). CudaFunctions: dlopen two more libcuda driver-API symbols — cuGraphicsGLRegisterImage + cuGraphicsSubResourceGetMappedArray (OPTIONAL, null-checked, no CUDA Toolkit needed). CudaP2PBridge: cuda_p2p_register_gl_image / cuda_p2p_gl_write_image — register a GL texture and cuMemcpy2D a device bounce straight into its level-0 array, map/unmap per frame for GL coherency (mirrors the register_gl_buffer / gl_write_buffer pair, array dst instead of buffer dst). GpuDirectCaptureStrategy: add currentTexture() (defaults to outputTexture()) so a double-buffering strategy can publish a fresh sampled texture per frame. DMACaptureInputNode: after acquireForRender, rebind every pass's SRB to currentTexture() when it changes (replaceTexture/updateResources — no pipeline rebuild). No-op for single-texture strategies (constant currentTexture()). Validated on the AJA rig (Kona5, Quadro RTX 4000 RDMA), connector pair 3: GL RDMA-GL/T3 and Vulkan RDMA-Vulkan/T3 both PASS at 1080p5994 + UHD_SL_60, YCbCr8 + v210, PSNR held at baseline (52.26 / 51.92 dB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…module The open NVIDIA kernel module (595.71.05) unblocks the CUDA dma-buf EXPORT half of capture-collapse option (d): DMA_BUF_SUPPORTED=1 on the Quadro, cuMemGetHandleForAddressRange(DMA_BUF_FD) returns a valid fd, and DMABufferLock(inRDMA=true) now succeeds directly on a VMM allocation. But option (d) still cannot engage: the NVIDIA graphics-side IMPORT of the CUDA-exported dma-buf is unsupported. Vulkan advertises no VK_EXT_external_memory_dma_buf (only drm_format_modifier), and EGL rejects a CUDA-VMM dma-buf with EGL_BAD_ALLOC. The CUDA-bounce path (OPAQUE_FD image + separate DMABufferLock'd bounce + per-frame DtoD) is retained. The doc comment on exportDmaBufFd() previously blamed the export returning CUDA_ERROR_NOT_SUPPORTED; that is stale on the open module. Corrected to name the real, import-side blocker. Verified end-to-end by scratchpad/rdmaprobe4 (Vulkan, fails at import) and rdmaprobe4gl (GL/EGL, fails at import); the CUDA-export + AJA-pin + card->GPU DMA half content-verifies at 100%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…Frame
M1: hoist the verbatim-duplicated one-time UNDEFINED->GENERAL image layout
transition (copied into 3 vendor RDMA shims) into a single
score::gfx::vkinterop::transitionImageLayout() in VkExternalMemoryHelpers.
Conservative access/stage masks (superset of the shader-read capture and
transfer-write output uses) + the existing vkQueueWaitIdle keep it
behaviour-identical.
M5: hoist the BAR1 slot-count policy ("frame >= 32MB ? reduced : full"),
duplicated across the GL/Vulkan capture and output RDMA shims, into
interop::rdmaRingDepthForFrame() (new RdmaRingDepth.hpp).
Additive: old vendor code repointed onto the new generics; no rename yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Add score::gfx::interop::CpuStagedCapture<Policy>: the host-staged capture strategy (3-slot page-locked sysmem ring + portable QRhi upload, optional raw-GL fast path) that the five vendor CPU captures each re-implemented with only the name/namespace differing. A Policy supplies the deltas (has_dma_lock for AJA's DMABufferLock, has_gl_fast_path for AJA's raw glTexSubImage2D, the log tag/env/name strings). Behaviour-identical to each original: AJA keeps its tightly-packed texW*4 stride, the CPU-only vendors keep their raster-pitch (frameByteSize/height) stride. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
The AJA and Deltacast Vulkan RDMA paths share only the raw-Vulkan-handle acquisition out of QRhiVulkanNativeHandles + score's shared QVulkanInstance (copied verbatim into 3 shims). Hoist it into interop::acquireVulkanRhiContext (VulkanRhiContext.hpp). Deeper M3/M4 unification (one transfer base for both vendors) is DEFERRED and documented in the header: the two paths diverge by design — AJA uses per-slot LINEAR images imported as flat CUDA buffers (copy dtod-2d) + an exportable VkBuffer bounce pinned via DMABufferLock; Deltacast uses a single OPTIMAL image imported as a CUDA array (copy buffer<->array) backed by RdmaGpuBuffer. AJA's own comment notes OPTIMAL-as-array "scrambles per-tile on this driver", so a shared transfer base is a redesign, not a mechanical move, and would need Vulkan-RDMA hardware validation the GL smoke test doesn't provide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Rename the misleading "GpuDirect" generics (the interfaces are implemented by
every tier, including CPU staging — not GPU-direct-specific):
GpuDirectStrategy -> VideoOutputStrategy
GpuDirectStrategyConfig -> VideoOutputStrategyConfig
GpuDirectCaptureStrategy -> VideoCaptureStrategy
GpuDirectCaptureStrategyConfig -> VideoCaptureStrategyConfig
GpuDirectCaptureSlotRing -> VideoCaptureSlotRing
selectGpuDirectStrategy -> selectVideoOutputStrategy
GpuDirectOutput -> RdmaVideoOutput
HostStagedOutput -> CpuStagedVideoOutput
GpuRingBuffer -> ImportedGpuBufferRing (a ring of QRhi buffers
imported into CUDA, vs RdmaGpuBuffer's native
RDMA VRAM)
Files renamed to match (file name == class name). Plugin-internal consumers
repointed. The old names live on as transitional `using` aliases (in the new
headers + the two forward-declaring nodes) plus stub old-path headers, so the
out-of-tree addon still builds unchanged until it repoints (Phase 3). Aliases +
stubs are removed in Phase 5. No behaviour change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ake cleanup The addon (its master) has repointed to the new names (Phases 3-4), so remove the Phase-2 scaffolding: the `using` aliases in the renamed headers, the forward-decl aliases in DMACaptureInputNode, and the six stub old-path headers (GpuDirectStrategy/GpuDirectCaptureStrategy/GpuDirectStrategySelect/ GpuDirectOutput/HostStagedOutput/GpuRingBuffer). Plugin CMake source list cleaned. The interop layer now exposes only the new, accurate names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ept) "P2P" was a misnomer: this CUDA driver-API layer also does VMM alloc, GL/Vulkan/D3D external-memory import, semaphores, DtoD copies and dma-buf export. Rename the file, the exported C ABI (cuda_p2p_* -> cuda_interop_*), the handle/enum/error types (CudaP2P* -> CudaInterop*, CUDA_P2P_* -> CUDA_INTEROP_*) and the export macro. All score-plugin-gfx call sites and CMake updated. A transitional CudaP2PBridge.h shim aliases every old name to the new one so the not-yet-repointed AJA addon keeps building; it is removed in a follow-up once the addon is repointed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
The AJA addon is now repointed to <Gfx/Graph/interop/CudaInterop.h> and the cuda_interop_* names, so the backward-compat alias header is no longer needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ng clocks (Phase 1) Introduce a RenderClock abstraction that owns HOW render ticks are delivered to an output and guarantees the render callback runs on the render thread. This is Phase 1 of the render-clock / genlock refactor: a pure, zero-behaviour-change extraction that wraps the two clocks score already had. - RenderClock (abstract): start(tick)/stop(), kind(). - TimerClock wraps clock #2 (the default): one shared HighResolutionTimer at manualRenderingRate + the coalesced set of outputs on it, mirroring the old GfxContext m_manualTimers entry. The per-output fan-out closure is the old on_manual_timer body; the timer is acquired from the same per-context score::Timers pool with the same Qt::QueuedConnection wiring. - DisplayVSyncClock wraps clock #1: forwards the tick into OutputNode::setVSyncCallback (the swap-chain vsync push path) and clears it on stop(). GfxContext now holds vector<TimerClock> + a DisplayVSyncClock instead of the inline timer->set<OutputNode*> map and the raw setVSyncCallback call. The no_vsync graph-update pump and the watchdog timer are unchanged (they are not render clocks). Coalescing, rates, timer lifetimes and vsync semantics are all byte-identical. Validated behaviour unchanged: - PipewireRoundtrip --only s2s: all 14 cells PASS. - AJARoundtrip 1080p5994 YCbCr8 rdma rx-gpu (Kona5-8K, conn pair 3): PASS 52.26 dB, RDMA-GL/T3 engaged, out-ring drops still present (timer path). Phase 2 (opt-in AjaGenlockClock : ExternalGenlockClock driven by the card VBI) is not part of this commit. RFC: ~/ossia/rdma-test-utils/render-clock-rfc.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Add the hardware-genlock render clock (the pull facet of the RenderClock model from Phase 1): a dedicated tick thread blocks on an injected waitForNextTick() (a card's output VBI) and marshals render() onto the render thread via Qt::BlockingQueuedConnection — blocking so the next VBI wait begins only after the frame is rendered+pushed, keeping render phase-locked to the card and never bursting ahead. The pull facet is injected as a std::function, so the gfx layer stays vendor-SDK-free. DirectVideoOutputBackend gains an optional genlockTickSource() (default empty = no hardware genlock; the node keeps its timer clock); DirectVideoOutputNode forwards it. Nothing here is engaged unless a caller builds an ExternalGenlockClock — the default paths are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
3fb5a92 to
17ef50a
Compare
~Minimal[GUI]Application ran QApplication::processEvents() AFTER delete m_presenter, dispatching deferred slots queued during plugin load (Scenario::SearchWidget deferred-init → findDeviceExplorerWidgetInstance) that read the presenter-owned application context — a heap-use-after-free, and the SIGSEGV the non-sanitized harnesses hit on shutdown. Drain the event queue BEFORE deleting the presenter; remaining deferred deletes still flush via ~QApplication. Found by ASan on the DeckLink/Magewell harnesses; --list now exits cleanly (was SIGSEGV). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Member
Author
|
superseded by #2109 |
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.