Drop the GLX 1.3 requirement.
[profile/ivi/gstreamer-vaapi.git] / gst-libs / gst / vaapi / gstvaapiutils_glx.c
1 /*
2  *  gstvaapiutils_glx.c - GLX utilties
3  *
4  *  gstreamer-vaapi (C) 2010 Splitted-Desktop Systems
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
19  */
20
21 #define _GNU_SOURCE 1 /* RTLD_DEFAULT */
22 #include "config.h"
23 #include <string.h>
24 #include <math.h>
25 #include <dlfcn.h>
26 #include "gstvaapiutils_glx.h"
27 #include "gstvaapiutils_x11.h"
28
29 #define DEBUG 1
30 #include "gstvaapidebug.h"
31
32 /** Lookup for substring NAME in string EXT using SEP as separators */
33 static gboolean
34 find_string(const char *name, const char *ext, const char *sep)
35 {
36     const char *end;
37     int name_len, n;
38
39     if (!name || !ext)
40         return FALSE;
41
42     end = ext + strlen(ext);
43     name_len = strlen(name);
44     while (ext < end) {
45         n = strcspn(ext, sep);
46         if (n == name_len && strncmp(name, ext, n) == 0)
47             return TRUE;
48         ext += (n + 1);
49     }
50     return FALSE;
51 }
52
53 /**
54  * gl_get_error_string:
55  * @error: an OpenGL error enumeration
56  *
57  * Retrieves the string representation the OpenGL @error.
58  *
59  * Return error: the static string representing the OpenGL @error
60  */
61 const char *
62 gl_get_error_string(GLenum error)
63 {
64     static const struct {
65         GLenum val;
66         const char *str;
67     }
68     gl_errors[] = {
69         { GL_NO_ERROR,          "no error" },
70         { GL_INVALID_ENUM,      "invalid enumerant" },
71         { GL_INVALID_VALUE,     "invalid value" },
72         { GL_INVALID_OPERATION, "invalid operation" },
73         { GL_STACK_OVERFLOW,    "stack overflow" },
74         { GL_STACK_UNDERFLOW,   "stack underflow" },
75         { GL_OUT_OF_MEMORY,     "out of memory" },
76 #ifdef GL_INVALID_FRAMEBUFFER_OPERATION_EXT
77         { GL_INVALID_FRAMEBUFFER_OPERATION_EXT, "invalid framebuffer operation" },
78 #endif
79         { ~0, NULL }
80     };
81
82     guint i;
83     for (i = 0; gl_errors[i].str; i++) {
84         if (gl_errors[i].val == error)
85             return gl_errors[i].str;
86     }
87     return "unknown";
88 }
89
90 /**
91  * gl_purge_errors:
92  *
93  * Purges all OpenGL errors. This function is generally useful to
94  * clear up the pending errors prior to calling gl_check_error().
95  */
96 void
97 gl_purge_errors(void)
98 {
99     while (glGetError() != GL_NO_ERROR)
100         ; /* nothing */
101 }
102
103 /**
104  * gl_check_error:
105  *
106  * Checks whether there is any OpenGL error pending.
107  *
108  * Return value: %TRUE if an error was encountered
109  */
110 gboolean
111 gl_check_error(void)
112 {
113     GLenum error;
114     gboolean has_errors = FALSE;
115
116     while ((error = glGetError()) != GL_NO_ERROR) {
117         GST_DEBUG("glError: %s caught", gl_get_error_string(error));
118         has_errors = TRUE;
119     }
120     return has_errors;
121 }
122
123 /**
124  * gl_get_param:
125  * @param: the parameter name
126  * @pval: return location for the value
127  *
128  * This function is a wrapper around glGetIntegerv() that does extra
129  * error checking.
130  *
131  * Return value: %TRUE on success
132  */
133 gboolean
134 gl_get_param(GLenum param, guint *pval)
135 {
136     GLint val;
137
138     gl_purge_errors();
139     glGetIntegerv(param, &val);
140     if (gl_check_error())
141         return FALSE;
142
143     if (pval)
144         *pval = val;
145     return TRUE;
146 }
147
148 /**
149  * gl_get_texture_param:
150  * @target: the target to which the texture is bound
151  * @param: the parameter name
152  * @pval: return location for the value
153  *
154  * This function is a wrapper around glGetTexLevelParameteriv() that
155  * does extra error checking.
156  *
157  * Return value: %TRUE on success
158  */
159 gboolean
160 gl_get_texture_param(GLenum target, GLenum param, guint *pval)
161 {
162     GLint val;
163
164     gl_purge_errors();
165     glGetTexLevelParameteriv(target, 0, param, &val);
166     if (gl_check_error())
167         return FALSE;
168
169     if (pval)
170         *pval = val;
171     return TRUE;
172 }
173
174 /**
175  * gl_set_bgcolor:
176  * @color: the requested RGB color
177  *
178  * Sets background color to the RGB @color. This basically is a
179  * wrapper around glClearColor().
180  */
181 void
182 gl_set_bgcolor(guint32 color)
183 {
184     glClearColor(
185         ((color >> 16) & 0xff) / 255.0f,
186         ((color >>  8) & 0xff) / 255.0f,
187         ( color        & 0xff) / 255.0f,
188         1.0f
189     );
190 }
191
192 /**
193  * gl_perspective:
194  * @fovy: the field of view angle, in degrees, in the y direction
195  * @aspect: the aspect ratio that determines the field of view in the
196  *   x direction.  The aspect ratio is the ratio of x (width) to y
197  *   (height)
198  * @zNear: the distance from the viewer to the near clipping plane
199  *   (always positive)
200  * @zFar: the distance from the viewer to the far clipping plane
201  *   (always positive)
202  *
203  * Specified a viewing frustum into the world coordinate system. This
204  * basically is the Mesa implementation of gluPerspective().
205  */
206 static void
207 frustum(GLdouble left, GLdouble right,
208         GLdouble bottom, GLdouble top, 
209         GLdouble nearval, GLdouble farval)
210 {
211     GLdouble x, y, a, b, c, d;
212     GLdouble m[16];
213
214     x = (2.0 * nearval) / (right - left);
215     y = (2.0 * nearval) / (top - bottom);
216     a = (right + left) / (right - left);
217     b = (top + bottom) / (top - bottom);
218     c = -(farval + nearval) / ( farval - nearval);
219     d = -(2.0 * farval * nearval) / (farval - nearval);
220
221 #define M(row,col)  m[col*4+row]
222     M(0,0) = x;     M(0,1) = 0.0F;  M(0,2) = a;      M(0,3) = 0.0F;
223     M(1,0) = 0.0F;  M(1,1) = y;     M(1,2) = b;      M(1,3) = 0.0F;
224     M(2,0) = 0.0F;  M(2,1) = 0.0F;  M(2,2) = c;      M(2,3) = d;
225     M(3,0) = 0.0F;  M(3,1) = 0.0F;  M(3,2) = -1.0F;  M(3,3) = 0.0F;
226 #undef M
227
228     glMultMatrixd(m);
229 }
230
231 static void
232 gl_perspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
233 {
234     GLdouble xmin, xmax, ymin, ymax;
235
236     ymax = zNear * tan(fovy * M_PI / 360.0);
237     ymin = -ymax;
238     xmin = ymin * aspect;
239     xmax = ymax * aspect;
240
241     /* Don't call glFrustum() because of error semantics (covglu) */
242     frustum(xmin, xmax, ymin, ymax, zNear, zFar);
243 }
244
245 /**
246  * gl_resize:
247  * @width: the requested width, in pixels
248  * @height: the requested height, in pixels
249  *
250  * Resizes the OpenGL viewport to the specified dimensions, using an
251  * orthogonal projection. (0,0) represents the top-left corner of the
252  * window.
253  */
254 void
255 gl_resize(guint width, guint height)
256 {
257 #define FOVY     60.0f
258 #define ASPECT   1.0f
259 #define Z_NEAR   0.1f
260 #define Z_FAR    100.0f
261 #define Z_CAMERA 0.869f
262
263     glViewport(0, 0, width, height);
264     glMatrixMode(GL_PROJECTION);
265     glLoadIdentity();
266     gl_perspective(FOVY, ASPECT, Z_NEAR, Z_FAR);
267     glMatrixMode(GL_MODELVIEW);
268     glLoadIdentity();
269
270     glTranslatef(-0.5f, -0.5f, -Z_CAMERA);
271     glScalef(1.0f/width, -1.0f/height, 1.0f/width);
272     glTranslatef(0.0f, -1.0f*height, 0.0f);
273 }
274
275 /**
276  * gl_create_context:
277  * @dpy: an X11 #Display
278  * @screen: the associated screen of @dpy
279  * @parent: the parent #GLContextState, or %NULL if none is to be used
280  *
281  * Creates a GLX context sharing textures and displays lists with
282  * @parent, if not %NULL.
283  *
284  * Return value: the newly created GLX context
285  */
286 GLContextState *
287 gl_create_context(Display *dpy, int screen, GLContextState *parent)
288 {
289     GLContextState *cs;
290     GLXFBConfig *fbconfigs = NULL;
291     int fbconfig_id, val, n, n_fbconfigs;
292     Status status;
293
294     static GLint fbconfig_attrs[] = {
295         GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
296         GLX_RENDER_TYPE,   GLX_RGBA_BIT,
297         GLX_DOUBLEBUFFER,  True,
298         GLX_RED_SIZE,      8,
299         GLX_GREEN_SIZE,    8, 
300         GLX_BLUE_SIZE,     8,
301         None
302     };
303
304     cs = malloc(sizeof(*cs));
305     if (!cs)
306         goto error;
307
308     cs->display         = dpy;
309     cs->window          = parent ? parent->window : None;
310     cs->visual          = NULL;
311     cs->context         = NULL;
312     cs->swapped_buffers = FALSE;
313
314     if (parent && parent->context) {
315         status = glXQueryContext(
316             parent->display,
317             parent->context,
318             GLX_FBCONFIG_ID, &fbconfig_id
319         );
320         if (status != Success)
321             goto error;
322
323         if (fbconfig_id == GLX_DONT_CARE)
324             goto choose_fbconfig;
325
326         fbconfigs = glXGetFBConfigs(dpy, screen, &n_fbconfigs);
327         if (!fbconfigs)
328             goto error;
329
330         /* Find out a GLXFBConfig compatible with the parent context */
331         for (n = 0; n < n_fbconfigs; n++) {
332             status = glXGetFBConfigAttrib(
333                 dpy,
334                 fbconfigs[n],
335                 GLX_FBCONFIG_ID, &val
336             );
337             if (status == Success && val == fbconfig_id)
338                 break;
339         }
340         if (n == n_fbconfigs)
341             goto error;
342     }
343     else {
344     choose_fbconfig:
345         fbconfigs = glXChooseFBConfig(
346             dpy,
347             screen,
348             fbconfig_attrs, &n_fbconfigs
349         );
350         if (!fbconfigs)
351             goto error;
352
353         /* Select the first one */
354         n = 0;
355     }
356
357     cs->visual  = glXGetVisualFromFBConfig(dpy, fbconfigs[n]);
358     cs->context = glXCreateNewContext(
359         dpy,
360         fbconfigs[n],
361         GLX_RGBA_TYPE,
362         parent ? parent->context : NULL,
363         True
364     );
365     if (cs->context)
366         goto end;
367
368 error:
369     gl_destroy_context(cs);
370     cs = NULL;
371 end:
372     if (fbconfigs)
373         XFree(fbconfigs);
374     return cs;
375 }
376
377 /**
378  * gl_destroy_context:
379  * @cs: a #GLContextState
380  *
381  * Destroys the GLX context @cs
382  */
383 void
384 gl_destroy_context(GLContextState *cs)
385 {
386     if (!cs)
387         return;
388
389     if (cs->visual) {
390         XFree(cs->visual);
391         cs->visual = NULL;
392     }
393
394     if (cs->display && cs->context) {
395         if (glXGetCurrentContext() == cs->context) {
396             /* XXX: if buffers were never swapped, the application
397                will crash later with the NVIDIA driver */
398             if (!cs->swapped_buffers)
399                 gl_swap_buffers(cs);
400             glXMakeCurrent(cs->display, None, NULL);
401         }
402         glXDestroyContext(cs->display, cs->context);
403         cs->display = NULL;
404         cs->context = NULL;
405     }
406     free(cs);
407 }
408
409 /**
410  * gl_get_current_context:
411  * @cs: return location to the current #GLContextState
412  *
413  * Retrieves the current GLX context, display and drawable packed into
414  * the #GLContextState struct.
415  */
416 void
417 gl_get_current_context(GLContextState *cs)
418 {
419     cs->display = glXGetCurrentDisplay();
420     cs->window  = glXGetCurrentDrawable();
421     cs->context = glXGetCurrentContext();
422 }
423
424 /**
425  * gl_set_current_context:
426  * @new_cs: the requested new #GLContextState
427  * @old_cs: return location to the context that was previously current
428  *
429  * Makes the @new_cs GLX context the current GLX rendering context of
430  * the calling thread, replacing the previously current context if
431  * there was one.
432  *
433  * If @old_cs is non %NULL, the previously current GLX context and
434  * window are recorded.
435  *
436  * Return value: %TRUE on success
437  */
438 gboolean
439 gl_set_current_context(GLContextState *new_cs, GLContextState *old_cs)
440 {
441     /* If display is NULL, this could be that new_cs was retrieved from
442        gl_get_current_context() with none set previously. If that case,
443        the other fields are also NULL and we don't return an error */
444     if (!new_cs->display)
445         return !new_cs->window && !new_cs->context;
446
447     if (old_cs) {
448         if (old_cs == new_cs)
449             return TRUE;
450         gl_get_current_context(old_cs);
451         if (old_cs->display == new_cs->display &&
452             old_cs->window  == new_cs->window  &&
453             old_cs->context == new_cs->context)
454             return TRUE;
455     }
456     return glXMakeCurrent(new_cs->display, new_cs->window, new_cs->context);
457 }
458
459 /**
460  * gl_swap_buffers:
461  * @cs: a #GLContextState
462  *
463  * Promotes the contents of the back buffer of the @win window to
464  * become the contents of the front buffer. This simply is wrapper
465  * around glXSwapBuffers().
466  */
467 void
468 gl_swap_buffers(GLContextState *cs)
469 {
470     glXSwapBuffers(cs->display, cs->window);
471     cs->swapped_buffers = TRUE;
472 }
473
474 /**
475  * gl_bind_texture:
476  * @ts: a #GLTextureState
477  * @target: the target to which the texture is bound
478  * @texture: the name of a texture
479  *
480  * Binds @texture to the specified @target, while recording the
481  * previous state in @ts.
482  *
483  * Return value: %TRUE on success
484  */
485 gboolean
486 gl_bind_texture(GLTextureState *ts, GLenum target, GLuint texture)
487 {
488     ts->target      = target;
489     ts->old_texture = 0;
490     ts->was_bound   = 0;
491     ts->was_enabled = glIsEnabled(target);
492     if (!ts->was_enabled)
493         glEnable(target);
494
495     GLenum texture_binding;
496     switch (target) {
497     case GL_TEXTURE_1D:
498         texture_binding = GL_TEXTURE_BINDING_1D;
499         break;
500     case GL_TEXTURE_2D:
501         texture_binding = GL_TEXTURE_BINDING_2D;
502         break;
503     case GL_TEXTURE_3D:
504         texture_binding = GL_TEXTURE_BINDING_3D;
505         break;
506     case GL_TEXTURE_RECTANGLE_ARB:
507         texture_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB;
508         break;
509     default:
510         g_assert(!texture);
511         return FALSE;
512     }
513
514     if (!gl_get_param(texture_binding, &ts->old_texture))
515         return FALSE;
516
517     ts->was_bound = texture == ts->old_texture;
518     if (!ts->was_bound) {
519         gl_purge_errors();
520         glBindTexture(target, texture);
521         if (gl_check_error())
522             return FALSE;
523     }
524     return TRUE;
525 }
526
527 /**
528  * gl_unbind_texture:
529  * @ts: a #GLTextureState
530  *
531  * Rebinds the texture that was previously bound and recorded in @ts.
532  */
533 void
534 gl_unbind_texture(GLTextureState *ts)
535 {
536     if (!ts->was_bound && ts->old_texture)
537         glBindTexture(ts->target, ts->old_texture);
538     if (!ts->was_enabled)
539         glDisable(ts->target);
540 }
541
542 /**
543  * gl_create_texture:
544  * @target: the target to which the texture is bound
545  * @format: the format of the pixel data
546  * @width: the requested width, in pixels
547  * @height: the requested height, in pixels
548  *
549  * Creates a texture with the specified dimensions and @format. The
550  * internal format will be automatically derived from @format.
551  *
552  * Return value: the newly created texture name
553  */
554 GLuint
555 gl_create_texture(GLenum target, GLenum format, guint width, guint height)
556 {
557     GLenum internal_format;
558     GLuint texture;
559     GLTextureState ts;
560     guint bytes_per_component;
561
562     internal_format = format;
563     switch (format) {
564     case GL_LUMINANCE:
565         bytes_per_component = 1;
566         break;
567     case GL_LUMINANCE_ALPHA:
568         bytes_per_component = 2;
569         break;
570     case GL_RGBA:
571     case GL_BGRA:
572         internal_format = GL_RGBA;
573         bytes_per_component = 4;
574         break;
575     default:
576         bytes_per_component = 0;
577         break;
578     }
579     g_assert(bytes_per_component > 0);
580
581     glGenTextures(1, &texture);
582     if (!gl_bind_texture(&ts, target, texture))
583         return 0;
584     glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
585     glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
586     glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
587     glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
588     glPixelStorei(GL_UNPACK_ALIGNMENT, bytes_per_component);
589     glTexImage2D(
590         target,
591         0,
592         internal_format,
593         width, height,
594         0,
595         format,
596         GL_UNSIGNED_BYTE,
597         NULL
598     );
599     gl_unbind_texture(&ts);
600     return texture;
601 }
602
603 /**
604  * get_proc_address:
605  * @name: the name of the OpenGL extension function to lookup
606  *
607  * Returns the specified OpenGL extension function
608  *
609  * Return value: the OpenGL extension matching @name, or %NULL if none
610  *   was found
611  */
612 typedef void (*GLFuncPtr)(void);
613 typedef GLFuncPtr (*GLXGetProcAddressProc)(const char *);
614
615 static GLFuncPtr
616 get_proc_address_default(const char *name)
617 {
618     return NULL;
619 }
620
621 static GLXGetProcAddressProc
622 get_proc_address_func(void)
623 {
624     GLXGetProcAddressProc get_proc_func;
625
626     dlerror();
627     get_proc_func = (GLXGetProcAddressProc)
628         dlsym(RTLD_DEFAULT, "glXGetProcAddress");
629     if (!dlerror())
630         return get_proc_func;
631
632     get_proc_func = (GLXGetProcAddressProc)
633         dlsym(RTLD_DEFAULT, "glXGetProcAddressARB");
634     if (!dlerror())
635         return get_proc_func;
636
637     return get_proc_address_default;
638 }
639
640 static inline GLFuncPtr
641 get_proc_address(const char *name)
642 {
643     static GLXGetProcAddressProc get_proc_func = NULL;
644     if (!get_proc_func)
645         get_proc_func = get_proc_address_func();
646     return get_proc_func(name);
647 }
648
649 /**
650  * gl_init_vtable:
651  *
652  * Initializes the global #GLVTable.
653  *
654  * Return value: the #GLVTable filled in with OpenGL extensions, or
655  *   %NULL on error.
656  */
657 static GLVTable gl_vtable_static;
658
659 static GLVTable *
660 gl_init_vtable(void)
661 {
662     GLVTable * const gl_vtable = &gl_vtable_static;
663     const gchar *gl_extensions = (const gchar *)glGetString(GL_EXTENSIONS);
664     gboolean has_extension;
665
666     /* GLX_EXT_texture_from_pixmap */
667     gl_vtable->glx_create_pixmap = (PFNGLXCREATEPIXMAPPROC)
668         get_proc_address("glXCreatePixmap");
669     if (!gl_vtable->glx_create_pixmap)
670         return NULL;
671     gl_vtable->glx_destroy_pixmap = (PFNGLXDESTROYPIXMAPPROC)
672         get_proc_address("glXDestroyPixmap");
673     if (!gl_vtable->glx_destroy_pixmap)
674         return NULL;
675     gl_vtable->glx_bind_tex_image = (PFNGLXBINDTEXIMAGEEXTPROC)
676         get_proc_address("glXBindTexImageEXT");
677     if (!gl_vtable->glx_bind_tex_image)
678         return NULL;
679     gl_vtable->glx_release_tex_image = (PFNGLXRELEASETEXIMAGEEXTPROC)
680         get_proc_address("glXReleaseTexImageEXT");
681     if (!gl_vtable->glx_release_tex_image)
682         return NULL;
683
684     /* GL_ARB_framebuffer_object */
685     has_extension = (
686         find_string("GL_ARB_framebuffer_object", gl_extensions, " ") ||
687         find_string("GL_EXT_framebuffer_object", gl_extensions, " ")
688     );
689     if (has_extension) {
690         gl_vtable->gl_gen_framebuffers = (PFNGLGENFRAMEBUFFERSEXTPROC)
691             get_proc_address("glGenFramebuffersEXT");
692         if (!gl_vtable->gl_gen_framebuffers)
693             return NULL;
694         gl_vtable->gl_delete_framebuffers = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
695             get_proc_address("glDeleteFramebuffersEXT");
696         if (!gl_vtable->gl_delete_framebuffers)
697             return NULL;
698         gl_vtable->gl_bind_framebuffer = (PFNGLBINDFRAMEBUFFEREXTPROC)
699             get_proc_address("glBindFramebufferEXT");
700         if (!gl_vtable->gl_bind_framebuffer)
701             return NULL;
702         gl_vtable->gl_gen_renderbuffers = (PFNGLGENRENDERBUFFERSEXTPROC)
703             get_proc_address("glGenRenderbuffersEXT");
704         if (!gl_vtable->gl_gen_renderbuffers)
705             return NULL;
706         gl_vtable->gl_delete_renderbuffers = (PFNGLDELETERENDERBUFFERSEXTPROC)
707             get_proc_address("glDeleteRenderbuffersEXT");
708         if (!gl_vtable->gl_delete_renderbuffers)
709             return NULL;
710         gl_vtable->gl_bind_renderbuffer = (PFNGLBINDRENDERBUFFEREXTPROC)
711             get_proc_address("glBindRenderbufferEXT");
712         if (!gl_vtable->gl_bind_renderbuffer)
713             return NULL;
714         gl_vtable->gl_renderbuffer_storage = (PFNGLRENDERBUFFERSTORAGEEXTPROC)
715             get_proc_address("glRenderbufferStorageEXT");
716         if (!gl_vtable->gl_renderbuffer_storage)
717             return NULL;
718         gl_vtable->gl_framebuffer_renderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)
719             get_proc_address("glFramebufferRenderbufferEXT");
720         if (!gl_vtable->gl_framebuffer_renderbuffer)
721             return NULL;
722         gl_vtable->gl_framebuffer_texture_2d = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
723             get_proc_address("glFramebufferTexture2DEXT");
724         if (!gl_vtable->gl_framebuffer_texture_2d)
725             return NULL;
726         gl_vtable->gl_check_framebuffer_status = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
727             get_proc_address("glCheckFramebufferStatusEXT");
728         if (!gl_vtable->gl_check_framebuffer_status)
729             return NULL;
730         gl_vtable->has_framebuffer_object = TRUE;
731     }
732
733     /* GL_ARB_fragment_program */
734     has_extension = (
735         find_string("GL_ARB_fragment_program", gl_extensions, " ")
736     );
737     if (has_extension) {
738         gl_vtable->gl_gen_programs = (PFNGLGENPROGRAMSARBPROC)
739             get_proc_address("glGenProgramsARB");
740         if (!gl_vtable->gl_gen_programs)
741             return NULL;
742         gl_vtable->gl_delete_programs = (PFNGLDELETEPROGRAMSARBPROC)
743             get_proc_address("glDeleteProgramsARB");
744         if (!gl_vtable->gl_delete_programs)
745             return NULL;
746         gl_vtable->gl_bind_program = (PFNGLBINDPROGRAMARBPROC)
747             get_proc_address("glBindProgramARB");
748         if (!gl_vtable->gl_bind_program)
749             return NULL;
750         gl_vtable->gl_program_string = (PFNGLPROGRAMSTRINGARBPROC)
751             get_proc_address("glProgramStringARB");
752         if (!gl_vtable->gl_program_string)
753             return NULL;
754         gl_vtable->gl_get_program_iv = (PFNGLGETPROGRAMIVARBPROC)
755             get_proc_address("glGetProgramivARB");
756         if (!gl_vtable->gl_get_program_iv)
757             return NULL;
758         gl_vtable->gl_program_local_parameter_4fv = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)
759             get_proc_address("glProgramLocalParameter4fvARB");
760         if (!gl_vtable->gl_program_local_parameter_4fv)
761             return NULL;
762         gl_vtable->has_fragment_program = TRUE;
763     }
764
765     /* GL_ARB_multitexture */
766     has_extension = (
767         find_string("GL_ARB_multitexture", gl_extensions, " ")
768     );
769     if (has_extension) {
770         gl_vtable->gl_active_texture = (PFNGLACTIVETEXTUREPROC)
771             get_proc_address("glActiveTextureARB");
772         if (!gl_vtable->gl_active_texture)
773             return NULL;
774         gl_vtable->gl_multi_tex_coord_2f = (PFNGLMULTITEXCOORD2FPROC)
775             get_proc_address("glMultiTexCoord2fARB");
776         if (!gl_vtable->gl_multi_tex_coord_2f)
777             return NULL;
778         gl_vtable->has_multitexture = TRUE;
779     }
780     return gl_vtable;
781 }
782
783 /**
784  * gl_get_vtable:
785  *
786  * Retrieves a VTable for OpenGL extensions.
787  *
788  * Return value: VTable for OpenGL extensions
789  */
790 GLVTable *
791 gl_get_vtable(void)
792 {
793     static GStaticMutex mutex          = G_STATIC_MUTEX_INIT;
794     static gboolean     gl_vtable_init = TRUE;
795     static GLVTable    *gl_vtable      = NULL;
796
797     g_static_mutex_lock(&mutex);
798     if (gl_vtable_init) {
799         gl_vtable_init = FALSE;
800         gl_vtable      = gl_init_vtable();
801     }
802     g_static_mutex_unlock(&mutex);
803     return gl_vtable;
804 }
805
806 /**
807  * gl_create_pixmap_object:
808  * @dpy: an X11 #Display
809  * @width: the request width, in pixels
810  * @height: the request height, in pixels
811  *
812  * Creates a #GLPixmapObject of the specified dimensions. This
813  * requires the GLX_EXT_texture_from_pixmap extension.
814  *
815  * Return value: the newly created #GLPixmapObject object
816  */
817 GLPixmapObject *
818 gl_create_pixmap_object(Display *dpy, guint width, guint height)
819 {
820     GLVTable * const    gl_vtable = gl_get_vtable();
821     GLPixmapObject     *pixo;
822     GLXFBConfig        *fbconfig;
823     int                 screen;
824     Window              rootwin;
825     XWindowAttributes   wattr;
826     int                *attr;
827     int                 n_fbconfig_attrs;
828
829     int fbconfig_attrs[32] = {
830         GLX_DRAWABLE_TYPE,      GLX_PIXMAP_BIT,
831         GLX_DOUBLEBUFFER,       GL_FALSE,
832         GLX_RENDER_TYPE,        GLX_RGBA_BIT,
833         GLX_X_RENDERABLE,       GL_TRUE,
834         GLX_Y_INVERTED_EXT,     GL_TRUE,
835         GLX_RED_SIZE,           8,
836         GLX_GREEN_SIZE,         8,
837         GLX_BLUE_SIZE,          8,
838         GL_NONE,
839     };
840
841     int pixmap_attrs[10] = {
842         GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
843         GLX_MIPMAP_TEXTURE_EXT, GL_FALSE,
844         GL_NONE,
845     };
846
847     if (!gl_vtable)
848         return NULL;
849
850     screen  = DefaultScreen(dpy);
851     rootwin = RootWindow(dpy, screen);
852
853     /* XXX: this won't work for different displays */
854     if (!gl_vtable->has_texture_from_pixmap) {
855         const char *glx_extensions = glXQueryExtensionsString(dpy, screen);
856         if (!glx_extensions)
857             return NULL;
858         if (!find_string("GLX_EXT_texture_from_pixmap", glx_extensions, " "))
859             return NULL;
860         gl_vtable->has_texture_from_pixmap = TRUE;
861     }
862
863     pixo = calloc(1, sizeof(*pixo));
864     if (!pixo)
865         return NULL;
866
867     pixo->dpy           = dpy;
868     pixo->width         = width;
869     pixo->height        = height;
870     pixo->pixmap        = None;
871     pixo->glx_pixmap    = None;
872     pixo->is_bound      = FALSE;
873
874     XGetWindowAttributes(dpy, rootwin, &wattr);
875     pixo->pixmap  = XCreatePixmap(dpy, rootwin, width, height, wattr.depth);
876     if (!pixo->pixmap)
877         goto error;
878
879     /* Initialize FBConfig attributes */
880     for (attr = fbconfig_attrs; *attr != GL_NONE; attr += 2)
881         ;
882     *attr++ = GLX_DEPTH_SIZE;                 *attr++ = wattr.depth;
883     if (wattr.depth == 32) {
884     *attr++ = GLX_ALPHA_SIZE;                 *attr++ = 8;
885     *attr++ = GLX_BIND_TO_TEXTURE_RGBA_EXT;   *attr++ = GL_TRUE;
886     }
887     else {
888     *attr++ = GLX_BIND_TO_TEXTURE_RGB_EXT;    *attr++ = GL_TRUE;
889     }
890     *attr++ = GL_NONE;
891
892     fbconfig = glXChooseFBConfig(
893         dpy,
894         screen,
895         fbconfig_attrs, &n_fbconfig_attrs
896     );
897     if (!fbconfig)
898         goto error;
899
900     /* Initialize GLX Pixmap attributes */
901     for (attr = pixmap_attrs; *attr != GL_NONE; attr += 2)
902         ;
903     *attr++ = GLX_TEXTURE_FORMAT_EXT;
904     if (wattr.depth == 32)
905     *attr++ = GLX_TEXTURE_FORMAT_RGBA_EXT;
906     else
907     *attr++ = GLX_TEXTURE_FORMAT_RGB_EXT;
908     *attr++ = GL_NONE;
909
910     x11_trap_errors();
911     pixo->glx_pixmap = gl_vtable->glx_create_pixmap(
912         dpy,
913         fbconfig[0],
914         pixo->pixmap,
915         pixmap_attrs
916     );
917     free(fbconfig);
918     if (x11_untrap_errors() != 0)
919         goto error;
920
921     pixo->target = GL_TEXTURE_2D;
922     glGenTextures(1, &pixo->texture);
923     if (!gl_bind_texture(&pixo->old_texture, pixo->target, pixo->texture))
924         goto error;
925     glTexParameteri(pixo->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
926     glTexParameteri(pixo->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
927     gl_unbind_texture(&pixo->old_texture);
928     return pixo;
929
930 error:
931     gl_destroy_pixmap_object(pixo);
932     return NULL;
933 }
934
935 /**
936  * gl_destroy_pixmap_object:
937  * @pixo: a #GLPixmapObject
938  *
939  * Destroys the #GLPixmapObject object.
940  */
941 void
942 gl_destroy_pixmap_object(GLPixmapObject *pixo)
943 {
944     GLVTable * const gl_vtable = gl_get_vtable();
945
946     if (!pixo)
947         return;
948
949     gl_unbind_pixmap_object(pixo);
950
951     if (pixo->texture) {
952         glDeleteTextures(1, &pixo->texture);
953         pixo->texture = 0;
954     }
955
956     if (pixo->glx_pixmap) {
957         gl_vtable->glx_destroy_pixmap(pixo->dpy, pixo->glx_pixmap);
958         pixo->glx_pixmap = None;
959     }
960
961     if (pixo->pixmap) {
962         XFreePixmap(pixo->dpy, pixo->pixmap);
963         pixo->pixmap = None;
964     }
965     free(pixo);
966 }
967
968 /**
969  * gl_bind_pixmap_object:
970  * @pixo: a #GLPixmapObject
971  *
972  * Defines a two-dimensional texture image. The texture image is taken
973  * from the @pixo pixmap and need not be copied. The texture target,
974  * format and size are derived from attributes of the @pixo pixmap.
975  *
976  * Return value: %TRUE on success
977  */
978 gboolean
979 gl_bind_pixmap_object(GLPixmapObject *pixo)
980 {
981     GLVTable * const gl_vtable = gl_get_vtable();
982
983     if (pixo->is_bound)
984         return TRUE;
985
986     if (!gl_bind_texture(&pixo->old_texture, pixo->target, pixo->texture))
987         return FALSE;
988
989     x11_trap_errors();
990     gl_vtable->glx_bind_tex_image(
991         pixo->dpy,
992         pixo->glx_pixmap,
993         GLX_FRONT_LEFT_EXT,
994         NULL
995     );
996     XSync(pixo->dpy, False);
997     if (x11_untrap_errors() != 0) {
998         GST_DEBUG("failed to bind pixmap");
999         return FALSE;
1000     }
1001
1002     pixo->is_bound = TRUE;
1003     return TRUE;
1004 }
1005
1006 /**
1007  * gl_unbind_pixmap_object:
1008  * @pixo: a #GLPixmapObject
1009  *
1010  * Releases a color buffers that is being used as a texture.
1011  *
1012  * Return value: %TRUE on success
1013  */
1014 gboolean
1015 gl_unbind_pixmap_object(GLPixmapObject *pixo)
1016 {
1017     GLVTable * const gl_vtable = gl_get_vtable();
1018
1019     if (!pixo->is_bound)
1020         return TRUE;
1021
1022     x11_trap_errors();
1023     gl_vtable->glx_release_tex_image(
1024         pixo->dpy,
1025         pixo->glx_pixmap,
1026         GLX_FRONT_LEFT_EXT
1027     );
1028     XSync(pixo->dpy, False);
1029     if (x11_untrap_errors() != 0) {
1030         GST_DEBUG("failed to release pixmap");
1031         return FALSE;
1032     }
1033
1034     gl_unbind_texture(&pixo->old_texture);
1035
1036     pixo->is_bound = FALSE;
1037     return TRUE;
1038 }
1039
1040 /**
1041  * gl_create_framebuffer_object:
1042  * @target: the target to which the texture is bound
1043  * @texture: the GL texture to hold the framebuffer
1044  * @width: the requested width, in pixels
1045  * @height: the requested height, in pixels
1046  *
1047  * Creates an FBO with the specified texture and size.
1048  *
1049  * Return value: the newly created #GLFramebufferObject, or %NULL if
1050  *   an error occurred
1051  */
1052 GLFramebufferObject *
1053 gl_create_framebuffer_object(
1054     GLenum target,
1055     GLuint texture,
1056     guint  width,
1057     guint  height
1058 )
1059 {
1060     GLVTable * const gl_vtable = gl_get_vtable();
1061     GLFramebufferObject *fbo;
1062     GLenum status;
1063
1064     if (!gl_vtable || !gl_vtable->has_framebuffer_object)
1065         return NULL;
1066
1067     /* XXX: we only support GL_TEXTURE_2D at this time */
1068     if (target != GL_TEXTURE_2D)
1069         return NULL;
1070
1071     fbo = calloc(1, sizeof(*fbo));
1072     if (!fbo)
1073         return NULL;
1074
1075     fbo->width          = width;
1076     fbo->height         = height;
1077     fbo->fbo            = 0;
1078     fbo->old_fbo        = 0;
1079     fbo->is_bound       = FALSE;
1080
1081     gl_get_param(GL_FRAMEBUFFER_BINDING, &fbo->old_fbo);
1082     gl_vtable->gl_gen_framebuffers(1, &fbo->fbo);
1083     gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->fbo);
1084     gl_vtable->gl_framebuffer_texture_2d(
1085         GL_FRAMEBUFFER_EXT,
1086         GL_COLOR_ATTACHMENT0_EXT,
1087         target, texture,
1088         0
1089     );
1090
1091     status = gl_vtable->gl_check_framebuffer_status(GL_DRAW_FRAMEBUFFER_EXT);
1092     gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->old_fbo);
1093     if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
1094         goto error;
1095     return fbo;
1096
1097 error:
1098     gl_destroy_framebuffer_object(fbo);
1099     return NULL;
1100 }
1101
1102 /**
1103  * gl_destroy_framebuffer_object:
1104  * @fbo: a #GLFramebufferObject
1105  *
1106  * Destroys the @fbo object.
1107  */
1108 void
1109 gl_destroy_framebuffer_object(GLFramebufferObject *fbo)
1110 {
1111     GLVTable * const gl_vtable = gl_get_vtable();
1112
1113     if (!fbo)
1114         return;
1115
1116     gl_unbind_framebuffer_object(fbo);
1117
1118     if (fbo->fbo) {
1119         gl_vtable->gl_delete_framebuffers(1, &fbo->fbo);
1120         fbo->fbo = 0;
1121     }
1122     free(fbo);
1123 }
1124
1125 /**
1126  * gl_bind_framebuffer_object:
1127  * @fbo: a #GLFramebufferObject
1128  *
1129  * Binds @fbo object.
1130  *
1131  * Return value: %TRUE on success
1132  */
1133 gboolean
1134 gl_bind_framebuffer_object(GLFramebufferObject *fbo)
1135 {
1136     GLVTable * const gl_vtable = gl_get_vtable();
1137     const guint width  = fbo->width;
1138     const guint height = fbo->height;
1139
1140     const guint attribs = (GL_VIEWPORT_BIT|
1141                            GL_CURRENT_BIT|
1142                            GL_ENABLE_BIT|
1143                            GL_TEXTURE_BIT|
1144                            GL_COLOR_BUFFER_BIT);
1145
1146     if (fbo->is_bound)
1147         return TRUE;
1148
1149     gl_get_param(GL_FRAMEBUFFER_BINDING, &fbo->old_fbo);
1150     gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->fbo);
1151     glPushAttrib(attribs);
1152     glMatrixMode(GL_PROJECTION);
1153     glPushMatrix();
1154     glLoadIdentity();
1155     glMatrixMode(GL_MODELVIEW);
1156     glPushMatrix();
1157     glLoadIdentity();
1158     glViewport(0, 0, width, height);
1159     glTranslatef(-1.0f, -1.0f, 0.0f);
1160     glScalef(2.0f / width, 2.0f / height, 1.0f);
1161
1162     fbo->is_bound = TRUE;
1163     return TRUE;
1164 }
1165
1166 /**
1167  * gl_unbind_framebuffer_object:
1168  * @fbo: a #GLFramebufferObject
1169  *
1170  * Releases @fbo object.
1171  *
1172  * Return value: %TRUE on success
1173  */
1174 gboolean
1175 gl_unbind_framebuffer_object(GLFramebufferObject *fbo)
1176 {
1177     GLVTable * const gl_vtable = gl_get_vtable();
1178
1179     if (!fbo->is_bound)
1180         return TRUE;
1181
1182     glPopAttrib();
1183     glMatrixMode(GL_PROJECTION);
1184     glPopMatrix();
1185     glMatrixMode(GL_MODELVIEW);
1186     glPopMatrix();
1187     gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->old_fbo);
1188
1189     fbo->is_bound = FALSE;
1190     return TRUE;
1191 }