Skip to main content

surfman/base/egl/
surface.rs

1//! Functionality common to backends using EGL surfaces.
2
3use super::context::CurrentContextGuard;
4use super::device::EGL_FUNCTIONS;
5use crate::base::egl::error::ToWindowingApiError;
6use crate::base::egl::ffi::{
7    EGLClientBuffer, EGLImageKHR, EGL_EXTENSION_FUNCTIONS, EGL_GL_TEXTURE_2D_KHR,
8    EGL_IMAGE_PRESERVED_KHR, EGL_NO_IMAGE_KHR,
9};
10use crate::egl;
11use crate::egl::types::{EGLAttrib, EGLConfig, EGLContext, EGLDisplay, EGLSurface, EGLint};
12use crate::gl;
13use crate::gl_utils;
14use crate::renderbuffers::Renderbuffers;
15use crate::Gl;
16use crate::{ContextAttributes, ContextID, Error, SurfaceID, SurfaceInfo};
17
18use euclid::default::Size2D;
19use glow::{Framebuffer, HasContext, PixelUnpackData, Texture};
20use std::fmt::{self, Debug, Formatter};
21use std::marker::PhantomData;
22use std::mem;
23use std::os::raw::c_void;
24use std::ptr;
25
26#[allow(dead_code)]
27#[derive(Clone)]
28pub(crate) struct ExternalEGLSurfaces {
29    pub(crate) draw: EGLSurface,
30    pub(crate) read: EGLSurface,
31}
32
33pub struct EGLBackedSurface {
34    pub(crate) context_id: ContextID,
35    pub(crate) size: Size2D<i32>,
36    pub(crate) objects: EGLSurfaceObjects,
37    pub(crate) destroyed: bool,
38}
39
40impl Debug for EGLBackedSurface {
41    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
42        write!(f, "Surface({:x})", self.id().0)
43    }
44}
45
46unsafe impl Send for EGLBackedSurface {}
47
48#[allow(dead_code)]
49pub(crate) enum EGLSurfaceObjects {
50    TextureImage {
51        egl_image: EGLImageKHR,
52        framebuffer_object: Option<Framebuffer>,
53        texture_object: Option<Texture>,
54        renderbuffers: Renderbuffers,
55    },
56    Window {
57        native_window: *const c_void,
58        egl_surface: EGLSurface,
59    },
60}
61
62pub(crate) struct EGLSurfaceTexture {
63    pub(crate) surface: EGLBackedSurface,
64    pub(crate) texture_object: Option<Texture>,
65    pub(crate) phantom: PhantomData<*const ()>,
66}
67
68impl Debug for EGLSurfaceTexture {
69    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
70        write!(f, "SurfaceTexture({:?})", self.surface)
71    }
72}
73
74impl EGLBackedSurface {
75    pub(crate) fn new_generic(
76        gl: &Gl,
77        egl_display: EGLDisplay,
78        egl_context: EGLContext,
79        context_id: ContextID,
80        context_attributes: &ContextAttributes,
81        size: &Size2D<i32>,
82    ) -> EGLBackedSurface {
83        let egl_image_attribs = [
84            EGL_IMAGE_PRESERVED_KHR as EGLint,
85            egl::FALSE as EGLint,
86            egl::NONE as EGLint,
87            0,
88        ];
89
90        unsafe {
91            // Create our texture.
92            let texture_object = gl.create_texture().ok();
93            // Save the current texture binding
94            let old_texture_object = gl.get_parameter_texture(gl::TEXTURE_BINDING_2D);
95            gl.bind_texture(gl::TEXTURE_2D, texture_object);
96            // Unbind PIXEL_UNPACK_BUFFER, because if it is bound,
97            // it can cause errors in glTexImage2D.
98            // TODO: should this be inside a check for GL 2.0?
99            let unpack_buffer = gl.get_parameter_buffer(gl::PIXEL_UNPACK_BUFFER_BINDING);
100            if unpack_buffer.is_some() {
101                gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, None);
102            }
103            gl.tex_image_2d(
104                gl::TEXTURE_2D,
105                0,
106                gl::RGBA as i32,
107                size.width,
108                size.height,
109                0,
110                gl::RGBA,
111                gl::UNSIGNED_BYTE,
112                PixelUnpackData::Slice(None),
113            );
114            // Restore the old bindings
115            gl.bind_texture(gl::TEXTURE_2D, old_texture_object);
116            if unpack_buffer.is_some() {
117                gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, unpack_buffer);
118            }
119
120            // Create our image.
121            let egl_client_buffer =
122                texture_object.map_or(0, |tex| tex.0.get()) as usize as EGLClientBuffer;
123            let egl_image = (EGL_EXTENSION_FUNCTIONS.CreateImageKHR)(
124                egl_display,
125                egl_context,
126                EGL_GL_TEXTURE_2D_KHR,
127                egl_client_buffer,
128                egl_image_attribs.as_ptr(),
129            );
130
131            // Create the framebuffer, and bind the texture to it.
132            let framebuffer_object =
133                gl_utils::create_and_bind_framebuffer(gl, gl::TEXTURE_2D, texture_object);
134
135            // Bind renderbuffers as appropriate.
136            let renderbuffers = Renderbuffers::new(gl, size, context_attributes);
137            renderbuffers.bind_to_current_framebuffer(gl);
138
139            debug_assert_eq!(
140                gl.check_framebuffer_status(gl::FRAMEBUFFER),
141                gl::FRAMEBUFFER_COMPLETE
142            );
143
144            EGLBackedSurface {
145                context_id,
146                size: *size,
147                objects: EGLSurfaceObjects::TextureImage {
148                    egl_image,
149                    framebuffer_object: Some(framebuffer_object),
150                    texture_object,
151                    renderbuffers,
152                },
153                destroyed: false,
154            }
155        }
156    }
157
158    pub(crate) fn new_window(
159        egl_display: EGLDisplay,
160        egl_config: EGLConfig,
161        native_window: *mut c_void,
162        context_id: ContextID,
163        size: &Size2D<i32>,
164    ) -> EGLBackedSurface {
165        EGL_FUNCTIONS.with(|egl| unsafe {
166            let window_surface_attribs = [egl::NONE as EGLAttrib];
167            let egl_surface = egl.CreatePlatformWindowSurface(
168                egl_display,
169                egl_config,
170                native_window,
171                window_surface_attribs.as_ptr(),
172            );
173            assert_ne!(egl_surface, egl::NO_SURFACE);
174
175            EGLBackedSurface {
176                context_id,
177                size: *size,
178                objects: EGLSurfaceObjects::Window {
179                    native_window,
180                    egl_surface,
181                },
182                destroyed: false,
183            }
184        })
185    }
186
187    pub(crate) fn to_surface_texture(
188        self,
189        gl: &Gl,
190    ) -> Result<EGLSurfaceTexture, (Error, EGLBackedSurface)> {
191        unsafe {
192            let egl_image = match self.objects {
193                EGLSurfaceObjects::TextureImage { egl_image, .. } => egl_image,
194                EGLSurfaceObjects::Window { .. } => return Err((Error::WidgetAttached, self)),
195            };
196            let texture_object = bind_egl_image_to_gl_texture(gl, egl_image);
197            Ok(EGLSurfaceTexture {
198                surface: self,
199                texture_object: Some(texture_object),
200                phantom: PhantomData,
201            })
202        }
203    }
204
205    pub(crate) fn destroy(
206        &mut self,
207        gl: &Gl,
208        egl_display: EGLDisplay,
209        context_id: ContextID,
210    ) -> Result<Option<*const c_void>, Error> {
211        if context_id != self.context_id {
212            return Err(Error::IncompatibleSurface);
213        }
214
215        unsafe {
216            match self.objects {
217                EGLSurfaceObjects::TextureImage {
218                    ref mut egl_image,
219                    ref mut framebuffer_object,
220                    ref mut texture_object,
221                    ref mut renderbuffers,
222                } => {
223                    gl.bind_framebuffer(gl::FRAMEBUFFER, None);
224                    if let Some(framebuffer) = framebuffer_object.take() {
225                        gl.delete_framebuffer(framebuffer);
226                    }
227                    renderbuffers.destroy(gl);
228
229                    let result = (EGL_EXTENSION_FUNCTIONS.DestroyImageKHR)(egl_display, *egl_image);
230                    assert_ne!(result, egl::FALSE);
231                    *egl_image = EGL_NO_IMAGE_KHR;
232
233                    if let Some(texture) = texture_object.take() {
234                        gl.delete_texture(texture);
235                    }
236
237                    self.destroyed = true;
238                    Ok(None)
239                }
240                EGLSurfaceObjects::Window {
241                    ref mut egl_surface,
242                    ref mut native_window,
243                } => {
244                    EGL_FUNCTIONS.with(|egl| {
245                        egl.DestroySurface(egl_display, *egl_surface);
246                        *egl_surface = egl::NO_SURFACE;
247                    });
248
249                    self.destroyed = true;
250                    Ok(Some(mem::replace(native_window, ptr::null())))
251                }
252            }
253        }
254    }
255
256    // TODO(pcwalton): Damage regions.
257    pub(crate) fn present(
258        &self,
259        egl_display: EGLDisplay,
260        egl_context: EGLContext,
261    ) -> Result<(), Error> {
262        unsafe {
263            match self.objects {
264                EGLSurfaceObjects::Window { egl_surface, .. } => {
265                    // The surface must be bound to the current context in EGL 1.4. Temporarily
266                    // make this surface current to enforce this.
267                    let _guard = CurrentContextGuard::new();
268
269                    EGL_FUNCTIONS.with(|egl| {
270                        let result =
271                            egl.MakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
272                        if result == egl::FALSE {
273                            let err = egl.GetError().to_windowing_api_error();
274                            return Err(Error::MakeCurrentFailed(err));
275                        }
276
277                        let ok = egl.SwapBuffers(egl_display, egl_surface);
278                        if ok != egl::FALSE {
279                            Ok(())
280                        } else {
281                            Err(Error::PresentFailed(
282                                egl.GetError().to_windowing_api_error(),
283                            ))
284                        }
285                    })
286                }
287                EGLSurfaceObjects::TextureImage { .. } => Err(Error::NoWidgetAttached),
288            }
289        }
290    }
291
292    pub(crate) fn info(&self) -> SurfaceInfo {
293        SurfaceInfo {
294            size: self.size,
295            id: self.id(),
296            context_id: self.context_id,
297            framebuffer_object: match self.objects {
298                EGLSurfaceObjects::TextureImage {
299                    framebuffer_object, ..
300                } => framebuffer_object,
301                EGLSurfaceObjects::Window { .. } => None,
302            },
303        }
304    }
305
306    pub(crate) fn id(&self) -> SurfaceID {
307        match self.objects {
308            EGLSurfaceObjects::TextureImage { egl_image, .. } => SurfaceID(egl_image as usize),
309            EGLSurfaceObjects::Window { egl_surface, .. } => SurfaceID(egl_surface as usize),
310        }
311    }
312
313    pub(crate) fn native_window(&self) -> Result<*const c_void, Error> {
314        match self.objects {
315            EGLSurfaceObjects::TextureImage { .. } => Err(Error::NoWidgetAttached),
316            EGLSurfaceObjects::Window { native_window, .. } => Ok(native_window),
317        }
318    }
319
320    pub(crate) fn unbind(&self, gl: &Gl, egl_display: EGLDisplay, egl_context: EGLContext) {
321        // If we're current, we stay current, but with no surface attached.
322        unsafe {
323            EGL_FUNCTIONS.with(|egl| {
324                if egl.GetCurrentContext() != egl_context {
325                    return;
326                }
327
328                egl.MakeCurrent(egl_display, egl::NO_SURFACE, egl::NO_SURFACE, egl_context);
329
330                match self.objects {
331                    EGLSurfaceObjects::TextureImage {
332                        framebuffer_object: Some(framebuffer_object),
333                        ..
334                    } => {
335                        gl_utils::unbind_framebuffer_if_necessary(gl, framebuffer_object);
336                    }
337                    EGLSurfaceObjects::TextureImage { .. } | EGLSurfaceObjects::Window { .. } => {}
338                }
339            })
340        }
341    }
342
343    pub(crate) fn egl_surfaces(&self) -> ExternalEGLSurfaces {
344        match self.objects {
345            EGLSurfaceObjects::Window { egl_surface, .. } => ExternalEGLSurfaces {
346                draw: egl_surface,
347                read: egl_surface,
348            },
349            EGLSurfaceObjects::TextureImage { .. } => ExternalEGLSurfaces::default(),
350        }
351    }
352
353    pub(crate) fn resize(&mut self, size: Size2D<i32>) {
354        self.size = size;
355    }
356}
357
358impl EGLSurfaceTexture {
359    pub(crate) fn destroy(mut self, gl: &Gl) -> EGLBackedSurface {
360        if let Some(texture) = self.texture_object.take() {
361            unsafe {
362                gl.delete_texture(texture);
363            }
364        }
365        self.surface
366    }
367}
368
369impl Default for ExternalEGLSurfaces {
370    #[inline]
371    fn default() -> ExternalEGLSurfaces {
372        ExternalEGLSurfaces {
373            draw: egl::NO_SURFACE,
374            read: egl::NO_SURFACE,
375        }
376    }
377}
378
379#[allow(dead_code)]
380pub(crate) unsafe fn create_pbuffer_surface(
381    egl_display: EGLDisplay,
382    egl_config: EGLConfig,
383    size: &Size2D<i32>,
384) -> EGLSurface {
385    let attributes = [
386        egl::WIDTH as EGLint,
387        size.width as EGLint,
388        egl::HEIGHT as EGLint,
389        size.height as EGLint,
390        egl::TEXTURE_FORMAT as EGLint,
391        egl::TEXTURE_RGBA as EGLint,
392        egl::TEXTURE_TARGET as EGLint,
393        egl::TEXTURE_2D as EGLint,
394        egl::NONE as EGLint,
395        0,
396        0,
397        0,
398    ];
399
400    EGL_FUNCTIONS.with(|egl| {
401        let egl_surface = egl.CreatePbufferSurface(egl_display, egl_config, attributes.as_ptr());
402        assert_ne!(egl_surface, egl::NO_SURFACE);
403        egl_surface
404    })
405}
406
407#[allow(dead_code)]
408pub(crate) unsafe fn bind_egl_image_to_gl_texture(gl: &Gl, egl_image: EGLImageKHR) -> Texture {
409    let texture = gl.create_texture().unwrap();
410
411    let texture_binding = gl.get_parameter_texture(gl::TEXTURE_BINDING_2D);
412
413    // FIXME(pcwalton): Should this be `GL_TEXTURE_EXTERNAL_OES`?
414    gl.bind_texture(gl::TEXTURE_2D, Some(texture));
415    (EGL_EXTENSION_FUNCTIONS.ImageTargetTexture2DOES)(gl::TEXTURE_2D, egl_image);
416    gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
417    gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
418    gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _);
419    gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _);
420    gl.bind_texture(gl::TEXTURE_2D, texture_binding);
421
422    debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
423    texture
424}