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