Metal kernels: 32-bit checked arithmetic, one thread per HP block, single-pass color output - #8
Merged
Merged
Conversation
Every reconstruction kernel widened each add, subtract, and multiply to 64-bit, which Apple GPUs emulate, and returned through a branch after each operation. Arithmetic now uses exact 32-bit overflow predicates (sign-bit tests for add/sub, mulhi for quantizer products, range tests for the times-three steps) accumulated into a sticky flag that each kernel tests once before storing. The transforms become straight-line code; per-phase status codes are unchanged, and results derived from an overflowed intermediate are never stored. The HP kernel ran one threadgroup per macroblock with thread 0 applying HP prediction serially between two barriers, leaving half of each SIMD group idle for 16-block luma macroblocks. It now runs one thread per 4x4 block in a flat grid; each thread accumulates its own prediction chain in the normative order, so every partial-sum overflow check matches the serial traversal. Block rows are stored as aligned int4 writes, and plane ABI construction now rejects sample planes that are not four-sample aligned. The output kernels converted color, including chroma upsampling, once per output channel. Each pixel is now loaded and converted once, and premultiplied stores scale alpha once per pixel. Chroma upsampling computes the weighted average exactly in 32 bits by splitting each operand into 8q + r, and unsigned premultiplication uses 32-bit division (65535^2 + 32767 fits in u32).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checked arithmetic. Every reconstruction kernel widened each add, subtract, and multiply to 64 bits, which Apple GPUs emulate, and returned through a branch after each operation. Arithmetic now uses exact 32-bit overflow predicates: sign-bit tests for add/sub,
mulhifor quantizer products, and range tests for the ×3 steps. Each kernel accumulates them into a sticky flag and tests it once before storing. The transforms become straight-line code. Per-phase status codes (1, 2, 3, 4, 16) are unchanged, and a result derived from an overflowed intermediate is never stored.HP transform. The kernel ran one threadgroup per macroblock, with thread 0 applying HP prediction serially between two barriers; that left half of each 32-wide SIMD group idle on 16-block luma macroblocks. It now runs one thread per 4×4 block in a flat grid, with no threadgroup memory or barriers. Each thread accumulates its own prediction chain in normative order, so every partial-sum overflow check matches the serial traversal. Block rows are stored as aligned
int4writes, andJxrPlaneAbi::from_plannow rejects sample planes that aren't four-sample aligned (they already are, since planes are 64-element aligned).Output stores. Color conversion, including 4:2:0/4:2:2 chroma upsampling, ran once per output channel; each pixel is now loaded and converted once. Premultiplied stores scale alpha once per pixel. Chroma upsampling computes the weighted average exactly in 32 bits by splitting each operand into
8q + r; the weights always sum to eight, so the result always fits. Unsigned premultiplication uses 32-bit division, since 65535² + 32767 fits inu32. F32 output and signed premultiply still use 64-bit math; they're rare formats, and this keeps them exact.One behavioral nuance: when two different overflows occur in the same macroblock, the HP kernel may report status 4 where it previously reported 3. The previous kernel could already do this across macroblocks, because first-failure-wins is racy between threadgroups.
Performance (M4 Pro, interleaved with
mainin the same session, better of two rounds).jxr-pathology-bench, 256×256 Boat tiles; "Metal-side" is submit + wait with CPU preparation excluded. Pipelined throughput also includes CPU entropy decoding, which this PR does not change:Single-image Metal decode (
jxr-load-bench, lower of two rounds' medians): Seattle 23.4 → 21.2 ms (−10%), P19d 31.3 → 29.1 ms (−7%), Maui 128bpp 39.8 → 37.1 ms (−7%), Maui RGBE 36.9 → 35.2 ms (−5%). CPU decodes are unchanged. VeryWideLevel255 Metal timings were bimodal (19–30 ms within one run) and are not quoted.Validation on an M4 Pro:
cargo fmt --all -- --check;cargo clippy --workspace --all-targets --all-features -- -D warnings;cargo test --workspace --all-features(255 passed, covering every store entry point and the post-submission arithmetic-failure path);cargo test -p jxr-mpsgraph --test metal -- --ignored --test-threads=1(7 passed); T.834/T.835 conformance Metal 517/517 byte-exact and CPU 517/517;jxr-pathology-benchchecksum validation of resident and dense batches. The owner-onlymetal-hardwareworkflow has not been run on this revision.