Repository navigation
Conversation
Skip swscale for same-size BGRZ, BGRA, RGBA, and RGBZ frames when the encoder output is YUV420P or NV12. Chroma is the 2x2 box average. AVX2 is selected at runtime, with a scalar fallback.
The BT.709 kernel runs only when width and height are both even. Odd frames stay on the existing swscale path.
|
@cursoragent is attempting to deploy a commit to the Cap Software Inc Team on Vercel. A member of the Team first needs to authorize it. |
| _mm_prefetch(row0.add(128) as *const i8, _MM_HINT_T0); | ||
| _mm_prefetch(row1.add(128) as *const i8, _MM_HINT_T0); |
There was a problem hiding this comment.
Prefetch pointers leave the buffer
kernel8 uses ptr.add(128) for prefetch addresses, but that operation requires the resulting pointer to stay within the allocation. The tight 8×2 test buffer has only 64 bytes, so both addresses exceed it. A non-faulting CPU prefetch does not make this Rust pointer arithmetic valid. Use wrapping_add(128) or bound the prefetch.
| _mm_prefetch(row0.add(128) as *const i8, _MM_HINT_T0); | |
| _mm_prefetch(row1.add(128) as *const i8, _MM_HINT_T0); | |
| _mm_prefetch(row0.wrapping_add(128) as *const i8, _MM_HINT_T0); | |
| _mm_prefetch(row1.wrapping_add(128) as *const i8, _MM_HINT_T0); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/enc-ffmpeg/src/video/rgb_to_yuv.rs
Line: 364-365
Comment:
**Prefetch pointers leave the buffer**
`kernel8` uses `ptr.add(128)` for prefetch addresses, but that operation requires the resulting pointer to stay within the allocation. The tight 8×2 test buffer has only 64 bytes, so both addresses exceed it. A non-faulting CPU prefetch does not make this Rust pointer arithmetic valid. Use `wrapping_add(128)` or bound the prefetch.
```suggestion
_mm_prefetch(row0.wrapping_add(128) as *const i8, _MM_HINT_T0);
_mm_prefetch(row1.wrapping_add(128) as *const i8, _MM_HINT_T0);
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in 4e205d7 by removing the prefetch. Release medians with no prefetch, add(128) and wrapping_add(128) were within about 1% (1080p 1.135 / 1.130 / 1.128 ms, 4K 4.520 / 4.494 / 4.513 ms), so dropping it also drops the pointer arithmetic.
| // SAFETY: `try_convert` checked each plane covers the active rectangle. | ||
| unsafe { convert_range(job, 0, job.width, 0, job.height) }; |
There was a problem hiding this comment.
The shortcut replaces swscale on Apple ARM and CPUs without AVX2 too. However, the benchmark selects the kernel automatically, so the reported AVX2 machine never measures convert_range. If the scalar loop is slower than swscale, these machines could use more CPU or drop recording frames. Add a scalar-versus-swscale benchmark, including an Apple ARM result, and keep swscale where the scalar loop loses.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/enc-ffmpeg/src/video/rgb_to_yuv.rs
Line: 126-127
Comment:
**Scalar speed is unmeasured**
The shortcut replaces swscale on Apple ARM and CPUs without AVX2 too. However, the benchmark selects the kernel automatically, so the reported AVX2 machine never measures `convert_range`. If the scalar loop is slower than swscale, these machines could use more CPU or drop recording frames. Add a scalar-versus-swscale benchmark, including an Apple ARM result, and keep swscale where the scalar loop loses.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 4e205d7. Measured on x86, the release scalar loop is slower than swscale (1080p 4.104 ms vs 3.100 ms), and I have no aarch64 numbers. try_convert now returns false unless AVX2 is detected at runtime, so Apple ARM and non-AVX2 CPUs keep swscale.
|
Thanks @kyzer1023, nice work. I checked the 16.16 coefficients against BT.709 (Kr 0.2126, Kb 0.0722, scaled by 219/255 and 224/255; each chroma row sums to 0). I also checked the round-half-up biases, the luma and chroma clamps, and that odd sizes and scaling still fall back to swscale. All of that is correct, and the encode/decode test confirms the stream is still tagged BT.709 limited. Two small changes before we merge: 1) In crates/enc-ffmpeg/src/video/rgb_to_yuv.rs:364-365, |
The prefetch past the end of the row is gone; it did not beat a plain load. try_convert returns false without runtime AVX2 so swscale stays on that path. ci-verify was compiling this workspace crate at opt-level 0, where the AVX2 kernel takes about 180 ms per 1080p frame and the Windows real-capture job drops the screen. opt-level 1 keeps it under 2 ms.
|
Thanks @richiemcilroy. In 4e205d7:
The windows-2022 real-capture failure was caused by this PR, not flakiness. Windows capture delivers RGBA, which takes this path into software libx264. Release bench: 2.79x at 720p, 2.54x at 1080p, 3.06x at 4K (n=31). The description is updated. @greptileai review |
| #[cfg(not(target_arch = "x86_64"))] | ||
| let _ = job; |
There was a problem hiding this comment.
Removing the scalar call from execute leaves convert_range, luma, chroma_from_sum, load_rgb, and store_chroma unused in non-test ARM builds. The macOS Clippy job builds on aarch64-apple-darwin with -D warnings, so these new dead_code warnings fail CI.
Gate the kernel-only helpers and data behind #[cfg(any(target_arch = "x86_64", test))], while keeping the swscale fallback available everywhere.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/enc-ffmpeg/src/video/rgb_to_yuv.rs
Line: 135-136
Comment:
**ARM Clippy builds fail**
Removing the scalar call from `execute` leaves `convert_range`, `luma`, `chroma_from_sum`, `load_rgb`, and `store_chroma` unused in non-test ARM builds. The macOS Clippy job builds on `aarch64-apple-darwin` with `-D warnings`, so these new `dead_code` warnings fail CI.
Gate the kernel-only helpers and data behind `#[cfg(any(target_arch = "x86_64", test))]`, while keeping the swscale fallback available everywhere.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in 16a5ccd. The scalar helpers, the Y_R..ROUND constants, ChannelOrder and Job are now #[cfg(any(target_arch = "x86_64", test))], and Job.width/height are x86_64 only, since only the AVX2 kernel reads them. Clippy with -D warnings --all-targets is clean on aarch64 Linux (lib test target included) and on x86_64.
|
@greptileai review |
Summary
Same-size packed screen frames with an even width and height (BGRZ, BGRA, RGBA, RGBZ) now convert to limited-range BT.709 YUV420P or NV12 with a fixed-point kernel, skipping swscale. This is 2.5x to 3.1x faster at 720p, 1080p and 4K. The kernel runs only when AVX2 is detected at runtime. Odd sizes, scaling, every other pixel format, and CPUs without AVX2 still go through swscale.
Change
crates/enc-ffmpeg/src/video/rgb_to_yuv.rs.queue_frame_toandqueue_frame_reusableinh264.rs, and the twoH264PacketEncoderencode paths inh264_packet.rs, now callconvert_or_scale. When both dimensions are even and the format matches, it writes into the existing converted frame. Otherwise it calls the existing scaler.try_convertreturns false unlessis_x86_feature_detected!("avx2")is true, so swscale runs everywhere else (including aarch64). The AVX2 kernel vectorizes luma and interior chroma, and widths that are not a multiple of 8 finish with a scalar tail using the same formula. The release scalar loop alone is slower than swscale on x86 (1080p 4.104 ms vs 3.100 ms), so it is not used as a standalone path.add(128)andwrapping_add(128)were within about 1% (1080p 1.135 / 1.130 / 1.128 ms), so it was removed.Cargo.toml:[profile.dev.package.cap-enc-ffmpeg]and[profile.ci-verify.package.cap-enc-ffmpeg]setopt-level = 1.[profile.ci-verify.package."*"]does not cover workspace members, so this crate was built at opt-level 0 in the verify job, where the AVX2 kernel takes about 180 ms per 1080p frame. At opt-level 1 it is about 1.7 ms (swscale 3.1 ms).open_video_encoder_innerandconfigure_rgb_converterare unchanged.Benchmarks
Command:
Release build. Each time is the median of 31 runs after 5 warmup runs. Before is swscale
SWS_FAST_BILINEARwith the same BT.709 full-to-limited setup the encoder already uses.Hardware: Intel Xeon, family 6 model 207, 4 vCPU, KVM, AVX2 and AVX-512. Prefetch is not in this build.
A separate run on the same machine gave 0.491, 1.135 and 4.520 ms for the kernel against 1.381, 3.100 and 12.852 ms for swscale (2.81x, 2.73x, 2.84x).
Tests
Before the review fixes,
cargo test -p cap-enc-ffmpeg --offline -- --test-threads=8: 212 passed, 0 failed, 2 ignored. After them, the cloud machine's FFmpeg 7.1 lacked libx264 and libopus, so the full run was 171 passed, 41 failed (allCodecNotFound), 2 ignored. Everyrgb_to_yuvtest passed. CI has the codecs.convert_or_scaleand match swscale exactly.rgb_software_conversion_matches_declared_bt709_through_encode_decodestill passes.queue_frame_to_bgrz_flat_red_decodes_as_bt709.swscale_fast_bilinear_stays_within_tolerance.Jobare compiled only for x86_64 and tests, andJob.width/heightonly for x86_64, so ARM builds have no dead code.cargo fmt --all -- --checkpasses, andcargo clippy -p cap-enc-ffmpeg --all-targets -- -D warningsis clean on x86_64 Linux and aarch64 Linux, including the lib test target. aarch64-apple-darwin could not be cross-checked on Linux because there is no Apple SDK, so macOS CI confirms that target.rgb_to_yuvtests: 7 passed, 1 ignored (the bench).Test plan
cargo fmt -p cap-enc-ffmpeg -- --checkcargo clippy -p cap-enc-ffmpeg --all-targets -- -D warnings(x86_64 and aarch64)cargo test -p cap-enc-ffmpeg -- --test-threads=8Risk
h264.rsis also edited by feat(rendering): add opt-in macOS software codec mode #2948 and perf(recording): cut Windows readback copies and encode from textures #2940, but in different functions. perf: AVX2 UYVY/YUYV conversion in the H264 encoder #2980 changes the same fourconverter.runlines, so whichever merges second should chain packed YUV, then RGB, then swscale.cap-enc-ffmpegbuilds at opt-level 1 indevandci-verify. Release is unchanged.Windows real-capture
The first push failed the windows-2022 real-capture job (run): studio-screen/mp4-holds had 334 missing frames against a budget of 5, and studio-screen/fragmented kept 52 frames instead of about 530. The cause was this PR. Windows capture delivers RGBA, which takes the fast path into software libx264, and the verify profile built this crate at opt-level 0, where the kernel ran at about 5.5 fps at 1080p. The same job passed on #2980 and on main at 2026-10-23 03:50 UTC. The opt-level 1 override above is the fix.


Confidence Score: 5/5
The PR appears safe to merge, with all previous findings addressed and no new blocking issues found.Summary
This PR adds fixed-point BT.709 conversion for same-size, even-sized packed RGB frames on CPUs with AVX2.
Reviews (3) · Last reviewed commit: "Gate x86 only conversion helpers" · Reviewed by Greptile