1use core::num::NonZeroU64;
2use crate::backend::proxy::ContextFunction;
3use crate::backend::util::GpuImage;
4use crate::backend::util::{ToStd140, UniformsBuffer};
5use crate::backend::window::Window;
6use crate::backend::window::WindowUniforms;
7use crate::background_thread::BackgroundThread;
8use crate::error::CreateWindowError;
9use crate::error::GetDeviceError;
10use crate::error::InvalidWindowId;
11use crate::error::NoSuitableAdapterFound;
12use crate::event::{self, Event, EventHandlerControlFlow, WindowEvent};
13use crate::ContextProxy;
14use crate::ImageView;
15use crate::WindowHandle;
16use crate::WindowId;
17use crate::WindowOptions;
18use glam::Affine2;
19
20type EventLoop = winit::event_loop::EventLoop<ContextFunction>;
24
25type DynContextEventHandler = dyn FnMut(&mut ContextHandle, &mut Event, &mut event::EventHandlerControlFlow);
29
30type EventLoopWindowTarget = winit::event_loop::EventLoopWindowTarget<ContextFunction>;
34
35impl From<crate::Color> for wgpu::Color {
36 fn from(other: crate::Color) -> Self {
37 Self {
38 r: other.red,
39 g: other.green,
40 b: other.blue,
41 a: other.alpha,
42 }
43 }
44}
45
46pub(crate) struct GpuContext {
47 pub device: wgpu::Device,
49
50 pub queue: wgpu::Queue,
52
53 pub window_bind_group_layout: wgpu::BindGroupLayout,
55
56 pub image_bind_group_layout: wgpu::BindGroupLayout,
58
59 pub window_pipeline: wgpu::RenderPipeline,
61
62 #[cfg(feature = "save")]
64 pub image_pipeline: wgpu::RenderPipeline,
65}
66
67pub(crate) struct Context {
69 pub unsend: std::marker::PhantomData<*const ()>,
71
72 pub instance: wgpu::Instance,
74
75 pub gpu: Option<GpuContext>,
77
78 pub event_loop: Option<EventLoop>,
83
84 pub proxy: ContextProxy,
86
87 pub swap_chain_format: wgpu::TextureFormat,
89
90 pub windows: Vec<Window>,
92
93 pub mouse_cache: super::mouse_cache::MouseCache,
95
96 pub exit_with_last_window: bool,
98
99 pub event_handlers: Vec<Box<DynContextEventHandler>>,
101
102 pub background_tasks: Vec<BackgroundThread<()>>,
104}
105
106pub struct ContextHandle<'a> {
111 pub(crate) context: &'a mut Context,
112 pub(crate) event_loop: &'a EventLoopWindowTarget,
113}
114
115impl GpuContext {
116 pub fn new(instance: &wgpu::Instance, swap_chain_format: wgpu::TextureFormat, surface: &wgpu::Surface) -> Result<Self, GetDeviceError> {
117 let (device, queue) = futures::executor::block_on(get_device(instance, surface))?;
118 device.on_uncaptured_error(Box::new(|error| {
119 panic!("Unhandled WGPU error: {}", error);
120 }));
121
122 let window_bind_group_layout = create_window_bind_group_layout(&device);
123 let image_bind_group_layout = create_image_bind_group_layout(&device);
124
125 let vertex_shader = device.create_shader_module(wgpu::include_spirv!("../../shaders/shader.vert.spv"));
126 let fragment_shader_unorm8 = device.create_shader_module(wgpu::include_spirv!("../../shaders/unorm8.frag.spv"));
127
128 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
129 label: Some("show-image-pipeline-layout"),
130 bind_group_layouts: &[&window_bind_group_layout, &image_bind_group_layout],
131 push_constant_ranges: &[],
132 });
133
134 let window_pipeline = create_render_pipeline(
135 &device,
136 &pipeline_layout,
137 &vertex_shader,
138 &fragment_shader_unorm8,
139 swap_chain_format,
140 );
141
142 #[cfg(feature = "save")]
143 let image_pipeline = create_render_pipeline(
144 &device,
145 &pipeline_layout,
146 &vertex_shader,
147 &fragment_shader_unorm8,
148 wgpu::TextureFormat::Rgba8Unorm,
149 );
150
151 Ok(Self {
152 device,
153 queue,
154 window_bind_group_layout,
155 image_bind_group_layout,
156 window_pipeline,
157 #[cfg(feature = "save")]
158 image_pipeline,
159 })
160 }
161}
162
163impl Context {
164 pub fn new(swap_chain_format: wgpu::TextureFormat) -> Result<Self, GetDeviceError> {
170 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
171 backends: select_backend(),
172 dx12_shader_compiler: wgpu::Dx12Compiler::Fxc,
173 });
174 let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
175 let proxy = ContextProxy::new(event_loop.create_proxy(), std::thread::current().id());
176
177 Ok(Self {
178 unsend: Default::default(),
179 instance,
180 gpu: None,
181 event_loop: Some(event_loop),
182 proxy,
183 swap_chain_format,
184 windows: Vec::new(),
185 mouse_cache: Default::default(),
186 exit_with_last_window: false,
187 event_handlers: Vec::new(),
188 background_tasks: Vec::new(),
189 })
190 }
191
192 pub fn add_event_handler<F>(&mut self, handler: F)
194 where
195 F: 'static + FnMut(&mut ContextHandle, &mut Event, &mut EventHandlerControlFlow),
196 {
197 self.event_handlers.push(Box::new(handler))
198 }
199
200 pub fn run(mut self) -> ! {
207 let event_loop = self.event_loop.take().unwrap();
208 event_loop.run(move |event, event_loop, control_flow| {
209 let initial_window_count = self.windows.len();
210 self.handle_event(event, event_loop, control_flow);
211
212 if self.windows.is_empty() && initial_window_count > 0 {
215 self.run_event_handlers(&mut Event::AllWindowsClosed, event_loop);
216 if self.exit_with_last_window {
217 self.exit(0);
218 }
219 }
220 });
221 }
222}
223
224impl<'a> ContextHandle<'a> {
225 fn new(context: &'a mut Context, event_loop: &'a EventLoopWindowTarget) -> Self {
227 Self { context, event_loop }
228 }
229
230 pub(crate) fn reborrow(&mut self) -> ContextHandle {
232 ContextHandle {
233 context: self.context,
234 event_loop: self.event_loop,
235 }
236 }
237
238 pub fn proxy(&self) -> ContextProxy {
244 self.context.proxy.clone()
245 }
246
247 pub fn set_exit_with_last_window(&mut self, exit_with_last_window: bool) {
249 self.context.exit_with_last_window = exit_with_last_window;
250 }
251
252 pub fn window(&mut self, window_id: WindowId) -> Result<WindowHandle, InvalidWindowId> {
254 let index = self.context.windows.iter().position(|x| x.id() == window_id).ok_or(InvalidWindowId { window_id })?;
255 Ok(WindowHandle::new(self.reborrow(), index, None))
256 }
257
258 pub fn create_window(&mut self, title: impl Into<String>, options: WindowOptions) -> Result<WindowHandle, CreateWindowError> {
260 let index = self.context.create_window(self.event_loop, title, options)?;
261 Ok(WindowHandle::new(self.reborrow(), index, None))
262 }
263
264 pub fn add_event_handler<F>(&mut self, handler: F)
266 where
267 F: 'static + FnMut(&mut ContextHandle, &mut Event, &mut EventHandlerControlFlow),
268 {
269 self.context.add_event_handler(handler);
270 }
271
272 pub fn run_background_task<F>(&mut self, task: F)
280 where
281 F: FnOnce() + Send + 'static,
282 {
283 self.context.run_background_task(task);
284 }
285
286 pub fn exit(&mut self, code: i32) -> ! {
293 self.context.exit(code);
294 }
295}
296
297impl Context {
298 fn create_window(
300 &mut self,
301 event_loop: &EventLoopWindowTarget,
302 title: impl Into<String>,
303 options: WindowOptions,
304 ) -> Result<usize, CreateWindowError> {
305 let fullscreen = if options.fullscreen {
306 Some(winit::window::Fullscreen::Borderless(None))
307 } else {
308 None
309 };
310 let mut window = winit::window::WindowBuilder::new()
311 .with_title(title)
312 .with_visible(!options.start_hidden)
313 .with_resizable(options.resizable)
314 .with_decorations(!options.borderless)
315 .with_fullscreen(fullscreen);
316
317 if let Some(size) = options.size {
318 window = window.with_inner_size(winit::dpi::PhysicalSize::new(size[0], size[1]));
319 }
320
321 let window = window.build(event_loop)?;
322 let surface = unsafe { self.instance.create_surface(&window)? };
323
324
325 let gpu = match &self.gpu {
326 Some(x) => x,
327 None => {
328 let gpu = GpuContext::new(&self.instance, self.swap_chain_format, &surface)?;
329 self.gpu.insert(gpu)
330 }
331 };
332
333 let size = glam::UVec2::new(window.inner_size().width, window.inner_size().height);
334 configure_surface(size, &surface, self.swap_chain_format, &gpu.device);
335 let uniforms = UniformsBuffer::from_value(&gpu.device, &WindowUniforms::no_image(), &gpu.window_bind_group_layout);
336
337 let window = Window {
338 window,
339 preserve_aspect_ratio: options.preserve_aspect_ratio,
340 background_color: options.background_color,
341 surface,
342 uniforms,
343 image: None,
344 user_transform: Affine2::IDENTITY,
345 overlays: Default::default(),
346 event_handlers: Vec::new(),
347 };
348
349 self.windows.push(window);
350 let index = self.windows.len() - 1;
351 if options.default_controls {
352 self.windows[index].event_handlers.push(Box::new(super::window::default_controls_handler));
353 }
354 Ok(index)
355 }
356
357 fn destroy_window(&mut self, window_id: WindowId) -> Result<(), InvalidWindowId> {
359 let index = self
360 .windows
361 .iter()
362 .position(|w| w.id() == window_id)
363 .ok_or(InvalidWindowId { window_id })?;
364 self.windows.remove(index);
365 Ok(())
366 }
367
368 pub fn make_gpu_image(&self, name: impl Into<String>, image: &ImageView) -> GpuImage {
370 let gpu = self.gpu.as_ref().unwrap();
371 GpuImage::from_data(name.into(), &gpu.device, &gpu.image_bind_group_layout, image)
372 }
373
374 fn resize_window(&mut self, window_id: WindowId, new_size: glam::UVec2) -> Result<(), InvalidWindowId> {
376 let window = self
377 .windows
378 .iter_mut()
379 .find(|w| w.id() == window_id)
380 .ok_or(InvalidWindowId { window_id })?;
381
382 let gpu = self.gpu.as_ref().unwrap();
383 configure_surface(new_size, &window.surface, self.swap_chain_format, &gpu.device);
384 window.uniforms.mark_dirty(true);
385 Ok(())
386 }
387
388 fn render_window(&mut self, window_id: WindowId) -> Result<(), InvalidWindowId> {
390 let window = self
391 .windows
392 .iter_mut()
393 .find(|w| w.id() == window_id)
394 .ok_or(InvalidWindowId { window_id })?;
395
396 let image = match &window.image {
397 Some(x) => x,
398 None => return Ok(()),
399 };
400
401 let frame = window
402 .surface
403 .get_current_texture()
404 .expect("Failed to acquire next frame");
405
406 let gpu = self.gpu.as_ref().unwrap();
407 let mut encoder = gpu.device.create_command_encoder(&Default::default());
408
409 if window.uniforms.is_dirty() {
410 window
411 .uniforms
412 .update_from(&gpu.device, &mut encoder, &window.calculate_uniforms());
413 }
414
415 render_pass(
416 &mut encoder,
417 &gpu.window_pipeline,
418 &window.uniforms,
419 image,
420 Some(window.background_color),
421 &frame.texture.create_view(&wgpu::TextureViewDescriptor::default()),
422 );
423 for (_name, overlay) in &window.overlays {
424 if overlay.visible {
425 render_pass(
426 &mut encoder,
427 &gpu.window_pipeline,
428 &window.uniforms,
429 &overlay.image,
430 None,
431 &frame.texture.create_view(&wgpu::TextureViewDescriptor::default()),
432 );
433 }
434 }
435 gpu.queue.submit(std::iter::once(encoder.finish()));
436 frame.present();
437 Ok(())
438 }
439
440 #[cfg(feature = "save")]
441 fn render_to_texture(&self, window_id: WindowId, overlays: bool) -> Result<Option<(String, crate::BoxImage)>, InvalidWindowId> {
442 let window = self
443 .windows
444 .iter()
445 .find(|w| w.id() == window_id)
446 .ok_or(InvalidWindowId { window_id })?;
447
448 let image = match &window.image {
449 Some(x) => x,
450 None => return Ok(None),
451 };
452
453 let bytes_per_row = align_next_u32(image.info().size.x * 4, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
454 let width_scale = image.info().size.x as f32 * 4.0 / bytes_per_row as f32;
455
456 let size = wgpu::Extent3d {
457 width: div_round_up(bytes_per_row, 4),
458 height: image.info().size.y,
459 depth_or_array_layers: 1,
460 };
461
462 let gpu = self.gpu.as_ref().unwrap();
463 let window_uniforms = WindowUniforms {
464 transform: Affine2::from_scale([width_scale, 1.0].into()),
465 image_size: image.info().size.as_vec2(),
466 };
467 let window_uniforms = UniformsBuffer::from_value(&gpu.device, &window_uniforms, &gpu.window_bind_group_layout);
468
469 let target = gpu.device.create_texture(&wgpu::TextureDescriptor {
470 label: Some(&format!("{}_render", image.name())),
471 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
472 sample_count: 1,
473 mip_level_count: 1,
474 format: wgpu::TextureFormat::Rgba8Unorm,
475 dimension: wgpu::TextureDimension::D2,
476 size,
477 view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
478 });
479
480 let mut encoder = gpu.device.create_command_encoder(&Default::default());
481 let transparent = crate::Color::rgba(0.0, 0.0, 0.0, 0.0);
482 let render_target = target.create_view(&wgpu::TextureViewDescriptor {
483 label: None,
484 format: None,
485 dimension: None,
486 aspect: wgpu::TextureAspect::All,
487 base_mip_level: 0,
488 mip_level_count: None,
489 base_array_layer: 0,
490 array_layer_count: None,
491 });
492
493 render_pass(
494 &mut encoder,
495 &gpu.image_pipeline,
496 &window_uniforms,
497 image,
498 Some(transparent),
499 &render_target,
500 );
501 if overlays {
502 for (_name, overlay) in &window.overlays {
503 if overlay.visible {
504 render_pass(&mut encoder, &gpu.image_pipeline, &window_uniforms, &overlay.image, None, &render_target);
505 }
506 }
507 }
508
509 let buffer = gpu.device.create_buffer(&wgpu::BufferDescriptor {
510 label: None,
511 size: u64::from(bytes_per_row) * u64::from(image.info().size.y),
512 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
513 mapped_at_creation: false,
514 });
515
516 encoder.copy_texture_to_buffer(
517 wgpu::ImageCopyTexture {
518 texture: &target,
519 mip_level: 0,
520 origin: wgpu::Origin3d::ZERO,
521 aspect: wgpu::TextureAspect::All,
522 },
523 wgpu::ImageCopyBuffer {
524 buffer: &buffer,
525 layout: wgpu::ImageDataLayout {
526 offset: 0,
527 bytes_per_row: Some(bytes_per_row),
528 rows_per_image: Some(image.info().size.y),
529 },
530 },
531 size,
532 );
533
534 gpu.queue.submit(std::iter::once(encoder.finish()));
535
536 let view = super::util::map_buffer(&gpu.device, buffer.slice(..)).unwrap();
537 let info = crate::ImageInfo {
538 pixel_format: crate::PixelFormat::Rgba8(crate::Alpha::Unpremultiplied),
539 size: image.info().size,
540 stride: glam::UVec2::new(4, bytes_per_row),
541 };
542 let data: Box<[u8]> = Box::from(&view[..]);
543 Ok(Some((image.name().to_string(), crate::BoxImage::new(info, data))))
544 }
545
546 fn handle_event(
548 &mut self,
549 event: winit::event::Event<ContextFunction>,
550 event_loop: &EventLoopWindowTarget,
551 control_flow: &mut winit::event_loop::ControlFlow,
552 ) {
553 *control_flow = winit::event_loop::ControlFlow::Wait;
554
555 let event = match super::event::map_nonuser_event(event) {
557 Ok(event) => event,
558 Err(function) => {
559 (function)(&mut ContextHandle::new(self, event_loop));
560 return;
561 },
562 };
563
564 self.mouse_cache.handle_event(&event);
565
566 let mut event = match super::event::convert_winit_event(event, &self.mouse_cache) {
568 Some(x) => x,
569 None => return,
570 };
571
572 if let Event::MainEventsCleared = &event {
574 self.clean_background_tasks();
575 }
576
577 let run_context_handlers = match &mut event {
579 Event::WindowEvent(event) => self.run_window_event_handlers(event, event_loop),
580 _ => true,
581 };
582
583 if run_context_handlers {
585 self.run_event_handlers(&mut event, event_loop);
586 }
587
588 match event {
590 #[cfg(feature = "save")]
591 #[allow(deprecated)]
592 Event::WindowEvent(WindowEvent::KeyboardInput(event)) => {
593 if event.input.state.is_pressed() && event.input.key_code == Some(event::VirtualKeyCode::S) {
594 let overlays = event.input.modifiers.alt();
595 let modifiers = event.input.modifiers & !event::ModifiersState::ALT;
596 if modifiers == event::ModifiersState::CTRL {
597 self.save_image_prompt(event.window_id, overlays);
598 } else if modifiers == event::ModifiersState::CTRL | event::ModifiersState::SHIFT {
599 self.save_image(event.window_id, overlays);
600 }
601 }
602 },
603 Event::WindowEvent(WindowEvent::Resized(event)) => {
604 if event.size.x > 0 && event.size.y > 0 {
605 let _ = self.resize_window(event.window_id, event.size);
606 }
607 },
608 Event::WindowEvent(WindowEvent::RedrawRequested(event)) => {
609 let _ = self.render_window(event.window_id);
610 },
611 Event::WindowEvent(WindowEvent::CloseRequested(event)) => {
612 let _ = self.destroy_window(event.window_id);
613 },
614 _ => {},
615 }
616 }
617
618 fn run_event_handlers(&mut self, event: &mut Event, event_loop: &EventLoopWindowTarget) {
620 use super::util::RetainMut;
621
622 let mut event_handlers = std::mem::take(&mut self.event_handlers);
628
629 let mut stop_propagation = false;
630 RetainMut::retain_mut(&mut event_handlers, |handler| {
631 if stop_propagation {
632 true
633 } else {
634 let mut context_handle = ContextHandle::new(self, event_loop);
635 let mut control = EventHandlerControlFlow::default();
636 (handler)(&mut context_handle, event, &mut control);
637 stop_propagation = control.stop_propagation;
638 !control.remove_handler
639 }
640 });
641
642 let new_event_handlers = std::mem::take(&mut self.event_handlers);
643 event_handlers.extend(new_event_handlers);
644 self.event_handlers = event_handlers;
645 }
646
647 fn run_window_event_handlers(&mut self, event: &mut WindowEvent, event_loop: &EventLoopWindowTarget) -> bool {
649 use super::util::RetainMut;
650
651 let window_index = match self.windows.iter().position(|x| x.id() == event.window_id()) {
652 Some(x) => x,
653 None => return true,
654 };
655
656 let mut event_handlers = std::mem::take(&mut self.windows[window_index].event_handlers);
657
658 let mut stop_propagation = false;
659 let mut window_destroyed = false;
660 RetainMut::retain_mut(&mut event_handlers, |handler| {
661 if window_destroyed || stop_propagation {
662 true
663 } else {
664 let context_handle = ContextHandle::new(self, event_loop);
665 let window_handle = WindowHandle::new(context_handle, window_index, Some(&mut window_destroyed));
666 let mut control = EventHandlerControlFlow::default();
667 (handler)(window_handle, event, &mut control);
668 stop_propagation = control.stop_propagation;
669 !control.remove_handler
670 }
671 });
672
673 if !window_destroyed {
674 let new_event_handlers = std::mem::take(&mut self.windows[window_index].event_handlers);
675 event_handlers.extend(new_event_handlers);
676 self.windows[window_index].event_handlers = event_handlers;
677 }
678
679 !stop_propagation && !window_destroyed
680 }
681
682 fn run_background_task<F>(&mut self, task: F)
684 where
685 F: FnOnce() + Send + 'static,
686 {
687 self.background_tasks.push(BackgroundThread::new(task))
688 }
689
690 fn clean_background_tasks(&mut self) {
692 self.background_tasks.retain(|task| !task.is_done());
693 }
694
695 fn join_background_tasks(&mut self) {
697 for task in std::mem::take(&mut self.background_tasks) {
698 task.join().unwrap();
699 }
700 }
701
702 fn exit(&mut self, code: i32) -> ! {
704 self.join_background_tasks();
705 std::process::exit(code);
706 }
707
708 #[cfg(feature = "save")]
709 fn save_image_prompt(&mut self, window_id: WindowId, overlays: bool) {
710 let (name, image) = match self.render_to_texture(window_id, overlays) {
711 Ok(Some(x)) => x,
712 Ok(None) => return,
713 Err(e) => return log::error!("failed to render window contents: {}", e),
714 };
715
716 let info = image.info();
717 let name = format!("{}.png", name);
718 self.run_background_task(move || {
719 let path = match tinyfiledialogs::save_file_dialog("Save image", &name) {
720 Some(x) => x,
721 _ => return,
722 };
723 if let Err(e) = crate::save_rgba8_image(&path, image.data(), info.size, info.stride.y) {
724 log::error!("failed to save image to {}: {}", path, e);
725 }
726 });
727 }
728
729 #[cfg(feature = "save")]
730 fn save_image(&mut self, window_id: WindowId, overlays: bool) {
731 let (name, image) = match self.render_to_texture(window_id, overlays) {
732 Ok(Some(x)) => x,
733 Ok(None) => return,
734 Err(e) => return log::error!("failed to render window contents: {}", e),
735 };
736
737 let info = image.info();
738 let name = format!("{}.png", name);
739 self.run_background_task(move || {
740 if let Err(e) = crate::save_rgba8_image(&name, image.data(), info.size, info.stride.y) {
741 log::error!("failed to save image to {}: {}", name, e);
742 }
743 });
744 }
745}
746
747fn select_backend() -> wgpu::Backends {
748 let backend = std::env::var_os("WGPU_BACKEND").unwrap_or_else(|| "primary".into());
749 let backend = match backend.to_str() {
750 Some(backend) => backend,
751 None => {
752 eprintln!("Unknown WGPU_BACKEND: {:?}", backend);
753 std::process::exit(1);
754 }
755 };
756
757 if backend.eq_ignore_ascii_case("primary") {
758 wgpu::Backends::PRIMARY
759 } else if backend.eq_ignore_ascii_case("vulkan") {
760 wgpu::Backends::VULKAN
761 } else if backend.eq_ignore_ascii_case("metal") {
762 wgpu::Backends::METAL
763 } else if backend.eq_ignore_ascii_case("dx12") {
764 wgpu::Backends::DX12
765 } else if backend.eq_ignore_ascii_case("dx11") {
766 wgpu::Backends::DX11
767 } else if backend.eq_ignore_ascii_case("gl") {
768 wgpu::Backends::GL
769 } else if backend.eq_ignore_ascii_case("webgpu") {
770 wgpu::Backends::BROWSER_WEBGPU
771 } else {
772 eprintln!("Unknown WGPU_BACKEND: {:?}", backend);
773 std::process::exit(1);
774 }
775}
776
777fn select_power_preference() -> wgpu::PowerPreference {
778 let power_pref = std::env::var_os("WGPU_POWER_PREF").unwrap_or_else(|| "low".into());
779 let power_pref = match power_pref.to_str() {
780 Some(power_pref) => power_pref,
781 None => {
782 eprintln!("Unknown WGPU_POWER_PREF: {:?}", power_pref);
783 std::process::exit(1);
784 }
785 };
786
787 if power_pref.eq_ignore_ascii_case("low") {
788 wgpu::PowerPreference::LowPower
789 } else if power_pref.eq_ignore_ascii_case("high") {
790 wgpu::PowerPreference::HighPerformance
791 } else {
792 eprintln!("Unknown WGPU_POWER_PREF: {:?}", power_pref);
793 std::process::exit(1);
794 }
795}
796
797async fn get_device(instance: &wgpu::Instance, surface: &wgpu::Surface) -> Result<(wgpu::Device, wgpu::Queue), GetDeviceError> {
799 let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
801 power_preference: select_power_preference(),
802 compatible_surface: Some(surface),
803 force_fallback_adapter: false,
804 });
805
806 let adapter = adapter.await.ok_or(NoSuitableAdapterFound)?;
807
808 let device = adapter.request_device(
810 &wgpu::DeviceDescriptor {
811 label: Some("show-image"),
812 limits: wgpu::Limits::default(),
813 features: wgpu::Features::default(),
814 },
815 None,
816 );
817
818 let (device, queue) = device.await?;
819
820 Ok((device, queue))
821}
822
823fn create_window_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
825 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
826 label: Some("window_bind_group_layout"),
827 entries: &[wgpu::BindGroupLayoutEntry {
828 binding: 0,
829 visibility: wgpu::ShaderStages::VERTEX,
830 count: None,
831 ty: wgpu::BindingType::Buffer {
832 ty: wgpu::BufferBindingType::Uniform,
833 has_dynamic_offset: false,
834 min_binding_size: Some(NonZeroU64::new(WindowUniforms::STD140_SIZE).unwrap()),
835 },
836 }],
837 })
838}
839
840fn create_image_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
842 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
843 label: Some("image_bind_group_layout"),
844 entries: &[
845 wgpu::BindGroupLayoutEntry {
846 binding: 0,
847 visibility: wgpu::ShaderStages::FRAGMENT,
848 count: None,
849 ty: wgpu::BindingType::Buffer {
850 ty: wgpu::BufferBindingType::Uniform,
851 has_dynamic_offset: false,
852 min_binding_size: Some(NonZeroU64::new(std::mem::size_of::<super::util::GpuImageUniforms>() as u64).unwrap()),
853 },
854 },
855 wgpu::BindGroupLayoutEntry {
856 binding: 1,
857 visibility: wgpu::ShaderStages::FRAGMENT,
858 count: None,
859 ty: wgpu::BindingType::Buffer {
860 ty: wgpu::BufferBindingType::Storage {
861 read_only: true,
862 },
863 has_dynamic_offset: false,
864 min_binding_size: None,
865 },
866 },
867 ],
868 })
869}
870
871fn create_render_pipeline(
873 device: &wgpu::Device,
874 layout: &wgpu::PipelineLayout,
875 vertex_shader: &wgpu::ShaderModule,
876 fragment_shader: &wgpu::ShaderModule,
877 swap_chain_format: wgpu::TextureFormat,
878) -> wgpu::RenderPipeline {
879 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
880 label: Some("show-image-pipeline"),
881 layout: Some(layout),
882 vertex: wgpu::VertexState {
883 module: vertex_shader,
884 entry_point: "main",
885 buffers: &[],
886 },
887 fragment: Some(wgpu::FragmentState {
888 module: fragment_shader,
889 entry_point: "main",
890 targets: &[Some(wgpu::ColorTargetState {
891 format: swap_chain_format,
892 blend: Some(wgpu::BlendState {
893 color: wgpu::BlendComponent {
894 src_factor: wgpu::BlendFactor::SrcAlpha,
895 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
896 operation: wgpu::BlendOperation::Add,
897 },
898 alpha: wgpu::BlendComponent {
899 src_factor: wgpu::BlendFactor::One,
900 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
901 operation: wgpu::BlendOperation::Add,
902 },
903 }),
904 write_mask: wgpu::ColorWrites::ALL,
905 })],
906 }),
907 primitive: wgpu::PrimitiveState {
908 topology: wgpu::PrimitiveTopology::TriangleList,
909 strip_index_format: None,
910 front_face: wgpu::FrontFace::Cw,
911 cull_mode: Some(wgpu::Face::Back),
912 unclipped_depth: false,
913 polygon_mode: wgpu::PolygonMode::Fill,
914 conservative: false,
915 },
916 depth_stencil: None,
917 multisample: wgpu::MultisampleState {
918 count: 1,
919 mask: !0,
920 alpha_to_coverage_enabled: false,
921 },
922 multiview: None,
923 })
924}
925
926fn configure_surface(
928 size: glam::UVec2,
929 surface: &wgpu::Surface,
930 format: wgpu::TextureFormat,
931 device: &wgpu::Device,
932) {
933 let config = wgpu::SurfaceConfiguration {
934 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
935 format,
936 width: size.x,
937 height: size.y,
938 present_mode: wgpu::PresentMode::AutoVsync,
939 alpha_mode: wgpu::CompositeAlphaMode::Auto,
940 view_formats: vec![format],
941 };
942 surface.configure(device, &config);
943}
944
945fn render_pass(
947 encoder: &mut wgpu::CommandEncoder,
948 render_pipeline: &wgpu::RenderPipeline,
949 window_uniforms: &UniformsBuffer<WindowUniforms>,
950 image: &GpuImage,
951 clear: Option<crate::Color>,
952 target: &wgpu::TextureView,
953) {
954 let load = match clear {
955 Some(color) => wgpu::LoadOp::Clear(color.into()),
956 None => wgpu::LoadOp::Load,
957 };
958
959 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
960 label: Some("render-image"),
961 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
962 view: target,
963 resolve_target: None,
964 ops: wgpu::Operations { load, store: true },
965 })],
966 depth_stencil_attachment: None,
967 });
968
969 render_pass.set_pipeline(render_pipeline);
970 render_pass.set_bind_group(0, window_uniforms.bind_group(), &[]);
971 render_pass.set_bind_group(1, image.bind_group(), &[]);
972 render_pass.draw(0..6, 0..1);
973 drop(render_pass);
974}
975
976#[cfg(feature = "save")]
977fn align_next_u32(input: u32, alignment: u32) -> u32 {
978 let remainder = input % alignment;
979 if remainder == 0 {
980 input
981 } else {
982 input - remainder + alignment
983 }
984}
985
986#[cfg(feature = "save")]
987fn div_round_up(input: u32, divisor: u32) -> u32 {
988 if input % divisor == 0 {
989 input / divisor
990 } else {
991 input / divisor + 1
992 }
993}