refactor: remove dependency on primary output for egl context management system

This commit is contained in:
drendog 2025-11-18 21:21:30 +01:00
parent 4fb4d87125
commit 5a1c551efb
Signed by: dwenya
GPG key ID: 8DD77074645332D0
6 changed files with 166 additions and 206 deletions

View file

@ -1,208 +1,57 @@
use super::context::EGLContext; use super::context::EGLContext;
use super::render_context_manager::RenderContextManager;
use crate::errors::{EGLError, LayerShikaError, Result}; use crate::errors::{EGLError, LayerShikaError, Result};
use glutin::{ use glutin::{
api::egl::{ api::egl::{config::Config, display::Display, surface::Surface},
config::Config,
context::{NotCurrentContext, PossiblyCurrentContext},
display::Display,
surface::Surface,
},
config::ConfigTemplateBuilder,
context::ContextAttributesBuilder, context::ContextAttributesBuilder,
prelude::*, prelude::*,
surface::{SurfaceAttributesBuilder, WindowSurface}, surface::{SurfaceAttributesBuilder, WindowSurface},
}; };
use log::{debug, info}; use log::info;
use raw_window_handle::{ use raw_window_handle::{RawWindowHandle, WaylandWindowHandle};
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use slint::PhysicalSize; 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; use wayland_client::backend::ObjectId;
pub struct RenderContextFactory { pub struct RenderContextFactory {
shared_state: RefCell<Option<SharedRenderState>>, manager: Rc<RenderContextManager>,
}
struct SharedRenderState {
display: Display,
config: Config,
primary_context: PossiblyCurrentContext,
} }
impl RenderContextFactory { impl RenderContextFactory {
#[must_use] #[must_use]
pub fn new() -> Rc<Self> { pub fn new(manager: Rc<RenderContextManager>) -> Rc<Self> {
Rc::new(Self { Rc::new(Self { manager })
shared_state: RefCell::new(None),
})
} }
pub fn create_context( pub fn create_context(&self, surface_id: &ObjectId, size: PhysicalSize) -> Result<EGLContext> {
&self, info!("Creating shared EGL context from root context manager");
display_id: &ObjectId,
surface_id: &ObjectId,
size: PhysicalSize,
) -> Result<EGLContext> {
let mut state = self.shared_state.borrow_mut();
if state.is_none() { let context_attributes =
info!("Creating primary EGL context (will be shared with subsequent contexts)"); ContextAttributesBuilder::default().with_sharing(self.manager.root_context());
let new_state = self.create_primary_context(display_id, surface_id, size)?;
*state = Some(new_state); 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<SharedRenderState> {
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)?;
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<EGLContext> {
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() })?; .map_err(|e| EGLError::ContextCreation { source: e.into() })?;
let surface_handle = create_surface_handle(surface_id)?; 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 let context = not_current
.make_current(&surface) .make_current(&surface)
.map_err(|e| EGLError::MakeCurrent { source: e.into() })?; .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)) 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<EGLContext> {
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<RawDisplayHandle> {
let display = NonNull::new(display_id.as_ptr().cast::<c_void>()).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<Config> {
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<NotCurrentContext> {
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<RawWindowHandle> { fn create_surface_handle(surface_id: &ObjectId) -> Result<RawWindowHandle> {

View file

@ -1,2 +1,3 @@
pub mod context; pub mod context;
pub mod context_factory; pub mod context_factory;
pub mod render_context_manager;

View file

@ -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<Rc<Self>> {
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<PossiblyCurrentContext> {
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<PossiblyCurrentContext> {
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<PossiblyCurrentContext> {
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<RawDisplayHandle> {
let display = NonNull::new(display_id.as_ptr().cast::<c_void>()).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<Config> {
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())
}

View file

@ -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 log::info;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1;
use std::rc::Rc;
use wayland_client::{ use wayland_client::{
globals::registry_queue_init, globals::registry_queue_init,
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat}, 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::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
@ -20,6 +24,7 @@ pub struct GlobalContext {
pub xdg_wm_base: Option<XdgWmBase>, pub xdg_wm_base: Option<XdgWmBase>,
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>, pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>, pub viewporter: Option<WpViewporter>,
pub render_context_manager: Rc<RenderContextManager>,
} }
impl GlobalContext { impl GlobalContext {
@ -100,6 +105,9 @@ impl GlobalContext {
info!("Viewporter protocol not available"); info!("Viewporter protocol not available");
} }
let render_context_manager =
RenderContextManager::new(&connection.display().id())?;
Ok(Self { Ok(Self {
compositor, compositor,
outputs, outputs,
@ -108,6 +116,7 @@ impl GlobalContext {
xdg_wm_base, xdg_wm_base,
fractional_scale_manager, fractional_scale_manager,
viewporter, viewporter,
render_context_manager,
}) })
} }
} }

View file

@ -38,7 +38,7 @@ use std::rc::Rc;
use wayland_client::{ use wayland_client::{
Connection, EventQueue, Proxy, QueueHandle, Connection, EventQueue, Proxy, QueueHandle,
backend::ObjectId, backend::ObjectId,
protocol::{wl_display::WlDisplay, wl_pointer::WlPointer, wl_surface::WlSurface}, protocol::{wl_pointer::WlPointer, wl_surface::WlSurface},
}; };
type PopupManagersAndSurfaces = (Vec<Rc<PopupManager>>, Vec<Rc<ZwlrLayerSurfaceV1>>); type PopupManagersAndSurfaces = (Vec<Rc<PopupManager>>, Vec<Rc<ZwlrLayerSurfaceV1>>);
@ -55,8 +55,6 @@ pub struct WaylandWindowingSystem {
connection: Rc<Connection>, connection: Rc<Connection>,
event_queue: EventQueue<AppState>, event_queue: EventQueue<AppState>,
event_loop: EventLoop<'static, AppState>, event_loop: EventLoop<'static, AppState>,
#[allow(dead_code)]
render_factory: Rc<RenderContextFactory>,
} }
impl WaylandWindowingSystem { impl WaylandWindowingSystem {
@ -66,15 +64,13 @@ impl WaylandWindowingSystem {
let event_loop = let event_loop =
EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; 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)?;
let state = Self::init_state(config, &connection, &mut event_queue, &render_factory)?;
Ok(Self { Ok(Self {
state, state,
connection, connection,
event_queue, event_queue,
event_loop, event_loop,
render_factory,
}) })
} }
@ -101,7 +97,6 @@ impl WaylandWindowingSystem {
event_queue: &mut EventQueue<AppState>, event_queue: &mut EventQueue<AppState>,
pointer: &Rc<WlPointer>, pointer: &Rc<WlPointer>,
layer_surface_config: &LayerSurfaceConfig, layer_surface_config: &LayerSurfaceConfig,
render_factory: &Rc<RenderContextFactory>,
) -> Result<Vec<OutputSetup>> { ) -> Result<Vec<OutputSetup>> {
let mut setups = Vec::new(); let mut setups = Vec::new();
@ -132,8 +127,10 @@ impl WaylandWindowingSystem {
let surface_ctx = SurfaceCtx::setup(&setup_params, layer_surface_config); let surface_ctx = SurfaceCtx::setup(&setup_params, layer_surface_config);
let main_surface_id = surface_ctx.surface.id(); let main_surface_id = surface_ctx.surface.id();
let window = let render_factory =
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), config, 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() let mut builder = WindowStateBuilder::new()
.with_component_definition(config.component_definition.clone()) .with_component_definition(config.component_definition.clone())
@ -222,7 +219,6 @@ impl WaylandWindowingSystem {
config: &WaylandWindowConfig, config: &WaylandWindowConfig,
connection: &Connection, connection: &Connection,
event_queue: &mut EventQueue<AppState>, event_queue: &mut EventQueue<AppState>,
render_factory: &Rc<RenderContextFactory>,
) -> Result<AppState> { ) -> Result<AppState> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?; let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
let layer_surface_config = Self::create_layer_surface_config(config); let layer_surface_config = Self::create_layer_surface_config(config);
@ -235,15 +231,17 @@ impl WaylandWindowingSystem {
Rc::clone(&shared_serial), Rc::clone(&shared_serial),
); );
let render_factory =
RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager));
let popup_context = PopupContext::new( let popup_context = PopupContext::new(
global_ctx.compositor.clone(), global_ctx.compositor.clone(),
global_ctx.xdg_wm_base.clone(), global_ctx.xdg_wm_base.clone(),
global_ctx.seat.clone(), global_ctx.seat.clone(),
global_ctx.fractional_scale_manager.clone(), global_ctx.fractional_scale_manager.clone(),
global_ctx.viewporter.clone(), global_ctx.viewporter.clone(),
connection.display(),
Rc::new(connection.clone()), Rc::new(connection.clone()),
Rc::clone(render_factory), Rc::clone(&render_factory),
); );
let setups = Self::create_output_setups( let setups = Self::create_output_setups(
@ -253,7 +251,6 @@ impl WaylandWindowingSystem {
event_queue, event_queue,
&pointer, &pointer,
&layer_surface_config, &layer_surface_config,
render_factory,
)?; )?;
let platform = Self::setup_platform(&setups)?; let platform = Self::setup_platform(&setups)?;
@ -327,17 +324,12 @@ impl WaylandWindowingSystem {
fn initialize_renderer( fn initialize_renderer(
surface: &Rc<WlSurface>, surface: &Rc<WlSurface>,
display: &WlDisplay,
config: &WaylandWindowConfig, config: &WaylandWindowConfig,
render_factory: &Rc<RenderContextFactory>, render_factory: &Rc<RenderContextFactory>,
) -> Result<Rc<FemtoVGWindow>> { ) -> Result<Rc<FemtoVGWindow>> {
let init_size = PhysicalSize::new(1, 1); let init_size = PhysicalSize::new(1, 1);
let context = render_factory.create_context( let context = render_factory.create_context(&surface.id(), init_size)?;
&display.id(),
&surface.id(),
init_size,
)?;
let renderer = FemtoVGRenderer::new(context) let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;

View file

@ -15,7 +15,7 @@ use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use wayland_client::{ use wayland_client::{
backend::ObjectId, 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, Connection, Proxy, QueueHandle,
}; };
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
@ -73,7 +73,6 @@ pub struct PopupContext {
seat: WlSeat, seat: WlSeat,
fractional_scale_manager: Option<WpFractionalScaleManagerV1>, fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>, viewporter: Option<WpViewporter>,
display: WlDisplay,
render_factory: Rc<RenderContextFactory>, render_factory: Rc<RenderContextFactory>,
} }
@ -86,7 +85,6 @@ impl PopupContext {
seat: WlSeat, seat: WlSeat,
fractional_scale_manager: Option<WpFractionalScaleManagerV1>, fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>, viewporter: Option<WpViewporter>,
display: WlDisplay,
_connection: Rc<Connection>, _connection: Rc<Connection>,
render_factory: Rc<RenderContextFactory>, render_factory: Rc<RenderContextFactory>,
) -> Self { ) -> Self {
@ -96,7 +94,6 @@ impl PopupContext {
seat, seat,
fractional_scale_manager, fractional_scale_manager,
viewporter, viewporter,
display,
render_factory, render_factory,
} }
} }
@ -332,11 +329,10 @@ impl PopupManager {
popup_surface.surface.commit(); popup_surface.surface.commit();
} }
let context = self.context.render_factory.create_context( let context = self
&self.context.display.id(), .context
&popup_surface.surface.id(), .render_factory
popup_size, .create_context(&popup_surface.surface.id(), popup_size)?;
)?;
let renderer = FemtoVGRenderer::new(context) let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;