mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-25 23:04:27 +00:00
Linear gradients in dark color ranges (5-15% lightness) show visible banding due to 8-bit quantization — only ~7 distinct values exist in that range, producing hard steps instead of smooth transitions. This affects every dark theme in Zed. ## What this does Adds triangular-distributed dithering after gradient interpolation in both the Metal and HLSL fragment shaders. The noise breaks up quantization steps at the sub-pixel level, producing perceptually smooth gradients. ## How it works Two hash-based pseudo-random values (seeded from fragment position x golden ratio) are summed to produce a triangular probability distribution. This is added to the RGB channels at +/-1/255 amplitude. - **Triangular PDF** — mean-zero, so no brightness shift across the gradient - **+/-1/255 amplitude** — below perceptual threshold, invisible on bright gradients where 8-bit precision is already sufficient - **Deterministic per-pixel** — seeded from position, no temporal flickering - **Zero-cost** — a couple of `fract`/`sin` per fragment, negligible vs. the existing gradient math ### Channel-specific amplitudes | Channel | Amplitude | Rationale | |---------|-----------|----------------------------------------------------------------------------------------------------| | RGB | ±2/255 | Breaks dark-on-dark banding where adjacent 8-bit values are perceptually close | | Alpha | ±3/255 | Alpha gradients over dark backgrounds need stronger noise — α × dark color = tiny composited steps | The higher alpha amplitude is necessary because when a semi-transparent gradient (e.g., 0.4 → 0.0 alpha) composites over a dark background, the effective visible difference per quantization step is smaller than the RGB case. ±3/255 is still well below the perceptual threshold on bright/opaque elements. ## Scope Two files changed, purely additive: | File | Change | |------|--------| | `crates/gpui_macos/src/shaders.metal` | 13 lines after `mix()` in `fill_color()` | | `crates/gpui_windows/src/shaders.hlsl` | 13 lines after `lerp()` in `gradient_color()` | No changes to Rust code, no API changes, no new dependencies. ## Screenshots <img width="1886" height="1003" alt="gradient_dithering_before" src="https://github.com/user-attachments/assets/f75ae93b-b142-4d0e-9b61-e08f30fe1758" /> _Before_ <img width="1902" height="1052" alt="gradient_dithering_after" src="https://github.com/user-attachments/assets/7aee9a36-f578-4e08-a846-44d092bcf043" /> _After_ ## Test plan This is a shader-level fix; no Rust test harness exists for visual output. Manual testing is appropriate here. Visual regression tests cover UI layout, not sub-pixel rendering quality. **Manual (macOS):** - [x] Dark gradients (5-13% lightness range) — banding eliminated - [x] Bright gradients — no visible difference (dither amplitude below precision threshold) - [x] Oklab and sRGB color spaces — both paths dithered - [x] Solid colours, pattern fills, checkerboard — unaffected (dither only applies to LinearGradient case) - [x] Alpha gradients (semi-transparent over dark bg) — banding eliminated with alpha dithering - [x] Path gradients (paint_path) — same fill_colour() function, dithering applies **Windows:** HLSL change is identical logic with HLSL built-ins (`frac`/`lerp` vs `fract`/`mix`) — not tested locally. Release Notes: - Improved linear gradient rendering by adding dithering to eliminate visible banding in dark color ranges Liam |
||
|---|---|---|
| .. | ||
| src | ||
| build.rs | ||
| Cargo.toml | ||
| LICENSE-APACHE | ||