Detailed changes
@@ -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<gpui_wgpu::CompositorGpuHint> {
+ 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<u32> {
+ 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::*;
@@ -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<WgpuContext>,
+ pub compositor_gpu: Option<CompositorGpuHint>,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@@ -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<u64>,
+}
+
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for DmabufProbeState {
+ fn event(
+ _: &mut Self,
+ _: &wl_registry::WlRegistry,
+ _: wl_registry::Event,
+ _: &GlobalListContents,
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for DmabufProbeState {
+ fn event(
+ _: &mut Self,
+ _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
+ _: zwp_linux_dmabuf_v1::Event,
+ _: &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ()> for DmabufProbeState {
+ fn event(
+ state: &mut Self,
+ _: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
+ event: zwp_linux_dmabuf_feedback_v1::Event,
+ _: &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ 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<CompositorGpuHint> {
+ let connection = Connection::connect_to_env().ok()?;
+ let (globals, mut event_queue) = registry_queue_init::<DmabufProbeState>(&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<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
fn event(
this: &mut Self,
@@ -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<WgpuContext>,
+ compositor_gpu: Option<CompositorGpuHint>,
options: WindowParams,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
@@ -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<WgpuContext>,
+ compositor_gpu: Option<CompositorGpuHint>,
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
@@ -517,6 +519,7 @@ impl WaylandWindow {
client,
globals,
gpu_context,
+ compositor_gpu,
params,
parent,
)?)),
@@ -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<WgpuContext>,
+ pub(crate) compositor_gpu: Option<CompositorGpuHint>,
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<CompositorGpuHint> {
+ 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(
@@ -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<WgpuContext>,
+ compositor_gpu: Option<CompositorGpuHint>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
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<WgpuContext>,
+ compositor_gpu: Option<CompositorGpuHint>,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -816,6 +818,7 @@ impl X11Window {
client,
executor,
gpu_context,
+ compositor_gpu,
params,
xcb,
client_side_decorations_supported,
@@ -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<Self> {
+ pub fn new(
+ instance: wgpu::Instance,
+ surface: &wgpu::Surface<'_>,
+ compositor_gpu: Option<CompositorGpuHint>,
+ ) -> 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,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<u32>,
- compatible_surface: Option<&wgpu::Surface<'_>>,
- ) -> anyhow::Result<wgpu::Adapter> {
+ 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<wgpu::AdapterInfo> = 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 {
@@ -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<wgpu::Queue>,
surface: wgpu::Surface<'static>,
surface_config: wgpu::SurfaceConfiguration,
+ surface_configured: bool,
pipelines: WgpuPipelines,
bind_group_layouts: WgpuBindGroupLayouts,
atlas: Arc<WgpuAtlas>,
@@ -129,6 +130,7 @@ impl WgpuRenderer {
gpu_context: &mut Option<WgpuContext>,
window: &W,
config: WgpuSurfaceConfig,
+ compositor_gpu: Option<CompositorGpuHint>,
) -> anyhow::Result<Self> {
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<Self> {
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) => {