Skip to content

Add 16bpc (RGBA64 / 64bpp) high-precision color pipeline support - #188

Open
edco wants to merge 1 commit into
linebender:mainfrom
edco:16bpc-pipeline
Open

Add 16bpc (RGBA64 / 64bpp) high-precision color pipeline support#188
edco wants to merge 1 commit into
linebender:mainfrom
edco:16bpc-pipeline

Conversation

@edco

@edco edco commented Aug 30, 2026

Copy link
Copy Markdown

Pull Request: 16bpc Color Pipeline Support for tiny-skia

This pull request introduces native 16-bit per channel (RGBA64 / 64bpp premultiplied) color rendering support to tiny-skia, gated behind an optional 16bpc feature flag.

Authored mostly by Gemini 3.7 Flash in Google Antigravity IDE. Forgive me for this AI slop.

Supports a corresponding PR to resvg: linebender/resvg#1125

Summary

  • Introduce optional 16bpc feature flag for 16-bit per channel rendering.
  • Add Pixel and HighPixel trait abstractions with PremultipliedColorU16, ColorU16, and AlphaU16.
  • Provide concrete dual-pipeline specialization (highp::Pipeline for 8bpc and highp::PipelineU16 for 16bpc) to guarantee zero performance regression on 8bpc rendering.
  • Add DynamicPixmap, DynamicPixmapMut, and DynamicPixmapRef enums for polymorphic buffer management.
  • Support 16-bit PNG decoding/encoding and 16bpc pattern textures.
  • Expand test suite to validate both 8bpc and 16bpc pipelines across all 160 integration tests and 64 unit tests.
  • Add layer compositing benchmarks and 16bpc benchmark dispatching.

Key Highlights

  1. Generic Pixel Abstraction (Pixel & HighPixel traits):
    • Generalizes PixmapGeneric<P>, PixmapMutGeneric<'_, P>, PixmapRefGeneric<'_, P>, PainterGeneric<'_, P>, and RasterPipelineBlitter<P> without runtime overhead.
    • Retains full backwards compatibility via type aliases (type Pixmap = PixmapGeneric<PremultipliedColorU8>).
  2. 16-Bit Precision Raster Pipeline:
    • Enables highp floating-point SIMD pipeline stages to load from and store directly into PixmapU16 buffers.
    • Preserves continuous high-precision subpixel anti-aliasing, linear/radial/conical/sweep gradients, dash stroking, pattern shaders, masks, and non-separable blend modes.
  3. Pattern Shaders with 16bpc Source Textures:
    • Pattern shader supports both 8bpc and 16bpc source textures via DynamicPixmapRef, sampling at full 16-bit fidelity across Nearest, Bilinear, and Bicubic interpolation.
  4. PNG I/O & Dynamic Dispatch:
    • Full 16-bit PNG decoding and encoding (PixmapU16::decode_png, PixmapU16::encode_png, PixmapU16::save_png).
    • DynamicPixmap, DynamicPixmapMut, and DynamicPixmapRef enums for polymorphic 8bpc / 16bpc canvas management.
  5. Comprehensive Dual-Pipeline Integration Test Suite:
    • The entire integration test suite (160 tests across dash, fill, stroke, gradients, hairline, pattern, mask, gamma, pixmap, path, png, and u16_precision) now executes against both the 8bpc and 16bpc raster pipelines with tolerance validation.
  6. Performance & Benchmarking:
    • Added TINY_SKIA_BENCH_16BPC environment switch and benches/src/draw_pixmap.rs layer compositing benchmarks.
  7. Zero Breaking Changes:
    • Default feature set (default = ["std", "simd", "png-format"]) maintains existing 8bpc behavior, performance, and binary size.
    • 160 / 160 integration tests and 64 / 64 unit tests passing cleanly.

Benchmarks

1. 8bpc Rendering: Baseline (Before) vs. Current (After)

