1use std::{any::TypeId, mem, sync::Arc};
2
3use parking_lot::RwLock;
4use tracing::{error, info, warn};
5use wgpu::{ImageSubresourceRange, TextureFormat};
6use winit::window::Window;
7
8use crate::{
9 ComputeCommand, DrawCommand, Px, PxPosition,
10 compute::resource::ComputeResourceManager,
11 dp::SCALE_FACTOR,
12 px::{PxRect, PxSize},
13 renderer::command::{BarrierRequirement, Command},
14};
15
16use super::{compute::ComputePipelineRegistry, drawer::Drawer};
17
18struct PassTarget {
20 texture: wgpu::Texture,
21 view: wgpu::TextureView,
22}
23
24struct WgpuContext<'a> {
26 encoder: &'a mut wgpu::CommandEncoder,
27 gpu: &'a wgpu::Device,
28 queue: &'a wgpu::Queue,
29 config: &'a wgpu::SurfaceConfiguration,
30}
31
32struct RenderCurrentPassParams<'a> {
34 msaa_view: &'a Option<wgpu::TextureView>,
35 is_first_pass: &'a mut bool,
36 encoder: &'a mut wgpu::CommandEncoder,
37 write_target: &'a PassTarget,
38 commands_in_pass: &'a mut Vec<DrawOrClip>,
39 scene_texture_view: &'a wgpu::TextureView,
40 drawer: &'a mut Drawer,
41 gpu: &'a wgpu::Device,
42 queue: &'a wgpu::Queue,
43 config: &'a wgpu::SurfaceConfiguration,
44 clip_stack: &'a mut Vec<PxRect>,
45}
46
47struct DoComputeParams<'a> {
49 encoder: &'a mut wgpu::CommandEncoder,
50 commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
51 compute_pipeline_registry: &'a mut ComputePipelineRegistry,
52 gpu: &'a wgpu::Device,
53 queue: &'a wgpu::Queue,
54 config: &'a wgpu::SurfaceConfiguration,
55 resource_manager: &'a mut ComputeResourceManager,
56 scene_view: &'a wgpu::TextureView,
57 target_a: &'a PassTarget,
58 target_b: &'a PassTarget,
59}
60
61struct ComputeResources<'a> {
63 compute_commands: &'a mut Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
64 compute_pipeline_registry: &'a mut ComputePipelineRegistry,
65 resource_manager: &'a mut ComputeResourceManager,
66 compute_target_a: &'a PassTarget,
67 compute_target_b: &'a PassTarget,
68}
69
70pub struct WgpuApp {
71 #[allow(unused)]
73 pub window: Arc<Window>,
74 pub gpu: wgpu::Device,
76 surface: wgpu::Surface<'static>,
78 pub queue: wgpu::Queue,
80 pub config: wgpu::SurfaceConfiguration,
82 size: winit::dpi::PhysicalSize<u32>,
84 size_changed: bool,
86 pub drawer: Drawer,
88 pub compute_pipeline_registry: ComputePipelineRegistry,
90
91 pass_a: PassTarget,
93 pass_b: PassTarget,
94
95 pub sample_count: u32,
97 msaa_texture: Option<wgpu::Texture>,
98 msaa_view: Option<wgpu::TextureView>,
99
100 compute_target_a: PassTarget,
102 compute_target_b: PassTarget,
103 compute_commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
104 pub resource_manager: Arc<RwLock<ComputeResourceManager>>,
105}
106
107impl WgpuApp {
108 async fn request_adapter_for_surface(
113 instance: &wgpu::Instance,
114 surface: &wgpu::Surface<'_>,
115 ) -> wgpu::Adapter {
116 match instance
117 .request_adapter(&wgpu::RequestAdapterOptions {
118 power_preference: wgpu::PowerPreference::default(),
119 compatible_surface: Some(surface),
120 force_fallback_adapter: false,
121 })
122 .await
123 {
124 Ok(gpu) => gpu,
125 Err(e) => {
126 error!("Failed to find an appropriate adapter: {e:?}");
127 panic!("Failed to find an appropriate adapter: {e:?}");
128 }
129 }
130 }
131
132 async fn request_device_and_queue_for_adapter(
133 adapter: &wgpu::Adapter,
134 ) -> (wgpu::Device, wgpu::Queue) {
135 match adapter
136 .request_device(&wgpu::DeviceDescriptor {
137 required_features: wgpu::Features::empty() | wgpu::Features::CLEAR_TEXTURE,
138 required_limits: if cfg!(target_arch = "wasm32") {
139 wgpu::Limits::downlevel_webgl2_defaults()
140 } else {
141 wgpu::Limits::default()
142 },
143 label: None,
144 memory_hints: wgpu::MemoryHints::Performance,
145 trace: wgpu::Trace::Off,
146 })
147 .await
148 {
149 Ok((gpu, queue)) => (gpu, queue),
150 Err(e) => {
151 error!("Failed to create device: {e:?}");
152 panic!("Failed to create device: {e:?}");
153 }
154 }
155 }
156
157 fn make_msaa_resources(
158 gpu: &wgpu::Device,
159 sample_count: u32,
160 config: &wgpu::SurfaceConfiguration,
161 ) -> (Option<wgpu::Texture>, Option<wgpu::TextureView>) {
162 if sample_count > 1 {
163 let texture = gpu.create_texture(&wgpu::TextureDescriptor {
164 label: Some("MSAA Framebuffer"),
165 size: wgpu::Extent3d {
166 width: config.width,
167 height: config.height,
168 depth_or_array_layers: 1,
169 },
170 mip_level_count: 1,
171 sample_count,
172 dimension: wgpu::TextureDimension::D2,
173 format: config.format,
174 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
175 view_formats: &[],
176 });
177 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
178 (Some(texture), Some(view))
179 } else {
180 (None, None)
181 }
182 }
183 pub(crate) async fn new(window: Arc<Window>, sample_count: u32) -> Self {
184 let instance: wgpu::Instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
186 backends: wgpu::Backends::all(),
187 ..Default::default()
188 });
189 let surface = match instance.create_surface(window.clone()) {
191 Ok(surface) => surface,
192 Err(e) => {
193 error!("Failed to create surface: {e:?}");
194 panic!("Failed to create surface: {e:?}");
195 }
196 };
197 let adapter = Self::request_adapter_for_surface(&instance, &surface).await;
199 let (gpu, queue) = Self::request_device_and_queue_for_adapter(&adapter).await;
201 let size = window.inner_size();
203 let caps = surface.get_capabilities(&adapter);
204 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
206 wgpu::PresentMode::Fifo
208 } else {
209 wgpu::PresentMode::Immediate
211 };
212 info!("Using present mode: {present_mode:?}");
213 let config = wgpu::SurfaceConfiguration {
214 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
215 format: caps.formats[0],
216 width: size.width,
217 height: size.height,
218 present_mode,
219 alpha_mode: wgpu::CompositeAlphaMode::Auto,
220 view_formats: vec![],
221 desired_maximum_frame_latency: 2,
222 };
223 surface.configure(&gpu, &config);
224
225 let (msaa_texture, msaa_view) = Self::make_msaa_resources(&gpu, sample_count, &config);
227
228 let pass_a = Self::create_pass_target(&gpu, &config, "A");
230 let pass_b = Self::create_pass_target(&gpu, &config, "B");
231 let compute_target_a =
232 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute A");
233 let compute_target_b =
234 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute B");
235
236 let drawer = Drawer::new();
237
238 let scale_factor = window.scale_factor();
240 info!("Window scale factor: {scale_factor}");
241 SCALE_FACTOR
242 .set(RwLock::new(scale_factor))
243 .expect("Failed to set scale factor");
244
245 Self {
246 window,
247 gpu,
248 surface,
249 queue,
250 config,
251 size,
252 size_changed: false,
253 drawer,
254 pass_a,
255 pass_b,
256 compute_pipeline_registry: ComputePipelineRegistry::new(),
257 sample_count,
258 msaa_texture,
259 msaa_view,
260 compute_target_a,
261 compute_target_b,
262 compute_commands: Vec::new(),
263 resource_manager: Arc::new(RwLock::new(ComputeResourceManager::new())),
264 }
265 }
266
267 fn create_pass_target(
268 gpu: &wgpu::Device,
269 config: &wgpu::SurfaceConfiguration,
270 label_suffix: &str,
271 ) -> PassTarget {
272 let label = format!("Pass {label_suffix} Texture");
273 let texture_descriptor = wgpu::TextureDescriptor {
274 label: Some(&label),
275 size: wgpu::Extent3d {
276 width: config.width,
277 height: config.height,
278 depth_or_array_layers: 1,
279 },
280 mip_level_count: 1,
281 sample_count: 1,
282 dimension: wgpu::TextureDimension::D2,
283 format: config.format,
285 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
286 | wgpu::TextureUsages::TEXTURE_BINDING
287 | wgpu::TextureUsages::COPY_DST
288 | wgpu::TextureUsages::COPY_SRC,
289 view_formats: &[],
290 };
291 let texture = gpu.create_texture(&texture_descriptor);
292 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
293 PassTarget { texture, view }
294 }
295
296 fn create_compute_pass_target(
297 gpu: &wgpu::Device,
298 config: &wgpu::SurfaceConfiguration,
299 format: TextureFormat,
300 label_suffix: &str,
301 ) -> PassTarget {
302 let label = format!("Compute {label_suffix} Texture");
303 let texture_descriptor = wgpu::TextureDescriptor {
304 label: Some(&label),
305 size: wgpu::Extent3d {
306 width: config.width,
307 height: config.height,
308 depth_or_array_layers: 1,
309 },
310 mip_level_count: 1,
311 sample_count: 1,
312 dimension: wgpu::TextureDimension::D2,
313 format,
314 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
315 | wgpu::TextureUsages::TEXTURE_BINDING
316 | wgpu::TextureUsages::STORAGE_BINDING
317 | wgpu::TextureUsages::COPY_DST
318 | wgpu::TextureUsages::COPY_SRC,
319 view_formats: &[],
320 };
321 let texture = gpu.create_texture(&texture_descriptor);
322 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
323 PassTarget { texture, view }
324 }
325
326 pub fn register_pipelines(&mut self, register_fn: impl FnOnce(&mut Self)) {
327 register_fn(self);
328 }
329
330 pub(crate) fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
333 if self.size == size {
334 return;
335 }
336 self.size = size;
337 self.size_changed = true;
338 }
339
340 pub(crate) fn size(&self) -> winit::dpi::PhysicalSize<u32> {
342 self.size
343 }
344
345 pub(crate) fn resize_pass_targets_if_needed(&mut self) {
346 if self.size_changed {
347 self.pass_a.texture.destroy();
348 self.pass_b.texture.destroy();
349 self.compute_target_a.texture.destroy();
350 self.compute_target_b.texture.destroy();
351
352 self.pass_a = Self::create_pass_target(&self.gpu, &self.config, "A");
353 self.pass_b = Self::create_pass_target(&self.gpu, &self.config, "B");
354 self.compute_target_a = Self::create_compute_pass_target(
355 &self.gpu,
356 &self.config,
357 TextureFormat::Rgba8Unorm,
358 "Compute A",
359 );
360 self.compute_target_b = Self::create_compute_pass_target(
361 &self.gpu,
362 &self.config,
363 TextureFormat::Rgba8Unorm,
364 "Compute B",
365 );
366
367 if self.sample_count > 1 {
368 if let Some(t) = self.msaa_texture.take() {
369 t.destroy();
370 }
371 let (msaa_texture, msaa_view) =
372 Self::make_msaa_resources(&self.gpu, self.sample_count, &self.config);
373 self.msaa_texture = msaa_texture;
374 self.msaa_view = msaa_view;
375 }
376 }
377 }
378
379 pub(crate) fn resize_if_needed(&mut self) -> bool {
381 let result = self.size_changed;
382 if self.size_changed {
383 self.config.width = self.size.width;
384 self.config.height = self.size.height;
385 self.resize_pass_targets_if_needed();
386 self.surface.configure(&self.gpu, &self.config);
387 self.size_changed = false;
388 }
389 result
390 }
391
392 fn handle_ping_pong_and_compute(
395 context: WgpuContext<'_>,
396 read_target: &mut PassTarget,
397 write_target: &mut PassTarget,
398 compute_resources: ComputeResources<'_>,
399 ) -> wgpu::TextureView {
400 std::mem::swap(read_target, write_target);
402 let texture_size = wgpu::Extent3d {
403 width: context.config.width,
404 height: context.config.height,
405 depth_or_array_layers: 1,
406 };
407 context.encoder.copy_texture_to_texture(
408 read_target.texture.as_image_copy(),
409 write_target.texture.as_image_copy(),
410 texture_size,
411 );
412 if !compute_resources.compute_commands.is_empty() {
414 let compute_commands_taken = std::mem::take(compute_resources.compute_commands);
415 Self::do_compute(DoComputeParams {
416 encoder: context.encoder,
417 commands: compute_commands_taken,
418 compute_pipeline_registry: compute_resources.compute_pipeline_registry,
419 gpu: context.gpu,
420 queue: context.queue,
421 config: context.config,
422 resource_manager: compute_resources.resource_manager,
423 scene_view: &read_target.view,
424 target_a: compute_resources.compute_target_a,
425 target_b: compute_resources.compute_target_b,
426 })
427 } else {
428 read_target.view.clone()
430 }
431 }
432
433 pub(crate) fn render(
447 &mut self,
448 commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
449 ) -> Result<(), wgpu::SurfaceError> {
450 let commands: Vec<_> = commands.into_iter().collect();
452 let commands = super::reorder::reorder_instructions(commands);
454
455 let output_frame = self.surface.get_current_texture()?;
456 let mut encoder = self
457 .gpu
458 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
459 label: Some("Render Encoder"),
460 });
461
462 let texture_size = wgpu::Extent3d {
463 width: self.config.width,
464 height: self.config.height,
465 depth_or_array_layers: 1,
466 };
467
468 let (read_target, write_target) = (&mut self.pass_a, &mut self.pass_b);
470
471 if !self.compute_commands.is_empty() {
473 warn!("Not every compute command is used in last frame. This is likely a bug.");
475 self.compute_commands.clear();
476 }
477
478 let mut is_first_pass = true;
480
481 self.drawer
483 .pipeline_registry
484 .begin_all_frames(&self.gpu, &self.queue, &self.config);
485
486 let mut scene_texture_view = read_target.view.clone();
489 let mut commands_in_pass: Vec<DrawOrClip> = Vec::new();
490 let mut barrier_draw_rects_in_pass: Vec<PxRect> = Vec::new();
491 let mut clip_stack: Vec<PxRect> = Vec::new();
492
493 for (command, command_type_id, size, start_pos) in commands {
494 let need_new_pass = commands_in_pass
495 .iter()
496 .rev()
497 .find_map(|command| match &command {
498 DrawOrClip::Draw(cmd) => Some(cmd),
499 DrawOrClip::Clip(_) => None,
500 })
501 .map(|cmd| {
502 match (cmd.command.barrier(), command.barrier()) {
503 (None, Some(_)) => true, (Some(_), Some(barrier)) => {
505 let last_draw_rect =
508 extract_draw_rect(Some(barrier), size, start_pos, texture_size);
509 !barrier_draw_rects_in_pass
511 .iter()
512 .all(|dr| dr.is_orthogonal(&last_draw_rect)) }
514 (Some(_), None) => false, (None, None) => false, }
517 })
518 .unwrap_or(false);
519
520 if need_new_pass {
521 if commands_in_pass
523 .iter()
524 .find_map(|command| match &command {
525 DrawOrClip::Draw(cmd) => Some(cmd),
526 DrawOrClip::Clip(_) => None,
527 })
528 .map(|cmd| cmd.command.barrier().is_some())
529 .unwrap_or(false)
530 {
531 let final_view_after_compute = Self::handle_ping_pong_and_compute(
533 WgpuContext {
534 encoder: &mut encoder,
535 gpu: &self.gpu,
536 queue: &self.queue,
537 config: &self.config,
538 },
539 read_target,
540 write_target,
541 ComputeResources {
542 compute_commands: &mut self.compute_commands,
543 compute_pipeline_registry: &mut self.compute_pipeline_registry,
544 resource_manager: &mut self.resource_manager.write(),
545 compute_target_a: &self.compute_target_a,
546 compute_target_b: &self.compute_target_b,
547 },
548 );
549 scene_texture_view = final_view_after_compute;
550 }
551
552 render_current_pass(RenderCurrentPassParams {
554 msaa_view: &self.msaa_view,
555 is_first_pass: &mut is_first_pass,
556 encoder: &mut encoder,
557 write_target,
558 commands_in_pass: &mut commands_in_pass,
559 scene_texture_view: &scene_texture_view,
560 drawer: &mut self.drawer,
561 gpu: &self.gpu,
562 queue: &self.queue,
563 config: &self.config,
564 clip_stack: &mut clip_stack,
565 });
566 commands_in_pass.clear();
567 barrier_draw_rects_in_pass.clear();
568 }
569
570 match command {
571 Command::Draw(cmd) => {
572 let draw_rect = extract_draw_rect(cmd.barrier(), size, start_pos, texture_size);
574 if cmd.barrier().is_some() {
576 barrier_draw_rects_in_pass.push(draw_rect);
577 }
578 commands_in_pass.push(DrawOrClip::Draw(DrawCommandWithMetadata {
580 command: cmd,
581 type_id: command_type_id,
582 size,
583 start_pos,
584 draw_rect,
585 }));
586 }
587 Command::Compute(cmd) => {
588 self.compute_commands.push((cmd, size, start_pos));
590 }
591 Command::ClipPush(rect) => {
592 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Push(rect)));
594 }
595 Command::ClipPop => {
596 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Pop));
598 }
599 }
600 }
601
602 if !commands_in_pass.is_empty() {
604 if commands_in_pass
606 .iter()
607 .find_map(|command| match &command {
608 DrawOrClip::Draw(cmd) => Some(cmd),
609 DrawOrClip::Clip(_) => None,
610 })
611 .map(|cmd| cmd.command.barrier().is_some())
612 .unwrap_or(false)
613 {
614 let final_view_after_compute = Self::handle_ping_pong_and_compute(
616 WgpuContext {
617 encoder: &mut encoder,
618 gpu: &self.gpu,
619 queue: &self.queue,
620 config: &self.config,
621 },
622 read_target,
623 write_target,
624 ComputeResources {
625 compute_commands: &mut self.compute_commands,
626 compute_pipeline_registry: &mut self.compute_pipeline_registry,
627 resource_manager: &mut self.resource_manager.write(),
628 compute_target_a: &self.compute_target_a,
629 compute_target_b: &self.compute_target_b,
630 },
631 );
632 scene_texture_view = final_view_after_compute;
633 }
634
635 render_current_pass(RenderCurrentPassParams {
637 msaa_view: &self.msaa_view,
638 is_first_pass: &mut is_first_pass,
639 encoder: &mut encoder,
640 write_target,
641 commands_in_pass: &mut commands_in_pass,
642 scene_texture_view: &scene_texture_view,
643 drawer: &mut self.drawer,
644 gpu: &self.gpu,
645 queue: &self.queue,
646 config: &self.config,
647 clip_stack: &mut clip_stack,
648 });
649 commands_in_pass.clear();
650 barrier_draw_rects_in_pass.clear();
651 }
652
653 self.drawer
655 .pipeline_registry
656 .end_all_frames(&self.gpu, &self.queue, &self.config);
657
658 encoder.copy_texture_to_texture(
660 write_target.texture.as_image_copy(),
661 output_frame.texture.as_image_copy(),
662 texture_size,
663 );
664
665 self.queue.submit(Some(encoder.finish()));
666 output_frame.present();
667
668 Ok(())
669 }
670
671 pub(crate) fn render_dummy(&mut self) -> Result<(), wgpu::SurfaceError> {
672 let output_frame = self.surface.get_current_texture()?;
673 let mut encoder = self
674 .gpu
675 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
676 label: Some("Render Encoder(Dummy)"),
677 });
678
679 encoder.copy_texture_to_texture(
680 self.pass_b.texture.as_image_copy(),
681 output_frame.texture.as_image_copy(),
682 wgpu::Extent3d {
683 width: self.config.width,
684 height: self.config.height,
685 depth_or_array_layers: 1,
686 },
687 );
688
689 self.queue.submit(Some(encoder.finish()));
690 output_frame.present();
691 Ok(())
692 }
693
694 fn do_compute(params: DoComputeParams<'_>) -> wgpu::TextureView {
695 if params.commands.is_empty() {
696 return params.scene_view.clone();
697 }
698
699 let mut read_view = params.scene_view.clone();
700 let (mut write_target, mut read_target) = (params.target_a, params.target_b);
701
702 for (command, size, start_pos) in params.commands {
703 params.encoder.clear_texture(
705 &write_target.texture,
706 &ImageSubresourceRange {
707 aspect: wgpu::TextureAspect::All,
708 base_mip_level: 0,
709 mip_level_count: None,
710 base_array_layer: 0,
711 array_layer_count: None,
712 },
713 );
714
715 {
717 let mut cpass = params
718 .encoder
719 .begin_compute_pass(&wgpu::ComputePassDescriptor {
720 label: Some("Compute Pass"),
721 timestamp_writes: None,
722 });
723
724 let texture_size = wgpu::Extent3d {
726 width: params.config.width,
727 height: params.config.height,
728 depth_or_array_layers: 1,
729 };
730 let area =
731 extract_draw_rect(Some(command.barrier()), size, start_pos, texture_size);
732
733 params.compute_pipeline_registry.dispatch_erased(
734 params.gpu,
735 params.queue,
736 params.config,
737 &mut cpass,
738 &*command,
739 params.resource_manager,
740 area,
741 &read_view,
742 &write_target.view,
743 );
744 } read_view = write_target.view.clone();
749 std::mem::swap(&mut write_target, &mut read_target);
751 }
752
753 read_view
756 }
757}
758
759fn compute_padded_rect(
760 size: PxSize,
761 start_pos: PxPosition,
762 top: Px,
763 right: Px,
764 bottom: Px,
765 left: Px,
766 texture_size: wgpu::Extent3d,
767) -> PxRect {
768 let padded_x = (start_pos.x - left).max(Px(0));
769 let padded_y = (start_pos.y - top).max(Px(0));
770 let padded_width = (size.width + left + right).min(Px(texture_size.width as i32 - padded_x.0));
771 let padded_height =
772 (size.height + top + bottom).min(Px(texture_size.height as i32 - padded_y.0));
773 PxRect {
774 x: padded_x,
775 y: padded_y,
776 width: padded_width,
777 height: padded_height,
778 }
779}
780
781fn clamp_rect_to_texture(mut rect: PxRect, texture_size: wgpu::Extent3d) -> PxRect {
782 rect.x = rect.x.positive().min(texture_size.width).into();
783 rect.y = rect.y.positive().min(texture_size.height).into();
784 rect.width = rect
785 .width
786 .positive()
787 .min(texture_size.width - rect.x.positive())
788 .into();
789 rect.height = rect
790 .height
791 .positive()
792 .min(texture_size.height - rect.y.positive())
793 .into();
794 rect
795}
796
797fn extract_draw_rect(
798 barrier: Option<BarrierRequirement>,
799 size: PxSize,
800 start_pos: PxPosition,
801 texture_size: wgpu::Extent3d,
802) -> PxRect {
803 match barrier {
804 Some(BarrierRequirement::Global) => PxRect {
805 x: Px(0),
806 y: Px(0),
807 width: Px(texture_size.width as i32),
808 height: Px(texture_size.height as i32),
809 },
810 Some(BarrierRequirement::PaddedLocal {
811 top,
812 right,
813 bottom,
814 left,
815 }) => compute_padded_rect(size, start_pos, top, right, bottom, left, texture_size),
816 Some(BarrierRequirement::Absolute(rect)) => clamp_rect_to_texture(rect, texture_size),
817 None => {
818 let x = start_pos.x.positive().min(texture_size.width);
819 let y = start_pos.y.positive().min(texture_size.height);
820 let width = size.width.positive().min(texture_size.width - x);
821 let height = size.height.positive().min(texture_size.height - y);
822 PxRect {
823 x: Px::from(x),
824 y: Px::from(y),
825 width: Px::from(width),
826 height: Px::from(height),
827 }
828 }
829 }
830}
831
832fn render_current_pass(params: RenderCurrentPassParams<'_>) {
833 let (view, resolve_target) = if let Some(msaa_view) = params.msaa_view {
834 (msaa_view, Some(¶ms.write_target.view))
835 } else {
836 (¶ms.write_target.view, None)
837 };
838
839 let load_ops = if *params.is_first_pass {
840 *params.is_first_pass = false;
841 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
842 } else {
843 wgpu::LoadOp::Load
844 };
845
846 let mut rpass = params
847 .encoder
848 .begin_render_pass(&wgpu::RenderPassDescriptor {
849 label: Some("Render Pass"),
850 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
851 view,
852 depth_slice: None,
853 resolve_target,
854 ops: wgpu::Operations {
855 load: load_ops,
856 store: wgpu::StoreOp::Store,
857 },
858 })],
859 ..Default::default()
860 });
861
862 params.drawer.begin_pass(
863 params.gpu,
864 params.queue,
865 params.config,
866 &mut rpass,
867 params.scene_texture_view,
868 );
869
870 let mut buffer: Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)> = Vec::new();
872 let mut last_command_type_id = None;
873 let mut current_batch_draw_rect: Option<PxRect> = None;
874 for cmd in mem::take(params.commands_in_pass).into_iter() {
875 let cmd = match cmd {
876 DrawOrClip::Clip(clip_ops) => {
877 if !buffer.is_empty() {
879 submit_buffered_commands(
880 &mut rpass,
881 params.drawer,
882 params.gpu,
883 params.queue,
884 params.config,
885 &mut buffer,
886 params.scene_texture_view,
887 params.clip_stack,
888 &mut current_batch_draw_rect,
889 );
890 last_command_type_id = None; }
892 match clip_ops {
894 ClipOps::Push(rect) => {
895 params.clip_stack.push(rect);
896 }
897 ClipOps::Pop => {
898 params.clip_stack.pop();
899 }
900 }
901 continue;
903 }
904 DrawOrClip::Draw(cmd) => cmd, };
906
907 if !can_merge_into_batch(&last_command_type_id, cmd.type_id) && !buffer.is_empty() {
909 submit_buffered_commands(
910 &mut rpass,
911 params.drawer,
912 params.gpu,
913 params.queue,
914 params.config,
915 &mut buffer,
916 params.scene_texture_view,
917 params.clip_stack,
918 &mut current_batch_draw_rect,
919 );
920 }
921
922 buffer.push((cmd.command, cmd.size, cmd.start_pos));
924 last_command_type_id = Some(cmd.type_id);
925 current_batch_draw_rect = Some(merge_batch_rect(current_batch_draw_rect, cmd.draw_rect));
926 }
927
928 if !buffer.is_empty() {
930 submit_buffered_commands(
931 &mut rpass,
932 params.drawer,
933 params.gpu,
934 params.queue,
935 params.config,
936 &mut buffer,
937 params.scene_texture_view,
938 params.clip_stack,
939 &mut current_batch_draw_rect,
940 );
941 }
942
943 params.drawer.end_pass(
944 params.gpu,
945 params.queue,
946 params.config,
947 &mut rpass,
948 params.scene_texture_view,
949 );
950}
951
952fn submit_buffered_commands(
953 rpass: &mut wgpu::RenderPass<'_>,
954 drawer: &mut Drawer,
955 gpu: &wgpu::Device,
956 queue: &wgpu::Queue,
957 config: &wgpu::SurfaceConfiguration,
958 buffer: &mut Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)>,
959 scene_texture_view: &wgpu::TextureView,
960 clip_stack: &mut [PxRect],
961 current_batch_draw_rect: &mut Option<PxRect>,
962) {
963 let commands = mem::take(buffer);
965 let commands = commands
966 .iter()
967 .map(|(cmd, sz, pos)| (&**cmd, *sz, *pos))
968 .collect::<Vec<_>>();
969
970 let (current_clip_rect, anything_to_submit) =
972 apply_clip_to_batch_rect(clip_stack, current_batch_draw_rect);
973 if !anything_to_submit {
974 return;
975 }
976
977 let rect = current_batch_draw_rect.unwrap();
978 set_scissor_rect_from_pxrect(rpass, rect);
979
980 drawer.submit(
981 gpu,
982 queue,
983 config,
984 rpass,
985 &commands,
986 scene_texture_view,
987 current_clip_rect,
988 );
989 *current_batch_draw_rect = None;
990}
991
992fn set_scissor_rect_from_pxrect(rpass: &mut wgpu::RenderPass<'_>, rect: PxRect) {
993 rpass.set_scissor_rect(
994 rect.x.positive(),
995 rect.y.positive(),
996 rect.width.positive(),
997 rect.height.positive(),
998 );
999}
1000
1001fn apply_clip_to_batch_rect(
1006 clip_stack: &[PxRect],
1007 current_batch_draw_rect: &mut Option<PxRect>,
1008) -> (Option<PxRect>, bool) {
1009 if let Some(clipped_rect) = clip_stack.last() {
1010 let Some(current_rect) = current_batch_draw_rect.as_ref() else {
1011 return (Some(*clipped_rect), false);
1012 };
1013 if let Some(final_rect) = current_rect.intersection(clipped_rect) {
1014 *current_batch_draw_rect = Some(final_rect);
1015 return (Some(*clipped_rect), true);
1016 } else {
1017 return (Some(*clipped_rect), false);
1018 }
1019 }
1020 (None, true)
1021}
1022
1023fn can_merge_into_batch(last_command_type_id: &Option<TypeId>, next_type_id: TypeId) -> bool {
1027 match last_command_type_id {
1028 Some(l) => *l == next_type_id,
1029 None => false,
1030 }
1031}
1032
1033fn merge_batch_rect(current: Option<PxRect>, next: PxRect) -> PxRect {
1035 current.map(|dr| dr.union(&next)).unwrap_or(next)
1036}
1037
1038struct DrawCommandWithMetadata {
1039 command: Box<dyn DrawCommand>,
1040 type_id: TypeId,
1041 size: PxSize,
1042 start_pos: PxPosition,
1043 draw_rect: PxRect,
1044}
1045
1046enum DrawOrClip {
1047 Draw(DrawCommandWithMetadata),
1048 Clip(ClipOps),
1049}
1050
1051enum ClipOps {
1052 Push(PxRect),
1053 Pop,
1054}