Skip to content

perf: improve backdrop blur quality - #8

Merged
BumpyClock merged 4 commits into
mainfrom
fix/backdrop-blur-quality
Jul 28, 2026
Merged

perf: improve backdrop blur quality#8
BumpyClock merged 4 commits into
mainfrom
fix/backdrop-blur-quality

Conversation

@BumpyClock

@BumpyClock BumpyClock commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Replace backend-specific backdrop blur approximations with one bounded, phase-stable blur pyramid across Metal, WGPU, and Direct3D.

Changes

  • solve a shared six-level downsample/upsample plan from the requested three-sigma support
  • use fixed four-tap downsampling plus a weighted eight-tap tent upsample in linear light
  • preserve alpha through premultiplied intermediates and guard zero-alpha unpremultiply
  • cluster overlapping padded blur regions, skip offscreen work, preserve draw order/source epochs, and quantize reusable scratch textures
  • use linear UNORM WGPU intermediates even when the presentation surface is sRGB
  • make invalid/non-positive blur values true no-ops and document the roughly 3.7-device-pixel minimum positive support

Testing

  • cargo fmt --all -- --check
  • cargo metadata --locked --format-version 1
  • cargo check --locked --workspace --all-targets
  • cargo test --locked -p gpui --features test-support — 277 tests passed
  • cargo test -p gpui_macos --lib — 10 tests passed
  • cargo test -p gpui_wgpu --lib — 38 tests passed, including WGSL validation
  • cargo test -p gpui_windows — host-side crate/doc gate passed; native D3D execution unavailable on macOS
  • cargo build -p gpui --example backdrop_blur
  • targeted clippy completed; deny-warnings remains blocked by pre-existing crates/util/src/util.rs:188 (manual_clear)
  • 64-phase impulse simulation over radii 4–407: zero centroid shift and measured sigma near target through level transitions
  • Metal example and gpui-component light/dark A/B captures reviewed; no halos, banding, leakage, or text-contrast regression

Review notes

Claude Fable and Oracle browser reviews challenged the original kernel/variance model; the final exact-moment 4+8 design incorporates that feedback. Independent Codex autoreview reports no actionable findings. RGBA16Float and a direct sub-minimum-radius path are intentionally deferred: either doubles pyramid memory or expands scope without evidence of a menu/dropdown blocker.

Texture reuse stays bounded by copy-safe cluster extents. Rare alternating edge-cluster sizes may reallocate rather than process an oversized retained texture; pooling is separate follow-up territory.

Note

Improve backdrop blur quality with multi-pass downsample/upsample pyramid

  • Replaces single-pass blur with a multi-pass Laplacian pyramid approach: explicit downsample and upsample passes are executed per cluster, with blur intensity controlled by a per-plan sample_distance rather than a fixed offset.
  • Introduces BackdropBlurPlan in scene.rs to compute pass count and sample distance for a given radius using binary search over variance; helper functions for clustering, scratch bounds, texture reuse, and plan grouping are extracted to gpui and shared across all three renderer backends (Metal, WGPU, DirectX).
  • Upsample shaders now use an 8-tap weighted tent filter (4 axial + 4 diagonal samples); downsample shaders use a fixed 0.75-texel offset; final compositing corrects a double-premultiplication bug by converting to straight alpha before modulating coverage.
  • Intermediate blur textures are allocated in linear (non-sRGB) color space to avoid gamma errors during multi-pass filtering.
  • Risk: Blur output appearance will change for all existing uses of backdrop_blur; the number and size of intermediate GPU textures allocated per frame increases proportionally with blur radius and cluster count.

Macroscope summarized 3d37709.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Backdrop blur now uses a shared BackdropBlurPlan for bounded pass selection, padding, and sampling. Metal, WebGPU, and DirectX renderers use plan-based clustering and multi-pass downsample/upsample pipelines, with updated shader parameters, resource management, validation, and documentation.

Changes

Backdrop blur pipeline

