f6b3b5b4034fd57754f1adf8054f90552f206a4c
[vlc.git] / modules / video_output / xcb / glx.c
1 /**
2  * @file glx.c
3  * @brief GLX video output module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2004 VLC authors and VideoLAN
7  * Copyright © 2009 Rémi Denis-Courmont
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <assert.h>
30
31 #include <xcb/xcb.h>
32 #include <GL/glx.h>
33 #include <GL/glxext.h>
34
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_xlib.h>
38 #include <vlc_vout_display.h>
39 #include <vlc_opengl.h>
40 #include "../opengl.h"
41
42 #include "xcb_vlc.h"
43
44 static int  Open (vlc_object_t *);
45 static void Close (vlc_object_t *);
46
47 /*
48  * Module descriptor
49  */
50 vlc_module_begin ()
51     set_shortname (N_("GLX"))
52     set_description (N_("OpenGL GLX video output (XCB)"))
53     set_category (CAT_VIDEO)
54     set_subcategory (SUBCAT_VIDEO_VOUT)
55     set_capability ("vout display", 150)
56     set_callbacks (Open, Close)
57
58     add_shortcut ("xcb-glx", "glx", "opengl", "xid")
59 vlc_module_end ()
60
61 struct vout_display_sys_t
62 {
63     Display *display; /* Xlib instance */
64     xcb_connection_t *conn; /**< XCB connection */
65     vout_window_t *embed; /* VLC window (when windowed) */
66
67     xcb_cursor_t cursor; /* blank cursor */
68     xcb_window_t window; /* drawable X window */
69     xcb_window_t glwin; /* GLX window */
70     bool visible; /* whether to draw */
71
72     GLXContext ctx;
73     vlc_gl_t gl;
74     vout_display_opengl_t *vgl;
75     picture_pool_t *pool; /* picture pool */
76 };
77
78 static picture_pool_t *Pool (vout_display_t *, unsigned);
79 static void PictureRender (vout_display_t *, picture_t *, subpicture_t *);
80 static void PictureDisplay (vout_display_t *, picture_t *, subpicture_t *);
81 static int Control (vout_display_t *, int, va_list);
82 static void Manage (vout_display_t *);
83
84 static void SwapBuffers (vlc_gl_t *gl);
85 static void *GetProcAddress (vlc_gl_t *gl, const char *);
86
87 static unsigned GetScreenNumber (xcb_connection_t *conn,
88                                  const xcb_screen_t *screen)
89 {
90     const xcb_setup_t *setup = xcb_get_setup (conn);
91     unsigned num = 0;
92
93     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);;
94          xcb_screen_next (&i))
95     {
96         if (i.data->root == screen->root)
97             return num;
98         num++;
99     }
100 }
101
102 static bool CheckGLX (vout_display_t *vd, Display *dpy)
103 {
104     int major, minor;
105     bool ok = false;
106
107     if (!glXQueryVersion (dpy, &major, &minor))
108         msg_Dbg (vd, "GLX extension not available");
109     else
110     if (major != 1)
111         msg_Dbg (vd, "GLX extension version %d.%d unknown", major, minor);
112     else
113     if (minor < 3)
114         msg_Dbg (vd, "GLX extension version %d.%d too old", major, minor);
115     else
116     {
117         msg_Dbg (vd, "using GLX extension version %d.%d", major, minor);
118         ok = true;
119     }
120     return ok;
121 }
122
123 static int CreateWindow (vout_display_t *vd, xcb_connection_t *conn,
124                          const xcb_screen_t *screen,
125                          uint_fast16_t width, uint_fast16_t height)
126 {
127     vout_display_sys_t *sys = vd->sys;
128     xcb_pixmap_t pixmap = xcb_generate_id (conn);
129     const uint32_t mask =
130         XCB_CW_BACK_PIXMAP |
131         XCB_CW_BACK_PIXEL |
132         XCB_CW_BORDER_PIXMAP |
133         XCB_CW_BORDER_PIXEL |
134         XCB_CW_EVENT_MASK |
135         XCB_CW_COLORMAP;
136     const uint32_t values[] = {
137         pixmap,
138         screen->black_pixel,
139         pixmap,
140         screen->black_pixel,
141         XCB_EVENT_MASK_VISIBILITY_CHANGE,
142         screen->default_colormap,
143     };
144     xcb_void_cookie_t cc, cm;
145
146     xcb_create_pixmap (conn, screen->root_depth, pixmap, screen->root, 1, 1);
147     cc = xcb_create_window_checked (conn, screen->root_depth, sys->window,
148                                     sys->embed->handle.xid, 0, 0,
149                                     width, height, 0,
150                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,
151                                     screen->root_visual, mask, values);
152     cm = xcb_map_window_checked (conn, sys->window);
153     if (XCB_error_Check (vd, conn, "cannot create X11 window", cc)
154      || XCB_error_Check (vd, conn, "cannot map X11 window", cm))
155         return VLC_EGENERIC;
156
157     msg_Dbg (vd, "using X11 window %08"PRIx32, sys->window);
158     return VLC_SUCCESS;
159 }
160
161 /**
162  * Probe the X server.
163  */
164 static int Open (vlc_object_t *obj)
165 {
166     if (!vlc_xlib_init (obj))
167         return VLC_EGENERIC;
168
169     vout_display_t *vd = (vout_display_t *)obj;
170     vout_display_sys_t *sys = malloc (sizeof (*sys));
171
172     if (sys == NULL)
173         return VLC_ENOMEM;
174
175     vd->sys = sys;
176     sys->pool = NULL;
177     sys->gl.sys = NULL;
178
179     /* Get window, connect to X server (via XCB) */
180     xcb_connection_t *conn;
181     const xcb_screen_t *scr;
182     uint16_t width, height;
183     sys->embed = XCB_parent_Create (vd, &conn, &scr, &width, &height);
184     if (sys->embed == NULL)
185     {
186         free (sys);
187         return VLC_EGENERIC;
188     }
189     const unsigned snum = GetScreenNumber (conn, scr);
190
191     sys->conn = conn;
192
193     Display *dpy = XOpenDisplay (sys->embed->display.x11);
194     if (dpy == NULL)
195     {
196         xcb_disconnect (conn);
197         vout_display_DeleteWindow (vd, sys->embed);
198         free (sys);
199         return VLC_EGENERIC;
200     }
201     sys->display = dpy;
202     sys->ctx = NULL;
203
204     if (!CheckGLX (vd, dpy))
205         goto error;
206
207     sys->window = xcb_generate_id (conn);
208
209     /* Determine our pixel format */
210     static const int attr[] = {
211         GLX_RED_SIZE, 5,
212         GLX_GREEN_SIZE, 5,
213         GLX_BLUE_SIZE, 5,
214         GLX_DOUBLEBUFFER, True,
215         GLX_X_RENDERABLE, True,
216         GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
217         None
218     };
219
220     xcb_get_window_attributes_reply_t *wa =
221         xcb_get_window_attributes_reply (conn,
222             xcb_get_window_attributes (conn, sys->embed->handle.xid), NULL);
223     if (wa == NULL)
224         goto error;
225     xcb_visualid_t visual = wa->visual;
226     free (wa);
227
228     int nelem;
229     GLXFBConfig *confs = glXChooseFBConfig (dpy, snum, attr, &nelem);
230     if (confs == NULL)
231     {
232         msg_Err (vd, "no GLX frame buffer configurations");
233         goto error;
234     }
235
236     GLXFBConfig conf;
237     bool found = false;
238
239     for (int i = 0; i < nelem && !found; i++)
240     {
241         conf = confs[i];
242
243         XVisualInfo *vi = glXGetVisualFromFBConfig (dpy, conf);
244         if (vi == NULL)
245             continue;
246
247         if (vi->visualid == visual)
248             found = true;
249         XFree (vi);
250     }
251     XFree (confs);
252
253     if (!found)
254     {
255         msg_Err (vd, "no matching GLX frame buffer configuration");
256         goto error;
257     }
258
259     sys->glwin = None;
260     if (!CreateWindow (vd, conn, scr, width, height))
261         sys->glwin = glXCreateWindow (dpy, conf, sys->window, NULL );
262     if (sys->glwin == None)
263     {
264         msg_Err (vd, "cannot create GLX window");
265         goto error;
266     }
267
268     /* Create an OpenGL context */
269     sys->ctx = glXCreateNewContext (dpy, conf, GLX_RGBA_TYPE, NULL, True);
270     if (sys->ctx == NULL)
271     {
272         msg_Err (vd, "cannot create GLX context");
273         goto error;
274     }
275
276     if (!glXMakeContextCurrent (dpy, sys->glwin, sys->glwin, sys->ctx))
277         goto error;
278
279     const char *glx_extensions = glXQueryExtensionsString (dpy, snum);
280
281     bool is_swap_interval_set = false;
282 #ifdef GLX_SGI_swap_control
283     if (HasExtension (glx_extensions, "GLX_SGI_swap_control")) {
284         PFNGLXSWAPINTERVALSGIPROC SwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)GetProcAddress (NULL, "glXSwapIntervalSGI");
285         if (!is_swap_interval_set && SwapIntervalSGI)
286             is_swap_interval_set = !SwapIntervalSGI (1);
287     }
288 #endif
289 #ifdef GLX_EXT_swap_control
290     if (HasExtension (glx_extensions, "GLX_EXT_swap_control")) {
291         PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)GetProcAddress (NULL, "glXSwapIntervalEXT");
292         if (!is_swap_interval_set && SwapIntervalEXT)
293         {
294             SwapIntervalEXT (dpy, sys->glwin, 1);
295             is_swap_interval_set = true;
296         }
297     }
298 #endif
299
300     /* Initialize common OpenGL video display */
301     sys->gl.lock = NULL;
302     sys->gl.unlock = NULL;
303     sys->gl.swap = SwapBuffers;
304     sys->gl.getProcAddress = GetProcAddress;
305     sys->gl.sys = sys;
306
307     vout_display_info_t info = vd->info;
308     info.has_pictures_invalid = false;
309     info.has_event_thread = true;
310
311     sys->vgl = vout_display_opengl_New (&vd->fmt, &info.subpicture_chromas,
312                                         &sys->gl);
313     if (!sys->vgl)
314     {
315         sys->gl.sys = NULL;
316         goto error;
317     }
318
319     sys->cursor = XCB_cursor_Create (conn, scr);
320     sys->visible = false;
321
322     /* Setup vout_display_t once everything is fine */
323     vd->info = info;
324     vd->pool = Pool;
325     vd->prepare = PictureRender;
326     vd->display = PictureDisplay;
327     vd->control = Control;
328     vd->manage = Manage;
329
330     /* */
331     bool is_fullscreen = vd->cfg->is_fullscreen;
332     if (is_fullscreen && vout_window_SetFullScreen (sys->embed, true))
333         is_fullscreen = false;
334     vout_display_SendEventFullscreen (vd, is_fullscreen);
335     vout_display_SendEventDisplaySize (vd, width, height, is_fullscreen);
336
337     return VLC_SUCCESS;
338
339 error:
340     Close (obj);
341     return VLC_EGENERIC;
342 }
343
344
345 /**
346  * Disconnect from the X server.
347  */
348 static void Close (vlc_object_t *obj)
349 {
350     vout_display_t *vd = (vout_display_t *)obj;
351     vout_display_sys_t *sys = vd->sys;
352     Display *dpy = sys->display;
353
354     if (sys->gl.sys != NULL)
355         vout_display_opengl_Delete (sys->vgl);
356
357     if (sys->ctx != NULL)
358     {
359         glXMakeContextCurrent (dpy, None, None, NULL);
360         glXDestroyContext (dpy, sys->ctx);
361         glXDestroyWindow (dpy, sys->glwin);
362     }
363     XCloseDisplay (dpy);
364
365     /* show the default cursor */
366     xcb_change_window_attributes (sys->conn, sys->embed->handle.xid,
367                                XCB_CW_CURSOR, &(uint32_t) { XCB_CURSOR_NONE });
368     xcb_flush (sys->conn);
369     xcb_disconnect (sys->conn);
370
371     vout_display_DeleteWindow (vd, sys->embed);
372     free (sys);
373 }
374
375 static void SwapBuffers (vlc_gl_t *gl)
376 {
377     vout_display_sys_t *sys = gl->sys;
378
379     glXSwapBuffers (sys->display, sys->glwin);
380 }
381
382 static void *GetProcAddress (vlc_gl_t *gl, const char *name)
383 {
384     (void)gl;
385 #ifdef GLX_ARB_get_proc_address
386     return glXGetProcAddressARB ((const GLubyte *)name);
387 #else
388     return NULL;
389 #endif
390 }
391
392 /**
393  * Return a direct buffer
394  */
395 static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count)
396 {
397     vout_display_sys_t *sys = vd->sys;
398
399     if (!sys->pool)
400         sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
401     return sys->pool;
402 }
403
404 static void PictureRender (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
405 {
406     vout_display_sys_t *sys = vd->sys;
407
408     vout_display_opengl_Prepare (sys->vgl, pic, subpicture);
409 }
410
411 static void PictureDisplay (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
412 {
413     vout_display_sys_t *sys = vd->sys;
414
415     vout_display_opengl_Display (sys->vgl, &vd->source);
416     picture_Release (pic);
417     if (subpicture)
418         subpicture_Delete(subpicture);
419 }
420
421 static int Control (vout_display_t *vd, int query, va_list ap)
422 {
423     vout_display_sys_t *sys = vd->sys;
424
425     switch (query)
426     {
427     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
428     {
429         const vout_display_cfg_t *c = va_arg (ap, const vout_display_cfg_t *);
430         return vout_window_SetFullScreen (sys->embed, c->is_fullscreen);
431     }
432
433     case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
434     {
435         unsigned state = va_arg (ap, unsigned);
436         return vout_window_SetState (sys->embed, state);
437     }
438
439     case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
440     case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
441     case VOUT_DISPLAY_CHANGE_ZOOM:
442     case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
443     case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
444     {
445         const vout_display_cfg_t *cfg;
446         const video_format_t *source;
447         bool is_forced = false;
448
449         if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
450          || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
451         {
452             source = (const video_format_t *)va_arg (ap, const video_format_t *);
453             cfg = vd->cfg;
454         }
455         else
456         {
457             source = &vd->source;
458             cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
459             if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
460                 is_forced = (bool)va_arg (ap, int);
461         }
462
463         /* */
464         if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
465          && is_forced
466          && (cfg->display.width  != vd->cfg->display.width
467            ||cfg->display.height != vd->cfg->display.height)
468          && vout_window_SetSize (sys->embed,
469                                  cfg->display.width, cfg->display.height))
470             return VLC_EGENERIC;
471
472         vout_display_place_t place;
473         vout_display_PlacePicture (&place, source, cfg, false);
474
475         /* Move the picture within the window */
476         const uint32_t values[] = { place.x, place.y,
477                                     place.width, place.height, };
478         xcb_void_cookie_t ck =
479             xcb_configure_window_checked (sys->conn, sys->window,
480                             XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y
481                           | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
482                               values);
483         if (XCB_error_Check (vd, sys->conn, "cannot resize X11 window", ck))
484             return VLC_EGENERIC;
485
486         glViewport (0, 0, place.width, place.height);
487         return VLC_SUCCESS;
488     }
489
490     /* Hide the mouse. It will be send when
491      * vout_display_t::info.b_hide_mouse is false */
492     case VOUT_DISPLAY_HIDE_MOUSE:
493         xcb_change_window_attributes (sys->conn, sys->embed->handle.xid,
494                                     XCB_CW_CURSOR, &(uint32_t){ sys->cursor });
495         xcb_flush (sys->conn);
496         return VLC_SUCCESS;
497
498     case VOUT_DISPLAY_GET_OPENGL:
499     {
500         vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
501         *gl = &sys->gl;
502         return VLC_SUCCESS;
503     }
504
505     case VOUT_DISPLAY_RESET_PICTURES:
506         assert (0);
507     default:
508         msg_Err (vd, "Unknown request in XCB vout display");
509         return VLC_EGENERIC;
510     }
511 }
512
513 static void Manage (vout_display_t *vd)
514 {
515     vout_display_sys_t *sys = vd->sys;
516
517     XCB_Manage (vd, sys->conn, &sys->visible);
518 }