tessera_ui_basic_components/pipelines/
contrast.rs

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