surfman/platform/generic/egl/
context.rs

1// surfman/surfman/src/platform/generic/egl/context.rs
2//
3//! Functionality common to backends using EGL contexts.
4
5use super::device::EGL_FUNCTIONS;
6use super::error::ToWindowingApiError;
7use super::ffi::EGL_CONTEXT_OPENGL_PROFILE_MASK;
8use super::ffi::{EGL_CONTEXT_MINOR_VERSION_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT};
9use super::surface::{EGLBackedSurface, ExternalEGLSurfaces};
10use crate::context::{self, CREATE_CONTEXT_MUTEX};
11use crate::egl;
12use crate::egl::types::{EGLConfig, EGLContext, EGLDisplay, EGLSurface, EGLint};
13use crate::surface::Framebuffer;
14use crate::{ContextAttributeFlags, ContextAttributes, ContextID, Error, GLApi, GLVersion};
15use crate::{Gl, SurfaceInfo};
16use glow::HasContext;
17
18use std::ffi::CString;
19use std::mem;
20use std::os::raw::c_void;
21use std::ptr;
22use std::thread;
23
24#[allow(dead_code)]
25const DUMMY_PBUFFER_SIZE: EGLint = 16;
26const RGB_CHANNEL_BIT_DEPTH: EGLint = 8;
27
28pub(crate) struct EGLBackedContext {
29    pub(crate) egl_context: EGLContext,
30    pub(crate) id: ContextID,
31    pbuffer: EGLSurface,
32    framebuffer: Framebuffer<EGLBackedSurface, ExternalEGLSurfaces>,
33    context_is_owned: bool,
34}
35
36/// Wrapper for a native `EGLContext`.
37#[derive(Clone, Copy)]
38pub struct NativeContext {
39    /// The EGL context.
40    pub egl_context: EGLContext,
41    /// The EGL read surface that is to be attached to that context.
42    pub egl_read_surface: EGLSurface,
43    /// The EGL draw surface that is to be attached to that context.
44    pub egl_draw_surface: EGLSurface,
45}
46
47/// Information needed to create a context. Some APIs call this a "config" or a "pixel format".
48///
49/// These are local to a device.
50#[derive(Clone)]
51pub struct ContextDescriptor {
52    pub(crate) egl_config_id: EGLint,
53    pub(crate) gl_version: GLVersion,
54    pub(crate) compatibility_profile: bool,
55}
56
57#[must_use]
58pub(crate) struct CurrentContextGuard {
59    egl_display: EGLDisplay,
60    old_egl_draw_surface: EGLSurface,
61    old_egl_read_surface: EGLSurface,
62    old_egl_context: EGLContext,
63}
64
65impl Drop for EGLBackedContext {
66    #[inline]
67    fn drop(&mut self) {
68        if self.egl_context != egl::NO_CONTEXT && !thread::panicking() {
69            panic!("Contexts must be destroyed explicitly with `destroy_context`!")
70        }
71    }
72}
73
74impl Drop for CurrentContextGuard {
75    fn drop(&mut self) {
76        EGL_FUNCTIONS.with(|egl| unsafe {
77            if self.egl_display != egl::NO_DISPLAY {
78                egl.MakeCurrent(
79                    self.egl_display,
80                    self.old_egl_draw_surface,
81                    self.old_egl_read_surface,
82                    self.old_egl_context,
83                );
84            }
85        })
86    }
87}
88
89impl EGLBackedContext {
90    pub(crate) unsafe fn new(
91        egl_display: EGLDisplay,
92        descriptor: &ContextDescriptor,
93        share_with: Option<&EGLBackedContext>,
94        gl_api: GLApi,
95    ) -> Result<EGLBackedContext, Error> {
96        let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap();
97
98        // Create the context.
99        let egl_context = create_context(
100            egl_display,
101            descriptor,
102            share_with.map_or(egl::NO_CONTEXT, |ctx| ctx.egl_context),
103            gl_api,
104        )?;
105
106        // Create a dummy pbuffer.
107        let pbuffer = create_dummy_pbuffer(egl_display, egl_context).unwrap_or(egl::NO_SURFACE);
108
109        // Wrap and return it.
110        let context = EGLBackedContext {
111            egl_context,
112            id: *next_context_id,
113            framebuffer: Framebuffer::None,
114            context_is_owned: true,
115            pbuffer,
116        };
117        next_context_id.0 += 1;
118        Ok(context)
119    }
120
121    pub(crate) unsafe fn from_native_context(native_context: NativeContext) -> EGLBackedContext {
122        let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap();
123        let context = EGLBackedContext {
124            egl_context: native_context.egl_context,
125            id: *next_context_id,
126            framebuffer: Framebuffer::External(ExternalEGLSurfaces {
127                draw: native_context.egl_draw_surface,
128                read: native_context.egl_read_surface,
129            }),
130            context_is_owned: false,
131            pbuffer: egl::NO_SURFACE,
132        };
133        next_context_id.0 += 1;
134        context
135    }
136
137    pub(crate) unsafe fn destroy(&mut self, egl_display: EGLDisplay) {
138        EGL_FUNCTIONS.with(|egl| {
139            if self.pbuffer != egl::NO_SURFACE {
140                let result = egl.DestroySurface(egl_display, self.pbuffer);
141                assert_ne!(result, egl::FALSE);
142                self.pbuffer = egl::NO_SURFACE;
143            }
144
145            egl.MakeCurrent(
146                egl_display,
147                egl::NO_SURFACE,
148                egl::NO_SURFACE,
149                egl::NO_CONTEXT,
150            );
151
152            if self.context_is_owned {
153                let result = egl.DestroyContext(egl_display, self.egl_context);
154                assert_ne!(result, egl::FALSE);
155            }
156
157            self.egl_context = egl::NO_CONTEXT;
158        });
159    }
160
161    pub(crate) fn native_context(&self) -> NativeContext {
162        let egl_surfaces = match self.framebuffer {
163            Framebuffer::Surface(ref surface) => surface.egl_surfaces(),
164            Framebuffer::External(ref surfaces) => (*surfaces).clone(),
165            Framebuffer::None => ExternalEGLSurfaces::default(),
166        };
167
168        NativeContext {
169            egl_context: self.egl_context,
170            egl_draw_surface: egl_surfaces.draw,
171            egl_read_surface: egl_surfaces.read,
172        }
173    }
174
175    pub(crate) unsafe fn make_current(&self, egl_display: EGLDisplay) -> Result<(), Error> {
176        let egl_surfaces = match self.framebuffer {
177            Framebuffer::Surface(ref surface) => surface.egl_surfaces(),
178            Framebuffer::External(ref surfaces) => (*surfaces).clone(),
179            Framebuffer::None => ExternalEGLSurfaces {
180                draw: self.pbuffer,
181                read: self.pbuffer,
182            },
183        };
184
185        EGL_FUNCTIONS.with(|egl| {
186            let result = egl.MakeCurrent(
187                egl_display,
188                egl_surfaces.draw,
189                egl_surfaces.read,
190                self.egl_context,
191            );
192            if result == egl::FALSE {
193                let err = egl.GetError().to_windowing_api_error();
194                return Err(Error::MakeCurrentFailed(err));
195            }
196            Ok(())
197        })
198    }
199
200    #[inline]
201    pub(crate) fn is_current(&self) -> bool {
202        unsafe { EGL_FUNCTIONS.with(|egl| egl.GetCurrentContext() == self.egl_context) }
203    }
204
205    pub(crate) unsafe fn bind_surface(
206        &mut self,
207        egl_display: EGLDisplay,
208        surface: EGLBackedSurface,
209    ) -> Result<(), (Error, EGLBackedSurface)> {
210        if self.id != surface.context_id {
211            return Err((Error::IncompatibleSurface, surface));
212        }
213
214        match self.framebuffer {
215            Framebuffer::None => self.framebuffer = Framebuffer::Surface(surface),
216            Framebuffer::External(_) => return Err((Error::ExternalRenderTarget, surface)),
217            Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, surface)),
218        }
219
220        // If we're current, call `make_context_current()` again to switch to the new framebuffer.
221        if self.is_current() {
222            drop(self.make_current(egl_display))
223        }
224
225        Ok(())
226    }
227
228    pub(crate) unsafe fn unbind_surface(
229        &mut self,
230        gl: &Gl,
231        egl_display: EGLDisplay,
232    ) -> Result<Option<EGLBackedSurface>, Error> {
233        // Flush to avoid races on Mesa/Intel and possibly other GPUs.
234        gl.flush();
235
236        match self.framebuffer {
237            Framebuffer::None => return Ok(None),
238            Framebuffer::Surface(_) => {}
239            Framebuffer::External(_) => return Err(Error::ExternalRenderTarget),
240        }
241
242        let surface = match mem::replace(&mut self.framebuffer, Framebuffer::None) {
243            Framebuffer::Surface(surface) => surface,
244            Framebuffer::None | Framebuffer::External(_) => unreachable!(),
245        };
246
247        // If we're current, we stay current, but with no surface attached.
248        surface.unbind(gl, egl_display, self.egl_context);
249
250        Ok(Some(surface))
251    }
252
253    pub(crate) fn surface_info(&self) -> Result<Option<SurfaceInfo>, Error> {
254        match self.framebuffer {
255            Framebuffer::None => Ok(None),
256            Framebuffer::External(_) => Err(Error::ExternalRenderTarget),
257            Framebuffer::Surface(ref surface) => Ok(Some(surface.info())),
258        }
259    }
260}
261
262impl NativeContext {
263    /// Returns the current EGL context and surfaces, if applicable.
264    ///
265    /// If there is no current EGL context, this returns a `NoCurrentContext` error.
266    pub fn current() -> Result<NativeContext, Error> {
267        EGL_FUNCTIONS.with(|egl| unsafe {
268            let egl_context = egl.GetCurrentContext();
269            if egl_context == egl::NO_CONTEXT {
270                Err(Error::NoCurrentContext)
271            } else {
272                Ok(NativeContext {
273                    egl_context,
274                    egl_read_surface: egl.GetCurrentSurface(egl::READ as EGLint),
275                    egl_draw_surface: egl.GetCurrentSurface(egl::DRAW as EGLint),
276                })
277            }
278        })
279    }
280}
281
282impl ContextDescriptor {
283    pub(crate) unsafe fn new(
284        egl_display: EGLDisplay,
285        attributes: &ContextAttributes,
286        extra_config_attributes: &[EGLint],
287    ) -> Result<ContextDescriptor, Error> {
288        let flags = attributes.flags;
289
290        let alpha_size = if flags.contains(ContextAttributeFlags::ALPHA) {
291            8
292        } else {
293            0
294        };
295        let depth_size = if flags.contains(ContextAttributeFlags::DEPTH) {
296            24
297        } else {
298            0
299        };
300        let stencil_size = if flags.contains(ContextAttributeFlags::STENCIL) {
301            8
302        } else {
303            0
304        };
305
306        let compatibility_profile = flags.contains(ContextAttributeFlags::COMPATIBILITY_PROFILE);
307
308        // Mesa doesn't support the OpenGL compatibility profile post version 3.0. Take that into
309        // account.
310        if compatibility_profile
311            && (attributes.version.major > 3
312                || attributes.version.major == 3 && attributes.version.minor > 0)
313        {
314            return Err(Error::UnsupportedGLProfile);
315        }
316
317        // Android/OHOS does not support OpenGL compatibility profile at all
318        if cfg!(any(android_platform, ohos_platform)) && compatibility_profile {
319            return Err(Error::UnsupportedGLProfile);
320        }
321
322        // Create required config attributes.
323        //
324        // We check these separately because `eglChooseConfig` on its own might give us 32-bit
325        // color when 24-bit color is requested, and that can break code.
326        let required_config_attributes = [
327            egl::RED_SIZE as EGLint,
328            RGB_CHANNEL_BIT_DEPTH,
329            egl::GREEN_SIZE as EGLint,
330            RGB_CHANNEL_BIT_DEPTH,
331            egl::BLUE_SIZE as EGLint,
332            RGB_CHANNEL_BIT_DEPTH,
333        ];
334
335        // Create config attributes.
336        let mut requested_config_attributes = required_config_attributes.to_vec();
337        requested_config_attributes.extend_from_slice(&[
338            egl::ALPHA_SIZE as EGLint,
339            alpha_size,
340            egl::DEPTH_SIZE as EGLint,
341            depth_size,
342            egl::STENCIL_SIZE as EGLint,
343            stencil_size,
344        ]);
345        requested_config_attributes.extend_from_slice(extra_config_attributes);
346        requested_config_attributes.extend_from_slice(&[egl::NONE as EGLint, 0, 0, 0]);
347
348        EGL_FUNCTIONS.with(|egl| {
349            // See how many applicable configs there are.
350            let mut config_count = 0;
351            let result = egl.ChooseConfig(
352                egl_display,
353                requested_config_attributes.as_ptr(),
354                ptr::null_mut(),
355                0,
356                &mut config_count,
357            );
358            if result == egl::FALSE {
359                let err = egl.GetError().to_windowing_api_error();
360                return Err(Error::PixelFormatSelectionFailed(err));
361            }
362            if config_count == 0 {
363                return Err(Error::NoPixelFormatFound);
364            }
365
366            // Enumerate all those configs.
367            let mut configs = vec![ptr::null(); config_count as usize];
368            let mut real_config_count = config_count;
369            let result = egl.ChooseConfig(
370                egl_display,
371                requested_config_attributes.as_ptr(),
372                configs.as_mut_ptr(),
373                config_count,
374                &mut real_config_count,
375            );
376            if result == egl::FALSE {
377                let err = egl.GetError().to_windowing_api_error();
378                return Err(Error::PixelFormatSelectionFailed(err));
379            }
380
381            // Sanitize configs.
382            let egl_config = configs.into_iter().find(|&egl_config| {
383                required_config_attributes
384                    .chunks(2)
385                    .all(|pair| get_config_attr(egl_display, egl_config, pair[0]) == pair[1])
386            });
387            let egl_config = match egl_config {
388                None => return Err(Error::NoPixelFormatFound),
389                Some(egl_config) => egl_config,
390            };
391
392            // Get the config ID and version.
393            let egl_config_id = get_config_attr(egl_display, egl_config, egl::CONFIG_ID as EGLint);
394            let gl_version = attributes.version;
395
396            Ok(ContextDescriptor {
397                egl_config_id,
398                gl_version,
399                compatibility_profile,
400            })
401        })
402    }
403
404    pub(crate) unsafe fn from_egl_context(
405        gl: &Gl,
406        egl_display: EGLDisplay,
407        egl_context: EGLContext,
408    ) -> ContextDescriptor {
409        let egl_config_id = get_context_attr(egl_display, egl_context, egl::CONFIG_ID as EGLint);
410        let gl_version = GLVersion::current(&gl);
411        let compatibility_profile = context::current_context_uses_compatibility_profile(&gl);
412
413        ContextDescriptor {
414            egl_config_id,
415            gl_version,
416            compatibility_profile,
417        }
418    }
419
420    #[allow(dead_code)]
421    pub(crate) unsafe fn to_egl_config(&self, egl_display: EGLDisplay) -> EGLConfig {
422        let config_attributes = [
423            egl::CONFIG_ID as EGLint,
424            self.egl_config_id,
425            egl::NONE as EGLint,
426            0,
427            0,
428            0,
429        ];
430
431        EGL_FUNCTIONS.with(|egl| {
432            let (mut config, mut config_count) = (ptr::null(), 0);
433            let result = egl.ChooseConfig(
434                egl_display,
435                config_attributes.as_ptr(),
436                &mut config,
437                1,
438                &mut config_count,
439            );
440            assert_ne!(result, egl::FALSE);
441            assert!(config_count > 0);
442            config
443        })
444    }
445
446    pub(crate) unsafe fn attributes(&self, egl_display: EGLDisplay) -> ContextAttributes {
447        let egl_config = egl_config_from_id(egl_display, self.egl_config_id);
448
449        let alpha_size = get_config_attr(egl_display, egl_config, egl::ALPHA_SIZE as EGLint);
450        let depth_size = get_config_attr(egl_display, egl_config, egl::DEPTH_SIZE as EGLint);
451        let stencil_size = get_config_attr(egl_display, egl_config, egl::STENCIL_SIZE as EGLint);
452
453        // Convert to `surfman` context attribute flags.
454        let mut attribute_flags = ContextAttributeFlags::empty();
455        attribute_flags.set(ContextAttributeFlags::ALPHA, alpha_size != 0);
456        attribute_flags.set(ContextAttributeFlags::DEPTH, depth_size != 0);
457        attribute_flags.set(ContextAttributeFlags::STENCIL, stencil_size != 0);
458
459        attribute_flags.set(
460            ContextAttributeFlags::COMPATIBILITY_PROFILE,
461            self.compatibility_profile,
462        );
463
464        // Create appropriate context attributes.
465        ContextAttributes {
466            flags: attribute_flags,
467            version: self.gl_version,
468        }
469    }
470}
471
472impl CurrentContextGuard {
473    pub(crate) fn new() -> CurrentContextGuard {
474        EGL_FUNCTIONS.with(|egl| unsafe {
475            CurrentContextGuard {
476                egl_display: egl.GetCurrentDisplay(),
477                old_egl_draw_surface: egl.GetCurrentSurface(egl::DRAW as EGLint),
478                old_egl_read_surface: egl.GetCurrentSurface(egl::READ as EGLint),
479                old_egl_context: egl.GetCurrentContext(),
480            }
481        })
482    }
483}
484
485pub(crate) unsafe fn create_context(
486    egl_display: EGLDisplay,
487    descriptor: &ContextDescriptor,
488    share_with: EGLContext,
489    gl_api: GLApi,
490) -> Result<EGLContext, Error> {
491    EGL_FUNCTIONS.with(|egl| {
492        let ok = egl.BindAPI(match gl_api {
493            GLApi::GL => egl::OPENGL_API,
494            GLApi::GLES => egl::OPENGL_ES_API,
495        });
496        assert_ne!(ok, egl::FALSE);
497    });
498
499    let egl_config = egl_config_from_id(egl_display, descriptor.egl_config_id);
500
501    let mut egl_context_attributes = vec![
502        egl::CONTEXT_CLIENT_VERSION as EGLint,
503        descriptor.gl_version.major as EGLint,
504        EGL_CONTEXT_MINOR_VERSION_KHR as EGLint,
505        descriptor.gl_version.minor as EGLint,
506    ];
507
508    // D3D11 ANGLE doesn't seem happy if EGL_CONTEXT_OPENGL_PROFILE_MASK is set
509    // to be a core profile.
510    if descriptor.compatibility_profile {
511        egl_context_attributes.extend(&[
512            EGL_CONTEXT_OPENGL_PROFILE_MASK as EGLint,
513            EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
514        ]);
515    }
516
517    // Include some extra zeroes to work around broken implementations.
518    //
519    // FIXME(pcwalton): Which implementations are those? (This is copied from Gecko.)
520    egl_context_attributes.extend(&[egl::NONE as EGLint, 0, 0, 0]);
521
522    EGL_FUNCTIONS.with(|egl| {
523        let egl_context = egl.CreateContext(
524            egl_display,
525            egl_config,
526            share_with,
527            egl_context_attributes.as_ptr(),
528        );
529        if egl_context == egl::NO_CONTEXT {
530            let err = egl.GetError();
531            let err = err.to_windowing_api_error();
532            return Err(Error::ContextCreationFailed(err));
533        }
534
535        Ok(egl_context)
536    })
537}
538
539pub(crate) unsafe fn make_no_context_current(egl_display: EGLDisplay) -> Result<(), Error> {
540    EGL_FUNCTIONS.with(|egl| {
541        let result = egl.MakeCurrent(
542            egl_display,
543            egl::NO_SURFACE,
544            egl::NO_SURFACE,
545            egl::NO_CONTEXT,
546        );
547        if result == egl::FALSE {
548            let err = egl.GetError().to_windowing_api_error();
549            return Err(Error::MakeCurrentFailed(err));
550        }
551        Ok(())
552    })
553}
554
555pub(crate) unsafe fn get_config_attr(
556    egl_display: EGLDisplay,
557    egl_config: EGLConfig,
558    attr: EGLint,
559) -> EGLint {
560    EGL_FUNCTIONS.with(|egl| {
561        let mut value = 0;
562        let result = egl.GetConfigAttrib(egl_display, egl_config, attr, &mut value);
563        assert_ne!(result, egl::FALSE);
564        value
565    })
566}
567
568pub(crate) unsafe fn get_context_attr(
569    egl_display: EGLDisplay,
570    egl_context: EGLContext,
571    attr: EGLint,
572) -> EGLint {
573    EGL_FUNCTIONS.with(|egl| {
574        let mut value = 0;
575        let result = egl.QueryContext(egl_display, egl_context, attr, &mut value);
576        assert_ne!(result, egl::FALSE);
577        value
578    })
579}
580
581pub(crate) unsafe fn egl_config_from_id(
582    egl_display: EGLDisplay,
583    egl_config_id: EGLint,
584) -> EGLConfig {
585    let config_attributes = [
586        egl::CONFIG_ID as EGLint,
587        egl_config_id,
588        egl::NONE as EGLint,
589        0,
590        0,
591        0,
592    ];
593
594    EGL_FUNCTIONS.with(|egl| {
595        let (mut config, mut config_count) = (ptr::null(), 0);
596        let result = egl.ChooseConfig(
597            egl_display,
598            config_attributes.as_ptr(),
599            &mut config,
600            1,
601            &mut config_count,
602        );
603        assert_ne!(result, egl::FALSE);
604        assert!(config_count > 0);
605        config
606    })
607}
608
609pub(crate) fn get_proc_address(symbol_name: &str) -> *const c_void {
610    EGL_FUNCTIONS.with(|egl| unsafe {
611        let symbol_name: CString = CString::new(symbol_name).unwrap();
612        egl.GetProcAddress(symbol_name.as_ptr()).cast()
613    })
614}
615
616// Creates and returns a dummy pbuffer surface for the given context. This is used as the default
617// framebuffer on some backends.
618#[allow(dead_code)]
619pub(crate) unsafe fn create_dummy_pbuffer(
620    egl_display: EGLDisplay,
621    egl_context: EGLContext,
622) -> Option<EGLSurface> {
623    let egl_config_id = get_context_attr(egl_display, egl_context, egl::CONFIG_ID as EGLint);
624    let egl_config = egl_config_from_id(egl_display, egl_config_id);
625
626    let pbuffer_attributes = [
627        egl::WIDTH as EGLint,
628        DUMMY_PBUFFER_SIZE,
629        egl::HEIGHT as EGLint,
630        DUMMY_PBUFFER_SIZE,
631        egl::NONE as EGLint,
632        0,
633        0,
634        0,
635    ];
636
637    EGL_FUNCTIONS.with(|egl| {
638        let pbuffer =
639            egl.CreatePbufferSurface(egl_display, egl_config, pbuffer_attributes.as_ptr());
640        if pbuffer == egl::NO_SURFACE {
641            None
642        } else {
643            Some(pbuffer)
644        }
645    })
646}