show_image/backend/
context.rs

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
20/// Internal shorthand type-alias for the correct [`winit::event_loop::EventLoop`].
21///
22/// Not for use in public APIs.
23type EventLoop = winit::event_loop::EventLoop<ContextFunction>;
24
25/// Internal shorthand for context event handlers.
26///
27/// Not for use in public APIs.
28type DynContextEventHandler = dyn FnMut(&mut ContextHandle, &mut Event, &mut event::EventHandlerControlFlow);
29
30/// Internal shorthand type-alias for the correct [`winit::event_loop::EventLoopWindowTarget`].
31///
32/// Not for use in public APIs.
33type 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	/// The wgpu device to use.
48	pub device: wgpu::Device,
49
50	/// The wgpu command queue to use.
51	pub queue: wgpu::Queue,
52
53	/// The bind group layout for the window specific bindings.
54	pub window_bind_group_layout: wgpu::BindGroupLayout,
55
56	/// The bind group layout for the image specific bindings.
57	pub image_bind_group_layout: wgpu::BindGroupLayout,
58
59	/// The render pipeline to use for windows.
60	pub window_pipeline: wgpu::RenderPipeline,
61
62	/// The render pipeline to use for rendering to image.
63	#[cfg(feature = "save")]
64	pub image_pipeline: wgpu::RenderPipeline,
65}
66
67/// The global context managing all windows and the main event loop.
68pub(crate) struct Context {
69	/// Marker to make context !Send.
70	pub unsend: std::marker::PhantomData<*const ()>,
71
72	/// The wgpu instance to create surfaces with.
73	pub instance: wgpu::Instance,
74
75	/// GPU related context that can not be initialized until we have a valid surface.
76	pub gpu: Option<GpuContext>,
77
78	/// The event loop to use.
79	///
80	/// Running the event loop consumes it,
81	/// so from that point on this field is `None`.
82	pub event_loop: Option<EventLoop>,
83
84	/// A proxy object to clone for new requests.
85	pub proxy: ContextProxy,
86
87	/// The swap chain format to use.
88	pub swap_chain_format: wgpu::TextureFormat,
89
90	/// The windows.
91	pub windows: Vec<Window>,
92
93	/// Cache for mouse state.
94	pub mouse_cache: super::mouse_cache::MouseCache,
95
96	/// If true, exit the program when the last window closes.
97	pub exit_with_last_window: bool,
98
99	/// The global event handlers.
100	pub event_handlers: Vec<Box<DynContextEventHandler>>,
101
102	/// Background tasks, like saving images.
103	pub background_tasks: Vec<BackgroundThread<()>>,
104}
105
106/// Handle to the global context.
107///
108/// You can interact with the global context through a [`ContextHandle`] only from the global context thread.
109/// To interact with the context from a different thread, use a [`ContextProxy`].
110pub 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	/// Create a new global context.
165	///
166	/// You can theoreticlly create as many contexts as you want,
167	/// but they must be run from the main thread and the [`run`](Self::run) function never returns.
168	/// So it is not possible to *run* more than one context.
169	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	/// Add a global event handler.
193	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	/// Run the event loop of the context.
201	///
202	/// This function must be run from the main thread and never returns.
203	///
204	/// Not all platforms have this restriction,
205	/// but for portability reasons it is enforced on all platforms.
206	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			// Check if the event handlers caused the last window(s) to close.
213			// If so, generate an AllWIndowsClosed event for the event handlers.
214			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	/// Create a new context handle.
226	fn new(context: &'a mut Context, event_loop: &'a EventLoopWindowTarget) -> Self {
227		Self { context, event_loop }
228	}
229
230	/// Reborrow self with a shorter lifetime.
231	pub(crate) fn reborrow(&mut self) -> ContextHandle {
232		ContextHandle {
233			context: self.context,
234			event_loop: self.event_loop,
235		}
236	}
237
238	/// Get a proxy for the context to interact with it from a different thread.
239	///
240	/// You should not use proxy objects from withing the global context thread.
241	/// The proxy objects often wait for the global context to perform some action.
242	/// Doing so from within the global context thread would cause a deadlock.
243	pub fn proxy(&self) -> ContextProxy {
244		self.context.proxy.clone()
245	}
246
247	/// Exit the program when the last window closes.
248	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	/// Get a window handle for the given window ID.
253	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	/// Create a new window.
259	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	/// Add a global event handler.
265	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	/// Run a task in a background thread and register it with the context.
273	///
274	/// The task will be executed in a different thread than the context.
275	/// Currently, each task is spawned in a separate thread.
276	/// In the future, tasks may be run in a dedicated thread pool.
277	///
278	/// The background task will be joined before the process is terminated when you use [`Self::exit()`] or one of the other exit functions of this crate.
279	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	/// Join all background tasks and then exit the process.
287	///
288	/// If you use [`std::process::exit`], running background tasks may be killed.
289	/// To ensure no data loss occurs, you should use this function instead.
290	///
291	/// Background tasks are spawned when an image is saved through the built-in Ctrl+S or Ctrl+Shift+S shortcut, or by user code.
292	pub fn exit(&mut self, code: i32) -> ! {
293		self.context.exit(code);
294	}
295}
296
297impl Context {
298	/// Create a window.
299	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	/// Destroy a window.
358	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	/// Upload an image to the GPU.
369	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	/// Resize a window.
375	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	/// Render the contents of a window.
389	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	/// Handle an event from the event loop.
547	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		// Split between Event<ContextFunction> and ContextFunction commands.
556		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		// Convert to own event type.
567		let mut event = match super::event::convert_winit_event(event, &self.mouse_cache) {
568			Some(x) => x,
569			None => return,
570		};
571
572		// If we have nothing more to do, clean the background tasks.
573		if let Event::MainEventsCleared = &event {
574			self.clean_background_tasks();
575		}
576
577		// Run window event handlers.
578		let run_context_handlers = match &mut event {
579			Event::WindowEvent(event) => self.run_window_event_handlers(event, event_loop),
580			_ => true,
581		};
582
583		// Run context event handlers.
584		if run_context_handlers {
585			self.run_event_handlers(&mut event, event_loop);
586		}
587
588		// Perform default actions for events.
589		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	/// Run global event handlers.
619	fn run_event_handlers(&mut self, event: &mut Event, event_loop: &EventLoopWindowTarget) {
620		use super::util::RetainMut;
621
622		// Event handlers could potentially modify the list of event handlers.
623		// Also, even if they couldn't we'd still need borrow self mutably multiple times to run the event handlers.
624		// That's not allowed, of course, so temporarily swap the event handlers with a new vector.
625		// When we've run all handlers, we add the new handlers to the original vector and place it back.
626		// https://newfastuff.com/wp-content/uploads/2019/05/dVIkgAf.png
627		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	/// Run window-specific event handlers.
648	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	/// Run a background task in a separate thread.
683	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	/// Clean-up finished background tasks.
691	fn clean_background_tasks(&mut self) {
692		self.background_tasks.retain(|task| !task.is_done());
693	}
694
695	/// Join all background tasks.
696	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	/// Join all background tasks and then exit the process.
703	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
797/// Get a wgpu device to use.
798async fn get_device(instance: &wgpu::Instance, surface: &wgpu::Surface) -> Result<(wgpu::Device, wgpu::Queue), GetDeviceError> {
799	// Find a suitable display adapter.
800	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	// Create the logical device and command queue
809	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
823/// Create the bind group layout for the window specific bindings.
824fn 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
840/// Create the bind group layout for the image specific bindings.
841fn 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
871/// Create a render pipeline with the specified device, layout, shaders and swap chain format.
872fn 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
926/// Create a swap chain for a surface.
927fn 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
945/// Perform a render pass of an image.
946fn 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}