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}