Skip to content

feat(export): add Turbo export mode using every core - #3479

Merged
richiemcilroy merged 9 commits into
mainfrom
feat/turbo-export
Oct 27, 2026
Merged

richiemcilroy merged 9 commits into
mainfrom
feat/turbo-export

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Turbo export

Adds a Turbo export toggle to the GPUI export page (MP4). It uses much more CPU, GPU and memory to finish 2-2.8x faster on an M4. The standard export is unchanged, byte for byte.

Turbo export toggle and resource warning on the export page

flowchart LR
    T[Timeline] --> C0[Chunk 0<br/>decoders, render, libx264]
    T --> C1[Chunk 1<br/>pre-roll, decoders, render, libx264]
    T --> CN[Chunk N]
    C0 -->|packets| S[Stitcher<br/>final MP4 + audio]
    C1 -->|spool file| S
    CN -->|spool file| S
    A[Audio worker<br/>whole timeline] --> S
    S -->|any failure but cancel| F[Standard export]
Loading

Why the standard export can't go faster on its own

  • The standard export encodes with VideoToolbox, and most Apple silicon has one hardware encode engine. On an M4, 1, 2, 3 and 4 concurrent standard exports take exactly 1x, 2x, 3x and 4x as long, so splitting the timeline alone gains nothing.
  • libx264 across all cores, with Cap's existing software export settings, runs 2-3x faster at matching quality.

How it works

  • crates/export/src/mp4/turbo.rs splits the timeline into contiguous chunks: one per 4 cores, up to 4, at least 4 s each. Each chunk gets its own decoders and render loop on its own thread, all sharing one wgpu device.
  • Chunk 0 encodes straight into the output, like a standard export. Later chunks encode into temporary packet spools, which are appended in order with their timestamps offset. Each chunk's SPS/PPS must match chunk 0's.
  • Later chunks render a short pre-roll (half a second, at least 16 frames) and discard it, so frame-to-frame state such as camera blur mask smoothing matches a standard export at the seams.
  • libx264 runs in constant quality with a VBV ceiling at the export's normal bitrate (SoftwareEncoderTuning in crates/enc-ffmpeg/src/video/h264.rs:45). The CRF for each preset and frame rate was calibrated so turbo matches or beats VideoToolbox's SSIM and PSNR against a lossless render, at a smaller file:
Preset 15 fps 30 fps 60 fps
Maximum 17 15 12
Social 20 18 15
Web 22 20 17
Potato 24 22 19
  • Audio renders once for the whole timeline on its own thread and is interleaved frame by frame, as in a standard export.
  • New outputs are always staged and moved into place on success. Cancelling, through the progress callback or the cancel flag, stops every chunk and leaves no output and no spool files.
  • Any turbo failure other than cancellation falls back to the standard export (export_prepared_turbo, crates/export/src/mp4.rs:216).
  • When the toggle is on, size and time estimates sample through the turbo encoder (estimate_turbo_export, crates/export/src/estimates.rs:136).
  • The toggle is offered on macOS with 8+ cores (turbo_available, apps/desktop-gpui/src/editor_export.rs:2978). Windows and Linux run the same pipeline in the verify suite, but their speed is not measured yet.

Results

Two real studio recordings (screen, camera, mic) on a Mac mini M4 (10 cores), median of 3 alternating runs with cargo run --release -p cap-export --example export-turbo-benchmark. main is the standard export. Turbo uses the default plan (2 chunks).

Recording, output main Turbo Speedup File size
38 s, 1080p30 Maximum 4.66 s 2.38 s 2.0x 12.2 → 10.0 MB
38 s, 1080p60 Maximum 9.09 s 3.67 s 2.5x 22.0 → 13.6 MB
38 s, 4K30 Maximum 16.71 s 5.95 s 2.8x 26.0 → 24.9 MB
38 s, 1080p30, camera blur 13.26 s 6.70 s 2.0x 11.8 → 9.4 MB
165 s, 1080p60 Social 24.67 s 10.08 s 2.4x 41.3 → 30.2 MB
38 s, 1080p30, Optimize file size 5.23 s 4.58 s 1.14x 5.0 → 5.0 MB
Resource (38 s) main Turbo
CPU time, 1080p60 7.3 s 28.0 s
Peak memory, 1080p 140 MB 520 MB
Peak memory, 4K 152 MB 1.48 GB
  • At 30 fps turbo is GPU-bound (90-100% GPU residency at max clock). At 60 fps it is CPU-bound in libx264. Chips with more GPU and CPU cores have more headroom, while the standard export stays capped by the one encode engine.

Quality against a lossless render of the same timeline (PSNR dB / SSIM):

