diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 6bdd641ce43af90687f765c89d2517e628ecdf75..dd6d8dafe888bc4d2f5fce4987c80781f1df9e19 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -1024,6 +1024,46 @@ impl crate::Capslock { } } +/// Resolve a Linux `dev_t` to PCI vendor/device IDs via sysfs, returning a +/// [`CompositorGpuHint`] that the GPU adapter selection code can use to +/// prioritize the compositor's rendering device. +#[cfg(any(feature = "wayland", feature = "x11"))] +pub(super) fn compositor_gpu_hint_from_dev_t(dev: u64) -> Option { + fn dev_major(dev: u64) -> u32 { + ((dev >> 8) & 0xfff) as u32 | (((dev >> 32) & !0xfff) as u32) + } + + fn dev_minor(dev: u64) -> u32 { + (dev & 0xff) as u32 | (((dev >> 12) & !0xff) as u32) + } + + fn read_sysfs_hex_id(path: &str) -> Option { + let content = std::fs::read_to_string(path).ok()?; + let trimmed = content.trim().strip_prefix("0x").unwrap_or(content.trim()); + u32::from_str_radix(trimmed, 16).ok() + } + + let major = dev_major(dev); + let minor = dev_minor(dev); + + let vendor_path = format!("/sys/dev/char/{major}:{minor}/device/vendor"); + let device_path = format!("/sys/dev/char/{major}:{minor}/device/device"); + + let vendor_id = read_sysfs_hex_id(&vendor_path)?; + let device_id = read_sysfs_hex_id(&device_path)?; + + log::info!( + "Compositor GPU hint: vendor={:#06x}, device={:#06x} (from dev {major}:{minor})", + vendor_id, + device_id, + ); + + Some(gpui_wgpu::CompositorGpuHint { + vendor_id, + device_id, + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index e187d8e169defad1da3464570d5437e4bad691cd..1f68a3c946088f5946776aecc80f1ceb7b1b879b 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -97,7 +97,13 @@ use crate::{ }; use crate::{ TaskTiming, - platform::{PlatformWindow, wgpu::WgpuContext}, + platform::{ + PlatformWindow, + wgpu::{CompositorGpuHint, WgpuContext}, + }, +}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::{ + zwp_linux_dmabuf_feedback_v1, zwp_linux_dmabuf_v1, }; /// Used to convert evdev scancode to xkb scancode @@ -205,6 +211,7 @@ pub(crate) struct WaylandClientState { serial_tracker: SerialTracker, globals: Globals, pub gpu_context: Option, + pub compositor_gpu: Option, wl_seat: wl_seat::WlSeat, // TODO: Multi seat support wl_pointer: Option, wl_keyboard: Option, @@ -519,6 +526,7 @@ impl WaylandClient { }) .unwrap(); + let compositor_gpu = detect_compositor_gpu(); let gpu_context = None; let seat = seat.unwrap(); @@ -575,6 +583,7 @@ impl WaylandClient { serial_tracker: SerialTracker::new(), globals, gpu_context, + compositor_gpu, wl_seat: seat, wl_pointer: None, wl_keyboard: None, @@ -719,10 +728,12 @@ impl LinuxClient for WaylandClient { let parent = state.keyboard_focused_window.clone(); let appearance = state.common.appearance; + let compositor_gpu = state.compositor_gpu.take(); let (window, surface_id) = WaylandWindow::new( handle, state.globals.clone(), &mut state.gpu_context, + compositor_gpu, WaylandClientStatePtr(Rc::downgrade(&self.0)), params, appearance, @@ -905,6 +916,70 @@ impl LinuxClient for WaylandClient { } } +struct DmabufProbeState { + device: Option, +} + +impl Dispatch for DmabufProbeState { + fn event( + _: &mut Self, + _: &wl_registry::WlRegistry, + _: wl_registry::Event, + _: &GlobalListContents, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for DmabufProbeState { + fn event( + _: &mut Self, + _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, + _: zwp_linux_dmabuf_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for DmabufProbeState { + fn event( + state: &mut Self, + _: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, + event: zwp_linux_dmabuf_feedback_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } = event { + if let Ok(bytes) = <[u8; 8]>::try_from(device.as_slice()) { + state.device = Some(u64::from_ne_bytes(bytes)); + } + } + } +} + +fn detect_compositor_gpu() -> Option { + let connection = Connection::connect_to_env().ok()?; + let (globals, mut event_queue) = registry_queue_init::(&connection).ok()?; + let queue_handle = event_queue.handle(); + + let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = + globals.bind(&queue_handle, 4..=4, ()).ok()?; + let feedback = dmabuf.get_default_feedback(&queue_handle, ()); + + let mut state = DmabufProbeState { device: None }; + + event_queue.roundtrip(&mut state).ok()?; + + feedback.destroy(); + dmabuf.destroy(); + + crate::linux::compositor_gpu_hint_from_dev_t(state.device?) +} + impl Dispatch for WaylandClientStatePtr { fn event( this: &mut Self, diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index c3cbcb9ddd99017696b98181b92e6ea60a4a9fee..4642fd405c7900e20e912df308a033773fb07cd0 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -36,7 +36,7 @@ use crate::{ platform::{ PlatformAtlas, PlatformInputHandler, PlatformWindow, linux::wayland::{display::WaylandDisplay, serial::SerialKind}, - wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig}, + wgpu::{CompositorGpuHint, WgpuContext, WgpuRenderer, WgpuSurfaceConfig}, }, }; use crate::{WindowKind, scene::Scene}; @@ -320,6 +320,7 @@ impl WaylandWindowState { client: WaylandClientStatePtr, globals: Globals, gpu_context: &mut Option, + compositor_gpu: Option, options: WindowParams, parent: Option, ) -> anyhow::Result { @@ -340,7 +341,7 @@ impl WaylandWindowState { }, transparent: true, }; - WgpuRenderer::new(gpu_context, &raw_window, config)? + WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? }; if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state { @@ -490,6 +491,7 @@ impl WaylandWindow { handle: AnyWindowHandle, globals: Globals, gpu_context: &mut Option, + compositor_gpu: Option, client: WaylandClientStatePtr, params: WindowParams, appearance: WindowAppearance, @@ -517,6 +519,7 @@ impl WaylandWindow { client, globals, gpu_context, + compositor_gpu, params, parent, )?)), diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 40c6b71a7aa34341f26c7dcfd4e13d1e702aaf3b..b35194d15b0563b53d8645376de25cccc3884e02 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -31,7 +31,7 @@ use x11rb::{ AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _, EventMask, ModMask, Visibility, }, - protocol::{Event, randr, render, xinput, xkb, xproto}, + protocol::{Event, dri3, randr, render, xinput, xkb, xproto}, resource_manager::Database, wrapper::ConnectionExt as _, xcb_ffi::XCBConnection, @@ -48,6 +48,7 @@ use super::{ pressed_button_from_mask, }; +use crate::platform::wgpu::{CompositorGpuHint, WgpuContext}; use crate::platform::{ LinuxCommon, PlatformWindow, linux::{ @@ -178,6 +179,7 @@ pub struct X11ClientState { pub(crate) current_count: usize, pub(crate) gpu_context: Option, + pub(crate) compositor_gpu: Option, pub(crate) scale_factor: f32, @@ -430,6 +432,9 @@ impl X11Client { let clipboard = Clipboard::new().context("Failed to initialize clipboard")?; + let screen = &xcb_connection.setup().roots[x_root_index]; + let compositor_gpu = detect_compositor_gpu(&xcb_connection, screen); + let xcb_connection = Rc::new(xcb_connection); let ximc = X11rbClient::init(Rc::clone(&xcb_connection), x_root_index, None).ok(); @@ -490,6 +495,7 @@ impl X11Client { last_location: Point::new(px(0.0), px(0.0)), current_count: 0, gpu_context: None, + compositor_gpu, scale_factor, xkb_context, @@ -1511,11 +1517,13 @@ impl LinuxClient for X11Client { let atoms = state.atoms; let scale_factor = state.scale_factor; let appearance = state.common.appearance; + let compositor_gpu = state.compositor_gpu.take(); let window = X11Window::new( handle, X11ClientStatePtr(Rc::downgrade(&self.0)), state.common.foreground_executor.clone(), &mut state.gpu_context, + compositor_gpu, params, &xcb_connection, client_side_decorations_supported, @@ -1965,7 +1973,30 @@ fn fp3232_to_f32(value: xinput::Fp3232) -> f32 { value.integral as f32 + value.frac as f32 / u32::MAX as f32 } -fn check_compositor_present(xcb_connection: &XCBConnection, root: u32) -> bool { +fn detect_compositor_gpu( + xcb_connection: &XCBConnection, + screen: &xproto::Screen, +) -> Option { + use std::os::fd::AsRawFd; + use std::os::unix::fs::MetadataExt; + + xcb_connection + .extension_information(dri3::X11_EXTENSION_NAME) + .ok()??; + + let reply = dri3::open(xcb_connection, screen.root, 0) + .ok()? + .reply() + .ok()?; + let fd = reply.device_fd; + + let path = format!("/proc/self/fd/{}", fd.as_raw_fd()); + let metadata = std::fs::metadata(&path).ok()?; + + crate::linux::compositor_gpu_hint_from_dev_t(metadata.rdev()) +} + +fn check_compositor_present(xcb_connection: &XCBConnection, root: xproto::Window) -> bool { // Method 1: Check for _NET_WM_CM_S{root} let atom_name = format!("_NET_WM_CM_S{}", root); let atom1 = get_reply( diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index ff0a6052da8b7e30d424484c4d2b7deb7733d24a..78fe980b5cbedc3512800204c663da20e9028eab 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -1,7 +1,7 @@ use anyhow::{Context as _, anyhow}; use x11rb::connection::RequestConnection; -use crate::platform::wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; +use crate::platform::wgpu::{CompositorGpuHint, WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; use crate::{ AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, @@ -393,6 +393,7 @@ impl X11WindowState { client: X11ClientStatePtr, executor: ForegroundExecutor, gpu_context: &mut Option, + compositor_gpu: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -695,7 +696,7 @@ impl X11WindowState { // too transparent: false, }; - WgpuRenderer::new(gpu_context, &raw_window, config)? + WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? }; let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?); @@ -800,6 +801,7 @@ impl X11Window { client: X11ClientStatePtr, executor: ForegroundExecutor, gpu_context: &mut Option, + compositor_gpu: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -816,6 +818,7 @@ impl X11Window { client, executor, gpu_context, + compositor_gpu, params, xcb, client_side_decorations_supported, diff --git a/crates/gpui/src/platform/wgpu/wgpu_context.rs b/crates/gpui/src/platform/wgpu/wgpu_context.rs index 3a1171239a5725dd3084c27a7e624b8bc1d351c1..206239e6de72f94989c2665df0ade650bc2531ef 100644 --- a/crates/gpui/src/platform/wgpu/wgpu_context.rs +++ b/crates/gpui/src/platform/wgpu/wgpu_context.rs @@ -10,8 +10,18 @@ pub struct WgpuContext { dual_source_blending: bool, } +#[cfg(not(target_family = "wasm"))] +pub struct CompositorGpuHint { + pub vendor_id: u32, + pub device_id: u32, +} + impl WgpuContext { - pub fn new(instance: wgpu::Instance, surface: &wgpu::Surface<'_>) -> anyhow::Result { + pub fn new( + instance: wgpu::Instance, + surface: &wgpu::Surface<'_>, + compositor_gpu: Option, + ) -> anyhow::Result { let device_id_filter = match std::env::var("ZED_DEVICE_ID") { Ok(val) => parse_pci_id(&val) .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable") @@ -24,24 +34,15 @@ impl WgpuContext { } }; - let adapter = smol::block_on(Self::select_adapter( - &instance, - device_id_filter, - Some(surface), - ))?; - - let caps = surface.get_capabilities(&adapter); - if caps.formats.is_empty() { - let info = adapter.get_info(); - anyhow::bail!( - "No adapter compatible with the display surface could be found. \ - Best candidate {:?} (backend={:?}, device={:#06x}) reports no \ - supported surface formats.", - info.name, - info.backend, - info.device, - ); - } + // Select an adapter by actually testing surface configuration with the real device. + // This is the only reliable way to determine compatibility on hybrid GPU systems. + let (adapter, device, queue, dual_source_blending) = + smol::block_on(Self::select_adapter_and_device( + &instance, + device_id_filter, + surface, + compositor_gpu.as_ref(), + ))?; log::info!( "Selected GPU adapter: {:?} ({:?})", @@ -49,8 +50,6 @@ impl WgpuContext { adapter.get_info().backend ); - let (device, queue, dual_source_blending) = Self::create_device(&adapter)?; - Ok(Self { instance, adapter, @@ -84,13 +83,15 @@ impl WgpuContext { Ok(()) } - fn create_device(adapter: &wgpu::Adapter) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> { - let dual_source_blending_available = adapter + async fn create_device( + adapter: &wgpu::Adapter, + ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> { + let dual_source_blending = adapter .features() .contains(wgpu::Features::DUAL_SOURCE_BLENDING); let mut required_features = wgpu::Features::empty(); - if dual_source_blending_available { + if dual_source_blending { required_features |= wgpu::Features::DUAL_SOURCE_BLENDING; } else { log::warn!( @@ -99,8 +100,8 @@ impl WgpuContext { ); } - let (device, queue) = smol::block_on( - adapter.request_device(&wgpu::DeviceDescriptor { + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { label: Some("gpui_device"), required_features, required_limits: wgpu::Limits::downlevel_defaults() @@ -109,76 +110,170 @@ impl WgpuContext { memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, experimental_features: wgpu::ExperimentalFeatures::disabled(), - }), - ) - .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?; + }) + .await + .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?; - Ok((device, queue, dual_source_blending_available)) + Ok((device, queue, dual_source_blending)) } - async fn select_adapter( + /// Select an adapter and create a device, testing that the surface can actually be configured. + /// This is the only reliable way to determine compatibility on hybrid GPU systems, where + /// adapters may report surface compatibility via get_capabilities() but fail when actually + /// configuring (e.g., NVIDIA reporting Vulkan Wayland support but failing because the + /// Wayland compositor runs on the Intel GPU). + async fn select_adapter_and_device( instance: &wgpu::Instance, device_id_filter: Option, - compatible_surface: Option<&wgpu::Surface<'_>>, - ) -> anyhow::Result { + surface: &wgpu::Surface<'_>, + compositor_gpu: Option<&CompositorGpuHint>, + ) -> anyhow::Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue, bool)> { + let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await; + + if adapters.is_empty() { + anyhow::bail!("No GPU adapters found"); + } + if let Some(device_id) = device_id_filter { - let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await; + log::info!("ZED_DEVICE_ID filter: {:#06x}", device_id); + } - if adapters.is_empty() { - anyhow::bail!("No GPU adapters found"); - } + // Sort adapters into a single priority order. Tiers (from highest to lowest): + // + // 1. ZED_DEVICE_ID match — explicit user override + // 2. Compositor GPU match — the GPU the display server is rendering on + // 3. Device type — WGPU HighPerformance order (Discrete > Integrated > + // Other > Virtual > Cpu). "Other" ranks above "Virtual" because + // backends like OpenGL may report real hardware as "Other". + // 4. Backend — prefer Vulkan/Metal/Dx12 over GL/etc. + adapters.sort_by_key(|adapter| { + let info = adapter.get_info(); + + // Backends like OpenGL report device=0 for all adapters, so + // device-based matching is only meaningful when non-zero. + let device_known = info.device != 0; - let mut non_matching_adapter_infos: Vec = Vec::new(); - - for adapter in adapters.into_iter() { - let info = adapter.get_info(); - if info.device == device_id { - if let Some(surface) = compatible_surface { - let caps = surface.get_capabilities(&adapter); - if caps.formats.is_empty() { - log::warn!( - "GPU matching ZED_DEVICE_ID={:#06x} ({}) is not compatible \ - with the display surface. Falling back to auto-selection.", - device_id, - info.name, - ); - break; - } - } + let user_override: u8 = match device_id_filter { + Some(id) if device_known && info.device == id => 0, + _ => 1, + }; + + let compositor_match: u8 = match compositor_gpu { + Some(hint) + if device_known + && info.vendor == hint.vendor_id + && info.device == hint.device_id => + { + 0 + } + _ => 1, + }; + + let type_priority: u8 = match info.device_type { + wgpu::DeviceType::DiscreteGpu => 0, + wgpu::DeviceType::IntegratedGpu => 1, + wgpu::DeviceType::Other => 2, + wgpu::DeviceType::VirtualGpu => 3, + wgpu::DeviceType::Cpu => 4, + }; + + let backend_priority: u8 = match info.backend { + wgpu::Backend::Vulkan => 0, + wgpu::Backend::Metal => 0, + wgpu::Backend::Dx12 => 0, + _ => 1, + }; + + ( + user_override, + compositor_match, + type_priority, + backend_priority, + ) + }); + + // Log all available adapters (in sorted order) + log::info!("Found {} GPU adapter(s):", adapters.len()); + for adapter in &adapters { + let info = adapter.get_info(); + log::info!( + " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})", + info.name, + info.vendor, + info.device, + info.backend, + info.device_type, + ); + } + + // Test each adapter by creating a device and configuring the surface + for adapter in adapters { + let info = adapter.get_info(); + log::info!("Testing adapter: {} ({:?})...", info.name, info.backend); + + match Self::try_adapter_with_surface(&adapter, surface).await { + Ok((device, queue, dual_source_blending)) => { + log::info!( + "Selected GPU (passed configuration test): {} ({:?})", + info.name, + info.backend + ); + return Ok((adapter, device, queue, dual_source_blending)); + } + Err(e) => { log::info!( - "Found GPU matching ZED_DEVICE_ID={:#06x}: {}", - device_id, - info.name + " Adapter {} ({:?}) failed: {}, trying next...", + info.name, + info.backend, + e ); - return Ok(adapter); - } else { - non_matching_adapter_infos.push(info); } } + } - log::warn!( - "No compatible GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:", - device_id - ); + anyhow::bail!("No GPU adapter found that can configure the display surface") + } - for info in &non_matching_adapter_infos { - log::warn!( - " - {} (device_id={:#06x}, backend={})", - info.name, - info.device, - info.backend - ); - } + /// Try to use an adapter with a surface by creating a device and testing configuration. + /// Returns the device and queue if successful, allowing them to be reused. + async fn try_adapter_with_surface( + adapter: &wgpu::Adapter, + surface: &wgpu::Surface<'_>, + ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> { + let caps = surface.get_capabilities(adapter); + if caps.formats.is_empty() { + anyhow::bail!("no compatible surface formats"); + } + if caps.alpha_modes.is_empty() { + anyhow::bail!("no compatible alpha modes"); } - instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface, - force_fallback_adapter: false, - }) - .await - .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}")) + // Create the real device with full features + let (device, queue, dual_source_blending) = Self::create_device(adapter).await?; + + // Use an error scope to capture any validation errors during configure + let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation); + + let test_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: caps.formats[0], + width: 64, + height: 64, + present_mode: wgpu::PresentMode::Fifo, + desired_maximum_frame_latency: 2, + alpha_mode: caps.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &test_config); + + // Check if there was a validation error + let error = error_scope.pop().await; + if let Some(e) = error { + anyhow::bail!("surface configuration failed: {e}"); + } + + Ok((device, queue, dual_source_blending)) } pub fn supports_dual_source_blending(&self) -> bool { diff --git a/crates/gpui/src/platform/wgpu/wgpu_renderer.rs b/crates/gpui/src/platform/wgpu/wgpu_renderer.rs index 6b00fa5f5b22c8175237b90d68608351e3fbe74b..a15c833d499e45fde7fbfe04db260fc1af443687 100644 --- a/crates/gpui/src/platform/wgpu/wgpu_renderer.rs +++ b/crates/gpui/src/platform/wgpu/wgpu_renderer.rs @@ -1,4 +1,4 @@ -use super::{WgpuAtlas, WgpuContext}; +use super::{CompositorGpuHint, WgpuAtlas, WgpuContext}; use crate::{ AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite, @@ -95,6 +95,7 @@ pub struct WgpuRenderer { queue: Arc, surface: wgpu::Surface<'static>, surface_config: wgpu::SurfaceConfiguration, + surface_configured: bool, pipelines: WgpuPipelines, bind_group_layouts: WgpuBindGroupLayouts, atlas: Arc, @@ -129,6 +130,7 @@ impl WgpuRenderer { gpu_context: &mut Option, window: &W, config: WgpuSurfaceConfig, + compositor_gpu: Option, ) -> anyhow::Result { let window_handle = window .window_handle() @@ -164,9 +166,17 @@ impl WgpuRenderer { context.check_compatible_with_surface(&surface)?; context } - None => gpu_context.insert(WgpuContext::new(instance, &surface)?), + None => gpu_context.insert(WgpuContext::new(instance, &surface, compositor_gpu)?), }; + Self::new_with_surface(context, surface, config) + } + + fn new_with_surface( + context: &WgpuContext, + surface: wgpu::Surface<'static>, + config: WgpuSurfaceConfig, + ) -> anyhow::Result { let surface_caps = surface.get_capabilities(&context.adapter); let preferred_formats = [ wgpu::TextureFormat::Bgra8Unorm, @@ -242,6 +252,8 @@ impl WgpuRenderer { alpha_mode, view_formats: vec![], }; + // Configure the surface immediately. The adapter selection process already validated + // that this adapter can successfully configure this surface. surface.configure(&context.device, &surface_config); let queue = Arc::clone(&context.queue); @@ -341,6 +353,7 @@ impl WgpuRenderer { queue, surface, surface_config, + surface_configured: true, pipelines, bind_group_layouts, atlas, @@ -808,7 +821,9 @@ impl WgpuRenderer { self.surface_config.width = clamped_width.max(1); self.surface_config.height = clamped_height.max(1); - self.surface.configure(&self.device, &self.surface_config); + if self.surface_configured { + self.surface.configure(&self.device, &self.surface_config); + } // Invalidate intermediate textures - they will be lazily recreated // in draw() after we confirm the surface is healthy. This avoids @@ -859,7 +874,9 @@ impl WgpuRenderer { if new_alpha_mode != self.surface_config.alpha_mode { self.surface_config.alpha_mode = new_alpha_mode; - self.surface.configure(&self.device, &self.surface_config); + if self.surface_configured { + self.surface.configure(&self.device, &self.surface_config); + } self.pipelines = Self::create_pipelines( &self.device, &self.bind_group_layouts, @@ -902,7 +919,7 @@ impl WgpuRenderer { let frame = match self.surface.get_current_texture() { Ok(frame) => frame, Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { - self.surface.configure(&self.device, &self.surface_config); + self.surface_configured = false; return; } Err(e) => {