1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//! Spatially varying backdrop blur.
//!
//! Unlike an alpha gradient drawn over a uniformly blurred surface, this
//! shader changes the sampling kernel radius at every fragment. It is intended
//! for edge-to-edge system-bar treatments where content should move smoothly
//! from sharp to fully frosted without a visible material boundary.
use crate::{RenderEffect, RuntimeShader, SubstrateSpec};
/// Axis and direction used to interpolate the blur radius.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum GradientBlurDirection {
/// The blur radius changes from the start radius at the left edge to the end radius at the
/// right edge.
LeftToRight,
/// The blur radius changes from the start radius at the right edge to the end radius at the
/// left edge.
RightToLeft,
/// The blur radius changes from the start radius at the top edge to the end radius at the
/// bottom edge.
#[default]
TopToBottom,
/// The blur radius changes from the start radius at the bottom edge to the end radius at the
/// top edge.
BottomToTop,
}
impl GradientBlurDirection {
fn uniform_code(self) -> f32 {
match self {
Self::LeftToRight => 0.0,
Self::RightToLeft => 1.0,
Self::TopToBottom => 2.0,
Self::BottomToTop => 3.0,
}
}
}
/// WGSL implementation of a per-fragment Gaussian-style blur kernel.
pub const GRADIENT_BLUR_WGSL: &str = runtime_shader_wgsl!("gradient_blur.wgsl");
/// The blur levels' shares of the wide radius: the renderer blurs the
/// capture by each, and the shader blends the sharp source and the levels
/// around the radius each fragment asks for.
pub const GRADIENT_BLUR_LEVELS: [f32; 3] = [1.0, 0.5, 0.25];
/// Build a spatially varying blur effect.
///
/// Radii are physical pixels. The radius interpolates smoothly across the
/// complete effect bounds in `direction`; callers normally reach this through
/// `Modifier::backdrop_gradient_blur`, which converts from dp automatically.
/// The shader declares three blurred substrates, the wide radius halved
/// twice, and realises every radius between as a blend of the level below
/// and the level above it.
pub fn gradient_blur_effect(
start_radius_px: f32,
end_radius_px: f32,
direction: GradientBlurDirection,
) -> RenderEffect {
let start_radius_px = start_radius_px.max(0.0);
let end_radius_px = end_radius_px.max(0.0);
let mut shader = RuntimeShader::new(GRADIENT_BLUR_WGSL);
shader.set_position_independent(true);
shader.set_float(0, start_radius_px);
shader.set_float(1, end_radius_px);
shader.set_float(2, direction.uniform_code());
let wide_radius_px = start_radius_px.max(end_radius_px);
shader.set_input_padding(wide_radius_px.ceil());
shader.set_batched_source(true);
shader.set_substrates(&GRADIENT_BLUR_LEVELS.map(|level| SubstrateSpec::Blur {
radius_px: wide_radius_px * level,
}));
RenderEffect::runtime_shader(shader)
}
#[cfg(test)]
#[path = "tests/gradient_blur_tests.rs"]
mod tests;