Skip to content

spare_strips: Add image tinting support#1460

Merged
grebmeg merged 5 commits into
mainfrom
gemberg/glyph-cache-image-tinting
Feb 24, 2026
Merged

spare_strips: Add image tinting support#1460
grebmeg merged 5 commits into
mainfrom
gemberg/glyph-cache-image-tinting

Conversation

@grebmeg
Copy link
Copy Markdown
Collaborator

@grebmeg grebmeg commented Feb 20, 2026

This PR introduces the ability to tint image paints at rasterization time. A new Tint type carries a color and a TintMode — either AlphaMask, which treats the source image's alpha as a coverage mask and fills with the tint color (useful for glyph rendering and monochrome icons), or Multiply, which does a per-channel multiply of the source pixels by the tint color (useful for colorizing full-color images).

The tint is threaded through the encoding pipeline so that EncodeExt::encode_into accepts an optional Tint, and the Scene / Recorder APIs expose set_tint for controlling it. On the CPU side, tinting is applied in both the nearest-neighbor and filtered image painters, working in the appropriate precision for each path. On the GPU side, the WGSL shader unpacks a packed RGBA8 tint color from the encoded paint data and applies the selected mode before blending.

@LaurenzV
Copy link
Copy Markdown
Collaborator

This does unsurprisingly cause regressions for normal images. :( But I also wouldn't know how to avoid it, so if we really need that then it is what it is.

fine/image/transform/none_u8_neon
                        time:   [279.76 ns 282.04 ns 284.63 ns]
                        change: [+6.0489% +7.3013% +8.6111%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 5 outliers among 100 measurements (5.00%)
  4 (4.00%) high mild
  1 (1.00%) high severe

fine/image/transform/scale_u8_neon
                        time:   [280.17 ns 282.00 ns 284.08 ns]
                        change: [+5.6126% +6.9340% +8.5438%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 9 outliers among 100 measurements (9.00%)
  7 (7.00%) high mild
  2 (2.00%) high severe

fine/image/transform/rotate_u8_neon
                        time:   [428.67 ns 430.09 ns 431.49 ns]
                        change: [+20.111% +20.727% +21.340%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 7 outliers among 100 measurements (7.00%)
  6 (6.00%) high mild
  1 (1.00%) high severe

fine/image/quality/low_u8_neon
                        time:   [281.17 ns 283.51 ns 286.10 ns]
                        change: [+3.5725% +4.7283% +5.9705%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
  4 (4.00%) high mild

fine/image/quality/medium_u8_neon
                        time:   [1.3702 µs 1.3747 µs 1.3794 µs]
                        change: [+2.2158% +2.8353% +3.3934%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

fine/image/quality/high_u8_neon
                        time:   [8.1643 µs 8.1825 µs 8.2008 µs]
                        change: [+2.4438% +2.8503% +3.2690%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 9 outliers among 100 measurements (9.00%)
  6 (6.00%) high mild
  3 (3.00%) high severe

fine/image/extend/pad_u8_neon
                        time:   [289.64 ns 290.53 ns 291.49 ns]
                        change: [+4.6107% +6.1515% +7.8261%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 7 outliers among 100 measurements (7.00%)
  6 (6.00%) high mild
  1 (1.00%) high severe

fine/image/extend/repeat_u8_neon
                        time:   [340.92 ns 342.20 ns 343.56 ns]
                        change: [+18.855% +19.406% +19.898%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild

fine/image/extend/reflect_u8_neon
                        time:   [562.56 ns 563.28 ns 564.19 ns]
                        change: [+11.354% +11.655% +11.935%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 8 outliers among 100 measurements (8.00%)
  5 (5.00%) high mild
  3 (3.00%) high severe

images/overlapping      time:   [2.5914 ms 2.5936 ms 2.5961 ms]
                        change: [+1.0254% +1.2325% +1.4240%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 7 outliers among 100 measurements (7.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe

Base automatically changed from gemberg/glyph-cache-image-resolver to main February 23, 2026 08:45
@grebmeg grebmeg requested review from LaurenzV and laurenz-canva and removed request for LaurenzV February 23, 2026 08:58
@LaurenzV
Copy link
Copy Markdown
Collaborator

LaurenzV commented Feb 23, 2026

Could you rebase onto main? Because I'm still seeing the changes from the image resolver.

@grebmeg grebmeg force-pushed the gemberg/glyph-cache-image-tinting branch from c5b8841 to d197630 Compare February 23, 2026 10:04
@grebmeg
Copy link
Copy Markdown
Collaborator Author

grebmeg commented Feb 23, 2026

Could you rebase onto main? Because I'm still seeing the changes from the image resolver.

@LaurenzV Sure, just give me some time, I need to double check my fix with performance.

@grebmeg grebmeg force-pushed the gemberg/glyph-cache-image-tinting branch from d197630 to c6a9d4f Compare February 23, 2026 10:16
@grebmeg
Copy link
Copy Markdown
Collaborator Author

grebmeg commented Feb 23, 2026

@LaurenzV I updated everything and refactored the tinting so that it now runs as a post pass. It should be equivalent to the previous solution, but without affecting the main hot image path. It seems to have fixed the regression.

However, I noticed that bench now enables sse4_2, and that is the only case that regressed. I have not looked into the details yet to understand why. Maybe you already know what could be causing it?

@LaurenzV
Copy link
Copy Markdown
Collaborator

I don't have an x86 machine so I can't test. How much is it regressing? Seems weird though that there are regressions only for that specific level. 🤔

Copy link
Copy Markdown
Collaborator

@LaurenzV LaurenzV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like the new approach much better! Will leave it up to you whether it's worth porting lowp to use u8x32, but I think it would be nice, it should make it faster.

Comment thread sparse_strips/vello_cpu/src/fine/highp/mod.rs Outdated
Comment thread sparse_strips/vello_cpu/src/fine/lowp/mod.rs Outdated
Comment thread sparse_strips/vello_cpu/src/fine/lowp/mod.rs Outdated
Comment thread sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl Outdated
@grebmeg
Copy link
Copy Markdown
Collaborator Author

grebmeg commented Feb 24, 2026

I don't have an x86 machine so I can't test. How much is it regressing? Seems weird though that there are regressions only for that specific level. 🤔

The regression is approximately 14%.

@grebmeg grebmeg enabled auto-merge February 24, 2026 00:18
@grebmeg grebmeg added this pull request to the merge queue Feb 24, 2026
Merged via the queue into main with commit 7a68954 Feb 24, 2026
17 checks passed
@grebmeg grebmeg deleted the gemberg/glyph-cache-image-tinting branch February 24, 2026 00:35
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