mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-11-17 23:14:23 +00:00
218 lines
6.7 KiB
Rust
218 lines
6.7 KiB
Rust
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<WindowSurface>,
|
|
context: PossiblyCurrentContext,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct EGLContextBuilder {
|
|
display_id: Option<ObjectId>,
|
|
surface_id: Option<ObjectId>,
|
|
size: Option<PhysicalSize>,
|
|
config_template: Option<ConfigTemplateBuilder>,
|
|
context_attributes: Option<ContextAttributesBuilder>,
|
|
}
|
|
|
|
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<EGLContext> {
|
|
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<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> {
|
|
let surface = NonNull::new(surface_id.as_ptr().cast::<c_void>()).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<Surface<WindowSurface>> {
|
|
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::<WindowSurface>::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<dyn Error + Send + Sync>> {
|
|
self.ensure_current()
|
|
.map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)
|
|
}
|
|
|
|
fn swap_buffers(&self) -> StdResult<(), Box<dyn Error + Send + Sync>> {
|
|
self.surface
|
|
.swap_buffers(&self.context)
|
|
.map_err(|e| EGLError::SwapBuffers { source: e.into() }.into())
|
|
}
|
|
|
|
fn resize(
|
|
&self,
|
|
width: NonZeroU32,
|
|
height: NonZeroU32,
|
|
) -> StdResult<(), Box<dyn Error + Send + Sync>> {
|
|
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)
|
|
}
|
|
}
|