use vulkan first and fallback to gl if driver errors
This commit is contained in:
+75
-19
@@ -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
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user