gstreamer/gst-libs/gst/vaapi/gstvaapiutils_egl.c
Matthew Waters 499e248d4c egl: Fix racyness in display thread creation
Multiple different scenarios could break the display thread creation and
end up blocking waiting for thread o be created.  Fix them all by
correctly waiting for a new boolean to become valid.
2019-09-18 05:36:52 +00:00

1485 lines
40 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"
#if USE_GST_GL_HELPERS
# include <gst/gl/gl.h>
# if GST_GL_HAVE_PLATFORM_EGL
# include <gst/gl/egl/gstgldisplay_egl.h>
# endif
#endif
#define DEBUG 1
#include "gstvaapidebug.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;
typedef struct egl_object_class_s EglWindowClass;
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);
EGL_OBJECT_DEFINE_CLASS (EglWindow, egl_window);
/* ------------------------------------------------------------------------- */
// 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;
/* ERRORS */
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_get_display_from_native (guintptr native_display, guint gl_platform)
{
#if USE_GST_GL_HELPERS && GST_GL_HAVE_PLATFORM_EGL
EGLDisplay ret;
GstGLDisplayType display_type = GST_GL_DISPLAY_TYPE_ANY;
switch (gl_platform) {
case EGL_PLATFORM_X11:
display_type = GST_GL_DISPLAY_TYPE_X11;
break;
case EGL_PLATFORM_WAYLAND:
display_type = GST_GL_DISPLAY_TYPE_WAYLAND;
break;
default:
break;
}
ret = gst_gl_display_egl_get_from_native (display_type, native_display);
if (ret != EGL_NO_DISPLAY)
return ret;
#endif
return eglGetDisplay ((EGLNativeDisplayType) native_display);
}
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;
g_mutex_lock (&display->mutex);
if (!display->base.is_wrapped) {
gl_display = display->base.handle.p =
egl_get_display_from_native (display->base.handle.u,
display->gl_platform);
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;
display->created = TRUE;
g_cond_broadcast (&display->gl_thread_ready);
g_mutex_unlock (&display->mutex);
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);
}
}
g_mutex_lock (&display->mutex);
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);
g_mutex_unlock (&display->mutex);
return NULL;
/* ERRORS */
error:
{
display->created = TRUE;
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);
while (!display->created)
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, guint platform)
{
EglDisplay *display;
display = egl_object_new0 (egl_display_class ());
if (!display)
return NULL;
display->base.handle.p = handle;
display->base.is_wrapped = is_wrapped;
display->gl_platform = platform;
if (!egl_display_init (display))
goto error;
return display;
/* ERRORS */
error:
{
egl_object_unref (display);
return NULL;
}
}
EglDisplay *
egl_display_new (gpointer native_display, guint platform)
{
g_return_val_if_fail (native_display != NULL, NULL);
return egl_display_new_full (native_display, FALSE, platform);
}
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_PLATFORM_UNKNOWN);
}
/* ------------------------------------------------------------------------- */
// 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;
/* ERRORS */
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);
}
static 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;
/* ERRORS */
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;
args.context = NULL;
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;
}
static 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;
/* ERRORS */
error:
{
egl_object_replace (&program, NULL);
return NULL;
}
}
/* ------------------------------------------------------------------------- */
// EGL Window
static gboolean
egl_window_init (EglWindow * window, EglContext * ctx, gpointer native_window)
{
EGLSurface gl_surface;
window->context = egl_context_new (ctx->display, ctx->config, ctx);
if (!window->context)
return FALSE;
ctx = window->context;
gl_surface = eglCreateWindowSurface (ctx->display->base.handle.p,
ctx->config->base.handle.p, (EGLNativeWindowType) native_window, NULL);
if (!gl_surface)
return FALSE;
window->surface = egl_surface_new_wrapped (ctx->display, gl_surface);
if (!window->surface)
goto error_create_surface;
window->base.handle.p = gl_surface;
window->base.is_wrapped = FALSE;
egl_context_set_surface (ctx, window->surface);
return TRUE;
/* ERRORS */
error_create_surface:
GST_ERROR ("failed to create EGL wrapper surface");
eglDestroySurface (ctx->display->base.handle.p, gl_surface);
return FALSE;
}
static void
egl_window_finalize (EglWindow * window)
{
if (window->context && window->base.handle.p)
eglDestroySurface (window->context->display->base.handle.p,
window->base.handle.p);
egl_object_replace (&window->surface, NULL);
egl_object_replace (&window->context, NULL);
}
EglWindow *
egl_window_new (EglContext * ctx, gpointer native_window)
{
EglWindow *window;
g_return_val_if_fail (ctx != NULL, NULL);
g_return_val_if_fail (native_window != NULL, NULL);
window = egl_object_new0 (egl_window_class ());
if (!window || !egl_window_init (window, ctx, native_window))
goto error;
return window;
/* ERRORS */
error:
{
egl_object_replace (&window, 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);
}