tessera_ui_basic_components/pipelines/
contrast.rs

1use tessera_ui::{
2    BarrierRequirement, PxRect,
3    compute::{ComputeResourceRef, resource::ComputeResourceManager},
4    renderer::compute::{ComputablePipeline, command::ComputeCommand},
5    wgpu::{self, util::DeviceExt},
6};
7
8// --- Command ---
9
10/// Command to apply a contrast adjustment using a pre-calculated mean luminance.
11///
12/// # Parameters
13/// - `contrast`: The contrast adjustment factor.
14/// - `mean_result_handle`: Handle to the buffer containing mean luminance data.
15///
16/// # Example
17/// ```rust,ignore
18/// use tessera_ui_basic_components::pipelines::contrast::ContrastCommand;
19/// let command = ContrastCommand::new(1.2, mean_result_handle);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct ContrastCommand {
23    /// The contrast adjustment factor.
24    pub contrast: f32,
25    /// A handle to the `wgpu::Buffer` containing the mean luminance data.
26    pub mean_result_handle: ComputeResourceRef,
27}
28
29impl ContrastCommand {
30    /// Creates a new `ContrastCommand`.
31    ///
32    /// # Parameters
33    /// - `contrast`: The contrast adjustment factor.
34    /// - `mean_result_handle`: Handle to the buffer containing mean luminance data.
35    pub fn new(contrast: f32, mean_result_handle: ComputeResourceRef) -> Self {
36        Self {
37            contrast,
38            mean_result_handle,
39        }
40    }
41}
42
43impl ComputeCommand for ContrastCommand {
44    fn barrier(&self) -> tessera_ui::BarrierRequirement {
45        BarrierRequirement::ZERO_PADDING_LOCAL
46    }
47}
48
49// --- Pipeline ---
50
51#[repr(C)]
52#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
53struct Uniforms {
54    contrast: f32,
55    area_x: u32,
56    area_y: u32,
57    area_width: u32,
58    area_height: u32,
59}
60
61/// Pipeline for applying contrast adjustment to an image using a compute shader.
62///
63/// # Example
64/// ```rust,ignore
65/// use tessera_ui_basic_components::pipelines::contrast::ContrastPipeline;
66/// let pipeline = ContrastPipeline::new(&device);
67/// ```
68pub struct ContrastPipeline {
69    pipeline: wgpu::ComputePipeline,
70    bind_group_layout: wgpu::BindGroupLayout,
71}
72
73impl ContrastPipeline {
74    pub fn new(device: &wgpu::Device) -> Self {
75        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
76            label: Some("Contrast Shader"),
77            source: wgpu::ShaderSource::Wgsl(include_str!("contrast/contrast.wgsl").into()),
78        });
79
80        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
81            entries: &[
82                // 0: Uniforms
83                wgpu::BindGroupLayoutEntry {
84                    binding: 0,
85                    visibility: wgpu::ShaderStages::COMPUTE,
86                    ty: wgpu::BindingType::Buffer {
87                        ty: wgpu::BufferBindingType::Uniform,
88                        has_dynamic_offset: false,
89                        min_binding_size: Some(std::num::NonZeroU64::new(20).unwrap()),
90                    },
91                    count: None,
92                },
93                // 1: Source Texture
94                wgpu::BindGroupLayoutEntry {
95                    binding: 1,
96                    visibility: wgpu::ShaderStages::COMPUTE,
97                    ty: wgpu::BindingType::Texture {
98                        sample_type: wgpu::TextureSampleType::Float { filterable: false },
99                        view_dimension: wgpu::TextureViewDimension::D2,
100                        multisampled: false,
101                    },
102                    count: None,
103                },
104                // 2: Destination Texture (Storage)
105                wgpu::BindGroupLayoutEntry {
106                    binding: 2,
107                    visibility: wgpu::ShaderStages::COMPUTE,
108                    ty: wgpu::BindingType::StorageTexture {
109                        access: wgpu::StorageTextureAccess::WriteOnly,
110                        format: wgpu::TextureFormat::Rgba8Unorm,
111                        view_dimension: wgpu::TextureViewDimension::D2,
112                    },
113                    count: None,
114                },
115                // 3: Mean Result Buffer (Storage)
116                wgpu::BindGroupLayoutEntry {
117                    binding: 3,
118                    visibility: wgpu::ShaderStages::COMPUTE,
119                    ty: wgpu::BindingType::Buffer {
120                        ty: wgpu::BufferBindingType::Storage { read_only: true },
121                        has_dynamic_offset: false,
122                        min_binding_size: Some(std::num::NonZeroU64::new(8).unwrap()),
123                    },
124                    count: None,
125                },
126            ],
127            label: Some("contrast_bind_group_layout"),
128        });
129
130        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
131            label: Some("Contrast Pipeline Layout"),
132            bind_group_layouts: &[&bind_group_layout],
133            push_constant_ranges: &[],
134        });
135
136        let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
137            label: Some("Contrast Pipeline"),
138            layout: Some(&pipeline_layout),
139            module: &shader,
140            entry_point: Some("main"),
141            compilation_options: Default::default(),
142            cache: None,
143        });
144
145        Self {
146            pipeline,
147            bind_group_layout,
148        }
149    }
150}
151
152impl ComputablePipeline<ContrastCommand> for ContrastPipeline {
153    /// Dispatches the contrast adjustment compute shader.
154    /// - `target_area`: The area of the output texture to be affected (PxRect).
155    fn dispatch(
156        &mut self,
157        device: &wgpu::Device,
158        _queue: &wgpu::Queue,
159        config: &wgpu::SurfaceConfiguration,
160        compute_pass: &mut wgpu::ComputePass<'_>,
161        command: &ContrastCommand,
162        resource_manager: &mut ComputeResourceManager,
163        target_area: PxRect,
164        input_view: &wgpu::TextureView,
165        output_view: &wgpu::TextureView,
166    ) {
167        if let Some(mean_buffer) = resource_manager.get(&command.mean_result_handle) {
168            let uniforms = Uniforms {
169                contrast: command.contrast,
170                area_x: target_area.x.0 as u32,
171                area_y: target_area.y.0 as u32,
172                area_width: target_area.width.0 as u32,
173                area_height: target_area.height.0 as u32,
174            };
175
176            let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
177                label: Some("Contrast Uniform Buffer"),
178                contents: bytemuck::cast_slice(&[uniforms]),
179                usage: wgpu::BufferUsages::UNIFORM,
180            });
181
182            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
183                layout: &self.bind_group_layout,
184                entries: &[
185                    wgpu::BindGroupEntry {
186                        binding: 0,
187                        resource: uniform_buffer.as_entire_binding(),
188                    },
189                    wgpu::BindGroupEntry {
190                        binding: 1,
191                        resource: wgpu::BindingResource::TextureView(input_view),
192                    },
193                    wgpu::BindGroupEntry {
194                        binding: 2,
195                        resource: wgpu::BindingResource::TextureView(output_view),
196                    },
197                    wgpu::BindGroupEntry {
198                        binding: 3,
199                        resource: mean_buffer.as_entire_binding(),
200                    },
201                ],
202                label: Some("contrast_bind_group"),
203            });
204
205            compute_pass.set_pipeline(&self.pipeline);
206            compute_pass.set_bind_group(0, &bind_group, &[]);
207            compute_pass.dispatch_workgroups(
208                config.width.div_ceil(8),
209                config.height.div_ceil(8),
210                1,
211            );
212        }
213    }
214}