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 std::{any::TypeId, collections::HashMap};
147
148use crate::{
149    PxPosition,
150    px::{PxRect, PxSize},
151    renderer::DrawCommand,
152};
153
154/// Core trait for implementing custom graphics rendering pipelines.
155///
156/// This trait defines the interface for rendering pipelines that process specific types
157/// of draw commands. Each pipeline is responsible for setting up GPU resources,
158/// managing render state, and executing the actual drawing operations.
159///
160/// # Type Parameters
161///
162/// * `T` - The specific [`DrawCommand`] type this pipeline can handle
163///
164/// # Lifecycle Methods
165///
166/// The pipeline system provides five lifecycle hooks, executed in the following order:
167///
168/// 1. [`begin_frame()`](Self::begin_frame): Called once at the start of a new frame, before any render passes.
169/// 2. [`begin_pass()`](Self::begin_pass): Called at the start of each render pass that involves this pipeline.
170/// 3. [`draw()`](Self::draw): Called for each command of type `T` within a render pass.
171/// 4. [`end_pass()`](Self::end_pass): Called at the end of each render pass that involved this pipeline.
172/// 5. [`end_frame()`](Self::end_frame): Called once at the end of the frame, after all render passes are complete.
173///
174/// Typically, `begin_pass`, `draw`, and `end_pass` are used for the core rendering logic within a pass,
175/// while `begin_frame` and `end_frame` are used for setup and teardown that spans the entire frame.
176///
177/// # Implementation Notes
178///
179/// - Only the [`draw()`](Self::draw) method is required; others have default empty implementations.
180/// - Pipelines should be stateless between frames when possible
181/// - Resource management should prefer reuse over recreation
182/// - Consider batching multiple commands for better performance
183///
184/// # Example
185///
186/// See the module-level documentation for a complete implementation example.
187#[allow(unused_variables)]
188pub trait DrawablePipeline<T: DrawCommand> {
189    /// Called once at the beginning of the frame, before any render passes.
190    ///
191    /// This method is the first hook in the pipeline's frame lifecycle. It's invoked
192    /// after a new `CommandEncoder` has been created but before any rendering occurs.
193    /// It's ideal for per-frame setup that is not tied to a specific `wgpu::RenderPass`.
194    ///
195    /// Since this method is called outside a render pass, it cannot be used for drawing
196    /// commands. However, it can be used for operations like:
197    ///
198    /// - Updating frame-global uniform buffers (e.g., with time or resolution data)
199    ///   using [`wgpu::Queue::write_buffer`].
200    /// - Preparing or resizing buffers that will be used throughout the frame.
201    /// - Performing CPU-side calculations needed for the frame.
202    ///
203    /// # Parameters
204    ///
205    /// * `gpu` - The WGPU device, for resource creation.
206    /// * `gpu_queue` - The WGPU queue, for submitting buffer writes.
207    /// * `config` - The current surface configuration.
208    ///
209    /// # Default Implementation
210    ///
211    /// The default implementation does nothing.
212    fn begin_frame(
213        &mut self,
214        gpu: &wgpu::Device,
215        gpu_queue: &wgpu::Queue,
216        config: &wgpu::SurfaceConfiguration,
217    ) {
218    }
219
220    /// Called once at the beginning of the render pass.
221    ///
222    /// Use this method to perform one-time setup operations that apply to all
223    /// draw commands of this type in the current frame. This is ideal for:
224    ///
225    /// - Setting up shared uniform buffers
226    /// - Binding global resources
227    /// - Configuring render state that persists across multiple draw calls
228    ///
229    /// # Parameters
230    ///
231    /// * `gpu` - The WGPU device for creating resources
232    /// * `gpu_queue` - The WGPU queue for submitting commands
233    /// * `config` - Current surface configuration
234    /// * `render_pass` - The active render pass
235    ///
236    /// # Default Implementation
237    ///
238    /// The default implementation does nothing, which is suitable for most pipelines.
239    fn begin_pass(
240        &mut self,
241        gpu: &wgpu::Device,
242        gpu_queue: &wgpu::Queue,
243        config: &wgpu::SurfaceConfiguration,
244        render_pass: &mut wgpu::RenderPass<'_>,
245        scene_texture_view: &wgpu::TextureView,
246    ) {
247    }
248
249    /// Renders a single draw command.
250    ///
251    /// This is the core method where the actual rendering happens. It's called
252    /// once for each draw command of type `T` that needs to be rendered.
253    ///
254    /// # Parameters
255    ///
256    /// * `gpu` - The WGPU device for creating resources
257    /// * `gpu_queue` - The WGPU queue for submitting commands and updating buffers
258    /// * `config` - Current surface configuration containing format and size information
259    /// * `render_pass` - The active render pass to record draw commands into
260    /// * `command` - The specific draw command to render
261    /// * `size` - The size of the rendering area in pixels
262    /// * `start_pos` - The top-left position where rendering should begin
263    /// * `scene_texture_view` - View of the current scene texture for background sampling
264    ///
265    /// # Implementation Guidelines
266    ///
267    /// - Update any per-command uniforms or push constants
268    /// - Set the appropriate render pipeline
269    /// - Bind necessary resources (textures, buffers, bind groups)
270    /// - Issue draw calls (typically `draw()`, `draw_indexed()`, or `draw_indirect()`)
271    /// - Avoid expensive operations like buffer creation; prefer reusing resources
272    ///
273    /// # Scene Texture Usage
274    ///
275    /// The `scene_texture_view` provides access to the current rendered scene,
276    /// enabling effects that sample from the background. This is commonly used for:
277    ///
278    /// - Blur and post-processing effects
279    /// - Glass and transparency effects
280    /// - Distortion and refraction
281    ///
282    /// # Example
283    ///
284    /// ```rust,ignore
285    /// fn draw(&mut self, gpu: &wgpu::Device, gpu_queue: &wgpu::Queue,
286    ///         config: &wgpu::SurfaceConfiguration, render_pass: &mut wgpu::RenderPass<'_>,
287    ///         command: &MyCommand, size: PxSize, start_pos: PxPosition,
288    ///         scene_texture_view: &wgpu::TextureView) {
289    ///     // Update uniforms with command-specific data
290    ///     let uniforms = MyUniforms {
291    ///         color: command.color,
292    ///         position: [start_pos.x as f32, start_pos.y as f32],
293    ///         size: [size.width as f32, size.height as f32],
294    ///     };
295    ///     gpu_queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
296    ///     
297    ///     // Set pipeline and resources
298    ///     render_pass.set_pipeline(&self.render_pipeline);
299    ///     render_pass.set_bind_group(0, &self.bind_group, &[]);
300    ///     
301    ///     // Draw a quad (two triangles)
302    ///     render_pass.draw(0..6, 0..1);
303    /// }
304    /// ```
305    fn draw(
306        &mut self,
307        gpu: &wgpu::Device,
308        gpu_queue: &wgpu::Queue,
309        config: &wgpu::SurfaceConfiguration,
310        render_pass: &mut wgpu::RenderPass<'_>,
311        commands: &[(&T, PxSize, PxPosition)],
312        scene_texture_view: &wgpu::TextureView,
313        clip_rect: Option<PxRect>,
314    );
315
316    /// Called once at the end of the render pass.
317    ///
318    /// Use this method to perform cleanup operations or finalize rendering
319    /// for all draw commands of this type in the current frame. This is useful for:
320    ///
321    /// - Cleaning up temporary resources
322    /// - Finalizing multi-pass rendering operations
323    /// - Submitting batched draw calls
324    ///
325    /// # Parameters
326    ///
327    /// * `gpu` - The WGPU device for creating resources
328    /// * `gpu_queue` - The WGPU queue for submitting commands
329    /// * `config` - Current surface configuration
330    /// * `render_pass` - The active render pass
331    ///
332    /// # Default Implementation
333    ///
334    /// The default implementation does nothing, which is suitable for most pipelines.
335    fn end_pass(
336        &mut self,
337        gpu: &wgpu::Device,
338        gpu_queue: &wgpu::Queue,
339        config: &wgpu::SurfaceConfiguration,
340        render_pass: &mut wgpu::RenderPass<'_>,
341        scene_texture_view: &wgpu::TextureView,
342    ) {
343    }
344
345    /// Called once at the end of the frame, after all render passes are complete.
346    ///
347    /// This method is the final hook in the pipeline's frame lifecycle. It's invoked
348    /// after all `begin_pass`, `draw`, and `end_pass` calls for the frame have
349    /// completed, but before the frame's command buffer is submitted to the GPU.
350    ///
351    /// It's suitable for frame-level cleanup or finalization tasks, such as:
352    ///
353    /// - Reading data back from the GPU (though this can be slow and should be used sparingly).
354    /// - Cleaning up temporary resources created in `begin_frame`.
355    /// - Preparing data for the next frame.
356    ///
357    /// # Parameters
358    ///
359    /// * `gpu` - The WGPU device.
360    /// * `gpu_queue` - The WGPU queue.
361    /// * `config` - The current surface configuration.
362    ///
363    /// # Default Implementation
364    ///
365    /// The default implementation does nothing.
366    fn end_frame(
367        &mut self,
368        gpu: &wgpu::Device,
369        gpu_queue: &wgpu::Queue,
370        config: &wgpu::SurfaceConfiguration,
371    ) {
372    }
373}
374
375/// Internal trait for type erasure of drawable pipelines.
376///
377/// This trait enables dynamic dispatch of draw commands to their corresponding pipelines
378/// without knowing the specific command type at compile time. It's used internally by
379/// the [`PipelineRegistry`] and should not be implemented directly by users.
380///
381/// The type erasure is achieved through the [`AsAny`] trait, which allows downcasting
382/// from `&dyn DrawCommand` to concrete command types.
383///
384/// # Implementation Note
385///
386/// This trait is automatically implemented for any type that implements
387/// [`DrawablePipeline<T>`] through the [`DrawablePipelineImpl`] wrapper.
388pub trait ErasedDrawablePipeline {
389    fn begin_frame(
390        &mut self,
391        gpu: &wgpu::Device,
392        gpu_queue: &wgpu::Queue,
393        config: &wgpu::SurfaceConfiguration,
394    );
395    fn end_frame(
396        &mut self,
397        gpu: &wgpu::Device,
398        gpu_queue: &wgpu::Queue,
399        config: &wgpu::SurfaceConfiguration,
400    );
401    fn begin_pass(
402        &mut self,
403        gpu: &wgpu::Device,
404        gpu_queue: &wgpu::Queue,
405        config: &wgpu::SurfaceConfiguration,
406        render_pass: &mut wgpu::RenderPass<'_>,
407        scene_texture_view: &wgpu::TextureView,
408    );
409
410    fn end_pass(
411        &mut self,
412        gpu: &wgpu::Device,
413        gpu_queue: &wgpu::Queue,
414        config: &wgpu::SurfaceConfiguration,
415        render_pass: &mut wgpu::RenderPass<'_>,
416        scene_texture_view: &wgpu::TextureView,
417    );
418
419    fn draw_erased(
420        &mut self,
421        gpu: &wgpu::Device,
422        gpu_queue: &wgpu::Queue,
423        config: &wgpu::SurfaceConfiguration,
424        render_pass: &mut wgpu::RenderPass<'_>,
425        commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
426        scene_texture_view: &wgpu::TextureView,
427        clip_rect: Option<PxRect>,
428    ) -> bool;
429}
430
431struct DrawablePipelineImpl<T: DrawCommand, P: DrawablePipeline<T>> {
432    pipeline: P,
433    _marker: std::marker::PhantomData<T>,
434}
435
436impl<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static> ErasedDrawablePipeline
437    for DrawablePipelineImpl<T, P>
438{
439    fn begin_frame(
440        &mut self,
441        gpu: &wgpu::Device,
442        gpu_queue: &wgpu::Queue,
443        config: &wgpu::SurfaceConfiguration,
444    ) {
445        self.pipeline.begin_frame(gpu, gpu_queue, config);
446    }
447
448    fn end_frame(
449        &mut self,
450        gpu: &wgpu::Device,
451        gpu_queue: &wgpu::Queue,
452        config: &wgpu::SurfaceConfiguration,
453    ) {
454        self.pipeline.end_frame(gpu, gpu_queue, config);
455    }
456
457    fn begin_pass(
458        &mut self,
459        gpu: &wgpu::Device,
460        gpu_queue: &wgpu::Queue,
461        config: &wgpu::SurfaceConfiguration,
462        render_pass: &mut wgpu::RenderPass<'_>,
463        scene_texture_view: &wgpu::TextureView,
464    ) {
465        self.pipeline
466            .begin_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
467    }
468
469    fn end_pass(
470        &mut self,
471        gpu: &wgpu::Device,
472        gpu_queue: &wgpu::Queue,
473        config: &wgpu::SurfaceConfiguration,
474        render_pass: &mut wgpu::RenderPass<'_>,
475        scene_texture_view: &wgpu::TextureView,
476    ) {
477        self.pipeline
478            .end_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
479    }
480
481    fn draw_erased(
482        &mut self,
483        gpu: &wgpu::Device,
484        gpu_queue: &wgpu::Queue,
485        config: &wgpu::SurfaceConfiguration,
486        render_pass: &mut wgpu::RenderPass<'_>,
487        commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
488        scene_texture_view: &wgpu::TextureView,
489        clip_rect: Option<PxRect>,
490    ) -> bool {
491        if commands.is_empty() {
492            return true;
493        }
494
495        if commands[0].0.as_any().is::<T>() {
496            let typed_commands: Vec<(&T, PxSize, PxPosition)> = commands
497                .iter()
498                .map(|(cmd, size, pos)| {
499                    (
500                        cmd.as_any().downcast_ref::<T>().expect(
501                            "FATAL: A command in a batch has a different type than the first one.",
502                        ),
503                        *size,
504                        *pos,
505                    )
506                })
507                .collect();
508
509            self.pipeline.draw(
510                gpu,
511                gpu_queue,
512                config,
513                render_pass,
514                &typed_commands,
515                scene_texture_view,
516                clip_rect,
517            );
518            true
519        } else {
520            false
521        }
522    }
523}
524
525/// Registry for managing and dispatching drawable pipelines.
526///
527/// The `PipelineRegistry` serves as the central hub for all rendering pipelines in the
528/// Tessera framework. It maintains a collection of registered pipelines and handles
529/// the dispatch of draw commands to their appropriate pipelines.
530///
531/// # Architecture
532///
533/// The registry uses type erasure to store pipelines of different types in a single
534/// collection. When a draw command needs to be rendered, the registry iterates through
535/// all registered pipelines until it finds one that can handle the command type.
536///
537/// # Usage Pattern
538///
539/// 1. Create a new registry
540/// 2. Register all required pipelines during application initialization
541/// 3. The renderer uses the registry to dispatch commands during frame rendering
542///
543/// # Example
544///
545/// ```rust,ignore
546/// use tessera_ui::renderer::drawer::PipelineRegistry;
547///
548/// // Create registry and register pipelines
549/// let mut registry = PipelineRegistry::new();
550/// registry.register(my_shape_pipeline);
551/// registry.register(my_text_pipeline);
552/// registry.register(my_image_pipeline);
553///
554/// // Registry is now ready for use by the renderer
555/// ```
556///
557/// # Performance Considerations
558///
559/// - Pipeline lookup is O(1) on average due to HashMap implementation.
560pub struct PipelineRegistry {
561    pub(crate) pipelines: HashMap<TypeId, Box<dyn ErasedDrawablePipeline>>,
562}
563
564impl Default for PipelineRegistry {
565    fn default() -> Self {
566        Self::new()
567    }
568}
569
570impl PipelineRegistry {
571    /// Creates a new empty pipeline registry.
572    ///
573    /// # Example
574    ///
575    /// ```
576    /// use tessera_ui::renderer::drawer::PipelineRegistry;
577    ///
578    /// let registry = PipelineRegistry::new();
579    /// ```
580    pub fn new() -> Self {
581        Self {
582            pipelines: HashMap::new(),
583        }
584    }
585
586    /// Registers a new drawable pipeline for a specific command type.
587    ///
588    /// This method takes ownership of the pipeline and wraps it in a type-erased
589    /// container that can be stored alongside other pipelines of different types.
590    ///
591    /// # Type Parameters
592    ///
593    /// * `T` - The [`DrawCommand`] type this pipeline handles
594    /// * `P` - The pipeline implementation type
595    ///
596    /// # Parameters
597    ///
598    /// * `pipeline` - The pipeline instance to register
599    ///
600    /// # Panics
601    ///
602    /// This method does not panic, but the registry will panic during dispatch
603    /// if no pipeline is found for a given command type.
604    ///
605    /// # Example
606    ///
607    /// ```rust,ignore
608    /// use tessera_ui::renderer::drawer::PipelineRegistry;
609    ///
610    /// let mut registry = PipelineRegistry::new();
611    ///
612    /// // Register a custom pipeline
613    /// let my_pipeline = MyCustomPipeline::new(&device, &config, sample_count);
614    /// registry.register(my_pipeline);
615    ///
616    /// // Register multiple pipelines
617    /// registry.register(ShapePipeline::new(&device, &config, sample_count));
618    /// registry.register(TextPipeline::new(&device, &config, sample_count));
619    /// ```
620    pub fn register<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static>(
621        &mut self,
622        pipeline: P,
623    ) {
624        let erased = Box::new(DrawablePipelineImpl::<T, P> {
625            pipeline,
626            _marker: std::marker::PhantomData,
627        });
628        self.pipelines.insert(TypeId::of::<T>(), erased);
629    }
630
631    pub(crate) fn begin_all_passes(
632        &mut self,
633        gpu: &wgpu::Device,
634        gpu_queue: &wgpu::Queue,
635        config: &wgpu::SurfaceConfiguration,
636        render_pass: &mut wgpu::RenderPass<'_>,
637        scene_texture_view: &wgpu::TextureView,
638    ) {
639        for pipeline in self.pipelines.values_mut() {
640            pipeline.begin_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
641        }
642    }
643
644    pub(crate) fn end_all_passes(
645        &mut self,
646        gpu: &wgpu::Device,
647        gpu_queue: &wgpu::Queue,
648        config: &wgpu::SurfaceConfiguration,
649        render_pass: &mut wgpu::RenderPass<'_>,
650        scene_texture_view: &wgpu::TextureView,
651    ) {
652        for pipeline in self.pipelines.values_mut() {
653            pipeline.end_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
654        }
655    }
656
657    pub(crate) fn begin_all_frames(
658        &mut self,
659        gpu: &wgpu::Device,
660        gpu_queue: &wgpu::Queue,
661        config: &wgpu::SurfaceConfiguration,
662    ) {
663        for pipeline in self.pipelines.values_mut() {
664            pipeline.begin_frame(gpu, gpu_queue, config);
665        }
666    }
667
668    pub(crate) fn end_all_frames(
669        &mut self,
670        gpu: &wgpu::Device,
671        gpu_queue: &wgpu::Queue,
672        config: &wgpu::SurfaceConfiguration,
673    ) {
674        for pipeline in self.pipelines.values_mut() {
675            pipeline.end_frame(gpu, gpu_queue, config);
676        }
677    }
678
679    pub(crate) fn dispatch(
680        &mut self,
681        gpu: &wgpu::Device,
682        gpu_queue: &wgpu::Queue,
683        config: &wgpu::SurfaceConfiguration,
684        render_pass: &mut wgpu::RenderPass<'_>,
685        commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
686        scene_texture_view: &wgpu::TextureView,
687        clip_rect: Option<PxRect>,
688    ) {
689        if commands.is_empty() {
690            return;
691        }
692
693        let command_type_id = commands[0].0.as_any().type_id();
694        if let Some(pipeline) = self.pipelines.get_mut(&command_type_id) {
695            if !pipeline.draw_erased(
696                gpu,
697                gpu_queue,
698                config,
699                render_pass,
700                commands,
701                scene_texture_view,
702                clip_rect,
703            ) {
704                panic!(
705                    "FATAL: A command in a batch has a different type than the first one. This should not happen."
706                )
707            }
708        } else {
709            panic!(
710                "No pipeline found for command {:?}",
711                std::any::type_name_of_val(commands[0].0)
712            );
713        }
714    }
715}