1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// surfman/src/platform/unix/x11/surface.rs

//! Wrapper for GL-renderable pixmaps on X11.

use crate::context::ContextID;
use crate::gl::types::{GLenum, GLint, GLuint};
use crate::glx::types::{Display as GlxDisplay, GLXFBConfig};
use crate::{gl, glx};
use crate::{Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, WindowingApiError};
use super::context::{Context, GLX_FUNCTIONS, GL_FUNCTIONS};
use super::device::Device;
use super::error;

use euclid::default::Size2D;
use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_int, c_uint, c_void};
use std::thread;
use x11::glx::{GLX_VISUAL_ID, GLXPixmap};
use x11::xlib::{self, Display, Pixmap, VisualID, Window, XCreatePixmap, XDefaultScreen};
use x11::xlib::{XDefaultScreenOfDisplay, XFree, XGetGeometry, XGetVisualInfo};
use x11::xlib::{XRootWindowOfScreen, XVisualInfo};

#[cfg(feature = "sm-winit")]
use winit::Window as WinitWindow;
#[cfg(feature = "sm-winit")]
use winit::os::unix::WindowExt;

const SURFACE_GL_TEXTURE_TARGET: GLenum = gl::TEXTURE_RECTANGLE;

static GLX_PIXMAP_ATTRIBUTES: [c_int; 5] = [
    glx::TEXTURE_FORMAT_EXT as c_int, glx::TEXTURE_FORMAT_RGBA_EXT as c_int,
    glx::TEXTURE_TARGET_EXT as c_int, glx::TEXTURE_RECTANGLE_EXT as c_int,
    0,
];

pub struct Surface {
    pub(crate) size: Size2D<i32>,
    pub(crate) context_id: ContextID,
    pub(crate) drawables: SurfaceDrawables,
    destroyed: bool,
}

pub struct SurfaceTexture {
    pub(crate) surface: Surface,
    pub(crate) gl_texture: GLuint,
    pub(crate) phantom: PhantomData<*const ()>,
}

pub(crate) enum SurfaceDrawables {
    Pixmap {
        glx_pixmap: GLXPixmap,
        #[allow(dead_code)]
        pixmap: Pixmap,
    },
    Window {
        window: Window,
    },
}

pub struct NativeWidget {
    pub(crate) window: Window,
}

unsafe impl Send for Surface {}

impl Debug for Surface {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        write!(formatter, "Surface({:x})", self.id().0)
    }
}

impl Drop for Surface {
    fn drop(&mut self) {
        if !self.destroyed && !thread::panicking() {
            panic!("Should have destroyed the surface first with `destroy_surface()`!")
        }
    }
}

impl Device {
    pub fn create_surface(&mut self,
                          context: &Context,
                          _: SurfaceAccess,
                          surface_type: &SurfaceType<NativeWidget>)
                          -> Result<Surface, Error> {
        match *surface_type {
            SurfaceType::Generic { ref size } => self.create_generic_surface(context, size),
            SurfaceType::Widget { ref native_widget } => {
                self.create_widget_surface(context, native_widget)
            }
        }
    }

    fn create_generic_surface(&mut self, context: &Context, size: &Size2D<i32>)
                              -> Result<Surface, Error> {
        let display = self.connection.native_display.display();
        let glx_display = self.glx_display();

        let context_descriptor = self.context_descriptor(context);
        let glx_fb_config = self.context_descriptor_to_glx_fb_config(&context_descriptor);

        unsafe {
            let (glx_pixmap, pixmap) = create_pixmaps(display, glx_display, glx_fb_config, size)?;
            Ok(Surface {
                drawables: SurfaceDrawables::Pixmap { glx_pixmap, pixmap },
                size: *size,
                context_id: context.id,
                destroyed: false,
            })
        }
    }

    fn create_widget_surface(&mut self, context: &Context, native_widget: &NativeWidget)
                             -> Result<Surface, Error> {
        let display = self.connection.native_display.display();
        unsafe {
            let (mut root_window, mut x, mut y, mut width, mut height) = (0, 0, 0, 0, 0);
            let (mut border_width, mut depth) = (0, 0);
            XGetGeometry(display,
                         native_widget.window,
                         &mut root_window,
                         &mut x,
                         &mut y,
                         &mut width,
                         &mut height,
                         &mut border_width,
                         &mut depth);
            Ok(Surface {
                size: Size2D::new(width as i32, height as i32),
                context_id: context.id,
                drawables: SurfaceDrawables::Window { window: native_widget.window },
                destroyed: false,
            })
        }
    }

