Skip to content

gpui: add effect layers to the scene, style and window - #63773

Closed
mateo-m wants to merge 21 commits into
zed-industries:mainfrom
mateo-m:zed/gpui-effect-layers
Closed

mateo-m wants to merge 21 commits into
zed-industries:mainfrom
mateo-m:zed/gpui-effect-layers

Conversation

@mateo-m

@mateo-m mateo-m commented Sep 4, 2026 •

Copy link
Copy Markdown

Objective

Let an element draw its subtree into its own texture, then paint that texture over the frame with a blur, a colour matrix, a mask and a blend mode. CSS filter, backdrop-filter, mask-image and mix-blend-mode all need this, and GPUI has no way to do it today.

I need it for GPUIX, a React renderer for GPUI by @remorses. Same change as remorses#7.

I first opened this PR with the whole feature in one diff, then split it so each part gets its own review. This PR is the core: the scene, the style and the window. Stacked on #63793, so the diff GitHub shows includes #63771, #63792 and #63793. This PR alone: mateo-m/zed@zed/gpui-linear-gradients...zed/gpui-effect-layers.

The rest of the stack, in order:

Until a backend has its PR, its renderer draws a layer's content straight into the frame with no effect. That is why this PR alone changes nothing on screen.

Solution

  • LayerEffects in style: blur, color_matrix, backdrop_blur, backdrop_matrix, mask, blend_mode, clips. Each field refines on its own, so a hover style that sets a blur keeps the mask. Styled gets a setter for each, plus background_image and background_blend_mode, which paint through the same layer.
  • EffectLayer, ColorMatrix, BlendMode, LayerRegion and BlurPlan in a new effect_layer.rs. The scene gets LayerBegin and LayerEnd batches. EffectLayer is #[repr(C)] so the Metal build can export it through cbindgen.
  • Window::paint_effect_layer(bounds, corner_radii, corner_shapes, effects, f). Style::paint calls it when a style has any effect. A content blur grows the layer by three sigmas, the way a CSS filter bleeds past the border box.
  • BlurPlan: two Gaussian passes, one per axis, on a texture shrunk by a scale, so a wide blur costs about the same as a narrow one. The renderers read it.
  • The three renderers get a no-op arm for the two new batches.
  • LayerEffects::clips has no writer until gpui: clip a rounded overflow box through an effect layer #63794. It is part of the struct the three renderers share, so it lands here and the struct layout stays fixed in the later PRs.

Testing

  • One new test in crates/gpui/src/style.rs, a_hover_refinement_keeps_the_other_effects. It does not compile against the first version of this PR, where effects refined as one block.
  • cargo test -p gpui at this head: 314 passed, 1 doc test passed. cargo check -p gpui -p gpui_wgpu -p gpui_apple --tests passes. cargo fmt --all -- --check is clean.
  • cargo check -p gpui_windows --target x86_64-pc-windows-msvc passes from macOS.
  • The pixel behaviour is tested in the backend PRs and in GPUIX, not here.
  • Performance: an element without effects never opens a layer and takes the old path.
  • EffectLayer ends in a pad field, so its size stays a multiple of 8 bytes. The wgpu PR needs that, because WGSL rounds a struct that holds a vec2<f32> up to 8 bytes and the field after the layer in LayerComposite must land at the same offset on both sides.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • N/A

Taffy computes one tree at a time. The measure closure of
`Window::request_measured_layout` runs inside that computation, so an
element cannot lay a child out there, and `Window::layout_engine` is
`None` for the duration anyway. An element that wants to size itself
from content it has to measure has no way to do it.

`IsolatedLayout` holds a second tree for that content. `enter` puts it in
front of the window's tree for the duration of a closure and gives the
window its own tree back afterwards.

GPUIX needs this for a panel whose height animates to the height of its
content. The width is what the parent gives it, which taffy hands to the
measure closure, and the content has to be measured at that width for
text to wrap the way it will on screen.

Sent upstream as the same change on gpui/isolated-layout.
@cla-bot

cla-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @mateo-m on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@zed-community-bot zed-community-bot Bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Sep 4, 2026
@mateo-m

mateo-m commented Sep 4, 2026

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Sep 4, 2026
@cla-bot

cla-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@mateo-m
mateo-m force-pushed the zed/gpui-effect-layers branch from 7dd56fa to 86aefea Compare September 4, 2026 20:54
@mateo-m
mateo-m force-pushed the zed/gpui-effect-layers branch from 86aefea to 7498262 Compare September 4, 2026 21:57
@mateo-m
mateo-m force-pushed the zed/gpui-effect-layers branch from 7498262 to d10f381 Compare September 4, 2026 22:47
@mateo-m
mateo-m force-pushed the zed/gpui-effect-layers branch from d10f381 to 08bbe47 Compare September 4, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants