diff --git a/crates/adapters/src/rendering/egl/context_factory.rs b/crates/adapters/src/rendering/egl/context_factory.rs index ba62713..6dabe7d 100644 --- a/crates/adapters/src/rendering/egl/context_factory.rs +++ b/crates/adapters/src/rendering/egl/context_factory.rs @@ -1,208 +1,57 @@ use super::context::EGLContext; +use super::render_context_manager::RenderContextManager; use crate::errors::{EGLError, LayerShikaError, Result}; use glutin::{ - api::egl::{ - config::Config, - context::{NotCurrentContext, PossiblyCurrentContext}, - display::Display, - surface::Surface, - }, - config::ConfigTemplateBuilder, + api::egl::{config::Config, display::Display, surface::Surface}, context::ContextAttributesBuilder, prelude::*, surface::{SurfaceAttributesBuilder, WindowSurface}, }; -use log::{debug, info}; -use raw_window_handle::{ - RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, -}; +use log::info; +use raw_window_handle::{RawWindowHandle, WaylandWindowHandle}; use slint::PhysicalSize; -use std::{cell::RefCell, ffi::c_void, num::NonZeroU32, ptr::NonNull, rc::Rc}; +use std::{ffi::c_void, num::NonZeroU32, ptr::NonNull, rc::Rc}; use wayland_client::backend::ObjectId; pub struct RenderContextFactory { - shared_state: RefCell>, -} - -struct SharedRenderState { - display: Display, - config: Config, - primary_context: PossiblyCurrentContext, + manager: Rc, } impl RenderContextFactory { #[must_use] - pub fn new() -> Rc { - Rc::new(Self { - shared_state: RefCell::new(None), - }) + pub fn new(manager: Rc) -> Rc { + Rc::new(Self { manager }) } - pub fn create_context( - &self, - display_id: &ObjectId, - surface_id: &ObjectId, - size: PhysicalSize, - ) -> Result { - let mut state = self.shared_state.borrow_mut(); + pub fn create_context(&self, surface_id: &ObjectId, size: PhysicalSize) -> Result { + info!("Creating shared EGL context from root context manager"); - if state.is_none() { - info!("Creating primary EGL context (will be shared with subsequent contexts)"); - let new_state = self.create_primary_context(display_id, surface_id, size)?; - *state = Some(new_state); + let context_attributes = + ContextAttributesBuilder::default().with_sharing(self.manager.root_context()); + + let not_current = unsafe { + self.manager + .display() + .create_context(self.manager.config(), &context_attributes.build(None)) } - - let Some(shared_state) = state.as_ref() else { - return Err(LayerShikaError::InvalidInput { - message: "Shared state initialization failed".into(), - }); - }; - - if shared_state.primary_context.is_current() { - debug!("Creating shared context while primary is current"); - self.create_shared_context_from_current( - &shared_state.display, - &shared_state.config, - &shared_state.primary_context, - surface_id, - size, - ) - } else { - debug!("Creating shared context (primary not current)"); - self.create_shared_context( - &shared_state.display, - &shared_state.config, - &shared_state.primary_context, - surface_id, - size, - ) - } - } - - #[allow(clippy::unused_self)] - fn create_primary_context( - &self, - display_id: &ObjectId, - surface_id: &ObjectId, - size: PhysicalSize, - ) -> Result { - let display_handle = create_wayland_display_handle(display_id)?; - let display = unsafe { Display::new(display_handle) } - .map_err(|e| EGLError::DisplayCreation { source: e.into() })?; - - let config_template = ConfigTemplateBuilder::default(); - let config = select_config(&display, config_template)?; - - let context_attributes = ContextAttributesBuilder::default(); - let not_current = create_context(&display, &config, context_attributes)?; + .map_err(|e| EGLError::ContextCreation { source: e.into() })?; let surface_handle = create_surface_handle(surface_id)?; - let surface = create_surface(&display, &config, surface_handle, size)?; - - let primary_context = not_current - .make_current(&surface) - .map_err(|e| EGLError::MakeCurrent { source: e.into() })?; - - info!("Primary EGL context created successfully"); - - Ok(SharedRenderState { - display, - config, - primary_context, - }) - } - - #[allow(clippy::unused_self)] - fn create_shared_context( - &self, - display: &Display, - config: &Config, - share_context: &PossiblyCurrentContext, - surface_id: &ObjectId, - size: PhysicalSize, - ) -> Result { - let context_attributes = ContextAttributesBuilder::default().with_sharing(share_context); - - let not_current = - unsafe { display.create_context(config, &context_attributes.build(None)) } - .map_err(|e| EGLError::ContextCreation { source: e.into() })?; - - let surface_handle = create_surface_handle(surface_id)?; - let surface = create_surface(display, config, surface_handle, size)?; + let surface = create_surface( + self.manager.display(), + self.manager.config(), + surface_handle, + size, + )?; let context = not_current .make_current(&surface) .map_err(|e| EGLError::MakeCurrent { source: e.into() })?; - info!("Shared EGL context created successfully"); + info!("Shared EGL context created successfully from root manager"); Ok(EGLContext::from_raw(surface, context)) } - - #[allow(clippy::unused_self)] - fn create_shared_context_from_current( - &self, - display: &Display, - config: &Config, - share_context: &PossiblyCurrentContext, - surface_id: &ObjectId, - size: PhysicalSize, - ) -> Result { - let context_attributes = ContextAttributesBuilder::default().with_sharing(share_context); - - let not_current = - unsafe { display.create_context(config, &context_attributes.build(None)) } - .map_err(|e| EGLError::ContextCreation { source: e.into() })?; - - let surface_handle = create_surface_handle(surface_id)?; - let surface = create_surface(display, config, surface_handle, size)?; - - let context = not_current - .make_current(&surface) - .map_err(|e| EGLError::MakeCurrent { source: e.into() })?; - - info!("Shared EGL context created successfully (from current)"); - - Ok(EGLContext::from_raw(surface, context)) - } -} - -impl Default for RenderContextFactory { - fn default() -> Self { - Self { - shared_state: RefCell::new(None), - } - } -} - -fn create_wayland_display_handle(display_id: &ObjectId) -> Result { - let display = NonNull::new(display_id.as_ptr().cast::()).ok_or_else(|| { - LayerShikaError::InvalidInput { - message: "Failed to create NonNull pointer for display".into(), - } - })?; - let handle = WaylandDisplayHandle::new(display); - Ok(RawDisplayHandle::Wayland(handle)) -} - -fn select_config( - glutin_display: &Display, - config_template: ConfigTemplateBuilder, -) -> Result { - let mut configs = unsafe { glutin_display.find_configs(config_template.build()) } - .map_err(|e| EGLError::ConfigSelection { source: e.into() })?; - configs - .next() - .ok_or_else(|| EGLError::NoCompatibleConfig.into()) -} - -fn create_context( - glutin_display: &Display, - config: &Config, - context_attributes: ContextAttributesBuilder, -) -> Result { - unsafe { glutin_display.create_context(config, &context_attributes.build(None)) } - .map_err(|e| EGLError::ContextCreation { source: e.into() }.into()) } fn create_surface_handle(surface_id: &ObjectId) -> Result { diff --git a/crates/adapters/src/rendering/egl/mod.rs b/crates/adapters/src/rendering/egl/mod.rs index 2f3ebb2..cd70189 100644 --- a/crates/adapters/src/rendering/egl/mod.rs +++ b/crates/adapters/src/rendering/egl/mod.rs @@ -1,2 +1,3 @@ pub mod context; pub mod context_factory; +pub mod render_context_manager; diff --git a/crates/adapters/src/rendering/egl/render_context_manager.rs b/crates/adapters/src/rendering/egl/render_context_manager.rs new file mode 100644 index 0000000..ecec15e --- /dev/null +++ b/crates/adapters/src/rendering/egl/render_context_manager.rs @@ -0,0 +1,113 @@ +use crate::errors::{EGLError, LayerShikaError, Result}; +use glutin::{ + api::egl::{config::Config, context::PossiblyCurrentContext, display::Display}, + config::ConfigTemplateBuilder, + context::ContextAttributesBuilder, + prelude::*, +}; +use log::{info, warn}; +use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; +use std::{ffi::c_void, ptr::NonNull, rc::Rc}; +use wayland_client::backend::ObjectId; + +pub struct RenderContextManager { + display: Display, + config: Config, + root_context: PossiblyCurrentContext, +} + +impl RenderContextManager { + pub fn new(display_id: &ObjectId) -> Result> { + info!("Initializing RenderContextManager with independent root context"); + + let display_handle = create_wayland_display_handle(display_id)?; + let display = unsafe { Display::new(display_handle) } + .map_err(|e| EGLError::DisplayCreation { source: e.into() })?; + + let config_template = ConfigTemplateBuilder::default(); + let config = select_config(&display, config_template)?; + + let root_context = Self::create_root_context(&display, &config)?; + + info!("RenderContextManager initialized successfully"); + + Ok(Rc::new(Self { + display, + config, + root_context, + })) + } + + fn create_root_context(display: &Display, config: &Config) -> Result { + if let Ok(context) = Self::try_create_surfaceless_context(display, config) { + info!("Created surfaceless root EGL context"); + return Ok(context); + } + + warn!( + "Surfaceless context not available, using workaround with make_current_surfaceless anyway" + ); + Self::create_surfaceless_fallback(display, config) + } + + fn try_create_surfaceless_context( + display: &Display, + config: &Config, + ) -> Result { + let context_attributes = ContextAttributesBuilder::default(); + let not_current = + unsafe { display.create_context(config, &context_attributes.build(None)) } + .map_err(|e| EGLError::ContextCreation { source: e.into() })?; + + not_current + .make_current_surfaceless() + .map_err(|e| EGLError::MakeCurrent { source: e.into() }.into()) + } + + fn create_surfaceless_fallback( + display: &Display, + config: &Config, + ) -> Result { + let context_attributes = ContextAttributesBuilder::default(); + let not_current = + unsafe { display.create_context(config, &context_attributes.build(None)) } + .map_err(|e| EGLError::ContextCreation { source: e.into() })?; + + not_current + .make_current_surfaceless() + .map_err(|e| EGLError::MakeCurrent { source: e.into() }.into()) + } + + pub fn display(&self) -> &Display { + &self.display + } + + pub fn config(&self) -> &Config { + &self.config + } + + pub fn root_context(&self) -> &PossiblyCurrentContext { + &self.root_context + } +} + +fn create_wayland_display_handle(display_id: &ObjectId) -> Result { + let display = NonNull::new(display_id.as_ptr().cast::()).ok_or_else(|| { + LayerShikaError::InvalidInput { + message: "Failed to create NonNull pointer for display".into(), + } + })?; + let handle = WaylandDisplayHandle::new(display); + Ok(RawDisplayHandle::Wayland(handle)) +} + +fn select_config( + glutin_display: &Display, + config_template: ConfigTemplateBuilder, +) -> Result { + let mut configs = unsafe { glutin_display.find_configs(config_template.build()) } + .map_err(|e| EGLError::ConfigSelection { source: e.into() })?; + configs + .next() + .ok_or_else(|| EGLError::NoCompatibleConfig.into()) +} diff --git a/crates/adapters/src/wayland/globals/context.rs b/crates/adapters/src/wayland/globals/context.rs index 8a3df01..29cfec4 100644 --- a/crates/adapters/src/wayland/globals/context.rs +++ b/crates/adapters/src/wayland/globals/context.rs @@ -1,10 +1,14 @@ -use crate::{bind_globals, errors::LayerShikaError}; +use crate::{ + bind_globals, errors::LayerShikaError, + rendering::egl::render_context_manager::RenderContextManager, +}; use log::info; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; +use std::rc::Rc; use wayland_client::{ globals::registry_queue_init, protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat}, - Connection, QueueHandle, + Connection, Proxy, QueueHandle, }; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; @@ -20,6 +24,7 @@ pub struct GlobalContext { pub xdg_wm_base: Option, pub fractional_scale_manager: Option, pub viewporter: Option, + pub render_context_manager: Rc, } impl GlobalContext { @@ -100,6 +105,9 @@ impl GlobalContext { info!("Viewporter protocol not available"); } + let render_context_manager = + RenderContextManager::new(&connection.display().id())?; + Ok(Self { compositor, outputs, @@ -108,6 +116,7 @@ impl GlobalContext { xdg_wm_base, fractional_scale_manager, viewporter, + render_context_manager, }) } } diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index c584632..ee60f23 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -38,7 +38,7 @@ use std::rc::Rc; use wayland_client::{ Connection, EventQueue, Proxy, QueueHandle, backend::ObjectId, - protocol::{wl_display::WlDisplay, wl_pointer::WlPointer, wl_surface::WlSurface}, + protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, }; type PopupManagersAndSurfaces = (Vec>, Vec>); @@ -55,8 +55,6 @@ pub struct WaylandWindowingSystem { connection: Rc, event_queue: EventQueue, event_loop: EventLoop<'static, AppState>, - #[allow(dead_code)] - render_factory: Rc, } impl WaylandWindowingSystem { @@ -66,15 +64,13 @@ impl WaylandWindowingSystem { let event_loop = EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; - let render_factory = RenderContextFactory::new(); - let state = Self::init_state(config, &connection, &mut event_queue, &render_factory)?; + let state = Self::init_state(config, &connection, &mut event_queue)?; Ok(Self { state, connection, event_queue, event_loop, - render_factory, }) } @@ -101,7 +97,6 @@ impl WaylandWindowingSystem { event_queue: &mut EventQueue, pointer: &Rc, layer_surface_config: &LayerSurfaceConfig, - render_factory: &Rc, ) -> Result> { let mut setups = Vec::new(); @@ -132,8 +127,10 @@ impl WaylandWindowingSystem { let surface_ctx = SurfaceCtx::setup(&setup_params, layer_surface_config); let main_surface_id = surface_ctx.surface.id(); - let window = - Self::initialize_renderer(&surface_ctx.surface, &connection.display(), config, render_factory)?; + let render_factory = + RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); + + let window = Self::initialize_renderer(&surface_ctx.surface, config, &render_factory)?; let mut builder = WindowStateBuilder::new() .with_component_definition(config.component_definition.clone()) @@ -222,7 +219,6 @@ impl WaylandWindowingSystem { config: &WaylandWindowConfig, connection: &Connection, event_queue: &mut EventQueue, - render_factory: &Rc, ) -> Result { let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?; let layer_surface_config = Self::create_layer_surface_config(config); @@ -235,15 +231,17 @@ impl WaylandWindowingSystem { Rc::clone(&shared_serial), ); + let render_factory = + RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); + let popup_context = PopupContext::new( global_ctx.compositor.clone(), global_ctx.xdg_wm_base.clone(), global_ctx.seat.clone(), global_ctx.fractional_scale_manager.clone(), global_ctx.viewporter.clone(), - connection.display(), Rc::new(connection.clone()), - Rc::clone(render_factory), + Rc::clone(&render_factory), ); let setups = Self::create_output_setups( @@ -253,7 +251,6 @@ impl WaylandWindowingSystem { event_queue, &pointer, &layer_surface_config, - render_factory, )?; let platform = Self::setup_platform(&setups)?; @@ -327,17 +324,12 @@ impl WaylandWindowingSystem { fn initialize_renderer( surface: &Rc, - display: &WlDisplay, config: &WaylandWindowConfig, render_factory: &Rc, ) -> Result> { let init_size = PhysicalSize::new(1, 1); - let context = render_factory.create_context( - &display.id(), - &surface.id(), - init_size, - )?; + let context = render_factory.create_context(&surface.id(), init_size)?; let renderer = FemtoVGRenderer::new(context) .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; diff --git a/crates/adapters/src/wayland/surfaces/popup_manager.rs b/crates/adapters/src/wayland/surfaces/popup_manager.rs index c134082..7d43ad4 100644 --- a/crates/adapters/src/wayland/surfaces/popup_manager.rs +++ b/crates/adapters/src/wayland/surfaces/popup_manager.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; use std::rc::Rc; use wayland_client::{ backend::ObjectId, - protocol::{wl_compositor::WlCompositor, wl_display::WlDisplay, wl_seat::WlSeat, wl_surface::WlSurface}, + protocol::{wl_compositor::WlCompositor, wl_seat::WlSeat, wl_surface::WlSurface}, Connection, Proxy, QueueHandle, }; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; @@ -73,7 +73,6 @@ pub struct PopupContext { seat: WlSeat, fractional_scale_manager: Option, viewporter: Option, - display: WlDisplay, render_factory: Rc, } @@ -86,7 +85,6 @@ impl PopupContext { seat: WlSeat, fractional_scale_manager: Option, viewporter: Option, - display: WlDisplay, _connection: Rc, render_factory: Rc, ) -> Self { @@ -96,7 +94,6 @@ impl PopupContext { seat, fractional_scale_manager, viewporter, - display, render_factory, } } @@ -332,11 +329,10 @@ impl PopupManager { popup_surface.surface.commit(); } - let context = self.context.render_factory.create_context( - &self.context.display.id(), - &popup_surface.surface.id(), - popup_size, - )?; + let context = self + .context + .render_factory + .create_context(&popup_surface.surface.id(), popup_size)?; let renderer = FemtoVGRenderer::new(context) .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;