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}