tessera_ui/renderer/drawer/
pipeline.rs1use std::{any::TypeId, collections::HashMap};
8
9use smallvec::SmallVec;
10
11use crate::{
12 px::{PxRect, PxSize},
13 render_graph::DrawGeometry,
14 renderer::DrawCommand,
15};
16
17pub struct FrameContext<'a> {
19 pub graphics: &'a crate::GraphicsDevice,
21 pub device: &'a wgpu::Device,
23 pub queue: &'a wgpu::Queue,
25 pub config: &'a wgpu::SurfaceConfiguration,
27}
28
29pub struct DrawContext<'a, 'b, 'c, T> {
31 pub graphics: &'a crate::GraphicsDevice,
33 pub device: &'a wgpu::Device,
35 pub queue: &'a wgpu::Queue,
37 pub config: &'a wgpu::SurfaceConfiguration,
39 pub target_size: PxSize,
41 pub render_pass: &'a mut wgpu::RenderPass<'b>,
43 pub commands: &'c [(&'c T, DrawGeometry)],
45 pub scene_texture_view: &'a wgpu::TextureView,
47 pub scene_rect: PxRect,
50 pub clip_rect: Option<PxRect>,
52}
53
54pub struct ErasedDrawContext<'a, 'b> {
56 pub graphics: &'a crate::GraphicsDevice,
58 pub device: &'a wgpu::Device,
60 pub queue: &'a wgpu::Queue,
62 pub config: &'a wgpu::SurfaceConfiguration,
64 pub target_size: PxSize,
66 pub render_pass: &'a mut wgpu::RenderPass<'b>,
68 pub scene_texture_view: &'a wgpu::TextureView,
70 pub scene_rect: PxRect,
73 pub clip_rect: Option<PxRect>,
75}
76
77#[allow(unused_variables)]
82pub trait DrawablePipeline<T: DrawCommand> {
83 fn begin_frame(&mut self, context: &FrameContext<'_>) {}
85
86 fn draw(&mut self, context: &mut DrawContext<'_, '_, '_, T>);
89
90 fn end_frame(&mut self, context: &FrameContext<'_>) {}
92}
93
94pub(crate) trait ErasedDrawablePipeline {
95 fn begin_frame(&mut self, context: &FrameContext<'_>);
97 fn end_frame(&mut self, context: &FrameContext<'_>);
99 fn draw_erased(
101 &mut self,
102 context: ErasedDrawContext<'_, '_>,
103 commands: &[(&dyn DrawCommand, DrawGeometry)],
104 ) -> bool;
105}
106
107struct DrawablePipelineImpl<T: DrawCommand, P: DrawablePipeline<T>> {
108 pipeline: P,
109 _marker: std::marker::PhantomData<T>,
110}
111
112impl<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static> ErasedDrawablePipeline
113 for DrawablePipelineImpl<T, P>
114{
115 fn begin_frame(&mut self, context: &FrameContext<'_>) {
116 self.pipeline.begin_frame(context);
117 }
118
119 fn end_frame(&mut self, context: &FrameContext<'_>) {
120 self.pipeline.end_frame(context);
121 }
122
123 fn draw_erased(
124 &mut self,
125 context: ErasedDrawContext<'_, '_>,
126 commands: &[(&dyn DrawCommand, DrawGeometry)],
127 ) -> bool {
128 let timing = tracing::enabled!(tracing::Level::DEBUG).then(crate::time::Instant::now);
129 if commands.is_empty() {
130 return true;
131 }
132
133 let ErasedDrawContext {
134 graphics,
135 device,
136 queue,
137 config,
138 target_size,
139 render_pass,
140 scene_texture_view,
141 scene_rect,
142 clip_rect,
143 } = context;
144
145 if commands[0].0.is::<T>() {
146 let typed_commands: SmallVec<[(&T, DrawGeometry); 16]> = commands
147 .iter()
148 .map(|(cmd, geometry)| {
149 (
150 cmd.downcast_ref::<T>().expect(
151 "FATAL: A command in a batch has a different type than the first one.",
152 ),
153 *geometry,
154 )
155 })
156 .collect();
157
158 self.pipeline.draw(&mut DrawContext {
159 graphics,
160 device,
161 queue,
162 config,
163 target_size,
164 render_pass,
165 commands: &typed_commands,
166 scene_texture_view,
167 scene_rect,
168 clip_rect,
169 });
170 if let Some(timing) = timing {
171 tracing::debug!(
172 pipeline = std::any::type_name::<P>(),
173 phase = "draw",
174 commands = commands.len(),
175 elapsed_us = timing.elapsed().as_micros(),
176 frame_nanos = crate::current_frame_nanos(),
177 "pipeline_cpu"
178 );
179 }
180 true
181 } else {
182 false
183 }
184 }
185}
186
187#[derive(Default)]
189pub struct PipelineRegistry {
190 pub(crate) pipelines: HashMap<TypeId, Box<dyn ErasedDrawablePipeline>>,
191}
192
193impl PipelineRegistry {
194 pub fn new() -> Self {
196 Self::default()
197 }
198
199 pub fn register<T: DrawCommand + 'static, P: DrawablePipeline<T> + 'static>(
201 &mut self,
202 pipeline: P,
203 ) {
204 let erased = Box::new(DrawablePipelineImpl::<T, P> {
205 pipeline,
206 _marker: std::marker::PhantomData,
207 });
208 self.pipelines.insert(TypeId::of::<T>(), erased);
209 }
210
211 pub(crate) fn begin_all_frames(
212 &mut self,
213 device: &wgpu::Device,
214 queue: &wgpu::Queue,
215 config: &wgpu::SurfaceConfiguration,
216 graphics: &crate::GraphicsDevice,
217 ) {
218 graphics.begin_frame();
219 for pipeline in self.pipelines.values_mut() {
220 pipeline.begin_frame(&FrameContext {
221 graphics,
222 device,
223 queue,
224 config,
225 });
226 }
227 }
228
229 pub(crate) fn end_all_frames(
230 &mut self,
231 device: &wgpu::Device,
232 queue: &wgpu::Queue,
233 config: &wgpu::SurfaceConfiguration,
234 graphics: &crate::GraphicsDevice,
235 ) {
236 for pipeline in self.pipelines.values_mut() {
237 pipeline.end_frame(&FrameContext {
238 graphics,
239 device,
240 queue,
241 config,
242 });
243 }
244 }
245
246 pub(crate) fn dispatch(
247 &mut self,
248 context: ErasedDrawContext<'_, '_>,
249 commands: &[(&dyn DrawCommand, DrawGeometry)],
250 ) {
251 if commands.is_empty() {
252 return;
253 }
254
255 let command_type_id = commands[0].0.as_any().type_id();
256 if let Some(pipeline) = self.pipelines.get_mut(&command_type_id) {
257 if !pipeline.draw_erased(context, commands) {
258 panic!(
259 "FATAL: A command in a batch has a different type than the first one. This should not happen."
260 )
261 }
262 } else {
263 panic!(
264 "No pipeline found for command {:?}",
265 std::any::type_name_of_val(commands[0].0)
266 );
267 }
268 }
269}