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 let mut commands_iter = commands.into_iter().peekable();
402 let mut scene_texture_view = &read_target.view;
403 while let Some((command, size, start_pos)) = commands_iter.next() {
404 if command.barrier().is_some() {
406 std::mem::swap(&mut read_target, &mut write_target);
408 encoder.copy_texture_to_texture(
409 read_target.texture.as_image_copy(),
410 write_target.texture.as_image_copy(),
411 texture_size,
412 );
413 let final_view_after_compute = if !self.compute_commands.is_empty() {
415 let compute_commands = mem::take(&mut self.compute_commands);
416 Self::do_compute(
417 &mut encoder,
418 compute_commands,
419 &mut self.compute_pipeline_registry,
420 &self.gpu,
421 &self.queue,
422 &self.config,
423 &mut self.resource_manager.write(),
424 &read_target.view,
425 &self.compute_target_a,
426 &self.compute_target_b,
427 )
428 } else {
429 &read_target.view
430 };
431 scene_texture_view = final_view_after_compute;
432 }
433
434 match command {
435 Command::Draw(command) => {
437 let (view, resolve_target) = if let Some(msaa_view) = &self.msaa_view {
438 (msaa_view, Some(&write_target.view))
439 } else {
440 (&write_target.view, None)
441 };
442 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
443 label: Some("Render Pass"),
444 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
445 view,
446 depth_slice: None,
447 resolve_target,
448 ops: wgpu::Operations {
449 load: wgpu::LoadOp::Load,
450 store: wgpu::StoreOp::Store,
451 },
452 })],
453 ..Default::default()
454 });
455 self.drawer
456 .begin_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
457
458 self.drawer.submit(
460 &self.gpu,
461 &self.queue,
462 &self.config,
463 &mut rpass,
464 &*command,
465 size,
466 start_pos,
467 scene_texture_view,
468 );
469
470 while let Some((Command::Draw(command), _, _)) = commands_iter.peek() {
472 if command.barrier().is_some() {
473 break; }
475 if let Some((Command::Draw(command), size, start_pos)) =
476 commands_iter.next()
477 {
478 self.drawer.submit(
479 &self.gpu,
480 &self.queue,
481 &self.config,
482 &mut rpass,
483 &*command,
484 size,
485 start_pos,
486 scene_texture_view,
487 );
488 }
489 }
490 self.drawer
491 .end_pass(&self.gpu, &self.queue, &self.config, &mut rpass);
492 }
493 Command::Compute(command) => {
495 self.compute_commands.push(command);
496 while let Some((Command::Compute(_), _, _)) = commands_iter.peek() {
498 if let Some((Command::Compute(command), _, _)) = commands_iter.next() {
499 self.compute_commands.push(command);
500 }
501 }
502 }
503 }
504 }
505
506 encoder.copy_texture_to_texture(
508 write_target.texture.as_image_copy(),
509 output_frame.texture.as_image_copy(),
510 texture_size,
511 );
512
513 self.queue.submit(Some(encoder.finish()));
514 output_frame.present();
515
516 Ok(())
517 }
518
519 fn do_compute<'a>(
520 encoder: &mut wgpu::CommandEncoder,
521 commands: Vec<Box<dyn ComputeCommand>>,
522 compute_pipeline_registry: &mut ComputePipelineRegistry,
523 gpu: &wgpu::Device,
524 queue: &wgpu::Queue,
525 config: &wgpu::SurfaceConfiguration,
526 resource_manager: &mut ComputeResourceManager,
527 scene_view: &'a wgpu::TextureView,
529 target_a: &'a PassTarget,
531 target_b: &'a PassTarget,
532 ) -> &'a wgpu::TextureView {
533 if commands.is_empty() {
534 return scene_view;
535 }
536
537 let mut read_view = scene_view;
538 let (mut write_target, mut read_target) = (target_a, target_b);
539
540 for command in commands {
541 let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
543 label: Some("Compute Target Clear"),
544 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
545 view: &write_target.view,
546 resolve_target: None,
547 ops: wgpu::Operations {
548 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
549 store: wgpu::StoreOp::Store,
550 },
551 depth_slice: None,
552 })],
553 ..Default::default()
554 });
555 drop(rpass);
556
557 {
559 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
560 label: Some("Compute Pass"),
561 timestamp_writes: None,
562 });
563
564 compute_pipeline_registry.dispatch_erased(
565 gpu,
566 queue,
567 config,
568 &mut cpass,
569 &*command,
570 resource_manager,
571 read_view,
572 &write_target.view,
573 );
574 } read_view = &write_target.view;
579 std::mem::swap(&mut write_target, &mut read_target);
581 }
582
583 read_view
586 }
587}