8000
Skip to content

modular: wrap residual subtraction for lossless fp32 predictors - #4948

Open
Alb3e3 wants to merge 3 commits into
libjxl:mainfrom
Alb3e3:fix-fp32-lossless-polarity
Open

modular: wrap residual subtraction for lossless fp32 predictors#4948
Alb3e3 wants to merge 3 commits into
libjxl:mainfrom
Alb3e3:fix-fp32-lossless-polarity

Conversation

@Alb3e3
@Alb3e3 Alb3e3 commented Aug 20, 2026
Copy link
Copy Markdown
Contributor

Summary

  • add a focused regression for lossless float32 grayscale input that mixes negative and positive samples
  • compute fast-path modular residuals with defined modulo-2^32 subtraction instead of rejecting wrapped values as overflow
  • use the same wrapped subtraction when estimating weighted-predictor cost

Root cause

Lossless float32 input is encoded through modular int32 channels that carry the raw IEEE-754 bit patterns. For mixed-sign samples, predictor guesses can differ from the source value by more than the signed int32 range while still being valid modulo-2^32 residuals.

The fast encoder paths were using SubOverflow(...) and returning Residual overflow when that happened, even though the decoder and slower modular paths operate on wrapped 32-bit residuals. That made valid lossless fp32 inputs fail with JXL_ENC_ERR_GENERIC.

Verification

  • reproduced the failure first with EncodeTest.LosslessFloatMixedSignValues at efforts 1, 4, and 7
  • ./tests/encode_test --gtest_filter=EncodeTest.LosslessFloatMixedSignValues
  • ./tests/encode_test --gtest_filter=EncodeTest.FrameEncodingTest:EncodeTest.LosslessFloatMixedSignValues

AI disclosure

Prepared with AI assistance, then manually reviewed and verified locally.

Fixes #4902.

@jonnyawsom3
Copy link
Copy Markdown
Collaborator

Not directly related, but any chance you could take a look at #3511 too? It was also caused by an overflow mitigation (27afa5e), but in the palette code instead of predictors. If you can figure it out, density could significantly improve.

@Alb3e3
Alb3e3 commented Aug 21, 2026
Copy link
Copy Markdown
Contributor Author

Follow-up for #3511 is pushed in 0607064.

This keeps the earlier predictor fix intact and narrows the palette regression fix to the exact safe case: allow the lossless single-channel palette path for 32-bit inputs again, while leaving the 32-bit bailout in place for the wider implicit/multi-channel palette code that 27afa5e was protecting.

Focused proof:

  • red on unpatched enc_palette.cc: ModularTest.Float32SingleChannelPaletteRegression fails because FwdPalette(...) returns false
  • green after the change: the same test passes and inserts a palette meta-channel
  • EncodeTest.LosslessFloatMixedSignValues still passes on this branch

Commands rerun locally after the push:

  • cmake --build /home/beneatgoon2/libjxl-3511-build --target modular_test encode_test -j2
  • /home/beneatgoon2/libjxl-3511-build/lib/tests/modular_test --gtest_filter='ModularTest.Float32SingleChannelPaletteRegression\\n- `/home/beneatgoon2/libjxl-3511-build/lib/tests/encode_test --gtest_filter=\EncodeTest.LosslessFloatMixedSignValues'

@kleisauke
Copy link
Copy Markdown
Contributor

I assume the SubOverflow() function introduced in commit 87bee19 (plus the MSVC-specific intrin.h include) can be removed now as well?

FWIW, I wonder if it might make more sense to revert commit 9f72fea, 87bee19 and 27afa5e first and then apply your fixes on top of that. That might make the changes easier to review.

@Alb3e3
Alb3e3 commented Aug 24, 2026
Copy link
Copy Markdown
Contributor Author

Yes, good catch — removed in 5c0503bb. SubOverflow() had no callers left after the residual fix, so that also drops the MSVC-only <intrin.h> include it needed, and with it the _M_AMD64 || _M_IX86 guard that #4877 had to add for the MSVC ARM64 build. SubOverflow/_sub_overflow_i32 now appear nowhere under lib/.

On the restructure: I'd rather not fold the reverts into this PR. 9f72fea, 87bee19 and 27afa5e each fixed a real problem, and a revert-then-reapply would put the tree through a state where those are unfixed — bad for bisecting, and it makes the diff look like "undo three fixes" rather than "here is what was wrong with them". What's here now is three commits that read in order:

  1. a5107b2e — wrap the residual subtraction for lossless fp32 predictors (the 87bee19 overflow check was too strict)
  2. 06070643 — re-allow the single-channel palette path for 32-bit lossless (the 27afa5e bailout was too broad), narrowly, leaving the multi-channel bailout in place
  3. 5c0503bb — drop the now-dead helper

That said, it's your call and I don't feel strongly. If you and the other reviewers would rather see revert-first, say the word and I'll restructure it.

Verified on this branch after the removal:

  • modular_test — 79/79 pass, including ModularTest.Float32SingleChannelPaletteRegression
  • encode_test --gtest_filter=EncodeTest.LosslessFloat*LosslessFloatMixedSignValues passes

I'll rebase onto current main as well since the branch is showing behind.

Alb3e3 and others added 3 commits August 24, 2026 20:37
The residual subtraction in enc_encoding.cc no longer needs an overflow
check, so SubOverflow has no callers left. Removing it also drops the
MSVC-only <intrin.h> include it required, along with the _M_AMD64 /
_M_IX86 guard added in libjxl#4877 for the MSVC ARM64 build.
@Alb3e3
Alb3e3 force-pushed the fix-fp32-lossless-polarity branch from 5c0503b to ed3fb70 Compare August 24, 2026 16:39
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.

Lossless fp32 encoding fails when the image mixes negative and positive values

3 participants

0