makepad_platform/os/linux/x11/
opengl_x11.rs

1use {
2    std::{
3        mem,
4        os::raw::{c_long, c_void},
5        ffi::CString,
6        os::{self, fd::{AsRawFd as _, FromRawFd as _, OwnedFd}},
7    },
8    self::super::{
9        x11_sys,
10        xlib_window::XlibWindow,
11    },
12    self::super::super::{
13        dma_buf,
14        egl_sys::{self, LibEgl},
15        gl_sys,
16        opengl::CxOsTexture,
17    },
18    crate::{
19        cx::Cx,
20        window::WindowId,
21        makepad_error_log::*,
22        makepad_math::DVec2,
23        pass::{PassClearColor, PassClearDepth, PassId},
24        event::*,
25        texture::{Texture, TextureDesc},
26    },
27};
28
29impl Cx {
30    
31    pub fn draw_pass_to_window(
32        &mut self,
33        pass_id: PassId,
34        opengl_window: &mut OpenglWindow,
35    ) {
36        let draw_list_id = self.passes[pass_id].main_draw_list_id.unwrap();
37        
38        self.setup_render_pass(pass_id);
39        
40        let egl_surface = opengl_window.egl_surface;
41        
42        self.passes[pass_id].paint_dirty = false;
43
44        let pix_width = opengl_window.window_geom.inner_size.x * opengl_window.window_geom.dpi_factor;
45        let pix_height = opengl_window.window_geom.inner_size.y * opengl_window.window_geom.dpi_factor;
46        unsafe {
47            let opengl_cx = self.os.opengl_cx.as_ref().unwrap();
48            (opengl_cx.libegl.eglMakeCurrent.unwrap())(opengl_cx.egl_display, egl_surface, egl_surface, opengl_cx.egl_context);
49            gl_sys::Viewport(0, 0, pix_width.floor() as i32, pix_height.floor() as i32);
50        }
51        
52        let clear_color = if self.passes[pass_id].color_textures.len() == 0 {
53            self.passes[pass_id].clear_color 
54        }
55        else {
56            match self.passes[pass_id].color_textures[0].clear_color {
57                PassClearColor::InitWith(color) => color,
58                PassClearColor::ClearWith(color) => color
59            }
60        };
61        let clear_depth = match self.passes[pass_id].clear_depth {
62            PassClearDepth::InitWith(depth) => depth,
63            PassClearDepth::ClearWith(depth) => depth
64        };
65        
66        if !self.passes[pass_id].dont_clear {
67            unsafe {
68                gl_sys::BindFramebuffer(gl_sys::FRAMEBUFFER, 0);
69                gl_sys::ClearDepthf(clear_depth as f32);
70                gl_sys::ClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
71                gl_sys::Clear(gl_sys::COLOR_BUFFER_BIT | gl_sys::DEPTH_BUFFER_BIT);
72            }
73        }
74        Self::set_default_depth_and_blend_mode();
75        
76        let mut zbias = 0.0;
77        let zbias_step = self.passes[pass_id].zbias_step;
78        
79        self.render_view(
80            pass_id,
81            draw_list_id,
82            &mut zbias,
83            zbias_step,
84        );
85
86        unsafe {
87            let opengl_cx = self.os.opengl_cx.as_ref().unwrap();
88            (opengl_cx.libegl.eglSwapBuffers.unwrap())(opengl_cx.egl_display, egl_surface);
89        }
90    }
91
92    pub fn share_texture_for_presentable_image(
93        &mut self,
94        texture: &Texture,
95    ) -> dma_buf::Image<OwnedFd> {
96        let cxtexture = &mut self.textures[texture.texture_id()];
97        cxtexture.os.update_shared_texture(&cxtexture.desc);
98
99        let opengl_cx = self.os.opengl_cx.as_ref().unwrap();
100        unsafe {
101            let egl_image = (opengl_cx.libegl.eglCreateImageKHR.unwrap())(
102                opengl_cx.egl_display,
103                opengl_cx.egl_context,
104                egl_sys::EGL_GL_TEXTURE_2D_KHR,
105                cxtexture.os.gl_texture.unwrap() as egl_sys::EGLClientBuffer,
106                std::ptr::null(),
107            );
108            assert!(!egl_image.is_null(), "eglCreateImageKHR failed");
109
110            let (mut fourcc, mut num_planes) = (0, 0);
111            assert!(
112                (
113                    opengl_cx.libegl.eglExportDMABUFImageQueryMESA
114                        .expect("eglExportDMABUFImageQueryMESA unsupported")
115                )(
116                    opengl_cx.egl_display,
117                    egl_image,
118                    &mut fourcc as *mut u32 as *mut i32,
119                    &mut num_planes,
120                    std::ptr::null_mut(),
121                ) != 0,
122                "eglExportDMABUFImageQueryMESA failed",
123            );
124            assert!(
125                num_planes == 1,
126                "planar DRM format {:?} ({fourcc:#x}) unsupported (num_planes={num_planes})",
127                std::str::from_utf8(&u32::to_le_bytes(fourcc))
128            );
129
130            // HACK(eddyb) `modifiers` are reported per-plane, so to avoid UB,
131            // a second query call is used *after* the `num_planes == 1` check.
132            let mut modifiers = 0;
133            assert!(
134                (opengl_cx.libegl.eglExportDMABUFImageQueryMESA.unwrap())(
135                    opengl_cx.egl_display,
136                    egl_image,
137                    std::ptr::null_mut(),
138                    std::ptr::null_mut(),
139                    &mut modifiers,
140                ) != 0,
141                "eglExportDMABUFImageQueryMESA failed",
142            );
143
144            let (mut dma_buf_fd, mut offset, mut stride) = (0, 0, 0);
145            assert!(
146                (opengl_cx.libegl.eglExportDMABUFImageMESA.unwrap())(
147                    opengl_cx.egl_display,
148                    egl_image,
149                    &mut dma_buf_fd,
150                    &mut stride as *mut u32 as *mut i32,
151                    &mut offset as *mut u32 as *mut i32,
152                ) != 0,
153                "eglExportDMABUFImageMESA failed",
154            );
155
156            assert!(
157                (opengl_cx.libegl.eglDestroyImageKHR.unwrap())(
158                    opengl_cx.egl_display,
159                    egl_image,
160                ) != 0,
161                "eglDestroyImageKHR failed",
162            );
163
164            dma_buf::Image {
165                drm_format: dma_buf::DrmFormat {
166                    fourcc,
167                    modifiers,
168                },
169                planes: dma_buf::ImagePlane {
170                    dma_buf_fd: os::fd::OwnedFd::from_raw_fd(dma_buf_fd),
171                    offset,
172                    stride,
173                },
174            }
175        }
176    }
177}
178
179
180impl CxOsTexture {
181    fn update_shared_texture(&mut self, desc: &TextureDesc) {
182        // FIXME(eddyb) !!!!! arne't these the same!!!??
183        if false { self.update_platform_render_target(desc, Default::default(), false); }
184
185        // we need a width/height for this one.
186        if desc.width.is_none() || desc.height.is_none() {
187            log!("Shared texture width/height is undefined, cannot allocate it");
188            return
189        }
190
191        let width = desc.width.unwrap() as u64;
192        let height = desc.height.unwrap() as u64;
193
194        if self.gl_texture.is_some() && self.width == width && self.height == height && self.alloc_desc == *desc {
195            return;
196        }
197
198        // HACK(eddyb) drain error queue, so that we can check erors below.
199        while unsafe { gl_sys::GetError() } != 0 {}
200
201        self.alloc_desc = desc.clone();
202        self.width = width;
203        self.height = height;
204
205        unsafe {
206            if self.gl_texture.is_none() {
207                let mut gl_texture = std::mem::MaybeUninit::uninit();
208                gl_sys::GenTextures(1, gl_texture.as_mut_ptr());
209                self.gl_texture = Some(gl_texture.assume_init());
210            }
211
212            gl_sys::BindTexture(gl_sys::TEXTURE_2D, self.gl_texture.unwrap());
213
214            gl_sys::TexParameteri(gl_sys::TEXTURE_2D, gl_sys::TEXTURE_MIN_FILTER, gl_sys::NEAREST as i32);
215            gl_sys::TexParameteri(gl_sys::TEXTURE_2D, gl_sys::TEXTURE_MAG_FILTER, gl_sys::NEAREST as i32);
216            gl_sys::TexImage2D(
217                gl_sys::TEXTURE_2D,
218                0,
219                gl_sys::RGBA as i32,
220                width as i32,
221                height as i32,
222                0,
223                gl_sys::RGBA,
224                gl_sys::UNSIGNED_BYTE,
225                std::ptr::null()
226            );
227            assert_eq!(gl_sys::GetError(), 0, "glTexImage2D({width}, {height}) failed");
228            gl_sys::BindTexture(gl_sys::TEXTURE_2D, 0);
229        }
230    }
231
232    pub fn update_from_shared_dma_buf_image(
233        &mut self,
234        opengl_cx: &OpenglCx,
235        desc: &TextureDesc,
236        dma_buf_image: &dma_buf::Image<os::fd::OwnedFd>,
237    ) {
238        // we need a width/height for this one.
239        if desc.width.is_none() || desc.height.is_none() {
240            log!("Shared texture width/height is undefined, cannot import it");
241            return
242        }
243
244        let width = desc.width.unwrap() as u64;
245        let height = desc.height.unwrap() as u64;
246
247        // HACK(eddyb) drain error queue, so that we can check erors below.
248        while unsafe { gl_sys::GetError() } != 0 {}
249        opengl_cx.make_current();
250        while unsafe { gl_sys::GetError() } != 0 {}
251
252        let dma_buf::Image { drm_format, planes: ref plane0 } = *dma_buf_image;
253
254        let image_attribs = [
255            egl_sys::EGL_LINUX_DRM_FOURCC_EXT,
256            drm_format.fourcc,
257            egl_sys::EGL_WIDTH,
258            width as u32,
259            egl_sys::EGL_HEIGHT,
260            height as u32,
261            egl_sys::EGL_DMA_BUF_PLANE0_FD_EXT,
262            plane0.dma_buf_fd.as_raw_fd() as u32,
263            egl_sys::EGL_DMA_BUF_PLANE0_OFFSET_EXT,
264            plane0.offset,
265            egl_sys::EGL_DMA_BUF_PLANE0_PITCH_EXT,
266            plane0.stride,
267            egl_sys::EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
268            drm_format.modifiers as u32,
269            egl_sys::EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
270            (drm_format.modifiers >> 32) as u32,
271            egl_sys::EGL_NONE,
272        ];
273        let egl_image = unsafe { (opengl_cx.libegl.eglCreateImageKHR.unwrap())(
274            opengl_cx.egl_display,
275            std::ptr::null_mut(),
276            egl_sys::EGL_LINUX_DMA_BUF_EXT,
277            std::ptr::null_mut(),
278            image_attribs.as_ptr() as _,
279        ) };
280        assert!(!egl_image.is_null(), "eglCreateImageKHR failed");
281
282        self.alloc_desc = desc.clone();
283        self.width = width;
284        self.height = height;
285
286        unsafe {
287            let gl_texture = *self.gl_texture.get_or_insert_with(|| {
288                let mut gl_texture = std::mem::MaybeUninit::uninit();
289                gl_sys::GenTextures(1, gl_texture.as_mut_ptr());
290                assert_eq!(gl_sys::GetError(), 0, "glGenTextures failed");
291                gl_texture.assume_init()
292            });
293
294            gl_sys::BindTexture(gl_sys::TEXTURE_2D, gl_texture);
295            assert_eq!(gl_sys::GetError(), 0, "glBindTexture({gl_texture}) failed");
296
297            (opengl_cx.libegl.glEGLImageTargetTexture2DOES.unwrap())(gl_sys::TEXTURE_2D, egl_image);
298            assert_eq!(gl_sys::GetError(), 0, "glEGLImageTargetTexture2DOES failed");
299
300            gl_sys::BindTexture(gl_sys::TEXTURE_2D, 0);
301        }
302    }
303}
304
305// FIXME(eddyb) move this out of `linux::x11`, since it's mostly generic EGL.
306pub struct OpenglCx {
307    libegl: LibEgl,
308    egl_display: egl_sys::EGLDisplay,
309    egl_config: egl_sys::EGLConfig,
310    egl_context: egl_sys::EGLContext,
311
312    egl_platform: egl_sys::EGLenum,
313    egl_platform_display: *mut c_void,
314}
315
316impl OpenglCx {
317    pub unsafe fn from_egl_platform_display<T>(
318        egl_platform: egl_sys::EGLenum,
319        egl_platform_display: *mut T,
320    ) -> OpenglCx {
321        let egl_platform_display = egl_platform_display as *mut c_void;
322
323        // Load EGL function pointers.
324        let libegl = LibEgl::try_load().expect("can't load LibEGL");
325
326        let mut major = 0;
327        let mut minor = 0;
328
329        let egl_display = (libegl.eglGetPlatformDisplayEXT.unwrap())(
330            egl_platform,
331            egl_platform_display,
332            std::ptr::null(),
333        );
334        assert!(!egl_display.is_null(), "can't get EGL platform display");
335
336        assert!(
337            (libegl.eglInitialize.unwrap())(egl_display, &mut major, &mut minor) != 0,
338            "can't initialize EGL",
339        );
340
341        assert!(
342            (libegl.eglBindAPI.unwrap())(egl_sys::EGL_OPENGL_ES_API) != 0,
343            "can't bind EGL_OPENGL_ES_API",
344        );
345
346        // Choose framebuffer configuration.
347        let cfg_attribs = [
348            egl_sys::EGL_RED_SIZE,
349            8,
350            egl_sys::EGL_GREEN_SIZE,
351            8,
352            egl_sys::EGL_BLUE_SIZE,
353            8,
354            egl_sys::EGL_ALPHA_SIZE,
355            8,
356            // egl_sys::EGL_DEPTH_SIZE,
357            // 24,
358            // egl_sys::EGL_STENCIL_SIZE,
359            // 8,
360            egl_sys::EGL_RENDERABLE_TYPE,
361            egl_sys::EGL_OPENGL_ES2_BIT,
362            egl_sys::EGL_NONE
363        ];
364
365        let mut egl_config = 0 as egl_sys::EGLConfig;
366        let mut matched_egl_configs = 0;
367        assert!(
368            (libegl.eglChooseConfig.unwrap())(
369                egl_display,
370                cfg_attribs.as_ptr() as _,
371                &mut egl_config,
372                1,
373                &mut matched_egl_configs
374            ) != 0 && matched_egl_configs == 1,
375            "eglChooseConfig failed",
376        );
377
378        // Create EGL context.
379        let ctx_attribs = [
380            egl_sys::EGL_CONTEXT_CLIENT_VERSION,
381            2,
382            egl_sys::EGL_NONE
383        ];
384
385        let egl_context = (libegl.eglCreateContext.unwrap())(
386            egl_display,
387            egl_config,
388            egl_sys::EGL_NO_CONTEXT,
389            ctx_attribs.as_ptr() as _,
390        );
391        assert!(!egl_context.is_null(), "eglCreateContext failed");
392
393        // Load GL function pointers.
394        gl_sys::load_with(|symbol| {
395            let s = CString::new(symbol).unwrap();
396            (libegl.eglGetProcAddress.unwrap())(s.as_ptr())
397        });
398
399        OpenglCx {
400            libegl,
401            egl_display,
402            egl_config,
403            egl_context,
404
405            egl_platform,
406            egl_platform_display,
407        }
408    }
409
410    pub fn make_current(&self) {
411        unsafe {
412            (self.libegl.eglMakeCurrent.unwrap())(
413                self.egl_display,
414                egl_sys::EGL_NO_SURFACE,
415                egl_sys::EGL_NO_SURFACE,
416                self.egl_context,
417            );
418        }
419    }
420}
421
422#[derive(Clone)]
423pub struct OpenglWindow {
424    pub first_draw: bool,
425    pub window_id: WindowId,
426    pub window_geom: WindowGeom,
427    pub opening_repaint_count: u32,
428    pub cal_size: DVec2,
429    pub xlib_window: Box<XlibWindow>,
430    pub egl_surface: egl_sys::EGLSurface,
431}
432
433impl OpenglWindow {
434    pub fn new(
435        window_id: WindowId,
436        opengl_cx: &OpenglCx,
437        inner_size: DVec2,
438        position: Option<DVec2>,
439        title: &str
440    ) -> OpenglWindow {
441        // Checked "downcast" of the EGL platform display to a X11 display.
442        assert_eq!(opengl_cx.egl_platform, egl_sys::EGL_PLATFORM_X11_EXT);
443        let display = opengl_cx. egl_platform_display as *mut x11_sys::Display;
444
445        let mut xlib_window = Box::new(XlibWindow::new(window_id));
446
447        // Get X11 visual from EGL configuration.
448        let visual_info = unsafe {
449            let mut native_visual_id = 0;
450            assert!(
451                (opengl_cx.libegl.eglGetConfigAttrib.unwrap())(
452                    opengl_cx.egl_display,
453                    opengl_cx.egl_config,
454                    egl_sys::EGL_NATIVE_VISUAL_ID as _,
455                    &mut native_visual_id,
456                ) != 0,
457                "eglGetConfigAttrib(EGL_NATIVE_VISUAL_ID) failed",
458            );
459
460            let mut visual_template = mem::zeroed::<x11_sys::XVisualInfo>();
461            visual_template.visualid = native_visual_id as _;
462
463            let mut count = 0;
464            let visual_info_ptr = x11_sys::XGetVisualInfo(
465                display,
466                x11_sys::VisualIDMask as c_long,
467                &mut visual_template,
468                &mut count,
469            );
470            assert!(
471                !visual_info_ptr.is_null() && count == 1,
472                "can't get visual from EGL configuration with XGetVisualInfo",
473            );
474
475            let visual_info = *visual_info_ptr;
476            x11_sys::XFree(visual_info_ptr as *mut c_void);
477            visual_info
478        };
479
480        let custom_window_chrome = false;
481        xlib_window.init(title, inner_size, position, visual_info, custom_window_chrome);
482
483        let egl_surface = unsafe {
484            (opengl_cx.libegl.eglCreateWindowSurface.unwrap())(
485                opengl_cx.egl_display,
486                opengl_cx.egl_config,
487                xlib_window.window.unwrap(),
488                std::ptr::null(),
489            )
490        };
491        assert!(!egl_surface.is_null(), "eglCreateWindowSurface failed");
492
493        OpenglWindow {
494            first_draw: true,
495            window_id,
496            opening_repaint_count: 0,
497            cal_size: DVec2::default(),
498            window_geom: xlib_window.get_window_geom(),
499            xlib_window,
500            egl_surface,
501        }
502    }
503    
504    pub fn resize_buffers(&mut self) -> bool {
505        let cal_size = DVec2 {
506            x: self.window_geom.inner_size.x * self.window_geom.dpi_factor,
507            y: self.window_geom.inner_size.y * self.window_geom.dpi_factor
508        };
509        if self.cal_size != cal_size {
510            self.cal_size = cal_size;
511            // resize the framebuffer
512            true
513        }
514        else {
515            false
516        }
517    }
518    
519}