/* * gstvaapiutils_egl.c - EGL utilities * * Copyright (C) 2014 Intel Corporation * Author: Gwenole Beauchesne * * 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; 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; 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); } 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; 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; } 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" : ""); 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; } /* ------------------------------------------------------------------------- */ // 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; 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); }