Skip to content

vello_cpu: Fix Color and Luminosity blends turning some grays black - #2000

Open
zlstringham wants to merge 1 commit into
linebender:mainfrom
zlstringham:vello-cpu-clip-color-nan
Open

zlstringham wants to merge 1 commit into
linebender:mainfrom
zlstringham:vello-cpu-clip-color-nan

Conversation

@zlstringham

Copy link
Copy Markdown

Fixes #1999.

clip_color divides by l - n when n < 0 and by x - l when x > 1, without checking the denominator. After set_lum, a gray has three equal components, so l == n == x. When rounding leaves them just below 0 (or just above 1), the division is 0/0. The NaN survives apply_alpha, even over a transparent backdrop, and packs to black. With Mix::Color and Mix::Luminosity, some gray levels come out black in both pipelines.

This PR clips only where the denominator is positive: n < 0 && l - n > 0 and x > 1 && x - l > 0. For a neutral color, the clipped value's limit is the color itself, so skipping the clip loses nothing. Skia (l != mn, l != mx) and pixman (FLOAT_IS_ZERO(l - n)) guard the same case.

vello_gpu_shaders/shaders/helpers/blend.wesl and research/vello_shaders/shader/shared/blend.wgsl have the same unguarded clip_color and get the same guard. I couldn't make the GPU path produce black pixels on Metal, but WGSL gives an indeterminate value for a division without a finite result, so the guard still helps there.

Testing:

  • New unit test non_separable_mix_of_neutral_colors_is_finite checks that mix stays finite for Hue/Saturation/Color/Luminosity with every gray at 50% alpha over a transparent backdrop and over every opaque gray. It fails without the guard and passes with it.
  • cargo test -p vello_cpu passes.
  • cargo test -p vello_tests --release passes on CPU and on GPU (Metal, Apple M3 Max), with no snapshot changes.
  • cargo test -p vello_gpu_shaders --all-features passes. vello_gpu_shaders and vello_shaders build, and their build scripts validate the WGSL with naga.
  • The reproducer from the issue now reports 0 black pixels for every mode in both render modes.
  • cargo fmt --check, typos, the CI clippy commands for vello_cpu (including no_std and wasm32), and the 1.89 MSRV check are clean.

After `set_lum`, a gray has `l == n == x`, so `clip_color` divides 0 by
0 when rounding leaves the channels just outside [0, 1]. The NaN
survives `apply_alpha` and packs to black. Clip only where the
denominator is positive, in vello_cpu and in the vello_gpu and research
shaders alike, and add a unit test that `mix` stays finite for every
gray pair.

Fixes linebender#1999.
@zlstringham
zlstringham force-pushed the vello-cpu-clip-color-nan branch from 9cc3903 to c068039 Compare October 11, 2026 02:12

This branch has not been deployed

No deployments
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.

vello_cpu: Color and Luminosity blends draw some grays black (0/0 in clip_color)

1 participant