1use khronos_egl as egl;
2use optic_core::{OpticError, OpticErrorKind, OpticResult, Size2D};
3use raw_window_handle::RawWindowHandle;
4use std::ffi::c_void;
5use std::ptr;
6
7pub struct WindowSurface {
12 pub surface: egl::Surface,
13 pub size: Size2D,
14}
15
16pub struct RenderContext {
37 pub display: egl::Display,
38 pub context: egl::Context,
39 config: egl::Config,
40 pub surfaces: Vec<WindowSurface>,
41 pub active_index: Option<usize>,
42 pub gl_ver: String,
43 pub glsl_ver: String,
44 pub device: String,
45}
46
47const GL_ATTRIBS: [i32; 7] = [
48 egl::CONTEXT_MAJOR_VERSION as i32, 4,
49 egl::CONTEXT_MINOR_VERSION as i32, 6,
50 egl::CONTEXT_OPENGL_PROFILE_MASK as i32,
51 egl::CONTEXT_OPENGL_CORE_PROFILE_BIT as i32,
52 egl::NONE as i32,
53];
54
55const PBUFFER_ATTRIBS: [i32; 5] = [
56 egl::WIDTH as i32, 1,
57 egl::HEIGHT as i32, 1,
58 egl::NONE as i32,
59];
60
61const CONFIG_ATTRIBS: [i32; 15] = [
62 egl::SURFACE_TYPE as i32, egl::PBUFFER_BIT as i32 | egl::WINDOW_BIT as i32,
63 egl::RENDERABLE_TYPE as i32, egl::OPENGL_BIT as i32,
64 egl::RED_SIZE as i32, 8,
65 egl::GREEN_SIZE as i32, 8,
66 egl::BLUE_SIZE as i32, 8,
67 egl::ALPHA_SIZE as i32, 8,
68 egl::DEPTH_SIZE as i32, 24,
69 egl::NONE as i32,
70];
71
72fn create_display_and_context() -> OpticResult<(egl::Instance<egl::Static>, egl::Display, egl::Context, egl::Config)> {
73 let egl_instance = egl::Instance::new(egl::Static);
74
75 let display = unsafe {
76 egl_instance.get_display(egl::DEFAULT_DISPLAY)
77 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no EGL display found"))?
78 };
79
80 egl_instance.initialize(display)
81 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL init failed: {e}")))?;
82
83 let mut configs = Vec::with_capacity(1);
84 egl_instance.choose_config(display, &CONFIG_ATTRIBS, &mut configs)
85 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL config failed: {e}")))?;
86
87 let config = *configs.first()
88 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no suitable EGL config"))?;
89
90 egl_instance.bind_api(egl::OPENGL_API)
91 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL bind API failed: {e}")))?;
92
93 let context = egl_instance.create_context(display, config, None, &GL_ATTRIBS)
94 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL context creation failed: {e}")))?;
95
96 Ok((egl_instance, display, context, config))
97}
98
99fn load_gl_info() -> (String, String, String) {
100 let gl_ver = unsafe {
101 let ptr = gl::GetString(gl::VERSION);
102 if ptr.is_null() {
103 return ("unknown".into(), "unknown".into(), "unknown".into());
104 }
105 std::ffi::CStr::from_ptr(ptr as *const i8)
106 .to_string_lossy()
107 .to_string()
108 };
109
110 let glsl_ver = unsafe {
111 let ptr = gl::GetString(gl::SHADING_LANGUAGE_VERSION);
112 if ptr.is_null() {
113 return (gl_ver, "unknown".into(), "unknown".into());
114 }
115 std::ffi::CStr::from_ptr(ptr as *const i8)
116 .to_string_lossy()
117 .to_string()
118 };
119
120 let device = unsafe {
121 let ptr = gl::GetString(gl::RENDERER);
122 if ptr.is_null() {
123 return (gl_ver, glsl_ver, "unknown".into());
124 }
125 format!(
126 "OPENGL {}",
127 std::ffi::CStr::from_ptr(ptr as *const i8)
128 .to_string_lossy()
129 )
130 };
131
132 (gl_ver, glsl_ver, device)
133}
134
135fn raw_handle_to_native(handle: RawWindowHandle) -> OpticResult<*mut c_void> {
136 match handle {
137 RawWindowHandle::Xlib(h) => Ok(h.window as usize as *mut c_void),
138 RawWindowHandle::Xcb(h) => Ok(h.window.get() as usize as *mut c_void),
139 RawWindowHandle::Wayland(h) => Ok(h.surface.as_ptr() as *mut c_void),
140 RawWindowHandle::Win32(h) => Ok(h.hwnd.get() as *mut c_void),
141 _ => Err(OpticError::new(
142 OpticErrorKind::OpenGL,
143 "unsupported platform for EGL window surface",
144 )),
145 }
146}
147
148impl RenderContext {
149 pub fn new_headless() -> OpticResult<Self> {
154 let (egl_instance, display, context, config) = create_display_and_context()?;
155
156 let pbuffer = egl_instance.create_pbuffer_surface(display, config, &PBUFFER_ATTRIBS)
157 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("pbuffer creation failed: {e}")))?;
158
159 egl_instance.make_current(display, Some(pbuffer), Some(pbuffer), Some(context))
160 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("make current failed: {e}")))?;
161
162 gl::load_with(|s| {
163 egl_instance.get_proc_address(s)
164 .map(|p| p as *const _)
165 .unwrap_or(ptr::null())
166 });
167
168 let (gl_ver, glsl_ver, device) = load_gl_info();
169 let pbuffer_surface = WindowSurface { surface: pbuffer, size: Size2D::from(1, 1) };
170
171 Ok(Self {
172 display,
173 context,
174 config,
175 surfaces: vec![pbuffer_surface],
176 active_index: Some(0),
177 gl_ver,
178 glsl_ver,
179 device,
180 })
181 }
182
183 pub fn new_windowed(
189 raw_handle: RawWindowHandle,
190 display_handle: raw_window_handle::RawDisplayHandle,
191 size: Size2D,
192 ) -> OpticResult<Self> {
193 let egl_instance = egl::Instance::new(egl::Static);
194
195 let display: egl::Display = match display_handle {
197 raw_window_handle::RawDisplayHandle::Xlib(h) => {
198 let platform = 0x31D5; let native_display = h.display.map_or(std::ptr::null_mut(), |d| d.as_ptr());
200 let r = unsafe {
201 egl_instance.get_platform_display(platform, native_display, &[])
202 };
203 match r {
204 Ok(d) => d,
205 Err(_) => unsafe {
206 egl_instance.get_display(egl::DEFAULT_DISPLAY)
207 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no EGL display found"))?
208 },
209 }
210 }
211 raw_window_handle::RawDisplayHandle::Wayland(h) => {
212 let platform = 0x31D6; let native_display = h.display.as_ptr() as *mut c_void;
214 let r = unsafe {
215 egl_instance.get_platform_display(platform, native_display, &[])
216 };
217 match r {
218 Ok(d) => d,
219 Err(_) => unsafe {
220 egl_instance.get_display(egl::DEFAULT_DISPLAY)
221 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no EGL display found"))?
222 },
223 }
224 }
225 _ => unsafe {
226 egl_instance.get_display(egl::DEFAULT_DISPLAY)
227 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no EGL display found"))?
228 },
229 };
230
231 egl_instance.initialize(display)
232 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL init failed: {e}")))?;
233
234 let native = raw_handle_to_native(raw_handle)?;
235
236 let visual_id: Option<u32> = match raw_handle {
237 RawWindowHandle::Xlib(h) => Some(h.visual_id as u32),
238 RawWindowHandle::Xcb(h) => h.visual_id.map(|v| v.get()),
239 _ => None,
240 };
241
242 let result = if let Some(vid) = visual_id {
243 Self::try_create_windowed(&egl_instance, display, native, size, Some(vid))
244 } else {
245 Self::try_create_windowed(&egl_instance, display, native, size, None)
246 };
247
248 match result {
249 Ok(ctx) => Ok(ctx),
250 Err(_) if visual_id.is_some() => {
251 Self::try_create_windowed(&egl_instance, display, native, size, None)
252 }
253 Err(e) => Err(e),
254 }
255 }
256
257 fn try_create_windowed(
258 egl_instance: &egl::Instance<egl::Static>,
259 display: egl::Display,
260 native: *mut c_void,
261 size: Size2D,
262 visual_id: Option<u32>,
263 ) -> OpticResult<RenderContext> {
264 let mut cfg_attribs = Vec::new();
265 cfg_attribs.push(egl::SURFACE_TYPE as i32);
266 cfg_attribs.push(egl::WINDOW_BIT as i32);
267 cfg_attribs.push(egl::RENDERABLE_TYPE as i32);
268 cfg_attribs.push(egl::OPENGL_BIT as i32);
269 cfg_attribs.push(egl::RED_SIZE as i32); cfg_attribs.push(8);
270 cfg_attribs.push(egl::GREEN_SIZE as i32); cfg_attribs.push(8);
271 cfg_attribs.push(egl::BLUE_SIZE as i32); cfg_attribs.push(8);
272 cfg_attribs.push(egl::ALPHA_SIZE as i32); cfg_attribs.push(8);
273 cfg_attribs.push(egl::DEPTH_SIZE as i32); cfg_attribs.push(24);
274 if let Some(vid) = visual_id {
275 cfg_attribs.push(egl::NATIVE_VISUAL_ID as i32);
276 cfg_attribs.push(vid as i32);
277 }
278 cfg_attribs.push(egl::NONE as i32);
279
280 let mut configs = Vec::with_capacity(1);
281 egl_instance.choose_config(display, &cfg_attribs, &mut configs)
282 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL config failed: {e}")))?;
283
284 let config = *configs.first()
285 .ok_or_else(|| OpticError::new(OpticErrorKind::OpenGL, "no suitable EGL config"))?;
286
287 egl_instance.bind_api(egl::OPENGL_API)
288 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL bind API failed: {e}")))?;
289
290 let context = egl_instance.create_context(display, config, None, &GL_ATTRIBS)
291 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("EGL context creation failed: {e}")))?;
292
293 let surface = unsafe { egl_instance.create_window_surface(display, config, native, None)
294 .map_err(|e| OpticError::new(
295 OpticErrorKind::OpenGL,
296 &format!("EGL window surface creation failed: {e}"),
297 ))? };
298
299 egl_instance.make_current(display, Some(surface), Some(surface), Some(context))
300 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("make current failed: {e}")))?;
301
302 gl::load_with(|s| {
303 egl_instance.get_proc_address(s)
304 .map(|p| p as *const _)
305 .unwrap_or(ptr::null())
306 });
307
308 let (gl_ver, glsl_ver, device) = load_gl_info();
309 let window_surface = WindowSurface { surface, size };
310
311 Ok(Self {
312 display,
313 context,
314 config,
315 surfaces: vec![window_surface],
316 active_index: Some(0),
317 gl_ver,
318 glsl_ver,
319 device,
320 })
321 }
322
323 pub fn attach_window(
328 &mut self,
329 raw_handle: RawWindowHandle,
330 size: Size2D,
331 ) -> OpticResult<usize> {
332 let native = raw_handle_to_native(raw_handle)?;
333 let egl = egl::Instance::new(egl::Static);
334
335 let surface = unsafe { egl.create_window_surface(
336 self.display,
337 self.config,
338 native,
339 None,
340 ).map_err(|e| OpticError::new(
341 OpticErrorKind::OpenGL,
342 &format!("EGL window surface creation failed: {e}"),
343 ))? };
344
345 let index = self.surfaces.len();
346 self.surfaces.push(WindowSurface { surface, size });
347 Ok(index)
348 }
349
350 pub fn resize_window(&mut self, index: usize, size: Size2D) {
356 if let Some(ws) = self.surfaces.get_mut(index) {
357 ws.size = size;
358 }
359 }
360
361 pub fn make_current(&self, index: usize) -> OpticResult<()> {
363 let egl_instance = egl::Instance::new(egl::Static);
364 let ws = self.surfaces.get(index).ok_or_else(|| {
365 OpticError::new(OpticErrorKind::OpenGL, "invalid surface index")
366 })?;
367
368 egl_instance.make_current(self.display, Some(ws.surface), Some(ws.surface), Some(self.context))
369 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("make current failed: {e}")))?;
370
371 unsafe { gl::Viewport(0, 0, ws.size.w as i32, ws.size.h as i32); }
372 Ok(())
373 }
374
375 pub fn swap_buffers(&self, index: usize) -> OpticResult<()> {
377 let egl_instance = egl::Instance::new(egl::Static);
378 let ws = self.surfaces.get(index).ok_or_else(|| {
379 OpticError::new(OpticErrorKind::OpenGL, "invalid surface index")
380 })?;
381
382 egl_instance.swap_buffers(self.display, ws.surface)
383 .map_err(|e| OpticError::new(OpticErrorKind::OpenGL, &format!("swap buffers failed: {e}")))
384 }
385
386 pub fn clear(&self) {
388 unsafe { gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); }
389 }
390
391 pub fn set_vsync(&self, enable: bool) {
393 let egl_instance = egl::Instance::new(egl::Static);
394 let interval = if enable { 1 } else { 0 };
395 let _ = egl_instance.swap_interval(self.display, interval);
396 }
397
398 pub fn set_clear_color(&self, color: optic_core::RGBA) {
400 unsafe { gl::ClearColor(color.0, color.1, color.2, color.3); }
401 }
402}
403
404impl Drop for RenderContext {
405 fn drop(&mut self) {
406 let egl_instance = egl::Instance::new(egl::Static);
407 let _ = egl_instance.make_current(self.display, None, None, None);
408 for ws in self.surfaces.drain(..) {
409 let _ = egl_instance.destroy_surface(self.display, ws.surface);
410 }
411 let _ = egl_instance.destroy_context(self.display, self.context);
412 let _ = egl_instance.terminate(self.display);
413 }
414}