tessera_ui_basic_components/pipelines/blur/
pipeline.rs

1use encase::{ShaderType, UniformBuffer};
2use tessera_ui::{
3    PxRect,
4    renderer::compute::ComputablePipeline,
5    wgpu::{self, util::DeviceExt},
6};
7
8use super::command::BlurCommand;
9
10#[derive(ShaderType)]
11struct BlurUniforms {
12    radius: f32,
13    direction_x: f32,
14    direction_y: f32,
15    area_x: u32,
16    area_y: u32,
17    area_width: u32,
18    area_height: u32,
19}
20
21pub struct BlurPipeline {
22    pipeline: wgpu::ComputePipeline,
23    bind_group_layout: wgpu::BindGroupLayout,
24}
25
26impl BlurPipeline {
27    pub fn new(device: &wgpu::Device) -> Self {
28        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
29            label: Some("Blur Shader"),
30            source: wgpu::ShaderSource::Wgsl(include_str!("blur.wgsl").into()),
31        });
32
33        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
34            entries: &[
35                // 0: Uniforms
36                wgpu::BindGroupLayoutEntry {
37                    binding: 0,
38                    visibility: wgpu::ShaderStages::COMPUTE,
39                    ty: wgpu::BindingType::Buffer {
40                        ty: wgpu::BufferBindingType::Uniform,
41                        has_dynamic_offset: false,
42                        min_binding_size: None,
43                    },
44                    count: None,
45                },
46                // 1: Source Texture (Sampled)
47                wgpu::BindGroupLayoutEntry {
48                    binding: 1,
49                    visibility: wgpu::ShaderStages::COMPUTE,
50                    ty: wgpu::BindingType::Texture {
51                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
52                        view_dimension: wgpu::TextureViewDimension::D2,
53                        multisampled: false,
54                    },
55                    count: None,
56                },
57                // 2: Destination Texture (Storage)
58                wgpu::BindGroupLayoutEntry {
59                    binding: 2,
60                    visibility: wgpu::ShaderStages::COMPUTE,
61                    ty: wgpu::BindingType::StorageTexture {
62                        access: wgpu::StorageTextureAccess::WriteOnly,
63                        // This format needs to match the destination texture format.
64                        // We assume a common format here, but a robust implementation might
65                        // create pipelines on the fly based on the texture's actual format.
66                        format: wgpu::TextureFormat::Rgba8Unorm,
67                        view_dimension: wgpu::TextureViewDimension::D2,
68                    },
69                    count: None,
70                },
71            ],
72            label: Some("blur_bind_group_layout"),
73        });
74
75        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
76            label: Some("Blur Pipeline Layout"),
77            bind_group_layouts: &[&bind_group_layout],
78            push_constant_ranges: &[],
79        });
80
81        let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
82            label: Some("Blur Pipeline"),
83            layout: Some(&pipeline_layout),
84            module: &shader,
85            entry_point: Some("main"),
86            compilation_options: Default::default(),
87            cache: None,
88        });
89
90        Self {
91            pipeline,
92            bind_group_layout,
93        }
94    }
95}
96
97impl ComputablePipeline<BlurCommand> for BlurPipeline {
98    /// Dispatches the blur compute shader.
99    /// - `target_area`: The area of the output texture to be affected (PxRect).
100    fn dispatch(
101        &mut self,
102        device: &wgpu::Device,
103        _queue: &wgpu::Queue,
104        config: &wgpu::SurfaceConfiguration,
105        compute_pass: &mut wgpu::ComputePass<'_>,
106        command: &BlurCommand,
107        _resource_manager: &mut tessera_ui::ComputeResourceManager,
108        target_area: PxRect,
109        input_view: &wgpu::TextureView,
110        output_view: &wgpu::TextureView,
111    ) {
112        let uniforms = BlurUniforms {
113            radius: command.radius,
114            direction_x: command.direction.0,
115            direction_y: command.direction.1,
116            area_x: target_area.x.0 as u32,
117            area_y: target_area.y.0 as u32,
118            area_width: target_area.width.0 as u32,
119            area_height: target_area.height.0 as u32,
120        };
121        let mut buffer = UniformBuffer::new(Vec::new());
122        buffer.write(&uniforms).unwrap();
123        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
124            label: Some("Blur Uniform Buffer"),
125            contents: &buffer.into_inner(),
126            usage: wgpu::BufferUsages::UNIFORM,
127        });
128
129        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
130            layout: &self.bind_group_layout,
131            entries: &[
132                wgpu::BindGroupEntry {
133                    binding: 0,
134                    resource: uniform_buffer.as_entire_binding(),
135                },
136                wgpu::BindGroupEntry {
137                    binding: 1,
138                    resource: wgpu::BindingResource::TextureView(input_view),
139                },
140                wgpu::BindGroupEntry {
141                    binding: 2,
142                    resource: wgpu::BindingResource::TextureView(output_view),
143                },
144            ],
145            label: Some("blur_bind_group"),
146        });
147
148        compute_pass.set_pipeline(&self.pipeline);
149        compute_pass.set_bind_group(0, &bind_group, &[]);
150        compute_pass.dispatch_workgroups(config.width.div_ceil(8), config.height.div_ceil(8), 1);
151    }
152}