From a3b42ecc400403109d469e5cecb1ebc2e1ed792c Mon Sep 17 00:00:00 2001 From: Eval Exec Date: Mon, 25 May 2026 13:56:10 -0400 Subject: [PATCH] Use display-bound wgpu instance Create the GUI render instance from winit's display handle. This gives GLES presentation the Wayland display connection wgpu expects. Keep that instance in the render GPU context. Reuse it for secondary window surfaces. Use env-aware descriptors so WGPU_BACKEND is honored. Constraint: Scope to wgpu ownership and adapter selection Rejected: Replace renderer backend | too broad for this fix Confidence: medium Scope-risk: moderate Not-tested: Haswell HD 4600 under niri/Wayland --- .../src/backend/wgpu/backend.rs | 7 ++--- neomacs-display-runtime/src/lib.rs | 10 +++++++ .../src/render_thread/bootstrap.rs | 30 ++++++++++++------- .../src/render_thread/lifecycle.rs | 30 +++++++++++++------ .../src/render_thread/multi_window.rs | 7 ++--- .../src/render_thread/state.rs | 19 ++++++------ .../src/render_thread/window_events.rs | 2 +- neomacs-renderer-wgpu/src/renderer/mod.rs | 3 +- 8 files changed, 67 insertions(+), 41 deletions(-) diff --git a/neomacs-display-runtime/src/backend/wgpu/backend.rs b/neomacs-display-runtime/src/backend/wgpu/backend.rs index 36374b9a17..fdece9c527 100644 --- a/neomacs-display-runtime/src/backend/wgpu/backend.rs +++ b/neomacs-display-runtime/src/backend/wgpu/backend.rs @@ -159,8 +159,7 @@ impl WinitBackend { } // Create wgpu instance - let mut instance_descriptor = wgpu::InstanceDescriptor::new_without_display_handle(); - instance_descriptor.backends = wgpu::Backends::all(); + let instance_descriptor = crate::wgpu_instance_descriptor_from_env(); let instance = wgpu::Instance::new(instance_descriptor); // Request adapter without a surface (headless) @@ -349,8 +348,8 @@ impl WinitBackend { self.height = size.height; // Create wgpu instance - let mut instance_descriptor = wgpu::InstanceDescriptor::new_without_display_handle(); - instance_descriptor.backends = wgpu::Backends::all(); + let instance_descriptor = + crate::wgpu_instance_descriptor_with_display(event_loop.owned_display_handle()); let instance = wgpu::Instance::new(instance_descriptor); // Create surface - we need to use unsafe to create a surface from the window diff --git a/neomacs-display-runtime/src/lib.rs b/neomacs-display-runtime/src/lib.rs index 255a762f09..ba8e46a6a3 100644 --- a/neomacs-display-runtime/src/lib.rs +++ b/neomacs-display-runtime/src/lib.rs @@ -72,6 +72,16 @@ pub fn gpu_power_preference() -> wgpu::PowerPreference { } } +pub(crate) fn wgpu_instance_descriptor_from_env() -> wgpu::InstanceDescriptor { + wgpu::InstanceDescriptor::new_without_display_handle_from_env() +} + +pub(crate) fn wgpu_instance_descriptor_with_display( + display: winit::event_loop::OwnedDisplayHandle, +) -> wgpu::InstanceDescriptor { + wgpu::InstanceDescriptor::new_with_display_handle_from_env(Box::new(display)) +} + /// Initialize the display engine. /// /// Logging is initialized separately by the binary entry point via diff --git a/neomacs-display-runtime/src/render_thread/bootstrap.rs b/neomacs-display-runtime/src/render_thread/bootstrap.rs index 4e7f18af17..b0bb8fddad 100644 --- a/neomacs-display-runtime/src/render_thread/bootstrap.rs +++ b/neomacs-display-runtime/src/render_thread/bootstrap.rs @@ -1,10 +1,11 @@ use super::{ RenderApp, RenderUserEvent, SharedImageDimensions, SharedMonitorInfo, surface_readback, }; +use crate::render_thread::state::RenderGpuContext; use crate::thread_comm::{InputEvent, RenderComms}; use neomacs_renderer_wgpu::{WgpuGlyphAtlas, WgpuRenderer}; use std::sync::Arc; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; #[cfg(target_os = "linux")] use winit::platform::wayland::EventLoopBuilderExtWayland; #[cfg(target_os = "linux")] @@ -20,12 +21,16 @@ use crate::backend::wpe::sys::platform as plat; impl RenderApp { /// Initialize wgpu with the window - pub(super) fn init_wgpu(&mut self, window: Arc) { + pub(super) fn init_wgpu(&mut self, event_loop: &ActiveEventLoop, window: Arc) { tracing::info!("Initializing wgpu for render thread"); - // Create wgpu instance - let mut instance_descriptor = wgpu::InstanceDescriptor::new_without_display_handle(); - instance_descriptor.backends = wgpu::Backends::all(); + // GLES presentation needs the compositor/display handle, especially on Wayland. + let instance_descriptor = + crate::wgpu_instance_descriptor_with_display(event_loop.owned_display_handle()); + tracing::info!( + "wgpu requested backends: {:?}", + instance_descriptor.backends + ); let instance = wgpu::Instance::new(instance_descriptor); // Create surface from window @@ -135,11 +140,14 @@ impl RenderApp { format ); - self.adapter = Some(adapter); + self.gpu = Some(RenderGpuContext { + instance, + adapter, + device: device.clone(), + queue: queue.clone(), + }); self.surface = Some(surface); self.surface_config = Some(config); - self.device = Some(device.clone()); - self.queue = Some(queue); self.renderer = Some(renderer); self.glyph_atlas = Some(glyph_atlas); @@ -183,12 +191,12 @@ impl RenderApp { self.height = height; // Reconfigure surface - if let (Some(surface), Some(config), Some(device)) = - (&self.surface, &mut self.surface_config, &self.device) + if let (Some(surface), Some(config), Some(gpu)) = + (&self.surface, &mut self.surface_config, &self.gpu) { config.width = width; config.height = height; - surface.configure(device, config); + surface.configure(&gpu.device, config); } // Resize renderer diff --git a/neomacs-display-runtime/src/render_thread/lifecycle.rs b/neomacs-display-runtime/src/render_thread/lifecycle.rs index d6f0705fb6..d4edf9fcda 100644 --- a/neomacs-display-runtime/src/render_thread/lifecycle.rs +++ b/neomacs-display-runtime/src/render_thread/lifecycle.rs @@ -1,5 +1,7 @@ use super::RenderApp; -use super::state::{effective_window_scale_factor, window_size_from_emacs_pixels}; +use super::state::{ + RenderGpuContext, effective_window_scale_factor, window_size_from_emacs_pixels, +}; use super::x11_hints::apply_window_geometry_hints; use crate::thread_comm::InputEvent; use std::sync::Arc; @@ -125,7 +127,7 @@ impl RenderApp { ); // Initialize wgpu with the window - self.init_wgpu(window.clone()); + self.init_wgpu(event_loop, window.clone()); // Enable IME input for CJK and compose support window.set_ime_allowed(true); @@ -164,9 +166,13 @@ impl RenderApp { } // Process multi-window creates/destroys - if let (Some(device), Some(adapter)) = (&self.device, &self.adapter) { - self.multi_windows - .process_creates(event_loop, device, adapter); + if let Some(gpu) = &self.gpu { + self.multi_windows.process_creates( + event_loop, + &gpu.instance, + &gpu.device, + &gpu.adapter, + ); } self.multi_windows.process_destroys(); @@ -336,16 +342,22 @@ impl RenderApp { // Drop surface (holds wl_surface proxy if on Wayland) drop(self.surface.take()); self.surface_config = None; - // Drop device and queue - drop(self.device.take()); - drop(self.queue.take()); // Drop multi-window state (secondary surfaces) self.multi_windows.destroy_all(); // Leak the adapter to prevent eglTerminate crash on Wayland. // The adapter's Drop triggers eglTerminate → dri2_teardown_wayland which // SEGVs if the Wayland connection is already gone. Since we're exiting, // the OS will reclaim all GPU/EGL resources. - if let Some(adapter) = self.adapter.take() { + if let Some(gpu) = self.gpu.take() { + let RenderGpuContext { + instance, + adapter, + device, + queue, + } = gpu; + drop(device); + drop(queue); + drop(instance); std::mem::forget(adapter); } diff --git a/neomacs-display-runtime/src/render_thread/multi_window.rs b/neomacs-display-runtime/src/render_thread/multi_window.rs index 883202828e..9232c73321 100644 --- a/neomacs-display-runtime/src/render_thread/multi_window.rs +++ b/neomacs-display-runtime/src/render_thread/multi_window.rs @@ -120,6 +120,7 @@ impl MultiWindowManager { pub fn process_creates( &mut self, event_loop: &ActiveEventLoop, + instance: &wgpu::Instance, device: &wgpu::Device, adapter: &wgpu::Adapter, ) { @@ -143,11 +144,7 @@ impl MultiWindowManager { let scale_factor = effective_window_scale_factor(raw_scale_factor); let phys = window.inner_size(); - // Create surface for this window - let mut instance_descriptor = - wgpu::InstanceDescriptor::new_without_display_handle(); - instance_descriptor.backends = wgpu::Backends::all(); - let instance = wgpu::Instance::new(instance_descriptor); + // Create surface for this window using the primary display-bound instance. let surface = match instance.create_surface(window.clone()) { Ok(s) => s, Err(e) => { diff --git a/neomacs-display-runtime/src/render_thread/state.rs b/neomacs-display-runtime/src/render_thread/state.rs index d460e276f5..1804647cd9 100644 --- a/neomacs-display-runtime/src/render_thread/state.rs +++ b/neomacs-display-runtime/src/render_thread/state.rs @@ -186,6 +186,13 @@ pub(super) struct ImeCursorArea { pub(super) height: u32, } +pub(super) struct RenderGpuContext { + pub(super) instance: wgpu::Instance, + pub(super) adapter: wgpu::Adapter, + pub(super) device: Arc, + pub(super) queue: Arc, +} + pub(super) struct RenderApp { pub(super) comms: RenderComms, pub(super) window: Option>, @@ -195,12 +202,11 @@ pub(super) struct RenderApp { pub(super) title: String, pub(super) primary_geometry_hints: Option, - // wgpu state + // Shared wgpu context used by the primary surface and secondary windows. + pub(super) gpu: Option, pub(super) renderer: Option, pub(super) surface: Option>, pub(super) surface_config: Option, - pub(super) device: Option>, - pub(super) queue: Option>, pub(super) glyph_atlas: Option, // Face cache built from frame data @@ -256,9 +262,6 @@ pub(super) struct RenderApp { // Multi-window manager (secondary OS windows for top-level frames) pub(super) multi_windows: MultiWindowManager, - // wgpu adapter (needed for creating surfaces on new windows) - pub(super) adapter: Option, - // Child frames (posframe, which-key-posframe, etc.) pub(super) child_frames: ChildFrameManager, // Child frame visual style @@ -375,11 +378,10 @@ impl RenderApp { title, primary_geometry_hints: None, scale_factor: 1.0, + gpu: None, renderer: None, surface: None, surface_config: None, - device: None, - queue: None, glyph_atlas: None, faces: HashMap::new(), modifiers: 0, @@ -404,7 +406,6 @@ impl RenderApp { #[cfg(feature = "neo-term")] shared_terminals, multi_windows: MultiWindowManager::new(), - adapter: None, child_frames: ChildFrameManager::new(), child_frame_corner_radius: 8.0, child_frame_shadow_enabled: true, diff --git a/neomacs-display-runtime/src/render_thread/window_events.rs b/neomacs-display-runtime/src/render_thread/window_events.rs index e4f8ce74d2..70bacc9247 100644 --- a/neomacs-display-runtime/src/render_thread/window_events.rs +++ b/neomacs-display-runtime/src/render_thread/window_events.rs @@ -54,7 +54,7 @@ impl RenderApp { height: emacs_h, emacs_frame_id: 0, }); - } else if let Some(device) = self.device.clone() { + } else if let Some(device) = self.gpu.as_ref().map(|gpu| gpu.device.clone()) { if let Some(ws) = self.multi_windows.get_mut(emacs_fid) { ws.handle_resize(&device, size.width, size.height); let (emacs_w, emacs_h) = diff --git a/neomacs-renderer-wgpu/src/renderer/mod.rs b/neomacs-renderer-wgpu/src/renderer/mod.rs index 5bf6e409e1..6e6f894078 100644 --- a/neomacs-renderer-wgpu/src/renderer/mod.rs +++ b/neomacs-renderer-wgpu/src/renderer/mod.rs @@ -1295,8 +1295,7 @@ impl WgpuRenderer { height: u32, ) -> Result { // Create wgpu instance - let mut instance_descriptor = wgpu::InstanceDescriptor::new_without_display_handle(); - instance_descriptor.backends = wgpu::Backends::all(); + let instance_descriptor = wgpu::InstanceDescriptor::new_without_display_handle_from_env(); let instance = wgpu::Instance::new(instance_descriptor); // Request adapter