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