Skip to content

JPEG 4:2:0→RGB fused decode + v3.11 benchmark review response - #145

Open
sebastient wants to merge 7 commits into
mainfrom
feat/jpeg-420-rgb-fused
Open

JPEG 4:2:0→RGB fused decode + v3.11 benchmark review response#145
sebastient wants to merge 7 commits into
mainfrom
feat/jpeg-420-rgb-fused

Conversation

@sebastient

Copy link
Copy Markdown
Contributor

Summary

Addresses docs/BENCHMARKS_JPEG_REVIEW_v3.11.md end to end — both the documentation cleanups and the real objections that needed new measurement or implementation work.

New feature (crates/codec): the opt-in fused set_output_format(Some(Rgb)) decode path previously covered only 4:4:4 sources, silently falling back to native NV12 for the dominant real-world subsampling (4:2:0) — exactly the cell a libjpeg-turbo-literate reviewer would expect EdgeFirst to lose on. Implemented write_rgb_rows_420: a 2×2 nearest-neighbour (box) chroma upsample fused into the MCU write, reusing the existing SIMD ycbcr_to_rgb_row kernels on the upsampled row. EdgeFirst wins the RGB arm on every board/corpus measured, including the one host (A53) where the YUV arm was a dead heat — with zero fast-class loss cells. Box (not fancy/triangle) upsampling is a deliberate speed tradeoff, measured at 44–50 dB PSNR against a reference decode (comparable to the already-documented DctMethod::Fast accuracy cost). No public API change.

Benchmark harness: added EDGEFIRST_WUFFS_FORCE_4BPP to wuffs_bench to isolate Wuffs' 3-byte-swizzle-vs-4-byte-native cost (closes review item 2.3).

BENCHMARKS.md — full review response:

  • Blocking: superseded the contradicted v0.22.1 Image Codec Decode JPEG rows; reconciled the nvJPEG June-vs-August conclusions; scoped the "two releases behind" banner to the GL/preprocessing matrix only
  • High: measured zune's neon feature is genuinely engaged (not inferred); measured Wuffs' RGB slow-path penalty; published per-board libjpeg-turbo version/compiler provenance; added mbp-m2-max to the full 8-arm sweep (largest accurate-class lead of any board, 1.41×)
  • Medium: added a Max-spread column and corrected the "sub-1%" prose it wasn't measuring; unified ratio framing throughout; quantified the zune greyscale-skip asymmetry at exactly zero affected images; ran a second AWS instance per queue (4/5 within 0.6%, m7i's real ~7% instance effect barely moves the published ratio)

Deferred per explicit product decision: fast-mode mAP impact (after this release + profiler update) and per-queue AWS second-instance runs beyond the headline corpus.

Test plan

  • cargo test -p edgefirst-codec — all passing, including new write_rgb_rows_420 correctness tests (matches NV12 decode + box upsample + existing colour kernel, byte-for-byte) and a rejection test for genuinely non-4:2:0 subsamplings (4:2:2)
  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace — clean (2 pre-existing macOS GL parallel-test flakes confirmed via git stash, unrelated to this diff)
  • Wuffs C harness (bench.c) builds clean natively and cross-compiled (zig cc, aarch64), zero warnings
  • Real hardware re-capture: full 8-arm/6-corpus sweep re-run on imx8mp-frdm, imx95-pro, rpi5-hailo, adis-uav1 (Orin Nano stand-in), sebstation, and mbp-m2-max for the corpora affected by the new RGB path
  • Real AWS Batch: second independently-launched instance per queue (Graviton2/3/4, Sapphire Rapids, Genoa) for the headline corpus
  • dump_rgb420 example used to measure real PSNR against a reference decode on 3 test fixtures

🤖 Generated with Claude Code

set_output_format(Some(Rgb)) previously honoured only 4:4:4 sources,
falling back to native NV12 for the dominant real-world subsampling
(4:2:0). Add write_rgb_rows_420: a 2x2 nearest-neighbour (box) chroma
upsample fused into the MCU write stage, reusing the existing
ycbcr_to_rgb_row SIMD kernels on the upsampled row rather than a new
hand-written subsampled colour kernel. 4:2:2 and other non-4:2:0
subsamplings still fall back to native output.