Benchmark Category Upstream Main Baseline (Before) Current 8bpc State (After) Delta Ratio Status
blend::color_burn 1,037,382 ns 1,047,932 ns 1.01x ✓ Matched (< 1.05x)
blend::color_dodge 965,679 ns 968,595 ns 1.00x ✓ Matched (1.00x)
blend::color 1,426,378 ns 1,430,993 ns 1.00x ✓ Matched (1.00x)
blend::hue 1,695,861 ns 1,671,013 ns 0.99x ✓ Matched (< 1.00x)
blend::luminosity 1,350,474 ns 1,348,782 ns 1.00x ✓ Matched (1.00x)
blend::saturation 1,705,808 ns 1,676,968 ns 0.98x ✓ Matched (< 1.00x)
blend::soft_light 1,372,955 ns 1,368,898 ns 1.00x ✓ Matched (1.00x)
gradients::*::hq 1,181,922 – 1,804,438 ns 1,133,960 – 1,813,336 ns 0.96x – 1.01x ✓ Matched (1.00x)
patterns::hq 13,234,832 ns 13,210,361 ns 1.00x ✓ Matched (1.00x)
patterns::lq 4,300,789 ns 4,261,942 ns 0.99x ✓ Matched (< 1.00x)
patterns::plain 1,733,675 ns 1,727,813 ns 1.00x ✓ Matched (1.00x)
draw_pixmap::* 2,544,142 – 4,374,013 ns 2,554,828 – 4,428,752 ns 1.00x – 1.01x ✓ Matched (1.00x)
fill::* / clip::* 37,050 – 1,621,291 ns 37,722 – 1,622,617 ns 1.00x – 1.02x ✓ Matched (1.00x)

2. 16bpc Mode vs. 8bpc Mode Performance

Operation Category 8bpc Performance 16bpc Performance 16bpc vs 8bpc Ratio Observations
Separable Blends (source_over, screen, plus, multiply) 600 – 740 µs 650 – 780 µs 1.05x – 1.08x Near identical SIMD vectorization speed
Non-Separable Blends (color_burn, dodge, color, hue, soft_light) 970 – 1,710 µs 1,015 – 1,695 µs 1.00x – 1.05x Computational bound math is identically efficient
Linear Gradients (two_stops, three_stops) 1,130 – 2,206 µs 1,133 – 1,686 µs 0.76x – 1.00x High precision shader eliminates 8-bit intermediate table lookups
Radial Gradients (simple_radial, two_point_radial) 2,063 – 2,290 µs 1,935 – 1,996 µs 0.87x – 0.95x Float shader in 16bpc is slightly faster than lowp branching
Geometry & Paths (path_aa, rect_aa, spiral) 597 – 1,244 µs 620 – 1,274 µs 1.02x – 1.04x Geometry rasterization overhead is identical
Memory-Bound Fill (clear, opaque, all) 38 – 72 µs 80 – 168 µs 2.1x – 2.3x Exactly tracks 2x memory bandwidth scaling (8 bytes/px vs 4 bytes/px)
Pattern Shader HQ (patterns::hq) 13.2 ms 17.8 ms 1.35x Higher precision bilinear/bicubic filtering across 16-bit texture channels

3. Compiled Binary Size (tiny-skia libtiny_skia.rlib, Release Build)

Configuration Binary Size (libtiny_skia.rlib) Delta vs Baseline Notes
Upstream Main Baseline (Before) 880,878 bytes (~880 KB) Baseline Standard 8bpc pipeline only
Current 8bpc State (Default Features) 801,362 bytes (~801 KB) -9.0% (-79 KB) More compact binary footprint via unified stage macros
Current State (--features 16bpc Enabled) 1,086,866 bytes (~1.08 MB) +23.4% (+206 KB) Contains dual concrete 8bpc + 16bpc stage tables and 16-bit PNG I/O

Configuration & Command Reference

Feature Flag

  • 16bpc (off by default): Enables 16-bit per channel color types (PixmapU16, PremultipliedColorU16, ColorU16), the 16bpc raster pipeline (highp::STAGES_U16), and 16-bit PNG I/O.

Environment Variable

  • TINY_SKIA_BENCH_16BPC (benchmark runner only): When set to any value during cargo bench --features 16bpc, instructs benchmark cases to allocate PixmapU16 instead of Pixmap and measure the 16bpc rendering pipeline.

Command Examples

1. Building & Testing

# Build standard 8bpc library (release)
cargo build --release

# Build with 16bpc feature enabled (release)
cargo build --release --features 16bpc

# Run test suite against 8bpc pipeline
cargo test

# Run full test suite against both 8bpc and 16bpc pipelines
cargo test --features 16bpc

2. Running Benchmarks

