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