Box (not fancy/triangle) upsampling is a deliberate speed tradeoff,
measured at 44-50 dB PSNR / max pixel delta 20-24 against a reference
decode on the COCO-family test fixtures - comparable to the existing
accurate-vs-fast-DCT accuracy cost.

Signed-off-by: Sébastien Taylor <[email protected]>
Addresses docs/BENCHMARKS_JPEG_REVIEW_v3.11.md end to end:

Blocking:
- Mark the v0.22.1 Image Codec Decode JPEG rows superseded in place
  (decoder internals they measured no longer exist post-rewrite)
- Reconcile the nvJPEG June-vs-August conclusions (pre-optimization
  CPU baseline, single fixture vs corpus, full load_image vs
  decode-only)
- Scope the "two releases behind" banner to the GL/preprocessing
  matrix only

High:
- Measure zune-jpeg's neon feature is genuinely engaged (1.16-1.20x
  A/B on rpi5-hailo) rather than inferred from a features table
- Measure Wuffs' RGB row is on its 3-byte swizzle slow path
  (1.52-1.59x behind its native 4-byte output); add
  EDGEFIRST_WUFFS_FORCE_4BPP to wuffs_bench for the A/B
- Publish per-board libjpeg-turbo version/package-source/compiler
  (none of the five original hosts run turbo 3.2+)
- Add mbp-m2-max to the eight-arm sweep (largest accurate-class lead
  of any board, 1.41x on val2017)

Medium:
- Add a Max-spread column to the headline table; correct the "sub-1%"
  prose it was not actually measuring against
- Unify ratio (not percentage) framing throughout, label YUV/RGB pairs
- Quantify the zune greyscale-skip asymmetry: exactly zero of val2017's
  10 greyscale files land in the n=200 evenly-spaced sample
- Run a second independently-launched AWS instance per queue: 4/5
  queues within 0.6%, m7i shows a real ~7% instance effect that barely
  moves the published ratio (1.176x vs 1.185x)

The high-priority "4:2:0->RGB implementation" item is addressed by a
companion feat commit (write_rgb_rows_420) rather than being deferred
as a known frontier; this commit updates BENCHMARKS.md's Control
Corpora section with the resulting RGB-on-4:2:0 measurements.

Signed-off-by: Sébastien Taylor <[email protected]>
Copilot AI lite review requested due to automatic review settings August 14, 2026 18:45

Copilot AI left a comment

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.

Pull request overview

This PR enhances the edgefirst-codec JPEG CPU decoder by extending the opt-in fused Rgb output path to cover native 4:2:0 sources (the common real-world subsampling), and updates the benchmark harness + documentation to address the v3.11 JPEG benchmark review feedback end-to-end.

Changes:

  • Implement fused native-4:2:0 → RGB write (write_rgb_rows_420) using a 2×2 nearest-neighbour (box) chroma upsample fused into the MCU write path, reusing existing YCbCr→RGB row kernels.
  • Add/adjust correctness tests for the new 4:2:0 fused RGB path and for rejecting unsupported subsampling shapes (e.g. true 4:2:2) in fused RGB mode.
  • Update benchmarking harness/docs (including a new EDGEFIRST_WUFFS_FORCE_4BPP toggle) and refresh BENCHMARKS.md narrative/tables to reconcile prior captures and document the new measurements.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/codec/TESTING.md Expands the test coverage matrix description to include fused RGB for native 4:2:0 and rejection behavior for other subsamplings.
crates/codec/src/jpeg/mod.rs Extends output-format resolution so Rgb can be selected for native 4:2:0 (when sampling matches is_native_420).
crates/codec/src/jpeg/mcu.rs Adds native-4:2:0 fused RGB writer (write_rgb_rows_420), scratch upsample buffers, and new unit tests.
crates/codec/src/decoder.rs Updates public decoder docs to describe fused RGB behavior for 4:4:4 and native 4:2:0.
crates/codec/examples/dump_rgb420.rs Adds a local helper example to dump fused 4:2:0→RGB output as PPM for accuracy comparisons.
crates/codec/ARCHITECTURE.md Updates architecture docs to describe the new fused 4:2:0→RGB path and its accuracy/speed tradeoffs.
CHANGELOG.md Documents the new fused native-4:2:0→RGB decode capability under Unreleased.
benchmarks/modules/wuffs/bench.c Adds EDGEFIRST_WUFFS_FORCE_4BPP to reorder Wuffs pixel format probing for a targeted 3bpp vs 4bpp measurement.
BENCHMARKS.md Incorporates the full v3.11 review response: scope notes, reconciliations, new measurements, and “superseded” annotations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +25 to +27
/// Full-width horizontally-upsampled Cb/Cr rows for the fused 4:2:0→RGB
/// write (see `write_rgb_rows_420`). Empty until first grown by
/// `ensure_capacity`/`new` for an image wide enough to need them.
* for an isolated 3-byte-swizzle-vs-4-byte-native A/B on Wuffs' own
* decoder (not a comparison against another arm) — see BENCHMARKS.md
* § JPEG Decode's Wuffs accuracy/performance note. */
int force_4bpp = getenv("EDGEFIRST_WUFFS_FORCE_4BPP") != NULL;
@github-actions