Layer / File(s) Summary
Blur plan contract and API behavior
crates/gpui/src/scene.rs, crates/gpui/src/styled.rs, crates/gpui/src/window.rs
Defines BackdropBlurPlan, validates blur inputs, computes padding and sample distances, and documents the updated blur semantics.
Metal clustered blur rendering
crates/gpui_macos/src/metal_renderer.rs, crates/gpui_macos/src/shaders.metal
Groups overlapping blur regions by plan, manages reusable textures, runs no-blend downsample/upsample passes, and updates Metal sampling and alpha handling.
WebGPU pyramid resources and passes
crates/gpui_wgpu/src/wgpu_renderer.rs, crates/gpui_wgpu/src/shaders.wgsl
Adds per-level blur resources and dedicated pipelines, renders plan-based pass chains, and introduces WGSL downsample and upsample shaders.
DirectX plan-driven blur execution
crates/gpui_windows/src/directx_renderer.rs
Uses BackdropBlurPlan for batching, padding, pass execution, shader parameters, blend states, and tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Window
  participant BackdropBlurPlan
  participant Renderer
  participant BlurShaders
  participant TargetTexture
  Window->>Renderer: submit positive finite blur radius
  Renderer->>BackdropBlurPlan: derive passes, padding, sample_distance
  Renderer->>TargetTexture: snapshot backdrop source
  Renderer->>BlurShaders: execute downsample and upsample passes
  BlurShaders-->>Renderer: return blurred view
  Renderer->>TargetTexture: composite blurred output
Loading

Possibly related PRs

  • BumpyClock/gpui#5: Modifies overlapping backdrop-blur shader logic and BackdropBlur parameter wiring.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and clearly describes the main change: improving backdrop blur quality.
Description check ✅ Passed The description is directly related to the backdrop blur overhaul and matches the changeset.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/backdrop-blur-quality

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/gpui_macos/src/metal_renderer.rs (1)

637-666: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Backdrop texture reuse rebuilds the whole pyramid whenever a cluster's max_size shrinks. Both backends copied the same can_reuse_backdrop_texture predicate, which rejects an existing texture that is larger than the current cluster's max_size; the fallback then reallocates the backdrop texture and every downsample/upsample level clamped down to that bound. Because max_backdrop_texture_size is viewport - cluster_origin, clusters at different origins in a single frame alternately shrink and regrow the pyramid every frame. The upper bound itself is needed (it keeps the region copy inside the surface), so the fix is to stop letting it force a downsize — reuse on required_size alone and clamp the scratch bounds to max_size, or only shrink when the excess is large.

  • crates/gpui_macos/src/metal_renderer.rs#L637-L666: drop the current <= max condition from can_reuse_backdrop_texture and rely on fit_backdrop_scratch_bounds to keep the sampled region inside the viewport.
  • crates/gpui_wgpu/src/wgpu_renderer.rs#L1404-L1425: apply the same change, and clamp scratch_bounds.texture_size to max_size before copy_texture_to_texture so the region copy stays in bounds.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gpui_macos/src/metal_renderer.rs` around lines 637 - 666, Stop
