use crate::errors::{EGLError, LayerShikaError, Result}; use glutin::{ api::egl::{ config::Config, context::{NotCurrentContext, PossiblyCurrentContext}, display::Display, surface::Surface, }, config::ConfigTemplateBuilder, context::ContextAttributesBuilder, display::GetGlDisplay, prelude::*, surface::{SurfaceAttributesBuilder, WindowSurface}, }; use raw_window_handle::{ RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, }; use slint::{PhysicalSize, platform::femtovg_renderer::OpenGLInterface}; use std::{ error::Error, ffi::{self, CStr, c_void}, num::NonZeroU32, ptr::NonNull, result::Result as StdResult, }; use wayland_client::backend::ObjectId; pub struct EGLContext { surface: Surface, context: PossiblyCurrentContext, } #[derive(Default)] pub struct EGLContextBuilder { display_id: Option, surface_id: Option, size: Option, config_template: Option, context_attributes: Option, } impl EGLContextBuilder { #[must_use] pub fn new() -> Self { Self::default() } #[must_use] pub fn with_display_id(mut self, display_id: ObjectId) -> Self { self.display_id = Some(display_id); self } #[must_use] pub fn with_surface_id(mut self, surface_id: ObjectId) -> Self { self.surface_id = Some(surface_id); self } #[must_use] pub const fn with_size(mut self, size: PhysicalSize) -> Self { self.size = Some(size); self } pub fn build(self) -> Result { let display_id = self .display_id .ok_or_else(|| LayerShikaError::InvalidInput { message: "Display ID is required".into(), })?; let surface_id = self .surface_id .ok_or_else(|| LayerShikaError::InvalidInput { message: "Surface ID is required".into(), })?; let size = self.size.ok_or_else(|| LayerShikaError::InvalidInput { message: "Size is required".into(), })?; let display_handle = create_wayland_display_handle(&display_id)?; let glutin_display = unsafe { Display::new(display_handle) } .map_err(|e| EGLError::DisplayCreation { source: e.into() })?; let config_template = self.config_template.unwrap_or_default(); let config = select_config(&glutin_display, config_template)?; let context_attributes = self.context_attributes.unwrap_or_default(); let context = create_context(&glutin_display, &config, context_attributes)?; let surface_handle = create_surface_handle(&surface_id)?; let surface = create_surface(&glutin_display, &config, surface_handle, size)?; let context = context .make_current(&surface) .map_err(|e| EGLError::MakeCurrent { source: e.into() })?; Ok(EGLContext { surface, context }) } } impl EGLContext { #[must_use] pub fn builder() -> EGLContextBuilder { EGLContextBuilder::new() } fn ensure_current(&self) -> Result<()> { if !self.context.is_current() { self.context .make_current(&self.surface) .map_err(|e| EGLError::MakeCurrent { source: e.into() })?; } Ok(()) } } impl Drop for EGLContext { fn drop(&mut self) { if self.context.is_current() { if let Err(e) = self.context.make_not_current_in_place() { log::error!("Failed to make EGL context not current during cleanup: {e}"); } else { log::debug!("Successfully made EGL context not current during cleanup"); } } } } 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 { let surface = NonNull::new(surface_id.as_ptr().cast::()).ok_or_else(|| { LayerShikaError::InvalidInput { message: "Failed to create NonNull pointer for surface".into(), } })?; let handle = WaylandWindowHandle::new(surface); Ok(RawWindowHandle::Wayland(handle)) } fn create_surface( glutin_display: &Display, config: &Config, surface_handle: RawWindowHandle, size: PhysicalSize, ) -> Result> { let width = NonZeroU32::new(size.width).ok_or_else(|| LayerShikaError::InvalidInput { message: "Width cannot be zero".into(), })?; let height = NonZeroU32::new(size.height).ok_or_else(|| LayerShikaError::InvalidInput { message: "Height cannot be zero".into(), })?; let attrs = SurfaceAttributesBuilder::::new().build(surface_handle, width, height); unsafe { glutin_display.create_window_surface(config, &attrs) } .map_err(|e| EGLError::SurfaceCreation { source: e.into() }.into()) } unsafe impl OpenGLInterface for EGLContext { fn ensure_current(&self) -> StdResult<(), Box> { self.ensure_current() .map_err(|e| Box::new(e) as Box) } fn swap_buffers(&self) -> StdResult<(), Box> { self.surface .swap_buffers(&self.context) .map_err(|e| EGLError::SwapBuffers { source: e.into() }.into()) } fn resize( &self, width: NonZeroU32, height: NonZeroU32, ) -> StdResult<(), Box> { self.ensure_current()?; self.surface.resize(&self.context, width, height); Ok(()) } fn get_proc_address(&self, name: &CStr) -> *const ffi::c_void { self.context.display().get_proc_address(name) } }