1use std::{
2 cell::Cell,
3 ffi::{c_void, CStr},
4};
5
6use khronos_egl::{self as egl, ClientBuffer, Dynamic, Instance};
7
8use crate::types::{error::Result, video_frame::DmaBufPlane};
9
10type PFNGLEGLIMAGETARGETTEXTURE2DOESPROC =
11 unsafe extern "C" fn(target: gl::types::GLenum, image: *const c_void);
12
13unsafe impl Sync for EglContext {}
14unsafe impl Send for EglContext {}
15
16#[derive(Clone, Copy)]
17#[allow(clippy::upper_case_acronyms)]
18pub enum GpuVendor {
19 NVIDIA,
20 AMD,
21 INTEL,
22 UNKNOWN,
23}
24
25impl From<&CStr> for GpuVendor {
26 fn from(value: &CStr) -> Self {
27 match value.to_str() {
28 Ok(s) => {
29 let s_lower = s.to_lowercase();
30 if s_lower.contains("nvidia") {
31 Self::NVIDIA
32 } else if s_lower.contains("ati")
33 || s_lower.contains("amd")
34 || s_lower.contains("advanced micro devices")
35 {
36 Self::AMD
37 } else if s_lower.contains("intel") {
38 Self::INTEL
39 } else {
40 log::error!("The GPU vendor {s:?} is not supported.");
41 Self::UNKNOWN
42 }
43 }
44 _ => Self::UNKNOWN,
45 }
46 }
47}
48
49pub struct EglContext {
50 egl_instance: Instance<Dynamic<libloading::Library, egl::EGL1_5>>,
51 display: egl::Display,
52 context: egl::Context,
53 surface: Option<egl::Surface>, _config: egl::Config,
55 dmabuf_supported: bool,
56 dmabuf_modifiers_supported: bool,
57 persistent_texture_id: Cell<Option<u32>>,
58 gpu_vendor: GpuVendor,
59 width: i32,
60 height: i32,
61
62 _wayland_display: wayland_client::Display,
64}
65
66impl EglContext {
67 pub fn new(width: i32, height: i32) -> Result<Self> {
68 let lib =
69 unsafe { libloading::Library::new("libEGL.so.1") }.expect("unable to find libEGL.so.1");
70 let egl_instance = unsafe { egl::DynamicInstance::<egl::EGL1_5>::load_required_from(lib) }
71 .expect("unable to load libEGL.so.1");
72
73 egl_instance.bind_api(egl::OPENGL_ES_API)?;
74
75 let wayland_display = wayland_client::Display::connect_to_env().unwrap();
76 let display =
77 unsafe { egl_instance.get_display(wayland_display.c_ptr() as *mut std::ffi::c_void) }
78 .unwrap();
79
80 egl_instance.initialize(display)?;
81
82 let attributes = [
83 egl::SURFACE_TYPE,
84 egl::PBUFFER_BIT,
85 egl::RENDERABLE_TYPE,
86 egl::OPENGL_ES_BIT,
87 egl::NONE,
88 ];
89
90 let config = egl_instance
91 .choose_first_config(display, &attributes)?
92 .or_else(|| {
93 log::warn!("pbuffer config not found, trying window config");
94 let fallback_attributes = [
95 egl::SURFACE_TYPE,
96 egl::WINDOW_BIT,
97 egl::RENDERABLE_TYPE,
98 egl::OPENGL_ES_BIT,
99 egl::NONE,
100 ];
101 egl_instance
102 .choose_first_config(display, &fallback_attributes)
103 .ok()
104 .flatten()
105 })
106 .expect("unable to find an appropriate EGL configuration");
107
108 let context_attributes = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE];
109
110 let context = egl_instance.create_context(display, config, None, &context_attributes)?;
111
112 let extensions = egl_instance.query_string(Some(display), egl::EXTENSIONS)?;
113 let ext_str = extensions.to_string_lossy();
114
115 let surface_type = egl_instance.get_config_attrib(display, config, egl::SURFACE_TYPE)?;
117 let supports_pbuffer = (surface_type & egl::PBUFFER_BIT) != 0;
118
119 let surface = if supports_pbuffer {
120 log::debug!("Using pbuffer surface");
121 let surface_attributes = [egl::WIDTH, width, egl::HEIGHT, height, egl::NONE];
122 let surface =
123 egl_instance.create_pbuffer_surface(display, config, &surface_attributes)?;
124 egl_instance.make_current(display, Some(surface), Some(surface), Some(context))?;
125 Some(surface)
126 } else if ext_str.contains("EGL_KHR_surfaceless_context") {
127 log::debug!("Using surfaceless context");
128 egl_instance.make_current(display, None, None, Some(context))?;
129 None
130 } else {
131 return Err("No suitable surface type available".into());
132 };
133
134 gl::load_with(|symbol| egl_instance.get_proc_address(symbol).unwrap() as *const _);
135
136 let (dmabuf_supported, dmabuf_modifiers_supported) =
137 Self::check_dmabuf_support(&egl_instance, display).unwrap();
138
139 let gpu_vendor = get_gpu_vendor();
140
141 Ok(Self {
142 egl_instance,
143 display,
144 _config: config,
145 context,
146 surface,
147 dmabuf_supported,
148 dmabuf_modifiers_supported,
149 persistent_texture_id: Cell::new(None),
150 gpu_vendor,
151 width,
152 height,
153
154 _wayland_display: wayland_display,
155 })
156 }
157
158 pub fn update_texture_from_image(&self, egl_image: egl::Image) -> Result<()> {
159 assert!(self.persistent_texture_id.get().is_some());
160
161 unsafe {
162 let mut temp_texture = 0;
164 gl::GenTextures(1, &mut temp_texture);
165 gl::BindTexture(gl::TEXTURE_2D, temp_texture);
166
167 let egl_texture_2d = {
169 let proc_name = "glEGLImageTargetTexture2DOES";
170 let proc_addr = self.egl_instance.get_proc_address(proc_name);
171
172 if proc_addr.is_none() {
173 gl::DeleteTextures(1, &temp_texture);
174 return Err("glEGLImageTargetTexture2DOES not available".into());
175 } else {
176 std::mem::transmute::<
177 Option<extern "system" fn()>,
178 PFNGLEGLIMAGETARGETTEXTURE2DOESPROC,
179 >(proc_addr)
180 }
181 };
182
183 egl_texture_2d(gl::TEXTURE_2D, egl_image.as_ptr());
184
185 let gl_error = gl::GetError();
186 if gl_error != gl::NO_ERROR {
187 gl::DeleteTextures(1, &temp_texture);
188 return Err(
189 format!("Failed to bind EGL image to temp texture: 0x{gl_error:x}").into(),
190 );
191 }
192
193 let mut width = 0;
195 let mut height = 0;
196 gl::GetTexLevelParameteriv(gl::TEXTURE_2D, 0, gl::TEXTURE_WIDTH, &mut width);
197 gl::GetTexLevelParameteriv(gl::TEXTURE_2D, 0, gl::TEXTURE_HEIGHT, &mut height);
198
199 let mut fbo = 0;
201 gl::GenFramebuffers(1, &mut fbo);
202 gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
203
204 gl::FramebufferTexture2D(
206 gl::FRAMEBUFFER,
207 gl::COLOR_ATTACHMENT0,
208 gl::TEXTURE_2D,
209 temp_texture,
210 0,
211 );
212
213 let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
215 if status != gl::FRAMEBUFFER_COMPLETE {
216 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
217 gl::DeleteFramebuffers(1, &fbo);
218 gl::DeleteTextures(1, &temp_texture);
219 return Err(format!("Framebuffer not complete: 0x{status:x}").into());
220 }
221
222 gl::BindTexture(gl::TEXTURE_2D, self.persistent_texture_id.get().unwrap());
224
225 gl::CopyTexSubImage2D(
228 gl::TEXTURE_2D,
229 0, 0,
231 0, 0,
233 0, width, height, );
237
238 let gl_error = gl::GetError();
239 if gl_error != gl::NO_ERROR {
240 gl::BindTexture(gl::TEXTURE_2D, 0);
241 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
242 gl::DeleteFramebuffers(1, &fbo);
243 gl::DeleteTextures(1, &temp_texture);
244 return Err(format!("Failed to copy texture data: 0x{gl_error:x}").into());
245 }
246
247 gl::BindTexture(gl::TEXTURE_2D, 0);
249 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
250 gl::DeleteFramebuffers(1, &fbo);
251 gl::DeleteTextures(1, &temp_texture);
252
253 Ok(())
254 }
255 }
256
257 pub fn create_persistent_texture(&self) -> Result<()> {
258 unsafe {
259 let mut texture_id = 0;
260 gl::GenTextures(1, &mut texture_id);
261 gl::BindTexture(gl::TEXTURE_2D, texture_id);
262
263 gl::TexImage2D(
265 gl::TEXTURE_2D,
266 0,
267 gl::RGBA8 as i32, self.width,
269 self.height,
270 0,
271 gl::RGBA,
272 gl::UNSIGNED_BYTE,
273 std::ptr::null(), );
275
276 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
278 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
279 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
280 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
281
282 gl::BindTexture(gl::TEXTURE_2D, 0);
283
284 let gl_error = gl::GetError();
285 if gl_error != gl::NO_ERROR {
286 gl::DeleteTextures(1, &texture_id);
287 return Err(format!("Failed to create persistent texture: 0x{gl_error:x}").into());
288 }
289
290 log::trace!(
291 "✓ Created persistent texture: ID {texture_id} ({}x{})",
292 self.width,
293 self.height
294 );
295 self.persistent_texture_id.set(Some(texture_id));
296 Ok(())
297 }
298 }
299
300 fn check_dmabuf_support(
301 egl_instance: &Instance<Dynamic<libloading::Library, egl::EGL1_5>>,
302 display: egl::Display,
303 ) -> Result<(bool, bool)> {
304 let extensions = egl_instance.query_string(Some(display), egl::EXTENSIONS)?;
305 let ext_str = extensions.to_string_lossy();
306
307 let dmabuf_import = ext_str.contains("EGL_EXT_image_dma_buf_import");
308 let dmabuf_modifiers = ext_str.contains("EGL_EXT_image_dma_buf_import_modifiers");
309
310 if !dmabuf_import {
311 return Err("EGL_EXT_image_dma_buf_import not supported".into());
312 }
313
314 Ok((dmabuf_import, dmabuf_modifiers))
315 }
316
317 pub fn create_image_from_dmabuf(
318 &self,
319 planes: &[DmaBufPlane],
320 format: u32,
321 width: u32,
322 height: u32,
323 modifier: u64,
324 ) -> Result<egl::Image> {
325 if !self.dmabuf_supported {
326 return Err("DMA-BUF import not supported".into());
327 }
328
329 let mut attributes = vec![
330 0x3271,
332 format as usize,
333 egl::WIDTH as usize,
334 width as usize,
335 egl::HEIGHT as usize,
336 height as usize,
337 ];
338
339 for (i, plane) in planes.iter().enumerate() {
340 let plane_attrs = match i {
341 0 => vec![
342 0x3272,
344 plane.fd as usize,
345 0x3273,
347 plane.offset as usize,
348 0x3274,
350 plane.stride as usize,
351 ],
352 1 => vec![
353 0x3275,
355 plane.fd as usize,
356 0x3276,
358 plane.offset as usize,
359 0x3277,
361 plane.stride as usize,
362 ],
363 2 => vec![
364 0x3278,
366 plane.fd as usize,
367 0x3279,
369 plane.offset as usize,
370 0x327A,
372 plane.stride as usize,
373 ],
374 _ => break,
375 };
376
377 attributes.extend(plane_attrs);
378
379 if self.dmabuf_modifiers_supported {
381 let modifier_attrs = match i {
382 0 => vec![
383 0x3443,
385 (modifier & 0xFFFFFFFF) as usize,
386 0x3444,
388 (modifier >> 32) as usize,
389 ],
390 1 => vec![
391 0x3445,
393 (modifier & 0xFFFFFFFF) as usize,
394 0x3446,
396 (modifier >> 32) as usize,
397 ],
398 2 => vec![
399 0x3447,
401 (modifier & 0xFFFFFFFF) as usize,
402 0x3448,
404 (modifier >> 32) as usize,
405 ],
406 _ => break,
407 };
408 attributes.extend(modifier_attrs);
409 }
410 }
411
412 attributes.push(egl::NONE as usize);
413
414 let image = self
416 .egl_instance
417 .create_image(
418 self.display,
419 unsafe { egl::Context::from_ptr(egl::NO_CONTEXT) },
420 0x3270,
422 unsafe { ClientBuffer::from_ptr(std::ptr::null_mut()) },
423 &attributes,
424 )
425 .map_err(|e| format!("Failed to create EGL image from DMA-BUF: {e:?}"))?;
426
427 Ok(image)
428 }
429
430 pub fn destroy_image(&self, image: egl::Image) -> Result<()> {
431 self.egl_instance
432 .destroy_image(self.display, image)
433 .map_err(|e| format!("Failed to destroy EGL image: {e:?}").into())
434 }
435
436 pub fn delete_texture(&self, texture_id: u32) {
437 unsafe {
438 gl::DeleteTextures(1, &texture_id);
439 }
440 }
441
442 pub fn make_current(&self) -> Result<()> {
443 self.egl_instance.make_current(
444 self.display,
445 self.surface,
446 self.surface,
447 Some(self.context),
448 )?;
449 Ok(())
450 }
451
452 pub fn release_current(&self) -> Result<()> {
453 self.egl_instance
454 .make_current(self.display, None, None, None)?;
455 Ok(())
456 }
457
458 pub fn get_texture_id(&self) -> Option<u32> {
459 self.persistent_texture_id.get()
460 }
461
462 pub fn get_gpu_vendor(&self) -> GpuVendor {
463 self.gpu_vendor
464 }
465}
466
467impl Drop for EglContext {
468 fn drop(&mut self) {
469 let _ = self
470 .egl_instance
471 .make_current(self.display, None, None, None);
472
473 if let Some(surface) = self.surface {
474 let _ = self.egl_instance.destroy_surface(self.display, surface);
475 }
476
477 let _ = self
478 .egl_instance
479 .destroy_context(self.display, self.context);
480 let _ = self.egl_instance.terminate(self.display);
481 if let Some(texture) = self.persistent_texture_id.get() {
482 self.delete_texture(texture);
483 }
484 }
485}
486
487fn get_gpu_vendor() -> GpuVendor {
488 unsafe {
489 let vendor_ptr = gl::GetString(gl::VENDOR);
490 if vendor_ptr.is_null() {
491 GpuVendor::UNKNOWN
492 } else {
493 let vendor = CStr::from_ptr(vendor_ptr as *const std::ffi::c_char);
494 GpuVendor::from(vendor)
495 }
496 }
497}