From a8d0360284515b2203571173bd118bfb2c9ef455 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 24 Sep 2025 01:06:59 +0000 Subject: [PATCH 1/7] rudimentary custom wgpu adapter selection --- desktop/src/main.rs | 51 ++++++++++++++++++++++++- node-graph/wgpu-executor/src/context.rs | 9 +++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 16190d0f76..621cfc1a67 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -31,7 +31,7 @@ fn main() { return; } - let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap(); + let wgpu_context = futures::executor::block_on(init_wgpu_context()); let event_loop = EventLoop::new().unwrap(); let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel(); @@ -67,3 +67,52 @@ fn main() { event_loop.run_app(&mut app).unwrap(); } + +async fn init_wgpu_context() -> WgpuContext { + // TODO: make this configurable via cli flags instead + let adapter_override = std::env::var("GRAPHITE_WGPU_ADAPTER").ok().map(|s| usize::from_str_radix(&s, 10).ok()).flatten(); + + let instance_descriptor = wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }; + let instance = wgpu::Instance::new(&instance_descriptor); + + let mut adapters = instance.enumerate_adapters(wgpu::Backends::all()); + + // TODO: add a cli flag to list adapters and exit instead of always printing + let adapters_fmt = adapters + .iter() + .enumerate() + .map(|(i, a)| { + let info = a.get_info(); + format!( + "\nAdapter {}:\n Name: {}\n Backend: {:?}\n Driver: {}\n Device ID: {}\n Vendor ID: {}", + i, info.name, info.backend, info.driver, info.device, info.vendor + ) + }) + .collect::>() + .join("\n"); + println!("\nAvailable wgpu adapters:\n {}\n", adapters_fmt); + + let adapter_index = if let Some(index) = adapter_override + && index < adapters.len() + { + index + } else if cfg!(target_os = "windows") { + match adapters.iter().enumerate().find(|(_, a)| a.get_info().backend == wgpu::Backend::Dx12) { + Some((index, _)) => index, + None => 0, + } + } else { + 0 // Same behavior as requests adapter + }; + + tracing::info!("Using WGPU adapter {adapter_index}"); + + let adapter = adapters.remove(adapter_index); + + WgpuContext::new_with_instance_and_adapter(instance, adapter) + .await + .expect("Failed to create WGPU context with specified adapter") +} diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 06da16e0ac..1bad004cb3 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -11,7 +11,6 @@ pub struct Context { impl Context { pub async fn new() -> Option { - // Instantiates instance of WebGPU let instance_descriptor = wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() @@ -23,12 +22,14 @@ impl Context { compatible_surface: None, force_fallback_adapter: false, }; - // `request_adapter` instantiates the general connection to the GPU + let adapter = instance.request_adapter(&adapter_options).await.ok()?; + Self::new_with_instance_and_adapter(instance, adapter).await + } + + pub async fn new_with_instance_and_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Option { let required_limits = adapter.limits(); - // `request_device` instantiates the feature specific connection to the GPU, defining some parameters, - // `features` being the available features. let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, From a5c83bbd0ba0e000ddd9d83a312b5b8036041d8a Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 24 Sep 2025 01:06:59 +0000 Subject: [PATCH 2/7] WgpuContextBuilder --- desktop/src/gpu.rs | 25 ++++ desktop/src/main.rs | 55 +------- desktop/wrapper/src/lib.rs | 4 +- .../graph-craft/src/wasm_application_io.rs | 2 +- node-graph/wgpu-executor/src/context.rs | 125 ++++++++++++++---- node-graph/wgpu-executor/src/lib.rs | 12 +- .../wgpu-executor/src/shader_runtime/mod.rs | 6 +- .../per_pixel_adjust_runtime.rs | 16 +-- 8 files changed, 148 insertions(+), 97 deletions(-) create mode 100644 desktop/src/gpu.rs diff --git a/desktop/src/gpu.rs b/desktop/src/gpu.rs new file mode 100644 index 0000000000..e66d69181d --- /dev/null +++ b/desktop/src/gpu.rs @@ -0,0 +1,25 @@ +use graphite_desktop_wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures}; + +pub(super) async fn create_wgpu_context() -> WgpuContext { + let wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::PUSH_CONSTANTS); + + // 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); + + // TODO: make this configurable via cli flags instead + 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(|adapters: &mut Vec<_>| if adapter_index < adapters.len() { Some(adapters.remove(adapter_index)) } else { None }) + .await + } + } + .expect("Failed to create WGPU context"); + + // TODO: add a cli flag to list adapters and exit instead of always printing + println!("Using WGPU adapter: {:?}", wgpu_context.adapter.get_info()); + + wgpu_context +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 621cfc1a67..edc6b148c8 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -2,8 +2,6 @@ use std::process::exit; use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; -use graphite_desktop_wrapper::WgpuContext; - pub(crate) mod consts; mod app; @@ -14,6 +12,8 @@ mod native_window; mod persist; mod render; +mod gpu; + use app::App; use cef::CefHandler; use event::CreateAppEventSchedulerEventLoopExt; @@ -31,7 +31,7 @@ fn main() { return; } - let wgpu_context = futures::executor::block_on(init_wgpu_context()); + let wgpu_context = futures::executor::block_on(gpu::create_wgpu_context()); let event_loop = EventLoop::new().unwrap(); let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel(); @@ -67,52 +67,3 @@ fn main() { event_loop.run_app(&mut app).unwrap(); } - -async fn init_wgpu_context() -> WgpuContext { - // TODO: make this configurable via cli flags instead - let adapter_override = std::env::var("GRAPHITE_WGPU_ADAPTER").ok().map(|s| usize::from_str_radix(&s, 10).ok()).flatten(); - - let instance_descriptor = wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), - ..Default::default() - }; - let instance = wgpu::Instance::new(&instance_descriptor); - - let mut adapters = instance.enumerate_adapters(wgpu::Backends::all()); - - // TODO: add a cli flag to list adapters and exit instead of always printing - let adapters_fmt = adapters - .iter() - .enumerate() - .map(|(i, a)| { - let info = a.get_info(); - format!( - "\nAdapter {}:\n Name: {}\n Backend: {:?}\n Driver: {}\n Device ID: {}\n Vendor ID: {}", - i, info.name, info.backend, info.driver, info.device, info.vendor - ) - }) - .collect::>() - .join("\n"); - println!("\nAvailable wgpu adapters:\n {}\n", adapters_fmt); - - let adapter_index = if let Some(index) = adapter_override - && index < adapters.len() - { - index - } else if cfg!(target_os = "windows") { - match adapters.iter().enumerate().find(|(_, a)| a.get_info().backend == wgpu::Backend::Dx12) { - Some((index, _)) => index, - None => 0, - } - } else { - 0 // Same behavior as requests adapter - }; - - tracing::info!("Using WGPU adapter {adapter_index}"); - - let adapter = adapters.remove(adapter_index); - - WgpuContext::new_with_instance_and_adapter(instance, adapter) - .await - .expect("Failed to create WGPU context with specified adapter") -} diff --git a/desktop/wrapper/src/lib.rs b/desktop/wrapper/src/lib.rs index 0410f153bb..3050c96d2a 100644 --- a/desktop/wrapper/src/lib.rs +++ b/desktop/wrapper/src/lib.rs @@ -5,8 +5,10 @@ use graphite_editor::messages::prelude::{FrontendMessage, Message}; // TODO: Remove usage of this reexport in desktop create and remove this line pub use graphene_std::Color; -pub use wgpu_executor::Context as WgpuContext; +pub use wgpu_executor::WgpuContext; +pub use wgpu_executor::WgpuContextBuilder; pub use wgpu_executor::WgpuExecutor; +pub use wgpu_executor::WgpuFeatures; pub mod messages; use messages::{DesktopFrontendMessage, DesktopWrapperMessage}; diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index abca58d88b..9a956d52b6 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -143,7 +143,7 @@ impl WasmApplicationIo { io } #[cfg(all(not(target_family = "wasm"), feature = "wgpu"))] - pub fn new_with_context(context: wgpu_executor::Context) -> Self { + pub fn new_with_context(context: wgpu_executor::WgpuContext) -> Self { #[cfg(feature = "wgpu")] let executor = WgpuExecutor::with_context(context); diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 1bad004cb3..1a39b7f768 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -1,54 +1,123 @@ use std::sync::Arc; -use wgpu::{Device, Instance, Queue}; +use wgpu::{Adapter, Backends, Device, Features, Instance, Queue}; #[derive(Debug, Clone)] pub struct Context { pub device: Arc, pub queue: Arc, pub instance: Arc, - pub adapter: Arc, + pub adapter: Arc, } impl Context { pub async fn new() -> Option { - let instance_descriptor = wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), - ..Default::default() - }; - let instance = Instance::new(&instance_descriptor); + ContextBuilder::new().build().await + } +} - let adapter_options = wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: None, - force_fallback_adapter: false, +#[derive(Default)] +pub struct ContextBuilder { + backends: Backends, + features: Features, +} +impl ContextBuilder { + pub fn new() -> Self { + Self { + backends: Backends::all(), + features: Features::empty(), + } + } + pub fn with_backends(mut self, backends: Backends) -> Self { + self.backends = backends; + self + } + pub fn with_features(mut self, features: Features) -> Self { + self.features = features; + self + } + pub async fn build(self) -> Option { + self.build_with_adapter_selection_inner(None::).await + } + pub async fn build_with_adapter_selection(self, select: S) -> Option { + self.build_with_adapter_selection_inner(Some(select)).await + } + async fn build_with_adapter_selection_inner(self, select: Option) -> Option { + let instance = self.build_instance(); + + let selected_adapter = if let Some(select) = select { + self.select_adapter(&instance, select) + } else if cfg!(target_os = "windows") { + self.select_adapter(&instance, |adapters: &mut Vec| { + adapters.iter().position(|a| a.get_info().backend == wgpu::Backend::Dx12).map(|i| adapters.remove(i)) + }) + } else { + None }; - let adapter = instance.request_adapter(&adapter_options).await.ok()?; + let adapter = if let Some(adapter) = selected_adapter { adapter } else { self.request_adapter(&instance).await? }; - Self::new_with_instance_and_adapter(instance, adapter).await + let (device, queue) = self.request_device(&adapter).await?; + Some(Context { + device: Arc::new(device), + queue: Arc::new(queue), + adapter: Arc::new(adapter), + instance: Arc::new(instance), + }) + } + pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { + let instance = self.build_instance(); + let adapters = instance.enumerate_adapters(self.backends); + AvailableAdaptersFormatter(adapters) } - pub async fn new_with_instance_and_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Option { - let required_limits = adapter.limits(); - let (device, queue) = adapter + fn build_instance(&self) -> Instance { + Instance::new(&wgpu::InstanceDescriptor { + backends: self.backends, + ..Default::default() + }) + } + async fn request_adapter(&self, instance: &Instance) -> Option { + instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + }) + .await + .ok() + } + fn select_adapter(&self, instance: &Instance, select: S) -> Option { + select(&mut instance.enumerate_adapters(self.backends)) + } + async fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> { + adapter .request_device(&wgpu::DeviceDescriptor { label: None, - #[cfg(target_family = "wasm")] - required_features: wgpu::Features::empty(), - #[cfg(not(target_family = "wasm"))] - required_features: wgpu::Features::PUSH_CONSTANTS, - required_limits, + required_features: self.features, + required_limits: adapter.limits(), memory_hints: Default::default(), trace: wgpu::Trace::Off, }) .await - .ok()?; + .ok() + } +} - Some(Self { - device: Arc::new(device), - queue: Arc::new(queue), - adapter: Arc::new(adapter), - instance: Arc::new(instance), - }) +pub trait WgpuAdapterSelector: FnOnce(&mut Vec) -> Option {} +impl WgpuAdapterSelector for F where F: FnOnce(&mut Vec) -> Option {} +type WgpuAdapterSelectorFn = fn(&mut Vec) -> Option; + +struct AvailableAdaptersFormatter(Vec); +impl std::fmt::Display for AvailableAdaptersFormatter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, adapter) in self.0.iter().enumerate() { + let info = adapter.get_info(); + writeln!( + f, + "[{}] {:?} {:?} (Name: {}, Driver: {}, Device: {})", + i, info.backend, info.device_type, info.name, info.driver, info.device, + )?; + } + Ok(()) } } diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 4a1e7f3e36..cd8e3d4263 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -4,7 +4,6 @@ pub mod texture_upload; use crate::shader_runtime::ShaderRuntime; use anyhow::Result; -pub use context::Context; use dyn_any::StaticType; use futures::lock::Mutex; use glam::UVec2; @@ -16,9 +15,14 @@ use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene} use wgpu::util::TextureBlitter; use wgpu::{Origin3d, SurfaceConfiguration, TextureAspect}; +pub use context::Context as WgpuContext; +pub use context::ContextBuilder as WgpuContextBuilder; +pub use wgpu::Backends as WgpuBackends; +pub use wgpu::Features as WgpuFeatures; + #[derive(dyn_any::DynAny)] pub struct WgpuExecutor { - pub context: Context, + pub context: WgpuContext, vello_renderer: Mutex, pub shader_runtime: ShaderRuntime, } @@ -182,10 +186,10 @@ impl WgpuExecutor { impl WgpuExecutor { pub async fn new() -> Option { - Self::with_context(Context::new().await?) + Self::with_context(WgpuContext::new().await?) } - pub fn with_context(context: Context) -> Option { + pub fn with_context(context: WgpuContext) -> Option { let vello_renderer = Renderer::new( &context.device, RendererOptions { diff --git a/node-graph/wgpu-executor/src/shader_runtime/mod.rs b/node-graph/wgpu-executor/src/shader_runtime/mod.rs index e7e0df8d94..7260fa6e56 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/mod.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/mod.rs @@ -1,4 +1,4 @@ -use crate::Context; +use crate::WgpuContext; use crate::shader_runtime::per_pixel_adjust_runtime::PerPixelAdjustShaderRuntime; pub mod per_pixel_adjust_runtime; @@ -6,12 +6,12 @@ pub mod per_pixel_adjust_runtime; pub const FULLSCREEN_VERTEX_SHADER_NAME: &str = "fullscreen_vertexfullscreen_vertex"; pub struct ShaderRuntime { - context: Context, + context: WgpuContext, per_pixel_adjust: PerPixelAdjustShaderRuntime, } impl ShaderRuntime { - pub fn new(context: &Context) -> Self { + pub fn new(context: &WgpuContext) -> Self { Self { context: context.clone(), per_pixel_adjust: PerPixelAdjustShaderRuntime::new(), diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index aa567a5b93..763b1e1e30 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -1,4 +1,4 @@ -use crate::Context; +use crate::WgpuContext; use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; use futures::lock::Mutex; use graphene_core::raster_types::{GPU, Raster}; @@ -31,7 +31,7 @@ impl ShaderRuntime { let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; let pipeline = cache .entry(shaders.fragment_shader_name.to_owned()) - .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, &shaders)); + .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, shaders)); let arg_buffer = args.map(|args| { let device = &self.context.device; @@ -58,7 +58,7 @@ pub struct PerPixelAdjustGraphicsPipeline { } impl PerPixelAdjustGraphicsPipeline { - pub fn new(context: &Context, info: &Shaders) -> Self { + pub fn new(context: &WgpuContext, info: &Shaders) -> Self { let device = &context.device; let name = info.fragment_shader_name.to_owned(); @@ -67,7 +67,7 @@ impl PerPixelAdjustGraphicsPipeline { // TODO workaround to naga removing `:` let fragment_name = fragment_name.replace(":", ""); let shader_module = device.create_shader_module(ShaderModuleDescriptor { - label: Some(&format!("PerPixelAdjust {} wgsl shader", name)), + label: Some(&format!("PerPixelAdjust {name} wgsl shader")), source: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)), }); @@ -107,16 +107,16 @@ impl PerPixelAdjustGraphicsPipeline { }] }; let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: Some(&format!("PerPixelAdjust {} PipelineLayout", name)), + label: Some(&format!("PerPixelAdjust {name} PipelineLayout")), bind_group_layouts: &[&device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some(&format!("PerPixelAdjust {} BindGroupLayout 0", name)), + label: Some(&format!("PerPixelAdjust {name} BindGroupLayout 0")), entries, })], push_constant_ranges: &[], }); let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { - label: Some(&format!("PerPixelAdjust {} Pipeline", name)), + label: Some(&format!("PerPixelAdjust {name} Pipeline")), layout: Some(&pipeline_layout), vertex: VertexState { module: &shader_module, @@ -155,7 +155,7 @@ impl PerPixelAdjustGraphicsPipeline { } } - pub fn dispatch(&self, context: &Context, textures: Table>, arg_buffer: Option) -> Table> { + pub fn dispatch(&self, context: &WgpuContext, textures: Table>, arg_buffer: Option) -> Table> { assert_eq!(self.has_uniform, arg_buffer.is_some()); let device = &context.device; let name = self.name.as_str(); From e0e4330aa1e279d29c78bb22d91a6e07a10452a2 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 24 Sep 2025 01:06:59 +0000 Subject: [PATCH 3/7] wasm fix --- node-graph/wgpu-executor/src/context.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 1a39b7f768..8cc21b0a2a 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -65,9 +65,13 @@ impl ContextBuilder { }) } pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { - let instance = self.build_instance(); - let adapters = instance.enumerate_adapters(self.backends); - AvailableAdaptersFormatter(adapters) + #[cfg(not(target_family = "wasm"))] + { + let instance = self.build_instance(); + AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends)) + } + #[cfg(target_family = "wasm")] + AvailableAdaptersFormatter(Vec::new()) } fn build_instance(&self) -> Instance { @@ -87,7 +91,10 @@ impl ContextBuilder { .ok() } fn select_adapter(&self, instance: &Instance, select: S) -> Option { - select(&mut instance.enumerate_adapters(self.backends)) + #[cfg(not(target_family = "wasm"))] + return select(&mut instance.enumerate_adapters(self.backends)); + #[cfg(target_family = "wasm")] + None } async fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> { adapter From dc36ea03ce0922ab2816228c9ce67f5f94a8fe0d Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 24 Sep 2025 01:06:59 +0000 Subject: [PATCH 4/7] fix wasm warnings --- node-graph/wgpu-executor/src/context.rs | 75 +++++++++++++++---------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 8cc21b0a2a..419d2ac65f 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -35,15 +35,31 @@ impl ContextBuilder { self.features = features; self } + #[cfg(target_family = "wasm")] + pub async fn build(self) -> Option { + let instance = self.build_instance(); + let adapter = self.request_adapter(&instance).await?; + let (device, queue) = self.request_device(&adapter).await?; + Some(Context { + device: Arc::new(device), + queue: Arc::new(queue), + adapter: Arc::new(adapter), + instance: Arc::new(instance), + }) + } + #[cfg(not(target_family = "wasm"))] pub async fn build(self) -> Option { self.build_with_adapter_selection_inner(None::).await } + #[cfg(not(target_family = "wasm"))] pub async fn build_with_adapter_selection(self, select: S) -> Option { self.build_with_adapter_selection_inner(Some(select)).await } + #[cfg(not(target_family = "wasm"))] async fn build_with_adapter_selection_inner(self, select: Option) -> Option { let instance = self.build_instance(); + #[cfg(not(target_family = "wasm"))] let selected_adapter = if let Some(select) = select { self.select_adapter(&instance, select) } else if cfg!(target_os = "windows") { @@ -53,6 +69,8 @@ impl ContextBuilder { } else { None }; + #[cfg(target_family = "wasm")] + let selected_adapter = None; let adapter = if let Some(adapter) = selected_adapter { adapter } else { self.request_adapter(&instance).await? }; @@ -64,14 +82,10 @@ impl ContextBuilder { instance: Arc::new(instance), }) } + #[cfg(not(target_family = "wasm"))] pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { - #[cfg(not(target_family = "wasm"))] - { - let instance = self.build_instance(); - AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends)) - } - #[cfg(target_family = "wasm")] - AvailableAdaptersFormatter(Vec::new()) + let instance = self.build_instance(); + AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends)) } fn build_instance(&self) -> Instance { @@ -81,40 +95,39 @@ impl ContextBuilder { }) } async fn request_adapter(&self, instance: &Instance) -> Option { - instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: None, - force_fallback_adapter: false, - }) - .await - .ok() - } - fn select_adapter(&self, instance: &Instance, select: S) -> Option { - #[cfg(not(target_family = "wasm"))] - return select(&mut instance.enumerate_adapters(self.backends)); - #[cfg(target_family = "wasm")] - None + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + }; + instance.request_adapter(&request_adapter_options).await.ok() } async fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> { - adapter - .request_device(&wgpu::DeviceDescriptor { - label: None, - required_features: self.features, - required_limits: adapter.limits(), - memory_hints: Default::default(), - trace: wgpu::Trace::Off, - }) - .await - .ok() + let device_descriptor = wgpu::DeviceDescriptor { + label: None, + required_features: self.features, + required_limits: adapter.limits(), + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }; + adapter.request_device(&device_descriptor).await.ok() + } + #[cfg(not(target_family = "wasm"))] + fn select_adapter(&self, instance: &Instance, select: S) -> Option { + select(&mut instance.enumerate_adapters(self.backends)) } } +#[cfg(not(target_family = "wasm"))] pub trait WgpuAdapterSelector: FnOnce(&mut Vec) -> Option {} +#[cfg(not(target_family = "wasm"))] impl WgpuAdapterSelector for F where F: FnOnce(&mut Vec) -> Option {} +#[cfg(not(target_family = "wasm"))] type WgpuAdapterSelectorFn = fn(&mut Vec) -> Option; +#[cfg(not(target_family = "wasm"))] struct AvailableAdaptersFormatter(Vec); +#[cfg(not(target_family = "wasm"))] impl std::fmt::Display for AvailableAdaptersFormatter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (i, adapter) in self.0.iter().enumerate() { From 256ea119585245a55255d35ef03093de24c9cb72 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 24 Sep 2025 01:06:59 +0000 Subject: [PATCH 5/7] Clean up --- node-graph/wgpu-executor/src/context.rs | 127 +++++++++++++----------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 419d2ac65f..5de3f3eb0d 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -35,7 +35,25 @@ impl ContextBuilder { self.features = features; self } - #[cfg(target_family = "wasm")] +} +#[cfg(not(target_family = "wasm"))] +impl ContextBuilder { + pub async fn build(self) -> Option { + self.build_with_adapter_selection_inner(None::) -> Option>).await + } + pub async fn build_with_adapter_selection(self, select: S) -> Option + where + S: FnOnce(&mut Vec) -> Option, + { + self.build_with_adapter_selection_inner(Some(select)).await + } + pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { + let instance = self.build_instance(); + fmt::AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends)) + } +} +#[cfg(target_family = "wasm")] +impl ContextBuilder { pub async fn build(self) -> Option { let instance = self.build_instance(); let adapter = self.request_adapter(&instance).await?; @@ -47,16 +65,40 @@ impl ContextBuilder { instance: Arc::new(instance), }) } - #[cfg(not(target_family = "wasm"))] - pub async fn build(self) -> Option { - self.build_with_adapter_selection_inner(None::).await +} +impl ContextBuilder { + fn build_instance(&self) -> Instance { + Instance::new(&wgpu::InstanceDescriptor { + backends: self.backends, + ..Default::default() + }) } - #[cfg(not(target_family = "wasm"))] - pub async fn build_with_adapter_selection(self, select: S) -> Option { - self.build_with_adapter_selection_inner(Some(select)).await + async fn request_adapter(&self, instance: &Instance) -> Option { + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + }; + instance.request_adapter(&request_adapter_options).await.ok() + } + async fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> { + let device_descriptor = wgpu::DeviceDescriptor { + label: None, + required_features: self.features, + required_limits: adapter.limits(), + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }; + adapter.request_device(&device_descriptor).await.ok() } +} +#[cfg(not(target_family = "wasm"))] +impl ContextBuilder { #[cfg(not(target_family = "wasm"))] - async fn build_with_adapter_selection_inner(self, select: Option) -> Option { + async fn build_with_adapter_selection_inner(self, select: Option) -> Option + where + S: FnOnce(&mut Vec) -> Option, + { let instance = self.build_instance(); #[cfg(not(target_family = "wasm"))] @@ -82,62 +124,29 @@ impl ContextBuilder { instance: Arc::new(instance), }) } - #[cfg(not(target_family = "wasm"))] - pub async fn available_adapters_fmt(&self) -> impl std::fmt::Display { - let instance = self.build_instance(); - AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends)) - } - - fn build_instance(&self) -> Instance { - Instance::new(&wgpu::InstanceDescriptor { - backends: self.backends, - ..Default::default() - }) - } - async fn request_adapter(&self, instance: &Instance) -> Option { - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: None, - force_fallback_adapter: false, - }; - instance.request_adapter(&request_adapter_options).await.ok() - } - async fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> { - let device_descriptor = wgpu::DeviceDescriptor { - label: None, - required_features: self.features, - required_limits: adapter.limits(), - memory_hints: Default::default(), - trace: wgpu::Trace::Off, - }; - adapter.request_device(&device_descriptor).await.ok() - } - #[cfg(not(target_family = "wasm"))] - fn select_adapter(&self, instance: &Instance, select: S) -> Option { + fn select_adapter(&self, instance: &Instance, select: S) -> Option + where + S: FnOnce(&mut Vec) -> Option, + { select(&mut instance.enumerate_adapters(self.backends)) } } - #[cfg(not(target_family = "wasm"))] -pub trait WgpuAdapterSelector: FnOnce(&mut Vec) -> Option {} -#[cfg(not(target_family = "wasm"))] -impl WgpuAdapterSelector for F where F: FnOnce(&mut Vec) -> Option {} -#[cfg(not(target_family = "wasm"))] -type WgpuAdapterSelectorFn = fn(&mut Vec) -> Option; +mod fmt { + use super::*; -#[cfg(not(target_family = "wasm"))] -struct AvailableAdaptersFormatter(Vec); -#[cfg(not(target_family = "wasm"))] -impl std::fmt::Display for AvailableAdaptersFormatter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (i, adapter) in self.0.iter().enumerate() { - let info = adapter.get_info(); - writeln!( - f, - "[{}] {:?} {:?} (Name: {}, Driver: {}, Device: {})", - i, info.backend, info.device_type, info.name, info.driver, info.device, - )?; + pub(super) struct AvailableAdaptersFormatter(pub(super) Vec); + impl std::fmt::Display for AvailableAdaptersFormatter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, adapter) in self.0.iter().enumerate() { + let info = adapter.get_info(); + writeln!( + f, + "[{}] {:?} {:?} (Name: {}, Driver: {}, Device: {})", + i, info.backend, info.device_type, info.name, info.driver, info.device, + )?; + } + Ok(()) } - Ok(()) } } From 29fedebdd7bf01363e96270729615efb4400c692 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 25 Sep 2025 12:06:31 +0000 Subject: [PATCH 6/7] Review suggestions --- desktop/src/{gpu.rs => gpu_context.rs} | 0 desktop/src/main.rs | 4 ++-- node-graph/wgpu-executor/src/context.rs | 23 +++++++++++------------ 3 files changed, 13 insertions(+), 14 deletions(-) rename desktop/src/{gpu.rs => gpu_context.rs} (100%) diff --git a/desktop/src/gpu.rs b/desktop/src/gpu_context.rs similarity index 100% rename from desktop/src/gpu.rs rename to desktop/src/gpu_context.rs diff --git a/desktop/src/main.rs b/desktop/src/main.rs index edc6b148c8..dd6116415f 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -12,7 +12,7 @@ mod native_window; mod persist; mod render; -mod gpu; +mod gpu_context; use app::App; use cef::CefHandler; @@ -31,7 +31,7 @@ fn main() { return; } - let wgpu_context = futures::executor::block_on(gpu::create_wgpu_context()); + let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context()); let event_loop = EventLoop::new().unwrap(); let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel(); diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 5de3f3eb0d..ff95686d05 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -39,11 +39,11 @@ impl ContextBuilder { #[cfg(not(target_family = "wasm"))] impl ContextBuilder { pub async fn build(self) -> Option { - self.build_with_adapter_selection_inner(None::) -> Option>).await + self.build_with_adapter_selection_inner(None:: Option>).await } pub async fn build_with_adapter_selection(self, select: S) -> Option where - S: FnOnce(&mut Vec) -> Option, + S: Fn(&[Adapter]) -> Option, { self.build_with_adapter_selection_inner(Some(select)).await } @@ -94,25 +94,19 @@ impl ContextBuilder { } #[cfg(not(target_family = "wasm"))] impl ContextBuilder { - #[cfg(not(target_family = "wasm"))] async fn build_with_adapter_selection_inner(self, select: Option) -> Option where - S: FnOnce(&mut Vec) -> Option, + S: Fn(&[Adapter]) -> Option, { let instance = self.build_instance(); - #[cfg(not(target_family = "wasm"))] let selected_adapter = if let Some(select) = select { self.select_adapter(&instance, select) } else if cfg!(target_os = "windows") { - self.select_adapter(&instance, |adapters: &mut Vec| { - adapters.iter().position(|a| a.get_info().backend == wgpu::Backend::Dx12).map(|i| adapters.remove(i)) - }) + self.select_adapter(&instance, |adapters: &[Adapter]| adapters.iter().position(|a| a.get_info().backend == wgpu::Backend::Dx12)) } else { None }; - #[cfg(target_family = "wasm")] - let selected_adapter = None; let adapter = if let Some(adapter) = selected_adapter { adapter } else { self.request_adapter(&instance).await? }; @@ -126,9 +120,14 @@ impl ContextBuilder { } fn select_adapter(&self, instance: &Instance, select: S) -> Option where - S: FnOnce(&mut Vec) -> Option, + S: Fn(&[Adapter]) -> Option, { - select(&mut instance.enumerate_adapters(self.backends)) + let mut adapters = instance.enumerate_adapters(self.backends); + let selected_index = select(&adapters)?; + if selected_index >= adapters.len() { + return None; + } + Some(adapters.remove(selected_index)) } } #[cfg(not(target_family = "wasm"))] From a2bdded14fbdec513395d097b64683b25ea852d9 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 25 Sep 2025 12:09:22 +0000 Subject: [PATCH 7/7] fix --- desktop/src/gpu_context.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/desktop/src/gpu_context.rs b/desktop/src/gpu_context.rs index e66d69181d..787d6cda2f 100644 --- a/desktop/src/gpu_context.rs +++ b/desktop/src/gpu_context.rs @@ -11,9 +11,7 @@ pub(super) async fn create_wgpu_context() -> WgpuContext { 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(|adapters: &mut Vec<_>| if adapter_index < adapters.len() { Some(adapters.remove(adapter_index)) } else { None }) - .await + wgpu_context_builder.build_with_adapter_selection(|_| Some(adapter_index)).await } } .expect("Failed to create WGPU context");