1use std::{any::TypeId, mem, sync::Arc};
2
3use parking_lot::RwLock;
4use tracing::{error, info, warn};
5use wgpu::TextureFormat;
6use winit::window::Window;
7
8use crate::{
9 ComputablePipeline, ComputeCommand, DrawCommand, DrawablePipeline, Px, PxPosition,
10 compute::resource::ComputeResourceManager,
11 dp::SCALE_FACTOR,
12 px::{PxRect, PxSize},
13 renderer::command::{AsAny, BarrierRequirement, Command},
14};
15
16use super::{
17 compute::{ComputePipelineRegistry, ErasedComputeBatchItem},
18 drawer::Drawer,
19};
20
21struct WgpuContext<'a> {
23 encoder: &'a mut wgpu::CommandEncoder,
24 gpu: &'a wgpu::Device,
25 queue: &'a wgpu::Queue,
26 config: &'a wgpu::SurfaceConfiguration,
27}
28
29struct RenderCurrentPassParams<'a> {
31 msaa_view: &'a Option<wgpu::TextureView>,
32 is_first_pass: &'a mut bool,
33 encoder: &'a mut wgpu::CommandEncoder,
34 write_target: &'a wgpu::TextureView,
35 commands_in_pass: &'a mut Vec<DrawOrClip>,
36 scene_texture_view: &'a wgpu::TextureView,
37 drawer: &'a mut Drawer,
38 gpu: &'a wgpu::Device,
39 queue: &'a wgpu::Queue,
40 config: &'a wgpu::SurfaceConfiguration,
41 clip_stack: &'a mut Vec<PxRect>,
42}
43
44struct DoComputeParams<'a> {
46 encoder: &'a mut wgpu::CommandEncoder,
47 commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
48 compute_pipeline_registry: &'a mut ComputePipelineRegistry,
49 gpu: &'a wgpu::Device,
50 queue: &'a wgpu::Queue,
51 config: &'a wgpu::SurfaceConfiguration,
52 resource_manager: &'a mut ComputeResourceManager,
53 scene_view: &'a wgpu::TextureView,
54 target_a: &'a wgpu::TextureView,
55 target_b: &'a wgpu::TextureView,
56 blit_bind_group_layout: &'a wgpu::BindGroupLayout,
57 blit_sampler: &'a wgpu::Sampler,
58 compute_blit_pipeline: &'a wgpu::RenderPipeline,
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 wgpu::TextureView,
67 compute_target_b: &'a wgpu::TextureView,
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 offscreen_texture: wgpu::TextureView,
93
94 pub sample_count: u32,
96 msaa_texture: Option<wgpu::Texture>,
97 msaa_view: Option<wgpu::TextureView>,
98
99 compute_target_a: wgpu::TextureView,
101 compute_target_b: wgpu::TextureView,
102 compute_commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
103 pub resource_manager: Arc<RwLock<ComputeResourceManager>>,
104
105 blit_pipeline: wgpu::RenderPipeline,
107 blit_bind_group_layout: wgpu::BindGroupLayout,
108 blit_sampler: wgpu::Sampler,
109 compute_blit_pipeline: wgpu::RenderPipeline,
110}
111
112impl WgpuApp {
113 async fn request_adapter_for_surface(
117 instance: &wgpu::Instance,
118 surface: &wgpu::Surface<'_>,
119 ) -> wgpu::Adapter {
120 match instance
121 .request_adapter(&wgpu::RequestAdapterOptions {
122 power_preference: wgpu::PowerPreference::default(),
123 compatible_surface: Some(surface),
124 force_fallback_adapter: false,
125 })
126 .await
127 {
128 Ok(gpu) => gpu,
129 Err(e) => {
130 error!("Failed to find an appropriate adapter: {e:?}");
131 panic!("Failed to find an appropriate adapter: {e:?}");
132 }
133 }
134 }
135
136 async fn request_device_and_queue_for_adapter(
137 adapter: &wgpu::Adapter,
138 ) -> (wgpu::Device, wgpu::Queue) {
139 match adapter
140 .request_device(&wgpu::DeviceDescriptor {
141 required_features: wgpu::Features::empty() | wgpu::Features::CLEAR_TEXTURE,
142 required_limits: if cfg!(target_arch = "wasm32") {
143 wgpu::Limits::downlevel_webgl2_defaults()
144 } else {
145 wgpu::Limits::default()
146 },
147 label: None,
148 memory_hints: wgpu::MemoryHints::Performance,
149 trace: wgpu::Trace::Off,
150 experimental_features: wgpu::ExperimentalFeatures::default(),
151 })
152 .await
153 {
154 Ok((gpu, queue)) => (gpu, queue),
155 Err(e) => {
156 error!("Failed to create device: {e:?}");
157 panic!("Failed to create device: {e:?}");
158 }
159 }
160 }
161
162 fn make_msaa_resources(
163 gpu: &wgpu::Device,
164 sample_count: u32,
165 config: &wgpu::SurfaceConfiguration,
166 ) -> (Option<wgpu::Texture>, Option<wgpu::TextureView>) {
167 if sample_count > 1 {
168 let texture = gpu.create_texture(&wgpu::TextureDescriptor {
169 label: Some("MSAA Framebuffer"),
170 size: wgpu::Extent3d {
171 width: config.width,
172 height: config.height,
173 depth_or_array_layers: 1,
174 },
175 mip_level_count: 1,
176 sample_count,
177 dimension: wgpu::TextureDimension::D2,
178 format: config.format,
179 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
180 view_formats: &[],
181 });
182 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
183 (Some(texture), Some(view))
184 } else {
185 (None, None)
186 }
187 }
188
189 pub(crate) async fn new(window: Arc<Window>, sample_count: u32) -> Self {
191 let instance: wgpu::Instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
193 backends: wgpu::Backends::all(),
194 ..Default::default()
195 });
196 let surface = match instance.create_surface(window.clone()) {
198 Ok(surface) => surface,
199 Err(e) => {
200 error!("Failed to create surface: {e:?}");
201 panic!("Failed to create surface: {e:?}");
202 }
203 };
204 let adapter = Self::request_adapter_for_surface(&instance, &surface).await;
206 let (gpu, queue) = Self::request_device_and_queue_for_adapter(&adapter).await;
208 let size = window.inner_size();
210 let caps = surface.get_capabilities(&adapter);
211 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
213 wgpu::PresentMode::Fifo
215 } else {
216 wgpu::PresentMode::Immediate
218 };
219 info!("Using present mode: {present_mode:?}");
220 let config = wgpu::SurfaceConfiguration {
221 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
222 format: caps.formats[0],
223 width: size.width,
224 height: size.height,
225 present_mode,
226 alpha_mode: wgpu::CompositeAlphaMode::Auto,
227 view_formats: vec![],
228 desired_maximum_frame_latency: 2,
229 };
230 surface.configure(&gpu, &config);
231
232 let (msaa_texture, msaa_view) = Self::make_msaa_resources(&gpu, sample_count, &config);
234
235 let offscreen_texture = Self::create_pass_target(&gpu, &config, "Offscreen");
237 let compute_target_a =
238 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute A");
239 let compute_target_b =
240 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute B");
241
242 let drawer = Drawer::new();
243
244 let scale_factor = window.scale_factor();
246 info!("Window scale factor: {scale_factor}");
247 let _ = SCALE_FACTOR.set(RwLock::new(scale_factor));
248
249 let blit_shader = gpu.create_shader_module(wgpu::include_wgsl!("shaders/blit.wgsl"));
251 let blit_sampler = gpu.create_sampler(&wgpu::SamplerDescriptor::default());
252 let blit_bind_group_layout =
253 gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
254 label: Some("Blit Bind Group Layout"),
255 entries: &[
256 wgpu::BindGroupLayoutEntry {
257 binding: 0,
258 visibility: wgpu::ShaderStages::FRAGMENT,
259 ty: wgpu::BindingType::Texture {
260 sample_type: wgpu::TextureSampleType::Float { filterable: true },
261 view_dimension: wgpu::TextureViewDimension::D2,
262 multisampled: false,
263 },
264 count: None,
265 },
266 wgpu::BindGroupLayoutEntry {
267 binding: 1,
268 visibility: wgpu::ShaderStages::FRAGMENT,
269 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
270 count: None,
271 },
272 ],
273 });
274
275 let blit_pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
276 label: Some("Blit Pipeline Layout"),
277 bind_group_layouts: &[&blit_bind_group_layout],
278 push_constant_ranges: &[],
279 });
280
281 let blit_pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
282 label: Some("Blit Pipeline"),
283 layout: Some(&blit_pipeline_layout),
284 vertex: wgpu::VertexState {
285 module: &blit_shader,
286 entry_point: Some("vs_main"),
287 buffers: &[],
288 compilation_options: Default::default(),
289 },
290 fragment: Some(wgpu::FragmentState {
291 module: &blit_shader,
292 entry_point: Some("fs_main"),
293 targets: &[Some(config.format.into())],
294 compilation_options: Default::default(),
295 }),
296 primitive: wgpu::PrimitiveState::default(),
297 depth_stencil: None,
298 multisample: wgpu::MultisampleState::default(),
299 multiview: None,
300 cache: None,
301 });
302
303 let compute_blit_pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
304 label: Some("Compute Copy Pipeline"),
305 layout: Some(&blit_pipeline_layout),
306 vertex: wgpu::VertexState {
307 module: &blit_shader,
308 entry_point: Some("vs_main"),
309 buffers: &[],
310 compilation_options: Default::default(),
311 },
312 fragment: Some(wgpu::FragmentState {
313 module: &blit_shader,
314 entry_point: Some("fs_main"),
315 targets: &[Some(TextureFormat::Rgba8Unorm.into())],
316 compilation_options: Default::default(),
317 }),
318 primitive: wgpu::PrimitiveState::default(),
319 depth_stencil: None,
320 multisample: wgpu::MultisampleState::default(),
321 multiview: None,
322 cache: None,
323 });
324
325 Self {
326 window,
327 gpu,
328 surface,
329 queue,
330 config,
331 size,
332 size_changed: false,
333 drawer,
334 offscreen_texture,
335 compute_pipeline_registry: ComputePipelineRegistry::new(),
336 sample_count,
337 msaa_texture,
338 msaa_view,
339 compute_target_a,
340 compute_target_b,
341 compute_commands: Vec::new(),
342 resource_manager: Arc::new(RwLock::new(ComputeResourceManager::new())),
343 blit_pipeline,
344 blit_bind_group_layout,
345 blit_sampler,
346 compute_blit_pipeline,
347 }
348 }
349
350 pub fn register_draw_pipeline<T, P>(&mut self, pipeline: P)
354 where
355 T: DrawCommand + 'static,
356 P: DrawablePipeline<T> + 'static,
357 {
358 self.drawer.pipeline_registry.register(pipeline);
359 }
360
361 pub fn register_compute_pipeline<T, P>(&mut self, pipeline: P)
365 where
366 T: ComputeCommand + 'static,
367 P: ComputablePipeline<T> + 'static,
368 {
369 self.compute_pipeline_registry.register(pipeline);
370 }
371
372 fn create_pass_target(
373 gpu: &wgpu::Device,
374 config: &wgpu::SurfaceConfiguration,
375 label_suffix: &str,
376 ) -> wgpu::TextureView {
377 let label = format!("Pass {label_suffix} Texture");
378 let texture_descriptor = wgpu::TextureDescriptor {
379 label: Some(&label),
380 size: wgpu::Extent3d {
381 width: config.width,
382 height: config.height,
383 depth_or_array_layers: 1,
384 },
385 mip_level_count: 1,
386 sample_count: 1,
387 dimension: wgpu::TextureDimension::D2,
388 format: config.format,
390 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
391 | wgpu::TextureUsages::TEXTURE_BINDING
392 | wgpu::TextureUsages::COPY_DST
393 | wgpu::TextureUsages::COPY_SRC,
394 view_formats: &[],
395 };
396 let texture = gpu.create_texture(&texture_descriptor);
397 texture.create_view(&wgpu::TextureViewDescriptor::default())
398 }
399
400 fn create_compute_pass_target(
401 gpu: &wgpu::Device,
402 config: &wgpu::SurfaceConfiguration,
403 format: TextureFormat,
404 label_suffix: &str,
405 ) -> wgpu::TextureView {
406 let label = format!("Compute {label_suffix} Texture");
407 let texture_descriptor = wgpu::TextureDescriptor {
408 label: Some(&label),
409 size: wgpu::Extent3d {
410 width: config.width,
411 height: config.height,
412 depth_or_array_layers: 1,
413 },
414 mip_level_count: 1,
415 sample_count: 1,
416 dimension: wgpu::TextureDimension::D2,
417 format,
418 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
419 | wgpu::TextureUsages::TEXTURE_BINDING
420 | wgpu::TextureUsages::STORAGE_BINDING
421 | wgpu::TextureUsages::COPY_DST
422 | wgpu::TextureUsages::COPY_SRC,
423 view_formats: &[],
424 };
425 let texture = gpu.create_texture(&texture_descriptor);
426 texture.create_view(&wgpu::TextureViewDescriptor::default())
427 }
428
429 pub fn register_pipelines(&mut self, register_fn: impl FnOnce(&mut Self)) {
430 register_fn(self);
431 }
432
433 pub(crate) fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
436 if self.size == size {
437 return;
438 }
439 self.size = size;
440 self.size_changed = true;
441 }
442
443 pub(crate) fn size(&self) -> winit::dpi::PhysicalSize<u32> {
445 self.size
446 }
447
448 pub(crate) fn resize_surface(&mut self) {
449 if self.size.width > 0 && self.size.height > 0 {
450 self.config.width = self.size.width;
451 self.config.height = self.size.height;
452 self.surface.configure(&self.gpu, &self.config);
453 self.rebuild_pass_targets();
454 }
455 }
456
457 pub(crate) fn rebuild_pass_targets(&mut self) {
458 self.offscreen_texture.texture().destroy();
459 self.compute_target_a.texture().destroy();
460 self.compute_target_b.texture().destroy();
461
462 self.offscreen_texture = Self::create_pass_target(&self.gpu, &self.config, "Offscreen");
463 self.compute_target_a = Self::create_compute_pass_target(
464 &self.gpu,
465 &self.config,
466 TextureFormat::Rgba8Unorm,
467 "Compute A",
468 );
469 self.compute_target_b = Self::create_compute_pass_target(
470 &self.gpu,
471 &self.config,
472 TextureFormat::Rgba8Unorm,
473 "Compute B",
474 );
475
476 if self.sample_count > 1 {
477 if let Some(t) = self.msaa_texture.take() {
478 t.destroy();
479 }
480 let (msaa_texture, msaa_view) =
481 Self::make_msaa_resources(&self.gpu, self.sample_count, &self.config);
482 self.msaa_texture = msaa_texture;
483 self.msaa_view = msaa_view;
484 }
485 }
486
487 pub(crate) fn resize_if_needed(&mut self) -> bool {
489 let result = self.size_changed;
490 if self.size_changed {
491 self.resize_surface();
492 self.size_changed = false;
493 }
494 result
495 }
496
497 fn handle_offscreen_and_compute(
500 context: WgpuContext<'_>,
501 offscreen_texture: &mut wgpu::TextureView,
502 output_texture: &mut wgpu::TextureView,
503 compute_resources: ComputeResources<'_>,
504 copy_rect: PxRect,
505 blit_bind_group_layout: &wgpu::BindGroupLayout,
506 blit_sampler: &wgpu::Sampler,
507 blit_pipeline: &wgpu::RenderPipeline,
508 compute_blit_pipeline: &wgpu::RenderPipeline,
509 ) -> wgpu::TextureView {
510 let blit_bind_group = context.gpu.create_bind_group(&wgpu::BindGroupDescriptor {
511 layout: blit_bind_group_layout,
512 entries: &[
513 wgpu::BindGroupEntry {
514 binding: 0,
515 resource: wgpu::BindingResource::TextureView(output_texture),
516 },
517 wgpu::BindGroupEntry {
518 binding: 1,
519 resource: wgpu::BindingResource::Sampler(blit_sampler),
520 },
521 ],
522 label: Some("Blit Bind Group"),
523 });
524
525 let mut rpass = context
526 .encoder
527 .begin_render_pass(&wgpu::RenderPassDescriptor {
528 label: Some("Blit Pass"),
529 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
530 view: offscreen_texture,
531 resolve_target: None,
532 ops: wgpu::Operations {
533 load: wgpu::LoadOp::Load,
534 store: wgpu::StoreOp::Store,
535 },
536 depth_slice: None,
537 })],
538 depth_stencil_attachment: None,
539 ..Default::default()
540 });
541
542 rpass.set_pipeline(blit_pipeline);
543 rpass.set_bind_group(0, &blit_bind_group, &[]);
544 rpass.set_scissor_rect(
546 copy_rect.x.0.max(0) as u32,
547 copy_rect.y.0.max(0) as u32,
548 copy_rect.width.0.max(0) as u32,
549 copy_rect.height.0.max(0) as u32,
550 );
551 rpass.draw(0..3, 0..1);
553
554 drop(rpass); if !compute_resources.compute_commands.is_empty() {
558 let compute_commands_taken = std::mem::take(compute_resources.compute_commands);
559 Self::do_compute(DoComputeParams {
560 encoder: context.encoder,
561 commands: compute_commands_taken,
562 compute_pipeline_registry: compute_resources.compute_pipeline_registry,
563 gpu: context.gpu,
564 queue: context.queue,
565 config: context.config,
566 resource_manager: compute_resources.resource_manager,
567 scene_view: offscreen_texture,
568 target_a: compute_resources.compute_target_a,
569 target_b: compute_resources.compute_target_b,
570 blit_bind_group_layout,
571 blit_sampler,
572 compute_blit_pipeline,
573 })
574 } else {
575 offscreen_texture.clone()
577 }
578 }
579
580 pub(crate) fn render(
595 &mut self,
596 commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
597 ) -> Result<(), wgpu::SurfaceError> {
598 let commands: Vec<_> = commands.into_iter().collect();
600 let commands = super::reorder::reorder_instructions(commands);
602
603 let output_frame = self.surface.get_current_texture()?;
604 let mut encoder = self
605 .gpu
606 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
607 label: Some("Render Encoder"),
608 });
609
610 let texture_size = wgpu::Extent3d {
611 width: self.config.width,
612 height: self.config.height,
613 depth_or_array_layers: 1,
614 };
615
616 if !self.compute_commands.is_empty() {
618 warn!("Not every compute command is used in last frame. This is likely a bug.");
620 self.compute_commands.clear();
621 }
622
623 let mut is_first_pass = true;
625
626 self.drawer
628 .pipeline_registry
629 .begin_all_frames(&self.gpu, &self.queue, &self.config);
630
631 let mut scene_texture_view = self.offscreen_texture.clone();
632 let mut commands_in_pass: Vec<DrawOrClip> = Vec::new();
633 let mut barrier_draw_rects_in_pass: Vec<PxRect> = Vec::new();
634 let mut clip_stack: Vec<PxRect> = Vec::new();
635
636 let mut output_view = output_frame
637 .texture
638 .create_view(&wgpu::TextureViewDescriptor::default());
639
640 for (command, command_type_id, size, start_pos) in commands {
641 let need_new_pass = commands_in_pass
642 .iter()
643 .rev()
644 .find_map(|command| match &command {
645 DrawOrClip::Draw(cmd) => Some(cmd),
646 DrawOrClip::Clip(_) => None,
647 })
648 .map(|cmd| match (cmd.command.barrier(), command.barrier()) {
649 (None, Some(_)) => true,
650 (Some(_), Some(barrier)) => {
651 let last_draw_rect =
652 extract_draw_rect(Some(barrier), size, start_pos, texture_size);
653 !barrier_draw_rects_in_pass
654 .iter()
655 .all(|dr| dr.is_orthogonal(&last_draw_rect))
656 }
657 (Some(_), None) => false,
658 (None, None) => false,
659 })
660 .unwrap_or(false);
661
662 if need_new_pass {
663 if commands_in_pass
665 .iter()
666 .find_map(|command| match &command {
667 DrawOrClip::Draw(cmd) => Some(cmd),
668 DrawOrClip::Clip(_) => None,
669 })
670 .map(|cmd| cmd.command.barrier().is_some())
671 .unwrap_or(false)
672 {
673 let mut combined_rect = barrier_draw_rects_in_pass[0];
674 for rect in barrier_draw_rects_in_pass.iter().skip(1) {
675 combined_rect = combined_rect.union(rect);
676 }
677
678 let final_view_after_compute = Self::handle_offscreen_and_compute(
679 WgpuContext {
680 encoder: &mut encoder,
681 gpu: &self.gpu,
682 queue: &self.queue,
683 config: &self.config,
684 },
685 &mut self.offscreen_texture,
686 &mut output_view,
687 ComputeResources {
688 compute_commands: &mut self.compute_commands,
689 compute_pipeline_registry: &mut self.compute_pipeline_registry,
690 resource_manager: &mut self.resource_manager.write(),
691 compute_target_a: &self.compute_target_a,
692 compute_target_b: &self.compute_target_b,
693 },
694 combined_rect,
695 &self.blit_bind_group_layout,
696 &self.blit_sampler,
697 &self.blit_pipeline,
698 &self.compute_blit_pipeline,
699 );
700 scene_texture_view = final_view_after_compute;
701 }
702
703 render_current_pass(RenderCurrentPassParams {
704 msaa_view: &self.msaa_view,
705 is_first_pass: &mut is_first_pass,
706 encoder: &mut encoder,
707 write_target: &output_view,
708 commands_in_pass: &mut commands_in_pass,
709 scene_texture_view: &scene_texture_view,
710 drawer: &mut self.drawer,
711 gpu: &self.gpu,
712 queue: &self.queue,
713 config: &self.config,
714 clip_stack: &mut clip_stack,
715 });
716 commands_in_pass.clear();
717 barrier_draw_rects_in_pass.clear();
718 }
719
720 match command {
721 Command::Draw(cmd) => {
722 let draw_rect = extract_draw_rect(cmd.barrier(), size, start_pos, texture_size);
724 if cmd.barrier().is_some() {
726 barrier_draw_rects_in_pass.push(draw_rect);
727 }
728 commands_in_pass.push(DrawOrClip::Draw(DrawCommandWithMetadata {
730 command: cmd,
731 type_id: command_type_id,
732 size,
733 start_pos,
734 draw_rect,
735 }));
736 }
737 Command::Compute(cmd) => {
738 self.compute_commands.push((cmd, size, start_pos));
740 }
741 Command::ClipPush(rect) => {
742 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Push(rect)));
744 }
745 Command::ClipPop => {
746 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Pop));
748 }
749 }
750 }
751
752 if !commands_in_pass.is_empty() {
754 if commands_in_pass
756 .iter()
757 .find_map(|command| match &command {
758 DrawOrClip::Draw(cmd) => Some(cmd),
759 DrawOrClip::Clip(_) => None,
760 })
761 .map(|cmd| cmd.command.barrier().is_some())
762 .unwrap_or(false)
763 {
764 let mut combined_rect = barrier_draw_rects_in_pass[0];
765 for rect in barrier_draw_rects_in_pass.iter().skip(1) {
766 combined_rect = combined_rect.union(rect);
767 }
768
769 let final_view_after_compute = Self::handle_offscreen_and_compute(
770 WgpuContext {
771 encoder: &mut encoder,
772 gpu: &self.gpu,
773 queue: &self.queue,
774 config: &self.config,
775 },
776 &mut self.offscreen_texture,
777 &mut output_view,
778 ComputeResources {
779 compute_commands: &mut self.compute_commands,
780 compute_pipeline_registry: &mut self.compute_pipeline_registry,
781 resource_manager: &mut self.resource_manager.write(),
782 compute_target_a: &self.compute_target_a,
783 compute_target_b: &self.compute_target_b,
784 },
785 combined_rect,
786 &self.blit_bind_group_layout,
787 &self.blit_sampler,
788 &self.blit_pipeline,
789 &self.compute_blit_pipeline,
790 );
791 scene_texture_view = final_view_after_compute;
792 }
793
794 render_current_pass(RenderCurrentPassParams {
796 msaa_view: &self.msaa_view,
797 is_first_pass: &mut is_first_pass,
798 encoder: &mut encoder,
799 write_target: &output_view,
800 commands_in_pass: &mut commands_in_pass,
801 scene_texture_view: &scene_texture_view,
802 drawer: &mut self.drawer,
803 gpu: &self.gpu,
804 queue: &self.queue,
805 config: &self.config,
806 clip_stack: &mut clip_stack,
807 });
808 commands_in_pass.clear();
809 barrier_draw_rects_in_pass.clear();
810 }
811
812 self.drawer
814 .pipeline_registry
815 .end_all_frames(&self.gpu, &self.queue, &self.config);
816
817 self.queue.submit(Some(encoder.finish()));
818 output_frame.present();
819
820 Ok(())
821 }
822
823 fn do_compute(params: DoComputeParams<'_>) -> wgpu::TextureView {
824 if params.commands.is_empty() {
825 return params.scene_view.clone();
826 }
827
828 let texture_size = wgpu::Extent3d {
829 width: params.config.width,
830 height: params.config.height,
831 depth_or_array_layers: 1,
832 };
833
834 Self::blit_to_view(
835 params.encoder,
836 params.gpu,
837 params.scene_view,
838 params.target_a,
839 params.blit_bind_group_layout,
840 params.blit_sampler,
841 params.compute_blit_pipeline,
842 );
843
844 let mut read_view = params.target_a.clone();
845 let mut write_target = params.target_b;
846 let mut read_target = params.target_a;
847
848 let mut index = 0;
849 while index < params.commands.len() {
850 let (command, size, start_pos) = ¶ms.commands[index];
851 let type_id = AsAny::as_any(&**command).type_id();
852
853 let mut batch_items: Vec<ErasedComputeBatchItem<'_>> = Vec::new();
854 let mut batch_areas: Vec<PxRect> = Vec::new();
855 let mut cursor = index;
856
857 while cursor < params.commands.len() {
858 let (candidate_command, candidate_size, candidate_pos) = ¶ms.commands[cursor];
859 if AsAny::as_any(&**candidate_command).type_id() != type_id {
860 break;
861 }
862
863 let area = extract_draw_rect(
864 Some(candidate_command.barrier()),
865 *candidate_size,
866 *candidate_pos,
867 texture_size,
868 );
869
870 if batch_areas
871 .iter()
872 .any(|existing| rects_overlap(*existing, area))
873 {
874 break;
875 }
876
877 batch_areas.push(area);
878 batch_items.push(ErasedComputeBatchItem {
879 command: &**candidate_command,
880 size: *candidate_size,
881 position: *candidate_pos,
882 target_area: area,
883 });
884 cursor += 1;
885 }
886
887 if batch_items.is_empty() {
888 let area =
889 extract_draw_rect(Some(command.barrier()), *size, *start_pos, texture_size);
890 batch_items.push(ErasedComputeBatchItem {
891 command: &**command,
892 size: *size,
893 position: *start_pos,
894 target_area: area,
895 });
896 batch_areas.push(area);
897 cursor = index + 1;
898 }
899
900 params.encoder.copy_texture_to_texture(
901 read_view.texture().as_image_copy(),
902 write_target.texture().as_image_copy(),
903 texture_size,
904 );
905
906 {
907 let mut cpass = params
908 .encoder
909 .begin_compute_pass(&wgpu::ComputePassDescriptor {
910 label: Some("Compute Pass"),
911 timestamp_writes: None,
912 });
913
914 params.compute_pipeline_registry.dispatch_erased(
915 params.gpu,
916 params.queue,
917 params.config,
918 &mut cpass,
919 &batch_items,
920 params.resource_manager,
921 &read_view,
922 write_target,
923 );
924 }
925
926 read_view = write_target.clone();
927 std::mem::swap(&mut write_target, &mut read_target);
928 index = cursor;
929 }
930
931 read_view
934 }
935
936 fn blit_to_view(
937 encoder: &mut wgpu::CommandEncoder,
938 device: &wgpu::Device,
939 source: &wgpu::TextureView,
940 target: &wgpu::TextureView,
941 bind_group_layout: &wgpu::BindGroupLayout,
942 sampler: &wgpu::Sampler,
943 pipeline: &wgpu::RenderPipeline,
944 ) {
945 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
946 layout: bind_group_layout,
947 entries: &[
948 wgpu::BindGroupEntry {
949 binding: 0,
950 resource: wgpu::BindingResource::TextureView(source),
951 },
952 wgpu::BindGroupEntry {
953 binding: 1,
954 resource: wgpu::BindingResource::Sampler(sampler),
955 },
956 ],
957 label: Some("Compute Copy Bind Group"),
958 });
959
960 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
961 label: Some("Compute Copy Pass"),
962 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
963 view: target,
964 resolve_target: None,
965 depth_slice: None,
966 ops: wgpu::Operations {
967 load: wgpu::LoadOp::Load,
968 store: wgpu::StoreOp::Store,
969 },
970 })],
971 depth_stencil_attachment: None,
972 ..Default::default()
973 });
974
975 rpass.set_pipeline(pipeline);
976 rpass.set_bind_group(0, &bind_group, &[]);
977 rpass.draw(0..3, 0..1);
978 }
979}
980
981fn rects_overlap(a: PxRect, b: PxRect) -> bool {
982 let a_left = a.x.0;
983 let a_top = a.y.0;
984 let a_right = a_left + a.width.0;
985 let a_bottom = a_top + a.height.0;
986
987 let b_left = b.x.0;
988 let b_top = b.y.0;
989 let b_right = b_left + b.width.0;
990 let b_bottom = b_top + b.height.0;
991
992 !(a_right <= b_left || b_right <= a_left || a_bottom <= b_top || b_bottom <= a_top)
993}
994
995fn compute_padded_rect(
996 size: PxSize,
997 start_pos: PxPosition,
998 top: Px,
999 right: Px,
1000 bottom: Px,
1001 left: Px,
1002 texture_size: wgpu::Extent3d,
1003) -> PxRect {
1004 let padded_x = (start_pos.x - left).max(Px(0));
1005 let padded_y = (start_pos.y - top).max(Px(0));
1006 let padded_width = (size.width + left + right).min(Px(texture_size.width as i32 - padded_x.0));
1007 let padded_height =
1008 (size.height + top + bottom).min(Px(texture_size.height as i32 - padded_y.0));
1009 PxRect {
1010 x: padded_x,
1011 y: padded_y,
1012 width: padded_width,
1013 height: padded_height,
1014 }
1015}
1016
1017fn clamp_rect_to_texture(mut rect: PxRect, texture_size: wgpu::Extent3d) -> PxRect {
1018 rect.x = rect.x.positive().min(texture_size.width).into();
1019 rect.y = rect.y.positive().min(texture_size.height).into();
1020 rect.width = rect
1021 .width
1022 .positive()
1023 .min(texture_size.width - rect.x.positive())
1024 .into();
1025 rect.height = rect
1026 .height
1027 .positive()
1028 .min(texture_size.height - rect.y.positive())
1029 .into();
1030 rect
1031}
1032
1033fn extract_draw_rect(
1034 barrier: Option<BarrierRequirement>,
1035 size: PxSize,
1036 start_pos: PxPosition,
1037 texture_size: wgpu::Extent3d,
1038) -> PxRect {
1039 match barrier {
1040 Some(BarrierRequirement::Global) => PxRect {
1041 x: Px(0),
1042 y: Px(0),
1043 width: Px(texture_size.width as i32),
1044 height: Px(texture_size.height as i32),
1045 },
1046 Some(BarrierRequirement::PaddedLocal {
1047 top,
1048 right,
1049 bottom,
1050 left,
1051 }) => compute_padded_rect(size, start_pos, top, right, bottom, left, texture_size),
1052 Some(BarrierRequirement::Absolute(rect)) => clamp_rect_to_texture(rect, texture_size),
1053 None => {
1054 let x = start_pos.x.positive().min(texture_size.width);
1055 let y = start_pos.y.positive().min(texture_size.height);
1056 let width = size.width.positive().min(texture_size.width - x);
1057 let height = size.height.positive().min(texture_size.height - y);
1058 PxRect {
1059 x: Px::from(x),
1060 y: Px::from(y),
1061 width: Px::from(width),
1062 height: Px::from(height),
1063 }
1064 }
1065 }
1066}
1067
1068fn render_current_pass(params: RenderCurrentPassParams<'_>) {
1069 let (view, resolve_target) = if let Some(msaa_view) = params.msaa_view {
1070 (msaa_view, Some(params.write_target))
1071 } else {
1072 (params.write_target, None)
1073 };
1074
1075 let load_ops = if *params.is_first_pass {
1076 *params.is_first_pass = false;
1077 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
1078 } else {
1079 wgpu::LoadOp::Load
1080 };
1081
1082 let mut rpass = params
1083 .encoder
1084 .begin_render_pass(&wgpu::RenderPassDescriptor {
1085 label: Some("Render Pass"),
1086 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1087 view,
1088 depth_slice: None,
1089 resolve_target,
1090 ops: wgpu::Operations {
1091 load: load_ops,
1092 store: wgpu::StoreOp::Store,
1093 },
1094 })],
1095 ..Default::default()
1096 });
1097
1098 params.drawer.begin_pass(
1099 params.gpu,
1100 params.queue,
1101 params.config,
1102 &mut rpass,
1103 params.scene_texture_view,
1104 );
1105
1106 let mut buffer: Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)> = Vec::new();
1108 let mut last_command_type_id = None;
1109 let mut current_batch_draw_rect: Option<PxRect> = None;
1110 for cmd in mem::take(params.commands_in_pass).into_iter() {
1111 let cmd = match cmd {
1112 DrawOrClip::Clip(clip_ops) => {
1113 if !buffer.is_empty() {
1115 submit_buffered_commands(
1116 &mut rpass,
1117 params.drawer,
1118 params.gpu,
1119 params.queue,
1120 params.config,
1121 &mut buffer,
1122 params.scene_texture_view,
1123 params.clip_stack,
1124 &mut current_batch_draw_rect,
1125 );
1126 last_command_type_id = None; }
1128 match clip_ops {
1130 ClipOps::Push(rect) => {
1131 params.clip_stack.push(rect);
1132 }
1133 ClipOps::Pop => {
1134 params.clip_stack.pop();
1135 }
1136 }
1137 continue;
1139 }
1140 DrawOrClip::Draw(cmd) => cmd, };
1142
1143 if !can_merge_into_batch(&last_command_type_id, cmd.type_id) && !buffer.is_empty() {
1145 submit_buffered_commands(
1146 &mut rpass,
1147 params.drawer,
1148 params.gpu,
1149 params.queue,
1150 params.config,
1151 &mut buffer,
1152 params.scene_texture_view,
1153 params.clip_stack,
1154 &mut current_batch_draw_rect,
1155 );
1156 }
1157
1158 buffer.push((cmd.command, cmd.size, cmd.start_pos));
1160 last_command_type_id = Some(cmd.type_id);
1161 current_batch_draw_rect = Some(merge_batch_rect(current_batch_draw_rect, cmd.draw_rect));
1162 }
1163
1164 if !buffer.is_empty() {
1166 submit_buffered_commands(
1167 &mut rpass,
1168 params.drawer,
1169 params.gpu,
1170 params.queue,
1171 params.config,
1172 &mut buffer,
1173 params.scene_texture_view,
1174 params.clip_stack,
1175 &mut current_batch_draw_rect,
1176 );
1177 }
1178
1179 params.drawer.end_pass(
1180 params.gpu,
1181 params.queue,
1182 params.config,
1183 &mut rpass,
1184 params.scene_texture_view,
1185 );
1186}
1187
1188fn submit_buffered_commands(
1189 rpass: &mut wgpu::RenderPass<'_>,
1190 drawer: &mut Drawer,
1191 gpu: &wgpu::Device,
1192 queue: &wgpu::Queue,
1193 config: &wgpu::SurfaceConfiguration,
1194 buffer: &mut Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)>,
1195 scene_texture_view: &wgpu::TextureView,
1196 clip_stack: &mut [PxRect],
1197 current_batch_draw_rect: &mut Option<PxRect>,
1198) {
1199 let commands = mem::take(buffer);
1201 let commands = commands
1202 .iter()
1203 .map(|(cmd, sz, pos)| (&**cmd, *sz, *pos))
1204 .collect::<Vec<_>>();
1205
1206 let (current_clip_rect, anything_to_submit) =
1208 apply_clip_to_batch_rect(clip_stack, current_batch_draw_rect);
1209 if !anything_to_submit {
1210 return;
1211 }
1212
1213 let rect = current_batch_draw_rect.unwrap();
1214 set_scissor_rect_from_pxrect(rpass, rect);
1215
1216 drawer.submit(
1217 gpu,
1218 queue,
1219 config,
1220 rpass,
1221 &commands,
1222 scene_texture_view,
1223 current_clip_rect,
1224 );
1225 *current_batch_draw_rect = None;
1226}
1227
1228fn set_scissor_rect_from_pxrect(rpass: &mut wgpu::RenderPass<'_>, rect: PxRect) {
1229 rpass.set_scissor_rect(
1230 rect.x.positive(),
1231 rect.y.positive(),
1232 rect.width.positive(),
1233 rect.height.positive(),
1234 );
1235}
1236
1237fn apply_clip_to_batch_rect(
1242 clip_stack: &[PxRect],
1243 current_batch_draw_rect: &mut Option<PxRect>,
1244) -> (Option<PxRect>, bool) {
1245 if let Some(clipped_rect) = clip_stack.last() {
1246 let Some(current_rect) = current_batch_draw_rect.as_ref() else {
1247 return (Some(*clipped_rect), false);
1248 };
1249 if let Some(final_rect) = current_rect.intersection(clipped_rect) {
1250 *current_batch_draw_rect = Some(final_rect);
1251 return (Some(*clipped_rect), true);
1252 }
1253 return (Some(*clipped_rect), false);
1254 }
1255 (None, true)
1256}
1257
1258fn can_merge_into_batch(last_command_type_id: &Option<TypeId>, next_type_id: TypeId) -> bool {
1262 match last_command_type_id {
1263 Some(l) => *l == next_type_id,
1264 None => true,
1265 }
1266}
1267
1268fn merge_batch_rect(current: Option<PxRect>, next: PxRect) -> PxRect {
1270 current.map(|dr| dr.union(&next)).unwrap_or(next)
1271}
1272
1273struct DrawCommandWithMetadata {
1274 command: Box<dyn DrawCommand>,
1275 type_id: TypeId,
1276 size: PxSize,
1277 start_pos: PxPosition,
1278 draw_rect: PxRect,
1279}
1280
1281enum DrawOrClip {
1282 Draw(DrawCommandWithMetadata),
1283 Clip(ClipOps),
1284}
1285
1286enum ClipOps {
1287 Push(PxRect),
1288 Pop,
1289}