Skip to content

perf(gpui): SIMD vectorize PNG adaptive filters and eliminate buffer copies - #2674

Open
nikolas-sapa wants to merge 1 commit into
CapSoftware:mainfrom
nikolas-sapa:perf/gpui-fast-png-filtering
Open

nikolas-sapa wants to merge 1 commit into
CapSoftware:mainfrom
nikolas-sapa:perf/gpui-fast-png-filtering

Conversation

@nikolas-sapa

@nikolas-sapa nikolas-sapa commented Oct 17, 2026 •

Copy link
Copy Markdown

Summary

• Vectorizes all 4 PNG adaptive filter encoders (Up, Sub, Avg, Paeth) in apps/desktop-gpui/kernels/src/png.rs using ARM NEON on aarch64 with unrolled 64-byte block processing.
• Zero-copy Up filter: Filter 2 (the initial filter evaluated by adaptive_filter) filters directly into the output row buffer while accumulating the absolute sum in vector registers, eliminating the intermediate candidate buffer write and subsequent memcpy.
• Vectorized Average filter (vhaddq_u8): evaluates floor halving addition and subtraction in vector instructions with zero scalar division or bitwise overhead.
• Vectorized Paeth filter: evaluates parallel 8-lane 16-bit absolute distance comparisons and bitselect (vbslq_u16), eliminating all per-pixel scalar branches.
• Fast SIMD sum_abs: replaces the double-nested .chunks(32).map().sum() iterator loop with NEON vector absolute and horizontal pairwise addition (vabsq_s8 + vaddlvq_u8).
• Unit tests: adds neon_filters_match_scalar_reference verifying all 4 filters and their cost calculations match the scalar reference byte-for-byte across RGB and RGBA formats and varied row lengths.
• Benchmark harness: adds default synthetic screen workloads (1080p, Retina, 4K) to benches/png_encode.rs when executed without CLI file arguments.

Benchmark Results (Apple Silicon M-series)

Workload Pixels Baseline (image crate) Optimized (cap-gpui-kernels) Speedup Correctness
1080p 2,073,600 76.1 ms 19.4 ms ~3.9x Bit-exact
Retina 5,939,136 197.8 ms 49.8 ms ~4.0x Bit-exact
4K 8,294,400 285.4 ms 66.6 ms ~4.3x Bit-exact

Test Plan

• Run cargo test --manifest-path apps/desktop-gpui/kernels/Cargo.toml
• Run cargo bench --manifest-path apps/desktop-gpui/kernels/Cargo.toml --bench png_encode
• Run cargo clippy --manifest-path apps/desktop-gpui/kernels/Cargo.toml

RetriggerView in GreptileConfidence Score: 4/5

The required Rust loop style must be satisfied before merging; the benchmark memory use and shared test reference are non-blocking improvements.

Findings

  1. P2 Loops violate required Rust style ▶
  2. P2 Image batches retain every buffer ▶
  3. P2 Cost tests share their implementation ▶
Fix with agent prompt
### Issue 1
apps/desktop-gpui/kernels/src/png.rs:282-285
The new loops index slices with a range variable. The repository instructions require slice iteration or `.iter().enumerate()` instead. Use zipped iterators for these equal-length slices. The same pattern appears at lines 353–356 and 835–837, and elsewhere in the added filters. This repository requirement must be satisfied before merging.

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!

### Issue 2
apps/desktop-gpui/kernels/benches/png_encode.rs:93
The CLI branch now decodes every image into `targets` before benchmarking the first one. Previously, it decoded and released one image at a time. Large screenshot batches now need memory for all decoded images at once, which can cause swapping, distort timings, or stop the benchmark with an out-of-memory failure. Keep CLI targets lazy and decode each image just before benchmarking it.

### Issue 3
apps/desktop-gpui/kernels/src/png.rs:842
The expected cost uses `sum_abs`, which this PR also replaces with NEON on `aarch64`. Both sides therefore use the same vector absolute-value and sum operations, so a shared cost bug could pass every assertion. Compute the expected cost with a scalar iterator in this test, including explicit `0x80` values, to check the new cost code independently.

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!

---

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

Summary

Adds ARM NEON implementations for PNG adaptive filters and writes the initial Up result directly into the output buffer.

  • ARM PNG encoding filters rows in vector blocks and writes the Up result straight to output.
  • The PNG benchmark runs screen-sized workloads when no image paths are given.

Reviews (1) · Last reviewed commit: "perf(gpui): SIMD vectorize PNG adaptive ..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

@nikolas-sapa is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

Comment on lines +282 to +285
for i in 0..len {
let diff = tail_c[i].wrapping_sub(tail_p[i]);
tail_o[i] = diff;
sum += u64::from((diff as i8).unsigned_abs());

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 Loops violate required Rust style

The new loops index slices with a range variable. The repository instructions require slice iteration or .iter().enumerate() instead. Use zipped iterators for these equal-length slices. The same pattern appears at lines 353–356 and 835–837, and elsewhere in the added filters. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/kernels/src/png.rs
Line: 282-285

Comment:
**Loops violate required Rust style**

The new loops index slices with a range variable. The repository instructions require slice iteration or `.iter().enumerate()` instead. Use zipped iterators for these equal-length slices. The same pattern appears at lines 353–356 and 835–837, and elsewhere in the added filters. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

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!

color,
}
})
.collect()

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 Image batches retain every buffer

The CLI branch now decodes every image into targets before benchmarking the first one. Previously, it decoded and released one image at a time. Large screenshot batches now need memory for all decoded images at once, which can cause swapping, distort timings, or stop the benchmark with an out-of-memory failure. Keep CLI targets lazy and decode each image just before benchmarking it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/kernels/benches/png_encode.rs
Line: 93

Comment:
**Image batches retain every buffer**

The CLI branch now decodes every image into `targets` before benchmarking the first one. Previously, it decoded and released one image at a time. Large screenshot batches now need memory for all decoded images at once, which can cause swapping, distort timings, or stop the benchmark with an out-of-memory failure. Keep CLI targets lazy and decode each image just before benchmarking it.

---

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


let mut ref_out = vec![0u8; len];
apply_filter(2, bpp, &prev, &curr, &mut ref_out);
let ref_cost = sum_abs(&ref_out);

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 Cost tests share their implementation

The expected cost uses sum_abs, which this PR also replaces with NEON on aarch64. Both sides therefore use the same vector absolute-value and sum operations, so a shared cost bug could pass every assertion. Compute the expected cost with a scalar iterator in this test, including explicit 0x80 values, to check the new cost code independently.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/kernels/src/png.rs
Line: 842

Comment:
**Cost tests share their implementation**

The expected cost uses `sum_abs`, which this PR also replaces with NEON on `aarch64`. Both sides therefore use the same vector absolute-value and sum operations, so a shared cost bug could pass every assertion. Compute the expected cost with a scalar iterator in this test, including explicit `0x80` values, to check the new cost code independently.

---

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!

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks @nikolas-sapa! Greptile is at 4/5 here. We only merge once Greptile reaches 5/5, so please address its findings in the summary above (or reply to them if you think one is wrong), then comment @greptileai review to re-run it. Once it is 5/5 we will run the full verification suite on it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants