Repository navigation
[ Rendering Bug ] Backdrop filter output is not clipped to the rounded border box #901
Description
An element with both border-radius and backdrop-filter: blur(...) applies the blur outside its rounded border
shape when rendered with the AnyRender Skia backend. The filtered output appears in the rectangular corner areas
bounded by the element's border-box bounding rectangle.
The backdrop filter may sample pixels outside the element so that blur has enough input, but its final composited
output should remain clipped to the element's rounded border box.
Minimal reproduction
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rounded backdrop-filter clip reproduction</title>
<style>
html, body { height: 100%; margin: 0; }
body { display: grid; place-items: center; background: #111827; }
.backdrop {
position: relative;
width: 360px;
height: 220px;
background: conic-gradient(
#ef476f 25%, #ffd166 0 50%, #118ab2 0 75%, #06d6a0 0
) 0 0 / 32px 32px;
}
.glass {
position: absolute;
inset: 40px 60px;
box-sizing: border-box;
background: rgb(16 26 42 / 45%);
border: 4px solid white;
border-radius: 52px 12px;
backdrop-filter: blur(12px);
}
</style>
</head>
<body>
<div class="backdrop"><div class="glass"></div></div>
</body>
</html>Steps to reproduce
- Render the HTML above with Blitz using the AnyRender Skia backend.
- Inspect the checkerboard immediately outside the rounded corners of the white glass panel.
- Compare the result with a browser rendering of the same HTML.
Actual result
The checkerboard is blurred in the rectangular corner areas outside the panel's rounded border. The visible filter
output follows the axis-aligned border-box bounds instead of the rounded border shape.
Expected result
Only pixels inside the rounded white border are composited with the blurred backdrop. Pixels outside the rounded
corners remain sharp and unchanged. No overflow: hidden should be required on the filtered element or its parent.
Possible implementation location
Based on reading the current code, the likely location of the problem appears to be
BlitzDomPainter::render_element in packages/blitz-paint/src/render.rs. That function derives the effect-layer
clip with:
let mut effect_layer_clip = cx.frame.border_box_path().bounding_box();One possible cause is that bounding_box() may discard the rounded geometry before the clip is passed through
PaintScene::push_layer. The Skia backend appears to clip the saved layer using the shape it receives, but the shape
may already be a plain rectangle at that point.
A potential fix may belong in packages/blitz-paint/src/render.rs, specifically
BlitzDomPainter::render_element. Backdrop sampling expansion and backdrop output clipping may need to remain
separate concepts: the exact rounded border path could be retained for backdrop-filter output while the blur operation
is still allowed to read the required pixels outside that path. If regular filter and backdrop-filter are
present together, separate nested effect layers may be required.
This explanation is a hypothesis from code inspection and has not yet been confirmed by implementing and validating a
fix.
Activity
yinnho commentedon Sep 15, 2026
We hit this exact surface in our Rust engine (diting, the renderer behind aginxbrowser) while implementing
backdrop-filter: blur()— same bug shape, and the fix converged on exactly the criterion described here: sampling may exceed the box, composited output must clip to the rounded border shape.What our implementation converged to, in case it's useful as a reference point:
Emit the filter as the element's first paint item. A backdrop-filter filters everything painted beneath the element, so pushing a
BackdropFilteritem before the element's own ink (box-shadows, background, border) means a flat-list painter gets the layering right without a separate compositing pass: the element's own ink then draws on top of the blurred backdrop.Snapshot the padded region, not the box.
pad = ceil(blur). A blur with zero padding reads clamped edge pixels and produces a frozen halo — the "may sample outside" requirement in this issue isn't just allowed, it's load-bearing.Premultiplied planes, one horizontal + one vertical box-blur pass (radius =
(blur * 0.5).round(), min 1). Two box-blur passes on the padded region are visually close to gaussian at these radii and cheap — forblur(6px)on an 80×40 box the whole composite runs in microseconds.Clip with a signed-distance test, not a rect. Per output pixel:
sd_rounded_box(p, half_extent - r, r) > 0 → keep the original backdrop pixel. This is the part that fixes the reported bug — the composite replaces pixels only inside the rounded shape; the corners of the bounding rectangle stay crisp, which is the exact regression in the screenshot.One gotcha worth flagging because it bites the same way the original bug does: clamp the per-corner radii to half the box extents before building the SDF. With something like
border-radius: 50%on a small element, an unclamped radius makes the signed field invert near the corners and the "fixed" clip immediately leaks paint outside the box again — the same visual bug reappearing through a different hole. We clampr = min(r, w/2, h/2)per corner at emission.Our pixel test mirrors this issue's criterion: on a red/blue split backdrop with a glass box of
border-radius: 20px, the interior seam mixes channels (blur working), the corner-cut zone is byte-identical to the input (clip working), and pixels outside the box are untouched.