    pub fn create_surface_texture(&self, context: &mut Context, surface: Surface)
                                  -> Result<SurfaceTexture, Error> {

        GLX_FUNCTIONS.with(|glx| {
            GL_FUNCTIONS.with(|gl| {
                let mut gl_texture = 0;
                unsafe {
                    let glx_pixmap = match surface.drawables {
                        SurfaceDrawables::Window { .. } => {
                            drop(self.destroy_surface(context, surface));
                            return Err(Error::WidgetAttached);
                        }
                        SurfaceDrawables::Pixmap { glx_pixmap, .. } => glx_pixmap,
                    };

                    drop(self.make_context_current(context));

                    // Create a texture.
                    gl.GenTextures(1, &mut gl_texture);
                    debug_assert_ne!(gl_texture, 0);
                    gl.BindTexture(gl::TEXTURE_RECTANGLE, gl_texture);

                    // Initialize the texture, for convenience.
                    gl.TexParameteri(gl::TEXTURE_RECTANGLE,
                                     gl::TEXTURE_MAG_FILTER,
                                     gl::LINEAR as GLint);
                    gl.TexParameteri(gl::TEXTURE_RECTANGLE,
                                     gl::TEXTURE_MIN_FILTER,
                                     gl::LINEAR as GLint);
                    gl.TexParameteri(gl::TEXTURE_RECTANGLE,
                                     gl::TEXTURE_WRAP_S,
                                     gl::CLAMP_TO_EDGE as GLint);
                    gl.TexParameteri(gl::TEXTURE_RECTANGLE,
                                     gl::TEXTURE_WRAP_T,
                                     gl::CLAMP_TO_EDGE as GLint);

                    // Bind the surface's GLX pixmap to the texture.
                    let display = self.connection.native_display.display() as *mut GlxDisplay;
                    glx.BindTexImageEXT(display,
                                        glx_pixmap,
                                        glx::FRONT_EXT as c_int,
                                        GLX_PIXMAP_ATTRIBUTES.as_ptr());

                    gl.BindTexture(gl::TEXTURE_RECTANGLE, 0);
                    debug_assert_eq!(gl.GetError(), gl::NO_ERROR);
                }

                Ok(SurfaceTexture { surface, gl_texture, phantom: PhantomData })
            })
        })
    }

    pub fn destroy_surface(&self, context: &mut Context, mut surface: Surface)
                           -> Result<(), Error> {
        if context.id != surface.context_id {
            // Avoid a panic and just leak the surface.
            surface.destroyed = true;
            return Err(Error::IncompatibleSurface)
        }

        self.make_no_context_current()?;

        match surface.drawables {
            SurfaceDrawables::Pixmap { ref mut glx_pixmap, pixmap: _ } => {
                let glx_display = self.glx_display();
                GLX_FUNCTIONS.with(|glx| {
                    unsafe {
                        glx.DestroyPixmap(glx_display, *glx_pixmap);
                        *glx_pixmap = 0;
                    }
                });
            }
            SurfaceDrawables::Window { .. } => {}
        }

        surface.destroyed = true;
        Ok(())
    }

    pub fn destroy_surface_texture(&self, _: &mut Context, mut surface_texture: SurfaceTexture)
                                   -> Result<Surface, Error> {
        let glx_pixmap = match surface_texture.surface.drawables {
            SurfaceDrawables::Pixmap { glx_pixmap, .. } => glx_pixmap,
            SurfaceDrawables::Window { .. } => unreachable!(),
        };

        GLX_FUNCTIONS.with(|glx| {
            GL_FUNCTIONS.with(|gl| {
                unsafe {
                    gl.BindTexture(gl::TEXTURE_RECTANGLE, surface_texture.gl_texture);

                    // Release the GLX pixmap.
                    let display = self.connection.native_display.display() as *mut GlxDisplay;
                    glx.ReleaseTexImageEXT(display, glx_pixmap, glx::FRONT_EXT as c_int);

                    gl.BindTexture(gl::TEXTURE_RECTANGLE, 0);
                    gl.DeleteTextures(1, &mut surface_texture.gl_texture);
                    surface_texture.gl_texture = 0;
                }

                Ok(surface_texture.surface)
            })
        })
    }

    #[inline]
    pub fn surface_gl_texture_target(&self) -> GLenum {
        SURFACE_GL_TEXTURE_TARGET
    }

