Repository navigation
perf(rendering): recycle export RGBA readbacks and cache NV12 bind groups per texture - #2851
Merged
Merged
Conversation
…bind groups per source texture
This comment has been minimized.
This comment has been minimized.
richiemcilroy
marked this pull request as ready for review
October 22, 2026 06:22
Member
Author
|
@greptileai review |
# Conflicts: # crates/rendering/src/frame_pipeline.rs
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Member
Author
|
@greptileai review |
Comment on lines
+1660
to
+1662
| let mut data = Vec::new(); | ||
| reserve_readback(&mut data, active_bytes, frame_number)?; | ||
| Ok(data) |
Contributor
There was a problem hiding this comment.
Production readback now calls reserve_readback directly, leaving allocate_readback used only inside #[cfg(test)]. The helper still compiles in normal builds, so it triggers dead_code. The workspace Clippy job rejects that warning with -D warnings.
Add #[cfg(test)] to the helper, or remove it and test reserve_readback directly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/src/frame_pipeline.rs
Line: 1660-1662
Comment:
**Unused helper breaks Clippy**
Production readback now calls `reserve_readback` directly, leaving `allocate_readback` used only inside `#[cfg(test)]`. The helper still compiles in normal builds, so it triggers `dead_code`. The workspace Clippy job rejects that warning with `-D warnings`.
Add `#[cfg(test)]` to the helper, or remove it and test `reserve_readback` directly.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Member
Author
|
@greptileai review |
This branch was successfully deployed
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.
Two low-risk export allocation fixes in
crates/rendering/src/frame_pipeline.rs. Output is unchanged. Identity checks and numbers are below.RGBA readback buffers (ProRes MOV, GIF, software-adapter MP4). Each RGBA readback copied into a new
Vecofpadded_bytes_per_row * height + 24bytes (about 33 MB at 4K) and dropped it after the encoder copied it.PipelinedGpuReadbacknow keeps a clone of up to 8 of theArc<Vec<u8>>frames it hands out, and the next readback reuses one once every consumer has dropped it (Arc::try_unwrap).RenderedFramekeeps its shape, so the MOV encoder, GIF encoder and software NV12 conversion need no changes. Only export sessions (export_deadlines) recycle, so editor, thumbnail and screenshot sessions keep no extra memory. The pool grows only when every pooled frame is still in use, so it holds no more frames than the export already had in flight, and it drops with the session.NV12 converter bind groups. The converter keyed its bind group on the address of the
&wgpu::Textureit was given and rebuilt two identical bind groups plus a view whenever that changed. Colour grade, bokeh and masks swap the session texture an odd number of times per frame, so those exports rebuilt it every frame. It now caches one bind group per source texture identity, two entries, the same wayRgbaToBgraSurfaceConverter::source_bind_groupdoes. Entries are still cleared when the NV12 buffer is recreated. This runs on the macOS VideoToolbox surface path too, but only bind group creation changes there: same shader, same buffers, same copies.Not in this PR: moving the ProRes
swscaleRGBA to YUVA444P10 conversion to the GPU.Overlap with #2770. #2770 replaces
Vec::with_capacity(active_bytes + 24)inPendingReadback::waitwith a fallibleallocate_readback. The two PRs conflict on that line. Whichever merges second should applytry_reserve_exactto the recycledVec(it is cleared, so the capacity math is the same).PendingReadback::waitnow takes the session's recycled frames, so the standalonePendingReadbackin #2770's cleanup test needs&mut Default::default()as a second argument.Tests: unit tests for reuse after release and for the pool bound, and a macOS GPU test that alternates two source textures through one converter. It checks every output against a fresh converter and checks that the cached bind groups stay the same after the first pair.
Measurements
Scratch A/B:
ci/export-prores-pool-ab(this head plus counters) againstci/export-prores-pool-base(main plus the same counters). Synthetictestsrc2recording, colour background, padding and rounding. Themaskproject adds one full-length sensitive mask. Counters: bytes copied and freshly allocated per RGBA readback, time from allocation through copy, NV12 bind groups created, and process peak RSS, CPU time and page faults.Output identity (stream md5 and decoded framemd5, base vs head, plus a base vs base control)
ProRes MOV, macOS Metal (
mov-export-benchmark, 300 frames, median of 6 runs)ProRes MOV, Linux llvmpipe (
export-cli, 180 frames at 3840x2160, median of 8 interleaved runs)ProRes MOV, Windows WARP (
mov-export-benchmark, 300 frames, 3 runs each)NV12 bind groups, macOS Metal (VideoToolbox MP4, 1920x1080, 180 frames)
Export wall time with the mask did not measurably change (887 ms vs 881 ms). The saving is small CPU work per frame, as expected.
MP4 software NV12 path, Linux llvmpipe (1920x1080, 180 frames, median of 8 interleaved runs)
The result is neutral within noise. Wall time was 3,501 vs 3,646 ms (plain) and 4,923 vs 4,338 ms (mask). glibc already reuses a freed 8 MB chunk at this size, so pooling mostly matters at 4K and on macOS and Windows.
CI
The required checks pass, except macOS Engine verify. It fails on
crash/recorder-kill/{inproc,oop}/static-screen(fragment-span), the known failure on main. Recording code is unchanged here.

Confidence Score: 5/5
The PR appears safe to merge.Summary
Reuses released RGBA export buffers and caches NV12 bind groups for two source textures.
Reviews (3) · Last reviewed commit: "fix(rendering): test reserve_readback di..." · Reviewed by Greptile