waycap_rs/
waycap_egl.rs

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>, // Optional for surfaceless context
54    _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    // Keep Wayland display alive
63    _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        // Check supported surface types for this config
116        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            // Create a temporary texture from the EGL image
163            let mut temp_texture = 0;
164            gl::GenTextures(1, &mut temp_texture);
165            gl::BindTexture(gl::TEXTURE_2D, temp_texture);
166
167            // Bind EGL image to temporary texture
168            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            // Get dimensions from the EGL image texture
194            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            // Create framebuffer for copying
200            let mut fbo = 0;
201            gl::GenFramebuffers(1, &mut fbo);
202            gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
203
204            // Attach temporary EGL texture as source
205            gl::FramebufferTexture2D(
206                gl::FRAMEBUFFER,
207                gl::COLOR_ATTACHMENT0,
208                gl::TEXTURE_2D,
209                temp_texture,
210                0,
211            );
212
213            // Check framebuffer status
214            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            // Bind persistent texture as destination
223            gl::BindTexture(gl::TEXTURE_2D, self.persistent_texture_id.get().unwrap());
224
225            // Use CopyTexSubImage2D instead of CopyTexImage2D
226            // This updates existing texture data rather than reallocating
227            gl::CopyTexSubImage2D(
228                gl::TEXTURE_2D,
229                0, // mipmap level
230                0,
231                0, // destination x, y offset in texture
232                0,
233                0,      // source x, y offset in framebuffer
234                width,  // width to copy
235                height, // height to copy
236            );
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            // Cleanup
248            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            // Allocate texture storage with CUDA-compatible RGBA8 format
264            gl::TexImage2D(
265                gl::TEXTURE_2D,
266                0,
267                gl::RGBA8 as i32, // CUDA-compatible format
268                self.width,
269                self.height,
270                0,
271                gl::RGBA,
272                gl::UNSIGNED_BYTE,
273                std::ptr::null(), // No initial data
274            );
275
276            // Set texture parameters for better performance
277            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            // EGL_LINUX_DRM_FOURCC_EXT
331            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                    // EGL_DMA_BUF_PLANE0_FD_EXT
343                    0x3272,
344                    plane.fd as usize,
345                    // EGL_DMA_BUF_PLANE0_OFFSET_EXT
346                    0x3273,
347                    plane.offset as usize,
348                    // EGL_DMA_BUF_PLANE0_PITCH_EXT
349                    0x3274,
350                    plane.stride as usize,
351                ],
352                1 => vec![
353                    // EGL_DMA_BUF_PLANE1_FD_EXT
354                    0x3275,
355                    plane.fd as usize,
356                    // EGL_DMA_BUF_PLANE1_OFFSET_EXT
357                    0x3276,
358                    plane.offset as usize,
359                    // EGL_DMA_BUF_PLANE1_PITCH_EXT
360                    0x3277,
361                    plane.stride as usize,
362                ],
363                2 => vec![
364                    // EGL_DMA_BUF_PLANE2_FD_EXT
365                    0x3278,
366                    plane.fd as usize,
367                    // EGL_DMA_BUF_PLANE2_OFFSET_EXT
368                    0x3279,
369                    plane.offset as usize,
370                    // EGL_DMA_BUF_PLANE2_PITCH_EXT
371                    0x327A,
372                    plane.stride as usize,
373                ],
374                _ => break,
375            };
376
377            attributes.extend(plane_attrs);
378
379            // Add modifiers if supported
380            if self.dmabuf_modifiers_supported {
381                let modifier_attrs = match i {
382                    0 => vec![
383                        // EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT
384                        0x3443,
385                        (modifier & 0xFFFFFFFF) as usize,
386                        // EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
387                        0x3444,
388                        (modifier >> 32) as usize,
389                    ],
390                    1 => vec![
391                        // EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT
392                        0x3445,
393                        (modifier & 0xFFFFFFFF) as usize,
394                        // EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
395                        0x3446,
396                        (modifier >> 32) as usize,
397                    ],
398                    2 => vec![
399                        // EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT
400                        0x3447,
401                        (modifier & 0xFFFFFFFF) as usize,
402                        // EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
403                        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        // Create EGL image
415        let image = self
416            .egl_instance
417            .create_image(
418                self.display,
419                unsafe { egl::Context::from_ptr(egl::NO_CONTEXT) },
420                // EGL_LINUX_DMA_BUF_EXT
421                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}