tessera_ui/renderer/compute/pipeline.rs
1//! GPU compute pipeline system for Tessera UI framework.
2//!
3//! This module provides the infrastructure for GPU compute operations in Tessera,
4//! enabling advanced visual effects and post-processing operations that would be
5//! inefficient or impossible to achieve with traditional CPU-based approaches.
6//!
7//! # Architecture Overview
8//!
9//! The compute pipeline system is designed to work seamlessly with the rendering
10//! pipeline, using a ping-pong buffer approach for efficient multi-pass operations.
11//! Each compute pipeline processes a specific type of compute command and operates
12//! on texture data using GPU compute shaders.
13//!
14//! ## Key Components
15//!
16//! - [`ComputablePipeline<C>`]: The main trait for implementing custom compute pipelines
17//! - [`ComputePipelineRegistry`]: Manages and dispatches commands to registered compute pipelines
18//! - [`ComputeResourceManager`]: Manages GPU buffers and resources for compute operations
19//!
20//! # Design Philosophy
21//!
22//! The compute pipeline system embraces WGPU's compute shader capabilities to enable:
23//!
24//! - **Advanced Post-Processing**: Blur, contrast adjustment, color grading, and other image effects
25//! - **Parallel Processing**: Leverage GPU parallelism for computationally intensive operations
26//! - **Real-Time Effects**: Achieve complex visual effects at interactive frame rates
27//! - **Memory Efficiency**: Use GPU memory directly without CPU roundtrips
28//!
29//! # Ping-Pong Rendering
30//!
31//! The system uses a ping-pong approach where:
32//!
33//! 1. **Input Texture**: Contains the result from previous rendering or compute pass
34//! 2. **Output Texture**: Receives the processed result from the current compute operation
35//! 3. **Format Convention**: All textures use `wgpu::TextureFormat::Rgba8Unorm` for compatibility
36//!
37//! This approach enables efficient chaining of multiple compute operations without
38//! intermediate CPU involvement.
39//!
40//! # Implementation Guide
41//!
42//! ## Creating a Custom Compute Pipeline
43//!
44//! To create a custom compute pipeline:
45//!
46//! 1. Define your compute command struct implementing [`ComputeCommand`]
47//! 2. Create a pipeline struct implementing [`ComputablePipeline<YourCommand>`]
48//! 3. Write a compute shader in WGSL
49//! 4. Register the pipeline with [`ComputePipelineRegistry::register`]
50//!
51//! # Performance Considerations
52//!
53//! - **Workgroup Size**: Choose workgroup sizes that align with GPU architecture (typically 8x8 or 16x16)
54//! - **Memory Access**: Optimize memory access patterns in shaders for better cache utilization
55//! - **Resource Reuse**: Use the [`ComputeResourceManager`] to reuse buffers across frames
56//! - **Batch Operations**: Combine multiple similar operations when possible
57//!
58//! # Texture Format Requirements
59//!
60//! Due to WGPU limitations, compute shaders require specific texture formats:
61//!
62//! - **Input Textures**: Can be any readable format, typically from render passes
63//! - **Output Textures**: Must use `wgpu::TextureFormat::Rgba8Unorm` for storage binding
64//! - **sRGB Limitation**: sRGB formats cannot be used as storage textures
65//!
66//! The framework automatically handles format conversions when necessary.
67
68use std::{any::TypeId, collections::HashMap};
69
70use crate::{
71 PxPosition, PxRect, PxSize, compute::resource::ComputeResourceManager, renderer::command::AsAny,
72};
73
74use super::command::ComputeCommand;
75
76/// Type-erased metadata describing a compute command within a batch.
77pub struct ErasedComputeBatchItem<'a> {
78 /// The compute command to execute.
79 pub command: &'a dyn ComputeCommand,
80 /// The measured size of the target region.
81 pub size: PxSize,
82 /// The absolute position of the target region.
83 pub position: PxPosition,
84 /// The rectangle of the content that will be written.
85 pub target_area: PxRect,
86}
87
88/// Strongly typed metadata describing a compute command within a batch.
89pub struct ComputeBatchItem<'a, C: ComputeCommand> {
90 /// The compute command to execute.
91 pub command: &'a C,
92 /// The measured size of the target region.
93 pub size: PxSize,
94 /// The absolute position of the target region.
95 pub position: PxPosition,
96 /// The rectangle of the content that will be written.
97 pub target_area: PxRect,
98}
99
100/// Provides comprehensive context for compute operations within a compute pass.
101///
102/// This struct bundles essential WGPU resources, configuration, and command-specific data
103/// required for a compute pipeline to process its commands.
104///
105/// # Type Parameters
106///
107/// * `C` - The specific [`ComputeCommand`] type being processed.
108///
109/// # Fields
110///
111/// * `device` - The WGPU device, used for creating and managing GPU resources.
112/// * `queue` - The WGPU queue, used for submitting command buffers and writing buffer data.
113/// * `config` - The current surface configuration, providing information like format and dimensions.
114/// * `compute_pass` - The active `wgpu::ComputePass` encoder, used to record compute commands.
115/// * `items` - A slice of [`ComputeBatchItem`]s, each containing a compute command and its metadata.
116/// * `resource_manager` - A mutable reference to the [`ComputeResourceManager`], used for managing reusable GPU buffers.
117/// * `input_view` - A view of the input texture for the compute operation.
118/// * `output_view` - A view of the output texture for the compute operation.
119pub struct ComputeContext<'a, 'b, 'c, C: ComputeCommand> {
120 /// WGPU device used to create and manage GPU resources.
121 pub device: &'a wgpu::Device,
122 /// Queue for submitting GPU workloads.
123 pub queue: &'a wgpu::Queue,
124 /// Surface configuration describing output formats and dimensions.
125 pub config: &'a wgpu::SurfaceConfiguration,
126 /// Active compute pass encoder.
127 pub compute_pass: &'a mut wgpu::ComputePass<'b>,
128 /// Batch of typed compute items to process.
129 pub items: &'c [ComputeBatchItem<'c, C>],
130 /// Shared resource manager used to reuse GPU buffers.
131 pub resource_manager: &'a mut ComputeResourceManager,
132 /// Input texture view sampled by the compute pass.
133 pub input_view: &'a wgpu::TextureView,
134 /// Output texture view written by the compute pass.
135 pub output_view: &'a wgpu::TextureView,
136}
137
138/// Type-erased context used when dispatching compute pipelines.
139pub(crate) struct ErasedDispatchContext<'a, 'b> {
140 pub device: &'a wgpu::Device,
141 pub queue: &'a wgpu::Queue,
142 pub config: &'a wgpu::SurfaceConfiguration,
143 pub compute_pass: &'a mut wgpu::ComputePass<'b>,
144 pub resource_manager: &'a mut ComputeResourceManager,
145 pub input_view: &'a wgpu::TextureView,
146 pub output_view: &'a wgpu::TextureView,
147}
148
149/// Core trait for implementing GPU compute pipelines.
150///
151/// This trait defines the interface for compute pipelines that process specific types
152/// of compute commands using GPU compute shaders. Each pipeline is responsible for
153/// setting up compute resources, managing shader dispatch, and processing texture data.
154///
155/// # Type Parameters
156///
157/// * `C` - The specific [`ComputeCommand`] type this pipeline can handle
158///
159/// # Design Principles
160///
161/// - **Single Responsibility**: Each pipeline handles one specific type of compute operation
162/// - **Stateless Operation**: Pipelines should not maintain state between dispatch calls
163/// - **Resource Efficiency**: Reuse GPU resources when possible through the resource manager
164/// - **Thread Safety**: All implementations must be `Send + Sync` for parallel execution
165///
166/// # Integration with Rendering
167///
168/// Compute pipelines operate within the broader rendering pipeline, typically:
169///
170/// 1. **After Rendering**: Process the rendered scene for post-effects
171/// 2. **Between Passes**: Transform data between different rendering stages
172/// 3. **Before Rendering**: Prepare data or textures for subsequent render operations
173pub trait ComputablePipeline<C: ComputeCommand>: Send + Sync + 'static {
174 /// Dispatches the compute command within an active compute pass.
175 ///
176 /// This method receives one or more compute commands of the same type. Implementations
177 /// may choose to process the batch collectively (e.g., by packing data into a single
178 /// dispatch) or sequentially iterate over the items. It should set up the necessary GPU
179 /// resources, bind them to the compute pipeline, and dispatch the appropriate number of
180 /// workgroups to process the input texture.
181 ///
182 /// # Parameters
183 ///
184 /// * `context` - The context for the compute pass.
185 ///
186 /// # Texture Format Requirements
187 ///
188 /// Due to WGPU limitations, storage textures have specific format requirements:
189 ///
190 /// - **Input Texture**: Can be any readable format, typically from render passes
191 /// - **Output Texture**: Must use `wgpu::TextureFormat::Rgba8Unorm` format
192 /// - **sRGB Limitation**: sRGB formats cannot be used as storage textures
193 ///
194 /// The framework ensures that `output_view` always uses a compatible format
195 /// for storage binding operations.
196 ///
197 /// # Workgroup Dispatch Guidelines
198 ///
199 /// When dispatching workgroups, consider:
200 ///
201 /// - **Workgroup Size**: Match your shader's `@workgroup_size` declaration
202 /// - **Coverage**: Ensure all pixels are processed by calculating appropriate dispatch dimensions
203 /// - **Alignment**: Round up dispatch dimensions to cover the entire texture
204 ///
205 /// # Resource Management
206 ///
207 /// Use the `resource_manager` to:
208 /// - Store persistent buffers that can be reused across frames
209 /// - Avoid recreating expensive GPU resources
210 /// - Manage buffer lifetimes efficiently
211 ///
212 /// # Error Handling
213 ///
214 /// This method should handle errors gracefully:
215 /// - Validate command parameters before use
216 /// - Ensure texture dimensions are compatible
217 /// - Handle resource creation failures appropriately
218 fn dispatch(&mut self, context: &mut ComputeContext<C>);
219}
220
221/// Internal trait for type erasure of computable pipelines.
222///
223/// This trait enables dynamic dispatch of compute commands to their corresponding pipelines
224/// without knowing the specific command type at compile time. It's used internally by
225/// the [`ComputePipelineRegistry`] and should not be implemented directly by users.
226///
227/// The type erasure is achieved through the [`AsAny`] trait, which allows downcasting
228/// from `&dyn ComputeCommand` to concrete command types.
229///
230/// # Implementation Note
231///
232/// This trait is automatically implemented for any type that implements
233/// [`ComputablePipeline<C>`] through the [`ComputablePipelineImpl`] wrapper.
234pub(crate) trait ErasedComputablePipeline: Send + Sync {
235 /// Dispatches a type-erased compute command.
236 fn dispatch_erased(
237 &mut self,
238 context: ErasedDispatchContext<'_, '_>,
239 items: &[ErasedComputeBatchItem<'_>],
240 );
241}
242
243/// A wrapper to implement `ErasedComputablePipeline` for any `ComputablePipeline`.
244struct ComputablePipelineImpl<C: ComputeCommand, P: ComputablePipeline<C>> {
245 pipeline: P,
246 _command: std::marker::PhantomData<C>,
247}
248
249impl<C: ComputeCommand + 'static, P: ComputablePipeline<C>> ErasedComputablePipeline
250 for ComputablePipelineImpl<C, P>
251{
252 fn dispatch_erased(
253 &mut self,
254 context: ErasedDispatchContext<'_, '_>,
255 items: &[ErasedComputeBatchItem<'_>],
256 ) {
257 if items.is_empty() {
258 return;
259 }
260
261 let mut typed_items: Vec<ComputeBatchItem<'_, C>> = Vec::with_capacity(items.len());
262 for item in items {
263 let command = AsAny::as_any(item.command)
264 .downcast_ref::<C>()
265 .expect("Compute batch contained command of unexpected type");
266 typed_items.push(ComputeBatchItem {
267 command,
268 size: item.size,
269 position: item.position,
270 target_area: item.target_area,
271 });
272 }
273
274 self.pipeline.dispatch(&mut ComputeContext {
275 device: context.device,
276 queue: context.queue,
277 config: context.config,
278 compute_pass: context.compute_pass,
279 items: &typed_items,
280 resource_manager: context.resource_manager,
281 input_view: context.input_view,
282 output_view: context.output_view,
283 });
284 }
285}
286
287/// Registry for managing and dispatching compute pipelines.
288///
289/// The `ComputePipelineRegistry` serves as the central hub for all compute pipelines
290/// in the Tessera framework. It maintains a collection of registered pipelines and
291/// handles the dispatch of compute commands to their appropriate pipelines.
292///
293/// # Architecture
294///
295/// The registry uses type erasure to store pipelines of different types in a single
296/// collection. When a compute command needs to be processed, the registry attempts
297/// to dispatch it to all registered pipelines until one handles it successfully.
298///
299/// # Usage Pattern
300///
301/// 1. Create a new registry
302/// 2. Register all required compute pipelines during application initialization
303/// 3. The renderer uses the registry to dispatch commands during frame rendering
304///
305/// # Performance Considerations
306///
307/// - Pipeline lookup is O(1) on average due to HashMap implementation.
308///
309/// # Thread Safety
310///
311/// The registry and all registered pipelines must be `Send + Sync` to support
312/// parallel execution in the rendering system.
313#[derive(Default)]
314pub struct ComputePipelineRegistry {
315 pipelines: HashMap<TypeId, Box<dyn ErasedComputablePipeline>>,
316}
317
318impl ComputePipelineRegistry {
319 /// Creates a new empty compute pipeline registry.
320 ///
321 /// # Example
322 ///
323 /// ```
324 /// use tessera_ui::renderer::compute::ComputePipelineRegistry;
325 ///
326 /// let registry = ComputePipelineRegistry::new();
327 /// ```
328 pub fn new() -> Self {
329 Self::default()
330 }
331
332 /// Registers a new compute pipeline for a specific command type.
333 ///
334 /// This method takes ownership of the pipeline and wraps it in a type-erased
335 /// container that can be stored alongside other pipelines of different types.
336 ///
337 /// # Type Parameters
338 ///
339 /// * `C` - The [`ComputeCommand`] type this pipeline handles
340 ///
341 /// # Parameters
342 ///
343 /// * `pipeline` - The pipeline instance to register
344 ///
345 /// # Thread Safety
346 ///
347 /// The pipeline must implement `Send + Sync` to be compatible with Tessera's
348 /// parallel rendering architecture.
349 pub fn register<C: ComputeCommand + 'static>(
350 &mut self,
351 pipeline: impl ComputablePipeline<C> + 'static,
352 ) {
353 let erased_pipeline = Box::new(ComputablePipelineImpl {
354 pipeline,
355 _command: std::marker::PhantomData,
356 });
357 self.pipelines.insert(TypeId::of::<C>(), erased_pipeline);
358 }
359
360 /// Dispatches one or more commands to their corresponding registered pipeline.
361 pub(crate) fn dispatch_erased(
362 &mut self,
363 context: ErasedDispatchContext<'_, '_>,
364 items: &[ErasedComputeBatchItem<'_>],
365 ) {
366 if items.is_empty() {
367 return;
368 }
369
370 let command_type_id = AsAny::as_any(items[0].command).type_id();
371 if let Some(pipeline) = self.pipelines.get_mut(&command_type_id) {
372 pipeline.dispatch_erased(context, items);
373 } else {
374 panic!(
375 "No pipeline found for command {:?}",
376 std::any::type_name_of_val(items[0].command)
377 );
378 }
379 }
380}