Skip to content

[Feature] Dynamic Liquid Glass — real-time desktop refraction (Apple-quality, clone-based) #931

Description

@NB-Group

tl;dr

Real-time, Apple-quality liquid glass for the panel, dash-to-dock and popups — refracting the live desktop (wallpaper and windows, dynamic or static) through a clone-based backdrop + single-pass GLSL shader. Plus three-way coexistence with applications blur and dynamic wallpapers (e.g. waywallen).

🎬 Demo:

a3e0abb1babe07e6801b56154bd9cedc.mp4

📹 Handheld phone capture — OBS is bugged on this machine so I had to film the screen by hand. The actual on-screen effect is smooth; any flicker/tearing is a capture artifact, not the effect itself.

This is not BMS's existing "Liquid Glass" (refraction.glsl, which blurs a static wallpaper snapshot). That one can't follow windows or dynamic wallpapers. This is a new, clone-based pipeline that samples the actual live stage behind the widget, every frame.

What it does

  • Live refraction: the glass shows what's actually behind it right now — drag a window under the panel, switch workspaces, run a video wallpaper — it all refracts through the glass in real time.
  • Single-pass shader: gaussian blur + refraction bands + top-bright/bottom-dark sheen + rounded-rect mask (ported from the apple-inspired-glass-effects-library reference).
  • Three-way coexistence (the genuinely hard part): liquid-glass panel/dock/popup + applications blur + dynamic wallpaper — all at once, with windows showing through the glass.

How (technical)

  • New conveniences/liquid_glass_pipeline.js (clone-based see-through architecture): each glass widget clones _backgroundGroup + a shared window_group capture, then runs the LiquidGlassEffect shader on the widget.
  • Shared window_group capture (one offscreen-cached clone of window_group in uiGroup): N glass widgets sample one cached texture, so the desktop is composited once per frame, not N times (avoids the mutter multi-clone pollution / window-shadow corruption you'd get from each widget cloning window_group directly).
  • applications blur coexistence: applications keep BMS's native Shell.BlurEffect(BACKGROUND) inside window_actor. The whole-window_group capture includes that blur_actor → a mild render loop, deliberately accepted.

Known limitations (this is the "experimental" part)

Mutter/Clutter impose real constraints here. Most are worked around, but two remain:

  1. Applications blur actor desyncs during motion — when a window is dragged or the workspace snaps (3-finger swipe), the applications-blur blur_actor (native Shell.BlurEffect(BACKGROUND) inside window_actor) outruns the window and briefly disappears, exposing the solid wallpaper layer behind it for a beat ("purple veil"). Root cause is the accepted render loop (the whole-window_group capture includes the blur_actor, which itself samples _backgroundGroup) plus mutter's blur position sampling during motion. BMS-side fix is blocked: the capture can't exclude the blur_actor without losing window see-through, and GNOME 50's gesture snap doesn't emit the standard workspace_manager::workspace-switched signal so a freeze/hide hook can't reliably catch the moment. Small, motion-only; static at rest.
  2. Performance — single-pass 21×21 (441-tap) gaussian per widget per frame, tuned to avoid grain on high-frequency window content. Fine on discrete GPUs; heavy on low-end / integrated. A separable 2-pass blur would cut this ~4× at the same quality (future work).
  3. GNOME version — developed & tested on GNOME 50.3, NVIDIA, Wayland. Older versions untested.
  4. Rounded corners — shader mask reads the host's corner radius (panel = rect, dock = dash-to-dock's dash-background radius, popup = menu radius); AA tuned, may need per-theme tweaks.

The code comments also document the dead-ends that don't work under mutter, so the next person doesn't re-walk them: clone-based ShaderEffects don't apply below window_group; per-child Clone(MetaWindowActor) doesn't bind its texture; GaussianBlurEffect's chained FBO can't render a clone backdrop; etc.

Try it

Fork: NB-Group/blur-my-shell, branch feature/dynamic-liquid-glass.

Per-component "Liquid glass" toggle (dynamic mode). The Other page exposes three hero params: blur / tint / refraction.

Discussion

This deliberately pushes against mutter's compositing model. It works and (imo) looks genuinely great — but the applications-blur coexistence leans on an accepted render loop. Flagging for community discussion:

  • Is this direction welcome upstream?
  • Any cleaner way to break the applications-blur / glass-sees-windows loop without changing waywallen?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions