mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-26 10:10:32 +00:00
97c3b2ddff
Use glib >= 2.32 semantics for GMutex and GRecMutex wrt. initialization and termination. Basically, the new mutex objects can be used as static mutex objects from the deprecated APIs, e.g. GStaticMutex and GStaticRecMutex.
1139 lines
29 KiB
C
1139 lines
29 KiB
C
/*
|
|
* gstvaapiutils_glx.c - GLX utilties
|
|
*
|
|
* Copyright (C) 2010-2011 Splitted-Desktop Systems
|
|
* Copyright (C) 2011-2012 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#define _GNU_SOURCE 1 /* RTLD_DEFAULT */
|
|
#include "sysdeps.h"
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <dlfcn.h>
|
|
#include "gstvaapiutils_glx.h"
|
|
#include "gstvaapiutils_x11.h"
|
|
|
|
#define DEBUG 1
|
|
#include "gstvaapidebug.h"
|
|
|
|
/** Lookup for substring NAME in string EXT using SEP as separators */
|
|
static gboolean
|
|
find_string(const char *name, const char *ext, const char *sep)
|
|
{
|
|
const char *end;
|
|
int name_len, n;
|
|
|
|
if (!name || !ext)
|
|
return FALSE;
|
|
|
|
end = ext + strlen(ext);
|
|
name_len = strlen(name);
|
|
while (ext < end) {
|
|
n = strcspn(ext, sep);
|
|
if (n == name_len && strncmp(name, ext, n) == 0)
|
|
return TRUE;
|
|
ext += (n + 1);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gl_get_error_string:
|
|
* @error: an OpenGL error enumeration
|
|
*
|
|
* Retrieves the string representation the OpenGL @error.
|
|
*
|
|
* Return error: the static string representing the OpenGL @error
|
|
*/
|
|
const char *
|
|
gl_get_error_string(GLenum error)
|
|
{
|
|
switch (error) {
|
|
#define MAP(id, str) \
|
|
case id: return str " (" #id ")"
|
|
MAP(GL_NO_ERROR, "no error");
|
|
MAP(GL_INVALID_ENUM, "invalid enumerant");
|
|
MAP(GL_INVALID_VALUE, "invalid value");
|
|
MAP(GL_INVALID_OPERATION, "invalid operation");
|
|
MAP(GL_STACK_OVERFLOW, "stack overflow");
|
|
MAP(GL_STACK_UNDERFLOW, "stack underflow");
|
|
MAP(GL_OUT_OF_MEMORY, "out of memory");
|
|
#ifdef GL_INVALID_FRAMEBUFFER_OPERATION_EXT
|
|
MAP(GL_INVALID_FRAMEBUFFER_OPERATION_EXT,
|
|
"invalid framebuffer operation");
|
|
#endif
|
|
#undef MAP
|
|
default: break;
|
|
};
|
|
return "<unknown>";
|
|
}
|
|
|
|
/**
|
|
* gl_purge_errors:
|
|
*
|
|
* Purges all OpenGL errors. This function is generally useful to
|
|
* clear up the pending errors prior to calling gl_check_error().
|
|
*/
|
|
void
|
|
gl_purge_errors(void)
|
|
{
|
|
while (glGetError() != GL_NO_ERROR)
|
|
; /* nothing */
|
|
}
|
|
|
|
/**
|
|
* gl_check_error:
|
|
*
|
|
* Checks whether there is any OpenGL error pending.
|
|
*
|
|
* Return value: %TRUE if an error was encountered
|
|
*/
|
|
gboolean
|
|
gl_check_error(void)
|
|
{
|
|
GLenum error;
|
|
gboolean has_errors = FALSE;
|
|
|
|
while ((error = glGetError()) != GL_NO_ERROR) {
|
|
GST_DEBUG("glError: %s caught", gl_get_error_string(error));
|
|
has_errors = TRUE;
|
|
}
|
|
return has_errors;
|
|
}
|
|
|
|
/**
|
|
* gl_get_param:
|
|
* @param: the parameter name
|
|
* @pval: return location for the value
|
|
*
|
|
* This function is a wrapper around glGetIntegerv() that does extra
|
|
* error checking.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_get_param(GLenum param, guint *pval)
|
|
{
|
|
GLint val;
|
|
|
|
gl_purge_errors();
|
|
glGetIntegerv(param, &val);
|
|
if (gl_check_error())
|
|
return FALSE;
|
|
|
|
if (pval)
|
|
*pval = val;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_get_texture_param:
|
|
* @target: the target to which the texture is bound
|
|
* @param: the parameter name
|
|
* @pval: return location for the value
|
|
*
|
|
* This function is a wrapper around glGetTexLevelParameteriv() that
|
|
* does extra error checking.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_get_texture_param(GLenum target, GLenum param, guint *pval)
|
|
{
|
|
GLint val;
|
|
|
|
gl_purge_errors();
|
|
glGetTexLevelParameteriv(target, 0, param, &val);
|
|
if (gl_check_error())
|
|
return FALSE;
|
|
|
|
if (pval)
|
|
*pval = val;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_get_texture_binding:
|
|
* @target: a texture target
|
|
*
|
|
* Determines the texture binding type for the specified target.
|
|
*
|
|
* Return value: texture binding type for @target
|
|
*/
|
|
static GLenum
|
|
gl_get_texture_binding(GLenum target)
|
|
{
|
|
GLenum binding;
|
|
|
|
switch (target) {
|
|
case GL_TEXTURE_1D:
|
|
binding = GL_TEXTURE_BINDING_1D;
|
|
break;
|
|
case GL_TEXTURE_2D:
|
|
binding = GL_TEXTURE_BINDING_2D;
|
|
break;
|
|
case GL_TEXTURE_3D:
|
|
binding = GL_TEXTURE_BINDING_3D;
|
|
break;
|
|
case GL_TEXTURE_RECTANGLE_ARB:
|
|
binding = GL_TEXTURE_BINDING_RECTANGLE_ARB;
|
|
break;
|
|
default:
|
|
binding = 0;
|
|
break;
|
|
}
|
|
return binding;
|
|
}
|
|
|
|
/**
|
|
* gl_set_bgcolor:
|
|
* @color: the requested RGB color
|
|
*
|
|
* Sets background color to the RGB @color. This basically is a
|
|
* wrapper around glClearColor().
|
|
*/
|
|
void
|
|
gl_set_bgcolor(guint32 color)
|
|
{
|
|
glClearColor(
|
|
((color >> 16) & 0xff) / 255.0f,
|
|
((color >> 8) & 0xff) / 255.0f,
|
|
( color & 0xff) / 255.0f,
|
|
1.0f
|
|
);
|
|
}
|
|
|
|
/**
|
|
* gl_perspective:
|
|
* @fovy: the field of view angle, in degrees, in the y direction
|
|
* @aspect: the aspect ratio that determines the field of view in the
|
|
* x direction. The aspect ratio is the ratio of x (width) to y
|
|
* (height)
|
|
* @zNear: the distance from the viewer to the near clipping plane
|
|
* (always positive)
|
|
* @zFar: the distance from the viewer to the far clipping plane
|
|
* (always positive)
|
|
*
|
|
* Specified a viewing frustum into the world coordinate system. This
|
|
* basically is the Mesa implementation of gluPerspective().
|
|
*/
|
|
static void
|
|
gl_perspective(GLdouble fovy, GLdouble aspect, GLdouble near_val, GLdouble far_val)
|
|
{
|
|
GLdouble left, right, top, bottom;
|
|
|
|
/* Source (Q 9.085):
|
|
<http://www.opengl.org/resources/faq/technical/transformations.htm> */
|
|
top = tan(fovy * M_PI / 360.0) * near_val;
|
|
bottom = -top;
|
|
left = aspect * bottom;
|
|
right = aspect * top;
|
|
glFrustum(left, right, bottom, top, near_val, far_val);
|
|
}
|
|
|
|
/**
|
|
* gl_resize:
|
|
* @width: the requested width, in pixels
|
|
* @height: the requested height, in pixels
|
|
*
|
|
* Resizes the OpenGL viewport to the specified dimensions, using an
|
|
* orthogonal projection. (0,0) represents the top-left corner of the
|
|
* window.
|
|
*/
|
|
void
|
|
gl_resize(guint width, guint height)
|
|
{
|
|
#define FOVY 60.0f
|
|
#define ASPECT 1.0f
|
|
#define Z_NEAR 0.1f
|
|
#define Z_FAR 100.0f
|
|
#define Z_CAMERA 0.869f
|
|
|
|
glViewport(0, 0, width, height);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
gl_perspective(FOVY, ASPECT, Z_NEAR, Z_FAR);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
glTranslatef(-0.5f, -0.5f, -Z_CAMERA);
|
|
glScalef(1.0f/width, -1.0f/height, 1.0f/width);
|
|
glTranslatef(0.0f, -1.0f*height, 0.0f);
|
|
}
|
|
|
|
/**
|
|
* gl_create_context:
|
|
* @dpy: an X11 #Display
|
|
* @screen: the associated screen of @dpy
|
|
* @parent: the parent #GLContextState, or %NULL if none is to be used
|
|
*
|
|
* Creates a GLX context sharing textures and displays lists with
|
|
* @parent, if not %NULL.
|
|
*
|
|
* Return value: the newly created GLX context
|
|
*/
|
|
GLContextState *
|
|
gl_create_context(Display *dpy, int screen, GLContextState *parent)
|
|
{
|
|
GLContextState *cs;
|
|
GLXFBConfig *fbconfigs = NULL;
|
|
int fbconfig_id, val, n, n_fbconfigs;
|
|
Status status;
|
|
|
|
static GLint fbconfig_attrs[] = {
|
|
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DOUBLEBUFFER, True,
|
|
GLX_RED_SIZE, 8,
|
|
GLX_GREEN_SIZE, 8,
|
|
GLX_BLUE_SIZE, 8,
|
|
None
|
|
};
|
|
|
|
cs = malloc(sizeof(*cs));
|
|
if (!cs)
|
|
goto error;
|
|
|
|
if (parent) {
|
|
cs->display = parent->display;
|
|
cs->window = parent->window;
|
|
screen = DefaultScreen(parent->display);
|
|
}
|
|
else {
|
|
cs->display = dpy;
|
|
cs->window = None;
|
|
}
|
|
cs->visual = NULL;
|
|
cs->context = NULL;
|
|
cs->swapped_buffers = FALSE;
|
|
|
|
if (parent && parent->context) {
|
|
status = glXQueryContext(
|
|
parent->display,
|
|
parent->context,
|
|
GLX_FBCONFIG_ID, &fbconfig_id
|
|
);
|
|
if (status != Success)
|
|
goto error;
|
|
|
|
if (fbconfig_id == GLX_DONT_CARE)
|
|
goto choose_fbconfig;
|
|
|
|
fbconfigs = glXGetFBConfigs(parent->display, screen, &n_fbconfigs);
|
|
if (!fbconfigs)
|
|
goto error;
|
|
|
|
/* Find out a GLXFBConfig compatible with the parent context */
|
|
for (n = 0; n < n_fbconfigs; n++) {
|
|
status = glXGetFBConfigAttrib(
|
|
parent->display,
|
|
fbconfigs[n],
|
|
GLX_FBCONFIG_ID, &val
|
|
);
|
|
if (status == Success && val == fbconfig_id)
|
|
break;
|
|
}
|
|
if (n == n_fbconfigs)
|
|
goto error;
|
|
}
|
|
else {
|
|
choose_fbconfig:
|
|
fbconfigs = glXChooseFBConfig(
|
|
cs->display,
|
|
screen,
|
|
fbconfig_attrs, &n_fbconfigs
|
|
);
|
|
if (!fbconfigs)
|
|
goto error;
|
|
|
|
/* Select the first one */
|
|
n = 0;
|
|
}
|
|
|
|
cs->visual = glXGetVisualFromFBConfig(cs->display, fbconfigs[n]);
|
|
cs->context = glXCreateNewContext(
|
|
cs->display,
|
|
fbconfigs[n],
|
|
GLX_RGBA_TYPE,
|
|
parent ? parent->context : NULL,
|
|
True
|
|
);
|
|
if (cs->context)
|
|
goto end;
|
|
|
|
error:
|
|
gl_destroy_context(cs);
|
|
cs = NULL;
|
|
end:
|
|
if (fbconfigs)
|
|
XFree(fbconfigs);
|
|
return cs;
|
|
}
|
|
|
|
/**
|
|
* gl_destroy_context:
|
|
* @cs: a #GLContextState
|
|
*
|
|
* Destroys the GLX context @cs
|
|
*/
|
|
void
|
|
gl_destroy_context(GLContextState *cs)
|
|
{
|
|
if (!cs)
|
|
return;
|
|
|
|
if (cs->visual) {
|
|
XFree(cs->visual);
|
|
cs->visual = NULL;
|
|
}
|
|
|
|
if (cs->display && cs->context) {
|
|
if (glXGetCurrentContext() == cs->context) {
|
|
/* XXX: if buffers were never swapped, the application
|
|
will crash later with the NVIDIA driver */
|
|
if (!cs->swapped_buffers)
|
|
gl_swap_buffers(cs);
|
|
glXMakeCurrent(cs->display, None, NULL);
|
|
}
|
|
glXDestroyContext(cs->display, cs->context);
|
|
cs->display = NULL;
|
|
cs->context = NULL;
|
|
}
|
|
free(cs);
|
|
}
|
|
|
|
/**
|
|
* gl_get_current_context:
|
|
* @cs: return location to the current #GLContextState
|
|
*
|
|
* Retrieves the current GLX context, display and drawable packed into
|
|
* the #GLContextState struct.
|
|
*/
|
|
void
|
|
gl_get_current_context(GLContextState *cs)
|
|
{
|
|
cs->display = glXGetCurrentDisplay();
|
|
cs->window = glXGetCurrentDrawable();
|
|
cs->context = glXGetCurrentContext();
|
|
}
|
|
|
|
/**
|
|
* gl_set_current_context:
|
|
* @new_cs: the requested new #GLContextState
|
|
* @old_cs: return location to the context that was previously current
|
|
*
|
|
* Makes the @new_cs GLX context the current GLX rendering context of
|
|
* the calling thread, replacing the previously current context if
|
|
* there was one.
|
|
*
|
|
* If @old_cs is non %NULL, the previously current GLX context and
|
|
* window are recorded.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_set_current_context(GLContextState *new_cs, GLContextState *old_cs)
|
|
{
|
|
/* If display is NULL, this could be that new_cs was retrieved from
|
|
gl_get_current_context() with none set previously. If that case,
|
|
the other fields are also NULL and we don't return an error */
|
|
if (!new_cs->display)
|
|
return !new_cs->window && !new_cs->context;
|
|
|
|
if (old_cs) {
|
|
if (old_cs == new_cs)
|
|
return TRUE;
|
|
gl_get_current_context(old_cs);
|
|
if (old_cs->display == new_cs->display &&
|
|
old_cs->window == new_cs->window &&
|
|
old_cs->context == new_cs->context)
|
|
return TRUE;
|
|
}
|
|
return glXMakeCurrent(new_cs->display, new_cs->window, new_cs->context);
|
|
}
|
|
|
|
/**
|
|
* gl_swap_buffers:
|
|
* @cs: a #GLContextState
|
|
*
|
|
* Promotes the contents of the back buffer of the @win window to
|
|
* become the contents of the front buffer. This simply is wrapper
|
|
* around glXSwapBuffers().
|
|
*/
|
|
void
|
|
gl_swap_buffers(GLContextState *cs)
|
|
{
|
|
glXSwapBuffers(cs->display, cs->window);
|
|
cs->swapped_buffers = TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_bind_texture:
|
|
* @ts: a #GLTextureState
|
|
* @target: the target to which the texture is bound
|
|
* @texture: the name of a texture
|
|
*
|
|
* Binds @texture to the specified @target, while recording the
|
|
* previous state in @ts.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_bind_texture(GLTextureState *ts, GLenum target, GLuint texture)
|
|
{
|
|
GLenum binding;
|
|
|
|
ts->target = target;
|
|
|
|
if (glIsEnabled(target)) {
|
|
binding = gl_get_texture_binding(target);
|
|
if (!binding)
|
|
return FALSE;
|
|
if (!gl_get_param(binding, &ts->old_texture))
|
|
return FALSE;
|
|
ts->was_enabled = TRUE;
|
|
ts->was_bound = texture == ts->old_texture;
|
|
if (ts->was_bound)
|
|
return TRUE;
|
|
}
|
|
else {
|
|
glEnable(target);
|
|
ts->old_texture = 0;
|
|
ts->was_enabled = FALSE;
|
|
ts->was_bound = FALSE;
|
|
}
|
|
|
|
gl_purge_errors();
|
|
glBindTexture(target, texture);
|
|
if (gl_check_error())
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_unbind_texture:
|
|
* @ts: a #GLTextureState
|
|
*
|
|
* Rebinds the texture that was previously bound and recorded in @ts.
|
|
*/
|
|
void
|
|
gl_unbind_texture(GLTextureState *ts)
|
|
{
|
|
if (!ts->was_bound && ts->old_texture)
|
|
glBindTexture(ts->target, ts->old_texture);
|
|
if (!ts->was_enabled)
|
|
glDisable(ts->target);
|
|
}
|
|
|
|
/**
|
|
* gl_create_texture:
|
|
* @target: the target to which the texture is bound
|
|
* @format: the format of the pixel data
|
|
* @width: the requested width, in pixels
|
|
* @height: the requested height, in pixels
|
|
*
|
|
* Creates a texture with the specified dimensions and @format. The
|
|
* internal format will be automatically derived from @format.
|
|
*
|
|
* Return value: the newly created texture name
|
|
*/
|
|
GLuint
|
|
gl_create_texture(GLenum target, GLenum format, guint width, guint height)
|
|
{
|
|
GLenum internal_format;
|
|
GLuint texture;
|
|
GLTextureState ts;
|
|
guint bytes_per_component;
|
|
|
|
internal_format = format;
|
|
switch (format) {
|
|
case GL_LUMINANCE:
|
|
bytes_per_component = 1;
|
|
break;
|
|
case GL_LUMINANCE_ALPHA:
|
|
bytes_per_component = 2;
|
|
break;
|
|
case GL_RGBA:
|
|
case GL_BGRA:
|
|
internal_format = GL_RGBA;
|
|
bytes_per_component = 4;
|
|
break;
|
|
default:
|
|
bytes_per_component = 0;
|
|
break;
|
|
}
|
|
g_assert(bytes_per_component > 0);
|
|
|
|
glGenTextures(1, &texture);
|
|
if (!gl_bind_texture(&ts, target, texture))
|
|
return 0;
|
|
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, bytes_per_component);
|
|
glTexImage2D(
|
|
target,
|
|
0,
|
|
internal_format,
|
|
width, height,
|
|
0,
|
|
format,
|
|
GL_UNSIGNED_BYTE,
|
|
NULL
|
|
);
|
|
gl_unbind_texture(&ts);
|
|
return texture;
|
|
}
|
|
|
|
/**
|
|
* get_proc_address:
|
|
* @name: the name of the OpenGL extension function to lookup
|
|
*
|
|
* Returns the specified OpenGL extension function
|
|
*
|
|
* Return value: the OpenGL extension matching @name, or %NULL if none
|
|
* was found
|
|
*/
|
|
typedef void (*GLFuncPtr)(void);
|
|
typedef GLFuncPtr (*GLXGetProcAddressProc)(const char *);
|
|
|
|
static GLFuncPtr
|
|
get_proc_address_default(const char *name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static GLXGetProcAddressProc
|
|
get_proc_address_func(void)
|
|
{
|
|
GLXGetProcAddressProc get_proc_func;
|
|
|
|
dlerror();
|
|
*(void **)(&get_proc_func) = dlsym(RTLD_DEFAULT, "glXGetProcAddress");
|
|
if (!dlerror())
|
|
return get_proc_func;
|
|
|
|
*(void **)(&get_proc_func) = dlsym(RTLD_DEFAULT, "glXGetProcAddressARB");
|
|
if (!dlerror())
|
|
return get_proc_func;
|
|
|
|
return get_proc_address_default;
|
|
}
|
|
|
|
static inline GLFuncPtr
|
|
get_proc_address(const char *name)
|
|
{
|
|
static GLXGetProcAddressProc get_proc_func = NULL;
|
|
if (!get_proc_func)
|
|
get_proc_func = get_proc_address_func();
|
|
return get_proc_func(name);
|
|
}
|
|
|
|
/**
|
|
* gl_init_vtable:
|
|
*
|
|
* Initializes the global #GLVTable.
|
|
*
|
|
* Return value: the #GLVTable filled in with OpenGL extensions, or
|
|
* %NULL on error.
|
|
*/
|
|
static GLVTable gl_vtable_static;
|
|
|
|
static GLVTable *
|
|
gl_init_vtable(void)
|
|
{
|
|
GLVTable * const gl_vtable = &gl_vtable_static;
|
|
const gchar *gl_extensions = (const gchar *)glGetString(GL_EXTENSIONS);
|
|
gboolean has_extension;
|
|
|
|
/* GLX_EXT_texture_from_pixmap */
|
|
gl_vtable->glx_create_pixmap = (PFNGLXCREATEPIXMAPPROC)
|
|
get_proc_address("glXCreatePixmap");
|
|
if (!gl_vtable->glx_create_pixmap)
|
|
return NULL;
|
|
gl_vtable->glx_destroy_pixmap = (PFNGLXDESTROYPIXMAPPROC)
|
|
get_proc_address("glXDestroyPixmap");
|
|
if (!gl_vtable->glx_destroy_pixmap)
|
|
return NULL;
|
|
gl_vtable->glx_bind_tex_image = (PFNGLXBINDTEXIMAGEEXTPROC)
|
|
get_proc_address("glXBindTexImageEXT");
|
|
if (!gl_vtable->glx_bind_tex_image)
|
|
return NULL;
|
|
gl_vtable->glx_release_tex_image = (PFNGLXRELEASETEXIMAGEEXTPROC)
|
|
get_proc_address("glXReleaseTexImageEXT");
|
|
if (!gl_vtable->glx_release_tex_image)
|
|
return NULL;
|
|
|
|
/* GL_ARB_framebuffer_object */
|
|
has_extension = (
|
|
find_string("GL_ARB_framebuffer_object", gl_extensions, " ") ||
|
|
find_string("GL_EXT_framebuffer_object", gl_extensions, " ")
|
|
);
|
|
if (has_extension) {
|
|
gl_vtable->gl_gen_framebuffers = (PFNGLGENFRAMEBUFFERSEXTPROC)
|
|
get_proc_address("glGenFramebuffersEXT");
|
|
if (!gl_vtable->gl_gen_framebuffers)
|
|
return NULL;
|
|
gl_vtable->gl_delete_framebuffers = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
|
|
get_proc_address("glDeleteFramebuffersEXT");
|
|
if (!gl_vtable->gl_delete_framebuffers)
|
|
return NULL;
|
|
gl_vtable->gl_bind_framebuffer = (PFNGLBINDFRAMEBUFFEREXTPROC)
|
|
get_proc_address("glBindFramebufferEXT");
|
|
if (!gl_vtable->gl_bind_framebuffer)
|
|
return NULL;
|
|
gl_vtable->gl_gen_renderbuffers = (PFNGLGENRENDERBUFFERSEXTPROC)
|
|
get_proc_address("glGenRenderbuffersEXT");
|
|
if (!gl_vtable->gl_gen_renderbuffers)
|
|
return NULL;
|
|
gl_vtable->gl_delete_renderbuffers = (PFNGLDELETERENDERBUFFERSEXTPROC)
|
|
get_proc_address("glDeleteRenderbuffersEXT");
|
|
if (!gl_vtable->gl_delete_renderbuffers)
|
|
return NULL;
|
|
gl_vtable->gl_bind_renderbuffer = (PFNGLBINDRENDERBUFFEREXTPROC)
|
|
get_proc_address("glBindRenderbufferEXT");
|
|
if (!gl_vtable->gl_bind_renderbuffer)
|
|
return NULL;
|
|
gl_vtable->gl_renderbuffer_storage = (PFNGLRENDERBUFFERSTORAGEEXTPROC)
|
|
get_proc_address("glRenderbufferStorageEXT");
|
|
if (!gl_vtable->gl_renderbuffer_storage)
|
|
return NULL;
|
|
gl_vtable->gl_framebuffer_renderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)
|
|
get_proc_address("glFramebufferRenderbufferEXT");
|
|
if (!gl_vtable->gl_framebuffer_renderbuffer)
|
|
return NULL;
|
|
gl_vtable->gl_framebuffer_texture_2d = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
|
|
get_proc_address("glFramebufferTexture2DEXT");
|
|
if (!gl_vtable->gl_framebuffer_texture_2d)
|
|
return NULL;
|
|
gl_vtable->gl_check_framebuffer_status = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
|
|
get_proc_address("glCheckFramebufferStatusEXT");
|
|
if (!gl_vtable->gl_check_framebuffer_status)
|
|
return NULL;
|
|
gl_vtable->has_framebuffer_object = TRUE;
|
|
}
|
|
return gl_vtable;
|
|
}
|
|
|
|
/**
|
|
* gl_get_vtable:
|
|
*
|
|
* Retrieves a VTable for OpenGL extensions.
|
|
*
|
|
* Return value: VTable for OpenGL extensions
|
|
*/
|
|
GLVTable *
|
|
gl_get_vtable(void)
|
|
{
|
|
static gsize gl_vtable_init = FALSE;
|
|
static GLVTable *gl_vtable = NULL;
|
|
|
|
if (g_once_init_enter(&gl_vtable_init)) {
|
|
gl_vtable = gl_init_vtable();
|
|
g_once_init_leave(&gl_vtable_init, TRUE);
|
|
}
|
|
return gl_vtable;
|
|
}
|
|
|
|
/**
|
|
* gl_create_pixmap_object:
|
|
* @dpy: an X11 #Display
|
|
* @width: the request width, in pixels
|
|
* @height: the request height, in pixels
|
|
*
|
|
* Creates a #GLPixmapObject of the specified dimensions. This
|
|
* requires the GLX_EXT_texture_from_pixmap extension.
|
|
*
|
|
* Return value: the newly created #GLPixmapObject object
|
|
*/
|
|
GLPixmapObject *
|
|
gl_create_pixmap_object(Display *dpy, guint width, guint height)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
GLPixmapObject *pixo;
|
|
GLXFBConfig *fbconfig;
|
|
int screen;
|
|
Window rootwin;
|
|
XWindowAttributes wattr;
|
|
int *attr;
|
|
int n_fbconfig_attrs;
|
|
|
|
int fbconfig_attrs[32] = {
|
|
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
|
GLX_DOUBLEBUFFER, GL_FALSE,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_X_RENDERABLE, GL_TRUE,
|
|
GLX_Y_INVERTED_EXT, GL_TRUE,
|
|
GLX_RED_SIZE, 8,
|
|
GLX_GREEN_SIZE, 8,
|
|
GLX_BLUE_SIZE, 8,
|
|
GL_NONE,
|
|
};
|
|
|
|
int pixmap_attrs[10] = {
|
|
GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
|
|
GLX_MIPMAP_TEXTURE_EXT, GL_FALSE,
|
|
GL_NONE,
|
|
};
|
|
|
|
if (!gl_vtable)
|
|
return NULL;
|
|
|
|
screen = DefaultScreen(dpy);
|
|
rootwin = RootWindow(dpy, screen);
|
|
|
|
/* XXX: this won't work for different displays */
|
|
if (!gl_vtable->has_texture_from_pixmap) {
|
|
const char *glx_extensions = glXQueryExtensionsString(dpy, screen);
|
|
if (!glx_extensions)
|
|
return NULL;
|
|
if (!find_string("GLX_EXT_texture_from_pixmap", glx_extensions, " "))
|
|
return NULL;
|
|
gl_vtable->has_texture_from_pixmap = TRUE;
|
|
}
|
|
|
|
pixo = calloc(1, sizeof(*pixo));
|
|
if (!pixo)
|
|
return NULL;
|
|
|
|
pixo->dpy = dpy;
|
|
pixo->width = width;
|
|
pixo->height = height;
|
|
pixo->pixmap = None;
|
|
pixo->glx_pixmap = None;
|
|
pixo->is_bound = FALSE;
|
|
|
|
XGetWindowAttributes(dpy, rootwin, &wattr);
|
|
pixo->pixmap = XCreatePixmap(dpy, rootwin, width, height, wattr.depth);
|
|
if (!pixo->pixmap)
|
|
goto error;
|
|
|
|
/* Initialize FBConfig attributes */
|
|
for (attr = fbconfig_attrs; *attr != GL_NONE; attr += 2)
|
|
;
|
|
*attr++ = GLX_DEPTH_SIZE; *attr++ = wattr.depth;
|
|
if (wattr.depth == 32) {
|
|
*attr++ = GLX_ALPHA_SIZE; *attr++ = 8;
|
|
*attr++ = GLX_BIND_TO_TEXTURE_RGBA_EXT; *attr++ = GL_TRUE;
|
|
}
|
|
else {
|
|
*attr++ = GLX_BIND_TO_TEXTURE_RGB_EXT; *attr++ = GL_TRUE;
|
|
}
|
|
*attr++ = GL_NONE;
|
|
|
|
fbconfig = glXChooseFBConfig(
|
|
dpy,
|
|
screen,
|
|
fbconfig_attrs, &n_fbconfig_attrs
|
|
);
|
|
if (!fbconfig)
|
|
goto error;
|
|
|
|
/* Initialize GLX Pixmap attributes */
|
|
for (attr = pixmap_attrs; *attr != GL_NONE; attr += 2)
|
|
;
|
|
*attr++ = GLX_TEXTURE_FORMAT_EXT;
|
|
if (wattr.depth == 32)
|
|
*attr++ = GLX_TEXTURE_FORMAT_RGBA_EXT;
|
|
else
|
|
*attr++ = GLX_TEXTURE_FORMAT_RGB_EXT;
|
|
*attr++ = GL_NONE;
|
|
|
|
x11_trap_errors();
|
|
pixo->glx_pixmap = gl_vtable->glx_create_pixmap(
|
|
dpy,
|
|
fbconfig[0],
|
|
pixo->pixmap,
|
|
pixmap_attrs
|
|
);
|
|
free(fbconfig);
|
|
if (x11_untrap_errors() != 0)
|
|
goto error;
|
|
|
|
pixo->target = GL_TEXTURE_2D;
|
|
glGenTextures(1, &pixo->texture);
|
|
if (!gl_bind_texture(&pixo->old_texture, pixo->target, pixo->texture))
|
|
goto error;
|
|
glTexParameteri(pixo->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(pixo->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
gl_unbind_texture(&pixo->old_texture);
|
|
return pixo;
|
|
|
|
error:
|
|
gl_destroy_pixmap_object(pixo);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gl_destroy_pixmap_object:
|
|
* @pixo: a #GLPixmapObject
|
|
*
|
|
* Destroys the #GLPixmapObject object.
|
|
*/
|
|
void
|
|
gl_destroy_pixmap_object(GLPixmapObject *pixo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
|
|
if (!pixo)
|
|
return;
|
|
|
|
gl_unbind_pixmap_object(pixo);
|
|
|
|
if (pixo->texture) {
|
|
glDeleteTextures(1, &pixo->texture);
|
|
pixo->texture = 0;
|
|
}
|
|
|
|
if (pixo->glx_pixmap) {
|
|
gl_vtable->glx_destroy_pixmap(pixo->dpy, pixo->glx_pixmap);
|
|
pixo->glx_pixmap = None;
|
|
}
|
|
|
|
if (pixo->pixmap) {
|
|
XFreePixmap(pixo->dpy, pixo->pixmap);
|
|
pixo->pixmap = None;
|
|
}
|
|
free(pixo);
|
|
}
|
|
|
|
/**
|
|
* gl_bind_pixmap_object:
|
|
* @pixo: a #GLPixmapObject
|
|
*
|
|
* Defines a two-dimensional texture image. The texture image is taken
|
|
* from the @pixo pixmap and need not be copied. The texture target,
|
|
* format and size are derived from attributes of the @pixo pixmap.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_bind_pixmap_object(GLPixmapObject *pixo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
|
|
if (pixo->is_bound)
|
|
return TRUE;
|
|
|
|
if (!gl_bind_texture(&pixo->old_texture, pixo->target, pixo->texture))
|
|
return FALSE;
|
|
|
|
x11_trap_errors();
|
|
gl_vtable->glx_bind_tex_image(
|
|
pixo->dpy,
|
|
pixo->glx_pixmap,
|
|
GLX_FRONT_LEFT_EXT,
|
|
NULL
|
|
);
|
|
XSync(pixo->dpy, False);
|
|
if (x11_untrap_errors() != 0) {
|
|
GST_DEBUG("failed to bind pixmap");
|
|
return FALSE;
|
|
}
|
|
|
|
pixo->is_bound = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_unbind_pixmap_object:
|
|
* @pixo: a #GLPixmapObject
|
|
*
|
|
* Releases a color buffers that is being used as a texture.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_unbind_pixmap_object(GLPixmapObject *pixo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
|
|
if (!pixo->is_bound)
|
|
return TRUE;
|
|
|
|
x11_trap_errors();
|
|
gl_vtable->glx_release_tex_image(
|
|
pixo->dpy,
|
|
pixo->glx_pixmap,
|
|
GLX_FRONT_LEFT_EXT
|
|
);
|
|
XSync(pixo->dpy, False);
|
|
if (x11_untrap_errors() != 0) {
|
|
GST_DEBUG("failed to release pixmap");
|
|
return FALSE;
|
|
}
|
|
|
|
gl_unbind_texture(&pixo->old_texture);
|
|
|
|
pixo->is_bound = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_create_framebuffer_object:
|
|
* @target: the target to which the texture is bound
|
|
* @texture: the GL texture to hold the framebuffer
|
|
* @width: the requested width, in pixels
|
|
* @height: the requested height, in pixels
|
|
*
|
|
* Creates an FBO with the specified texture and size.
|
|
*
|
|
* Return value: the newly created #GLFramebufferObject, or %NULL if
|
|
* an error occurred
|
|
*/
|
|
GLFramebufferObject *
|
|
gl_create_framebuffer_object(
|
|
GLenum target,
|
|
GLuint texture,
|
|
guint width,
|
|
guint height
|
|
)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
GLFramebufferObject *fbo;
|
|
GLenum status;
|
|
|
|
if (!gl_vtable || !gl_vtable->has_framebuffer_object)
|
|
return NULL;
|
|
|
|
/* XXX: we only support GL_TEXTURE_2D at this time */
|
|
if (target != GL_TEXTURE_2D)
|
|
return NULL;
|
|
|
|
fbo = calloc(1, sizeof(*fbo));
|
|
if (!fbo)
|
|
return NULL;
|
|
|
|
fbo->width = width;
|
|
fbo->height = height;
|
|
fbo->fbo = 0;
|
|
fbo->old_fbo = 0;
|
|
fbo->is_bound = FALSE;
|
|
|
|
gl_get_param(GL_FRAMEBUFFER_BINDING, &fbo->old_fbo);
|
|
gl_vtable->gl_gen_framebuffers(1, &fbo->fbo);
|
|
gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->fbo);
|
|
gl_vtable->gl_framebuffer_texture_2d(
|
|
GL_FRAMEBUFFER_EXT,
|
|
GL_COLOR_ATTACHMENT0_EXT,
|
|
target, texture,
|
|
0
|
|
);
|
|
|
|
status = gl_vtable->gl_check_framebuffer_status(GL_DRAW_FRAMEBUFFER_EXT);
|
|
gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->old_fbo);
|
|
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
|
|
goto error;
|
|
return fbo;
|
|
|
|
error:
|
|
gl_destroy_framebuffer_object(fbo);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gl_destroy_framebuffer_object:
|
|
* @fbo: a #GLFramebufferObject
|
|
*
|
|
* Destroys the @fbo object.
|
|
*/
|
|
void
|
|
gl_destroy_framebuffer_object(GLFramebufferObject *fbo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
|
|
if (!fbo)
|
|
return;
|
|
|
|
gl_unbind_framebuffer_object(fbo);
|
|
|
|
if (fbo->fbo) {
|
|
gl_vtable->gl_delete_framebuffers(1, &fbo->fbo);
|
|
fbo->fbo = 0;
|
|
}
|
|
free(fbo);
|
|
}
|
|
|
|
/**
|
|
* gl_bind_framebuffer_object:
|
|
* @fbo: a #GLFramebufferObject
|
|
*
|
|
* Binds @fbo object.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_bind_framebuffer_object(GLFramebufferObject *fbo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
const guint width = fbo->width;
|
|
const guint height = fbo->height;
|
|
|
|
const guint attribs = (GL_VIEWPORT_BIT|
|
|
GL_CURRENT_BIT|
|
|
GL_ENABLE_BIT|
|
|
GL_TEXTURE_BIT|
|
|
GL_COLOR_BUFFER_BIT);
|
|
|
|
if (fbo->is_bound)
|
|
return TRUE;
|
|
|
|
gl_get_param(GL_FRAMEBUFFER_BINDING, &fbo->old_fbo);
|
|
gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->fbo);
|
|
glPushAttrib(attribs);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glViewport(0, 0, width, height);
|
|
glTranslatef(-1.0f, -1.0f, 0.0f);
|
|
glScalef(2.0f / width, 2.0f / height, 1.0f);
|
|
|
|
fbo->is_bound = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gl_unbind_framebuffer_object:
|
|
* @fbo: a #GLFramebufferObject
|
|
*
|
|
* Releases @fbo object.
|
|
*
|
|
* Return value: %TRUE on success
|
|
*/
|
|
gboolean
|
|
gl_unbind_framebuffer_object(GLFramebufferObject *fbo)
|
|
{
|
|
GLVTable * const gl_vtable = gl_get_vtable();
|
|
|
|
if (!fbo->is_bound)
|
|
return TRUE;
|
|
|
|
glPopAttrib();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
gl_vtable->gl_bind_framebuffer(GL_FRAMEBUFFER_EXT, fbo->old_fbo);
|
|
|
|
fbo->is_bound = FALSE;
|
|
return TRUE;
|
|
}
|