gpui: Add support for inset shadow (#57685)

This PR adds support for inset shadows in the box shadow through the
`inset: true` field. It includes support for both the macOS as well WGSL
shaders. For now, there is no immediate application of it in Zed, so
nothing should change in the app.

<img width="600" alt="Screenshot 2026-05-25 at 8  46@2x"
src="https://github.com/user-attachments/assets/db564a6b-8af5-491a-a573-17c060a3647c"
/>

Run the example above with `cargo run --example shadow -p gpui`.

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Danilo LealandAgus Zubiaga authored and GitHub committed 2026-05-26 14:32:45 +00:00
1 parent e2d07eaa0c
commit d7b27f9ead
18 files changed
+341 -53

No files matched your search

+43 -18
View File
@@ -854,6 +854,10 @@ struct Shadow {
Corners corner_radii;
Bounds content_mask;
Hsla color;
Bounds element_bounds;
Corners element_corner_radii;
uint inset;
uint pad; // align to 8 bytes
};
struct ShadowVertexOutput {
@@ -875,10 +879,16 @@ ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
Shadow shadow = shadows[shadow_id];
float margin = 3.0 * shadow.blur_radius;
Bounds bounds = shadow.bounds;
bounds.origin -= margin;
bounds.size += 2.0 * margin;
Bounds bounds;
if (shadow.inset != 0u) {
bounds = shadow.element_bounds;
} else {
// Leave room for the gaussian tail outside the shadow rect.
float margin = 3.0 * shadow.blur_radius;
bounds = shadow.bounds;
bounds.origin -= margin;
bounds.size += 2.0 * margin;
}
float4 device_position = to_device_position(unit_vertex, bounds);
float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
@@ -901,21 +911,36 @@ float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
float2 point0 = input.position.xy - center;
float corner_radius = pick_corner_radius(point0, shadow.corner_radii);
// The signal is only non-zero in a limited range, so don't waste samples
float low = point0.y - half_size.y;
float high = point0.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
float alpha;
if (shadow.blur_radius == 0.) {
float distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii);
alpha = saturate(0.5 - distance);
} else {
// The signal is only non-zero in a limited range, so don't waste samples
float low = point0.y - half_size.y;
float high = point0.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
float alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
corner_radius, half_size) *
gaussian(y, shadow.blur_radius) * step;
y += step;
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
corner_radius, half_size) *
gaussian(y, shadow.blur_radius) * step;
y += step;
}
}
if (shadow.inset != 0u) {
// The inset shadow is the complement of the (blurred) hole rect, clipped to the element.
// `saturate(0.5 - d)` gives a 1-pixel antialiased edge: d <= -0.5 -> 1, d >= 0.5 -> 0.
alpha = 1.0 - alpha;
float element_distance = quad_sdf(input.position.xy, shadow.element_bounds,
shadow.element_corner_radii);
alpha *= saturate(0.5 - element_distance);
}
return input.color * float4(1., 1., 1., alpha);