diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e628fcb..f5f228ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Ability to select from available swapchain surface formats when creating an `EventLoop` +- Driver `surface` and `swapchain` modules (_and their types_) are now public API + +### Changed + +- Changed `KeyBuf` implementation functions to take values instead of borrows +- Updated `winit` to v0.29 +- Updated `egui` to v0.25 +- Updated `imgui-rs` to latest + [`main`](https://github.com/imgui-rs/imgui-rs/tree/ca05418cb449dadaabf014487c5c965908dfcbdd) ### Changed diff --git a/Cargo.toml b/Cargo.toml index 8f8ee2ea..5d82b499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ profiling = "1.0" raw-window-handle = "0.5" spirq = "=1.1.1" vk-sync = { version = "0.4.0", package = "vk-sync-fork" } # // SEE: https://github.com/gwihlidal/vk-sync-rs/pull/4 -> https://github.com/expenses/vk-sync-rs -winit = { version = "0.28" } +winit = { version = "0.29", features = ["rwh_05"] } [target.'cfg(target_os = "macos")'.dependencies] ash-molten = "0.15" diff --git a/contrib/screen-13-egui/Cargo.toml b/contrib/screen-13-egui/Cargo.toml index 8282797c..78d25a41 100644 --- a/contrib/screen-13-egui/Cargo.toml +++ b/contrib/screen-13-egui/Cargo.toml @@ -8,10 +8,10 @@ readme = "README.md" [dependencies] bytemuck = "1.13" -egui = { version = "0.22", features = [ +egui = { version = "0.25", features = [ "bytemuck" ] } -egui-winit = "0.22" +egui-winit = "0.25" inline-spirv = "0.1" screen-13 = { path = "../.." } screen-13-fx = { path = "../screen-13-fx" } diff --git a/contrib/screen-13-egui/src/lib.rs b/contrib/screen-13-egui/src/lib.rs index 8d3462c7..cb75bc3d 100644 --- a/contrib/screen-13-egui/src/lib.rs +++ b/contrib/screen-13-egui/src/lib.rs @@ -57,10 +57,29 @@ impl Egui { .unwrap(), ); + let ctx = egui::Context::default(); + let native_pixels_per_point = event_loop + .primary_monitor() + .map(|monitor| monitor.scale_factor() as f32); + let max_texture_side = Some( + device + .physical_device + .properties_v1_0 + .limits + .max_image_dimension2_d as usize, + ); + let egui_winit = egui_winit::State::new( + ctx.clone(), + egui::ViewportId::ROOT, + event_loop, + native_pixels_per_point, + max_texture_side, + ); + Self { ppl, - ctx: egui::Context::default(), - egui_winit: egui_winit::State::new(event_loop), + ctx, + egui_winit, textures: HashMap::default(), cache: HashPool::new(device), next_tex_id: 0, @@ -201,7 +220,7 @@ impl Egui { for egui::ClippedPrimitive { clip_rect, primitive, - } in self.ctx.tessellate(shapes) + } in self.ctx.tessellate(shapes, self.ctx.pixels_per_point()) { match primitive { egui::epaint::Primitive::Mesh(mesh) => { @@ -298,7 +317,7 @@ impl Egui { if let Event::WindowEvent { event, .. } = event { #[allow(unused_must_use)] { - self.egui_winit.on_event(&self.ctx, event); + self.egui_winit.on_window_event(window, event); } } } @@ -306,7 +325,7 @@ impl Egui { let full_output = self.ctx.run(raw_input, ui_fn); self.egui_winit - .handle_platform_output(window, &self.ctx, full_output.platform_output); + .handle_platform_output(window, full_output.platform_output); let deltas = full_output.textures_delta; diff --git a/contrib/screen-13-hot/examples/glsl.rs b/contrib/screen-13-hot/examples/glsl.rs index 9e844bb1..66a90125 100644 --- a/contrib/screen-13-hot/examples/glsl.rs +++ b/contrib/screen-13-hot/examples/glsl.rs @@ -14,7 +14,7 @@ fn main() -> Result<(), DisplayError> { pretty_env_logger::init(); let event_loop = EventLoop::new() - .desired_surface_format(|formats| EventLoopBuilder::linear_surface_format(formats).unwrap()) + .desired_surface_format(Surface::linear_or_default) .build()?; // Create a compute pipeline - the same as normal except for "Hot" prefixes and we provide the diff --git a/contrib/screen-13-imgui/Cargo.toml b/contrib/screen-13-imgui/Cargo.toml index 703b1c1f..7db766e3 100644 --- a/contrib/screen-13-imgui/Cargo.toml +++ b/contrib/screen-13-imgui/Cargo.toml @@ -9,9 +9,8 @@ readme = "README.md" [dependencies] bytemuck = "1.13" -# HACK: https://github.com/imgui-rs/imgui-rs/pull/716 -imgui = { git = "https://github.com/imgui-rs/imgui-rs", rev = "4d0c9ebc0faaea421e59e0a58f712abbb18c3093" } -imgui-winit-support = { git = "https://github.com/imgui-rs/imgui-rs", rev = "4d0c9ebc0faaea421e59e0a58f712abbb18c3093" } +imgui = { git = "https://github.com/imgui-rs/imgui-rs", rev = "ca05418cb449dadaabf014487c5c965908dfcbdd" } +imgui-winit-support = { git = "https://github.com/imgui-rs/imgui-rs", rev = "ca05418cb449dadaabf014487c5c965908dfcbdd" } inline-spirv = "0.1" screen-13 = { path = "../.." } diff --git a/contrib/screen-13-imgui/src/lib.rs b/contrib/screen-13-imgui/src/lib.rs index 52ef434f..56bfef7d 100644 --- a/contrib/screen-13-imgui/src/lib.rs +++ b/contrib/screen-13-imgui/src/lib.rs @@ -54,7 +54,7 @@ impl ImGui { pub fn draw( &mut self, dt: f32, - events: &[Event<'_, ()>], + events: &[Event<()>], window: &Window, render_graph: &mut RenderGraph, ui_func: impl FnOnce(&mut Ui), diff --git a/examples/egui.rs b/examples/egui.rs index f197cef3..51364c14 100644 --- a/examples/egui.rs +++ b/examples/egui.rs @@ -5,6 +5,7 @@ fn main() -> Result<(), DisplayError> { let event_loop = EventLoop::new() .desired_swapchain_image_count(2) + .desired_surface_format(Surface::linear_or_default) .window(|window| window.with_transparent(false)) .build()?; let mut egui = Egui::new(&event_loop.device, event_loop.as_ref()); @@ -44,7 +45,7 @@ fn main() -> Result<(), DisplayError> { .show(ui, |ui| { ui.add(egui::Button::new("Test")); ui.add(egui::Link::new("Test")); - ui.add(egui::Image::new(id, [50., 50.])); + ui.add(egui::Image::new((id, egui::Vec2::new(50., 50.)))); }); }, ); diff --git a/examples/imgui.rs b/examples/imgui.rs index d909748e..1c3833a4 100644 --- a/examples/imgui.rs +++ b/examples/imgui.rs @@ -10,7 +10,7 @@ fn main() -> Result<(), DisplayError> { // Screen 13 things we need for this demo let event_loop = EventLoop::new() - .desired_surface_format(|formats| EventLoopBuilder::linear_surface_format(formats).unwrap()) + .desired_surface_format(Surface::linear_or_default) .desired_swapchain_image_count(2) .build()?; let display = ComputePresenter::new(&event_loop.device)?; diff --git a/examples/ray_trace.rs b/examples/ray_trace.rs index a59e3acb..1aa3de7b 100644 --- a/examples/ray_trace.rs +++ b/examples/ray_trace.rs @@ -788,17 +788,17 @@ fn main() -> anyhow::Result<()> { const SPEED: f32 = 0.01f32; - if keyboard.is_pressed(&VirtualKeyCode::Left) { + if keyboard.is_pressed(KeyCode::ArrowLeft) { position[0] -= SPEED; - } else if keyboard.is_pressed(&VirtualKeyCode::Right) { + } else if keyboard.is_pressed(KeyCode::ArrowRight) { position[0] += SPEED; - } else if keyboard.is_pressed(&VirtualKeyCode::Up) { + } else if keyboard.is_pressed(KeyCode::ArrowUp) { position[2] -= SPEED; - } else if keyboard.is_pressed(&VirtualKeyCode::Down) { + } else if keyboard.is_pressed(KeyCode::ArrowDown) { position[2] += SPEED; - } else if keyboard.is_pressed(&VirtualKeyCode::Space) { + } else if keyboard.is_pressed(KeyCode::Space) { position[1] -= SPEED; - } else if keyboard.is_pressed(&VirtualKeyCode::LAlt) { + } else if keyboard.is_pressed(KeyCode::AltLeft) { position[1] += SPEED; } diff --git a/examples/rt_triangle.rs b/examples/rt_triangle.rs index 916556d6..cd930213 100644 --- a/examples/rt_triangle.rs +++ b/examples/rt_triangle.rs @@ -95,7 +95,7 @@ fn main() -> anyhow::Result<()> { pretty_env_logger::init(); let event_loop = EventLoop::new() - .desired_surface_format(|formats| EventLoopBuilder::linear_surface_format(formats).unwrap()) + .desired_surface_format(Surface::linear_or_default) .build()?; let mut pool = HashPool::new(&event_loop.device); diff --git a/examples/transitions.rs b/examples/transitions.rs index cde31a14..8cfa4024 100644 --- a/examples/transitions.rs +++ b/examples/transitions.rs @@ -12,7 +12,7 @@ fn main() -> anyhow::Result<()> { // Create Screen 13 things any similar program might need let event_loop = EventLoop::new() .window(|builder| builder.with_inner_size(LogicalSize::new(1024.0f64, 768.0f64))) - .desired_surface_format(|formats| EventLoopBuilder::linear_surface_format(formats).unwrap()) + .desired_surface_format(Surface::linear_or_default) .build()?; let display = ComputePresenter::new(&event_loop.device)?; let mut imgui = ImGui::new(&event_loop.device); diff --git a/examples/vsm_omni.rs b/examples/vsm_omni.rs index 9eb23856..835d2aaf 100644 --- a/examples/vsm_omni.rs +++ b/examples/vsm_omni.rs @@ -96,19 +96,19 @@ fn main() -> anyhow::Result<()> { update_keyboard(&mut keyboard, frame.events); // Hold spacebar to stop the light - if !keyboard.is_held(&VirtualKeyCode::Space) { + if !keyboard.is_held(KeyCode::Space) { elapsed += frame.dt; } // Hit F11 to enable borderless fullscreen - if keyboard.is_pressed(&VirtualKeyCode::F11) { + if keyboard.is_pressed(KeyCode::F11) { frame .window .set_fullscreen(Some(Fullscreen::Borderless(None))); } // Hit F12 to enable exclusive fullscreen - if keyboard.is_pressed(&VirtualKeyCode::F12) { + if keyboard.is_pressed(KeyCode::F12) { if let Some(monitor) = frame.window.current_monitor() { if let Some(video_mode) = monitor.video_modes().next() { frame @@ -119,7 +119,7 @@ fn main() -> anyhow::Result<()> { } // Hit Escape to cancel fullscreen or exit - if keyboard.is_pressed(&VirtualKeyCode::Escape) { + if keyboard.is_pressed(KeyCode::Escape) { if frame.window.fullscreen().is_some() { frame.window.set_fullscreen(None); } else { @@ -213,7 +213,7 @@ fn main() -> anyhow::Result<()> { ); // Hold tab to view a debug mode - if keyboard.is_held(&VirtualKeyCode::Tab) { + if keyboard.is_held(KeyCode::Tab) { frame .render_graph .begin_pass("DEBUG") diff --git a/src/driver/device.rs b/src/driver/device.rs index 1c7c100f..23fe686a 100644 --- a/src/driver/device.rs +++ b/src/driver/device.rs @@ -311,7 +311,9 @@ impl Device { ) { Ok(properties) => Ok(properties), Err(err) if err == vk::Result::ERROR_FORMAT_NOT_SUPPORTED => { - error!("Format not supported"); + // We don't log this condition because it is normal for unsupported + // formats to be checked - we use the result to inform callers they + // cannot use those formats. TODO: This may be better as an Option... Err(DriverError::Unsupported) } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 64f21e8a..65bcc9de 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -35,15 +35,14 @@ pub mod image; pub mod physical_device; pub mod ray_trace; pub mod shader; +pub mod surface; +pub mod swapchain; mod cmd_buf; mod descriptor_set; mod descriptor_set_layout; mod instance; mod render_pass; -mod surface; - -pub(crate) mod swapchain; pub use { self::{cmd_buf::CommandBuffer, instance::Instance}, diff --git a/src/driver/surface.rs b/src/driver/surface.rs index 6903b1a1..99e24f4f 100644 --- a/src/driver/surface.rs +++ b/src/driver/surface.rs @@ -1,3 +1,5 @@ +//! Native platform window surface types. + use { super::{device::Device, DriverError, Instance}, ash::vk, @@ -12,13 +14,18 @@ use { }, }; +/// Smart pointer handle to a [`vk::SurfaceKHR`] object. pub struct Surface { device: Arc, surface: vk::SurfaceKHR, } impl Surface { - pub fn new( + /// Create a surface from a raw window display handle. + /// + /// `device` must have been created with platform specific surface extensions enabled, acquired + /// through [`Device::create_display_window`]. + pub fn create( device: &Arc, display_window: &(impl HasRawDisplayHandle + HasRawWindowHandle), ) -> Result { @@ -34,7 +41,7 @@ impl Surface { ) } .map_err(|err| { - error!("unable to create surface: {err}"); + error!("Unable to create surface: {err}"); DriverError::Unsupported })?; @@ -42,6 +49,7 @@ impl Surface { Ok(Self { device, surface }) } + /// Lists the supported surface formats. pub fn formats(this: &Self) -> Result, DriverError> { unsafe { this.device @@ -50,12 +58,62 @@ impl Surface { .unwrap() .get_physical_device_surface_formats(*this.device.physical_device, this.surface) .map_err(|err| { - error!("unable to get surface formats: {err}"); + error!("Unable to get surface formats: {err}"); DriverError::Unsupported }) } } + + /// Helper function to automatically select the best UNORM format, if one is available. + pub fn linear(formats: &[vk::SurfaceFormatKHR]) -> Option { + formats + .iter() + .find(|&&vk::SurfaceFormatKHR { format, .. }| { + matches!( + format, + vk::Format::R8G8B8A8_UNORM | vk::Format::B8G8R8A8_UNORM + ) + }) + .copied() + } + + /// Helper function to automatically select the best UNORM format. + /// + /// **_NOTE:_** The default surface format is undefined, and although legal the results _may_ + /// not support presentation. You should prefer to use [`Surface::linear`] and fall back to + /// supported values manually. + pub fn linear_or_default(formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { + Self::linear(formats).unwrap_or_else(|| formats.first().copied().unwrap_or_default()) + } + + /// Helper function to automatically select the best sRGB format, if one is available. + pub fn srgb(formats: &[vk::SurfaceFormatKHR]) -> Option { + formats + .iter() + .find( + |&&vk::SurfaceFormatKHR { + color_space, + format, + }| { + matches!(color_space, vk::ColorSpaceKHR::SRGB_NONLINEAR) + && matches!( + format, + vk::Format::R8G8B8A8_SRGB | vk::Format::B8G8R8A8_SRGB + ) + }, + ) + .copied() + } + + /// Helper function to automatically select the best sRGB format. + /// + /// **_NOTE:_** The default surface format is undefined, and although legal the results _may_ + /// not support presentation. You should prefer to use [`Surface::srgb`] and fall back to + /// supported values manually. + pub fn srgb_or_default(formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { + Self::srgb(formats).unwrap_or_else(|| formats.first().copied().unwrap_or_default()) + } } impl Debug for Surface { diff --git a/src/driver/swapchain.rs b/src/driver/swapchain.rs index d29863a1..c144e9af 100644 --- a/src/driver/swapchain.rs +++ b/src/driver/swapchain.rs @@ -1,3 +1,5 @@ +//! Native window presentation types. + use { super::{ device::Device, @@ -10,6 +12,7 @@ use { std::{ops::Deref, slice, sync::Arc, thread::panicking, time::Duration}, }; +/// Provides the ability to present rendering results to a [`Surface`]. #[derive(Debug)] pub struct Swapchain { device: Arc, @@ -24,6 +27,8 @@ pub struct Swapchain { } impl Swapchain { + /// Prepares a [`vk::SwapchainKHR`] object which is lazily created after calling + /// [`acquire_next_image`][Self::acquire_next_image]. pub fn new( device: &Arc, surface: Surface, @@ -45,6 +50,8 @@ impl Swapchain { }) } + /// Gets the next available swapchain image which should be rendered to and then presented using + /// [`present_image`][Self::present_image]. #[profiling::function] pub fn acquire_next_image(&mut self) -> Result { if self.suboptimal { @@ -134,10 +141,13 @@ impl Swapchain { } } + /// Gets information about this swapchain. pub fn info(&self) -> SwapchainInfo { self.info } + /// Presents an image which has been previously acquired using + /// [`acquire_next_image`][Self::acquire_next_image]. #[profiling::function] pub fn present_image( &mut self, @@ -422,6 +432,9 @@ impl Swapchain { Ok(()) } + /// Sets information about this swapchain. + /// + /// Previously acquired swapchain images should be discarded after calling this function. pub fn set_info(&mut self, info: SwapchainInfo) { if self.info != info { self.info = info; @@ -467,12 +480,13 @@ impl Drop for Swapchain { } } +/// An opaque type representing a swapchain image. #[derive(Debug)] pub struct SwapchainImage { - pub acquired: vk::Semaphore, - pub image: Image, - pub idx: u32, - pub rendered: vk::Semaphore, + pub(crate) acquired: vk::Semaphore, + pub(crate) image: Image, + pub(crate) idx: u32, + pub(crate) rendered: vk::Semaphore, } impl Clone for SwapchainImage { @@ -494,7 +508,8 @@ impl Deref for SwapchainImage { } } -#[derive(Debug)] +/// Describes the condition of a swapchain. +#[derive(Clone, Copy, Debug, PartialEq)] pub enum SwapchainError { /// This frame is lost but more may be acquired later. DeviceLost, diff --git a/src/event_loop.rs b/src/event_loop.rs index db315224..544e2eed 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -11,18 +11,15 @@ use { pool::hash::HashPool, }, ash::vk, - log::{debug, info, trace, warn}, + log::{debug, error, info, trace, warn}, std::{ fmt::{Debug, Formatter}, - mem::take, sync::Arc, time::{Duration, Instant}, }, winit::{ event::{Event, WindowEvent}, - event_loop::ControlFlow, monitor::MonitorHandle, - platform::run_return::EventLoopExtRunReturn, window::{Fullscreen, Window, WindowBuilder}, }, }; @@ -74,6 +71,8 @@ impl EventLoop { { let mut events = Vec::new(); let mut will_exit = false; + let mut last_swapchain_err = None; + let mut run_result = Ok(()); // Use the same delta-time smoothing as Kajiya; but start it off with a reasonable // guess so the following updates are even smoother @@ -101,110 +100,151 @@ impl EventLoop { self.window.set_visible(true); - while !will_exit { - trace!("🟥🟩🟦 Event::RedrawRequested"); - profiling::scope!("Frame"); - - { - profiling::scope!("Event loop"); - self.event_loop.run_return(|event, _, control_flow| { - profiling::scope!("Window event"); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - will_exit = true; + self.event_loop + .run(|event, window| { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + window.exit(); + } + Event::WindowEvent { + event: WindowEvent::Focused(false), + .. + } => self.window.set_cursor_visible(true), + Event::AboutToWait => { + self.window.request_redraw(); + return; + } + _ => {} + } + + if !matches!( + event, + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } + ) { + events.push(event); + return; + } + + trace!("🟥🟩🟦 Event::RedrawRequested"); + profiling::scope!("Frame"); + + if !events.is_empty() { + trace!("received {} events", events.len(),); + } + + let now = Instant::now(); + + // Filter the frame time before passing it to the application and renderer. + // Fluctuations in frame rendering times cause stutter in animations, + // and time-dependent effects (such as motion blur). + // + // Should applications need unfiltered delta time, they can calculate + // it themselves, but it's good to pass the filtered time so users + // don't need to worry about it. + { + profiling::scope!("Calculate dt"); + + let dt_duration = now - last_frame; + last_frame = now; + + let dt_raw = dt_duration.as_secs_f32(); + dt_filtered = dt_filtered + (dt_raw - dt_filtered) / 10.0; + }; + + { + profiling::scope!("Update swapchain"); + + // Update the window size if it changes + let window_size = self.window.inner_size(); + let mut swapchain_info = self.swapchain.info(); + swapchain_info.width = window_size.width; + swapchain_info.height = window_size.height; + self.swapchain.set_info(swapchain_info); + } + + // Note: Errors when acquiring swapchain images are not considered fatal + match self.swapchain.acquire_next_image() { + Err(err) => { + if last_swapchain_err == Some(err) { + // Generally ignore repeated errors as the window may take some time to get + // back to a workable state + debug!("Unable to acquire swapchain image: {err:?}"); + } else { + // The error has changed - this may happen during some window events + warn!("Unable to acquire swapchain image: {err:?}"); + last_swapchain_err = Some(err); } - Event::WindowEvent { - event: WindowEvent::Focused(false), - .. - } => self.window.set_cursor_visible(true), - Event::MainEventsCleared => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Poll, } - events.extend(event.to_static()); - }); - } - - if !events.is_empty() { - trace!("received {} events", events.len(),); - } - - let now = Instant::now(); - - // Filter the frame time before passing it to the application and renderer. - // Fluctuations in frame rendering times cause stutter in animations, - // and time-dependent effects (such as motion blur). - // - // Should applications need unfiltered delta time, they can calculate - // it themselves, but it's good to pass the filtered time so users - // don't need to worry about it. - { - profiling::scope!("Calculate dt"); - - let dt_duration = now - last_frame; - last_frame = now; - - let dt_raw = dt_duration.as_secs_f32(); - dt_filtered = dt_filtered + (dt_raw - dt_filtered) / 10.0; - }; - - { - profiling::scope!("Update swapchain"); - - // Update the window size if it changes - let window_size = self.window.inner_size(); - let mut swapchain_info = self.swapchain.info(); - swapchain_info.width = window_size.width; - swapchain_info.height = window_size.height; - self.swapchain.set_info(swapchain_info); - } - - let swapchain_image = self.swapchain.acquire_next_image(); - if swapchain_image.is_err() { - events.clear(); + Ok(swapchain_image) => { + last_swapchain_err = None; + + let height = swapchain_image.info.height; + let width = swapchain_image.info.width; + let mut render_graph = RenderGraph::new(); + let swapchain_image = render_graph.bind_node(swapchain_image); + + { + profiling::scope!("Frame callback"); + + frame_fn(FrameContext { + device: &self.device, + dt: dt_filtered, + height, + render_graph: &mut render_graph, + events: &events, + swapchain_image, + width, + window: &self.window, + will_exit: &mut will_exit, + }); + + if will_exit { + window.exit(); + return; + } + } - continue; - } - - let swapchain_image = swapchain_image.unwrap(); - let height = swapchain_image.info.height; - let width = swapchain_image.info.width; - let mut render_graph = RenderGraph::new(); - let swapchain_image = render_graph.bind_node(swapchain_image); - - { - profiling::scope!("Frame callback"); - - frame_fn(FrameContext { - device: &self.device, - dt: dt_filtered, - height, - render_graph: &mut render_graph, - events: take(&mut events).as_slice(), - swapchain_image, - width, - window: &self.window, - will_exit: &mut will_exit, - }); - } - - let elapsed = Instant::now() - now; - - trace!( - "✅✅✅ render graph construction: {} μs ({}% load)", - elapsed.as_micros(), - ((elapsed.as_secs_f32() / refresh_rate) * 100.0) as usize, - ); + let elapsed = Instant::now() - now; - let swapchain_image = self.display.resolve_image(render_graph, swapchain_image)?; - self.swapchain.present_image(swapchain_image, 0, 0); + trace!( + "✅✅✅ render graph construction: {} μs ({}% load)", + elapsed.as_micros(), + ((elapsed.as_secs_f32() / refresh_rate) * 100.0) as usize, + ); - profiling::finish_frame!(); - } + match self.display.resolve_image(render_graph, swapchain_image) { + Err(err) => { + // This is considered a fatal error and will be thrown back to the + // caller + error!("Unable to resolve swapchain image: {err}"); + run_result = Err(err); + window.exit(); + } + Ok(swapchain_image) => { + self.window.pre_present_notify(); + self.swapchain.present_image(swapchain_image, 0, 0); + + profiling::finish_frame!(); + } + } + } + } + + events.clear(); + }) + .map_err(|err| { + error!("Unable to run event loop: {err}"); + + DisplayError::Driver(DriverError::Unsupported) + })?; + + run_result?; self.window.set_visible(false); @@ -250,7 +290,7 @@ impl Default for EventLoopBuilder { Self { cmd_buf_count: 5, device_info: DeviceInfoBuilder::default(), - event_loop: winit::event_loop::EventLoop::new(), + event_loop: winit::event_loop::EventLoop::new().expect("Unable to build event loop"), resolver_pool: None, surface_format_fn: None, swapchain_info: SwapchainInfoBuilder::default(), @@ -459,7 +499,7 @@ impl EventLoopBuilder { .unwrap_or_else(|| Box::new(HashPool::new(&device))); let display = Display::new(&device, pool, self.cmd_buf_count, queue_family_index)?; - let surface = Surface::new(&device, &window)?; + let surface = Surface::create(&device, &window)?; let surface_formats = Surface::formats(&surface)?; if surface_formats.is_empty() { @@ -475,13 +515,9 @@ impl EventLoopBuilder { ); } - let surface_format_fn = self.surface_format_fn.unwrap_or_else(|| { - Box::new(|formats| { - Self::srgb_surface_format(formats) - .or_else(|| Self::linear_surface_format(formats)) - .unwrap_or(formats[0]) - }) - }); + let surface_format_fn = self + .surface_format_fn + .unwrap_or_else(|| Box::new(Surface::srgb_or_default)); let surface_format = surface_format_fn(&surface_formats); let swapchain = Swapchain::new( &device, @@ -504,36 +540,4 @@ impl EventLoopBuilder { window, }) } - - /// Helper function to automatically select the best UNORM format. - pub fn linear_surface_format(formats: &[vk::SurfaceFormatKHR]) -> Option { - formats - .iter() - .find(|&&vk::SurfaceFormatKHR { format, .. }| { - matches!( - format, - vk::Format::R8G8B8A8_UNORM | vk::Format::B8G8R8A8_UNORM - ) - }) - .copied() - } - - /// Helper function to automatically select the best sRGB format. - pub fn srgb_surface_format(formats: &[vk::SurfaceFormatKHR]) -> Option { - formats - .iter() - .find( - |&&vk::SurfaceFormatKHR { - color_space, - format, - }| { - matches!(color_space, vk::ColorSpaceKHR::SRGB_NONLINEAR) - && matches!( - format, - vk::Format::R8G8B8A8_SRGB | vk::Format::B8G8R8A8_SRGB - ) - }, - ) - .copied() - } } diff --git a/src/frame.rs b/src/frame.rs index 1ddfab17..d553648d 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -30,7 +30,7 @@ pub struct FrameContext<'a> { pub dt: f32, /// A slice of events that have occurred since the previous frame. - pub events: &'a [Event<'a, ()>], + pub events: &'a [Event<()>], /// The height, in pixels, of the current frame. pub height: u32, diff --git a/src/input/key_buf.rs b/src/input/key_buf.rs index 59a722aa..80633181 100644 --- a/src/input/key_buf.rs +++ b/src/input/key_buf.rs @@ -1,4 +1,7 @@ -use winit::event::{ElementState, Event, VirtualKeyCode, WindowEvent}; +use winit::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + keyboard::{KeyCode, PhysicalKey}, +}; /// A container for Window-based keyboard input events. /// @@ -7,9 +10,9 @@ use winit::event::{ElementState, Event, VirtualKeyCode, WindowEvent}; #[derive(Clone, Debug, Default)] pub struct KeyBuf { chars: Vec, - held: Vec, - pressed: Vec, - released: Vec, + held: Vec, + pressed: Vec, + released: Vec, } impl KeyBuf { @@ -41,78 +44,86 @@ impl KeyBuf { } /// Handles a single event. - pub fn handle_event(&mut self, event: &Event<'_, ()>) -> bool { + pub fn handle_event(&mut self, event: &Event<()>) -> bool { match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { input, .. } if input.virtual_keycode.is_some() => { - let key = input.virtual_keycode.unwrap(); - match input.state { - ElementState::Pressed => { - if let Err(idx) = self.pressed.binary_search(&key) { - self.pressed.insert(idx, key); - } - - if let Err(idx) = self.held.binary_search(&key) { - self.held.insert(idx, key); - } + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: PhysicalKey::Code(key), + state, + text, + .. + }, + .. + }, + .. + } => { + match state { + ElementState::Pressed => { + if let Err(idx) = self.pressed.binary_search(key) { + self.pressed.insert(idx, *key); } - ElementState::Released => { - if let Ok(idx) = self.held.binary_search(&key) { - self.held.remove(idx); - } - - if let Err(idx) = self.released.binary_search(&key) { - self.released.insert(idx, key); - } + + if let Err(idx) = self.held.binary_search(key) { + self.held.insert(idx, *key); } } + ElementState::Released => { + if let Ok(idx) = self.held.binary_search(key) { + self.held.remove(idx); + } - true + if let Err(idx) = self.released.binary_search(key) { + self.released.insert(idx, *key); + } + } } - WindowEvent::ReceivedCharacter(char) => { - self.chars.push(*char); - true + if let Some(text) = text { + self.chars.extend(text.as_str().chars()); } - _ => false, - }, + + true + } _ => false, } } /// Returns `true` if the given key has been pressed since the last frame or held for multiple /// frames. - pub fn is_down(&self, key: VirtualKeyCode) -> bool { - self.is_held(&key) || self.is_pressed(&key) + pub fn is_down(&self, key: KeyCode) -> bool { + self.is_held(key) || self.is_pressed(key) } /// Returns `true` if the given key has been pressed for multiple frames. - pub fn is_held(&self, key: &VirtualKeyCode) -> bool { - self.held.binary_search(key).is_ok() + pub fn is_held(&self, key: KeyCode) -> bool { + self.held.binary_search(&key).is_ok() } /// Returns `true` if the given key has been pressed since the last frame. - pub fn is_pressed(&self, key: &VirtualKeyCode) -> bool { - self.pressed.binary_search(key).is_ok() + pub fn is_pressed(&self, key: KeyCode) -> bool { + self.pressed.binary_search(&key).is_ok() } /// Returns `true` if the given key has been released since the last frame. - pub fn is_released(&self, key: &VirtualKeyCode) -> bool { - self.released.binary_search(key).is_ok() + pub fn is_released(&self, key: KeyCode) -> bool { + self.released.binary_search(&key).is_ok() } /// Returns an iterator of keys pressed for multiple frames. - pub fn held(&self) -> impl Iterator + '_ { + pub fn held(&self) -> impl Iterator + '_ { self.held.iter().copied() } /// Returns an iterator of keys pressed since the last frame. - pub fn pressed(&self) -> impl Iterator + '_ { + pub fn pressed(&self) -> impl Iterator + '_ { self.pressed.iter().copied() } /// Returns an iterator of keys released since the last frame. - pub fn released(&self) -> impl Iterator + '_ { + pub fn released(&self) -> impl Iterator + '_ { self.released.iter().copied() } } diff --git a/src/input/key_map.rs b/src/input/key_map.rs index 887ca7e5..6a6dd015 100644 --- a/src/input/key_map.rs +++ b/src/input/key_map.rs @@ -1,14 +1,14 @@ use { super::KeyBuf, std::{fmt::Debug, time::Instant}, - winit::event::VirtualKeyCode, + winit::keyboard::KeyCode, }; /// A binding between key and axis activation values. #[derive(Clone, Debug)] pub struct Binding { axis: A, - key: VirtualKeyCode, + key: KeyCode, multiplier: f32, activation: f32, activation_time: f32, @@ -17,7 +17,7 @@ pub struct Binding { impl Binding { pub const DEFAULT_ACTIVATION_TIME: f32 = 0.15; - pub fn new(key: VirtualKeyCode, axis: A, multiplier: f32) -> Self { + pub fn new(key: KeyCode, axis: A, multiplier: f32) -> Self { Self { axis, key, @@ -64,7 +64,7 @@ where } /// Binds a key to an axis. - pub fn bind(self, key: VirtualKeyCode, axis: A, multiplier: f32) -> Self { + pub fn bind(self, key: KeyCode, axis: A, multiplier: f32) -> Self { self.binding(Binding::new(key, axis, multiplier)) } @@ -85,14 +85,14 @@ where for binding in &mut self.bindings { if binding.activation_time > 1e-10 { - let change = if keyboard.is_pressed(&binding.key) { + let change = if keyboard.is_pressed(binding.key) { dt } else { -dt }; binding.activation = (binding.activation + change / binding.activation_time).clamp(0.0, 1.0); - } else if keyboard.is_pressed(&binding.key) { + } else if keyboard.is_pressed(binding.key) { binding.activation = 1.0; } else { binding.activation = 0.0; diff --git a/src/input/mod.rs b/src/input/mod.rs index cb3a40d7..00392c92 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -16,7 +16,7 @@ pub use self::{ pub fn update_input<'a>( keyboard: &'a mut KeyBuf, mouse: &'a mut MouseBuf, - events: impl IntoIterator>, + events: impl IntoIterator>, ) { update_input_opt(Some(keyboard), Some(mouse), events); } @@ -24,23 +24,20 @@ pub fn update_input<'a>( /// Handles keyboard `Event`s while updating the provided buffer. pub fn update_keyboard<'a>( keyboard: &'a mut KeyBuf, - events: impl IntoIterator>, + events: impl IntoIterator>, ) { update_input_opt(Some(keyboard), None, events); } /// Handles mouse `Event`s while updating the provided buffer. -pub fn update_mouse<'a>( - mouse: &'a mut MouseBuf, - events: impl IntoIterator>, -) { +pub fn update_mouse<'a>(mouse: &'a mut MouseBuf, events: impl IntoIterator>) { update_input_opt(None, Some(mouse), events); } fn update_input_opt<'a>( mut keyboard: Option<&'a mut KeyBuf>, mut mouse: Option<&'a mut MouseBuf>, - events: impl IntoIterator>, + events: impl IntoIterator>, ) { if let Some(keyboard) = keyboard.as_mut() { keyboard.update(); diff --git a/src/input/mouse_buf.rs b/src/input/mouse_buf.rs index e76a9398..737d0f8c 100644 --- a/src/input/mouse_buf.rs +++ b/src/input/mouse_buf.rs @@ -7,6 +7,8 @@ const fn mouse_button_idx(button: MouseButton) -> u16 { MouseButton::Left => 0, MouseButton::Right => 1, MouseButton::Middle => 2, + MouseButton::Back => 3, + MouseButton::Forward => 4, MouseButton::Other(idx) => idx, } } @@ -17,6 +19,8 @@ const fn idx_mouse_button(button: u16) -> MouseButton { 0 => MouseButton::Left, 1 => MouseButton::Right, 2 => MouseButton::Middle, + 3 => MouseButton::Back, + 4 => MouseButton::Forward, idx => MouseButton::Other(idx), } } @@ -71,7 +75,7 @@ impl MouseBuf { } /// Handles a single event. - pub fn handle_event(&mut self, event: &Event<'_, ()>) -> bool { + pub fn handle_event(&mut self, event: &Event<()>) -> bool { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CursorMoved { position, .. } => { diff --git a/src/lib.rs b/src/lib.rs index 52bc9cf5..b62b2870 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -365,6 +365,10 @@ pub mod prelude { SamplerInfo, SamplerInfoBuilder, Shader, ShaderBuilder, ShaderCode, SpecializationInfo, }, + surface::Surface, + swapchain::{ + Swapchain, SwapchainError, SwapchainImage, SwapchainInfo, SwapchainInfoBuilder, + }, AccessType, CommandBuffer, DriverError, Instance, ResolveMode, }, event_loop::{EventLoop, EventLoopBuilder, FullscreenMode}, @@ -386,8 +390,10 @@ pub mod prelude { ash::vk, log::{debug, error, info, logger, trace, warn}, // Everyone wants a log winit::{ + self, dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event::{Event, VirtualKeyCode, WindowEvent}, + event::{Event, WindowEvent}, + keyboard::KeyCode, monitor::{MonitorHandle, VideoMode}, window::{ BadIcon, CursorGrabMode, CursorIcon, Fullscreen, Icon, Theme, UserAttentionType,