Recording, output main Turbo
38 s, 1080p30 Maximum 50.92 / 0.99800 52.68 / 0.99811
38 s, 1080p60 Maximum 52.28 / 0.99824 53.01 / 0.99825
38 s, 4K30 Maximum 52.36 / 0.99835 54.75 / 0.99864
165 s, 1080p30 Maximum 49.04 / 0.99704 50.57 / 0.99765
165 s, 1080p60 Maximum 50.75 / 0.99777 50.89 / 0.99779
165 s, 1080p60 Social 47.32 / 0.99613 48.99 / 0.99684
165 s, 4K30 Maximum 50.13 / 0.99770 52.36 / 0.99817
  • Turbo output keeps the same frame count, duration, time base and BT.709 tags, and its audio is bit-identical to the standard export's. It is High profile (libx264) instead of Main (VideoToolbox), the same stream Cap already writes when it falls back to software encoding.
  • The cap-verify oracle (crates/verify/tests/export.rs:279) runs turbo split into 3 chunks, so seams land inside cuts, speed changes, reordering, transitions, camera scene changes and VFR holds, and checks every frame on both decoders. It also covers a chunk failure falling back to the standard export, and cancellation by callback and by flag.

Standard export unchanged

  • Standard exports from this branch are byte-identical to main (file size, decoded-frame hashes, audio MD5) on both recordings, at the same speed (9.11 s on both).
  • The only shared-code changes are a generic PacketSink bound on H264Encoder::queue_frame_reusable, an opt-in builder option, and the screenshot path passed explicitly to export_render_to_channel.
  • GPU readbacks now wait out their deadline for a map_async callback when the submission has finished but the callback has not run yet (mapped_result, crates/rendering/src/frame_pipeline.rs:113). wgpu runs map callbacks on whichever thread's poll collected them, so with several renderers on one device another thread can run a buffer's callback just after this thread's wait returns. Before, that readback failed and the export stopped with a lost frame (seen on the Windows WARP CI runners). A single renderer finds its callback already run, exactly as before.

RetriggerView in GreptileConfidence Score: 5/5

The latest changes appear safe to merge; the earlier audio-queue concern remains non-blocking.

Findings

  1. P2 Audio queues keep growing ▶
Fix with agent prompt
### Issue 1
crates/export/src/mp4/turbo.rs:652-655
Full Turbo exports queue every audio frame before encoding video. Both channels in `ExportAudioWorker` are unbounded, and `write_through` consumes only audio up to the current video frame. If audio runs faster than video, packets for the remaining timeline build up in memory. Long recordings can therefore retain far more audio than the writer currently needs.

Request audio through a bounded lookahead, or otherwise limit how far the audio worker can run ahead.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds an optional Turbo MP4 export mode that renders and encodes timeline chunks in parallel.

  • Turbo MP4 exports render and encode timeline chunks in parallel.
  • The MP4 export page offers a saved Turbo option on eligible Macs.
  • GPU readbacks wait for map callbacks after shared-device polling.

Reviews (3) · Last reviewed commit: "test(verify): cover a failed turbo expor..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cap-status Ready Ready Preview Oct 27, 2026 3:49pm UTC

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 1 potential issue (1 medium).

View 2 other findings in Capy.

Open in Capy

Comment thread crates/export/src/mp4.rs Resolved
Comment thread crates/export/src/mp4/turbo.rs Resolved
Comment on lines +648 to +651
if !sampled {
for frame in 0..total_frames {
worker.request(frame, frame);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Audio queues keep growing

Full Turbo exports queue every audio frame before encoding video. Both channels in ExportAudioWorker are unbounded, and write_through consumes only audio up to the current video frame. If audio runs faster than video, packets for the remaining timeline build up in memory. Long recordings can therefore retain far more audio than the writer currently needs.

Request audio through a bounded lookahead, or otherwise limit how far the audio worker can run ahead.

Knowledge Base Used: Export and editor rendering

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/export/src/mp4/turbo.rs
Line: 648-651

Comment:
**Audio queues keep growing**

Full Turbo exports queue every audio frame before encoding video. Both channels in `ExportAudioWorker` are unbounded, and `write_through` consumes only audio up to the current video frame. If audio runs faster than video, packets for the remaining timeline build up in memory. Long recordings can therefore retain far more audio than the writer currently needs.

Request audio through a bounded lookahead, or otherwise limit how far the audio worker can run ahead.

**Knowledge Base Used:** [Export and editor rendering](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/export-and-editor-rendering.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread crates/export/src/mp4/turbo.rs Resolved
@blacksmith-sh

This comment has been minimized.

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 1 potential issue (1 medium).

View 1 other finding in Capy.

Open in Capy

Comment thread crates/export/src/mp4.rs Resolved
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@richiemcilroy
richiemcilroy merged commit 0a35a88 into main Oct 27, 2026
68 of 69 checks passed

This branch was successfully deployed

2 active deployments
Preview – cap-web — b4c097b3 Deployed Oct 27, 2026 by vercel[bot]
Preview – cap-status — b4c097b3 Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant