tessera_ui_basic_components/pipelines/
mean.rs

1//! # Mean Luminance Compute Pipeline
2//!
3//! This module implements a GPU-based compute pipeline for calculating the mean luminance of a texture.
4//! It provides both the pipeline and command abstractions for integrating mean luminance computation into Tessera UI rendering flows.
5//!
6//! ## Functionality
7//! - Calculates the average (mean) luminance of a given texture using a compute shader.
8//! - Exposes a [`MeanCommand`] for issuing the operation and a [`MeanPipeline`] for dispatching the compute workload.
9//!
10//! ## Typical Use Cases
11//! - Image processing and analysis
12//! - Tone mapping and exposure adaptation in UI or graphics applications
13//! - Adaptive UI rendering based on scene brightness
14//!
15//! ## Integration
16//! Register and use this pipeline when average brightness information is required for further rendering or UI logic.
17//! See [`MeanCommand`] and [`MeanPipeline`] for usage examples.
18
19use tessera_ui::{
20    BarrierRequirement, PxRect,
21    compute::{ComputeResourceRef, resource::ComputeResourceManager},
22    renderer::compute::{ComputablePipeline, command::ComputeCommand},
23    wgpu::{self, util::DeviceExt},
24};
25
26// --- Command ---
27
28/// A command to calculate the mean luminance of the input texture.
29#[derive(Debug, Clone, Copy, PartialEq)]
30/// Command to calculate the mean luminance of the input texture.
31///
32/// # Example
33/// ```rust,ignore
34/// use tessera_ui_basic_components::pipelines::mean::MeanCommand;
35/// let command = MeanCommand::new(&device, &mut resource_manager);
36/// ```
37pub struct MeanCommand {
38    result_buffer_ref: ComputeResourceRef,
39}
40
41impl MeanCommand {
42    /// Creates a new `MeanCommand` and allocates a result buffer.
43    ///
44    /// # Parameters
45    /// - `gpu`: The wgpu device.
46    /// - `compute_resource_manager`: Resource manager for compute buffers.
47    pub fn new(gpu: &wgpu::Device, compute_resource_manager: &mut ComputeResourceManager) -> Self {
48        let result_buffer = gpu.create_buffer(&wgpu::BufferDescriptor {
49            label: Some("Mean Result Buffer"),
50            size: 8, // two u32s: total_luminance, total_pixels
51            usage: wgpu::BufferUsages::STORAGE
52                | wgpu::BufferUsages::COPY_SRC
53                | wgpu::BufferUsages::COPY_DST,
54            mapped_at_creation: false,
55        });
56
57        let result_buffer_ref = compute_resource_manager.push(result_buffer);
58        MeanCommand { result_buffer_ref }
59    }
60
61    /// Returns the reference to the result buffer.
62    pub fn result_buffer_ref(&self) -> ComputeResourceRef {
63        self.result_buffer_ref
64    }
65}
66
67impl ComputeCommand for MeanCommand {
68    fn barrier(&self) -> BarrierRequirement {
69        BarrierRequirement::ZERO_PADDING_LOCAL
70    }
71}
72
73#[repr(C)]
74#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
75struct AreaUniform {
76    area_x: u32,
77    area_y: u32,
78    area_width: u32,
79    area_height: u32,
80}
81
82// --- Pipeline ---
83
84/// Pipeline for calculating mean luminance using a compute shader.
85///
86/// # Example
87/// ```rust,ignore
88/// use tessera_ui_basic_components::pipelines::mean::MeanPipeline;
89/// let pipeline = MeanPipeline::new(&device);
90/// ```
91pub struct MeanPipeline {
92    pipeline: wgpu::ComputePipeline,
93    bind_group_layout: wgpu::BindGroupLayout,
94}
95
96impl MeanPipeline {
97    pub fn new(device: &wgpu::Device) -> Self {
98        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
99            label: Some("Mean Shader"),
100            source: wgpu::ShaderSource::Wgsl(include_str!("mean/mean.wgsl").into()),
101        });
102
103        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
104            entries: &[
105                // 0: Area Uniform
106                wgpu::BindGroupLayoutEntry {
107                    binding: 0,
108                    visibility: wgpu::ShaderStages::COMPUTE,
109                    ty: wgpu::BindingType::Buffer {
110                        ty: wgpu::BufferBindingType::Uniform,
111                        has_dynamic_offset: false,
112                        min_binding_size: Some(std::num::NonZeroU64::new(16).unwrap()),
113                    },
114                    count: None,
115                },
116                // 1: Source Texture
117                wgpu::BindGroupLayoutEntry {
118                    binding: 1,
119                    visibility: wgpu::ShaderStages::COMPUTE,
120                    ty: wgpu::BindingType::Texture {
121                        sample_type: wgpu::TextureSampleType::Float { filterable: false },
122                        view_dimension: wgpu::TextureViewDimension::D2,
123                        multisampled: false,
124                    },
125                    count: None,
126                },
127                // 2: Result Buffer (Storage)
128                wgpu::BindGroupLayoutEntry {
129                    binding: 2,
130                    visibility: wgpu::ShaderStages::COMPUTE,
131                    ty: wgpu::BindingType::Buffer {
132                        ty: wgpu::BufferBindingType::Storage { read_only: false },
133                        has_dynamic_offset: false,
134                        min_binding_size: Some(std::num::NonZeroU64::new(8).unwrap()),
135                    },
136                    count: None,
137                },
138                // 3: Destination Texture (Storage)
139                wgpu::BindGroupLayoutEntry {
140                    binding: 3,
141                    visibility: wgpu::ShaderStages::COMPUTE,
142                    ty: wgpu::BindingType::StorageTexture {
143                        access: wgpu::StorageTextureAccess::WriteOnly,
144                        format: wgpu::TextureFormat::Rgba8Unorm,
145                        view_dimension: wgpu::TextureViewDimension::D2,
146                    },
147                    count: None,
148                },
149            ],
150            label: Some("mean_bind_group_layout"),
151        });
152
153        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
154            label: Some("Mean Pipeline Layout"),
155            bind_group_layouts: &[&bind_group_layout],
156            push_constant_ranges: &[],
157        });
158
159        let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
160            label: Some("Mean Pipeline"),
161            layout: Some(&pipeline_layout),
162            module: &shader,
163            entry_point: Some("main"),
164            compilation_options: Default::default(),
165            cache: None,
166        });
167
168        Self {
169            pipeline,
170            bind_group_layout,
171        }
172    }
173}
174
175impl ComputablePipeline<MeanCommand> for MeanPipeline {
176    /// Dispatches the compute shader to calculate mean luminance.
177    ///
178    /// # Parameters
179    /// - `device`: The wgpu device.
180    /// - `config`: Surface configuration.
181    /// - `compute_pass`: The compute pass to encode commands.
182    /// - `command`: The mean command with buffer reference.
183    /// - `resource_manager`: Resource manager for compute buffers.
184    /// - `input_view`: Source texture view.
185    /// - `output_view`: Destination texture view.
186    ///
187    /// Dispatches the mean luminance compute shader.
188    /// - `target_area`: The area of the output texture to be affected (PxRect).
189    fn dispatch(
190        &mut self,
191        device: &wgpu::Device,
192        queue: &wgpu::Queue,
193        config: &wgpu::SurfaceConfiguration,
194        compute_pass: &mut wgpu::ComputePass<'_>,
195        command: &MeanCommand,
196        resource_manager: &mut ComputeResourceManager,
197        target_area: PxRect,
198        input_view: &wgpu::TextureView,
199        output_view: &wgpu::TextureView,
200    ) {
201        let result_buffer = resource_manager.get(&command.result_buffer_ref).unwrap();
202        queue.write_buffer(result_buffer, 0, bytemuck::cast_slice(&[0u32, 0u32]));
203        let area_uniform = AreaUniform {
204            area_x: target_area.x.0 as u32,
205            area_y: target_area.y.0 as u32,
206            area_width: target_area.width.0 as u32,
207            area_height: target_area.height.0 as u32,
208        };
209        let area_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
210            label: Some("Mean Area Uniform Buffer"),
211            contents: bytemuck::bytes_of(&area_uniform),
212            usage: wgpu::BufferUsages::UNIFORM,
213        });
214        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
215            layout: &self.bind_group_layout,
216            entries: &[
217                wgpu::BindGroupEntry {
218                    binding: 0,
219                    resource: area_buffer.as_entire_binding(),
220                },
221                wgpu::BindGroupEntry {
222                    binding: 1,
223                    resource: wgpu::BindingResource::TextureView(input_view),
224                },
225                wgpu::BindGroupEntry {
226                    binding: 2,
227                    resource: result_buffer.as_entire_binding(),
228                },
229                wgpu::BindGroupEntry {
230                    binding: 3,
231                    resource: wgpu::BindingResource::TextureView(output_view),
232                },
233            ],
234            label: Some("mean_bind_group"),
235        });
236
237        compute_pass.set_pipeline(&self.pipeline);
238        compute_pass.set_bind_group(0, &bind_group, &[]);
239        compute_pass.dispatch_workgroups(config.width.div_ceil(8), config.height.div_ceil(8), 1);
240    }
241}