summaryrefslogtreecommitdiff
path: root/render/src/config.rs
blob: c3cc6b68d32e59135bc3111a41d855959bbdcb84 (plain)
use std::num::NonZeroU32;

use winit::dpi::{LogicalPosition, LogicalSize};
use winit::window::{Fullscreen, WindowBuilder};

/// Describes how a window may be resized
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Resizable {
	/// The minimum width of the window, or None if unconstrained
	pub min_width: Option<NonZeroU32>,
	/// The minimum height of the window, or None if unconstrained
	pub min_height: Option<NonZeroU32>,
	/// The maximum width of the window, or None if unconstrained
	pub max_width: Option<NonZeroU32>,
	/// The maximum height of the window, or None if unconstrained
	pub max_height: Option<NonZeroU32>,
}

/// Information about a window, that is not fullscreened
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WindowInfo {
	pub default_x: i32,
	pub default_y: i32,
	pub resizable: Option<Resizable>,
	pub default_maximized: bool,
}

impl Default for WindowInfo {
	fn default() -> Self {
		Self {
			default_x: 100,
			default_y: 100,
			resizable: Some(Resizable::default()),
			default_maximized: false,
		}
	}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum WindowMode {
	Windowed(WindowInfo),
	// TODO support choosing a monitor
	BorderlessFullscreen, // TODO exclusive fullscreen
}

impl Default for WindowMode {
	fn default() -> Self {
		Self::Windowed(WindowInfo::default())
	}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
// TODO window icon
pub struct RenderWindowConfig<'a> {
	/// The width of the window, once initialized
	pub default_width: NonZeroU32,
	/// The height of the window, once initialized
	pub default_height: NonZeroU32,
	/// The window may be fullscreen
	pub mode: WindowMode,
	/// The title for the window
	pub title: &'a str,
	/// If true, a low-power device will be selected as the GPU, if possible
	pub low_power: bool,
	/// If true, Fifo mode is used to present frames. If false, then Mailbox or
	/// Immediate will be used if available. Otherwise, Fifo will be used.
	pub vsync: bool,
	/// The initial capacity of the instance buffer. The size will increase if
	/// it's not large enough. Increasing this value may improve performance
	/// towards the beginning, if a lot of instances are being created. For
	/// compatibility with older devices, it's recommended to keep this number
	/// below 150 thousand.
	pub instance_capacity: usize,
}

impl<'a> Default for RenderWindowConfig<'a> {
	fn default() -> Self {
		Self {
			default_width: NonZeroU32::new(640).unwrap(),
			default_height: NonZeroU32::new(480).unwrap(),
			mode: WindowMode::default(),
			title: "Alligator Game",
			low_power: true,
			vsync: true,
			instance_capacity: 500,
		}
	}
}

impl<'a> RenderWindowConfig<'a> {
	/// Based on the vsync settings, choose a presentation mode
	pub(crate) fn present_mode(
		vsync: bool,
		supported_modes: &[wgpu::PresentMode],
	) -> wgpu::PresentMode {
		if vsync {
			wgpu::PresentMode::Fifo
		} else if supported_modes.contains(&wgpu::PresentMode::Mailbox) {
			wgpu::PresentMode::Mailbox
		} else if supported_modes.contains(&wgpu::PresentMode::Immediate) {
			wgpu::PresentMode::Immediate
		} else {
			wgpu::PresentMode::Fifo
		}
	}

	/// Pick an alpha mode
	fn alpha_mode(supported_modes: &[wgpu::CompositeAlphaMode]) -> wgpu::CompositeAlphaMode {
		if supported_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
			wgpu::CompositeAlphaMode::PostMultiplied
		} else {
			wgpu::CompositeAlphaMode::Auto
		}
	}

	/// Create a `WindowBuilder` from the configuration given. This window is
	/// initially invisible and must later be made visible.
	pub(crate) fn to_window(&self) -> WindowBuilder {
		// start building the window
		let mut builder = WindowBuilder::new()
			.with_title(self.title)
			.with_visible(false)
			.with_inner_size(LogicalSize::new(
				self.default_width.get(),
				self.default_height.get(),
			));

		match self.mode {
			WindowMode::Windowed(window_info) => {
				builder = builder.with_maximized(window_info.default_maximized);

				if let Some(resizing_options) = window_info.resizable {
					if resizing_options.max_height.is_some() || resizing_options.max_width.is_some()
					{
						builder = builder.with_max_inner_size(LogicalSize::new(
							resizing_options.max_width.unwrap_or(NonZeroU32::MAX).get(),
							resizing_options.max_height.unwrap_or(NonZeroU32::MAX).get(),
						));
					}

					if resizing_options.min_height.is_some() || resizing_options.min_width.is_some()
					{
						builder = builder.with_min_inner_size(LogicalSize::new(
							resizing_options.min_width.unwrap_or(NonZeroU32::MAX).get(),
							resizing_options.min_height.unwrap_or(NonZeroU32::MAX).get(),
						));
					}
				} else {
					builder = builder.with_resizable(false);
				}

				// TODO clamp the position to the monitor's size
				builder = builder.with_position(LogicalPosition::new(
					window_info.default_x,
					window_info.default_y,
				));
			}
			WindowMode::BorderlessFullscreen => {
				builder = builder.with_fullscreen(Some(Fullscreen::Borderless(None)));
			}
		}

		builder
	}

	/// Gets a surface configuration out of the config.
	pub(crate) fn to_surface_configuration(
		&self,
		supported_present_modes: &[wgpu::PresentMode],
		supported_alpha_modes: &[wgpu::CompositeAlphaMode],
	) -> wgpu::SurfaceConfiguration {
		let present_mode = Self::present_mode(self.vsync, supported_present_modes);
		let alpha_mode = Self::alpha_mode(supported_alpha_modes);

		// configuration for the surface
		wgpu::SurfaceConfiguration {
			usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
			format: wgpu::TextureFormat::Bgra8Unorm,
			width: self.default_width.get(),
			height: self.default_height.get(),
			alpha_mode,
			present_mode,
			view_formats: vec![
				wgpu::TextureFormat::Bgra8Unorm,
				wgpu::TextureFormat::Bgra8UnormSrgb,
			],
		}
	}

	/// Get the power preference
	pub(crate) const fn power_preference(&self) -> wgpu::PowerPreference {
		if self.low_power {
			wgpu::PowerPreference::LowPower
		} else {
			wgpu::PowerPreference::HighPerformance
		}
	}
}