surfman/platform/generic/egl/
surface.rs

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