Detailed changes
@@ -360,7 +360,7 @@ pub fn init(cx: &mut App) {
Editor::new_file(workspace, &Default::default(), window, cx)
},
)
- .detach();
+ .detach_and_log_err(cx);
}
})
.on_action(move |_: &workspace::NewWindow, cx| {
@@ -375,7 +375,7 @@ pub fn init(cx: &mut App) {
Editor::new_file(workspace, &Default::default(), window, cx)
},
)
- .detach();
+ .detach_and_log_err(cx);
}
});
_ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
@@ -46,50 +46,6 @@ pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
-#[cfg(any(feature = "x11", feature = "wayland"))]
-pub trait ResultExt {
- type Ok;
-
- fn notify_err(self, msg: &'static str) -> Self::Ok;
-}
-
-#[cfg(any(feature = "x11", feature = "wayland"))]
-impl<T> ResultExt for anyhow::Result<T> {
- type Ok = T;
-
- fn notify_err(self, msg: &'static str) -> T {
- match self {
- Ok(v) => v,
- Err(e) => {
- use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
- use futures::executor::block_on;
-
- let proxy = block_on(NotificationProxy::new()).expect(msg);
-
- let notification_id = "dev.zed.Oops";
- block_on(
- proxy.add_notification(
- notification_id,
- Notification::new("Zed failed to launch")
- .body(Some(
- format!(
- "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
- )
- .as_str(),
- ))
- .priority(Priority::High)
- .icon(ashpd::desktop::Icon::with_names(&[
- "dialog-question-symbolic",
- ])),
- )
- ).expect(msg);
-
- panic!("{msg}");
- }
- }
- }
-}
-
pub(crate) trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
@@ -74,10 +74,10 @@ use super::{
};
use crate::linux::{
- DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, ResultExt as _,
- SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,
- is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,
- modifiers_from_xkb, open_uri_internal, read_fd, reveal_path_internal,
+ DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, SCROLL_LINES,
+ capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state, is_within_click_distance,
+ keystroke_from_xkb, keystroke_underlying_dead_key, modifiers_from_xkb, open_uri_internal,
+ read_fd, reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
cursor::Cursor,
@@ -201,7 +201,7 @@ pub struct Output {
pub(crate) struct WaylandClientState {
serial_tracker: SerialTracker,
globals: Globals,
- pub gpu_context: WgpuContext,
+ pub gpu_context: Option<WgpuContext>,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@@ -515,8 +515,7 @@ impl WaylandClient {
})
.unwrap();
- // This could be unified with the notification handling in zed/main:fail_to_open_window.
- let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
+ let gpu_context = None;
let seat = seat.unwrap();
let globals = Globals::new(
@@ -715,13 +714,14 @@ impl LinuxClient for WaylandClient {
let parent = state.keyboard_focused_window.clone();
+ let appearance = state.common.appearance;
let (window, surface_id) = WaylandWindow::new(
handle,
state.globals.clone(),
- &state.gpu_context,
+ &mut state.gpu_context,
WaylandClientStatePtr(Rc::downgrade(&self.0)),
params,
- state.common.appearance,
+ appearance,
parent,
)?;
state.windows.insert(surface_id, window.0.clone());
@@ -317,7 +317,7 @@ impl WaylandWindowState {
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
globals: Globals,
- gpu_context: &WgpuContext,
+ gpu_context: &mut Option<WgpuContext>,
options: WindowParams,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
@@ -481,7 +481,7 @@ impl WaylandWindow {
pub fn new(
handle: AnyWindowHandle,
globals: Globals,
- gpu_context: &WgpuContext,
+ gpu_context: &mut Option<WgpuContext>,
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
@@ -1230,7 +1230,10 @@ impl PlatformWindow for WaylandWindow {
fn is_subpixel_rendering_supported(&self) -> bool {
let client = self.borrow().client.get_client();
let state = client.borrow();
- state.gpu_context.supports_dual_source_blending()
+ state
+ .gpu_context
+ .as_ref()
+ .is_some_and(|ctx| ctx.supports_dual_source_blending())
}
fn minimize(&self) {
@@ -49,10 +49,9 @@ use super::{
};
use crate::linux::{
- DEFAULT_CURSOR_ICON_NAME, LinuxClient, ResultExt as _, capslock_from_xkb,
- cursor_style_to_icon_names, get_xkb_compose_state, is_within_click_distance,
- keystroke_from_xkb, keystroke_underlying_dead_key, log_cursor_icon_warning, modifiers_from_xkb,
- open_uri_internal,
+ DEFAULT_CURSOR_ICON_NAME, LinuxClient, capslock_from_xkb, cursor_style_to_icon_names,
+ get_xkb_compose_state, is_within_click_distance, keystroke_from_xkb,
+ keystroke_underlying_dead_key, log_cursor_icon_warning, modifiers_from_xkb, open_uri_internal,
platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
reveal_path_internal,
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
@@ -178,7 +177,7 @@ pub struct X11ClientState {
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
- pub(crate) gpu_context: WgpuContext,
+ pub(crate) gpu_context: Option<WgpuContext>,
pub(crate) scale_factor: f32,
@@ -421,8 +420,6 @@ impl X11Client {
.to_string();
let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
- let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
-
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
.context("Failed to create resource database")?;
let scale_factor = get_scale_factor(&xcb_connection, &resource_database, x_root_index);
@@ -492,7 +489,7 @@ impl X11Client {
last_mouse_button: None,
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
- gpu_context,
+ gpu_context: None,
scale_factor,
xkb_context,
@@ -1511,19 +1508,25 @@ impl LinuxClient for X11Client {
.generate_id()
.context("X11: Failed to generate window ID")?;
+ let xcb_connection = state.xcb_connection.clone();
+ let client_side_decorations_supported = state.client_side_decorations_supported;
+ let x_root_index = state.x_root_index;
+ let atoms = state.atoms;
+ let scale_factor = state.scale_factor;
+ let appearance = state.common.appearance;
let window = X11Window::new(
handle,
X11ClientStatePtr(Rc::downgrade(&self.0)),
state.common.foreground_executor.clone(),
- &state.gpu_context,
+ &mut state.gpu_context,
params,
- &state.xcb_connection,
- state.client_side_decorations_supported,
- state.x_root_index,
+ &xcb_connection,
+ client_side_decorations_supported,
+ x_root_index,
x_window,
- &state.atoms,
- state.scale_factor,
- state.common.appearance,
+ &atoms,
+ scale_factor,
+ appearance,
parent_window,
)?;
check_reply(
@@ -391,7 +391,7 @@ impl X11WindowState {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
- gpu_context: &WgpuContext,
+ gpu_context: &mut Option<WgpuContext>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -798,7 +798,7 @@ impl X11Window {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
- gpu_context: &WgpuContext,
+ gpu_context: &mut Option<WgpuContext>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -1465,7 +1465,10 @@ impl PlatformWindow for X11Window {
.upgrade()
.map(|ref_cell| {
let state = ref_cell.borrow();
- state.gpu_context.supports_dual_source_blending()
+ state
+ .gpu_context
+ .as_ref()
+ .is_some_and(|ctx| ctx.supports_dual_source_blending())
})
.unwrap_or_default()
}
@@ -11,7 +11,7 @@ pub struct WgpuContext {
}
impl WgpuContext {
- pub fn new() -> anyhow::Result<Self> {
+ pub fn new(instance: wgpu::Instance, surface: &wgpu::Surface<'_>) -> anyhow::Result<Self> {
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,14 +24,24 @@ impl WgpuContext {
}
};
- let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
- backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
- flags: wgpu::InstanceFlags::default(),
- backend_options: wgpu::BackendOptions::default(),
- memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
- });
-
- let adapter = smol::block_on(Self::select_adapter(&instance, device_id_filter))?;
+ 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,
+ );
+ }
log::info!(
"Selected GPU adapter: {:?} ({:?})",
@@ -39,6 +49,42 @@ impl WgpuContext {
adapter.get_info().backend
);
+ let (device, queue, dual_source_blending) = Self::create_device(&adapter)?;
+
+ Ok(Self {
+ instance,
+ adapter,
+ device: Arc::new(device),
+ queue: Arc::new(queue),
+ dual_source_blending,
+ })
+ }
+
+ pub fn instance() -> wgpu::Instance {
+ wgpu::Instance::new(&wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
+ flags: wgpu::InstanceFlags::default(),
+ backend_options: wgpu::BackendOptions::default(),
+ memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
+ })
+ }
+
+ pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
+ let caps = surface.get_capabilities(&self.adapter);
+ if caps.formats.is_empty() {
+ let info = self.adapter.get_info();
+ anyhow::bail!(
+ "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
+ display surface for this window.",
+ info.name,
+ info.backend,
+ info.device,
+ );
+ }
+ Ok(())
+ }
+
+ fn create_device(adapter: &wgpu::Adapter) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> {
let dual_source_blending_available = adapter
.features()
.contains(wgpu::Features::DUAL_SOURCE_BLENDING);
@@ -63,18 +109,13 @@ impl WgpuContext {
}))
.map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
- Ok(Self {
- instance,
- adapter,
- device: Arc::new(device),
- queue: Arc::new(queue),
- dual_source_blending: dual_source_blending_available,
- })
+ Ok((device, queue, dual_source_blending_available))
}
async fn select_adapter(
instance: &wgpu::Instance,
device_id_filter: Option<u32>,
+ compatible_surface: Option<&wgpu::Surface<'_>>,
) -> anyhow::Result<wgpu::Adapter> {
if let Some(device_id) = device_id_filter {
let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
@@ -88,6 +129,18 @@ impl WgpuContext {
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;
+ }
+ }
log::info!(
"Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
device_id,
@@ -100,7 +153,7 @@ impl WgpuContext {
}
log::warn!(
- "No GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
+ "No compatible GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
device_id
);
@@ -117,7 +170,7 @@ impl WgpuContext {
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::None,
- compatible_surface: None,
+ compatible_surface,
force_fallback_adapter: false,
})
.await
@@ -124,7 +124,7 @@ impl WgpuRenderer {
/// The caller must ensure that the window handle remains valid for the lifetime
/// of the returned renderer.
pub fn new<W: HasWindowHandle + HasDisplayHandle>(
- context: &WgpuContext,
+ gpu_context: &mut Option<WgpuContext>,
window: &W,
config: WgpuSurfaceConfig,
) -> anyhow::Result<Self> {
@@ -140,20 +140,32 @@ impl WgpuRenderer {
raw_window_handle: window_handle.as_raw(),
};
+ // Use the existing context's instance if available, otherwise create a new one.
+ // The surface must be created with the same instance that will be used for
+ // adapter selection, otherwise wgpu will panic.
+ let instance = gpu_context
+ .as_ref()
+ .map(|ctx| ctx.instance.clone())
+ .unwrap_or_else(WgpuContext::instance);
+
// Safety: The caller guarantees that the window handle is valid for the
// lifetime of this renderer. In practice, the RawWindow struct is created
// from the native window handles and the surface is dropped before the window.
let surface = unsafe {
- context
- .instance
+ instance
.create_surface_unsafe(target)
.map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
};
+ let context = match gpu_context {
+ Some(context) => {
+ context.check_compatible_with_surface(&surface)?;
+ context
+ }
+ None => gpu_context.insert(WgpuContext::new(instance, &surface)?),
+ };
+
let surface_caps = surface.get_capabilities(&context.adapter);
- // Prefer standard 8-bit non-sRGB formats that don't require special features.
- // Other formats like Rgba16Unorm require TEXTURE_FORMAT_16BIT_NORM which may
- // not be available on all devices.
let preferred_formats = [
wgpu::TextureFormat::Bgra8Unorm,
wgpu::TextureFormat::Rgba8Unorm,
@@ -163,26 +175,38 @@ impl WgpuRenderer {
.find(|f| surface_caps.formats.contains(f))
.copied()
.or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
- .unwrap_or(surface_caps.formats[0]);
+ .or_else(|| surface_caps.formats.first().copied())
+ .ok_or_else(|| {
+ anyhow::anyhow!(
+ "Surface reports no supported texture formats for adapter {:?}",
+ context.adapter.get_info().name
+ )
+ })?;
let pick_alpha_mode =
- |preferences: &[wgpu::CompositeAlphaMode]| -> wgpu::CompositeAlphaMode {
+ |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
preferences
.iter()
.find(|p| surface_caps.alpha_modes.contains(p))
.copied()
- .unwrap_or(surface_caps.alpha_modes[0])
+ .or_else(|| surface_caps.alpha_modes.first().copied())
+ .ok_or_else(|| {
+ anyhow::anyhow!(
+ "Surface reports no supported alpha modes for adapter {:?}",
+ context.adapter.get_info().name
+ )
+ })
};
let transparent_alpha_mode = pick_alpha_mode(&[
wgpu::CompositeAlphaMode::PreMultiplied,
wgpu::CompositeAlphaMode::Inherit,
- ]);
+ ])?;
let opaque_alpha_mode = pick_alpha_mode(&[
wgpu::CompositeAlphaMode::Opaque,
wgpu::CompositeAlphaMode::Inherit,
- ]);
+ ])?;
let alpha_mode = if config.transparent {
transparent_alpha_mode
@@ -1119,7 +1119,7 @@ fn register_actions(
Editor::new_file(workspace, &Default::default(), window, cx)
},
)
- .detach();
+ .detach_and_log_err(cx);
}
}
})
@@ -498,7 +498,7 @@ async fn open_workspaces(
workspace::open_new(open_options, app_state, cx, |workspace, window, cx| {
Editor::new_file(workspace, &Default::default(), window, cx)
})
- .detach();
+ .detach_and_log_err(cx);
});
}
return Ok(());