Skip to content

perf: NEON emitFlat encoder + int32 reversible IDWT pipeline#401

Merged
osamu620 merged 2 commits into
mainfrom
perf/neon-emitflat
May 29, 2026
Merged

perf: NEON emitFlat encoder + int32 reversible IDWT pipeline#401
osamu620 merged 2 commits into
mainfrom
perf/neon-emitflat

Conversation

@osamu620

@osamu620 osamu620 commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • NEON emitFlat encoder: port the AVX2 deferred MagSgn drain pattern to NEON — accumulates (v_corr, m) pairs into flat arrays and drains once per line-pair instead of calling emitBits() per quad. 25% single-threaded lossless encode speedup on 4K 12-bit (183 ms → 138 ms).
  • Int32 reversible 5/3 IDWT pipeline: adds int32 IDWT primitives across all backends (NEON, AVX2, AVX-512, WASM SIMD, scalar) that match the int32 FDWT encoder, fixing the lossless encode→decode round-trip. The fused dequant inside ht_cleanup_decode and standalone dequantize() use a template<bool StoreI32> approach (if constexpr) for zero inner-loop branching. The finalize step reads int32 directly from the ring buffer to avoid in-place corruption of the IDWT vertical lifting neighbors.

Benchmarks (4K ElephantDream 4096x2160, Zen 5)

main patched delta
Lossless encode 59 ms 58 ms -1.7%
Lossless decode 48 ms 47 ms -2.1%
Lossy encode 54 ms 53 ms -1.9%
Lossy decode 40 ms 39 ms -2.5%

Test plan

  • 9/9 CI jobs pass (ubuntu gcc/clang, macOS ARM/Intel, Windows x86/ARM, ubuntu ARM gcc/clang, WASM)
  • Lossless round-trip: PAE = 0 (both even and odd dimensions)
  • All 137 WASM conformance tests pass
  • All 266 native conformance tests pass
  • No decode throughput regression on any path
  • Codestreams bitwise identical (emitFlat produces same output)

Port the AVX2 deferred-drain pattern to the ARM NEON cleanup encoder.
Instead of calling emitBits() per quad (which forces an overflow check
on every 4-lane emit), accumulate (v_corr, m) pairs into flat arrays
and drain once per line-pair through emitFlat().  The 4-wide prefix-sum
fast path inside emitFlat batches four consecutive values into Creg when
their total bit-width fits in 64 bits, avoiding per-value overflow
checks.

Lossless 8K 12-bit single-thread encode: 0.67 s → 0.51 s (≈24%).
Codestreams are bitwise identical to baseline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

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 improves HTJ2K performance on ARM by deferring MagSgn emission in the NEON cleanup encoder, and fixes lossless reversible (5/3) round-trip correctness by adding an int32-based IDWT decode pipeline that mirrors the int32 FDWT encoder path.

Changes:

  • Add a NEON emitFlat() MagSgn drain path and update the NEON cleanup encoder to batch (v_corr, m) emission per line-pair.
  • Introduce int32 reversible 5/3 IDWT primitives (scalar/NEON/AVX2/AVX-512/WASM) and a use_i32 streaming-IDWT mode for lossless decode.
  • Thread a dequant_i32 flag through codeblock/subband decode and update dequantize/finalize/MCT glue to consume int32 samples where applicable.

Reviewed changes

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

