tessera_ui/renderer/drawer/
pipeline.rs

1//! Graphics rendering pipeline system for Tessera UI framework.
2//!
3//! This module provides the core infrastructure for pluggable graphics rendering pipelines
4//! in Tessera. The design philosophy emphasizes flexibility and extensibility, allowing
5//! developers to create custom rendering effects without being constrained by built-in
6//! drawing primitives.
7//!
8//! # Architecture Overview
9//!
10//! The pipeline system uses a trait-based approach with type erasure to support dynamic
11//! dispatch of rendering commands. Each pipeline is responsible for rendering a specific
12//! type of draw command, such as shapes, text, images, or custom visual effects.
13//!
14//! ## Key Components
15//!
16//! - [`DrawablePipeline<T>`]: The main trait for implementing custom rendering pipelines
17//! - [`PipelineRegistry`]: Manages and dispatches commands to registered pipelines
18//! - [`ErasedDrawablePipeline`]: Internal trait for type erasure and dynamic dispatch
19//!
20//! # Design Philosophy
21//!
22//! Unlike traditional UI frameworks that provide built-in "brush" or drawing primitives,
23//! Tessera treats shaders as first-class citizens. This approach offers several advantages:
24//!
25//! - **Modern GPU Utilization**: Leverages WGPU and WGSL for efficient, cross-platform rendering
26//! - **Advanced Visual Effects**: Enables complex effects like neumorphic design, lighting,
27//!   shadows, reflections, and bloom that are difficult to achieve with traditional approaches
28//! - **Flexibility**: Custom shaders allow for unlimited creative possibilities
29//! - **Performance**: Direct GPU programming eliminates abstraction overhead
30//!
31//! # Pipeline Lifecycle
32//!
33//! Each pipeline follows a three-phase lifecycle during rendering:
34//!
35//! 1. **Begin Pass**: Setup phase for initializing pipeline-specific resources
36//! 2. **Draw**: Main rendering phase where commands are processed
37//! 3. **End Pass**: Cleanup phase for finalizing rendering operations
38//!
39//! # Implementation Guide
40//!
41//! ## Creating a Custom Pipeline
42//!
43//! To create a custom rendering pipeline:
44//!
45//! 1. Define your draw command struct implementing [`DrawCommand`]
46//! 2. Create a pipeline struct implementing [`DrawablePipeline<YourCommand>`]
47//! 3. Register the pipeline with [`PipelineRegistry::register`]
48//!
49//! ## Example: Simple Rectangle Pipeline
50//!
51//! ```rust,ignore
52//! use tessera_ui::{DrawCommand, DrawablePipeline, PxPosition, PxSize};
53//! use wgpu;
54//!
55//! // 1. Define the draw command
56//! #[derive(Debug)]
57//! struct RectangleCommand {
58//!     color: [f32; 4],
59//!     corner_radius: f32,
60//! }
61//!
62//! impl DrawCommand for RectangleCommand {
63//!     // Most commands don't need barriers
64//!     fn barrier(&self) -> Option<tessera_ui::BarrierRequirement> {
65//!         None
66//!     }
67//! }
68//!
69//! // 2. Implement the pipeline
70//! struct RectanglePipeline {
71//!     render_pipeline: wgpu::RenderPipeline,
72//!     uniform_buffer: wgpu::Buffer,
73//!     bind_group: wgpu::BindGroup,
74//! }
75//!
76//! impl RectanglePipeline {
77//!     fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, sample_count: u32) -> Self {
78//!         // Create shader, pipeline, buffers, etc.
79//!         // ... implementation details ...
80//!         # unimplemented!()
81//!     }
82//! }
83//!
84//! impl DrawablePipeline<RectangleCommand> for RectanglePipeline {
85//!     fn draw(
86//!         &mut self,
87//!         gpu: &wgpu::Device,
88//!         gpu_queue: &wgpu::Queue,
89//!         config: &wgpu::SurfaceConfiguration,
90//!         render_pass: &mut wgpu::RenderPass<'_>,
91//!         command: &RectangleCommand,
92//!         size: PxSize,
93//!         start_pos: PxPosition,
94//!         scene_texture_view: &wgpu::TextureView,
95//!     ) {
96//!         // Update uniforms with command data
97//!         // Set pipeline and draw
98//!         render_pass.set_pipeline(&self.render_pipeline);
99//!         render_pass.set_bind_group(0, &self.bind_group, &[]);
100//!         render_pass.draw(0..6, 0..1); // Draw quad
101//!     }
102//! }
103//!
104//! // 3. Register the pipeline
105//! let mut registry = PipelineRegistry::new();
106//! let rectangle_pipeline = RectanglePipeline::new(&device, &config, sample_count);
107//! registry.register(rectangle_pipeline);
108//! ```
109//!
110//! # Integration with Basic Components
111//!
112//! The `tessera_basic_components` crate demonstrates real-world pipeline implementations:
113//!
114//! - **ShapePipeline**: Renders rounded rectangles, circles, and complex shapes with shadows and ripple effects
115//! - **TextPipeline**: Handles text rendering with font management and glyph caching
116//! - **ImagePipeline**: Displays images with various scaling and filtering options
117//! - **FluidGlassPipeline**: Creates advanced glass effects with distortion and transparency
118//!
119//! These pipelines are registered in `tessera_ui_basic_components::pipelines::register_pipelines()`.
120//!
121//! # Performance Considerations
122//!
123//! - **Batch Similar Commands**: Group similar draw commands to minimize pipeline switches
124//! - **Resource Management**: Reuse buffers and textures when possible
125//! - **Shader Optimization**: Write efficient shaders optimized for your target platforms
126//! - **State Changes**: Minimize render state changes within the draw method
127//!
128//! # Advanced Features
129//!
130//! ## Barrier Requirements
131//!
132//! Some rendering effects need to sample from previously rendered content (e.g., blur effects).
133//! Implement [`DrawCommand::barrier()`] to return [`BarrierRequirement::SampleBackground`]
134//! for such commands.
135//!
136//! ## Multi-Pass Rendering
137//!
138//! Use `begin_pass()` and `end_pass()` for pipelines that require multiple rendering passes
139//! or complex setup/teardown operations.
140//!
141//! ## Scene Texture Access
142//!
143//! The `scene_texture_view` parameter provides access to the current scene texture,
144//! enabling effects that sample from the background or perform post-processing.
145
146use crate::{PxPosition, px::PxSize, renderer::DrawCommand};
147
148/// Core trait for implementing custom graphics rendering pipelines.
149///
150/// This trait defines the interface for rendering pipelines that process specific types
151/// of draw commands. Each pipeline is responsible for setting up GPU resources,
152/// managing render state, and executing the actual drawing operations.
153///
154/// # Type Parameters
155///
156/// * `T` - The specific [`DrawCommand`] type this pipeline can handle
157///
158/// # Lifecycle Methods
159///
160/// The pipeline system provides five lifecycle hooks, executed in the following order:
161///
162/// 1. [`begin_frame()`](Self::begin_frame): Called once at the start of a new frame, before any render passes.
163/// 2. [`begin_pass()`](Self::begin_pass): Called at the start of each render pass that involves this pipeline.
164/// 3. [`draw()`](Self::draw): Called for each command of type `T` within a render pass.
165/// 4. [`end_pass()`](Self::end_pass): Called at the end of each render pass that involved this pipeline.
166/// 5. [`end_frame()`](Self::end_frame): Called once at the end of the frame, after all render passes are complete.
167///
168/// Typically, `begin_pass`, `draw`, and `end_pass` are used for the core rendering logic within a pass,
169/// while `begin_frame` and `end_frame` are used for setup and teardown that spans the entire frame.
170///
171/// # Implementation Notes
172///
173/// - Only the [`draw()`](Self::draw) method is required; others have default empty implementations.
174/// - Pipelines should be stateless between frames when possible
175/// - Resource management should prefer reuse over recreation
176/// - Consider batching multiple commands for better performance
177///
178/// # Example
179///
180/// See the module-level documentation for a complete implementation example.
181#[allow(unused_variables)]
182pub trait DrawablePipeline<T: DrawCommand> {
183    /// Called once at the beginning of the frame, before any render passes.
184    ///
185    /// This method is the first hook in the pipeline's frame lifecycle. It's invoked
186    /// after a new `CommandEncoder` has been created but before any rendering occurs.
187    /// It's ideal for per-frame setup that is not tied to a specific `wgpu::RenderPass`.
188    ///
189    /// Since this method is called outside a render pass, it cannot be used for drawing
190    /// commands. However, it can be used for operations like:
191    ///
192    /// - Updating frame-global uniform buffers (e.g., with time or resolution data)
193    ///   using [`wgpu::Queue::write_buffer`].
194    /// - Preparing or resizing buffers that will be used throughout the frame.
195    /// - Performing CPU-side calculations needed for the frame.
196    ///
197    /// # Parameters
198    ///
199    /// * `gpu` - The WGPU device, for resource creation.
200    /// * `gpu_queue` - The WGPU queue, for submitting buffer writes.
201    /// * `config` - The current surface configuration.
202    ///
203    /// # Default Implementation
204    ///
205    /// The default implementation does nothing.
206    fn begin_frame(
207        &mut self,
208        gpu: &wgpu::Device,
209        gpu_queue: &wgpu::Queue,
210        config: &wgpu::SurfaceConfiguration,
211    ) {
212    }
213
214    /// Called once at the beginning of the render pass.
215    ///
216    /// Use this method to perform one-time setup operations that apply to all
217    /// draw commands of this type in the current frame. This is ideal for:
218    ///
219    /// - Setting up shared uniform buffers
220    /// - Binding global resources
221    /// - Configuring render state that persists across multiple draw calls
222    ///
223    /// # Parameters
224    ///
225    /// * `gpu` - The WGPU device for creating resources
226    /// * `gpu_queue` - The WGPU queue for submitting commands
227    /// * `config` - Current surface configuration
228    /// * `render_pass` - The active render pass
229    ///
230    /// # Default Implementation
231    ///
232    /// The default implementation does nothing, which is suitable for most pipelines.
233    fn begin_pass(
234        &mut self,
235        gpu: &wgpu::Device,
236        gpu_queue: &wgpu::Queue,
237        config: &wgpu::SurfaceConfiguration,
238        render_pass: &mut wgpu::RenderPass<'_>,
239    ) {
240    }
241
242    /// Renders a single draw command.
243    ///
244    /// This is the core method where the actual rendering happens. It's called
245    /// once for each draw command of type `T` that needs to be rendered.
246    ///
247    /// # Parameters
248    ///
249    /// * `gpu` - The WGPU device for creating resources
250    /// * `gpu_queue` - The WGPU queue for submitting commands and updating buffers
251    /// * `config` - Current surface configuration containing format and size information
252    /// * `render_pass` - The active render pass to record draw commands into
253    /// * `command` - The specific draw command to render
254    /// * `size` - The size of the rendering area in pixels
255    /// * `start_pos` - The top-left position where rendering should begin
256    /// * `scene_texture_view` - View of the current scene texture for background sampling
257    ///
258    /// # Implementation Guidelines
259    ///
260    /// - Update any per-command uniforms or push constants
261    /// - Set the appropriate render pipeline
262    /// - Bind necessary resources (textures, buffers, bind groups)
263    /// - Issue draw calls (typically `draw()`, `draw_indexed()`, or `draw_indirect()`)
264    /// - Avoid expensive operations like buffer creation; prefer reusing resources
265    ///
266    /// # Scene Texture Usage
267    ///
268    /// The `scene_texture_view` provides access to the current rendered scene,
269    /// enabling effects that sample from the background. This is commonly used for:
270    ///
271    /// - Blur and post-processing effects
272    /// - Glass and transparency effects
273    /// - Distortion and refraction
274    ///
275    /// # Example
276    ///
277    /// ```rust,ignore
278    /// fn draw(&mut self, gpu: &wgpu::Device, gpu_queue: &wgpu::Queue,
279    ///         config: &wgpu::SurfaceConfiguration, render_pass: &mut wgpu::RenderPass<'_>,
280    ///         command: &MyCommand, size: PxSize, start_pos: PxPosition,
281    ///         scene_texture_view: &wgpu::TextureView) {
282    ///     // Update uniforms with command-specific data
283    ///     let uniforms = MyUniforms {
284    ///         color: command.color,
285    ///         position: [start_pos.x as f32, start_pos.y as f32],
286    ///         size: [size.width as f32, size.height as f32],
287    ///     };
288    ///     gpu_queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
289    ///     
290    ///     // Set pipeline and resources
291    ///     render_pass.set_pipeline(&self.render_pipeline);
292    ///     render_pass.set_bind_group(0, &self.bind_group, &[]);
293    ///     
294    ///     // Draw a quad (two triangles)
295    ///     render_pass.draw(0..6, 0..1);
296    /// }
297    /// ```
298    fn draw(
299        &mut self,
300        gpu: &wgpu::Device,
301        gpu_queue: &wgpu::Queue,
302        config: &wgpu::SurfaceConfiguration,
303        render_pass: &mut wgpu::RenderPass<'_>,
304        command: &T,
305        size: PxSize,
306        start_pos: PxPosition,
307        scene_texture_view: &wgpu::TextureView,
308    );
309
310    /// Called once at the end of the render pass.
311    ///
312    /// Use this method to perform cleanup operations or finalize rendering
313    /// for all draw commands of this type in the current frame. This is useful for:
314    ///
315    /// - Cleaning up temporary resources
316    /// - Finalizing multi-pass rendering operations
317    /// - Submitting batched draw calls
318    ///
319    /// # Parameters
320    ///
321    /// * `gpu` - The WGPU device for creating resources
322    /// * `gpu_queue` - The WGPU queue for submitting commands
323    /// * `config` - Current surface configuration
324    /// * `render_pass` - The active render pass
325    ///
326    /// # Default Implementation
327    ///
328    /// The default implementation does nothing, which is suitable for most pipelines.
329    fn end_pass(
330        &mut self,
331        gpu: &wgpu::Device,
332        gpu_queue: &wgpu::Queue,
333        config: &wgpu::SurfaceConfiguration,
334        render_pass: &mut wgpu::RenderPass<'_>,
335    ) {
336    }
337
338    /// Called once at the end of the frame, after all render passes are complete.
339    ///
340    /// This method is the final hook in the pipeline's frame lifecycle. It's invoked
341    /// after all `begin_pass`, `draw`, and `end_pass` calls for the frame have
342    /// completed, but before the frame's command buffer is submitted to the GPU.
343    ///
344    /// It's suitable for frame-level cleanup or finalization tasks, such as:
345    ///
346    /// - Reading data back from the GPU (though this can be slow and should be used sparingly).
347    /// - Cleaning up temporary resources created in `begin_frame`.
348    /// - Preparing data for the next frame.
349    ///
350    /// # Parameters
351    ///
352    /// * `gpu` - The WGPU device.
353    /// * `gpu_queue` - The WGPU queue.
354    /// * `config` - The current surface configuration.
355    ///
356    /// # Default Implementation
357    ///
358    /// The default implementation does nothing.
359    fn end_frame(
360        &mut self,
361        gpu: &wgpu::Device,
362        gpu_queue: &wgpu::Queue,
363        config: &wgpu::SurfaceConfiguration,
364    ) {
365    }
366}
367
368/// Internal trait for type erasure of drawable pipelines.
369///
370/// This trait enables dynamic dispatch of draw commands to their corresponding pipelines
371/// without knowing the specific command type at compile time. It's used internally by
372/// the [`PipelineRegistry`] and should not be implemented directly by users.
373///
374/// The type erasure is achieved through the [`AsAny`] trait, which allows downcasting
375/// from `&dyn DrawCommand` to concrete command types.
376///
377/// # Implementation Note
378///
379/// This trait is automatically implemented for any type that implements
380/// [`DrawablePipeline<T>`] through the [`DrawablePipelineImpl`] wrapper.
381pub trait ErasedDrawablePipeline {
382    fn begin_frame(
383        &mut self,
384        gpu: &wgpu::Device,
385        gpu_queue: &wgpu::Queue,
386        config: &wgpu::SurfaceConfiguration,
387    );
388    fn end_frame(
389        &mut self,
390        gpu: &wgpu::Device,
391        gpu_queue: &wgpu::Queue,
392        config: &wgpu::SurfaceConfiguration,
393    );
394    fn begin_pass(
395        &mut self,
396        gpu: &wgpu::Device,
397        gpu_queue: &wgpu::Queue,
398        config: &wgpu::SurfaceConfiguration,
399        render_pass: &mut wgpu::RenderPass<'_>,
400    );
401
402    fn end_pass(
403        &mut self,
404        gpu: &wgpu::Device,
405        gpu_queue: &wgpu::Queue,
406        config: &wgpu::SurfaceConfiguration,
407        render_pass: &mut wgpu::RenderPass<'_>,
408    );
409
410    fn draw_erased(
411        &mut self,
412        gpu: &wgpu::Device,
413        gpu_queue: &wgpu::Queue,
414        config: &wgpu::SurfaceConfiguration,
415        render_pass: &mut wgpu::RenderPass<'_>,
416        command: &dyn DrawCommand,
417        size: PxSize,
418        start_pos: PxPosition,
419        scene_texture_view: &wgpu::TextureView,
420    ) -> bool;
421}
422
423struct DrawablePipelineImpl<T: DrawCommand, P: DrawablePipeline<T>> {
424    pipeline: P,
425    _marker: std::marker::PhantomData<T>,
426}
427
428impl<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static> ErasedDrawablePipeline
429    for DrawablePipelineImpl<T, P>
430{
431    fn begin_frame(
432        &mut self,
433        gpu: &wgpu::Device,
434        gpu_queue: &wgpu::Queue,
435        config: &wgpu::SurfaceConfiguration,
436    ) {
437        self.pipeline.begin_frame(gpu, gpu_queue, config);
438    }
439
440    fn end_frame(
441        &mut self,
442        gpu: &wgpu::Device,
443        gpu_queue: &wgpu::Queue,
444        config: &wgpu::SurfaceConfiguration,
445    ) {
446        self.pipeline.end_frame(gpu, gpu_queue, config);
447    }
448
449    fn begin_pass(
450        &mut self,
451        gpu: &wgpu::Device,
452        gpu_queue: &wgpu::Queue,
453        config: &wgpu::SurfaceConfiguration,
454        render_pass: &mut wgpu::RenderPass<'_>,
455    ) {
456        self.pipeline
457            .begin_pass(gpu, gpu_queue, config, render_pass);
458    }
459
460    fn end_pass(
461        &mut self,
462        gpu: &wgpu::Device,
463        gpu_queue: &wgpu::Queue,
464        config: &wgpu::SurfaceConfiguration,
465        render_pass: &mut wgpu::RenderPass<'_>,
466    ) {
467        self.pipeline.end_pass(gpu, gpu_queue, config, render_pass);
468    }
469
470    fn draw_erased(
471        &mut self,
472        gpu: &wgpu::Device,
473        gpu_queue: &wgpu::Queue,
474        config: &wgpu::SurfaceConfiguration,
475        render_pass: &mut wgpu::RenderPass<'_>,
476        command: &dyn DrawCommand,
477        size: PxSize,
478        start_pos: PxPosition,
479        scene_texture_view: &wgpu::TextureView,
480    ) -> bool {
481        if let Some(cmd) = command.as_any().downcast_ref::<T>() {
482            self.pipeline.draw(
483                gpu,
484                gpu_queue,
485                config,
486                render_pass,
487                cmd,
488                size,
489                start_pos,
490                scene_texture_view,
491            );
492            true
493        } else {
494            false
495        }
496    }
497}
498
499/// Registry for managing and dispatching drawable pipelines.
500///
501/// The `PipelineRegistry` serves as the central hub for all rendering pipelines in the
502/// Tessera framework. It maintains a collection of registered pipelines and handles
503/// the dispatch of draw commands to their appropriate pipelines.
504///
505/// # Architecture
506///
507/// The registry uses type erasure to store pipelines of different types in a single
508/// collection. When a draw command needs to be rendered, the registry iterates through
509/// all registered pipelines until it finds one that can handle the command type.
510///
511/// # Usage Pattern
512///
513/// 1. Create a new registry
514/// 2. Register all required pipelines during application initialization
515/// 3. The renderer uses the registry to dispatch commands during frame rendering
516///
517/// # Example
518///
519/// ```rust,ignore
520/// use tessera_ui::renderer::drawer::PipelineRegistry;
521///
522/// // Create registry and register pipelines
523/// let mut registry = PipelineRegistry::new();
524/// registry.register(my_shape_pipeline);
525/// registry.register(my_text_pipeline);
526/// registry.register(my_image_pipeline);
527///
528/// // Registry is now ready for use by the renderer
529/// ```
530///
531/// # Performance Considerations
532///
533/// - Pipeline lookup is O(n) where n is the number of registered pipelines
534/// - Register frequently used pipelines first for better average performance
535/// - Consider the order of registration based on command frequency
536pub struct PipelineRegistry {
537    pub(crate) pipelines: Vec<Box<dyn ErasedDrawablePipeline>>,
538}
539
540impl Default for PipelineRegistry {
541    fn default() -> Self {
542        Self::new()
543    }
544}
545
546impl PipelineRegistry {
547    /// Creates a new empty pipeline registry.
548    ///
549    /// # Example
550    ///
551    /// ```
552    /// use tessera_ui::renderer::drawer::PipelineRegistry;
553    ///
554    /// let registry = PipelineRegistry::new();
555    /// ```
556    pub fn new() -> Self {
557        Self {
558            pipelines: Vec::new(),
559        }
560    }
561
562    /// Registers a new drawable pipeline for a specific command type.
563    ///
564    /// This method takes ownership of the pipeline and wraps it in a type-erased
565    /// container that can be stored alongside other pipelines of different types.
566    ///
567    /// # Type Parameters
568    ///
569    /// * `T` - The [`DrawCommand`] type this pipeline handles
570    /// * `P` - The pipeline implementation type
571    ///
572    /// # Parameters
573    ///
574    /// * `pipeline` - The pipeline instance to register
575    ///
576    /// # Panics
577    ///
578    /// This method does not panic, but the registry will panic during dispatch
579    /// if no pipeline is found for a given command type.
580    ///
581    /// # Example
582    ///
583    /// ```rust,ignore
584    /// use tessera_ui::renderer::drawer::PipelineRegistry;
585    ///
586    /// let mut registry = PipelineRegistry::new();
587    ///
588    /// // Register a custom pipeline
589    /// let my_pipeline = MyCustomPipeline::new(&device, &config, sample_count);
590    /// registry.register(my_pipeline);
591    ///
592    /// // Register multiple pipelines
593    /// registry.register(ShapePipeline::new(&device, &config, sample_count));
594    /// registry.register(TextPipeline::new(&device, &config, sample_count));
595    /// ```
596    ///
597    /// # Registration Order
598    ///
599    /// The order of registration can affect performance since pipeline lookup
600    /// is performed linearly. Consider registering more frequently used pipelines first.
601    pub fn register<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static>(
602        &mut self,
603        pipeline: P,
604    ) {
605        let erased = Box::new(DrawablePipelineImpl::<T, P> {
606            pipeline,
607            _marker: std::marker::PhantomData,
608        });
609        self.pipelines.push(erased);
610    }
611
612    pub(crate) fn begin_all_passes(
613        &mut self,
614        gpu: &wgpu::Device,
615        gpu_queue: &wgpu::Queue,
616        config: &wgpu::SurfaceConfiguration,
617        render_pass: &mut wgpu::RenderPass<'_>,
618    ) {
619        for pipeline in self.pipelines.iter_mut() {
620            pipeline.begin_pass(gpu, gpu_queue, config, render_pass);
621        }
622    }
623
624    pub(crate) fn end_all_passes(
625        &mut self,
626        gpu: &wgpu::Device,
627        gpu_queue: &wgpu::Queue,
628        config: &wgpu::SurfaceConfiguration,
629        render_pass: &mut wgpu::RenderPass<'_>,
630    ) {
631        for pipeline in self.pipelines.iter_mut() {
632            pipeline.end_pass(gpu, gpu_queue, config, render_pass);
633        }
634    }
635
636    pub(crate) fn begin_all_frames(
637        &mut self,
638        gpu: &wgpu::Device,
639        gpu_queue: &wgpu::Queue,
640        config: &wgpu::SurfaceConfiguration,
641    ) {
642        for pipeline in self.pipelines.iter_mut() {
643            pipeline.begin_frame(gpu, gpu_queue, config);
644        }
645    }
646
647    pub(crate) fn end_all_frames(
648        &mut self,
649        gpu: &wgpu::Device,
650        gpu_queue: &wgpu::Queue,
651        config: &wgpu::SurfaceConfiguration,
652    ) {
653        for pipeline in self.pipelines.iter_mut() {
654            pipeline.end_frame(gpu, gpu_queue, config);
655        }
656    }
657
658    pub(crate) fn dispatch(
659        &mut self,
660        gpu: &wgpu::Device,
661        gpu_queue: &wgpu::Queue,
662        config: &wgpu::SurfaceConfiguration,
663        render_pass: &mut wgpu::RenderPass<'_>,
664        cmd: &dyn DrawCommand,
665        size: PxSize,
666        start_pos: PxPosition,
667        scene_texture_view: &wgpu::TextureView,
668    ) {
669        for pipeline in self.pipelines.iter_mut() {
670            if pipeline.draw_erased(
671                gpu,
672                gpu_queue,
673                config,
674                render_pass,
675                cmd,
676                size,
677                start_pos,
678                scene_texture_view,
679            ) {
680                return;
681            }
682        }
683
684        panic!(
685            "No pipeline found for command {:?}",
686            std::any::type_name_of_val(cmd)
687        );
688    }
689}