1use std::{mem, sync::Arc};
2
3use log::{error, info, warn};
4use parking_lot::RwLock;
5use wgpu::TextureFormat;
6use winit::window::Window;
7
8use crate::{
9 ComputeCommand, PxPosition, compute::resource::ComputeResourceManager, dp::SCALE_FACTOR,
10 px::PxSize, renderer::command::Command,
11};
12
13use super::{compute::ComputePipelineRegistry, drawer::Drawer};
14
15struct PassTarget {
17 texture: wgpu::Texture,
18 view: wgpu::TextureView,
19}
20
21pub struct WgpuApp {
22 #[allow(unused)]
24 pub window: Arc<Window>,
25 pub gpu: wgpu::Device,
27 surface: wgpu::Surface<'static>,
29 pub queue: wgpu::Queue,
31 pub config: wgpu::SurfaceConfiguration,
33 size: winit::dpi::PhysicalSize<u32>,
35 size_changed: bool,
37 pub drawer: Drawer,
39 pub compute_pipeline_registry: ComputePipelineRegistry,
41
42 pass_a: PassTarget,
44 pass_b: PassTarget,
45
46 pub sample_count: u32,
48 msaa_texture: Option<wgpu::Texture>,
49 msaa_view: Option<wgpu::TextureView>,
50
51 compute_target_a: PassTarget,
53 compute_target_b: PassTarget,
54 compute_commands: Vec<Box<dyn ComputeCommand>>,
55 pub resource_manager: Arc<RwLock<ComputeResourceManager>>,
56}
57
58impl WgpuApp {
59 pub(crate) async fn new(window: Arc<Window>, sample_count: u32) -> Self {
61 let instance: wgpu::Instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
63 backends: wgpu::Backends::all(),
64 ..Default::default()
65 });
66 let surface = match instance.create_surface(window.clone()) {
68 Ok(surface) => surface,
69 Err(e) => {
70 error!("Failed to create surface: {e:?}");
71 panic!("Failed to create surface: {e:?}");
72 }
73 };
74 let adapter = match instance
76 .request_adapter(&wgpu::RequestAdapterOptions {
77 power_preference: wgpu::PowerPreference::default(),
78 compatible_surface: Some(&surface),
79 force_fallback_adapter: false,
80 })
81 .await
82 {
83 Ok(gpu) => gpu,
84 Err(e) => {
85 error!("Failed to find an appropriate adapter: {e:?}");
86 panic!("Failed to find an appropriate adapter: {e:?}");
87 }
88 };
89 let (gpu, queue) = match adapter
91 .request_device(&wgpu::DeviceDescriptor {
92 required_features: wgpu::Features::empty(),
93 required_limits: if cfg!(target_arch = "wasm32") {
95 wgpu::Limits::downlevel_webgl2_defaults()
96 } else {
97 wgpu::Limits::default()
98 },
99 label: None,
100 memory_hints: wgpu::MemoryHints::Performance,
101 trace: wgpu::Trace::Off,
102 })
103 .await
104 {
105 Ok((gpu, queue)) => (gpu, queue),
106 Err(e) => {
107 error!("Failed to create device: {e:?}");
108 panic!("Failed to create device: {e:?}");
109 }
110 };
111 let size = window.inner_size();
113 let caps = surface.get_capabilities(&adapter);
114 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
116 wgpu::PresentMode::Fifo
118 } else {
119 wgpu::PresentMode::Immediate
121 };
122 info!("Using present mode: {present_mode:?}");
123 let config = wgpu::SurfaceConfiguration {
124 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
125 format: caps.formats[0],
126 width: size.width,
127 height: size.height,
128 present_mode,
129 alpha_mode: caps.alpha_modes[0],
130 view_formats: vec![],
131 desired_maximum_frame_latency: 2,
132 };
133 surface.configure(&gpu, &config);
134
135 let (msaa_texture, msaa_view) = if sample_count > 1 {
137 let texture = gpu.create_texture(&wgpu::TextureDescriptor {
138 label: Some("MSAA Framebuffer"),
139 size: wgpu::Extent3d {
140 width: config.width,
141 height: config.height,
142 depth_or_array_layers: 1,
143 },
144 mip_level_count: 1,
145 sample_count,
146 dimension: wgpu::TextureDimension::D2,
147 format: config.format,
149 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
150 view_formats: &[],
151 });
152 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
153 (Some(texture), Some(view))
154 } else {
155 (None, None)
156 };
157
158 let pass_a = Self::create_pass_target(&gpu, &config, "A");
160 let pass_b = Self::create_pass_target(&gpu, &config, "B");
161 let compute_target_a =
162 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute A");
163 let compute_target_b =
164 Self::create_compute_pass_target(&gpu, &config, TextureFormat::Rgba8Unorm, "Compute B");
165
166 let drawer = Drawer::new();
167
168 let scale_factor = window.scale_factor();
170 info!("Window scale factor: {scale_factor}");
171 SCALE_FACTOR
172 .set(RwLock::new(scale_factor))
173 .expect("Failed to set scale factor");
174
175 Self {
176 window,
177 gpu,
178 surface,
179 queue,
180 config,
181 size,
182 size_changed: false,
183 drawer,
184 pass_a,
185 pass_b,
186 compute_pipeline_registry: ComputePipelineRegistry::new(),
187 sample_count,
188 msaa_texture,
189 msaa_view,
190 compute_target_a,
191 compute_target_b,
192 compute_commands: Vec::new(),
193 resource_manager: Arc::new(RwLock::new(ComputeResourceManager::new())),
194 }
195 }
196
197 fn create_pass_target(
198 gpu: &wgpu::Device,
199 config: &wgpu::SurfaceConfiguration,
200 label_suffix: &str,
201 ) -> PassTarget {
202 let label = format!("Pass {label_suffix} Texture");
203 let texture_descriptor = wgpu::TextureDescriptor {
204 label: Some(&label),
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: 1,
212 dimension: wgpu::TextureDimension::D2,
213 format: config.format,
215 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
216 | wgpu::TextureUsages::TEXTURE_BINDING
217 | wgpu::TextureUsages::COPY_DST
218 | wgpu::TextureUsages::COPY_SRC,
219 view_formats: &[],
220 };
221 let texture = gpu.create_texture(&texture_descriptor);
222 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
223 PassTarget { texture, view }
224 }
225
226 fn create_compute_pass_target(
227 gpu: &wgpu::Device,
228 config: &wgpu::SurfaceConfiguration,
229 format: TextureFormat,
230 label_suffix: &str,
231 ) -> PassTarget {
232 let label = format!("Compute {label_suffix} Texture");
233 let texture_descriptor = wgpu::TextureDescriptor {
234 label: Some(&label),
235 size: wgpu::Extent3d {
236 width: config.width,
237 height: config.height,
238 depth_or_array_layers: 1,
239 },
240 mip_level_count: 1,
241 sample_count: 1,
242 dimension: wgpu::TextureDimension::D2,
243 format,
244 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
245 | wgpu::TextureUsages::TEXTURE_BINDING
246 | wgpu::TextureUsages::STORAGE_BINDING
247 | wgpu::TextureUsages::COPY_DST
248 | wgpu::TextureUsages::COPY_SRC,
249 view_formats: &[],
250 };
251 let texture = gpu.create_texture(&texture_descriptor);
252 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
253 PassTarget { texture, view }
254 }
255
256 pub fn register_pipelines(&mut self, register_fn: impl FnOnce(&mut Self)) {
257 register_fn(self);
258 }
259
260 pub(crate) fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
263 if self.size == size {
264 return;
265 }
266 self.size = size;
267 self.size_changed = true;
268 }
269
270 pub(crate) fn size(&self) -> winit::dpi::PhysicalSize<u32> {
272 self.size
273 }
274
275 pub(crate) fn resize_pass_targets_if_needed(&mut self) {
276 if self.size_changed {
277 self.pass_a.texture.destroy();
278 self.pass_b.texture.destroy();
279 self.compute_target_a.texture.destroy();
280 self.compute_target_b.texture.destroy();
281
282 self.pass_a = Self::create_pass_target(&self.gpu, &self.config, "A");
283 self.pass_b = Self::create_pass_target(&self.gpu, &self.config, "B");
284 self.compute_target_a = Self::create_compute_pass_target(
285 &self.gpu,
286 &self.config,
287 TextureFormat::Rgba8Unorm,
288 "Compute A",
289 );
290 self.compute_target_b = Self::create_compute_pass_target(
291 &self.gpu,
292 &self.config,
293 TextureFormat::Rgba8Unorm,
294 "Compute B",
295 );
296
297 if self.sample_count > 1 {
298 if let Some(t) = self.msaa_texture.take() {
299 t.destroy()
300 }
301 let texture = self.gpu.create_texture(&wgpu::TextureDescriptor {
302 label: Some("MSAA Framebuffer"),
303 size: wgpu::Extent3d {
304 width: self.config.width,
305 height: self.config.height,
306 depth_or_array_layers: 1,
307 },
308 mip_level_count: 1,
309 sample_count: self.sample_count,
310 dimension: wgpu::TextureDimension::D2,
311 format: self.config.format,
313 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
314 view_formats: &[],
315 });
316 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
317 self.msaa_texture = Some(texture);
318 self.msaa_view = Some(view);
319 }
320 }
321 }
322
323 pub(crate) fn resize_if_needed(&mut self) {
325 if self.size_changed {
326 self.config.width = self.size.width;
327 self.config.height = self.size.height;
328 self.resize_pass_targets_if_needed();
329 self.surface.configure(&self.gpu, &self.config);
330 self.size_changed = false;
331 }
332 }
333
334 pub(crate) fn render(
348 &mut self,
349 commands: impl IntoIterator<Item = (Command, PxSize, PxPosition)>,
350 ) -> Result<(), wgpu::SurfaceError> {
351 let output_frame = self.surface.get_current_texture()?;
352 let mut encoder = self
353 .gpu
354 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
355 label: Some("Render Encoder"),
356 });
357
358 let texture_size = wgpu::Extent3d {
359 width: self.config.width,
360 height: self.config.height,
361 depth_or_array_layers: 1,
362 };
363
364 let (mut read_target, mut write_target) = (&mut self.pass_a, &mut self.pass_b);
366
367 if !self.compute_commands.is_empty() {
369 warn!("Not every compute command is used in last frame. This is likely a bug.");
371 self.compute_commands.clear();
372 }
373
374 {
376 let (view, resolve_target) = if let Some(msaa_view) = &self.msaa_view {
377 (msaa_view, Some(&write_target.view))
378 } else {
379 (&write_target.view, None)
380 };
381 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
382 label: Some("Initial Clear Pass"),
383 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
384 view,
385 depth_slice: None,
386 resolve_target,
387 ops: wgpu::Operations {
388 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
389 store: wgpu::StoreOp::Store,
390 },
391 })],
392 ..Default::default()
393 });
394 self.drawer
395 .begin_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
396 self.drawer
397 .end_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
398 }
399
400 self.drawer
402 .pipeline_registry
403 .begin_all_frames(&self.gpu, &self.queue, &self.config);
404
405 let mut commands_iter = commands.into_iter().peekable();
407 let mut scene_texture_view = &read_target.view;
408 while let Some((command, size, start_pos)) = commands_iter.next() {
409 if command.barrier().is_some() {
411 std::mem::swap(&mut read_target, &mut write_target);
413 encoder.copy_texture_to_texture(
414 read_target.texture.as_image_copy(),
415 write_target.texture.as_image_copy(),
416 texture_size,
417 );
418 let final_view_after_compute = if !self.compute_commands.is_empty() {
420 let compute_commands = mem::take(&mut self.compute_commands);
421 Self::do_compute(
422 &mut encoder,
423 compute_commands,
424 &mut self.compute_pipeline_registry,
425 &self.gpu,
426 &self.queue,
427 &self.config,
428 &mut self.resource_manager.write(),
429 &read_target.view,
430 &self.compute_target_a,
431 &self.compute_target_b,
432 )
433 } else {
434 &read_target.view
435 };
436 scene_texture_view = final_view_after_compute;
437 }
438
439 match command {
440 Command::Draw(command) => {
442 let (view, resolve_target) = if let Some(msaa_view) = &self.msaa_view {
443 (msaa_view, Some(&write_target.view))
444 } else {
445 (&write_target.view, None)
446 };
447 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
448 label: Some("Render Pass"),
449 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
450 view,
451 depth_slice: None,
452 resolve_target,
453 ops: wgpu::Operations {
454 load: wgpu::LoadOp::Load,
455 store: wgpu::StoreOp::Store,
456 },
457 })],
458 ..Default::default()
459 });
460 self.drawer
461 .begin_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
462
463 self.drawer.submit(
465 &self.gpu,
466 &self.queue,
467 &self.config,
468 &mut rpass,
469 &*command,
470 size,
471 start_pos,
472 scene_texture_view,
473 );
474
475 while let Some((Command::Draw(command), _, _)) = commands_iter.peek() {
477 if command.barrier().is_some() {
478 break; }
480 if let Some((Command::Draw(command), size, start_pos)) =
481 commands_iter.next()
482 {
483 self.drawer.submit(
484 &self.gpu,
485 &self.queue,
486 &self.config,
487 &mut rpass,
488 &*command,
489 size,
490 start_pos,
491 scene_texture_view,
492 );
493 }
494 }
495 self.drawer
496 .end_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
497 }
498 Command::Compute(command) => {
500 self.compute_commands.push(command);
501 while let Some((Command::Compute(_), _, _)) = commands_iter.peek() {
503 if let Some((Command::Compute(command), _, _)) = commands_iter.next() {
504 self.compute_commands.push(command);
505 }
506 }
507 }
508 }
509 }
510
511 self.drawer
513 .pipeline_registry
514 .end_all_frames(&self.gpu, &self.queue, &self.config);
515
516 encoder.copy_texture_to_texture(
518 write_target.texture.as_image_copy(),
519 output_frame.texture.as_image_copy(),
520 texture_size,
521 );
522
523 self.queue.submit(Some(encoder.finish()));
524 output_frame.present();
525
526 Ok(())
527 }
528
529 fn do_compute<'a>(
530 encoder: &mut wgpu::CommandEncoder,
531 commands: Vec<Box<dyn ComputeCommand>>,
532 compute_pipeline_registry: &mut ComputePipelineRegistry,
533 gpu: &wgpu::Device,
534 queue: &wgpu::Queue,
535 config: &wgpu::SurfaceConfiguration,
536 resource_manager: &mut ComputeResourceManager,
537 scene_view: &'a wgpu::TextureView,
539 target_a: &'a PassTarget,
541 target_b: &'a PassTarget,
542 ) -> &'a wgpu::TextureView {
543 if commands.is_empty() {
544 return scene_view;
545 }
546
547 let mut read_view = scene_view;
548 let (mut write_target, mut read_target) = (target_a, target_b);
549
550 for command in commands {
551 let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
553 label: Some("Compute Target Clear"),
554 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
555 view: &write_target.view,
556 resolve_target: None,
557 ops: wgpu::Operations {
558 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
559 store: wgpu::StoreOp::Store,
560 },
561 depth_slice: None,
562 })],
563 ..Default::default()
564 });
565 drop(rpass);
566
567 {
569 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
570 label: Some("Compute Pass"),
571 timestamp_writes: None,
572 });
573
574 compute_pipeline_registry.dispatch_erased(
575 gpu,
576 queue,
577 config,
578 &mut cpass,
579 &*command,
580 resource_manager,
581 read_view,
582 &write_target.view,
583 );
584 } read_view = &write_target.view;
589 std::mem::swap(&mut write_target, &mut read_target);
591 }
592
593 read_view
596 }
597}