This release takes the complete animated-scene path established in 0.8 and restructures its frame preparation and command recording so a second CPU participant can record safely without sharing mutable renderer state. The thread arrives last: scene mutation, resource compilation, frame ownership, recording input, and measurement boundaries are made explicit before parallel recording is introduced.
A phase-level Release benchmark now separates transform updates, draw-list construction, recording-input compilation, command-pool resets, primary and secondary command recording, secondary execution, submission, and blocking presentation work. A direct-primary attribution control distinguishes the cost of secondary-command structure from the work common to both paths.
Redundant draw state is cached independently inside each command buffer. Vertex and index buffers are rebound only when their handles change, while sampled-image descriptors are pushed only when the sampler or image view changes. Mixed-resource fixtures exercise both redundant-state skips and real binding changes.
Scene registration is now an insertion invariant. Roots and children receive stable identities when they enter the scene, removing the structural registration sweep from per-frame transform updates. Incremental insertion preserves geometric vector growth and the parent-before-child ordering needed by the dense registry.
Resource compilation no longer borrows frame state. ResourceCompiler owns its upload context and produces a complete CompiledResourceGraph, which CompiledResources accepts only after compilation succeeds. Mesh vertex layouts are validated against the selected pipeline, and recording packets contain resolved plain handles rather than paths back to resource owners.
Scene draw lists are arena-backed immutable views. The application builds and freezes them before entering the renderer, and the arena reuses its high-water allocation across frames. RecordingInputCompiler resolves those views into a non-copyable, non-movable RecordingInput whose construction and lifetime are controlled by the compiler.
Submission slots and recording contexts are now separate owners. Two frame slots cycle independently of swapchain image indices, each with its own uniforms, fences, command state, and depth attachment. The application still prepares one frame at a time, while submitted GPU work from two slots may remain outstanding.
The platform boundary now exposes a Vulkan-free FramebufferExtent. Swapchain code receives the extent value it needs rather than borrowing the Window, and framebuffer Y inversion has moved from the projection matrix into a checked negative-height viewport. Vulkan instance-extension discovery and validation messenger construction have also moved from core into the renderer’s internal Vulkan support.
Secondary command recording can now use the coordinator and one persistent helper thread. Each participant owns and resets its own command pool, records a contiguous span of immutable packets into its own secondary command buffer, and establishes an independent draw-binding cache. The primary executes completed secondaries in draw order inside one dynamic-rendering instance.
The helper has a deliberately narrow role rather than forming a general job system. Requests and completion use C++ synchronization primitives, failures are captured and rethrown after the coordinator has joined the work, and destruction requires the helper to be idle. The coordinator polls completion for a measured 50-microsecond budget before falling back to an atomic wait, removing the observed completion wake-up tail without spinning throughout the inter-frame interval.
Parallel recording is selected by workload. Automatic mode uses two participants only when the synthetic workload provides at least 5,000 draws per participant—the measured boundary that benefited both decision-bearing implementations. Smaller workloads remain on the coordinator. The --recording-threads option remains a diagnostic override for reproducing measurements and forcing both paths under validation.
The final measurements show why that policy matters. Two-participant active work measured 1.374x and 1.700x faster on Lavapipe at 1,000 and 10,000 draws. On an NVIDIA hardware driver it measured 0.912x and 1.209x: a resolved regression at 1,000 draws but a clear improvement at 10,000. Retention is therefore justified by Lavapipe satisfying the registered materialization criterion at both workloads, not by a universal cross-driver win.
Participant CPU durations are reported as diagnostics rather than added together, because their work overlaps. Active work uses the complete coordinator-observed recording region, including dispatch and join. Aggregate CPU work may increase while critical-path elapsed time falls. On the measured NVIDIA workload, the remaining serial fraction—dominated by immutable snapshot construction—limits the ideal unlimited-worker speedup to approximately 1.74x.
Included in this release
- Dynamic-rendering inheritance for secondary command buffers.
- Phase-level CPU benchmarking with fixed workloads and animation steps.
- Direct-primary command-structure attribution.
- Independent reset, recording, execution, submission, and blocking-time
measurements. - Command-buffer-local vertex, index, and sampled-image binding caches.
- Mixed-resource correctness coverage for cached and changed bindings.
- Stable scene registration as an insertion invariant.
- Geometric registry growth for incremental scene construction.
- ResourceCompiler-owned upload state.
- Strongly committed CompiledResourceGraph replacement.
- Pipeline vertex-layout validation during resource compilation.
- Two presentation-independent frame slots.
- Per-slot uniforms, fences, command contexts, and depth attachments.
- Separation of frame submission state from recording ownership.
- A Vulkan-free FramebufferExtent platform boundary.
- Negative-height Vulkan viewport construction with compile-time checks.
- Vulkan instance support contained inside the renderer.
- Arena-backed immutable SceneDrawList views.
- Reuse of draw-list arena high-water storage.
- Application-owned camera input supplied with each frame.
- Compiler-produced immutable RecordingInput values.
- Resolved plain-handle recording packets.
- Transient command pools matching their per-frame usage.
- Independent recording contexts for the coordinator and helper.
- Persistent single-purpose secondary-recording worker.
- Ordered multi-secondary execution inside one rendering instance.
- Exception-safe helper dispatch, joining, and failure propagation.
- Bounded completion polling with atomic-wait fallback.
- Per-participant reset and recording diagnostics.
- Coordinator-observed worker-region timing that includes threading overhead.
- Automatic one- or two-participant workload selection.
- A 5,000-draw minimum per recording participant.
- Diagnostic one- and two-thread overrides for reproducible measurements.
- Automatic-policy coverage on both sides of the split threshold.
- Standard and synchronization-validation coverage for split recording.
- ThreadSanitizer verification of the concurrent immutable-input path.
- 77 CTest registrations covering device-free and Vulkan scenarios.
- Lavapipe CI measurements plus NVIDIA hardware-driver measurements.
- Project and package versions updated to 0.9.0.
Version 0.9 deliberately keeps CPU preparation single-frame, queue submission and presentation serial, and scene identities append-only. It does not add a general scheduler, parallel physics, additional render passes, shadow mapping, or a render graph. Its purpose is to prove that explicit ownership and immutable recording boundaries can make multithreaded command recording a small scheduling feature rather than a renderer rewrite.