summaryrefslogtreecommitdiff
path: root/src/renderer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer.rs')
-rw-r--r--src/renderer.rs150
1 files changed, 123 insertions, 27 deletions
diff --git a/src/renderer.rs b/src/renderer.rs
index f937183..879a447 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,10 +1,12 @@
use std::num::NonZeroU32;
+use pollster::FutureExt;
use thiserror::Error;
use winit::{
- dpi::{LogicalSize, PhysicalSize},
+ dpi::{LogicalPosition, LogicalSize, PhysicalSize},
error::OsError,
- event_loop::EventLoop,
+ event::{Event, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
window::{Fullscreen, Window, WindowBuilder},
};
@@ -31,22 +33,39 @@ pub enum NewRendererError {
WindowInitError(#[from] OsError),
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// 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 resizability: Option<Resizable>,
+ 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),
@@ -54,25 +73,50 @@ pub enum WindowMode {
BorderlessFullscreen, // TODO exclusive fullscreen
}
+impl Default for WindowMode {
+ fn default() -> Self {
+ Self::Windowed(WindowInfo::default())
+ }
+}
+
#[derive(Clone, Debug, PartialEq, Eq)]
// TODO to consider: don't allow the x and y to be too big
// TODO window icon
-pub struct RendererConfig {
+pub struct RenderWindowConfig {
+ /// The width of the window, once initialized
pub default_width: NonZeroU32,
+ /// The height of the window, once initialized
pub default_height: NonZeroU32,
- pub size: WindowMode,
- pub title: Option<Box<str>>,
+ /// The window may be fullscreen
+ pub mode: WindowMode,
+ /// The title for the window
+ pub title: Box<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 Default for RenderWindowConfig {
+ fn default() -> Self {
+ Self {
+ default_width: NonZeroU32::new(480).unwrap(),
+ default_height: NonZeroU32::new(640).unwrap(),
+ mode: WindowMode::default(),
+ title: "Alligator Game".into(),
+ low_power: true,
+ vsync: true,
+ }
+ }
+}
+
pub struct Renderer {
surface: wgpu::Surface,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
window: Window,
- event_loop: EventLoop<()>,
}
// TODO make this more complete
@@ -84,22 +128,23 @@ impl Renderer {
/// Returns a [`NoGpu`] error if no device could be detected that can
/// display to the window
// TODO make it possible to use without winit (ie, use a bitmap in memory as a surface)
- // TODO make PowerPreference a configuration option
- // TODO check for zero size windows
#[allow(clippy::missing_panics_doc)]
- pub async fn new(config: RendererConfig) -> Result<Self, NewRendererError> {
+ pub fn new(
+ config: RenderWindowConfig,
+ event_loop: &EventLoop<()>,
+ ) -> Result<Self, NewRendererError> {
let mut builder = WindowBuilder::new()
- .with_title(config.title.unwrap_or_else(|| "Alligator Game".into()))
+ .with_title(config.title)
.with_inner_size(LogicalSize::new(
config.default_width.get(),
config.default_height.get(),
));
- match config.size {
- WindowMode::Windowed(size) => {
- builder = builder.with_maximized(size.default_maximized);
+ match config.mode {
+ WindowMode::Windowed(window_info) => {
+ builder = builder.with_maximized(window_info.default_maximized);
- if let Some(resizing_options) = size.resizability {
+ 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(
@@ -118,14 +163,19 @@ impl Renderer {
} 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 event_loop = EventLoop::new();
- let window = builder.build(&event_loop)?;
+ let window = builder.build(event_loop)?;
// the instance's main purpose is to create an adapter and a surface
let instance = wgpu::Instance::new(wgpu::Backends::all());
@@ -147,7 +197,7 @@ impl Renderer {
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
- .await;
+ .block_on();
let adapter = adapter.or_else(|| {
instance
.enumerate_adapters(wgpu::Backends::all())
@@ -166,16 +216,30 @@ impl Renderer {
},
None,
)
- .await
+ .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], // TODO make this configurable
+ format: surface.get_supported_formats(&adapter)[0],
width: config.default_width.get(),
height: config.default_height.get(),
- present_mode: wgpu::PresentMode::Mailbox, // TODO make this configurable
+ present_mode,
};
surface.configure(&device, &config);
@@ -186,16 +250,19 @@ impl Renderer {
queue,
config,
window,
- event_loop,
})
}
- pub const fn size(&self) -> PhysicalSize<u32> {
- PhysicalSize::new(self.config.width, self.config.height)
+ /// Get the current logical size of the window. This adapts to the
+ /// window's scale factor
+ fn logical_size(&self) -> LogicalSize<u32> {
+ self.window
+ .inner_size()
+ .to_logical(self.window.scale_factor())
}
// TODO return error for zero-sized windows
- pub fn resize(&mut self, size: PhysicalSize<u32>) {
+ fn resize_renderer(&mut self, size: PhysicalSize<u32>) {
if size.width > 0 && size.height > 0 {
self.config.height = size.height;
self.config.width = size.width;
@@ -210,7 +277,7 @@ impl Renderer {
/// A number of problems could occur here. A timeout could occur while
/// trying to acquire the next frame. There may also be no more memory left
/// that can be used for the new frame.
- pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
+ fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
// the new texture we can render to
let output = self.surface.get_current_texture()?;
let view = output
@@ -247,4 +314,33 @@ impl Renderer {
Ok(())
}
+
+ pub fn run(mut self, event_loop: EventLoop<()>) {
+ event_loop.run(move |event, _, control_flow| match event {
+ Event::WindowEvent { window_id, event } => {
+ if window_id == self.window.id() {
+ match event {
+ WindowEvent::Resized(size) => {
+ if size.width > 0 && size.height > 0 {
+ self.config.height = size.height;
+ self.config.width = size.width;
+ self.surface.configure(&self.device, &self.config);
+ }
+ }
+ WindowEvent::CloseRequested => *control_flow = ControlFlow::ExitWithCode(0),
+ _ => (),
+ }
+ }
+ }
+ Event::RedrawRequested(window_id) => {
+ if window_id == self.window.id() {
+ _ = self.render();
+ }
+ }
+ Event::MainEventsCleared => {
+ self.window.request_redraw();
+ }
+ _ => {}
+ })
+ }
}