    #[inline]
    pub fn lock_surface_data<'s>(&self, _: &'s mut Surface)
                                 -> Result<SurfaceDataGuard<'s>, Error> {
        Err(Error::Unimplemented)
    }

    // TODO(pcwalton): Use the `XPRESENT` extension.
    pub fn present_surface(&self, _: &Context, surface: &mut Surface) -> Result<(), Error> {
        unsafe {
            GLX_FUNCTIONS.with(|glx| {
                match surface.drawables {
                    SurfaceDrawables::Window { window } => {
                        glx.SwapBuffers(self.glx_display(), window);
                        Ok(())
                    }
                    SurfaceDrawables::Pixmap { .. } => Err(Error::NoWidgetAttached),
                }
            })
        }
    }

    #[inline]
    pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo {
        SurfaceInfo {
            size: surface.size,
            id: surface.id(),
            context_id: surface.context_id,
            framebuffer_object: 0,
        }
    }
}

impl Surface {
    fn id(&self) -> SurfaceID {
        match self.drawables {
            SurfaceDrawables::Pixmap { glx_pixmap, .. } => SurfaceID(glx_pixmap as usize),
            SurfaceDrawables::Window { window } => SurfaceID(window as usize),
        }
    }
}

impl SurfaceTexture {
    #[inline]
    pub fn gl_texture(&self) -> GLuint {
        self.gl_texture
    }
}

impl NativeWidget {
    #[cfg(feature = "sm-winit")]
    #[inline]
    pub fn from_winit_window(window: &WinitWindow) -> NativeWidget {
        NativeWidget { window: window.get_xlib_window().expect("Where's the X11 window?") }
    }
}

pub struct SurfaceDataGuard<'a> {
    phantom: PhantomData<&'a ()>,
}

unsafe fn get_depth_of_visual_with_id(display: *mut Display, visual_id: VisualID)
                                      -> Option<c_uint> {
    let mut visual_info_template: XVisualInfo = mem::zeroed();
    visual_info_template.screen = XDefaultScreen(display);
    visual_info_template.visualid = visual_id;

    let mut matched_visual_infos_count = 0;
    let matched_visual_infos = XGetVisualInfo(display,
                                              xlib::VisualIDMask | xlib::VisualScreenMask,
                                              &mut visual_info_template,
                                              &mut matched_visual_infos_count);
    if matched_visual_infos_count == 0 || matched_visual_infos.is_null() {
        return None;
    }

    let depth = (*matched_visual_infos).depth as c_uint;
    XFree(matched_visual_infos as *mut c_void);
    Some(depth)
}

pub(crate) unsafe fn create_pixmaps(display: *mut Display,
                                    glx_display: *mut GlxDisplay,
                                    glx_fb_config: GLXFBConfig,
                                    size: &Size2D<i32>)
                                    -> Result<(GLXPixmap, Pixmap), Error> {
    GLX_FUNCTIONS.with(|glx| {
        let mut glx_visual_id = 0;
        let result = glx.GetFBConfigAttrib(glx_display,
                                           glx_fb_config,
                                           GLX_VISUAL_ID,
                                           &mut glx_visual_id);
        if result != xlib::Success as c_int {
            let windowing_api_error = error::glx_error_to_windowing_api_error(result);
            return Err(Error::SurfaceCreationFailed(windowing_api_error));
        }

        // Get the depth of the current visual.
        let depth = get_depth_of_visual_with_id(display, glx_visual_id as VisualID);
        let depth = depth.expect("GLX FB config has an invalid visual ID!");

        let pixmap = XCreatePixmap(display,
                                   XRootWindowOfScreen(XDefaultScreenOfDisplay(display)),
                                   size.width as u32,
                                   size.height as u32,
                                   depth);
        if pixmap == 0 {
            return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed));
        }

        // The Khronos documentation page states that `attributes` must be null. This is a filthy
        // lie. In reality, Mesa expects these attributes to be the same as those passed to
        // `glXBindTexImageEXT`. Following the documentation will result in no errors but will
        // produce a black texture.
        let glx_pixmap = glx.CreatePixmap(glx_display,
                                          glx_fb_config,
                                          pixmap,
                                          GLX_PIXMAP_ATTRIBUTES.as_ptr());
        if glx_pixmap == 0 {
            return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed));
        }

        Ok((glx_pixmap, pixmap))
    })
}