use vulkan first and fallback to gl if driver errors

This commit is contained in:
2026-06-08 19:19:56 +02:00
parent f53340ad8e
commit a35a741b07
2 changed files with 126 additions and 32 deletions
+51 -13
View File
@@ -49,6 +49,10 @@ pub struct AppState {
width: u32,
height: u32,
pub exit: bool,
// Frame callback: set true when compositor says it's ready for next frame
draw_ready: bool,
last_frame: Instant,
}
// Safety: we only use the raw pointers in single-threaded context
@@ -98,6 +102,8 @@ pub fn run(
width: 0,
height: 0,
exit: false,
draw_ready: false,
last_frame: Instant::now(),
};
// One roundtrip to discover outputs.
@@ -147,36 +153,66 @@ pub fn run(
.context("failed to create renderer")?
});
// Render loop.
// Render loop driven by wl_surface.frame callbacks.
// The compositor stops sending callbacks when the surface is fully occluded,
// so the loop naturally idles to zero CPU when another app is fullscreen.
let frame_duration = if fps > 0 {
Some(Duration::from_nanos(1_000_000_000 / fps as u64))
} else {
None
};
// Register the first frame callback then render immediately. On Wayland
// the compositor only fires frame callbacks for commits that contain a
// buffer; an empty commit would stall the loop forever on many compositors.
// Rendering here attaches the first buffer and commits it together with the
// frame callback so the compositor has something to display right away.
{
let wl_surface = state.layer.as_ref().unwrap().wl_surface();
wl_surface.frame(&qh, wl_surface.clone());
}
if let Some(r) = state.renderer.as_mut() {
r.render();
}
conn.flush().ok();
loop {
// Non-blocking Wayland event dispatch.
event_queue.dispatch_pending(&mut state).context("event dispatch")?;
// Flush outgoing messages.
// Block the thread until the compositor sends us any event (including
// the frame callback). Zero CPU while occluded.
event_queue.blocking_dispatch(&mut state).context("event dispatch")?;
conn.flush().ok();
if state.exit {
break;
}
let frame_start = Instant::now();
if !state.draw_ready {
continue;
}
state.draw_ready = false;
// Optional fps cap: sleep the remainder of the frame budget.
if let Some(dur) = frame_duration {
let elapsed = state.last_frame.elapsed();
if elapsed < dur {
std::thread::sleep(dur - elapsed);
}
}
state.last_frame = Instant::now();
// Register the NEXT frame callback before present() so that the
// request is included in the same wl_surface.commit that wgpu
// issues inside present().
{
let wl_surface = state.layer.as_ref().unwrap().wl_surface();
wl_surface.frame(&qh, wl_surface.clone());
}
if let Some(r) = state.renderer.as_mut() {
r.render();
}
// Cap frame rate if requested.
if let Some(dur) = frame_duration {
let elapsed = frame_start.elapsed();
if elapsed < dur {
std::thread::sleep(dur - elapsed);
}
}
conn.flush().ok();
}
Ok(())
@@ -220,7 +256,9 @@ impl CompositorHandler for AppState {
fn frame(
&mut self, _: &Connection, _: &QueueHandle<Self>,
_: &wl_surface::WlSurface, _: u32,
) {}
) {
self.draw_ready = true;
}
fn surface_enter(
&mut self, _: &Connection, _: &QueueHandle<Self>,