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 {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
backends: wgpu::Backends::GL,
|
backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
|
||||||
flags: wgpu::InstanceFlags::default(),
|
flags: wgpu::InstanceFlags::default(),
|
||||||
memory_budget_thresholds: Default::default(),
|
memory_budget_thresholds: Default::default(),
|
||||||
backend_options: Default::default(),
|
backend_options: Default::default(),
|
||||||
@@ -131,27 +131,83 @@ impl Renderer {
|
|||||||
})
|
})
|
||||||
.context("failed to create wgpu surface")?;
|
.context("failed to create wgpu surface")?;
|
||||||
|
|
||||||
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
// Build a priority-ordered candidate list: Vulkan discrete first, then
|
||||||
power_preference: wgpu::PowerPreference::default(),
|
// Vulkan integrated, then GL. We try each in turn and fall back on
|
||||||
compatible_surface: Some(&wgpu_surface),
|
// failure so that Vulkan is used wherever it works (NVIDIA, AMD, etc.)
|
||||||
force_fallback_adapter: false,
|
// while broken Vulkan drivers (e.g. Mesa V3DV on Raspberry Pi, which
|
||||||
}))
|
// advertises features it can't actually enable) are skipped silently.
|
||||||
.context("no suitable wgpu adapter found")?;
|
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());
|
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
|
// Surface configuration
|
||||||
let caps = wgpu_surface.get_capabilities(&adapter);
|
let caps = wgpu_surface.get_capabilities(&adapter);
|
||||||
let format = caps
|
let format = caps
|
||||||
|
|||||||
+51
-13
@@ -49,6 +49,10 @@ pub struct AppState {
|
|||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
pub exit: bool,
|
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
|
// Safety: we only use the raw pointers in single-threaded context
|
||||||
@@ -98,6 +102,8 @@ pub fn run(
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
exit: false,
|
exit: false,
|
||||||
|
draw_ready: false,
|
||||||
|
last_frame: Instant::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// One roundtrip to discover outputs.
|
// One roundtrip to discover outputs.
|
||||||
@@ -147,36 +153,66 @@ pub fn run(
|
|||||||
.context("failed to create renderer")?
|
.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 {
|
let frame_duration = if fps > 0 {
|
||||||
Some(Duration::from_nanos(1_000_000_000 / fps as u64))
|
Some(Duration::from_nanos(1_000_000_000 / fps as u64))
|
||||||
} else {
|
} else {
|
||||||
None
|
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 {
|
loop {
|
||||||
// Non-blocking Wayland event dispatch.
|
// Block the thread until the compositor sends us any event (including
|
||||||
event_queue.dispatch_pending(&mut state).context("event dispatch")?;
|
// the frame callback). Zero CPU while occluded.
|
||||||
// Flush outgoing messages.
|
event_queue.blocking_dispatch(&mut state).context("event dispatch")?;
|
||||||
conn.flush().ok();
|
conn.flush().ok();
|
||||||
|
|
||||||
if state.exit {
|
if state.exit {
|
||||||
break;
|
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() {
|
if let Some(r) = state.renderer.as_mut() {
|
||||||
r.render();
|
r.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap frame rate if requested.
|
conn.flush().ok();
|
||||||
if let Some(dur) = frame_duration {
|
|
||||||
let elapsed = frame_start.elapsed();
|
|
||||||
if elapsed < dur {
|
|
||||||
std::thread::sleep(dur - elapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -220,7 +256,9 @@ impl CompositorHandler for AppState {
|
|||||||
fn frame(
|
fn frame(
|
||||||
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||||||
_: &wl_surface::WlSurface, _: u32,
|
_: &wl_surface::WlSurface, _: u32,
|
||||||
) {}
|
) {
|
||||||
|
self.draw_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
fn surface_enter(
|
fn surface_enter(
|
||||||
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||||||
|
|||||||
Reference in New Issue
Block a user