Desktop: Prefer Vulkan as the WGPU backend on Linux (#4270)

* Manual wgpu adapter selection

* Fall back to remaining adapters when the preferred one fails to initialize
This commit is contained in:
Timon
2026-06-25 00:09:39 +00:00
committed by GitHub
parent ebbbb6bd1b
commit 6fb6e83d68
3 changed files with 61 additions and 49 deletions
+8 -10
View File
@@ -1,20 +1,18 @@
use crate::wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures}; use crate::wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures};
pub(super) async fn create_wgpu_context() -> WgpuContext { pub(super) async fn create_wgpu_context() -> WgpuContext {
let wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::IMMEDIATES); let mut wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::IMMEDIATES);
// TODO: make this configurable via cli flags instead
if let Some(index) = std::env::var("GRAPHITE_WGPU_ADAPTER").ok().and_then(|s| s.parse().ok()) {
tracing::info!("Overriding WGPU adapter selection with adapter index {index}");
wgpu_context_builder = wgpu_context_builder.with_selection(index);
}
// TODO: add a cli flag to list adapters and exit instead of always printing // TODO: add a cli flag to list adapters and exit instead of always printing
println!("\nAvailable WGPU adapters:\n{}", wgpu_context_builder.available_adapters_fmt().await); println!("\nAvailable WGPU adapters:\n{}", wgpu_context_builder.available_adapters_fmt().await);
// TODO: make this configurable via cli flags instead let wgpu_context = wgpu_context_builder.build().await.expect("Failed to create WGPU context");
let wgpu_context = match std::env::var("GRAPHITE_WGPU_ADAPTER").ok().and_then(|s| s.parse().ok()) {
None => wgpu_context_builder.build().await,
Some(adapter_index) => {
tracing::info!("Overriding WGPU adapter selection with adapter index {adapter_index}");
wgpu_context_builder.build_with_adapter_selection(|_| Some(adapter_index)).await
}
}
.expect("Failed to create WGPU context");
// TODO: add a cli flag to list adapters and exit instead of always printing // TODO: add a cli flag to list adapters and exit instead of always printing
println!("Using WGPU adapter: {:?}", wgpu_context.adapter.get_info()); println!("Using WGPU adapter: {:?}", wgpu_context.adapter.get_info());
+1
View File
@@ -8,6 +8,7 @@ use std::sync::Arc;
pub use graph_craft::application_io::resource::MmapResourceStorage; pub use graph_craft::application_io::resource::MmapResourceStorage;
pub use graphite_editor::consts::{DOUBLE_CLICK_MILLISECONDS, FILE_EXTENSION}; pub use graphite_editor::consts::{DOUBLE_CLICK_MILLISECONDS, FILE_EXTENSION};
pub use wgpu_executor::WgpuBackends;
pub use wgpu_executor::WgpuContext; pub use wgpu_executor::WgpuContext;
pub use wgpu_executor::WgpuContextBuilder; pub use wgpu_executor::WgpuContextBuilder;
pub use wgpu_executor::WgpuExecutor; pub use wgpu_executor::WgpuExecutor;
@@ -18,12 +18,14 @@ impl Context {
pub struct ContextBuilder { pub struct ContextBuilder {
backends: Backends, backends: Backends,
features: Features, features: Features,
selection: Option<usize>,
} }
impl ContextBuilder { impl ContextBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
backends: Backends::all(), backends: Backends::all(),
features: Features::empty(), features: Features::empty(),
selection: None,
} }
} }
pub fn with_backends(mut self, backends: Backends) -> Self { pub fn with_backends(mut self, backends: Backends) -> Self {
@@ -34,21 +36,34 @@ impl ContextBuilder {
self.features = features; self.features = features;
self self
} }
pub fn with_selection(mut self, index: usize) -> Self {
self.selection = Some(index);
self
}
} }
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
impl ContextBuilder { impl ContextBuilder {
pub async fn build(self) -> Option<Context> { pub async fn build(self) -> Option<Context> {
self.build_with_adapter_selection_inner(None::<fn(&[Adapter]) -> Option<usize>>).await let instance = self.build_instance();
} let mut adapters = enumerate_sorted(&instance, self.backends).await;
pub async fn build_with_adapter_selection<S>(self, select: S) -> Option<Context>
where if let Some(index) = self.selection
S: Fn(&[Adapter]) -> Option<usize>, && index >= adapters.len()
{ {
self.build_with_adapter_selection_inner(Some(select)).await let selected_adapter = adapters.remove(index);
adapters.insert(0, selected_adapter);
}
for adapter in adapters {
if let Some((device, queue)) = self.request_device(&adapter).await {
return Some(Context { device, queue, adapter, instance });
}
}
None
} }
pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display {
let instance = self.build_instance(); let instance = self.build_instance();
fmt::AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends).await) fmt::AvailableAdaptersFormatter(enumerate_sorted(&instance, self.backends).await)
} }
} }
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
@@ -67,6 +82,7 @@ impl ContextBuilder {
..wgpu::InstanceDescriptor::new_without_display_handle() ..wgpu::InstanceDescriptor::new_without_display_handle()
}) })
} }
#[cfg(target_family = "wasm")]
async fn request_adapter(&self, instance: &Instance) -> Option<Adapter> { async fn request_adapter(&self, instance: &Instance) -> Option<Adapter> {
let request_adapter_options = wgpu::RequestAdapterOptions { let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance, power_preference: wgpu::PowerPreference::HighPerformance,
@@ -88,38 +104,35 @@ impl ContextBuilder {
} }
} }
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
impl ContextBuilder { async fn enumerate_sorted(instance: &Instance, backends: Backends) -> Vec<Adapter> {
async fn build_with_adapter_selection_inner<S>(self, select: Option<S>) -> Option<Context> let mut adapters = instance.enumerate_adapters(backends).await;
where adapters.sort_by_key(adapter_priority);
S: Fn(&[Adapter]) -> Option<usize>, adapters
{ }
let instance = self.build_instance(); #[cfg(not(target_family = "wasm"))]
fn adapter_priority(adapter: &Adapter) -> (u8, u8) {
let selected_adapter = if let Some(select) = select { let info = adapter.get_info();
self.select_adapter(&instance, select).await let backend = if cfg!(target_os = "linux") {
} else if cfg!(target_os = "windows") { match info.backend {
self.select_adapter(&instance, |adapters: &[Adapter]| adapters.iter().position(|a| a.get_info().backend == wgpu::Backend::Dx12)) wgpu::Backend::Vulkan => 0,
.await _ => 1,
} else {
None
};
let adapter = if let Some(adapter) = selected_adapter { adapter } else { self.request_adapter(&instance).await? };
let (device, queue) = self.request_device(&adapter).await?;
Some(Context { device, queue, adapter, instance })
}
async fn select_adapter<S>(&self, instance: &Instance, select: S) -> Option<Adapter>
where
S: Fn(&[Adapter]) -> Option<usize>,
{
let mut adapters = instance.enumerate_adapters(self.backends).await;
let selected_index = select(&adapters)?;
if selected_index >= adapters.len() {
return None;
} }
Some(adapters.remove(selected_index)) } else if cfg!(target_os = "windows") {
} match info.backend {
wgpu::Backend::Dx12 => 0,
_ => 1,
}
} else {
0
};
let device_type = match info.device_type {
wgpu::DeviceType::DiscreteGpu => 0,
wgpu::DeviceType::IntegratedGpu => 1,
wgpu::DeviceType::VirtualGpu => 2,
wgpu::DeviceType::Cpu => 3,
wgpu::DeviceType::Other => 4,
};
(backend, device_type)
} }
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
mod fmt { mod fmt {