tessera_ui_basic_components/pipelines/blur/
command.rs

1use tessera_ui::{ComputeCommand, Px, renderer::command::BarrierRequirement};
2
3/// A synchronous command to execute a gaussian blur.
4/// `BlurCommand` describes a single directional blur pass.
5#[derive(Debug, Clone, PartialEq)]
6pub struct BlurCommand {
7    /// Blur radius.
8    pub radius: f32,
9    /// Blur direction: (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical.
10    pub direction: (f32, f32),
11}
12
13impl BlurCommand {
14    /// Convenience helper for building a horizontal blur pass.
15    pub fn horizontal(radius: f32) -> Self {
16        Self {
17            radius,
18            direction: (1.0, 0.0),
19        }
20    }
21
22    /// Convenience helper for building a vertical blur pass.
23    pub fn vertical(radius: f32) -> Self {
24        Self {
25            radius,
26            direction: (0.0, 1.0),
27        }
28    }
29}
30
31/// A compute command that runs two directional blur passes (typically horizontal + vertical)
32/// within a single dispatch sequence.
33#[derive(Debug, Clone, PartialEq)]
34pub struct DualBlurCommand {
35    pub passes: [BlurCommand; 2],
36}
37
38pub fn downscale_factor_for_radius(radius: f32) -> u32 {
39    if radius <= 6.0 {
40        1
41    } else if radius <= 18.0 {
42        2
43    } else {
44        4
45    }
46}
47
48impl DualBlurCommand {
49    pub fn new(passes: [BlurCommand; 2]) -> Self {
50        Self { passes }
51    }
52
53    /// Creates a dual blur command with horizontal and vertical passes using the same radius/padding.
54    pub fn horizontal_then_vertical(radius: f32) -> Self {
55        Self {
56            passes: [
57                BlurCommand::horizontal(radius),
58                BlurCommand::vertical(radius),
59            ],
60        }
61    }
62}
63
64impl ComputeCommand for DualBlurCommand {
65    fn barrier(&self) -> BarrierRequirement {
66        // Calculate maximum radius from both passes to determine required padding
67        // The barrier padding must be at least as large as the blur radius to ensure
68        // all pixels needed for the blur are available in the captured background
69        let max_radius = self
70            .passes
71            .iter()
72            .map(|pass| pass.radius)
73            .fold(0.0f32, f32::max);
74
75        let downscale = downscale_factor_for_radius(max_radius) as f32;
76        // When downsampling, each texel covers a larger source region, so extend
77        // the barrier padding proportionally to the chosen downscale factor.
78        let sampling_padding = (max_radius * downscale).ceil() as i32;
79
80        // The sampling padding is the actual padding needed for the blur effect.
81        // The renderer still relies on the component bounds for dependency checks,
82        // so orthogonal blur components can batch even if their sampling regions overlap.
83        BarrierRequirement::uniform_padding_local(Px(sampling_padding))
84    }
85}