Copy link
Copy Markdown

Test Results (x86_64)

1 695 tests  +2   1 677 ✅ +2   1m 46s ⏱️ +30s
   25 suites ±0      18 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 2e11e8e. ± Comparison against base commit d9d6531.

@github-actions

Copy link
Copy Markdown

Test Results (macOS)

1 459 tests  +2   1 459 ✅ +2   0s ⏱️ ±0s
   22 suites ±0       0 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 2e11e8e. ± Comparison against base commit d9d6531.

@github-actions

Copy link
Copy Markdown

Test Results (aarch64)

1 686 tests  +2   1 668 ✅ +2   43s ⏱️ -1s
    2 suites ±0      18 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 2e11e8e. ± Comparison against base commit d9d6531.

@sonarqubecloud

Copy link
Copy Markdown

Rebuild and push the multi-arch edgefirst-hal-jpeg-bench container
against the fused native-4:2:0->RGB codec (native amd64 build on
sebstation, native arm64 build locally, combined into one manifest),
then re-run the full 30-job AWS Batch matrix (5 queues x 6 corpora,
not just the val2017 headline that the second-instance run covered).

EdgeFirst is fastest in every cloud cell, YUV and RGB, every corpus.
Bonus finding: the prior revision's m7i cross-corpus DRI anomaly
(nonphysical negative delta, caused by a val2017/val2017-dri pair
landing on different-turbo-bin instances) does not reproduce on this
fresh all-corpora capture -- every queue now shows a physically
sensible positive DRI penalty, consistent with the second-instance
finding that m7i's absolute latency (not its competitive ratio)
is what varies instance-to-instance.

Signed-off-by: Sébastien Taylor <[email protected]>
Response to docs/BENCHMARKS_JPEG_REVIEW_v3.12.md:
- Add mbp-m2-max to the libjpeg-turbo provenance table (Homebrew
  jpeg-turbo 3.2.0, Apple clang 21.0.0, macOS 27.0); note the
  GNU-assembler-NEON-removal caveat is a Linux/Android toolchain
  concern, not verified to apply to Apple's build
- State explicitly that the fused native-4:2:0 box chroma upsample is
  opt-in (same set_output_format gate as every fused decode output),
  not a default -- there is no "default RGB output" to compare
- Confirm and document that the 4:2:0 YUV ratio shift between captures
  is measurement variance, not attributable to write_rgb_rows_420:
  verified write_nv12_rows/write_nv16_nv24_rows are untouched by that
  diff
- Cross-reference the Wuffs 3-byte-swizzle-slow-path caveat from the
  AWS cloud baselines section

Signed-off-by: Sébastien Taylor <[email protected]>
… tooling

Response to docs/BENCHMARKS_JPEG_REVIEW_v3.12.md's blocking and high items:

- turbojpeg_bench: add --upsample accurate|fast (TJFLAG_FASTUPSAMPLE),
  mirroring the existing --dct accurate|fast pattern. Turbo's default
  is fancy/triangle upsampling; --upsample fast selects its box/
  nearest-neighbour path -- the accuracy class EdgeFirst's fused
  native-4:2:0 write uses. Without this, the RGB tables compared a
  cheaper algorithm (EdgeFirst) against a more expensive one (turbo's
  default) and called the difference a performance lead.
- turbojpeg_bench: add EDGEFIRST_TURBOJPEG_LIB to dlopen an exact
  libturbojpeg path instead of the built-in candidate search, for A/B
  testing a source-built libjpeg-turbo against the distro-packaged one
  on the same host without uninstalling either.
