diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 166 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/renderer.rs | 169 |
3 files changed, 184 insertions, 153 deletions
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1e8c43e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,166 @@ +use std::borrow::Cow; +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)] +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)] +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)] +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)] +// 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: Cow<'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, +} + +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".into(), + low_power: true, + vsync: true, + } + } +} + +impl<'a> RenderWindowConfig<'a> { + /// Create a `WindowBuilder` from the configuration given + pub(crate) fn to_window(&self) -> WindowBuilder { + // start building the window + let mut builder = WindowBuilder::new() + .with_title(self.title.as_ref()) + .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_modes: &[wgpu::PresentMode], + texture_format: wgpu::TextureFormat, + ) -> wgpu::SurfaceConfiguration { + let present_mode = if self.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 + }; + + // configuration for the surface + wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: texture_format, + width: self.default_width.get(), + height: self.default_height.get(), + present_mode, + } + } + + pub(crate) const fn power_preference(&self) -> wgpu::PowerPreference { + if self.low_power { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + } + } +} @@ -4,6 +4,8 @@ #![warn(clippy::nursery)] #![allow(clippy::module_name_repetitions)] +pub mod config; pub mod renderer; +pub use config::RenderWindowConfig; pub use renderer::Renderer; diff --git a/src/renderer.rs b/src/renderer.rs index c9c51bc..bf7226d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,13 +1,12 @@ -use std::{borrow::Cow, num::NonZeroU32}; - +use crate::config::RenderWindowConfig; use pollster::FutureExt; use thiserror::Error; use winit::{ - dpi::{LogicalPosition, LogicalSize, PhysicalSize}, + dpi::PhysicalSize, error::OsError, event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{Fullscreen, Window, WindowBuilder}, + window::Window, }; /// No device could be found which supports the given surface @@ -33,83 +32,6 @@ pub enum NewRendererError { WindowInitError(#[from] OsError), } -/// Describes how a window may be resized -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -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)] -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)] -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)] -// 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: Cow<'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, -} - -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".into(), - low_power: true, - vsync: true, - } - } -} - pub struct Renderer { surface: wgpu::Surface, device: wgpu::Device, @@ -126,57 +48,19 @@ impl Renderer { /// /// Returns a [`NoGpu`] error if no device could be detected that can /// display to the window + /// + /// # Panics + /// + /// This function **must** be called on the main thread, or else it may + /// panic on some platforms. // TODO make it possible to use without a window (ie, use a bitmap in memory as a surface) // TODO this function needs to be smaller - #[allow(clippy::missing_panics_doc)] pub fn new( - config: RenderWindowConfig, + config: &RenderWindowConfig, event_loop: &EventLoop<()>, ) -> Result<Self, NewRendererError> { - // start building the window - let mut builder = WindowBuilder::new() - .with_title(config.title) - .with_inner_size(LogicalSize::new( - config.default_width.get(), - config.default_height.get(), - )); - - match config.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))); - } - } - - let window = builder.build(event_loop)?; + // build the window + let window = config.to_window().build(event_loop)?; // the instance's main purpose is to create an adapter and a surface let instance = wgpu::Instance::new(wgpu::Backends::all()); @@ -184,11 +68,7 @@ impl Renderer { // the surface is the part of the screen we'll draw to let surface = unsafe { instance.create_surface(&window) }; - let power_preference = if config.low_power { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }; + let power_preference = config.power_preference(); // the adapter is the handle to the GPU let adapter = instance @@ -219,28 +99,11 @@ impl Renderer { .block_on() .unwrap(); - let present_mode = if config.vsync { - wgpu::PresentMode::Fifo - } else { - let modes = surface.get_supported_modes(&adapter); - - if modes.contains(&wgpu::PresentMode::Mailbox) { - wgpu::PresentMode::Mailbox - } else if modes.contains(&wgpu::PresentMode::Immediate) { - wgpu::PresentMode::Immediate - } else { - wgpu::PresentMode::Fifo - } - }; - // configuration for the surface - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface.get_supported_formats(&adapter)[0], - width: config.default_width.get(), - height: config.default_height.get(), - present_mode, - }; + let config = config.to_surface_configuration( + &surface.get_supported_modes(&adapter), + surface.get_supported_formats(&adapter)[0], + ); surface.configure(&device, &config); |
