From 54ef2e65fe289a019d6a36198d5618fceefc7709 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 2 Mar 2026 13:30:40 -0700 Subject: [PATCH] wGPU: Select more specifically (#50528) This uses the compositor hints if available to pick the best GPU. If none is available, it tries each GPU in turn, and the first that actually works is chosen Release Notes: - Linux: Select a more appropriate GPU --------- Co-authored-by: John Tur --- crates/gpui_linux/Cargo.toml | 1 + crates/gpui_linux/src/linux/platform.rs | 40 +++ crates/gpui_linux/src/linux/wayland/client.rs | 74 ++++- crates/gpui_linux/src/linux/wayland/window.rs | 7 +- crates/gpui_linux/src/linux/x11/client.rs | 36 ++- crates/gpui_linux/src/linux/x11/window.rs | 7 +- crates/gpui_wgpu/src/wgpu_context.rs | 256 ++++++++++++------ crates/gpui_wgpu/src/wgpu_renderer.rs | 27 +- crates/zed/src/main.rs | 2 +- 9 files changed, 357 insertions(+), 93 deletions(-) diff --git a/crates/gpui_linux/Cargo.toml b/crates/gpui_linux/Cargo.toml index d1a3ef0bd6954e3527a4544ad8abe35fde0bf3d9..7d021e0cecef898641516de21848c50b57728032 100644 --- a/crates/gpui_linux/Cargo.toml +++ b/crates/gpui_linux/Cargo.toml @@ -127,6 +127,7 @@ x11rb = { version = "0.13.1", features = [ "cursor", "resource_manager", "sync", + "dri3", ], optional = true } # WARNING: If you change this, you must also publish a new version of zed-xim to crates.io xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8", features = [ diff --git a/crates/gpui_linux/src/linux/platform.rs b/crates/gpui_linux/src/linux/platform.rs index dfb37afda255a7e4297af0c3a6ac2dfa8b6d1849..3be017b9409d4bc4b04f3065e32fff3ce7388d94 100644 --- a/crates/gpui_linux/src/linux/platform.rs +++ b/crates/gpui_linux/src/linux/platform.rs @@ -1036,6 +1036,46 @@ pub(super) fn capslock_from_xkb(keymap_state: &State) -> gpui::Capslock { gpui::Capslock { on } } +/// 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_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index a810a00af642c3a252a9a144b884837f82eac7e7..b49e269a72459d52c13c21b8d1a474ab310dbffd 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -95,7 +95,10 @@ use gpui::{ ScrollDelta, ScrollWheelEvent, SharedString, Size, TaskTiming, TouchPhase, WindowParams, point, profiler, px, size, }; -use gpui_wgpu::WgpuContext; +use gpui_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 const MIN_KEYCODE: u32 = 8; @@ -202,6 +205,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, @@ -515,6 +519,7 @@ impl WaylandClient { }) .unwrap(); + let compositor_gpu = detect_compositor_gpu(); let gpu_context = None; let seat = seat.unwrap(); @@ -571,6 +576,7 @@ impl WaylandClient { serial_tracker: SerialTracker::new(), globals, gpu_context, + compositor_gpu, wl_seat: seat, wl_pointer: None, wl_keyboard: None, @@ -715,10 +721,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, @@ -904,6 +912,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_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index f464053ed7d0d9580a255ba0db5cd9e2befc7731..3d7f1002b8d2861b1d92056c21f931cc5e7b1367 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -34,7 +34,7 @@ use gpui::{ WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px, size, }; -use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; +use gpui_wgpu::{CompositorGpuHint, WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; #[derive(Default)] pub(crate) struct Callbacks { @@ -318,6 +318,7 @@ impl WaylandWindowState { client: WaylandClientStatePtr, globals: Globals, gpu_context: &mut Option, + compositor_gpu: Option, options: WindowParams, parent: Option, ) -> anyhow::Result { @@ -338,7 +339,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 { @@ -488,6 +489,7 @@ impl WaylandWindow { handle: AnyWindowHandle, globals: Globals, gpu_context: &mut Option, + compositor_gpu: Option, client: WaylandClientStatePtr, params: WindowParams, appearance: WindowAppearance, @@ -515,6 +517,7 @@ impl WaylandWindow { client, globals, gpu_context, + compositor_gpu, params, parent, )?)), diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 7e3f67c9bf5fe3176f3badd9b33375ffdeb9dc19..3a970d9f72e1dc82215fc0d11297d222835df431 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/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, @@ -64,7 +64,7 @@ use gpui::{ PlatformKeyboardLayout, PlatformWindow, Point, RequestFrameOptions, ScrollDelta, Size, TouchPhase, WindowParams, point, px, }; -use gpui_wgpu::WgpuContext; +use gpui_wgpu::{CompositorGpuHint, WgpuContext}; /// Value for DeviceId parameters which selects all devices. pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0; @@ -178,6 +178,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 +431,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 +494,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, @@ -1514,11 +1519,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, @@ -1976,7 +1983,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_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 89084189d7cae90cee47012f7b7b700abce11bfe..c70f2484a895fd0878be41059f8a5ceed4b12abd 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -9,7 +9,7 @@ use gpui::{ Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowDecorations, WindowKind, WindowParams, px, }; -use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; +use gpui_wgpu::{CompositorGpuHint, WgpuContext, WgpuRenderer, WgpuSurfaceConfig}; use collections::FxHashSet; use raw_window_handle as rwh; @@ -392,6 +392,7 @@ impl X11WindowState { client: X11ClientStatePtr, executor: ForegroundExecutor, gpu_context: &mut Option, + compositor_gpu: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -679,7 +680,7 @@ impl X11WindowState { // too transparent: false, }; - WgpuRenderer::new(gpu_context, &raw_window, config)? + WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? }; // Set max window size hints based on the GPU's maximum texture dimension. @@ -803,6 +804,7 @@ impl X11Window { client: X11ClientStatePtr, executor: ForegroundExecutor, gpu_context: &mut Option, + compositor_gpu: Option, params: WindowParams, xcb: &Rc, client_side_decorations_supported: bool, @@ -819,6 +821,7 @@ impl X11Window { client, executor, gpu_context, + compositor_gpu, params, xcb, client_side_decorations_supported, diff --git a/crates/gpui_wgpu/src/wgpu_context.rs b/crates/gpui_wgpu/src/wgpu_context.rs index 3a1171239a5725dd3084c27a7e624b8bc1d351c1..2127e1aa927f1c9db20956caf4c2dc4fc32d0c71 100644 --- a/crates/gpui_wgpu/src/wgpu_context.rs +++ b/crates/gpui_wgpu/src/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,171 @@ 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. + #[cfg(not(target_family = "wasm"))] + 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_wgpu/src/wgpu_renderer.rs b/crates/gpui_wgpu/src/wgpu_renderer.rs index 43b0c171e242c0a1e335869e899a0bc95fed3e46..d8ed5155c9d652d8b8f24d66d26447a744afa5b0 100644 --- a/crates/gpui_wgpu/src/wgpu_renderer.rs +++ b/crates/gpui_wgpu/src/wgpu_renderer.rs @@ -1,3 +1,5 @@ +#[cfg(not(target_family = "wasm"))] +use crate::CompositorGpuHint; use crate::{WgpuAtlas, WgpuContext}; use bytemuck::{Pod, Zeroable}; use gpui::{ @@ -95,6 +97,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, @@ -131,6 +134,7 @@ impl WgpuRenderer { gpu_context: &mut Option, window: &W, config: WgpuSurfaceConfig, + compositor_gpu: Option, ) -> anyhow::Result { let window_handle = window .window_handle() @@ -166,9 +170,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, @@ -244,6 +256,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); @@ -350,6 +364,7 @@ impl WgpuRenderer { queue, surface, surface_config, + surface_configured: true, pipelines, bind_group_layouts, atlas, @@ -819,7 +834,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 @@ -870,7 +887,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, @@ -927,7 +946,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) => { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 7cf238e9dda22f1cd60056ffd3556cecdf3fb231..88ae84f1d4c1437aadfe86cd955f95cf2c7f3065 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -276,7 +276,7 @@ fn main() { zlog::init(); - if stdout_is_a_pty() { + if true { zlog::init_output_stdout(); } else { let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));