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