# 1. Run benchmarks on the 8bpc pipeline with the 16bpc feature disabled
cargo bench

# 2. Run benchmarks on the 8bpc pipeline with the 16bpc feature enabled
cargo bench --features 16bpc

# 3. Run benchmarks on the 16bpc pipeline (using 16-bit PixmapU16 buffers)
TINY_SKIA_BENCH_16BPC=1 cargo bench --features 16bpc

Changed & Added Files (Changelog)

src/color.rs

  • AlphaU16 / ALPHA_U16_TRANSPARENT / ALPHA_U16_OPAQUE [NEW]: 16bpc equivalents to AlphaU8 and its constants.
  • ColorU16 [NEW]: 16bpc equivalent to ColorU8, including constructors (from_rgba, from_rgba8), getters, premultiply(), and to_color_u8().
  • PremultipliedColorU16 [NEW]: 16bpc equivalent to PremultipliedColorU8, including constructors (from_rgba, from_rgba_unchecked, from_rgba8), getters, demultiply(), is_opaque(), and to_color_u8().
  • Color::to_color_u16() / PremultipliedColor::to_color_u16() [NEW]: 16bpc equivalents to to_color_u8().
  • premultiply_u16(c, a) / color_f32_to_u16(...) [NEW]: 16bpc premultiplication and float conversion helpers using bit-exact rounded integer arithmetic ((c * a + 32768) / 65535).

src/geom.rs

  • Reordering for Clippy: Moved existing upstream extension traits (IntSizeExt and IntRectExt) above mod screen_int_rect_tests to resolve the clippy::items_after_test_module lint.

src/lib.rs

  • Module Re-exports: Added public re-exports for 16bpc types (PixmapU16, PixmapU16Mut, PixmapU16Ref, DynamicPixmap, DynamicPixmapMut, DynamicPixmapRef, ColorU16, PremultipliedColorU16, AlphaU16, and HighPixel).

src/mask.rs

  • Mask::from_pixmap<P: Pixel> [MODIFIED]: Generalized to construct alpha/luminance masks from generic 8bpc and 16bpc pixmaps.
  • Mask::apply_to_u16 [NEW]: 16bpc equivalent for applying masks to 16bpc buffers using high-precision scaling.

src/math.rs

  • mul_div65535_round(a, b) / div65535_round(x) [NEW]: Fast 32-bit register arithmetic for rounded 16-bit channel division (a * b + 0x8000) / 65535.

src/painter.rs

  • PainterGeneric<'a, P: Pixel> [MODIFIED / GENERIC]: fill_rect, fill_path, stroke_path, draw_paint, and draw_pixmap are made generic over pixel format, serving both 8bpc and 16bpc canvases.
  • DynamicPixmap & DynamicPixmapMut [NEW]: 8bpc/16bpc runtime enum dispatchers for painter methods.
  • blend_u16_op(dst, src, mode) [NEW]: 16bpc software compositor for all 29 separable and non-separable Porter-Duff and SVG blend modes.

src/pipeline/blitter.rs

  • RasterPipelineBlitter<'a, P: HighPixel> [MODIFIED / GENERIC]: new, blit_anti_h, blit_v, and blit_rect are made generic over pixel format for both 8bpc and 16bpc.

src/pipeline/highp.rs

  • HighPixel Trait [NEW]: Defines pipeline execution hooks (run_highp, run_lowp) implemented for PremultipliedColorU8 and PremultipliedColorU16.
  • Concrete Pipeline & PipelineU16 [SPECIALIZED]: Preserved concrete non-generic Pipeline and static STAGES table for 8bpc to ensure zero LLVM stack spill regressions, alongside a concrete PipelineU16 and static STAGES_U16 dispatch table for 16bpc.
  • Shared Declarative Stage Math [NEW]: Macros (stage_fn!, blend_fn!, blend_fn2!, blend_fn3!) generate both 8bpc and 16bpc concrete stage functions from unified mathematical bodies with zero code duplication.
  • load_16161616 / load_16161616_tail / store_16161616 / store_16161616_tail / unnorm_u16 [NEW]: 16bpc SIMD load, store, and un-normalization stages (16bpc equivalents to the 8888 stages).
  • gather / sampler_2x2 / sampler_4x4 / sample [MODIFIED]: Upgraded texture pattern sampling to unpack directly from 16bpc source textures (DynamicPixmapRef).

