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
+75 -19
View File
@@ -117,7 +117,7 @@ impl Renderer {
));
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::GL,
backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: Default::default(),
backend_options: Default::default(),
@@ -131,27 +131,83 @@ impl Renderer {
})
.context("failed to create wgpu surface")?;
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&wgpu_surface),
force_fallback_adapter: false,
}))
.context("no suitable wgpu adapter found")?;
// Build a priority-ordered candidate list: Vulkan discrete first, then
// Vulkan integrated, then GL. We try each in turn and fall back on
// failure so that Vulkan is used wherever it works (NVIDIA, AMD, etc.)
// while broken Vulkan drivers (e.g. Mesa V3DV on Raspberry Pi, which
// advertises features it can't actually enable) are skipped silently.
let mut candidates: Vec<wgpu::Adapter> = {
let all: Vec<wgpu::Adapter> = pollster::block_on(
instance.enumerate_adapters(wgpu::Backends::all()),
)
.into_iter()
.filter(|a| a.is_surface_supported(&wgpu_surface))
.collect();
let mut vulkan: Vec<_> = all
.iter()
.filter(|a| a.get_info().backend == wgpu::Backend::Vulkan)
.cloned()
.collect();
// Discrete GPUs first within the Vulkan set.
vulkan.sort_by_key(|a| {
(a.get_info().device_type != wgpu::DeviceType::DiscreteGpu) as u8
});
let gl: Vec<_> = all
.iter()
.filter(|a| a.get_info().backend == wgpu::Backend::Gl)
.cloned()
.collect();
vulkan.into_iter().chain(gl).collect()
};
if candidates.is_empty() {
anyhow::bail!("no suitable wgpu adapter found");
}
let device_desc = wgpu::DeviceDescriptor {
label: Some("gpupaper"),
required_features: wgpu::Features::empty(),
// downlevel_defaults is safe on both GL/GLES and Vulkan.
required_limits: wgpu::Limits::downlevel_defaults(),
memory_hints: wgpu::MemoryHints::default(),
experimental_features: Default::default(),
trace: Default::default(),
};
// Try each candidate. Some Vulkan drivers (e.g. Mesa V3DV) panic
// inside vkCreateDevice with VK_ERROR_FEATURE_NOT_PRESENT despite
// advertising the feature — catch that and move to the next adapter.
let (adapter, device, queue) = candidates
.drain(..)
.find_map(|adapter| {
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
pollster::block_on(adapter.request_device(&device_desc))
}));
match outcome {
Ok(Ok((device, queue))) => Some((adapter, device, queue)),
Ok(Err(e)) => {
log::warn!(
"skipping adapter {:?}: request_device failed: {e}",
adapter.get_info().name
);
None
}
Err(_) => {
log::warn!(
"skipping adapter {:?}: Vulkan driver panicked during device creation",
adapter.get_info().name
);
None
}
}
})
.context("no adapter could create a wgpu device")?;
log::info!("wgpu adapter: {:?}", adapter.get_info());
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("gpupaper"),
required_features: wgpu::Features::empty(),
required_limits: adapter.limits(),
memory_hints: wgpu::MemoryHints::default(),
experimental_features: Default::default(),
trace: Default::default(),
},
))
.context("failed to create wgpu device")?;
// Surface configuration
let caps = wgpu_surface.get_capabilities(&adapter);
let format = caps
+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>,