tessera_ui_basic_components/pipelines/blur/
pipeline.rs

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