src/pipeline/lowp.rs

  • Pipeline::fmt [NEW]: Added core::fmt::Debug implementation for the low-precision pipeline context struct.

src/pipeline/mod.rs

  • SubPixmapMutGeneric<'a, P: Pixel> [MODIFIED / GENERIC]: Maintains continuous byte storage (pub data: &'a mut [u8]) with typed chunk accessors (slice4_at_xy, slice16_at_xy, slice_mask_at_xy, slice16_mask_at_xy), ensuring zero-cost indexing across both pixmaps and 1-byte mask buffers.
  • Concrete Compiler & Runner Dispatch [MODIFIED]: RasterPipelineBuilder::compile and RasterPipelineGeneric::run compile and dispatch directly to concrete stage function tables (highp::STAGES for 8bpc, highp::STAGES_U16 for 16bpc, lowp::STAGES for lowp).

src/pixmap.rs

  • Pixel Trait [NEW]: Core trait defining pixel memory layout and color/alpha conversions.
  • PixmapGeneric<P> / PixmapMutGeneric<P> / PixmapRefGeneric<P> [MODIFIED / GENERIC]: Generic implementations of new, from_vec, data, pixels, fill, clone_rect, and draw_pixmap for both 8bpc and 16bpc.
  • PixmapU16::encode_png / save_png / decode_png [NEW]: 16bpc equivalents to the 8bpc PNG I/O methods.
  • DynamicPixmap, DynamicPixmapMut, and DynamicPixmapRef [NEW]: Polymorphic runtime representations wrapping either U8 or U16 pixmaps.

src/shaders/pattern.rs

  • Pattern<'a> [MODIFIED]: Generalized to store DynamicPixmapRef<'a>, allowing pattern shaders to wrap either 8bpc or 16bpc source pixmaps via Pattern::new_u16 and Pattern::from_pixmap.

benches/src/ [MODIFIED]

  • is_16bpc() in benches/src/main.rs [NEW]: Environment variable parser checking TINY_SKIA_BENCH_16BPC.
  • draw_pixmap.rs [NEW FILE]: Layer compositing benchmarks across all 29 blend modes in both 8bpc and 16bpc.
  • blend.rs / clip.rs / fill.rs / gradients.rs / hairline.rs / patterns.rs / spiral.rs [MODIFIED]: Added 16bpc benchmark dispatching when TINY_SKIA_BENCH_16BPC is active.

tests/integration/common.rs [NEW FILE]

  • assert_pixmap_eq<P: Pixel> & assert_mask_eq: Generic visual test assertions supporting strict byte-for-byte matching on 8bpc (tolerance = 0) and precision-adjusted comparison on 16bpc (tolerance <= 2-3).
  • test_raster!: Macro running test bodies against both 8bpc and 16bpc pipelines.

tests/integration/*.rs [MODIFIED]

  • dash.rs / fill.rs / stroke.rs / gradients.rs / hairline.rs / pattern.rs / mask.rs / gamma.rs / pixmap.rs / skia_dash.rs: Converted to dual-pipeline tests executing on both 8bpc and 16bpc pipelines.

tests/integration/u16_precision.rs [NEW FILE]

  • continuous_gradient_sub_8bit_steps / solid_fill_and_draw / path_and_hairline_rendering: Precision tests validating sub-8bit gradient steps, fine alpha ramps, and 16bpc PNG roundtripping.

- Introduce optional `16bpc` feature flag for 16-bit per channel rendering.
- Add `Pixel` and `HighPixel` trait abstractions with `PremultipliedColorU16`, `ColorU16`, and `AlphaU16`.
- Provide concrete dual-pipeline specialization (`highp::Pipeline` for 8bpc and `highp::PipelineU16` for 16bpc) to guarantee zero performance regression on 8bpc rendering.
- Add `DynamicPixmap`, `DynamicPixmapMut`, and `DynamicPixmapRef` enums for polymorphic buffer management.
- Support 16-bit PNG decoding/encoding and 16bpc pattern textures.
- Expand test suite to validate both 8bpc and 16bpc pipelines across all 160 integration tests and 64 unit tests.
- Add layer compositing benchmarks and 16bpc benchmark dispatching.
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.

1 participant