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 /// * `scene_texture_view` - View of the current scene texture for background sampling
236 ///
237 /// # Default Implementation
238 ///
239 /// The default implementation does nothing, which is suitable for most pipelines.
240 fn begin_pass(
241 &mut self,
242 gpu: &wgpu::Device,
243 gpu_queue: &wgpu::Queue,
244 config: &wgpu::SurfaceConfiguration,
245 render_pass: &mut wgpu::RenderPass<'_>,
246 scene_texture_view: &wgpu::TextureView,
247 ) {
248 }
249
250 /// Renders a batch of draw commands.
251 ///
252 /// This is the core method where the actual rendering happens. It's called
253 /// once for a batch of draw commands of type `T` that need to be rendered.
254 ///
255 /// # Parameters
256 ///
257 /// * `gpu` - The WGPU device for creating resources.
258 /// * `gpu_queue` - The WGPU queue for submitting commands and updating buffers.
259 /// * `config` - Current surface configuration containing format and size information.
260 /// * `render_pass` - The active render pass to record draw commands into.
261 /// * `commands` - A slice of tuples, each containing the command, its size, and its position.
262 /// * `scene_texture_view` - View of the current scene texture for background sampling.
263 /// * `clip_rect` - An optional rectangle to clip the drawing area.
264 ///
265 /// # Implementation Guidelines
266 ///
267 /// - Iterate over the `commands` slice to process each command.
268 /// - Update buffers (e.g., instance buffers, storage buffers) with data from the command batch.
269 /// - Set the appropriate render pipeline.
270 /// - Bind necessary resources (textures, buffers, bind groups).
271 /// - Issue one or more draw calls (e.g., an instanced draw call) to render the entire batch.
272 /// - If `clip_rect` is `Some`, use `render_pass.set_scissor_rect()` to clip rendering.
273 /// - Avoid expensive operations like buffer creation; prefer reusing and updating existing resources.
274 ///
275 /// # Scene Texture Usage
276 ///
277 /// The `scene_texture_view` provides access to the current rendered scene,
278 /// enabling effects that sample from the background.
279 fn draw(
280 &mut self,
281 gpu: &wgpu::Device,
282 gpu_queue: &wgpu::Queue,
283 config: &wgpu::SurfaceConfiguration,
284 render_pass: &mut wgpu::RenderPass<'_>,
285 commands: &[(&T, PxSize, PxPosition)],
286 scene_texture_view: &wgpu::TextureView,
287 clip_rect: Option<PxRect>,
288 );
289
290 /// Called once at the end of the render pass.
291 ///
292 /// Use this method to perform cleanup operations or finalize rendering
293 /// for all draw commands of this type in the current frame. This is useful for:
294 ///
295 /// - Cleaning up temporary resources
296 /// - Finalizing multi-pass rendering operations
297 /// - Submitting batched draw calls
298 ///
299 /// # Parameters
300 ///
301 /// * `gpu` - The WGPU device for creating resources
302 /// * `gpu_queue` - The WGPU queue for submitting commands
303 /// * `config` - Current surface configuration
304 /// * `render_pass` - The active render pass
305 /// * `scene_texture_view` - View of the current scene texture for background sampling
306 ///
307 /// # Default Implementation
308 ///
309 /// The default implementation does nothing, which is suitable for most pipelines.
310 fn end_pass(
311 &mut self,
312 gpu: &wgpu::Device,
313 gpu_queue: &wgpu::Queue,
314 config: &wgpu::SurfaceConfiguration,
315 render_pass: &mut wgpu::RenderPass<'_>,
316 scene_texture_view: &wgpu::TextureView,
317 ) {
318 }
319
320 /// Called once at the end of the frame, after all render passes are complete.
321 ///
322 /// This method is the final hook in the pipeline's frame lifecycle. It's invoked
323 /// after all `begin_pass`, `draw`, and `end_pass` calls for the frame have
324 /// completed, but before the frame's command buffer is submitted to the GPU.
325 ///
326 /// It's suitable for frame-level cleanup or finalization tasks, such as:
327 ///
328 /// - Reading data back from the GPU (though this can be slow and should be used sparingly).
329 /// - Cleaning up temporary resources created in `begin_frame`.
330 /// - Preparing data for the next frame.
331 ///
332 /// # Parameters
333 ///
334 /// * `gpu` - The WGPU device.
335 /// * `gpu_queue` - The WGPU queue.
336 /// * `config` - The current surface configuration.
337 ///
338 /// # Default Implementation
339 ///
340 /// The default implementation does nothing.
341 fn end_frame(
342 &mut self,
343 gpu: &wgpu::Device,
344 gpu_queue: &wgpu::Queue,
345 config: &wgpu::SurfaceConfiguration,
346 ) {
347 }
348}
349
350/// Internal trait for type erasure of drawable pipelines.
351///
352/// This trait enables dynamic dispatch of draw commands to their corresponding pipelines
353/// without knowing the specific command type at compile time. It's used internally by
354/// the [`PipelineRegistry`] and should not be implemented directly by users.
355///
356/// The type erasure is achieved through the [`AsAny`] trait, which allows downcasting
357/// from `&dyn DrawCommand` to concrete command types.
358///
359/// # Implementation Note
360///
361/// This trait is automatically implemented for any type that implements
362/// [`DrawablePipeline<T>`] through the [`DrawablePipelineImpl`] wrapper.
363pub trait ErasedDrawablePipeline {
364 fn begin_frame(
365 &mut self,
366 gpu: &wgpu::Device,
367 gpu_queue: &wgpu::Queue,
368 config: &wgpu::SurfaceConfiguration,
369 );
370 fn end_frame(
371 &mut self,
372 gpu: &wgpu::Device,
373 gpu_queue: &wgpu::Queue,
374 config: &wgpu::SurfaceConfiguration,
375 );
376 fn begin_pass(
377 &mut self,
378 gpu: &wgpu::Device,
379 gpu_queue: &wgpu::Queue,
380 config: &wgpu::SurfaceConfiguration,
381 render_pass: &mut wgpu::RenderPass<'_>,
382 scene_texture_view: &wgpu::TextureView,
383 );
384
385 fn end_pass(
386 &mut self,
387 gpu: &wgpu::Device,
388 gpu_queue: &wgpu::Queue,
389 config: &wgpu::SurfaceConfiguration,
390 render_pass: &mut wgpu::RenderPass<'_>,
391 scene_texture_view: &wgpu::TextureView,
392 );
393
394 fn draw_erased(
395 &mut self,
396 gpu: &wgpu::Device,
397 gpu_queue: &wgpu::Queue,
398 config: &wgpu::SurfaceConfiguration,
399 render_pass: &mut wgpu::RenderPass<'_>,
400 commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
401 scene_texture_view: &wgpu::TextureView,
402 clip_rect: Option<PxRect>,
403 ) -> bool;
404}
405
406struct DrawablePipelineImpl<T: DrawCommand, P: DrawablePipeline<T>> {
407 pipeline: P,
408 _marker: std::marker::PhantomData<T>,
409}
410
411impl<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static> ErasedDrawablePipeline
412 for DrawablePipelineImpl<T, P>
413{
414 fn begin_frame(
415 &mut self,
416 gpu: &wgpu::Device,
417 gpu_queue: &wgpu::Queue,
418 config: &wgpu::SurfaceConfiguration,
419 ) {
420 self.pipeline.begin_frame(gpu, gpu_queue, config);
421 }
422
423 fn end_frame(
424 &mut self,
425 gpu: &wgpu::Device,
426 gpu_queue: &wgpu::Queue,
427 config: &wgpu::SurfaceConfiguration,
428 ) {
429 self.pipeline.end_frame(gpu, gpu_queue, config);
430 }
431
432 fn begin_pass(
433 &mut self,
434 gpu: &wgpu::Device,
435 gpu_queue: &wgpu::Queue,
436 config: &wgpu::SurfaceConfiguration,
437 render_pass: &mut wgpu::RenderPass<'_>,
438 scene_texture_view: &wgpu::TextureView,
439 ) {
440 self.pipeline
441 .begin_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
442 }
443
444 fn end_pass(
445 &mut self,
446 gpu: &wgpu::Device,
447 gpu_queue: &wgpu::Queue,
448 config: &wgpu::SurfaceConfiguration,
449 render_pass: &mut wgpu::RenderPass<'_>,
450 scene_texture_view: &wgpu::TextureView,
451 ) {
452 self.pipeline
453 .end_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
454 }
455
456 fn draw_erased(
457 &mut self,
458 gpu: &wgpu::Device,
459 gpu_queue: &wgpu::Queue,
460 config: &wgpu::SurfaceConfiguration,
461 render_pass: &mut wgpu::RenderPass<'_>,
462 commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
463 scene_texture_view: &wgpu::TextureView,
464 clip_rect: Option<PxRect>,
465 ) -> bool {
466 if commands.is_empty() {
467 return true;
468 }
469
470 if commands[0].0.as_any().is::<T>() {
471 let typed_commands: Vec<(&T, PxSize, PxPosition)> = commands
472 .iter()
473 .map(|(cmd, size, pos)| {
474 (
475 cmd.as_any().downcast_ref::<T>().expect(
476 "FATAL: A command in a batch has a different type than the first one.",
477 ),
478 *size,
479 *pos,
480 )
481 })
482 .collect();
483
484 self.pipeline.draw(
485 gpu,
486 gpu_queue,
487 config,
488 render_pass,
489 &typed_commands,
490 scene_texture_view,
491 clip_rect,
492 );
493 true
494 } else {
495 false
496 }
497 }
498}
499
500/// Registry for managing and dispatching drawable pipelines.
501///
502/// The `PipelineRegistry` serves as the central hub for all rendering pipelines in the
503/// Tessera framework. It maintains a collection of registered pipelines and handles
504/// the dispatch of draw commands to their appropriate pipelines.
505///
506/// # Architecture
507///
508/// The registry uses type erasure to store pipelines of different types in a single
509/// collection. When a draw command needs to be rendered, the registry iterates through
510/// all registered pipelines until it finds one that can handle the command type.
511///
512/// # Usage Pattern
513///
514/// 1. Create a new registry
515/// 2. Register all required pipelines during application initialization
516/// 3. The renderer uses the registry to dispatch commands during frame rendering
517///
518/// # Example
519///
520/// ```rust,ignore
521/// use tessera_ui::renderer::drawer::PipelineRegistry;
522///
523/// // Create registry and register pipelines
524/// let mut registry = PipelineRegistry::new();
525/// registry.register(my_shape_pipeline);
526/// registry.register(my_text_pipeline);
527/// registry.register(my_image_pipeline);
528///
529/// // Registry is now ready for use by the renderer
530/// ```
531///
532/// # Performance Considerations
533///
534/// - Pipeline lookup is O(1) on average due to HashMap implementation.
535pub struct PipelineRegistry {
536 pub(crate) pipelines: HashMap<TypeId, Box<dyn ErasedDrawablePipeline>>,
537}
538
539impl Default for PipelineRegistry {
540 fn default() -> Self {
541 Self::new()
542 }
543}
544
545impl PipelineRegistry {
546 /// Creates a new empty pipeline registry.
547 ///
548 /// # Example
549 ///
550 /// ```
551 /// use tessera_ui::renderer::drawer::PipelineRegistry;
552 ///
553 /// let registry = PipelineRegistry::new();
554 /// ```
555 pub fn new() -> Self {
556 Self {
557 pipelines: HashMap::new(),
558 }
559 }
560
561 /// Registers a new drawable pipeline for a specific command type.
562 ///
563 /// This method takes ownership of the pipeline and wraps it in a type-erased
564 /// container that can be stored alongside other pipelines of different types.
565 ///
566 /// # Type Parameters
567 ///
568 /// * `T` - The [`DrawCommand`] type this pipeline handles
569 /// * `P` - The pipeline implementation type
570 ///
571 /// # Parameters
572 ///
573 /// * `pipeline` - The pipeline instance to register
574 ///
575 /// # Panics
576 ///
577 /// This method does not panic, but the registry will panic during dispatch
578 /// if no pipeline is found for a given command type.
579 ///
580 /// # Example
581 ///
582 /// ```rust,ignore
583 /// use tessera_ui::renderer::drawer::PipelineRegistry;
584 ///
585 /// let mut registry = PipelineRegistry::new();
586 ///
587 /// // Register a custom pipeline
588 /// let my_pipeline = MyCustomPipeline::new(&device, &config, sample_count);
589 /// registry.register(my_pipeline);
590 ///
591 /// // Register multiple pipelines
592 /// registry.register(ShapePipeline::new(&device, &config, sample_count));
593 /// registry.register(TextPipeline::new(&device, &config, sample_count));
594 /// ```
595 pub fn register<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static>(
596 &mut self,
597 pipeline: P,
598 ) {
599 let erased = Box::new(DrawablePipelineImpl::<T, P> {
600 pipeline,
601 _marker: std::marker::PhantomData,
602 });
603 self.pipelines.insert(TypeId::of::<T>(), erased);
604 }
605
606 pub(crate) fn begin_all_passes(
607 &mut self,
608 gpu: &wgpu::Device,
609 gpu_queue: &wgpu::Queue,
610 config: &wgpu::SurfaceConfiguration,
611 render_pass: &mut wgpu::RenderPass<'_>,
612 scene_texture_view: &wgpu::TextureView,
613 ) {
614 for pipeline in self.pipelines.values_mut() {
615 pipeline.begin_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
616 }
617 }
618
619 pub(crate) fn end_all_passes(
620 &mut self,
621 gpu: &wgpu::Device,
622 gpu_queue: &wgpu::Queue,
623 config: &wgpu::SurfaceConfiguration,
624 render_pass: &mut wgpu::RenderPass<'_>,
625 scene_texture_view: &wgpu::TextureView,
626 ) {
627 for pipeline in self.pipelines.values_mut() {
628 pipeline.end_pass(gpu, gpu_queue, config, render_pass, scene_texture_view);
629 }
630 }
631
632 pub(crate) fn begin_all_frames(
633 &mut self,
634 gpu: &wgpu::Device,
635 gpu_queue: &wgpu::Queue,
636 config: &wgpu::SurfaceConfiguration,
637 ) {
638 for pipeline in self.pipelines.values_mut() {
639 pipeline.begin_frame(gpu, gpu_queue, config);
640 }
641 }
642
643 pub(crate) fn end_all_frames(
644 &mut self,
645 gpu: &wgpu::Device,
646 gpu_queue: &wgpu::Queue,
647 config: &wgpu::SurfaceConfiguration,
648 ) {
649 for pipeline in self.pipelines.values_mut() {
650 pipeline.end_frame(gpu, gpu_queue, config);
651 }
652 }
653
654 pub(crate) fn dispatch(
655 &mut self,
656 gpu: &wgpu::Device,
657 gpu_queue: &wgpu::Queue,
658 config: &wgpu::SurfaceConfiguration,
659 render_pass: &mut wgpu::RenderPass<'_>,
660 commands: &[(&dyn DrawCommand, PxSize, PxPosition)],
661 scene_texture_view: &wgpu::TextureView,
662 clip_rect: Option<PxRect>,
663 ) {
664 if commands.is_empty() {
665 return;
666 }
667
668 let command_type_id = commands[0].0.as_any().type_id();
669 if let Some(pipeline) = self.pipelines.get_mut(&command_type_id) {
670 if !pipeline.draw_erased(
671 gpu,
672 gpu_queue,
673 config,
674 render_pass,
675 commands,
676 scene_texture_view,
677 clip_rect,
678 ) {
679 panic!(
680 "FATAL: A command in a batch has a different type than the first one. This should not happen."
681 )
682 }
683 } else {
684 panic!(
685 "No pipeline found for command {:?}",
686 std::any::type_name_of_val(commands[0].0)
687 );
688 }
689 }
690}