gstreamer/gst-libs/gst/vaapi/gstvaapiutils_egl.c
Gwenole Beauchesne 1e7c4db5a7 Add initial support for EGL.
Add initial support for EGL to libgstvaapi core library. The target
display server and the desired OpenGL API can be programmatically
selected at run-time.

A comprehensive set of EGL utilities are provided to support those
dynamic selection needs, but also most importantly to ensure that
the GL command stream is executed from within a single thread.

https://bugzilla.gnome.org/show_bug.cgi?id=743846
2015-02-24 15:20:03 +01:00

1342 lines
36 KiB
C

/*
* gstvaapiutils_egl.c - EGL utilities
*
* Copyright (C) 2014 Intel Corporation
* Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
*
* 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
*/
#include "sysdeps.h"
#include "gstvaapiutils_egl.h"
typedef struct egl_message_s EglMessage;
struct egl_message_s
{
EglObject base;
EglContextRunFunc func;
gpointer args;
};
static void
egl_message_finalize (EglMessage * msg)
{
}
/* ------------------------------------------------------------------------- */
// Utility functions
typedef struct gl_version_info_s GlVersionInfo;
struct gl_version_info_s
{
guint gles_version;
guint gl_api_bit;
guint gl_api;
const gchar *gl_api_name;
};
static const GlVersionInfo gl_version_info[] = {
{0, EGL_OPENGL_BIT, EGL_OPENGL_API, "OpenGL"},
{1, EGL_OPENGL_ES_BIT, EGL_OPENGL_ES_API, "OpenGL_ES"},
{2, EGL_OPENGL_ES2_BIT, EGL_OPENGL_ES_API, "OpenGL_ES2"},
{3, EGL_OPENGL_ES3_BIT_KHR, EGL_OPENGL_ES_API, "OpenGL_ES3"},
{0,}
};
static const GlVersionInfo *
gl_version_info_lookup (guint gles_version)
{
const GlVersionInfo *vinfo;
for (vinfo = gl_version_info; vinfo->gl_api_bit != 0; vinfo++) {
if (vinfo->gles_version == gles_version)
return vinfo;
}
return NULL;
}
static const GlVersionInfo *
gl_version_info_lookup_by_api (guint api)
{
const GlVersionInfo *vinfo;
for (vinfo = gl_version_info; vinfo->gl_api_bit != 0; vinfo++) {
if (api & vinfo->gl_api_bit)
return vinfo;
}
return NULL;
}
static const GlVersionInfo *
gl_version_info_lookup_by_api_name (const gchar * name)
{
const GlVersionInfo *vinfo;
for (vinfo = gl_version_info; vinfo->gl_api_bit != 0; vinfo++) {
if (g_strcmp0 (vinfo->gl_api_name, name) == 0)
return vinfo;
}
return NULL;
}
static gboolean
g_strv_match_string (gchar ** extensions_list, const gchar * name)
{
if (extensions_list) {
for (; *extensions_list != NULL; extensions_list++) {
if (g_strcmp0 (*extensions_list, name) == 0)
return TRUE;
}
}
return FALSE;
}
static gboolean
egl_find_attrib_value (const EGLint * attribs, EGLint type, EGLint * value_ptr)
{
while (attribs[0] != EGL_NONE) {
if (attribs[0] == type) {
if (value_ptr)
*value_ptr = attribs[1];
return TRUE;
}
attribs += 2;
}
return FALSE;
}
/* ------------------------------------------------------------------------- */
// Basic objects
#define EGL_OBJECT(object) ((EglObject *)(object))
#define EGL_OBJECT_CLASS(klass) ((EglObjectClass *)(klass))
#define EGL_OBJECT_DEFINE_CLASS_WITH_CODE(TN, t_n, code) \
static void \
G_PASTE(t_n,_finalize) (TN * object); \
\
static inline const EglObjectClass * \
G_PASTE(t_n,_class) (void) \
{ \
static G_PASTE(TN,Class) g_class; \
static gsize g_class_init = FALSE; \
\
if (g_once_init_enter (&g_class_init)) { \
GstVaapiMiniObjectClass *const object_class = \
GST_VAAPI_MINI_OBJECT_CLASS (&g_class); \
code; \
object_class->size = sizeof (TN); \
object_class->finalize = (GDestroyNotify) \
G_PASTE(t_n,_finalize); \
g_once_init_leave (&g_class_init, TRUE); \
} \
return EGL_OBJECT_CLASS (&g_class); \
}
#define EGL_OBJECT_DEFINE_CLASS(TN, t_n) \
EGL_OBJECT_DEFINE_CLASS_WITH_CODE (TN, t_n, /**/)
static inline gpointer
egl_object_new (const EglObjectClass * klass)
{
return gst_vaapi_mini_object_new (GST_VAAPI_MINI_OBJECT_CLASS (klass));
}
static inline gpointer
egl_object_new0 (const EglObjectClass * klass)
{
return gst_vaapi_mini_object_new0 (GST_VAAPI_MINI_OBJECT_CLASS (klass));
}
typedef struct egl_object_class_s EglMessageClass;
typedef struct egl_object_class_s EglVTableClass;
typedef struct egl_object_class_s EglDisplayClass;
typedef struct egl_object_class_s EglConfigClass;
typedef struct egl_object_class_s EglContextClass;
typedef struct egl_object_class_s EglSurfaceClass;
typedef struct egl_object_class_s EglProgramClass;
EGL_OBJECT_DEFINE_CLASS (EglMessage, egl_message);
EGL_OBJECT_DEFINE_CLASS (EglVTable, egl_vtable);
EGL_OBJECT_DEFINE_CLASS (EglDisplay, egl_display);
EGL_OBJECT_DEFINE_CLASS (EglConfig, egl_config);
EGL_OBJECT_DEFINE_CLASS (EglContext, egl_context);
EGL_OBJECT_DEFINE_CLASS (EglSurface, egl_surface);
EGL_OBJECT_DEFINE_CLASS (EglProgram, egl_program);
/* ------------------------------------------------------------------------- */
// Desktop OpenGL and OpenGL|ES dispatcher (vtable)
static GMutex gl_vtables_lock;
static EglVTable *gl_vtables[4];
#if (USE_GLES_VERSION_MASK & (1U << 0))
static const gchar *gl_library_names[] = {
"libGL.la",
"libGL.so.1",
NULL
};
#endif
#if (USE_GLES_VERSION_MASK & (1U << 1))
static const gchar *gles1_library_names[] = {
"libGLESv1_CM.la",
"libGLESv1_CM.so.1",
NULL
};
#endif
#if (USE_GLES_VERSION_MASK & (1U << 2))
static const gchar *gles2_library_names[] = {
"libGLESv2.la",
"libGLESv2.so.2",
NULL
};
#endif
static const gchar **gl_library_names_group[] = {
#if (USE_GLES_VERSION_MASK & (1U << 0))
gl_library_names,
#endif
NULL
};
static const gchar **gles1_library_names_group[] = {
#if (USE_GLES_VERSION_MASK & (1U << 1))
gles1_library_names,
#endif
NULL
};
static const gchar **gles2_library_names_group[] = {
#if (USE_GLES_VERSION_MASK & (1U << 2))
gles2_library_names,
#endif
NULL
};
static const gchar **gles3_library_names_group[] = {
#if (USE_GLES_VERSION_MASK & (1U << 3))
gles2_library_names,
#endif
NULL
};
static const gchar ***
egl_vtable_get_library_names_group (guint gles_version)
{
const gchar ***library_names_group;
switch (gles_version) {
case 0:
library_names_group = gl_library_names_group;
break;
case 1:
library_names_group = gles1_library_names_group;
break;
case 2:
library_names_group = gles2_library_names_group;
break;
case 3:
library_names_group = gles3_library_names_group;
break;
default:
library_names_group = NULL;
break;
}
return library_names_group;
}
static gboolean
egl_vtable_check_extension (EglVTable * vtable, EGLDisplay display,
gboolean is_egl, const gchar * group_name, guint * group_ptr)
{
gchar ***extensions_list;
const gchar *extensions;
g_return_val_if_fail (group_name != NULL, FALSE);
g_return_val_if_fail (group_ptr != NULL, FALSE);
if (*group_ptr > 0)
return TRUE;
GST_DEBUG ("check for %s extension %s", is_egl ? "EGL" : "GL", group_name);
if (is_egl) {
if (!vtable->egl_extensions) {
extensions = eglQueryString (display, EGL_EXTENSIONS);
if (!extensions)
return FALSE;
GST_DEBUG ("EGL extensions: %s", extensions);
vtable->egl_extensions = g_strsplit (extensions, " ", 0);
}
extensions_list = &vtable->egl_extensions;
} else {
if (!vtable->gl_extensions) {
extensions = (const gchar *) vtable->glGetString (GL_EXTENSIONS);
if (!extensions)
return FALSE;
GST_DEBUG ("GL extensions: %s", extensions);
vtable->gl_extensions = g_strsplit (extensions, " ", 0);
}
extensions_list = &vtable->gl_extensions;
}
if (!g_strv_match_string (*extensions_list, group_name))
return FALSE;
GST_LOG (" found %s extension %s", is_egl ? "EGL" : "GL", group_name);
(*group_ptr)++;
return TRUE;
}
static gboolean
egl_vtable_load_symbol (EglVTable * vtable, EGLDisplay display, gboolean is_egl,
const gchar * symbol_name, gpointer * symbol_ptr,
const gchar * group_name, guint * group_ptr)
{
void (*symbol) (void);
if (group_ptr && !*group_ptr) {
if (!egl_vtable_check_extension (vtable, display, is_egl, group_name,
group_ptr))
return FALSE;
}
if (is_egl) {
symbol = eglGetProcAddress (symbol_name);
} else {
if (!g_module_symbol (vtable->base.handle.p, symbol_name,
(gpointer *) & symbol))
return FALSE;
}
if (!symbol)
return FALSE;
GST_LOG (" found symbol %s", symbol_name);
if (symbol_ptr)
*symbol_ptr = symbol;
if (group_ptr)
(*group_ptr)++;
return TRUE;
}
static gboolean
egl_vtable_load_egl_symbols (EglVTable * vtable, EGLDisplay display)
{
guint n = 0;
#define EGL_DEFINE_EXTENSION(NAME) do { \
egl_vtable_check_extension (vtable, display, TRUE, \
"EGL_" G_STRINGIFY (NAME), \
&vtable->GL_PROTO_GEN_CONCAT(has_EGL_,NAME)); \
} while (0);
#define EGL_PROTO_BEGIN(NAME, TYPE, EXTENSION) do { \
n += egl_vtable_load_symbol (vtable, display, TRUE, \
G_STRINGIFY(GL_PROTO_GEN_CONCAT(egl,NAME)), \
(gpointer *) &vtable->GL_PROTO_GEN_CONCAT(egl,NAME), \
"EGL_" G_STRINGIFY(EXTENSION), \
&vtable->GL_PROTO_GEN_CONCAT(has_EGL_,EXTENSION)); \
} while (0);
#include "egl_vtable.h"
vtable->num_egl_symbols = n;
return TRUE;
}
static gboolean
egl_vtable_load_gl_symbols (EglVTable * vtable, EGLDisplay display)
{
guint n = 0;
vtable->has_GL_CORE_1_0 = 1;
vtable->has_GL_CORE_1_1 = 1;
vtable->has_GL_CORE_1_3 = 1;
vtable->has_GL_CORE_2_0 = 1;
#define GL_DEFINE_EXTENSION(NAME) do { \
egl_vtable_check_extension (vtable, display, FALSE, \
"GL_" G_STRINGIFY (NAME), \
&vtable->GL_PROTO_GEN_CONCAT(has_GL_,NAME)); \
} while (0);
#define GL_PROTO_BEGIN(NAME, TYPE, EXTENSION) do { \
n += egl_vtable_load_symbol (vtable, display, FALSE, \
G_STRINGIFY(GL_PROTO_GEN_CONCAT(gl,NAME)), \
(gpointer *) &vtable->GL_PROTO_GEN_CONCAT(gl,NAME), \
"GL_" G_STRINGIFY(EXTENSION), \
&vtable->GL_PROTO_GEN_CONCAT(has_GL_,EXTENSION)); \
} while (0);
#include "egl_vtable.h"
--vtable->has_GL_CORE_1_0;
--vtable->has_GL_CORE_1_1;
--vtable->has_GL_CORE_1_3;
--vtable->has_GL_CORE_2_0;
vtable->num_gl_symbols = n;
return TRUE;
}
static gboolean
egl_vtable_try_load_library (EglVTable * vtable, const gchar * name)
{
if (vtable->base.handle.p)
g_module_close (vtable->base.handle.p);
vtable->base.handle.p = g_module_open (name,
G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
if (!vtable->base.handle.p)
return FALSE;
GST_DEBUG ("loaded backend: %s", g_module_name (vtable->base.handle.p));
return TRUE;
}
static gboolean
egl_vtable_find_library (EglVTable * vtable)
{
const gchar ***library_names_ptr =
egl_vtable_get_library_names_group (vtable->gles_version);
if (!library_names_ptr)
return FALSE;
for (; *library_names_ptr != NULL; library_names_ptr++) {
const gchar **library_name_ptr = *library_names_ptr;
for (; *library_name_ptr != NULL; library_name_ptr++) {
if (egl_vtable_try_load_library (vtable, *library_name_ptr))
return TRUE;
}
}
return FALSE;
}
static gboolean
egl_vtable_init (EglVTable * vtable, EGLDisplay display, guint gles_version)
{
GST_DEBUG ("initialize for OpenGL|ES API version %d", gles_version);
vtable->gles_version = gles_version;
if (!egl_vtable_find_library (vtable))
return FALSE;
if (!egl_vtable_load_egl_symbols (vtable, display))
return FALSE;
return TRUE;
}
static void
egl_vtable_finalize (EglVTable * vtable)
{
g_strfreev (vtable->egl_extensions);
g_strfreev (vtable->gl_extensions);
if (vtable->base.handle.p)
g_module_close (vtable->base.handle.p);
if (vtable->base.is_wrapped) {
g_mutex_lock (&gl_vtables_lock);
gl_vtables[vtable->gles_version] = NULL;
g_mutex_unlock (&gl_vtables_lock);
}
}
static EglVTable *
egl_vtable_new (EglDisplay * display, guint gles_version)
{
EglVTable *vtable;
g_return_val_if_fail (display != NULL, NULL);
vtable = egl_object_new0 (egl_vtable_class ());
if (!vtable
|| !egl_vtable_init (vtable, display->base.handle.p, gles_version))
goto error;
return vtable;
error:
egl_object_replace (&vtable, NULL);
return NULL;
}
static EglVTable *
egl_vtable_new_cached (EglDisplay * display, guint gles_version)
{
EglVTable *vtable, **vtable_ptr;
g_return_val_if_fail (gles_version < G_N_ELEMENTS (gl_vtables), NULL);
vtable_ptr = &gl_vtables[gles_version];
g_mutex_lock (&gl_vtables_lock);
vtable = *vtable_ptr;
if (vtable)
egl_object_ref (vtable);
else {
vtable = egl_vtable_new (display, gles_version);
if (vtable) {
vtable->base.is_wrapped = TRUE;
*vtable_ptr = vtable;
}
}
g_mutex_unlock (&gl_vtables_lock);
return vtable;
}
/* ------------------------------------------------------------------------- */
// EGL Display
static gboolean
egl_display_run (EglDisplay * display, EglContextRunFunc func, gpointer args)
{
EglMessage *msg;
if (display->gl_thread == g_thread_self ()) {
func (args);
return TRUE;
}
msg = egl_object_new0 (egl_message_class ());
if (!msg)
return FALSE;
msg->base.is_valid = TRUE;
msg->func = func;
msg->args = args;
g_async_queue_push (display->gl_queue, egl_object_ref (msg));
g_mutex_lock (&display->mutex);
while (msg->base.is_valid)
g_cond_wait (&display->gl_thread_ready, &display->mutex);
g_mutex_unlock (&display->mutex);
egl_object_unref (msg);
return TRUE;
}
static gpointer
egl_display_thread (gpointer data)
{
EglDisplay *const display = data;
EGLDisplay gl_display = display->base.handle.p;
EGLint major_version, minor_version;
gchar **gl_apis, **gl_api;
if (!display->base.is_wrapped) {
gl_display = display->base.handle.p = eglGetDisplay (gl_display);
if (!gl_display)
goto error;
if (!eglInitialize (gl_display, &major_version, &minor_version))
goto error;
}
display->gl_vendor_string =
g_strdup (eglQueryString (gl_display, EGL_VENDOR));
display->gl_version_string =
g_strdup (eglQueryString (gl_display, EGL_VERSION));
display->gl_apis_string =
g_strdup (eglQueryString (gl_display, EGL_CLIENT_APIS));
GST_INFO ("EGL vendor: %s", display->gl_vendor_string);
GST_INFO ("EGL version: %s", display->gl_version_string);
GST_INFO ("EGL client APIs: %s", display->gl_apis_string);
gl_apis = g_strsplit (display->gl_apis_string, " ", 0);
if (!gl_apis)
goto error;
for (gl_api = gl_apis; *gl_api != NULL; gl_api++) {
const GlVersionInfo *const vinfo =
gl_version_info_lookup_by_api_name (*gl_api);
if (vinfo)
display->gl_apis |= vinfo->gl_api_bit;
}
g_strfreev (gl_apis);
if (!display->gl_apis)
goto error;
display->base.is_valid = TRUE;
g_cond_broadcast (&display->gl_thread_ready);
while (!display->gl_thread_cancel) {
EglMessage *const msg =
g_async_queue_timeout_pop (display->gl_queue, 100000);
if (msg) {
if (msg->base.is_valid) {
msg->func (msg->args);
msg->base.is_valid = FALSE;
g_cond_broadcast (&display->gl_thread_ready);
}
egl_object_unref (msg);
}
}
done:
if (gl_display != EGL_NO_DISPLAY && !display->base.is_wrapped)
eglTerminate (gl_display);
display->base.handle.p = NULL;
g_cond_broadcast (&display->gl_thread_ready);
return NULL;
error:
display->base.is_valid = FALSE;
goto done;
}
static gboolean
egl_display_init (EglDisplay * display)
{
display->gl_queue =
g_async_queue_new_full ((GDestroyNotify) gst_vaapi_mini_object_unref);
if (!display->gl_queue)
return FALSE;
g_mutex_init (&display->mutex);
g_cond_init (&display->gl_thread_ready);
display->gl_thread = g_thread_try_new ("OpenGL Thread", egl_display_thread,
display, NULL);
if (!display->gl_thread)
return FALSE;
g_mutex_lock (&display->mutex);
g_cond_wait (&display->gl_thread_ready, &display->mutex);
g_mutex_unlock (&display->mutex);
return display->base.is_valid;
}
static void
egl_display_finalize (EglDisplay * display)
{
display->gl_thread_cancel = TRUE;
g_thread_join (display->gl_thread);
g_cond_clear (&display->gl_thread_ready);
g_mutex_clear (&display->mutex);
g_async_queue_unref (display->gl_queue);
g_free (display->gl_vendor_string);
g_free (display->gl_version_string);
g_free (display->gl_apis_string);
}
static EglDisplay *
egl_display_new_full (gpointer handle, gboolean is_wrapped)
{
EglDisplay *display;
display = egl_object_new0 (egl_display_class ());
if (!display)
return NULL;
display->base.handle.p = handle;
display->base.is_wrapped = is_wrapped;
if (!egl_display_init (display))
goto error;
return display;
error:
egl_object_unref (display);
return NULL;
}
EglDisplay *
egl_display_new (gpointer native_display)
{
g_return_val_if_fail (native_display != NULL, NULL);
return egl_display_new_full (native_display, FALSE);
}
EglDisplay *
egl_display_new_wrapped (EGLDisplay gl_display)
{
g_return_val_if_fail (gl_display != EGL_NO_DISPLAY, NULL);
return egl_display_new_full (gl_display, TRUE);
}
/* ------------------------------------------------------------------------- */
// EGL Config
static gboolean
egl_config_init (EglConfig * config, EglDisplay * display,
const EGLint * attribs)
{
EGLDisplay const gl_display = display->base.handle.p;
const GlVersionInfo *vinfo;
EGLConfig gl_config;
EGLint v, gl_apis, num_configs;
egl_object_replace (&config->display, display);
if (!eglChooseConfig (gl_display, attribs, &gl_config, 1, &num_configs))
return FALSE;
if (num_configs != 1)
return FALSE;
config->base.handle.p = gl_config;
if (!eglGetConfigAttrib (gl_display, gl_config, EGL_CONFIG_ID, &v))
return FALSE;
config->config_id = v;
if (!eglGetConfigAttrib (gl_display, gl_config, EGL_NATIVE_VISUAL_ID, &v))
return FALSE;
config->visual_id = v;
if (!eglGetConfigAttrib (gl_display, gl_config, EGL_RENDERABLE_TYPE, &v))
return FALSE;
if (!egl_find_attrib_value (attribs, EGL_RENDERABLE_TYPE, &gl_apis))
return FALSE;
vinfo = gl_version_info_lookup_by_api (v & gl_apis);
if (!vinfo)
return FALSE;
config->gles_version = vinfo->gles_version;
config->gl_api = vinfo->gles_version > 0 ? EGL_OPENGL_ES_API : EGL_OPENGL_API;
return TRUE;
}
static void
egl_config_finalize (EglConfig * config)
{
egl_object_replace (&config->display, NULL);
}
EglConfig *
egl_config_new (EglDisplay * display, guint gles_version, GstVideoFormat format)
{
EGLint attribs[2 * 6 + 1], *attrib = attribs;
const GstVideoFormatInfo *finfo;
const GlVersionInfo *vinfo;
g_return_val_if_fail (display != NULL, NULL);
finfo = gst_video_format_get_info (format);
if (!finfo || !GST_VIDEO_FORMAT_INFO_IS_RGB (finfo))
return NULL;
vinfo = gl_version_info_lookup (gles_version);
if (!vinfo)
return NULL;
*attrib++ = EGL_COLOR_BUFFER_TYPE;
*attrib++ = EGL_RGB_BUFFER;
*attrib++ = EGL_RED_SIZE;
*attrib++ = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, GST_VIDEO_COMP_R);
*attrib++ = EGL_GREEN_SIZE;
*attrib++ = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, GST_VIDEO_COMP_G);
*attrib++ = EGL_BLUE_SIZE;
*attrib++ = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, GST_VIDEO_COMP_B);
*attrib++ = EGL_ALPHA_SIZE;
*attrib++ = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, GST_VIDEO_COMP_A);
*attrib++ = EGL_RENDERABLE_TYPE;
*attrib++ = vinfo->gl_api_bit;
*attrib++ = EGL_NONE;
g_assert (attrib - attribs <= G_N_ELEMENTS (attribs));
return egl_config_new_with_attribs (display, attribs);
}
EglConfig *
egl_config_new_with_attribs (EglDisplay * display, const EGLint * attribs)
{
EglConfig *config;
g_return_val_if_fail (display != NULL, NULL);
g_return_val_if_fail (attribs != NULL, NULL);
config = egl_object_new0 (egl_config_class ());
if (!config || !egl_config_init (config, display, attribs))
goto error;
return config;
error:
egl_object_replace (&config, NULL);
return NULL;
}
static EglConfig *
egl_config_new_from_gl_context (EglDisplay * display, EGLContext gl_context)
{
EGLDisplay const gl_display = display->base.handle.p;
EGLint attribs[3 * 2 + 1], *attrib = attribs;
EGLint config_id, api, v;
guint gles_version;
const GlVersionInfo *vinfo;
if (!eglQueryContext (gl_display, gl_context, EGL_CONFIG_ID, &config_id))
return NULL;
if (!eglQueryContext (gl_display, gl_context, EGL_CONTEXT_CLIENT_TYPE, &api))
return NULL;
if (!eglQueryContext (gl_display, gl_context, EGL_CONTEXT_CLIENT_VERSION, &v))
return NULL;
if (api == EGL_OPENGL_API)
gles_version = 0;
else if (api == EGL_OPENGL_ES_API)
gles_version = v;
else {
GST_ERROR ("unsupported EGL client API (%d)", api);
return NULL;
}
vinfo = gl_version_info_lookup (gles_version);
if (!vinfo)
return NULL;
*attrib++ = EGL_COLOR_BUFFER_TYPE;
*attrib++ = EGL_RGB_BUFFER;
*attrib++ = EGL_CONFIG_ID;
*attrib++ = config_id;
*attrib++ = EGL_RENDERABLE_TYPE;
*attrib++ = vinfo->gl_api_bit;
*attrib++ = EGL_NONE;
g_assert (attrib - attribs <= G_N_ELEMENTS (attribs));
return egl_config_new_with_attribs (display, attribs);
}
/* ------------------------------------------------------------------------- */
// EGL Surface
static void
egl_surface_finalize (EglSurface * surface)
{
if (surface->base.handle.p != EGL_NO_SURFACE && !surface->base.is_wrapped)
eglDestroySurface (surface->display->base.handle.p, surface->base.handle.p);
egl_object_replace (&surface->display, NULL);
}
EglSurface *
egl_surface_new_wrapped (EglDisplay * display, EGLSurface gl_surface)
{
EglSurface *surface;
g_return_val_if_fail (display != NULL, NULL);
surface = egl_object_new (egl_surface_class ());
if (!surface)
return NULL;
surface->base.is_wrapped = TRUE;
surface->base.handle.p = gl_surface;
surface->display = egl_object_ref (display);
return surface;
}
/* ------------------------------------------------------------------------- */
// EGL Context
static void
egl_context_state_get_current (EglContextState * cs);
static gboolean
egl_context_state_set_current (EglContextState * new_cs,
EglContextState * old_cs);
static gboolean
ensure_vtable (EglContext * ctx)
{
if (!ctx->vtable) {
ctx->vtable = egl_vtable_new_cached (ctx->display,
ctx->config ? ctx->config->gles_version : 0);
if (!ctx->vtable)
return FALSE;
}
return TRUE;
}
static gboolean
ensure_context (EglContext * ctx, EGLContext gl_parent_context)
{
EGLDisplay *const gl_display = ctx->display->base.handle.p;
EGLContext gl_context = ctx->base.handle.p;
EGLint gles_attribs[3], *attribs = NULL;
if (!gl_context) {
if (ctx->config->gles_version >= 2) {
attribs = gles_attribs;
attribs[0] = EGL_CONTEXT_CLIENT_VERSION;
attribs[1] = ctx->config->gles_version;
attribs[2] = EGL_NONE;
}
gl_context = eglCreateContext (gl_display, ctx->config->base.handle.p,
gl_parent_context, attribs);
if (!gl_context)
goto error_create_context;
ctx->base.handle.p = gl_context;
}
return TRUE;
/* ERRORS */
error_create_context:
GST_ERROR ("failed to create EGL context");
return FALSE;
}
static inline gboolean
ensure_gl_is_surfaceless (EglContext * ctx)
{
return ctx->vtable->has_EGL_KHR_surfaceless_context ||
(ctx->read_surface && ctx->draw_surface);
}
static gboolean
ensure_gl_scene (EglContext * ctx)
{
EglVTable *vtable;
if (!ensure_gl_is_surfaceless (ctx))
return FALSE;
if (ctx->base.is_valid)
return TRUE;
vtable = egl_context_get_vtable (ctx, TRUE);
if (!vtable)
return FALSE;
vtable->glClearColor (0.0, 0.0, 0.0, 1.0);
if (ctx->config && ctx->config->gles_version == 0)
vtable->glEnable (GL_TEXTURE_2D);
vtable->glDisable (GL_BLEND);
vtable->glDisable (GL_DEPTH_TEST);
ctx->base.is_valid = TRUE;
return TRUE;
}
/**
* egl_context_state_get_current:
* @cs: return location to the current #EglContextState
*
* Retrieves the current EGL context, display and surface to pack into
* the #EglContextState struct.
*/
static void
egl_context_state_get_current (EglContextState * cs)
{
cs->display = eglGetCurrentDisplay ();
cs->context = eglGetCurrentContext ();
if (cs->context) {
cs->read_surface = eglGetCurrentSurface (EGL_READ);
cs->draw_surface = eglGetCurrentSurface (EGL_DRAW);
} else {
cs->read_surface = EGL_NO_SURFACE;
cs->draw_surface = EGL_NO_SURFACE;
}
}
/**
* egl_context_state_set_current:
* @new_cs: the requested new #EglContextState
* @old_cs: return location to the context that was previously current
*
* Makes the @new_cs EGL context the current EGL rendering context of
* the calling thread, replacing the previously current context if
* there was one.
*
* If @old_cs is non %NULL, the previously current EGL context and
* surface are recorded.
*
* Return value: %TRUE on success
*/
static gboolean
egl_context_state_set_current (EglContextState * new_cs,
EglContextState * old_cs)
{
/* If display is NULL, this could be that new_cs was retrieved from
egl_context_state_get_current() 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->context && !new_cs->read_surface && !new_cs->draw_surface;
if (old_cs) {
if (old_cs == new_cs)
return TRUE;
egl_context_state_get_current (old_cs);
if (old_cs->display == new_cs->display &&
old_cs->context == new_cs->context &&
old_cs->read_surface == new_cs->read_surface &&
old_cs->draw_surface == new_cs->draw_surface)
return TRUE;
}
return eglMakeCurrent (new_cs->display, new_cs->draw_surface,
new_cs->read_surface, new_cs->context);
}
static gboolean
egl_context_init (EglContext * ctx, EglDisplay * display, EglConfig * config,
EGLContext gl_parent_context)
{
egl_object_replace (&ctx->display, display);
egl_object_replace (&ctx->config, config);
if (config)
eglBindAPI (config->gl_api);
if (!ensure_vtable (ctx))
return FALSE;
if (!ensure_context (ctx, gl_parent_context))
return FALSE;
return TRUE;
}
static void
egl_context_finalize (EglContext * ctx)
{
if (ctx->base.handle.p && !ctx->base.is_wrapped)
eglDestroyContext (ctx->display->base.handle.p, ctx->base.handle.p);
egl_object_replace (&ctx->read_surface, NULL);
egl_object_replace (&ctx->draw_surface, NULL);
egl_object_replace (&ctx->config, NULL);
egl_object_replace (&ctx->display, NULL);
egl_object_replace (&ctx->vtable, NULL);
}
typedef struct
{
EglDisplay *display;
EglConfig *config;
EGLContext gl_parent_context;
EglContext *context; /* result */
} CreateContextArgs;
static void
do_egl_context_new (CreateContextArgs * args)
{
EglContext *ctx;
ctx = egl_object_new0 (egl_context_class ());
if (!ctx || !egl_context_init (ctx, args->display, args->config,
args->gl_parent_context))
goto error;
args->context = ctx;
return;
error:
egl_object_replace (&ctx, NULL);
args->context = NULL;
}
EglContext *
egl_context_new (EglDisplay * display, EglConfig * config, EglContext * parent)
{
CreateContextArgs args;
g_return_val_if_fail (display != NULL, NULL);
g_return_val_if_fail (config != NULL, NULL);
args.display = display;
args.config = config;
args.gl_parent_context = parent ? parent->base.handle.p : EGL_NO_CONTEXT;
if (!egl_display_run (display, (EglContextRunFunc) do_egl_context_new, &args))
return NULL;
return args.context;
}
EglContext *
egl_context_new_wrapped (EglDisplay * display, EGLContext gl_context)
{
CreateContextArgs args;
EglConfig *config;
gboolean success;
g_return_val_if_fail (display != NULL, NULL);
g_return_val_if_fail (gl_context != EGL_NO_CONTEXT, NULL);
config = egl_config_new_from_gl_context (display, gl_context);
if (!config)
return NULL;
args.display = display;
args.config = config;
args.gl_parent_context = gl_context;
success = egl_display_run (display, (EglContextRunFunc) do_egl_context_new,
&args);
egl_object_unref (config);
if (!success)
return NULL;
return args.context;
}
EglVTable *
egl_context_get_vtable (EglContext * ctx, gboolean need_gl_symbols)
{
g_return_val_if_fail (ctx != NULL, NULL);
g_return_val_if_fail (ctx->display->gl_thread == g_thread_self (), NULL);
if (!ensure_vtable (ctx))
return NULL;
if (need_gl_symbols && !(ctx->vtable->num_gl_symbols > 0 ||
egl_vtable_load_gl_symbols (ctx->vtable,
ctx->display->base.handle.p)))
return NULL;
return ctx->vtable;
}
void
egl_context_set_surface (EglContext * ctx, EglSurface * surface)
{
g_return_if_fail (ctx != NULL);
g_return_if_fail (surface != NULL);
egl_object_replace (&ctx->read_surface, surface);
egl_object_replace (&ctx->draw_surface, surface);
}
gboolean
egl_context_set_current (EglContext * ctx, gboolean activate,
EglContextState * old_cs)
{
EglContextState cs, *new_cs;
g_return_val_if_fail (ctx != NULL, FALSE);
g_return_val_if_fail (ctx->display->gl_thread == g_thread_self (), FALSE);
if (activate) {
new_cs = &cs;
new_cs->display = ctx->display->base.handle.p;
new_cs->context = ctx->base.handle.p;
new_cs->draw_surface = ctx->draw_surface ?
ctx->draw_surface->base.handle.p : EGL_NO_SURFACE;
new_cs->read_surface = ctx->read_surface ?
ctx->read_surface->base.handle.p : EGL_NO_SURFACE;
} else if (old_cs) {
new_cs = old_cs;
old_cs = NULL;
} else {
new_cs = &cs;
new_cs->display = ctx->display->base.handle.p;
new_cs->context = EGL_NO_CONTEXT;
new_cs->draw_surface = EGL_NO_SURFACE;
new_cs->read_surface = EGL_NO_SURFACE;
old_cs = NULL;
}
if (!egl_context_state_set_current (new_cs, old_cs))
return FALSE;
if (activate && !ensure_gl_scene (ctx))
return FALSE;
return TRUE;
}
gboolean
egl_context_run (EglContext * ctx, EglContextRunFunc func, gpointer args)
{
g_return_val_if_fail (ctx != NULL, FALSE);
g_return_val_if_fail (func != NULL, FALSE);
return egl_display_run (ctx->display, func, args);
}
/* ------------------------------------------------------------------------- */
// EGL Program
static GLuint
egl_compile_shader (EglContext * ctx, GLenum type, const char *source)
{
EglVTable *const vtable = egl_context_get_vtable (ctx, TRUE);
GLuint shader;
GLint status;
char log[BUFSIZ];
GLsizei log_length;
shader = vtable->glCreateShader (type);
vtable->glShaderSource (shader, 1, &source, NULL);
vtable->glCompileShader (shader);
vtable->glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
if (!status) {
GST_ERROR ("failed to compile %s shader",
type == GL_FRAGMENT_SHADER ? "fragment" :
type == GL_VERTEX_SHADER ? "vertex" : "<unknown>");
vtable->glGetShaderInfoLog (shader, sizeof (log), &log_length, log);
GST_ERROR ("info log: %s", log);
return 0;
}
return shader;
}
static void
egl_program_finalize (EglProgram * program)
{
EglVTable *const vtable = program->vtable;
if (program->base.handle.u)
vtable->glDeleteProgram (program->base.handle.u);
if (program->frag_shader)
vtable->glDeleteShader (program->frag_shader);
if (program->vert_shader)
vtable->glDeleteShader (program->vert_shader);
egl_object_replace (&program->vtable, NULL);
}
static gboolean
egl_program_init (EglProgram * program, EglContext * ctx,
const gchar * frag_shader_text, const gchar * vert_shader_text)
{
EglVTable *const vtable = egl_context_get_vtable (ctx, TRUE);
GLuint prog_id;
char msg[BUFSIZ];
GLsizei msglen;
GLint status;
if (ctx->config->gles_version == 1)
goto error_unsupported_gles_version;
program->vtable = egl_object_ref (vtable);
program->frag_shader =
egl_compile_shader (ctx, GL_FRAGMENT_SHADER, frag_shader_text);
if (!program->frag_shader)
return FALSE;
program->vert_shader =
egl_compile_shader (ctx, GL_VERTEX_SHADER, vert_shader_text);
if (!program->vert_shader)
return FALSE;
prog_id = vtable->glCreateProgram ();
if (!prog_id)
return FALSE;
program->base.handle.u = prog_id;
vtable->glAttachShader (prog_id, program->frag_shader);
vtable->glAttachShader (prog_id, program->vert_shader);
vtable->glBindAttribLocation (prog_id, 0, "position");
vtable->glBindAttribLocation (prog_id, 1, "texcoord");
vtable->glLinkProgram (prog_id);
vtable->glGetProgramiv (prog_id, GL_LINK_STATUS, &status);
if (!status)
goto error_link_program;
return TRUE;
/* ERRORS */
error_unsupported_gles_version:
GST_ERROR ("unsupported shader with OpenGL|ES version 1");
return FALSE;
error_link_program:
vtable->glGetProgramInfoLog (prog_id, sizeof (msg), &msglen, msg);
GST_ERROR ("failed to link program: %s", msg);
return FALSE;
}
EglProgram *
egl_program_new (EglContext * ctx, const gchar * frag_shader_text,
const gchar * vert_shader_text)
{
EglProgram *program;
g_return_val_if_fail (ctx != NULL, NULL);
g_return_val_if_fail (frag_shader_text != NULL, NULL);
g_return_val_if_fail (vert_shader_text != NULL, NULL);
program = egl_object_new0 (egl_program_class ());
if (!program
|| !egl_program_init (program, ctx, frag_shader_text, vert_shader_text))
goto error;
return program;
error:
egl_object_replace (&program, NULL);
return NULL;
}
/* ------------------------------------------------------------------------- */
// Misc utility functions
void
egl_matrix_set_identity (gfloat m[16])
{
#define MAT(m,r,c) (m)[(c) * 4 + (r)]
MAT(m,0,0) = 1.0; MAT(m,0,1) = 0.0; MAT(m,0,2) = 0.0; MAT(m,0,3) = 0.0;
MAT(m,1,0) = 0.0; MAT(m,1,1) = 1.0; MAT(m,1,2) = 0.0; MAT(m,1,3) = 0.0;
MAT(m,2,0) = 0.0; MAT(m,2,1) = 0.0; MAT(m,2,2) = 1.0; MAT(m,2,3) = 0.0;
MAT(m,3,0) = 0.0; MAT(m,3,1) = 0.0; MAT(m,3,2) = 0.0; MAT(m,3,3) = 1.0;
#undef MAT
}
/**
* egl_create_texture:
* @ctx: the parent #EglContext object
* @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
*/
guint
egl_create_texture (EglContext * ctx, guint target, guint format,
guint width, guint height)
{
EglVTable *const vtable = egl_context_get_vtable (ctx, TRUE);
guint internal_format, texture, 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_EXT:
internal_format = GL_RGBA;
bytes_per_component = 4;
break;
default:
bytes_per_component = 0;
break;
}
g_assert (bytes_per_component > 0);
vtable->glGenTextures (1, &texture);
vtable->glBindTexture (target, texture);
if (width > 0 && height > 0)
vtable->glTexImage2D (target, 0, internal_format, width, height, 0,
format, GL_UNSIGNED_BYTE, NULL);
vtable->glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
vtable->glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
vtable->glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
vtable->glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
vtable->glPixelStorei (GL_UNPACK_ALIGNMENT, bytes_per_component);
return texture;
}
/**
* egl_destroy_texture:
* @ctx: the parent #EglContext object
* @texture: the texture name to delete
*
* Destroys the supplied @texture name.
*/
void
egl_destroy_texture (EglContext * ctx, guint texture)
{
EglVTable *const vtable = egl_context_get_vtable (ctx, TRUE);
vtable->glDeleteTextures (1, &texture);
}