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, /// The minimum height of the window, or None if unconstrained pub min_height: Option, /// The maximum width of the window, or None if unconstrained pub max_width: Option, /// The maximum height of the window, or None if unconstrained pub max_height: Option, } /// 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, 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], supported_texture_formats: Vec, ) -> 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: supported_texture_formats[0], width: self.default_width.get(), height: self.default_height.get(), alpha_mode, present_mode, view_formats: supported_texture_formats, } } /// Get the power preference pub(crate) const fn power_preference(&self) -> wgpu::PowerPreference { if self.low_power { wgpu::PowerPreference::LowPower } else { wgpu::PowerPreference::HighPerformance } } }