1use std::ffi::{c_void, CStr};
2
3use khronos_egl::{self as egl, ClientBuffer, Dynamic, Instance};
4
5use crate::types::video_frame::DmaBufPlane;
6
7type PFNGLEGLIMAGETARGETTEXTURE2DOESPROC =
8 unsafe extern "C" fn(target: gl::types::GLenum, image: *const c_void);
9
10unsafe impl Sync for EglContext {}
11unsafe impl Send for EglContext {}
12
13#[derive(Clone, Copy)]
14#[allow(clippy::upper_case_acronyms)]
15pub enum GpuVendor {
16 NVIDIA,
17 AMD,
18 UNKNOWN,
19}
20
21impl From<&CStr> for GpuVendor {
22 fn from(value: &CStr) -> Self {
23 match value.to_str() {
24 Ok(s) if s.eq_ignore_ascii_case("nvidia") => Self::NVIDIA,
25 Ok(s) if s.eq_ignore_ascii_case("amd") => Self::AMD,
26 _ => Self::UNKNOWN,
27 }
28 }
29}
30
31pub struct EglContext {
32 egl_instance: Instance<Dynamic<libloading::Library, egl::EGL1_5>>,
33 display: egl::Display,
34 context: egl::Context,
35 surface: egl::Surface,
36 _config: egl::Config,
37 dmabuf_supported: bool,
38 dmabuf_modifiers_supported: bool,
39 persistent_texture_id: u32,
40 #[allow(dead_code)]
41 gpu_vendor: GpuVendor,
42
43 _wayland_display: wayland_client::Display,
45}
46
47impl EglContext {
48 pub fn new(width: i32, height: i32) -> Result<Self, egl::Error> {
49 let lib =
50 unsafe { libloading::Library::new("libEGL.so.1") }.expect("unable to find libEGL.so.1");
51 let egl_instance = unsafe { egl::DynamicInstance::<egl::EGL1_5>::load_required_from(lib) }
52 .expect("unable to load libEGL.so.1");
53
54 egl_instance.bind_api(egl::OPENGL_ES_API)?;
55
56 let wayland_display = wayland_client::Display::connect_to_env().unwrap();
57 let display =
58 unsafe { egl_instance.get_display(wayland_display.c_ptr() as *mut std::ffi::c_void) }
59 .unwrap();
60
61 egl_instance.initialize(display)?;
62
63 let attributes = [
64 egl::BUFFER_SIZE,
65 24,
66 egl::RENDERABLE_TYPE,
67 egl::OPENGL_ES_BIT,
68 egl::NONE,
69 egl::NONE,
70 ];
71
72 let config = egl_instance
73 .choose_first_config(display, &attributes)?
74 .expect("unable to find an appropriate ELG configuration");
75
76 egl_instance.bind_api(egl::OPENGL_ES_API)?;
77
78 let context_attributes = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE];
79
80 let context = egl_instance.create_context(display, config, None, &context_attributes)?;
81
82 let surface_attributes = [egl::WIDTH, width, egl::HEIGHT, height, egl::NONE];
83
84 let surface = egl_instance.create_pbuffer_surface(display, config, &surface_attributes)?;
85 egl_instance.make_current(display, Some(surface), Some(surface), Some(context))?;
86
87 gl::load_with(|symbol| egl_instance.get_proc_address(symbol).unwrap() as *const _);
88
89 let (dmabuf_supported, dmabuf_modifiers_supported) =
90 Self::check_dmabuf_support(&egl_instance, display).unwrap();
91
92 let persistent_texture =
93 Self::create_persistent_texture(width as u32, height as u32).unwrap();
94
95 let gpu_vendor = GpuVendor::from(egl_instance.query_string(Some(display), egl::VENDOR)?);
96
97 Ok(Self {
98 egl_instance,
99 display,
100 _config: config,
101 context,
102 surface,
103 dmabuf_supported,
104 dmabuf_modifiers_supported,
105 persistent_texture_id: persistent_texture,
106 gpu_vendor,
107
108 _wayland_display: wayland_display,
109 })
110 }
111
112 pub fn update_texture_from_image(
113 &self,
114 egl_image: egl::Image,
115 ) -> Result<(), Box<dyn std::error::Error>> {
116 unsafe {
117 let mut temp_texture = 0;
119 gl::GenTextures(1, &mut temp_texture);
120 gl::BindTexture(gl::TEXTURE_2D, temp_texture);
121
122 let egl_texture_2d = {
124 let proc_name = "glEGLImageTargetTexture2DOES";
125 let proc_addr = self.egl_instance.get_proc_address(proc_name);
126
127 if proc_addr.is_none() {
128 gl::DeleteTextures(1, &temp_texture);
129 return Err("glEGLImageTargetTexture2DOES not available".into());
130 } else {
131 std::mem::transmute::<Option<extern "system" fn()>, PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(proc_addr)
132 }
133 };
134
135 egl_texture_2d(gl::TEXTURE_2D, egl_image.as_ptr());
136
137 let gl_error = gl::GetError();
138 if gl_error != gl::NO_ERROR {
139 gl::DeleteTextures(1, &temp_texture);
140 return Err(
141 format!("Failed to bind EGL image to temp texture: 0x{:x}", gl_error).into(),
142 );
143 }
144
145 let mut width = 0;
147 let mut height = 0;
148 gl::GetTexLevelParameteriv(gl::TEXTURE_2D, 0, gl::TEXTURE_WIDTH, &mut width);
149 gl::GetTexLevelParameteriv(gl::TEXTURE_2D, 0, gl::TEXTURE_HEIGHT, &mut height);
150
151 let mut fbo = 0;
153 gl::GenFramebuffers(1, &mut fbo);
154 gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
155
156 gl::FramebufferTexture2D(
158 gl::FRAMEBUFFER,
159 gl::COLOR_ATTACHMENT0,
160 gl::TEXTURE_2D,
161 temp_texture,
162 0,
163 );
164
165 let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
167 if status != gl::FRAMEBUFFER_COMPLETE {
168 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
169 gl::DeleteFramebuffers(1, &fbo);
170 gl::DeleteTextures(1, &temp_texture);
171 return Err(format!("Framebuffer not complete: 0x{:x}", status).into());
172 }
173
174 gl::BindTexture(gl::TEXTURE_2D, self.persistent_texture_id);
176
177 gl::CopyTexSubImage2D(
180 gl::TEXTURE_2D,
181 0, 0,
183 0, 0,
185 0, width, height, );
189
190 let gl_error = gl::GetError();
191 if gl_error != gl::NO_ERROR {
192 gl::BindTexture(gl::TEXTURE_2D, 0);
193 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
194 gl::DeleteFramebuffers(1, &fbo);
195 gl::DeleteTextures(1, &temp_texture);
196 return Err(format!("Failed to copy texture data: 0x{:x}", gl_error).into());
197 }
198
199 gl::BindTexture(gl::TEXTURE_2D, 0);
201 gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
202 gl::DeleteFramebuffers(1, &fbo);
203 gl::DeleteTextures(1, &temp_texture);
204
205 Ok(())
206 }
207 }
208
209 pub fn create_persistent_texture(
210 width: u32,
211 height: u32,
212 ) -> Result<u32, Box<dyn std::error::Error>> {
213 unsafe {
214 let mut texture_id = 0;
215 gl::GenTextures(1, &mut texture_id);
216 gl::BindTexture(gl::TEXTURE_2D, texture_id);
217
218 gl::TexImage2D(
220 gl::TEXTURE_2D,
221 0,
222 gl::RGBA8 as i32, width as i32,
224 height as i32,
225 0,
226 gl::RGBA,
227 gl::UNSIGNED_BYTE,
228 std::ptr::null(), );
230
231 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
233 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
234 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
235 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
236
237 gl::BindTexture(gl::TEXTURE_2D, 0);
238
239 let gl_error = gl::GetError();
240 if gl_error != gl::NO_ERROR {
241 gl::DeleteTextures(1, &texture_id);
242 return Err(
243 format!("Failed to create persistent texture: 0x{:x}", gl_error).into(),
244 );
245 }
246
247 log::trace!(
248 "✓ Created persistent texture: ID {} ({}x{})",
249 texture_id,
250 width,
251 height
252 );
253 Ok(texture_id)
254 }
255 }
256
257 fn check_dmabuf_support(
258 egl_instance: &Instance<Dynamic<libloading::Library, egl::EGL1_5>>,
259 display: egl::Display,
260 ) -> Result<(bool, bool), Box<dyn std::error::Error>> {
261 let extensions = egl_instance.query_string(Some(display), egl::EXTENSIONS)?;
262 let ext_str = extensions.to_string_lossy();
263
264 let dmabuf_import = ext_str.contains("EGL_EXT_image_dma_buf_import");
265 let dmabuf_modifiers = ext_str.contains("EGL_EXT_image_dma_buf_import_modifiers");
266
267 if !dmabuf_import {
268 return Err("EGL_EXT_image_dma_buf_import not supported".into());
269 }
270
271 Ok((dmabuf_import, dmabuf_modifiers))
272 }
273
274 pub fn create_image_from_dmabuf(
275 &self,
276 planes: &[DmaBufPlane],
277 format: u32,
278 width: u32,
279 height: u32,
280 modifier: u64,
281 ) -> Result<egl::Image, Box<dyn std::error::Error>> {
282 if !self.dmabuf_supported {
283 return Err("DMA-BUF import not supported".into());
284 }
285
286 let mut attributes = vec![
287 0x3271,
289 format as usize,
290 egl::WIDTH as usize,
291 width as usize,
292 egl::HEIGHT as usize,
293 height as usize,
294 ];
295
296 for (i, plane) in planes.iter().enumerate() {
297 let plane_attrs = match i {
298 0 => vec![
299 0x3272,
301 plane.fd as usize,
302 0x3273,
304 plane.offset as usize,
305 0x3274,
307 plane.stride as usize,
308 ],
309 1 => vec![
310 0x3275,
312 plane.fd as usize,
313 0x3276,
315 plane.offset as usize,
316 0x3277,
318 plane.stride as usize,
319 ],
320 2 => vec![
321 0x3278,
323 plane.fd as usize,
324 0x3279,
326 plane.offset as usize,
327 0x327A,
329 plane.stride as usize,
330 ],
331 _ => break,
332 };
333
334 attributes.extend(plane_attrs);
335
336 if self.dmabuf_modifiers_supported {
338 let modifier_attrs = match i {
339 0 => vec![
340 0x3443,
342 (modifier & 0xFFFFFFFF) as usize,
343 0x3444,
345 (modifier >> 32) as usize,
346 ],
347 1 => vec![
348 0x3445,
350 (modifier & 0xFFFFFFFF) as usize,
351 0x3446,
353 (modifier >> 32) as usize,
354 ],
355 2 => vec![
356 0x3447,
358 (modifier & 0xFFFFFFFF) as usize,
359 0x3448,
361 (modifier >> 32) as usize,
362 ],
363 _ => break,
364 };
365 attributes.extend(modifier_attrs);
366 }
367 }
368
369 attributes.push(egl::NONE as usize);
370
371 let image = self
373 .egl_instance
374 .create_image(
375 self.display,
376 unsafe { egl::Context::from_ptr(egl::NO_CONTEXT) },
377 0x3270,
379 unsafe { ClientBuffer::from_ptr(std::ptr::null_mut()) },
380 &attributes,
381 )
382 .map_err(|e| format!("Failed to create EGL image from DMA-BUF: {:?}", e))?;
383
384 Ok(image)
385 }
386
387 pub fn destroy_image(&self, image: egl::Image) -> Result<(), Box<dyn std::error::Error>> {
388 self.egl_instance
389 .destroy_image(self.display, image)
390 .map_err(|e| format!("Failed to destroy EGL image: {:?}", e).into())
391 }
392
393 pub fn delete_texture(&self, texture_id: u32) {
394 unsafe {
395 gl::DeleteTextures(1, &texture_id);
396 }
397 }
398
399 pub fn make_current(&self) -> Result<(), egl::Error> {
400 self.egl_instance.make_current(
401 self.display,
402 Some(self.surface),
403 Some(self.surface),
404 Some(self.context),
405 )?;
406 Ok(())
407 }
408
409 pub fn release_current(&self) -> Result<(), egl::Error> {
410 self.egl_instance
411 .make_current(self.display, None, None, None)?;
412 Ok(())
413 }
414
415 pub fn get_texture_id(&self) -> u32 {
416 self.persistent_texture_id
417 }
418
419 #[allow(unused)]
422 pub fn get_gpu_vendor(&self) -> GpuVendor {
423 self.gpu_vendor
424 }
425}
426
427impl Drop for EglContext {
428 fn drop(&mut self) {
429 let _ = self
430 .egl_instance
431 .make_current(self.display, None, None, None);
432 let _ = self
433 .egl_instance
434 .destroy_surface(self.display, self.surface);
435 let _ = self
436 .egl_instance
437 .destroy_context(self.display, self.context);
438 let _ = self.egl_instance.terminate(self.display);
439 self.delete_texture(self.persistent_texture_id);
440 }
441}