Skip to content

feat(desktop-gpui): Cap Glass, a cross-platform glass material - #3442

Open
richiemcilroy wants to merge 29 commits into
feat/gpui-window-arrange-focusfrom
feat/gpui-cap-glass
Open

richiemcilroy wants to merge 29 commits into
feat/gpui-window-arrange-focusfrom
feat/gpui-cap-glass

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Stacked on #3454. Part of the picker stack: #3441, #3450, #3452, #3454, this, #3456.

What this adds

Cap Glass is a glass material for the GPUI app. It renders the same on macOS, Windows and Linux, and was fitted against Apple's NSGlassEffectView.

Renderer (apps/desktop-gpui/patches/zed-glass.patch)

The gpui fork's BackdropBlur existed only in the Metal renderer. This patch extends it with a GlassStyle and implements it in all three renderers: Metal (shaders.metal), DirectX 11 (shaders.hlsl) and wgpu (shaders.wgsl).

Per pane, the renderer:

  1. snapshots the region the pane samples;
  2. blurs it (MPS on Metal; a downsample plus separable gaussian on DirectX and wgpu);
  3. runs one fragment pass with the same math on every backend:
    • a blur radius that scales with the pane's shorter side;
    • refraction along the rim's normal;
    • a per-channel cubic colour transform;
    • an edge line and glow;
    • the hairline just outside the edge.

Window::paint_glass wraps the pass in its own scene layer, so the glass draws even when nothing else is painted above it. prepare-gpui-dependency.sh applies the patch like the others.

Package (apps/desktop-gpui/glass, crate cap-glass)

  • model.rs is a CPU model of the same material. It is the reference the GPU output is checked against.
  • presets.json holds the four fitted materials: Regular and Clear, each in light and dark.
  • glass-fit fits a material to reference captures, at whatever backing scale they were taken. The colour transform is linear in its coefficients, so each evaluation solves it exactly by weighted least squares (neutral scenes weighted 12x). Nelder-Mead searches only blur, lensing and lighting.
  • src/ui/glass.rs is the app API: glass::pane(kind, dark, radius) gives a Div whose first child is the glass, so content paints on it.

Reference captures (scripts/glass-reference/reference.swift)

The script draws 13 scenes (flat greys, black, white, text, dense text, checker, grid, stripes, dots, saturated blobs, hues, a UV ramp, a split field). It puts NSGlassEffectView panes over them in each style and appearance, at several sizes and radii, and captures them.

The references were captured on a MacBook Pro (M4 Max, macOS 27). CI VMs render Liquid Glass in a reduced form, so references must come from real hardware. To regenerate them, run the script on a Mac with Screen Recording access, then glass-fit <dir> <out>.

Fit against Apple

Per preset, over the 13 scenes, inside the glass:

Preset Worst SSIM Mean ΔE2000 Scenes at SSIM ≥ 0.97 and ΔE < 2 Worst scene
Clear, light 0.991 0.86 12 / 13 checker, ΔE 3.6
Clear, dark 0.993 0.89 12 / 13 checker, ΔE 3.5
Regular, light 0.965 1.36 9 / 13 checker, ΔE 4.8
Regular, dark 0.967 1.94 6 / 13 checker 3.7, white 3.1, text 2.9

Notes on the table:

  • The worst SSIM in both Regular presets is the stripes scene. There, Apple's blur is not quite gaussian.
  • The saturated checker is the worst scene for colour in every preset. A cubic transform can't follow Apple's saturation curve exactly at the extremes.
  • Flat greys match Apple to within 3 levels per channel in every preset. tests/presets.rs pins this.
  • A further refit that searched a wider space did not improve on these numbers, so these presets ship.

GPU against the model, on every OS

The glass probe (CAP_GLASS_PROBE_OUTPUT, .github/workflows/glass-probe.yml) runs on all three OSes:

  1. it draws every scene with glass panes in each preset in the real app;
  2. it captures the window;
  3. it scores the capture against the CPU model.

On Windows and Linux it captures the window's screen area. Hosted Windows runners have no window capture, and Cap's own windows aren't in the X11 window list.

It passes at SSIM ≥ 0.97 and mean ΔE ≤ 2.

OS / backend Cases Worst SSIM Worst mean ΔE Worst ΔE p95
macOS / Metal 36 0.9992 0.41 0.66
Linux X11 / wgpu 36 0.9992 0.65 1.57
Windows / DirectX 11 36 0.9992 0.58 1.34

Cost

  • Glass costs nothing until a pane is on screen.
  • A pane copies and blurs only its own region plus the blur's reach.
  • Metal sizes its scratch textures to that region. DirectX and wgpu keep surface-sized ones.
  • Every backend frees its scratch textures after 120 frames with no glass, as the existing Metal backdrop blur already did.
  • cap-glass builds at opt-level = 3 in dev, so the probe and the fitter run at full speed.

Testing

  • cargo test -p cap-glass runs the preset calibration against Apple's flat values (wired into ci.yml).
  • The glass probe runs on macOS, Windows and Linux (table above).
  • node --test scripts/prepare-gpui-dependency.test.mjs covers the new patch's place in the patch list.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with three non-blocking issues still open in the CPU reference and scaled X11 probe.

Findings

  1. P2 Transparent reference images turn opaque ▶
  2. P2 Overlapping panes miss earlier glass ▶
  3. P2 Scaled X11 probe fails ▶
Fix with agent prompt
### Issue 1
apps/desktop-gpui/glass/src/model.rs:undefined-471
`model::composite` makes every touched pixel fully opaque and ignores `style.opacity`. The GPU shaders instead use the backdrop's alpha and the material's opacity, so the CPU reference gives different results for transparent images.

Match the GPU's behavior in the CPU model and add a transparent-backdrop test. The current opaque scenes cannot catch this difference.

### Issue 2
apps/desktop-gpui/glass/src/model.rs:undefined-481
`model::render` blurs the original `backdrop` for every pane. When panes overlap, a later pane ignores the glass already drawn beneath it. The GPU instead snapshots the window before each pane, so the CPU reference produces different colours and blur in the overlap.

Blur the current `out` before drawing each pane, and add an overlapping-pane test.

```suggestion
        composite(&style, shape, &out.blurred(style.blur), &mut out);
```

### Issue 3
apps/desktop-gpui/src/glass_probe.rs:506-512
On X11 with a backing scale above 1, `window_target` passes logical window coordinates to a capture path that reads physical pixels. At scale 2, it captures roughly 960×600 pixels while the expected image is 1920×1200. `crop_to` cannot enlarge the capture, so `metrics::ssim` panics before the probe writes its report.

Apply the X11 scale conversion already used by `capture_scale_factor` when building these capture bounds.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds Cap Glass across Metal, DirectX 11, and wgpu.

  • GPUI panes can render rounded glass across its three desktop backends.
  • Glass materials can be fitted to Apple captures and shipped as four presets.
  • A real-app probe compares glass output across macOS, Windows, and Linux.

Reviews (7) · Last reviewed commit: "Merge feat/gpui-window-arrange-focus int..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cap-status Ready Ready Preview Oct 27, 2026 9:46pm UTC

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

Comment thread apps/desktop-gpui/glass/src/bin/glass-fit.rs Outdated

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

This branch was successfully deployed

2 active deployments
Preview – cap-web — b8f4b0e5 Deployed Oct 27, 2026 by vercel[bot]
Preview – cap-status — b8f4b0e5 Deployed Oct 27, 2026 by vercel[bot]
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