backdrop texture pyramids from resizing when only max_size shrinks: in
crates/gpui_macos/src/metal_renderer.rs lines 637-666, update
can_reuse_backdrop_texture to require only current_size >= required_size and
rely on fit_backdrop_scratch_bounds for viewport safety; in
crates/gpui_wgpu/src/wgpu_renderer.rs lines 1404-1425, apply the same reuse
predicate and clamp scratch_bounds.texture_size to max_size before
copy_texture_to_texture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gpui_macos/src/metal_renderer.rs`:
- Around line 1451-1465: The backdrop blur pass derives level_sizes from the
fitted scratch bounds, which can differ from the allocated pyramid dimensions.
Update the Metal backdrop blur flow around backdrop_blur_downsample_textures,
upsample_textures, and render_backdrop_blur_texture_for_plan to use
self.backdrop_blur_level_sizes for pass texture dimensions, or otherwise pass
allocated dimensions separately from logical extents; preserve scratch_bounds
for planning while ensuring sampling uses the actual allocated level sizes.

In `@crates/gpui_wgpu/src/wgpu_renderer.rs`:
- Around line 2896-2954: Move the shared pure backdrop-blur
helpers—backdrop_blur_padding, backdrop_blur_level_sizes_for,
backdrop_blur_plan_for_radius, backdrop_blur_plan_groups,
backdrop_source_bounds, backdrop_blur_clusters, and
can_reuse_backdrop_texture—into the common scene module alongside
BackdropBlurPlan, then update the WGPU, Metal, and DirectX backends to use those
shared symbols and remove their local copies. Consolidate the duplicated unit
tests with the shared implementation, leaving only backend-specific
resource-binding logic in each renderer.
- Around line 2493-2520: The first backdrop blur pass in the loop around
draw_backdrop_blur_pass double-decodes sRGB input when backdrop_view uses an
sRGB surface format. Ensure the level == 0 input uses a linear-format view of
backdrop_view, or otherwise disable the shader’s manual sRGB decode only for
that input, while preserving existing decoding for subsequent blur passes.

In `@crates/gpui_windows/src/directx_renderer.rs`:
- Around line 605-621: Skip zero-pass plans before invoking draw_backdrop_blurs.
In the blur grouping logic around backdrop_blur_plan_for_radius and
current_plan, detect plan.passes == 0 and bypass drawing for that group while
preserving normal processing for plans with passes greater than zero.
- Around line 3441-3466: Update DirectXRenderer::backdrop_blur_clusters and its
regression coverage to handle interleaved transitive overlaps without reordering
source epochs: add an A, B, C case where C overlaps A, B is disjoint, and assert
the resulting draw order remains contiguous and source-preserving. Ensure
merging does not remove A and append A/C after B; retain the original sequence
position of merged clusters rather than relying only on sorting by first index.

---

Outside diff comments:
In `@crates/gpui_macos/src/metal_renderer.rs`:
- Around line 637-666: Stop backdrop texture pyramids from resizing when only
max_size shrinks: in crates/gpui_macos/src/metal_renderer.rs lines 637-666,
update can_reuse_backdrop_texture to require only current_size >= required_size
and rely on fit_backdrop_scratch_bounds for viewport safety; in
crates/gpui_wgpu/src/wgpu_renderer.rs lines 1404-1425, apply the same reuse
predicate and clamp scratch_bounds.texture_size to max_size before
copy_texture_to_texture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 70758928-865e-4184-ba68-48178023bd09

📥 Commits

Reviewing files that changed from the base of the PR and between 2a03ae6 and 5569a8c.

⛔ Files ignored due to path filters (1)
  • crates/gpui_windows/src/shaders.hlsl is excluded by !**/*.hlsl
📒 Files selected for processing (8)
  • crates/gpui/src/scene.rs
  • crates/gpui/src/styled.rs
  • crates/gpui/src/window.rs
  • crates/gpui_macos/src/metal_renderer.rs
  • crates/gpui_macos/src/shaders.metal
  • crates/gpui_wgpu/src/shaders.wgsl
  • crates/gpui_wgpu/src/wgpu_renderer.rs
  • crates/gpui_windows/src/directx_renderer.rs

Comment thread crates/gpui_macos/src/metal_renderer.rs Outdated
Comment thread crates/gpui_wgpu/src/wgpu_renderer.rs
Comment thread crates/gpui_wgpu/src/wgpu_renderer.rs Outdated
Comment thread crates/gpui_windows/src/directx_renderer.rs Outdated
Comment thread crates/gpui_windows/src/directx_renderer.rs Outdated
Centralize pure blur planning, preserve source epochs, prevent duplicate sRGB decoding, and retain safe texture allocations across backend clusters.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/gpui_wgpu/src/wgpu_renderer.rs (1)

2776-2874: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Duplicated scratch-bounds/fit/prepare helpers.

Same byte-identical duplication as crates/gpui_macos/src/metal_renderer.rs (lines 1700-1795) and crates/gpui_windows/src/directx_renderer.rs (lines 1026-1116); see consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gpui_wgpu/src/wgpu_renderer.rs` around lines 2776 - 2874, Remove the
duplicated backdrop scratch-bounds helpers from the renderer and reuse the
consolidated shared implementation used by the macOS and Windows renderers.
Update references to backdrop_scratch_bounds, max_backdrop_texture_size,
fit_backdrop_scratch_bounds, and prepare_backdrop_blurs to resolve through that
shared implementation while preserving their current behavior.
crates/gpui_macos/src/metal_renderer.rs (1)

1700-1795: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

backdrop_scratch_bounds, max_backdrop_texture_size, fit_backdrop_scratch_bounds, and prepare_backdrop_blurs remain duplicated across all three backends.

