Prefer RGBX8 over RGB8 as opaque working format — RGB8 has zero SIMD in zenresize
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 2
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The pipeline should prefer RGBX8 (4 bytes/pixel, padding byte) over RGB8 (3 bytes/pixel) as the working format for opaque images. RGB8 hits severe SIMD penalties in zenresize and moderate penalties in zenfilters scatter/gather.
When the pipeline needs to drop alpha (e.g., RemoveAlpha for JPEG output), it should produce RGBX8 instead of RGB8. The encoder can strip the padding byte at write time — zenjpeg already accepts RGBX8 natively.
## Findings
### zenresize: 3-5x penalty for RGB8
- **Horizontal filter** (`simd/x86.rs:489`): `match channels { 4 => filter_h_u8_4ch (AVX2 ymm madd), _ => filter_h_u8_generic (pure scalar) }`. RGB8 gets **zero SIMD** — falls to nested scalar loops.
- **Vertical filter** (`simd/x86.rs:1258`): processes `as_chunks::<16>()`. RGBA8 = 4 pixels/chunk (aligned). RGB8 = 5.33 pixels/chunk (misaligned) → most of the row hits scalar tail processing.
### zenfilters: 10-20% penalty for RGB8
- Scatter/gather (`simd/x86.rs:618`) deinterleaves to planar Oklab f32 then does SIMD matrix math. The deinterleave step is scalar for both 3ch and 4ch, but 4ch loads are aligned.
### zenblend: no RGB8 support
- Requires premultiplied linear f32 RGBA. RGB8 must be upconverted regardless.
### linear-srgb, zenpixels-convert: no penalty
- Element-wise LUT operations — channel count doesn't affect SIMD throughput.
## Proposal
### zenpipe
1. **RemoveAlpha should emit RGBX8 instead of RGB8** when the result stays in the pipeline (not going directly to an encoder). The padding byte costs 33% more memory but avoids the 3-5x resize penalty.
2. **For WASM/canvas targets**, keep RGBA8 through the entire pipeline — `putImageData()` wants RGBA anyway, and skipping RemoveAlpha entirely avoids a pointless format round-trip.
3. **`ensure_fmt!` should prefer RGBX8 over RGB8** when converting opaque RGBA to a 3-channel target. Or better: never convert to RGB8 internally at all — let the encoder do it.
### zenpixels-convert
4. **RowConverter should have a fast RGBA8→RGBX8 path** (just leave the alpha byte, or memset to 0xFF). Currently RGBA8→RGB8 requires a per-pixel 4→3 byte shuffle.
5. **RowConverter should prefer RGBX8 as the negotiated format** when a downstream consumer accepts both RGB8 and RGBX8. The codec format negotiation in zencodecs should rank RGBX8 higher than RGB8.
### zenresize (long-term)
6. **Add internal RGB8→RGBX8 upconversion** before SIMD kernels, strip padding on output. This is cheaper than the scalar fallback and makes the 3-channel API not a performance trap.
## Downstream format acceptance
| Encoder | RGB8 | RGBX8 | Notes |
|---------|------|-------|-------|
| zenjpeg | Native | Native | Both in `ENCODE_DESCRIPTORS` |
| zenbitmaps BMP | Native | Via Bgrx8 | |
| Canvas/WebGL | Needs expand to RGBA | Set alpha=0xFF → RGBA | One memset |
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing RemoveAlpha and ensure_fmt! in zenpipe, then inspect RowConverter in zenpixels-convert and codec format negotiation in zencodecs. Compare the proposed RGBX8 handling across opaque pipeline output, WASM/canvas targets, and encoder acceptance; done requires an agreed scope and consistent format preference without regressing those downstream paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, wasm
- Domain
- computer-graphics, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100