GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix(vk): a lost window surface ends the session instead of panicking
VkRenderer::new expect()ed every surface query, so a window requested over
a dead display connection took the whole process down -- cce-cloud's
daemon at logout, `No surface formats: ERROR_SURFACE_LOST_KHR`. Mesa's
Wayland WSI answers the surface queries with a roundtrip, so they are
the first thing to find out the compositor is gone.
- vk::SurfaceLost: the error for a failed surface call.
- VkRenderer::try_new returns it (surface creation, present support,
formats, swapchain build); `new` stays as the panicking wrapper for
tools that own their window outright.
- The runner ends the session as ConnectionLost on it: reconnect if the
compositor is still there, clean exit if not.
- Mid-session, a swapchain rebuild / acquire / present reporting the
surface lost latches surface_lost() and skips draws, logging once.
- attach_surface returns it; the menu popup closes on it.
Verified in a shadow: wl_surface opened, compositor killed, then
constructing -- `new` panics as before, `try_new` returns the error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 16 ++++++
src/backend/menu_popup.rs | 19 ++++++--
src/backend/window_runner.rs | 20 ++++++--
src/vk/core.rs | 90 ++++++++++++++++++++++++++--------
src/vk/mod.rs | 2 +-
src/vk/renderer.rs | 114 +++++++++++++++++++++++++++++++++++--------
6 files changed, 213 insertions(+), 48 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index ecc14f5..b2ea686 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -100,6 +100,22 @@ Key methods (see the trait def around `window_runner.rs:1450`):
The frame loop is demand-driven (single `redraw` dirty bool, gated by a Wayland frame-callback
vsync) — it idles correctly when nothing changes. Don't add per-frame I/O to the render hot path.
+### A lost surface ends the session; it does not panic (since 2026-09-25)
+
+`VkRenderer::try_new` returns `vk::SurfaceLost` when the display connection under the
+surface is already dead — Mesa's Wayland WSI answers the surface queries with a roundtrip,
+so `vkGetPhysicalDeviceSurfaceFormatsKHR` is the first call to find out, with
+`ERROR_SURFACE_LOST_KHR`. The runner ends that session as `ConnectionLost`: a reconnect if
+the compositor is still there, a clean exit if it is not. Mid-session, a swapchain
+rebuild, acquire or present that reports the surface lost latches `surface_lost()` and
+skips draws (one WARN) until the event loop sees the dead connection itself; the menu
+popup just closes. `VkRenderer::new` is the panicking wrapper, kept for tools that own
+their window outright (designer's `vk-smoke`) — **a client that can outlive its
+compositor calls `try_new`**. Found as cce-cloud's daemon panicking at logout on
+`No surface formats`: it had outlived a compositor and asked for a window over its
+connection. Reproduced by opening a `wl_surface`, killing the shadow compositor, then
+constructing: `new` panics, `try_new` returns the error.
+
### `renderer_init` — GPU handles do not survive a reconnect
A connection is one **session**. A Wayland transport cannot be repaired once it breaks,
diff --git a/src/backend/menu_popup.rs b/src/backend/menu_popup.rs
index 21ca19c..b995dfb 100644
--- a/src/backend/menu_popup.rs
+++ b/src/backend/menu_popup.rs
@@ -242,14 +242,27 @@ impl<A: Application> PopupHandler for EngineState<A> {
let (_, pw, ph) = Self::buffer_geometry(self.scale_factor, w, h);
let surface_ptr = mp.popup.wl_surface().id().as_ptr() as *mut std::ffi::c_void;
let display_ptr = self.display_ptr as *mut std::ffi::c_void;
- match self.menu_renderer.as_mut() {
- Some(r) if r.has_surface() => r.resize(pw, ph),
+ let attached = match self.menu_renderer.as_mut() {
+ Some(r) if r.has_surface() => {
+ r.resize(pw, ph);
+ Ok(())
+ }
Some(r) => unsafe { r.attach_surface(display_ptr, surface_ptr, pw, ph) },
None => {
let t = std::time::Instant::now();
- self.menu_renderer = Some(unsafe { VkRenderer::new(display_ptr, surface_ptr, pw, ph, 0.0) });
+ let made = unsafe { VkRenderer::try_new(display_ptr, surface_ptr, pw, ph, 0.0) };
log::debug!("[menu_popup] renderer created in {:?}", t.elapsed());
+ made.map(|r| self.menu_renderer = Some(r))
}
+ };
+ // A lost surface is the connection dying under the menu; the window's
+ // own event loop ends the session on it. Just drop the menu.
+ if let Err(lost) = attached {
+ log::warn!("[menu_popup] {lost}; closing the menu");
+ context_menu::hide();
+ self.close_menu_popup();
+ self.redraw = true;
+ return;
}
self.redraw = true;
self.render_menu_popup();
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 69f5586..797d3ab 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -3974,7 +3974,16 @@ pub struct EngineState<A: Application> {
}
impl<A: Application> EngineState<A> {
- pub fn init_gpu(&mut self, conn: &Connection, width_logical: f32, height_logical: f32) {
+ /// Build the window's renderer. A [`SurfaceLost`](crate::vk::SurfaceLost)
+ /// means the connection under the surface is already dead; the session
+ /// ends as a lost connection, which reconnects if the compositor is still
+ /// there and exits if it is not.
+ pub fn init_gpu(
+ &mut self,
+ conn: &Connection,
+ width_logical: f32,
+ height_logical: f32,
+ ) -> Result<(), crate::vk::SurfaceLost> {
let s = self.scale_factor as f32;
let pw = (width_logical * s) as u32;
let ph = (height_logical * s) as u32;
@@ -3987,8 +3996,7 @@ impl<A: Application> EngineState<A> {
let load_system_fonts = self.inner.as_ref().map_or(false, |a| a.load_system_fonts());
// Corner radius 0: runner apps tessellate their own rounded corners.
- let renderer =
- unsafe { VkRenderer::new(display_ptr, surface_ptr, pw, ph, 0.0) };
+ let renderer = unsafe { VkRenderer::try_new(display_ptr, surface_ptr, pw, ph, 0.0) }?;
self.font_system = Some(if load_system_fonts {
crate::create_font_system_with_system_fonts()
} else {
@@ -3997,6 +4005,7 @@ impl<A: Application> EngineState<A> {
self.renderer = Some(renderer);
self.logical_width = width_logical;
self.logical_height = height_logical;
+ Ok(())
}
/// Buffer scale and physical extent for a logical size under the current
@@ -6015,7 +6024,10 @@ fn run_session<'l, A: Application>(
// larger than the window frame on every side; geometry/input-region are
// published per-resize.
let rim = 2.0 * engine_state.inner.as_ref().unwrap().overflow_margin() as f32;
- engine_state.init_gpu(&conn, settings.width as f32 + rim, settings.height as f32 + rim);
+ if let Err(lost) = engine_state.init_gpu(&conn, settings.width as f32 + rim, settings.height as f32 + rim) {
+ log::error!("[window_runner] cannot create the renderer, ending session: {lost}");
+ return (engine_state.inner.take(), SessionEnd::ConnectionLost);
+ }
engine_state
.inner
.as_mut()
diff --git a/src/vk/core.rs b/src/vk/core.rs
index 0640939..17901e0 100644
--- a/src/vk/core.rs
+++ b/src/vk/core.rs
@@ -190,6 +190,33 @@ fn shared_instance() -> &'static SharedInstance {
})
}
+/// A Vulkan call on a window surface failed — in practice
+/// `ERROR_SURFACE_LOST_KHR`: the display connection under the surface is dead,
+/// because the compositor exited (a logout) or the transport broke. Mesa's
+/// Wayland WSI answers the surface queries with a roundtrip, so they are the
+/// first thing to find out.
+///
+/// That is the client's SESSION ending, not a renderer bug, so the window
+/// constructors and the swapchain path report it instead of panicking, and the
+/// caller ends the session the way it would for any other lost connection.
+/// Until 2026-09-25 each of these calls `expect`ed, and a daemon asked for a
+/// window over a dead connection took the whole process down at logout
+/// (cce-cloud, `No surface formats: ERROR_SURFACE_LOST_KHR`).
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SurfaceLost {
+ /// The Vulkan entry point that failed.
+ pub call: &'static str,
+ pub result: vk::Result,
+}
+
+impl std::fmt::Display for SurfaceLost {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "window surface lost ({}: {})", self.call, self.result)
+ }
+}
+
+impl std::error::Error for SurfaceLost {}
+
impl VkCore {
/// A core bound to a Wayland surface: the returned `vk::SurfaceKHR` is
/// created from the raw pointers and the chosen device supports presenting
@@ -198,12 +225,16 @@ impl VkCore {
/// # Safety
/// `display_ptr` and `surface_ptr` must be live `wl_display` / `wl_surface`
/// pointers that outlive the core and everything created from it.
+ ///
+ /// Fails with [`SurfaceLost`] when the surface cannot be created or no
+ /// device can be asked whether it presents to it — a dead display
+ /// connection, not a driver fault.
pub unsafe fn new_for_wayland_surface(
display_ptr: *mut c_void,
surface_ptr: *mut c_void,
- ) -> (Self, vk::SurfaceKHR) {
- let (core, surface) = Self::new_inner(Some((display_ptr, surface_ptr)));
- (core, surface.expect("surface requested but not created"))
+ ) -> Result<(Self, vk::SurfaceKHR), SurfaceLost> {
+ let (core, surface) = Self::new_inner(Some((display_ptr, surface_ptr)))?;
+ Ok((core, surface.expect("surface requested but not created")))
}
/// A new `VkSurfaceKHR` on another Wayland surface, from this core's
@@ -217,7 +248,7 @@ impl VkCore {
&self,
display_ptr: *mut c_void,
surface_ptr: *mut c_void,
- ) -> vk::SurfaceKHR {
+ ) -> Result<vk::SurfaceKHR, SurfaceLost> {
let shared = shared_instance();
let wayland_loader = ash::khr::wayland_surface::Instance::new(&shared.entry, &self.instance);
let surface = wayland_loader
@@ -227,7 +258,7 @@ impl VkCore {
.surface(surface_ptr),
None,
)
- .expect("Failed to create Wayland surface");
+ .map_err(|result| SurfaceLost { call: "vkCreateWaylandSurfaceKHR", result })?;
// The device was chosen for the FIRST surface's present support; a
// later surface on the same display is presentable from the same
// family on every driver this runs on, but say so if not.
@@ -238,18 +269,19 @@ impl VkCore {
{
log::warn!("[vk] queue family {} cannot present to the re-attached surface", self.queue_family);
}
- surface
+ Ok(surface)
}
/// A windowless core: no surface extensions, any graphics-capable device.
/// For offscreen rendering (thumbnails, previews) and compute.
pub fn new_headless() -> Self {
- unsafe { Self::new_inner(None).0 }
+ // Only a surface can be lost, and there is none here.
+ unsafe { Self::new_inner(None).expect("headless core cannot lose a surface").0 }
}
unsafe fn new_inner(
wayland: Option<(*mut c_void, *mut c_void)>,
- ) -> (Self, Option<vk::SurfaceKHR>) {
+ ) -> Result<(Self, Option<vk::SurfaceKHR>), SurfaceLost> {
// CCE_VK_DEVICE: "integrated" (the default), "discrete", or a device
// name substring. An explicit request also lifts a session-wide ICD
// pin (VK_DRIVER_FILES / VK_ICD_FILENAMES) for THIS process — the
@@ -275,23 +307,32 @@ impl VkCore {
// Instance-level loader; only usable when VK_KHR_surface was enabled.
let surface_loader = ash::khr::surface::Instance::new(entry, &instance);
- let surface = wayland.map(|(display_ptr, surface_ptr)| {
- let wayland_loader = ash::khr::wayland_surface::Instance::new(entry, &instance);
- wayland_loader
- .create_wayland_surface(
- &vk::WaylandSurfaceCreateInfoKHR::default()
- .display(display_ptr)
- .surface(surface_ptr),
- None,
+ let surface = match wayland {
+ Some((display_ptr, surface_ptr)) => {
+ let wayland_loader = ash::khr::wayland_surface::Instance::new(entry, &instance);
+ Some(
+ wayland_loader
+ .create_wayland_surface(
+ &vk::WaylandSurfaceCreateInfoKHR::default()
+ .display(display_ptr)
+ .surface(surface_ptr),
+ None,
+ )
+ .map_err(|result| SurfaceLost { call: "vkCreateWaylandSurfaceKHR", result })?,
)
- .expect("Failed to create Wayland surface")
- });
+ }
+ None => None,
+ };
// Physical device + queue family: graphics, plus present support when
// a surface exists. Prefer integrated (the toolkit's LowPower default)
// unless CCE_VK_DEVICE says otherwise; an unsatisfiable preference
// falls back to the default order rather than failing.
let mut candidates: Vec<(vk::PhysicalDevice, u32, i32)> = Vec::new();
+ // A present-support query that FAILED, as opposed to answering no:
+ // on a dead display connection every device fails it, and "no
+ // suitable device" would then misreport a lost surface.
+ let mut support_error: Option<vk::Result> = None;
for pd in instance
.enumerate_physical_devices()
.expect("No Vulkan physical devices")
@@ -302,7 +343,10 @@ impl VkCore {
let present = match surface {
Some(surface) => surface_loader
.get_physical_device_surface_support(pd, i as u32, surface)
- .unwrap_or(false),
+ .unwrap_or_else(|e| {
+ support_error = Some(e);
+ false
+ }),
None => true,
};
(graphics && present).then_some(i as u32)
@@ -342,6 +386,10 @@ impl VkCore {
}
}
candidates.sort_by_key(|&(_, _, rank)| rank);
+ if let (true, Some(surface), Some(result)) = (candidates.is_empty(), surface, support_error) {
+ surface_loader.destroy_surface(surface, None);
+ return Err(SurfaceLost { call: "vkGetPhysicalDeviceSurfaceSupportKHR", result });
+ }
let (physical_device, queue_family, _) = *candidates
.first()
.expect("No suitable Vulkan device found");
@@ -473,7 +521,7 @@ impl VkCore {
)
.expect("Failed to create command pool");
- (
+ Ok((
VkCore {
allocator: Some(allocator),
command_pool,
@@ -489,7 +537,7 @@ impl VkCore {
max_line_width,
},
surface,
- )
+ ))
}
}
diff --git a/src/vk/mod.rs b/src/vk/mod.rs
index e63ec0b..93154f2 100644
--- a/src/vk/mod.rs
+++ b/src/vk/mod.rs
@@ -47,7 +47,7 @@ mod scene;
mod text;
pub use compute::{workgroups, BindKind, Binding, ComputeDevice, Kernel, MAX_BINDINGS};
-pub use core::VkCore;
+pub use core::{SurfaceLost, VkCore};
pub use image::{
free_image, recycle_buffer, renderer_epoch, update_pixels, upload_pixels, upload_rgba, ImageQuad,
PixelFormat,
diff --git a/src/vk/renderer.rs b/src/vk/renderer.rs
index c4a28c6..219375a 100644
--- a/src/vk/renderer.rs
+++ b/src/vk/renderer.rs
@@ -17,6 +17,7 @@ use gpu_allocator::MemoryLocation;
use crate::engine::Vertex;
+use super::core::SurfaceLost;
use super::image::{ImageQuad, ImageStage};
use super::rt::{RtCamera, RtMaterial, RtStage, RtTriangle};
use super::scene::{MeshId, SceneDraw, SceneStage, Vertex3D};
@@ -279,6 +280,11 @@ pub struct VkRenderer {
swapchain_dirty: bool,
present_mode: vk::PresentModeKHR,
present_debug_count: u64,
+ /// Set when a surface call reports the surface lost (see [`SurfaceLost`]):
+ /// the display connection is dead, so every later draw is skipped until
+ /// a new surface is attached, rather than re-failing (and re-logging)
+ /// each frame while the caller's event loop finds out for itself.
+ surface_lost: bool,
// Declared last: everything above must be destroyed before the device/
// instance the core tears down in its own Drop.
@@ -440,9 +446,12 @@ pub(crate) fn flipped_viewport(extent: vk::Extent2D) -> vk::Viewport {
impl VkRenderer {
+ /// [`try_new`](Self::try_new) for a caller that owns its window outright
+ /// and has no session to end — a smoke test. Panics on a lost surface;
+ /// a client that can outlive its compositor wants `try_new`.
+ ///
/// # Safety
- /// `display_ptr` and `surface_ptr` must be live `wl_display` / `wl_surface`
- /// pointers that outlive the renderer (same contract as `WgpuAdapter::new`).
+ /// Same contract as [`try_new`](Self::try_new).
pub unsafe fn new(
display_ptr: *mut c_void,
surface_ptr: *mut c_void,
@@ -450,9 +459,28 @@ impl VkRenderer {
height: u32,
corner_radius_px: f32,
) -> Self {
+ Self::try_new(display_ptr, surface_ptr, width, height, corner_radius_px)
+ .unwrap_or_else(|e| panic!("{e}"))
+ }
+
+ /// A renderer presenting to `surface_ptr`, or [`SurfaceLost`] when the
+ /// display connection under it is already dead — which is what a window
+ /// requested as the compositor goes away gets. The caller should treat
+ /// that as its connection ending (the runner does), not retry here.
+ ///
+ /// # Safety
+ /// `display_ptr` and `surface_ptr` must be live `wl_display` / `wl_surface`
+ /// pointers that outlive the renderer.
+ pub unsafe fn try_new(
+ display_ptr: *mut c_void,
+ surface_ptr: *mut c_void,
+ width: u32,
+ height: u32,
+ corner_radius_px: f32,
+ ) -> Result<Self, SurfaceLost> {
let t_new = std::time::Instant::now();
let (mut core, surface) =
- super::core::VkCore::new_for_wayland_surface(display_ptr, surface_ptr);
+ super::core::VkCore::new_for_wayland_surface(display_ptr, surface_ptr)?;
log::debug!("[timing] VkCore::new_for_wayland_surface: {:?}", t_new.elapsed());
let t_rest = std::time::Instant::now();
// Locals over the core for the setup below (methods use self.core.*).
@@ -462,13 +490,21 @@ impl VkRenderer {
let physical_device = core.physical_device;
let min_uniform_align = core.min_uniform_align;
let surface_loader = core.surface_loader.clone();
- let allocator = core.allocator.as_mut().unwrap();
// Surface format: prefer sRGB (wgpu's get_default_config sorts sRGB first,
- // so this matches the colors the app renders today).
- let formats = surface_loader
+ // so this matches the colors the app renders today). The first query
+ // that talks to the compositor, so the one a dead connection fails.
+ let formats = match surface_loader
.get_physical_device_surface_formats(physical_device, surface)
- .expect("No surface formats");
+ {
+ Ok(formats) if !formats.is_empty() => formats,
+ Ok(_) => panic!("surface offers no formats"),
+ Err(result) => {
+ surface_loader.destroy_surface(surface, None);
+ return Err(SurfaceLost { call: "vkGetPhysicalDeviceSurfaceFormatsKHR", result });
+ }
+ };
+ let allocator = core.allocator.as_mut().unwrap();
let surface_format = formats
.iter()
.copied()
@@ -890,17 +926,20 @@ impl VkRenderer {
swapchain_dirty: false,
present_mode: vk::PresentModeKHR::FIFO,
present_debug_count: 0,
+ surface_lost: false,
core,
};
log::debug!("[timing] VkRenderer pipelines/stages: {:?}", t_rest.elapsed());
let t_swap = std::time::Instant::now();
- renderer.create_swapchain();
+ // On failure `renderer` drops here, and its Drop tears down everything
+ // built so far, surface included.
+ renderer.create_swapchain()?;
renderer.write_window_info();
// The swapchain may have settled on a different extent than requested;
// keep the backdrop targets in lockstep.
renderer.sync_backdrop_targets();
log::debug!("[timing] swapchain setup: {:?}", t_swap.elapsed());
- renderer
+ Ok(renderer)
}
/// The window-clip corner radius as the shaders consume it: the nominal
@@ -969,12 +1008,19 @@ impl VkRenderer {
}
}
- fn create_swapchain(&mut self) {
+ /// Build the swapchain for the current surface. Only the calls that ask
+ /// the surface can fail with [`SurfaceLost`]; everything after them is
+ /// device work and still panics as the bug it would be. A failure leaves
+ /// the previous swapchain (if any) in `self.swapchain` for Drop.
+ fn create_swapchain(&mut self) -> Result<(), SurfaceLost> {
unsafe {
let caps = self.core
.surface_loader
.get_physical_device_surface_capabilities(self.core.physical_device, self.surface)
- .expect("Failed to query surface capabilities");
+ .map_err(|result| SurfaceLost {
+ call: "vkGetPhysicalDeviceSurfaceCapabilitiesKHR",
+ result,
+ })?;
// Wayland reports "extent defined by the swapchain" (u32::MAX); use the
// size the configure events gave us.
@@ -1055,7 +1101,7 @@ impl VkRenderer {
.old_swapchain(old_swapchain),
None,
)
- .expect("Failed to create swapchain");
+ .map_err(|result| SurfaceLost { call: "vkCreateSwapchainKHR", result })?;
if old_swapchain != vk::SwapchainKHR::null() {
self.swapchain_loader.destroy_swapchain(old_swapchain, None);
}
@@ -1084,7 +1130,7 @@ impl VkRenderer {
let images = self
.swapchain_loader
.get_swapchain_images(self.swapchain)
- .expect("Failed to get swapchain images");
+ .map_err(|result| SurfaceLost { call: "vkGetSwapchainImagesKHR", result })?;
self.swapchain_images = images.clone();
let subresource_range = vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
@@ -1126,16 +1172,34 @@ impl VkRenderer {
);
}
}
+ Ok(())
}
- fn recreate_swapchain(&mut self) {
+ fn recreate_swapchain(&mut self) -> Result<(), SurfaceLost> {
unsafe {
let _ = self.core.device.device_wait_idle();
}
self.destroy_swapchain_resources();
- self.create_swapchain();
+ self.create_swapchain()?;
self.write_window_info();
self.sync_backdrop_targets();
+ Ok(())
+ }
+
+ /// Latch a lost surface: say so once, then skip draws until a new
+ /// surface is attached.
+ fn mark_surface_lost(&mut self, lost: SurfaceLost) {
+ if !self.surface_lost {
+ log::warn!("[vk] {lost}; skipping draws until the connection is replaced");
+ }
+ self.surface_lost = true;
+ }
+
+ /// Whether the surface has been reported lost (see [`SurfaceLost`]). A
+ /// caller with its own event loop can end its session on this rather
+ /// than wait for the connection error.
+ pub fn surface_lost(&self) -> bool {
+ self.surface_lost
}
/// Suspend the UI pass, copy the swapchain's frame-so-far into the blur
@@ -1483,13 +1547,15 @@ impl VkRenderer {
surface_ptr: *mut c_void,
width: u32,
height: u32,
- ) {
+ ) -> Result<(), SurfaceLost> {
if self.surface != vk::SurfaceKHR::null() {
self.detach_surface();
}
- self.surface = self.core.create_wayland_surface(display_ptr, surface_ptr);
+ self.surface = self.core.create_wayland_surface(display_ptr, surface_ptr)?;
+ self.surface_lost = false;
self.resize(width, height);
self.swapchain_dirty = true;
+ Ok(())
}
/// Whether a surface is attached — false between
@@ -1567,12 +1633,15 @@ impl VkRenderer {
/// top. Returns false if the frame was skipped (swapchain rebuild); the
/// caller just draws again next tick.
pub fn draw_frame_2d(&mut self, frame2d: Frame2D<'_>) -> bool {
- if self.surface == vk::SurfaceKHR::null() {
+ if self.surface == vk::SurfaceKHR::null() || self.surface_lost {
return false;
}
if self.swapchain_dirty {
self.swapchain_dirty = false;
- self.recreate_swapchain();
+ if let Err(lost) = self.recreate_swapchain() {
+ self.mark_surface_lost(lost);
+ return false;
+ }
if self.swapchain_dirty {
// The rebuild couldn't honor the requested extent (surface
// caps disagree, e.g. mid suspend/resume) — presenting it
@@ -1610,6 +1679,10 @@ impl VkRenderer {
self.swapchain_dirty = true;
return false;
}
+ Err(result @ vk::Result::ERROR_SURFACE_LOST_KHR) => {
+ self.mark_surface_lost(SurfaceLost { call: "vkAcquireNextImageKHR", result });
+ return false;
+ }
Err(e) => {
log::error!("acquire_next_image failed: {e:?}");
return false;
@@ -2101,6 +2174,9 @@ impl VkRenderer {
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
self.swapchain_dirty = true;
}
+ Err(result @ vk::Result::ERROR_SURFACE_LOST_KHR) => {
+ self.mark_surface_lost(SurfaceLost { call: "vkQueuePresentKHR", result });
+ }
Err(e) => log::error!("queue_present failed: {e:?}"),
}