Show a summary per file
File Description
source/core/transform/idwt.cpp Wires use_i32 into streaming IDWT steps and adds an int32 horizontal row-inplace path.
source/core/transform/idwt_wasm.cpp Adds WASM SIMD int32 rev53 filter + vertical lifting steps.
source/core/transform/idwt_neon.cpp Adds NEON int32 rev53 filter + vertical lifting steps.
source/core/transform/idwt_avx512.cpp Adds AVX-512 int32 rev53 filter + vertical lifting steps.
source/core/transform/idwt_avx2.cpp Adds AVX2 int32 rev53 filter + vertical lifting steps.
source/core/transform/dwt.hpp Declares new int32 IDWT entrypoints and adds idwt_2d_state::use_i32 + init param.
source/core/coding/subband_row_buf.hpp Adds dequant_i32 flag to subband row buffer state.
source/core/coding/subband_row_buf.cpp Propagates dequant_i32 into decode tasks/codeblocks.
source/core/coding/ht_block_encoding_neon.hpp Adds state_MS_enc::emitFlat() for deferred MagSgn draining.
source/core/coding/ht_block_encoding_neon.cpp Switches NEON cleanup encode MagSgn emission to batched emitFlat().
source/core/coding/ht_block_decoding.cpp Adds templated fused dequant store-to-int32 option and dequant_i32 plumbing.
source/core/coding/ht_block_decoding_wasm.cpp Adds templated fused dequant store-to-int32 option and dequant_i32 plumbing (WASM).
source/core/coding/ht_block_decoding_neon.cpp Adds templated fused dequant store-to-int32 option and dequant_i32 plumbing (NEON).
source/core/coding/ht_block_decoding_avx2.cpp Adds templated fused dequant store-to-int32 option and dequant_i32 plumbing (AVX2).
source/core/coding/coding_units.hpp Adds dequant_i32 to j2k_codeblock.
source/core/coding/coding_units.cpp Enables i32 pipeline for reversible line decode; adds int32→float conversions for finalize/MCT paths.
source/core/coding/block_dequant.hpp Extends j2k_dequant() API with dequant_i32 flag.
source/core/coding/block_dequant.cpp Adds scalar reversible dequant that can store int32 into band buffer.
source/core/coding/block_dequant_neon.cpp Adds NEON reversible dequant that can store int32 into band buffer.
source/core/coding/block_dequant_avx512.cpp Adds AVX-512 reversible dequant that can store int32 into band buffer.
source/core/coding/block_dequant_avx2.cpp Adds AVX2 reversible dequant that can store int32 into band buffer.
source/core/coding/block_decoding.cpp Passes block->dequant_i32 into j2k_dequant().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread source/core/transform/idwt_avx2.cpp
Comment thread source/core/transform/dwt.hpp

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

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Comment thread source/core/coding/coding_units.cpp
Comment thread source/core/coding/coding_units.cpp
Comment thread source/core/transform/idwt.cpp

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

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comment thread source/core/coding/coding_units.cpp

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

Copilot reviewed 23 out of 23 changed files in this pull request and generated 32 comments.

Comment thread source/core/coding/block_dequant.cpp Outdated
Comment thread source/core/coding/block_dequant_neon.cpp Outdated
Comment thread source/core/coding/block_dequant_avx2.cpp Outdated
Comment thread source/core/coding/block_dequant_avx512.cpp Outdated
Comment thread source/core/coding/block_dequant.cpp Outdated
Comment thread source/core/transform/idwt_avx512.cpp
Comment thread source/core/transform/idwt_avx2.cpp
Comment thread source/core/transform/idwt_avx2.cpp
Comment thread source/core/transform/idwt_avx512.cpp
Comment thread source/core/transform/idwt_avx512.cpp
@osamu620 osamu620 force-pushed the perf/neon-emitflat branch from ff9ca52 to 950b6b2 Compare May 29, 2026 01:25
Add an int32 IDWT pipeline for lossless 5/3 decode that matches the
int32 FDWT encoder.  The float IDWT cannot correctly invert the int32
FDWT due to integer-vs-floor(float) rounding divergence in compound
lifting expressions, breaking the lossless encode-decode round-trip.

Primitives (all backends: NEON, AVX2, AVX-512, WASM SIMD, scalar):
- idwt_1d_filtr_rev53_i32: horizontal 1D int32 IDWT
- idwt_rev_ver_{lp,hp}_step_i32: vertical lifting steps
- idwt_1d_row_inplace_i32: in-place horizontal IDWT for ring buffer

Pipeline wiring:
- dequant_i32 flag on j2k_codeblock and j2k_subband_row_buf propagates
  to both the standalone dequantize() and the fused dequant inside
  ht_cleanup_decode, storing raw int32 instead of float in band_buf
- Fused dequant uses template<bool StoreI32> (if constexpr) across all
  backends — zero inner-loop branching
- Finalize step reads int32 directly from ring buffer instead of
  converting in-place (which would corrupt the ring buffer for the
  IDWT's vertical lifting neighbors)
- MCT path uses scratch buffers for the int32-to-float conversion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

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

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

@osamu620 osamu620 merged commit b75ecb0 into main May 29, 2026
10 checks passed
@osamu620 osamu620 deleted the perf/neon-emitflat branch May 29, 2026 02:28
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