These four functions are byte-identical in crates/gpui_wgpu/src/wgpu_renderer.rs (lines 2776-2874) and crates/gpui_windows/src/directx_renderer.rs (lines 1026-1116). The prior review already moved backdrop_blur_padding, backdrop_blur_level_sizes_for, backdrop_blur_plan_groups, backdrop_source_bounds, backdrop_blur_clusters, and can_reuse_backdrop_texture into shared gpui helpers for exactly this reason; these remaining four pure functions over BackdropScratchBounds/Size<DevicePixels> should follow the same path to prevent future divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gpui_macos/src/metal_renderer.rs` around lines 1700 - 1795, The four
backdrop scratch helpers remain duplicated across the Metal, WGPU, and DirectX
backends. Move backdrop_scratch_bounds, max_backdrop_texture_size,
fit_backdrop_scratch_bounds, and prepare_backdrop_blurs into the shared gpui
helpers alongside the previously centralized backdrop functions, then remove the
backend-local copies and update each backend to use the shared implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gpui/src/scene.rs`:
- Around line 914-960: Update backdrop_source_bounds to return None before
calculating bounds when blur.blur_radius.0 is zero, negative, or NaN, so no-op
radii are excluded from backdrop_blur_clusters. Preserve existing bounds
clipping for valid radii, and add coverage for zero, negative, and NaN inputs.

---

Outside diff comments:
In `@crates/gpui_macos/src/metal_renderer.rs`:
- Around line 1700-1795: The four backdrop scratch helpers remain duplicated
across the Metal, WGPU, and DirectX backends. Move backdrop_scratch_bounds,
max_backdrop_texture_size, fit_backdrop_scratch_bounds, and
prepare_backdrop_blurs into the shared gpui helpers alongside the previously
centralized backdrop functions, then remove the backend-local copies and update
each backend to use the shared implementations.

In `@crates/gpui_wgpu/src/wgpu_renderer.rs`:
- Around line 2776-2874: Remove the duplicated backdrop scratch-bounds helpers
from the renderer and reuse the consolidated shared implementation used by the
macOS and Windows renderers. Update references to backdrop_scratch_bounds,
max_backdrop_texture_size, fit_backdrop_scratch_bounds, and
prepare_backdrop_blurs to resolve through that shared implementation while
preserving their current behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 37c534e0-7b91-4374-8bbb-1a41edf9544d

📥 Commits

Reviewing files that changed from the base of the PR and between 5569a8c and bfbe11a.

📒 Files selected for processing (4)
  • crates/gpui/src/scene.rs
  • crates/gpui_macos/src/metal_renderer.rs
  • crates/gpui_wgpu/src/wgpu_renderer.rs
  • crates/gpui_windows/src/directx_renderer.rs

Comment thread crates/gpui/src/scene.rs
Keep invalid radii out of source clustering and centralize scratch-bound preparation so every renderer preserves the same viewport and reuse rules.
@BumpyClock

Copy link
Copy Markdown
Owner Author

Addressed the two outside-diff scratch-helper findings in 0703fd4. BackdropScratchBounds, backdrop_scratch_bounds, max_backdrop_texture_size, fit_backdrop_scratch_bounds, and prepare_backdrop_blurs now live in the shared scene module. Metal, WGPU, and DirectX use the common implementation; backend-local copies are removed. Added shared bounds/max/fit/preparation regression coverage. Strict touched-crate clippy and full CI-equivalent tests pass.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gpui/src/scene.rs`:
- Around line 976-993: In backdrop_scratch_bounds, bind the result of
blurs.first()? once and reuse that BackdropBlur reference when initializing
bounds, instead of calling blurs.first()? separately for bounds and blur_radius.
Keep the subsequent union logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ce1767b8-0ac3-4129-a35d-e1ab982d0963

📥 Commits

Reviewing files that changed from the base of the PR and between bfbe11a and 0703fd4.

📒 Files selected for processing (4)
  • crates/gpui/src/scene.rs
  • crates/gpui_macos/src/metal_renderer.rs
  • crates/gpui_wgpu/src/wgpu_renderer.rs
  • crates/gpui_windows/src/directx_renderer.rs

Comment thread crates/gpui/src/scene.rs
Bind the cluster head once so scratch-bound initialization cannot drift between duplicate lookups.
@BumpyClock
BumpyClock merged commit 6646f57 into main Jul 28, 2026
8 checks passed
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