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 ComputablePipeline, ComputeCommand, DrawCommand, DrawablePipeline, 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 WgpuContext<'a> {
20 encoder: &'a mut wgpu::CommandEncoder,
21 gpu: &'a wgpu::Device,
22 queue: &'a wgpu::Queue,
23 config: &'a wgpu::SurfaceConfiguration,
24}
25
26struct RenderCurrentPassParams<'a> {
28 msaa_view: &'a Option<wgpu::TextureView>,
29 is_first_pass: &'a mut bool,
30 encoder: &'a mut wgpu::CommandEncoder,
31 write_target: &'a wgpu::TextureView,
32 commands_in_pass: &'a mut Vec<DrawOrClip>,
33 scene_texture_view: &'a wgpu::TextureView,
34 drawer: &'a mut Drawer,
35 gpu: &'a wgpu::Device,
36 queue: &'a wgpu::Queue,
37 config: &'a wgpu::SurfaceConfiguration,
38 clip_stack: &'a mut Vec<PxRect>,
39}
40
41struct DoComputeParams<'a> {
43 encoder: &'a mut wgpu::CommandEncoder,
44 commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
45 compute_pipeline_registry: &'a mut ComputePipelineRegistry,
46 gpu: &'a wgpu::Device,
47 queue: &'a wgpu::Queue,
48 config: &'a wgpu::SurfaceConfiguration,
49 resource_manager: &'a mut ComputeResourceManager,
50 scene_view: &'a wgpu::TextureView,
51 target_a: &'a wgpu::TextureView,
52 target_b: &'a wgpu::TextureView,
53}
54
55struct ComputeResources<'a> {
57 compute_commands: &'a mut Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
58 compute_pipeline_registry: &'a mut ComputePipelineRegistry,
59 resource_manager: &'a mut ComputeResourceManager,
60 compute_target_a: &'a wgpu::TextureView,
61 compute_target_b: &'a wgpu::TextureView,
62}
63
64pub struct WgpuApp {
65 #[allow(unused)]
67 pub window: Arc<Window>,
68 pub gpu: wgpu::Device,
70 surface: wgpu::Surface<'static>,
72 pub queue: wgpu::Queue,
74 pub config: wgpu::SurfaceConfiguration,
76 size: winit::dpi::PhysicalSize<u32>,
78 size_changed: bool,
80 pub drawer: Drawer,
82 pub compute_pipeline_registry: ComputePipelineRegistry,
84
85 offscreen_texture: wgpu::TextureView,
87
88 pub sample_count: u32,
90 msaa_texture: Option<wgpu::Texture>,
91 msaa_view: Option<wgpu::TextureView>,
92
93 compute_target_a: wgpu::TextureView,
95 compute_target_b: wgpu::TextureView,
96 compute_commands: Vec<(Box<dyn ComputeCommand>, PxSize, PxPosition)>,
97 pub resource_manager: Arc<RwLock<ComputeResourceManager>>,
98
99 blit_pipeline: wgpu::RenderPipeline,
101 blit_bind_group_layout: wgpu::BindGroupLayout,
102 blit_sampler: wgpu::Sampler,
103}
104
105impl WgpuApp {
106 async fn request_adapter_for_surface(
110 instance: &wgpu::Instance,
111 surface: &wgpu::Surface<'_>,
112 ) -> wgpu::Adapter {
113 match instance
114 .request_adapter(&wgpu::RequestAdapterOptions {
115 power_preference: wgpu::PowerPreference::default(),
116 compatible_surface: Some(surface),
117 force_fallback_adapter: false,
118 })
119 .await
120 {
121 Ok(gpu) => gpu,
122 Err(e) => {
123 error!("Failed to find an appropriate adapter: {e:?}");
124 panic!("Failed to find an appropriate adapter: {e:?}");
125 }
126 }
127 }
128
129 async fn request_device_and_queue_for_adapter(
130 adapter: &wgpu::Adapter,
131 ) -> (wgpu::Device, wgpu::Queue) {
132 match adapter
133 .request_device(&wgpu::DeviceDescriptor {
134 required_features: wgpu::Features::empty() | wgpu::Features::CLEAR_TEXTURE,
135 required_limits: if cfg!(target_arch = "wasm32") {
136 wgpu::Limits::downlevel_webgl2_defaults()
137 } else {
138 wgpu::Limits::default()
139 },
140 label: None,
141 memory_hints: wgpu::MemoryHints::Performance,
142 trace: wgpu::Trace::Off,
143 experimental_features: wgpu::ExperimentalFeatures::default(),
144 })
145 .await
146 {
147 Ok((gpu, queue)) => (gpu, queue),
148 Err(e) => {
149 error!("Failed to create device: {e:?}");
150 panic!("Failed to create device: {e:?}");
151 }
152 }
153 }
154
155 fn make_msaa_resources(
156 gpu: &wgpu::Device,
157 sample_count: u32,
158 config: &wgpu::SurfaceConfiguration,
159 ) -> (Option<wgpu::Texture>, Option<wgpu::TextureView>) {
160 if sample_count > 1 {
161 let texture = gpu.create_texture(&wgpu::TextureDescriptor {
162 label: Some("MSAA Framebuffer"),
163 size: wgpu::Extent3d {
164 width: config.width,
165 height: config.height,
166 depth_or_array_layers: 1,
167 },
168 mip_level_count: 1,
169 sample_count,
170 dimension: wgpu::TextureDimension::D2,
171 format: config.format,
172 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
173 view_formats: &[],
174 });
175 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
176 (Some(texture), Some(view))
177 } else {
178 (None, None)
179 }
180 }
181
182 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::TEXTURE_BINDING,
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 offscreen_texture = Self::create_pass_target(&gpu, &config, "Offscreen");
230 let compute_target_a =
231 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute A");
232 let compute_target_b =
233 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute B");
234
235 let drawer = Drawer::new();
236
237 let scale_factor = window.scale_factor();
239 info!("Window scale factor: {scale_factor}");
240 SCALE_FACTOR
241 .set(RwLock::new(scale_factor))
242 .expect("Failed to set scale factor");
243
244 let blit_shader = gpu.create_shader_module(wgpu::include_wgsl!("shaders/blit.wgsl"));
246 let blit_sampler = gpu.create_sampler(&wgpu::SamplerDescriptor::default());
247 let blit_bind_group_layout =
248 gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
249 label: Some("Blit Bind Group Layout"),
250 entries: &[
251 wgpu::BindGroupLayoutEntry {
252 binding: 0,
253 visibility: wgpu::ShaderStages::FRAGMENT,
254 ty: wgpu::BindingType::Texture {
255 sample_type: wgpu::TextureSampleType::Float { filterable: true },
256 view_dimension: wgpu::TextureViewDimension::D2,
257 multisampled: false,
258 },
259 count: None,
260 },
261 wgpu::BindGroupLayoutEntry {
262 binding: 1,
263 visibility: wgpu::ShaderStages::FRAGMENT,
264 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
265 count: None,
266 },
267 ],
268 });
269
270 let blit_pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
271 label: Some("Blit Pipeline Layout"),
272 bind_group_layouts: &[&blit_bind_group_layout],
273 push_constant_ranges: &[],
274 });
275
276 let blit_pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
277 label: Some("Blit Pipeline"),
278 layout: Some(&blit_pipeline_layout),
279 vertex: wgpu::VertexState {
280 module: &blit_shader,
281 entry_point: Some("vs_main"),
282 buffers: &[],
283 compilation_options: Default::default(),
284 },
285 fragment: Some(wgpu::FragmentState {
286 module: &blit_shader,
287 entry_point: Some("fs_main"),
288 targets: &[Some(config.format.into())],
289 compilation_options: Default::default(),
290 }),
291 primitive: wgpu::PrimitiveState::default(),
292 depth_stencil: None,
293 multisample: wgpu::MultisampleState::default(),
294 multiview: None,
295 cache: None,
296 });
297
298 Self {
299 window,
300 gpu,
301 surface,
302 queue,
303 config,
304 size,
305 size_changed: false,
306 drawer,
307 offscreen_texture,
308 compute_pipeline_registry: ComputePipelineRegistry::new(),
309 sample_count,
310 msaa_texture,
311 msaa_view,
312 compute_target_a,
313 compute_target_b,
314 compute_commands: Vec::new(),
315 resource_manager: Arc::new(RwLock::new(ComputeResourceManager::new())),
316 blit_pipeline,
317 blit_bind_group_layout,
318 blit_sampler,
319 }
320 }
321
322 pub fn register_draw_pipeline<T, P>(&mut self, pipeline: P)
326 where
327 T: DrawCommand + 'static,
328 P: DrawablePipeline<T> + 'static,
329 {
330 self.drawer.pipeline_registry.register(pipeline);
331 }
332
333 pub fn register_compute_pipeline<T, P>(&mut self, pipeline: P)
337 where
338 T: ComputeCommand + 'static,
339 P: ComputablePipeline<T> + 'static,
340 {
341 self.compute_pipeline_registry.register(pipeline);
342 }
343
344 fn create_pass_target(
345 gpu: &wgpu::Device,
346 config: &wgpu::SurfaceConfiguration,
347 label_suffix: &str,
348 ) -> wgpu::TextureView {
349 let label = format!("Pass {label_suffix} Texture");
350 let texture_descriptor = wgpu::TextureDescriptor {
351 label: Some(&label),
352 size: wgpu::Extent3d {
353 width: config.width,
354 height: config.height,
355 depth_or_array_layers: 1,
356 },
357 mip_level_count: 1,
358 sample_count: 1,
359 dimension: wgpu::TextureDimension::D2,
360 format: config.format,
362 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
363 | wgpu::TextureUsages::TEXTURE_BINDING
364 | wgpu::TextureUsages::COPY_DST
365 | wgpu::TextureUsages::COPY_SRC,
366 view_formats: &[],
367 };
368 let texture = gpu.create_texture(&texture_descriptor);
369 texture.create_view(&wgpu::TextureViewDescriptor::default())
370 }
371
372 fn create_compute_pass_target(
373 gpu: &wgpu::Device,
374 config: &wgpu::SurfaceConfiguration,
375 format: TextureFormat,
376 label_suffix: &str,
377 ) -> wgpu::TextureView {
378 let label = format!("Compute {label_suffix} Texture");
379 let texture_descriptor = wgpu::TextureDescriptor {
380 label: Some(&label),
381 size: wgpu::Extent3d {
382 width: config.width,
383 height: config.height,
384 depth_or_array_layers: 1,
385 },
386 mip_level_count: 1,
387 sample_count: 1,
388 dimension: wgpu::TextureDimension::D2,
389 format,
390 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
391 | wgpu::TextureUsages::TEXTURE_BINDING
392 | wgpu::TextureUsages::STORAGE_BINDING
393 | wgpu::TextureUsages::COPY_DST
394 | wgpu::TextureUsages::COPY_SRC,
395 view_formats: &[],
396 };
397 let texture = gpu.create_texture(&texture_descriptor);
398 texture.create_view(&wgpu::TextureViewDescriptor::default())
399 }
400
401 pub fn register_pipelines(&mut self, register_fn: impl FnOnce(&mut Self)) {
402 register_fn(self);
403 }
404
405 pub(crate) fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
408 if self.size == size {
409 return;
410 }
411 self.size = size;
412 self.size_changed = true;
413 }
414
415 pub(crate) fn size(&self) -> winit::dpi::PhysicalSize<u32> {
417 self.size
418 }
419
420 pub(crate) fn resize_surface(&mut self) {
421 if self.size.width > 0 && self.size.height > 0 {
422 self.config.width = self.size.width;
423 self.config.height = self.size.height;
424 self.surface.configure(&self.gpu, &self.config);
425 self.rebuild_pass_targets();
426 }
427 }
428
429 pub(crate) fn rebuild_pass_targets(&mut self) {
430 self.offscreen_texture.texture().destroy();
431 self.compute_target_a.texture().destroy();
432 self.compute_target_b.texture().destroy();
433
434 self.offscreen_texture = Self::create_pass_target(&self.gpu, &self.config, "Offscreen");
435 self.compute_target_a = Self::create_compute_pass_target(
436 &self.gpu,
437 &self.config,
438 TextureFormat::Rgba8Unorm,
439 "Compute A",
440 );
441 self.compute_target_b = Self::create_compute_pass_target(
442 &self.gpu,
443 &self.config,
444 TextureFormat::Rgba8Unorm,
445 "Compute B",
446 );
447
448 if self.sample_count > 1 {
449 if let Some(t) = self.msaa_texture.take() {
450 t.destroy();
451 }
452 let (msaa_texture, msaa_view) =
453 Self::make_msaa_resources(&self.gpu, self.sample_count, &self.config);
454 self.msaa_texture = msaa_texture;
455 self.msaa_view = msaa_view;
456 }
457 }
458
459 pub(crate) fn resize_if_needed(&mut self) -> bool {
461 let result = self.size_changed;
462 if self.size_changed {
463 self.resize_surface();
464 self.size_changed = false;
465 }
466 result
467 }
468
469 fn handle_offscreen_and_compute(
472 context: WgpuContext<'_>,
473 offscreen_texture: &mut wgpu::TextureView,
474 output_texture: &mut wgpu::TextureView,
475 compute_resources: ComputeResources<'_>,
476 copy_rect: PxRect,
477 blit_bind_group_layout: &wgpu::BindGroupLayout,
478 blit_sampler: &wgpu::Sampler,
479 blit_pipeline: &wgpu::RenderPipeline,
480 ) -> wgpu::TextureView {
481 let blit_bind_group = context.gpu.create_bind_group(&wgpu::BindGroupDescriptor {
482 layout: blit_bind_group_layout,
483 entries: &[
484 wgpu::BindGroupEntry {
485 binding: 0,
486 resource: wgpu::BindingResource::TextureView(output_texture),
487 },
488 wgpu::BindGroupEntry {
489 binding: 1,
490 resource: wgpu::BindingResource::Sampler(blit_sampler),
491 },
492 ],
493 label: Some("Blit Bind Group"),
494 });
495
496 let mut rpass = context
497 .encoder
498 .begin_render_pass(&wgpu::RenderPassDescriptor {
499 label: Some("Blit Pass"),
500 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
501 view: offscreen_texture,
502 resolve_target: None,
503 ops: wgpu::Operations {
504 load: wgpu::LoadOp::Load,
505 store: wgpu::StoreOp::Store,
506 },
507 depth_slice: None,
508 })],
509 depth_stencil_attachment: None,
510 ..Default::default()
511 });
512
513 rpass.set_pipeline(blit_pipeline);
514 rpass.set_bind_group(0, &blit_bind_group, &[]);
515 rpass.set_scissor_rect(
517 copy_rect.x.0.max(0) as u32,
518 copy_rect.y.0.max(0) as u32,
519 copy_rect.width.0.max(0) as u32,
520 copy_rect.height.0.max(0) as u32,
521 );
522 rpass.draw(0..3, 0..1);
524
525 drop(rpass); if !compute_resources.compute_commands.is_empty() {
529 let compute_commands_taken = std::mem::take(compute_resources.compute_commands);
530 Self::do_compute(DoComputeParams {
531 encoder: context.encoder,
532 commands: compute_commands_taken,
533 compute_pipeline_registry: compute_resources.compute_pipeline_registry,
534 gpu: context.gpu,
535 queue: context.queue,
536 config: context.config,
537 resource_manager: compute_resources.resource_manager,
538 scene_view: offscreen_texture,
539 target_a: compute_resources.compute_target_a,
540 target_b: compute_resources.compute_target_b,
541 })
542 } else {
543 offscreen_texture.clone()
545 }
546 }
547
548 pub(crate) fn render(
563 &mut self,
564 commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
565 ) -> Result<(), wgpu::SurfaceError> {
566 let commands: Vec<_> = commands.into_iter().collect();
568 let commands = super::reorder::reorder_instructions(commands);
570
571 let output_frame = self.surface.get_current_texture()?;
572 let mut encoder = self
573 .gpu
574 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
575 label: Some("Render Encoder"),
576 });
577
578 let texture_size = wgpu::Extent3d {
579 width: self.config.width,
580 height: self.config.height,
581 depth_or_array_layers: 1,
582 };
583
584 if !self.compute_commands.is_empty() {
586 warn!("Not every compute command is used in last frame. This is likely a bug.");
588 self.compute_commands.clear();
589 }
590
591 let mut is_first_pass = true;
593
594 self.drawer
596 .pipeline_registry
597 .begin_all_frames(&self.gpu, &self.queue, &self.config);
598
599 let mut scene_texture_view = self.offscreen_texture.clone();
600 let mut commands_in_pass: Vec<DrawOrClip> = Vec::new();
601 let mut barrier_draw_rects_in_pass: Vec<PxRect> = Vec::new();
602 let mut clip_stack: Vec<PxRect> = Vec::new();
603
604 let mut output_view = output_frame
605 .texture
606 .create_view(&wgpu::TextureViewDescriptor::default());
607
608 for (command, command_type_id, size, start_pos) in commands {
609 let need_new_pass = commands_in_pass
610 .iter()
611 .rev()
612 .find_map(|command| match &command {
613 DrawOrClip::Draw(cmd) => Some(cmd),
614 DrawOrClip::Clip(_) => None,
615 })
616 .map(|cmd| match (cmd.command.barrier(), command.barrier()) {
617 (None, Some(_)) => true,
618 (Some(_), Some(barrier)) => {
619 let last_draw_rect =
620 extract_draw_rect(Some(barrier), size, start_pos, texture_size);
621 !barrier_draw_rects_in_pass
622 .iter()
623 .all(|dr| dr.is_orthogonal(&last_draw_rect))
624 }
625 (Some(_), None) => false,
626 (None, None) => false,
627 })
628 .unwrap_or(false);
629
630 if need_new_pass {
631 if commands_in_pass
633 .iter()
634 .find_map(|command| match &command {
635 DrawOrClip::Draw(cmd) => Some(cmd),
636 DrawOrClip::Clip(_) => None,
637 })
638 .map(|cmd| cmd.command.barrier().is_some())
639 .unwrap_or(false)
640 {
641 let mut combined_rect = barrier_draw_rects_in_pass[0];
642 for rect in barrier_draw_rects_in_pass.iter().skip(1) {
643 combined_rect = combined_rect.union(rect);
644 }
645
646 let final_view_after_compute = Self::handle_offscreen_and_compute(
647 WgpuContext {
648 encoder: &mut encoder,
649 gpu: &self.gpu,
650 queue: &self.queue,
651 config: &self.config,
652 },
653 &mut self.offscreen_texture,
654 &mut output_view,
655 ComputeResources {
656 compute_commands: &mut self.compute_commands,
657 compute_pipeline_registry: &mut self.compute_pipeline_registry,
658 resource_manager: &mut self.resource_manager.write(),
659 compute_target_a: &self.compute_target_a,
660 compute_target_b: &self.compute_target_b,
661 },
662 combined_rect,
663 &self.blit_bind_group_layout,
664 &self.blit_sampler,
665 &self.blit_pipeline,
666 );
667 scene_texture_view = final_view_after_compute;
668 }
669
670 render_current_pass(RenderCurrentPassParams {
671 msaa_view: &self.msaa_view,
672 is_first_pass: &mut is_first_pass,
673 encoder: &mut encoder,
674 write_target: &output_view,
675 commands_in_pass: &mut commands_in_pass,
676 scene_texture_view: &scene_texture_view,
677 drawer: &mut self.drawer,
678 gpu: &self.gpu,
679 queue: &self.queue,
680 config: &self.config,
681 clip_stack: &mut clip_stack,
682 });
683 commands_in_pass.clear();
684 barrier_draw_rects_in_pass.clear();
685 }
686
687 match command {
688 Command::Draw(cmd) => {
689 let draw_rect = extract_draw_rect(cmd.barrier(), size, start_pos, texture_size);
691 if cmd.barrier().is_some() {
693 barrier_draw_rects_in_pass.push(draw_rect);
694 }
695 commands_in_pass.push(DrawOrClip::Draw(DrawCommandWithMetadata {
697 command: cmd,
698 type_id: command_type_id,
699 size,
700 start_pos,
701 draw_rect,
702 }));
703 }
704 Command::Compute(cmd) => {
705 self.compute_commands.push((cmd, size, start_pos));
707 }
708 Command::ClipPush(rect) => {
709 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Push(rect)));
711 }
712 Command::ClipPop => {
713 commands_in_pass.push(DrawOrClip::Clip(ClipOps::Pop));
715 }
716 }
717 }
718
719 if !commands_in_pass.is_empty() {
721 if commands_in_pass
723 .iter()
724 .find_map(|command| match &command {
725 DrawOrClip::Draw(cmd) => Some(cmd),
726 DrawOrClip::Clip(_) => None,
727 })
728 .map(|cmd| cmd.command.barrier().is_some())
729 .unwrap_or(false)
730 {
731 let mut combined_rect = barrier_draw_rects_in_pass[0];
732 for rect in barrier_draw_rects_in_pass.iter().skip(1) {
733 combined_rect = combined_rect.union(rect);
734 }
735
736 let final_view_after_compute = Self::handle_offscreen_and_compute(
737 WgpuContext {
738 encoder: &mut encoder,
739 gpu: &self.gpu,
740 queue: &self.queue,
741 config: &self.config,
742 },
743 &mut self.offscreen_texture,
744 &mut output_view,
745 ComputeResources {
746 compute_commands: &mut self.compute_commands,
747 compute_pipeline_registry: &mut self.compute_pipeline_registry,
748 resource_manager: &mut self.resource_manager.write(),
749 compute_target_a: &self.compute_target_a,
750 compute_target_b: &self.compute_target_b,
751 },
752 combined_rect,
753 &self.blit_bind_group_layout,
754 &self.blit_sampler,
755 &self.blit_pipeline,
756 );
757 scene_texture_view = final_view_after_compute;
758 }
759
760 render_current_pass(RenderCurrentPassParams {
762 msaa_view: &self.msaa_view,
763 is_first_pass: &mut is_first_pass,
764 encoder: &mut encoder,
765 write_target: &output_view,
766 commands_in_pass: &mut commands_in_pass,
767 scene_texture_view: &scene_texture_view,
768 drawer: &mut self.drawer,
769 gpu: &self.gpu,
770 queue: &self.queue,
771 config: &self.config,
772 clip_stack: &mut clip_stack,
773 });
774 commands_in_pass.clear();
775 barrier_draw_rects_in_pass.clear();
776 }
777
778 self.drawer
780 .pipeline_registry
781 .end_all_frames(&self.gpu, &self.queue, &self.config);
782
783 self.queue.submit(Some(encoder.finish()));
784 output_frame.present();
785
786 Ok(())
787 }
788
789 fn do_compute(params: DoComputeParams<'_>) -> wgpu::TextureView {
790 if params.commands.is_empty() {
791 return params.scene_view.clone();
792 }
793
794 let mut read_view = params.scene_view.clone();
795 let (mut write_target, mut read_target) = (params.target_a, params.target_b);
796
797 for (command, size, start_pos) in params.commands {
798 params.encoder.clear_texture(
800 write_target.texture(),
801 &ImageSubresourceRange {
802 aspect: wgpu::TextureAspect::All,
803 base_mip_level: 0,
804 mip_level_count: None,
805 base_array_layer: 0,
806 array_layer_count: None,
807 },
808 );
809
810 {
812 let mut cpass = params
813 .encoder
814 .begin_compute_pass(&wgpu::ComputePassDescriptor {
815 label: Some("Compute Pass"),
816 timestamp_writes: None,
817 });
818
819 let texture_size = wgpu::Extent3d {
821 width: params.config.width,
822 height: params.config.height,
823 depth_or_array_layers: 1,
824 };
825 let area =
826 extract_draw_rect(Some(command.barrier()), size, start_pos, texture_size);
827
828 params.compute_pipeline_registry.dispatch_erased(
829 params.gpu,
830 params.queue,
831 params.config,
832 &mut cpass,
833 &*command,
834 params.resource_manager,
835 area,
836 &read_view,
837 write_target,
838 );
839 } read_view = write_target.clone();
844 std::mem::swap(&mut write_target, &mut read_target);
846 }
847
848 read_view
851 }
852}
853
854fn compute_padded_rect(
855 size: PxSize,
856 start_pos: PxPosition,
857 top: Px,
858 right: Px,
859 bottom: Px,
860 left: Px,
861 texture_size: wgpu::Extent3d,
862) -> PxRect {
863 let padded_x = (start_pos.x - left).max(Px(0));
864 let padded_y = (start_pos.y - top).max(Px(0));
865 let padded_width = (size.width + left + right).min(Px(texture_size.width as i32 - padded_x.0));
866 let padded_height =
867 (size.height + top + bottom).min(Px(texture_size.height as i32 - padded_y.0));
868 PxRect {
869 x: padded_x,
870 y: padded_y,
871 width: padded_width,
872 height: padded_height,
873 }
874}
875
876fn clamp_rect_to_texture(mut rect: PxRect, texture_size: wgpu::Extent3d) -> PxRect {
877 rect.x = rect.x.positive().min(texture_size.width).into();
878 rect.y = rect.y.positive().min(texture_size.height).into();
879 rect.width = rect
880 .width
881 .positive()
882 .min(texture_size.width - rect.x.positive())
883 .into();
884 rect.height = rect
885 .height
886 .positive()
887 .min(texture_size.height - rect.y.positive())
888 .into();
889 rect
890}
891
892fn extract_draw_rect(
893 barrier: Option<BarrierRequirement>,
894 size: PxSize,
895 start_pos: PxPosition,
896 texture_size: wgpu::Extent3d,
897) -> PxRect {
898 match barrier {
899 Some(BarrierRequirement::Global) => PxRect {
900 x: Px(0),
901 y: Px(0),
902 width: Px(texture_size.width as i32),
903 height: Px(texture_size.height as i32),
904 },
905 Some(BarrierRequirement::PaddedLocal {
906 top,
907 right,
908 bottom,
909 left,
910 }) => compute_padded_rect(size, start_pos, top, right, bottom, left, texture_size),
911 Some(BarrierRequirement::Absolute(rect)) => clamp_rect_to_texture(rect, texture_size),
912 None => {
913 let x = start_pos.x.positive().min(texture_size.width);
914 let y = start_pos.y.positive().min(texture_size.height);
915 let width = size.width.positive().min(texture_size.width - x);
916 let height = size.height.positive().min(texture_size.height - y);
917 PxRect {
918 x: Px::from(x),
919 y: Px::from(y),
920 width: Px::from(width),
921 height: Px::from(height),
922 }
923 }
924 }
925}
926
927fn render_current_pass(params: RenderCurrentPassParams<'_>) {
928 let (view, resolve_target) = if let Some(msaa_view) = params.msaa_view {
929 (msaa_view, Some(params.write_target))
930 } else {
931 (params.write_target, None)
932 };
933
934 let load_ops = if *params.is_first_pass {
935 *params.is_first_pass = false;
936 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
937 } else {
938 wgpu::LoadOp::Load
939 };
940
941 let mut rpass = params
942 .encoder
943 .begin_render_pass(&wgpu::RenderPassDescriptor {
944 label: Some("Render Pass"),
945 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
946 view,
947 depth_slice: None,
948 resolve_target,
949 ops: wgpu::Operations {
950 load: load_ops,
951 store: wgpu::StoreOp::Store,
952 },
953 })],
954 ..Default::default()
955 });
956
957 params.drawer.begin_pass(
958 params.gpu,
959 params.queue,
960 params.config,
961 &mut rpass,
962 params.scene_texture_view,
963 );
964
965 let mut buffer: Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)> = Vec::new();
967 let mut last_command_type_id = None;
968 let mut current_batch_draw_rect: Option<PxRect> = None;
969 for cmd in mem::take(params.commands_in_pass).into_iter() {
970 let cmd = match cmd {
971 DrawOrClip::Clip(clip_ops) => {
972 if !buffer.is_empty() {
974 submit_buffered_commands(
975 &mut rpass,
976 params.drawer,
977 params.gpu,
978 params.queue,
979 params.config,
980 &mut buffer,
981 params.scene_texture_view,
982 params.clip_stack,
983 &mut current_batch_draw_rect,
984 );
985 last_command_type_id = None; }
987 match clip_ops {
989 ClipOps::Push(rect) => {
990 params.clip_stack.push(rect);
991 }
992 ClipOps::Pop => {
993 params.clip_stack.pop();
994 }
995 }
996 continue;
998 }
999 DrawOrClip::Draw(cmd) => cmd, };
1001
1002 if !can_merge_into_batch(&last_command_type_id, cmd.type_id) && !buffer.is_empty() {
1004 submit_buffered_commands(
1005 &mut rpass,
1006 params.drawer,
1007 params.gpu,
1008 params.queue,
1009 params.config,
1010 &mut buffer,
1011 params.scene_texture_view,
1012 params.clip_stack,
1013 &mut current_batch_draw_rect,
1014 );
1015 }
1016
1017 buffer.push((cmd.command, cmd.size, cmd.start_pos));
1019 last_command_type_id = Some(cmd.type_id);
1020 current_batch_draw_rect = Some(merge_batch_rect(current_batch_draw_rect, cmd.draw_rect));
1021 }
1022
1023 if !buffer.is_empty() {
1025 submit_buffered_commands(
1026 &mut rpass,
1027 params.drawer,
1028 params.gpu,
1029 params.queue,
1030 params.config,
1031 &mut buffer,
1032 params.scene_texture_view,
1033 params.clip_stack,
1034 &mut current_batch_draw_rect,
1035 );
1036 }
1037
1038 params.drawer.end_pass(
1039 params.gpu,
1040 params.queue,
1041 params.config,
1042 &mut rpass,
1043 params.scene_texture_view,
1044 );
1045}
1046
1047fn submit_buffered_commands(
1048 rpass: &mut wgpu::RenderPass<'_>,
1049 drawer: &mut Drawer,
1050 gpu: &wgpu::Device,
1051 queue: &wgpu::Queue,
1052 config: &wgpu::SurfaceConfiguration,
1053 buffer: &mut Vec<(Box<dyn DrawCommand>, PxSize, PxPosition)>,
1054 scene_texture_view: &wgpu::TextureView,
1055 clip_stack: &mut [PxRect],
1056 current_batch_draw_rect: &mut Option<PxRect>,
1057) {
1058 let commands = mem::take(buffer);
1060 let commands = commands
1061 .iter()
1062 .map(|(cmd, sz, pos)| (&**cmd, *sz, *pos))
1063 .collect::<Vec<_>>();
1064
1065 let (current_clip_rect, anything_to_submit) =
1067 apply_clip_to_batch_rect(clip_stack, current_batch_draw_rect);
1068 if !anything_to_submit {
1069 return;
1070 }
1071
1072 let rect = current_batch_draw_rect.unwrap();
1073 set_scissor_rect_from_pxrect(rpass, rect);
1074
1075 drawer.submit(
1076 gpu,
1077 queue,
1078 config,
1079 rpass,
1080 &commands,
1081 scene_texture_view,
1082 current_clip_rect,
1083 );
1084 *current_batch_draw_rect = None;
1085}
1086
1087fn set_scissor_rect_from_pxrect(rpass: &mut wgpu::RenderPass<'_>, rect: PxRect) {
1088 rpass.set_scissor_rect(
1089 rect.x.positive(),
1090 rect.y.positive(),
1091 rect.width.positive(),
1092 rect.height.positive(),
1093 );
1094}
1095
1096fn apply_clip_to_batch_rect(
1101 clip_stack: &[PxRect],
1102 current_batch_draw_rect: &mut Option<PxRect>,
1103) -> (Option<PxRect>, bool) {
1104 if let Some(clipped_rect) = clip_stack.last() {
1105 let Some(current_rect) = current_batch_draw_rect.as_ref() else {
1106 return (Some(*clipped_rect), false);
1107 };
1108 if let Some(final_rect) = current_rect.intersection(clipped_rect) {
1109 *current_batch_draw_rect = Some(final_rect);
1110 return (Some(*clipped_rect), true);
1111 }
1112 return (Some(*clipped_rect), false);
1113 }
1114 (None, true)
1115}
1116
1117fn can_merge_into_batch(last_command_type_id: &Option<TypeId>, next_type_id: TypeId) -> bool {
1121 match last_command_type_id {
1122 Some(l) => *l == next_type_id,
1123 None => true,
1124 }
1125}
1126
1127fn merge_batch_rect(current: Option<PxRect>, next: PxRect) -> PxRect {
1129 current.map(|dr| dr.union(&next)).unwrap_or(next)
1130}
1131
1132struct DrawCommandWithMetadata {
1133 command: Box<dyn DrawCommand>,
1134 type_id: TypeId,
1135 size: PxSize,
1136 start_pos: PxPosition,
1137 draw_rect: PxRect,
1138}
1139
1140enum DrawOrClip {
1141 Draw(DrawCommandWithMetadata),
1142 Clip(ClipOps),
1143}
1144
1145enum ClipOps {
1146 Push(PxRect),
1147 Pop,
1148}