- decode-ab-sweep.sh / decode-ab-sweep-macos-local.sh: add a
  tj_fastupsample arm (RGB-only) alongside tj_islow/tj_ifast.
- docker: capture full container provenance (build toolchain, base
  image, packaged libturbojpeg version+arch, resolved lib path) to
  <board>_provenance.txt per job. Add a second build stage that
  compiles libjpeg-turbo 3.1.2 from source and bakes it into the same
  image as an alternate .so (opt in via EDGEFIRST_TURBOJPEG_LIB), so
  the source-built-turbo control doesn't need a separate image variant.
- benchmarks/scripts/decode-ab-sweep-macos-local.sh: commit the
  mbp-m2-max local-runner script (previously only in a session
  scratchpad) -- the most reproducible board in the sweep is now
  actually reproducible.

Verified: turbojpeg_bench builds clean (native + aarch64 cross);
--upsample fast measurably faster than accurate on real corpora;
EDGEFIRST_TURBOJPEG_LIB correctly loads an alternate .so; Docker image
builds clean (arm64), provenance capture and source-built-turbo
override both verified working inside the container.

Signed-off-by: Sébastien Taylor <[email protected]>
…g fix

Adds the turbo TJFLAG_FASTUPSAMPLE (box/nearest-neighbour, matched IDCT
accuracy class) comparison the v3.12 review asked for, alongside the
existing default-fancy-upsample RGB tables, on boards and AWS. This
surfaces a real result the fancy-upsample comparison was masking: on
the two in-order cores (A53, A55), EdgeFirst's RGB lead collapses to
near-parity or a narrow loss (0.97x-0.98x) on two of three native-4:2:0
corpora once turbo's own box upsample is the baseline, though EdgeFirst
stays ahead on the DRI corpus on those boards and on every out-of-order
core throughout (boards and all five AWS queues).

Also adds a macOS QoS scheduling hint (advisory-only; macOS has no
accessible core-affinity API) plus a 5-round default to the
mbp-m2-max local runner, cutting worst observed cross-round spread
from 7.7% to 6.6%, and re-captures its full six-corpus sweep under
the new protocol.

BENCHMARKS.md documents all of this plus the resolution of the
turbo-version "confound" the review flagged (same-host source-built
3.1.2 A/B via a new EDGEFIRST_TURBOJPEG_LIB override shows no real
effect; better-supported explanation is in-order vs out-of-order core
class) and adds container/build provenance capture to every AWS Batch
job.

Signed-off-by: Sébastien Taylor <[email protected]>
…path

Blocking: added a pixel-parity harness (benchmarks/modules/rgb_parity,
dlopen-linked against libjpeg-turbo) comparing EdgeFirst's box-upsample
RGB against turbo TJFLAG_FASTUPSAMPLE, with a phase-shift diagnostic
that would catch a systematic half-pixel replication misalignment. 0/720
images across four native-4:2:0 corpora score better at a nonzero shift;
worst-case PSNR 51.2-51.3 dB, max|delta| 3 - the phase is measured
correct, not assumed.

Also addresses the review's remaining checklist items:
- set_output_format(Some(Rgb)) on a source that is neither 4:4:4-
  equivalent nor native 4:2:0 now returns CodecError::UnsupportedFormat
  instead of silently substituting the native NV12/NV16/Grey format.
- Unit tests for expand_row_2x/write_rgb_rows_420 at 1x1, odd x even,
  even x odd, and odd x odd dimensions.
- ChromaUpsample (#[non_exhaustive], Box only for now) and
  ImageDecoder::set_chroma_upsample, so a future fancy-upsample mode is
  an additive variant rather than a breaking API change.
- Direct (non-dispatch) parity tests for the SSE4.1 and NEON fused-RGB
  block kernels - the prior test only ever exercised whichever tier the
  CI/dev host's CPU happened to select.
- Re-ran the fast-class RGB comparison against matched
  ifast+TJFLAG_FASTUPSAMPLE on the two in-order boards plus one
  out-of-order board for contrast. Found a real loss cell on
  imx8mp-frdm/CLIC 4:2:0 (0.96x, non-overlapping round spreads) - the
  same in-order-core effect already documented for the accurate class,
  published as-is per the review's own option rather than treated as a
  release blocker.

BENCHMARKS.md bumped to 3.14 with the full writeup and changelog entry.

Signed-off-by: Sébastien Taylor <[email protected]>
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