diff --git a/crates/kas-core/src/runner/common.rs b/crates/kas-core/src/runner/common.rs index 52c7fdab1..4a2f7082a 100644 --- a/crates/kas-core/src/runner/common.rs +++ b/crates/kas-core/src/runner/common.rs @@ -41,6 +41,26 @@ pub enum RunError { RequestError(#[from] winit::error::RequestError), } +/// Frame presentation outcomes +#[non_exhaustive] +#[derive(Debug)] +pub enum PresentResult { + /// Success + /// + /// Includes the time at which rendering finishes (excluding synchronisation delays). + Success(Instant), + /// The frame was dropped, e.g. due to timeout or being occluded. + Dropped, + /// The surface is outdated and should be reconfigured. + /// + /// (The frame may or may not have been presented.) + ReconfigureSurface, + /// A fatal error: the window should be closed. + /// + /// An error message should be logged by the method returning this result. + Fatal, +} + /// Enumeration of platforms /// /// Each option is compile-time enabled only if that platform is possible. @@ -230,5 +250,5 @@ pub trait WindowSurface { /// Present frame /// /// Return time at which render finishes - fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> Instant; + fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> PresentResult; } diff --git a/crates/kas-core/src/runner/mod.rs b/crates/kas-core/src/runner/mod.rs index bcc19eb4b..aa62dbb9b 100644 --- a/crates/kas-core/src/runner/mod.rs +++ b/crates/kas-core/src/runner/mod.rs @@ -27,7 +27,7 @@ pub use runner::{ClosedError, PreLaunchState, Proxy}; #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(docsrs, doc(cfg(internal_doc)))] -pub use common::{GraphicsFeatures, GraphicsInstance, RunError, WindowSurface}; +pub use common::{GraphicsFeatures, GraphicsInstance, PresentResult, RunError, WindowSurface}; #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(docsrs, doc(cfg(internal_doc)))] diff --git a/crates/kas-core/src/runner/window.rs b/crates/kas-core/src/runner/window.rs index 69b7718a3..e2727ef01 100644 --- a/crates/kas-core/src/runner/window.rs +++ b/crates/kas-core/src/runner/window.rs @@ -16,6 +16,7 @@ use crate::event::{ConfigCx, CursorIcon, EventState}; use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::SolveCache; use crate::messages::Erased; +use crate::runner::PresentResult; use crate::theme::{DrawCx, SizeCx, Theme, ThemeDraw, Window as _}; use crate::window::{BoxedWindow, Decorations, PopupDescriptor, WindowId, WindowWidget}; use crate::{ @@ -674,33 +675,47 @@ impl> Window { } else { shared.theme.clear_color() }; - let time3 = window + let result = window .surface .present(&mut shared.draw.as_mut().unwrap().draw, clear_color); - let text_dur_micros = take(&mut window.surface.common_mut().dur_text); - let end = Instant::now(); - log::trace!( - target: "kas_perf::wgpu::window", - "do_draw: {}μs ({}μs widgets, {}μs text, {}μs render, {}μs present)", - (end - start).as_micros(), - (time2 - start).as_micros(), - text_dur_micros.as_micros(), - (time3 - time2).as_micros(), - (end - time2).as_micros() - ); - - const SECOND: Duration = Duration::from_secs(1); - window.frame_count.1 += 1; - if window.frame_count.0 + SECOND <= end { - log::debug!( - "Window {:?}: {} frames in last second", - window.window_id, - window.frame_count.1 - ); - window.frame_count.0 = end; - window.frame_count.1 = 0; - } + match result { + PresentResult::Success(time3) => { + let text_dur_micros = take(&mut window.surface.common_mut().dur_text); + let end = Instant::now(); + log::trace!( + target: "kas_perf::wgpu::window", + "do_draw: {}μs ({}μs widgets, {}μs text, {}μs render, {}μs present)", + (end - start).as_micros(), + (time2 - start).as_micros(), + text_dur_micros.as_micros(), + (time3 - time2).as_micros(), + (end - time2).as_micros() + ); + + const SECOND: Duration = Duration::from_secs(1); + window.frame_count.1 += 1; + if window.frame_count.0 + SECOND <= end { + log::debug!( + "Window {:?}: {} frames in last second", + window.window_id, + window.frame_count.1 + ); + window.frame_count.0 = end; + window.frame_count.1 = 0; + } + } + PresentResult::Dropped => (), + PresentResult::ReconfigureSurface => { + let size: Size = window.surface_size().cast(); + window + .surface + .configure(&mut shared.draw.as_mut().unwrap().draw, size); + } + PresentResult::Fatal => { + self.ev_state.close_own_window(); + } + }; Ok(()) } diff --git a/crates/kas-soft/src/lib.rs b/crates/kas-soft/src/lib.rs index e0c107d07..07c3ca065 100644 --- a/crates/kas-soft/src/lib.rs +++ b/crates/kas-soft/src/lib.rs @@ -25,7 +25,8 @@ use kas::draw::{SharedState, WindowCommon, color}; use kas::geom::Size; use kas::runner::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use kas::runner::{ - GraphicsFeatures, GraphicsInstance, HasDisplayAndWindowHandle, RunError, WindowSurface, + GraphicsFeatures, GraphicsInstance, HasDisplayAndWindowHandle, PresentResult, RunError, + WindowSurface, }; /// Graphics context @@ -67,11 +68,10 @@ impl WindowSurface for Surface { self.size = size; self.draw.resize(size); - let width = NonZeroU32::new(size.0.cast()).expect("zero-sized surface"); - let height = NonZeroU32::new(size.1.cast()).expect("zero-sized surface"); - self.surface - .resize(width, height) - .expect("surface resize failed"); + let (w, h) = NonZeroU32::new(size.0.cast()) + .zip(NonZeroU32::new(size.1.cast())) + .expect("zero-sized surface"); + self.surface.resize(w, h).expect("surface resize failed"); true } @@ -86,11 +86,14 @@ impl WindowSurface for Surface { &mut self.draw.common } - fn present(&mut self, shared: &mut Shared, clear_color: color::Rgba) -> Instant { - let mut buffer = self - .surface - .buffer_mut() - .expect("failed to access surface buffer"); + fn present(&mut self, shared: &mut Shared, clear_color: color::Rgba) -> PresentResult { + let mut buffer = match self.surface.buffer_mut() { + Ok(b) => b, + Err(e) => { + log::error!("present surface: {e}"); + return PresentResult::Fatal; + } + }; let width: usize = self.size.0.cast(); let height: usize = self.size.1.cast(); debug_assert_eq!(width * height, buffer.len()); @@ -101,8 +104,13 @@ impl WindowSurface for Surface { self.draw.render(shared, &mut buffer, (width, height)); let pre_present = Instant::now(); - buffer.present().expect("failed to present buffer"); - pre_present + match buffer.present() { + Ok(()) => PresentResult::Success(pre_present), + Err(e) => { + log::warn!("failed to present buffer: {e}"); + PresentResult::Dropped + } + } } } diff --git a/crates/kas-wgpu/src/surface.rs b/crates/kas-wgpu/src/surface.rs index 475ad0687..4c4719904 100644 --- a/crates/kas-wgpu/src/surface.rs +++ b/crates/kas-wgpu/src/surface.rs @@ -10,7 +10,7 @@ use kas::cast::Cast; use kas::draw::color::Rgba; use kas::draw::{DrawIface, DrawSharedImpl, WindowCommon}; use kas::geom::Size; -use kas::runner::{HasDisplayAndWindowHandle, RunError, WindowSurface}; +use kas::runner::{HasDisplayAndWindowHandle, PresentResult, RunError, WindowSurface}; use std::time::Instant; use wgpu::{CurrentSurfaceTexture, PresentMode}; @@ -111,19 +111,21 @@ impl WindowSurface for Surface { &mut self.draw.common } - /// Return time at which render finishes - fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> Instant { - // TODO: review error handling - let frame = match self.surface.get_current_texture() { - CurrentSurfaceTexture::Success(frame) | CurrentSurfaceTexture::Suboptimal(frame) => { - frame + fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> PresentResult { + let (frame, outdated) = match self.surface.get_current_texture() { + CurrentSurfaceTexture::Success(frame) => (frame, false), + CurrentSurfaceTexture::Suboptimal(frame) => (frame, true), + CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => { + return PresentResult::Dropped; } - CurrentSurfaceTexture::Timeout - | CurrentSurfaceTexture::Occluded - | CurrentSurfaceTexture::Outdated - | CurrentSurfaceTexture::Lost - | CurrentSurfaceTexture::Validation => { - return Instant::now(); + CurrentSurfaceTexture::Outdated => return PresentResult::ReconfigureSurface, + CurrentSurfaceTexture::Lost => { + log::error!("present surface: surface has been lost"); + return PresentResult::Fatal; + } + CurrentSurfaceTexture::Validation => { + log::error!("present surface: validation error"); + return PresentResult::Fatal; } }; @@ -134,7 +136,12 @@ impl WindowSurface for Surface { let pre_present = Instant::now(); frame.present(); - pre_present + + if !outdated { + PresentResult::Success(pre_present) + } else { + PresentResult::ReconfigureSurface + } } }