Skip to main content

surfman/base/egl/
context.rs

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