/* * GStreamer * Copyright (C) 2013 Matthew Waters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:gstglcontext * @short_description: OpenGL context abstraction * @title: GstGLContext * @see_also: #GstGLDisplay, #GstGLWindow * * #GstGLContext wraps an OpenGL context object in a uniform API. As a result * of the limitation on OpenGL context, this object is not thread safe unless * specified and must only be activated in a single thread. */ #if HAVE_CONFIG_H # include "config.h" #endif #if defined(ANDROID) || defined(__ANDROID__) /* Avoid a linker error with _isoc99_sscanf() when building a shared library * for android */ #define _GNU_SOURCE #endif #include #include #include #include "gl.h" #include "gstglcontext.h" #ifndef GL_NUM_EXTENSIONS #define GL_NUM_EXTENSIONS 0x0000821d #endif #if GST_GL_HAVE_PLATFORM_GLX #include "x11/gstglcontext_glx.h" #endif #if GST_GL_HAVE_PLATFORM_EGL #include "egl/gstglcontext_egl.h" #endif #if GST_GL_HAVE_PLATFORM_CGL #include "cocoa/gstglcontext_cocoa.h" #endif #if GST_GL_HAVE_PLATFORM_WGL #include "win32/gstglcontext_wgl.h" #endif #if GST_GL_HAVE_PLATFORM_EAGL #include "eagl/gstglcontext_eagl.h" #endif extern void _gst_gl_debug_enable (GstGLContext * context); static GPrivate current_context_key; static GModule *module_self; static GOnce module_self_gonce = G_ONCE_INIT; #if GST_GL_HAVE_OPENGL static GOnce module_opengl_gonce = G_ONCE_INIT; static GModule *module_opengl; static gpointer load_opengl_module (gpointer user_data) { #ifdef GST_GL_LIBGL_MODULE_NAME module_opengl = g_module_open (GST_GL_LIBGL_MODULE_NAME, G_MODULE_BIND_LAZY); #else /* On Linux the .so is only in -dev packages, try with a real soname * Proper compilers will optimize away the strcmp */ if (g_strcmp0 (G_MODULE_SUFFIX, "so") == 0) module_opengl = g_module_open ("libGL.so.1", G_MODULE_BIND_LAZY); /* This automatically handles the suffix and even .la files */ if (!module_opengl) module_opengl = g_module_open ("libGL", G_MODULE_BIND_LAZY); #endif return NULL; } #endif #if GST_GL_HAVE_GLES2 static GOnce module_gles2_gonce = G_ONCE_INIT; static GModule *module_gles2; static gpointer load_gles2_module (gpointer user_data) { #ifdef GST_GL_LIBGLESV2_MODULE_NAME module_gles2 = g_module_open (GST_GL_LIBGLESV2_MODULE_NAME, G_MODULE_BIND_LAZY); #else /* On Linux the .so is only in -dev packages, try with a real soname * Proper compilers will optimize away the strcmp */ if (g_strcmp0 (G_MODULE_SUFFIX, "so") == 0) module_gles2 = g_module_open ("libGLESv2.so.2", G_MODULE_BIND_LAZY); /* This automatically handles the suffix and even .la files */ if (!module_gles2) module_gles2 = g_module_open ("libGLESv2", G_MODULE_BIND_LAZY); #endif return NULL; } #endif static gpointer load_self_module (gpointer user_data) { module_self = g_module_open (NULL, G_MODULE_BIND_LAZY); return NULL; } /* Context sharedness is tracked by a refcounted pointer stored in each context * object to track complex creation/deletion scenarios. As a result, * sharedness can only be successfully validated between two GstGLContext's * where one is not a wrapped context. * * As there is no API at the winsys level to tell whether two OpenGL contexts * can share GL resources, this is the next best thing. * * XXX: we may need a way to associate two wrapped GstGLContext's as being * shared however I have not come across a use case that requries this yet. */ struct ContextShareGroup { volatile int refcount; }; static struct ContextShareGroup * _context_share_group_new (void) { struct ContextShareGroup *ret = g_new0 (struct ContextShareGroup, 1); ret->refcount = 1; return ret; } static struct ContextShareGroup * _context_share_group_ref (struct ContextShareGroup *share) { g_atomic_int_inc (&share->refcount); return share; } static void _context_share_group_unref (struct ContextShareGroup *share) { if (g_atomic_int_dec_and_test (&share->refcount)) g_free (share); } static gboolean _context_share_group_is_shared (struct ContextShareGroup *share) { return g_atomic_int_get (&share->refcount) > 1; } #define GST_CAT_DEFAULT gst_gl_context_debug GST_DEBUG_CATEGORY (GST_CAT_DEFAULT); GST_DEBUG_CATEGORY_STATIC (gst_gl_debug); #define gst_gl_context_parent_class parent_class G_DEFINE_ABSTRACT_TYPE (GstGLContext, gst_gl_context, GST_TYPE_OBJECT); #define GST_GL_CONTEXT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_CONTEXT, GstGLContextPrivate)) static void _init_debug (void); static gpointer gst_gl_context_create_thread (GstGLContext * context); static void gst_gl_context_finalize (GObject * object); struct _GstGLContextPrivate { GThread *gl_thread; GThread *active_thread; /* conditions */ GMutex render_lock; GCond create_cond; gboolean created; gboolean alive; GWeakRef other_context_ref; struct ContextShareGroup *sharegroup; GError **error; gint gl_major; gint gl_minor; gchar *gl_exts; }; typedef struct { GstGLContext parent; guintptr handle; GstGLPlatform platform; GstGLAPI available_apis; } GstGLWrappedContext; typedef struct { GstGLContextClass parent; } GstGLWrappedContextClass; #define GST_GL_TYPE_WRAPPED_CONTEXT (gst_gl_wrapped_context_get_type()) GType gst_gl_wrapped_context_get_type (void); G_DEFINE_TYPE (GstGLWrappedContext, gst_gl_wrapped_context, GST_GL_TYPE_CONTEXT); #define GST_GL_WRAPPED_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GST_GL_TYPE_WRAPPED_CONTEXT, GstGLWrappedContext)) #define GST_GL_WRAPPED_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS((k), GST_GL_TYPE_CONTEXT, GstGLContextClass)) #define GST_IS_GL_WRAPPED_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GST_GL_TYPE_WRAPPED_CONTEXT)) #define GST_IS_GL_WRAPPED_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), GST_GL_TYPE_WRAPPED_CONTEXT)) #define GST_GL_WRAPPED_CONTEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GST_GL_TYPE_WRAPPED_CONTEXT, GstGLWrappedContextClass)) GQuark gst_gl_context_error_quark (void) { return g_quark_from_static_string ("gst-gl-context-error-quark"); } static void _ensure_window (GstGLContext * context) { GstGLWindow *window; if (context->window) return; window = gst_gl_window_new (context->display); gst_gl_context_set_window (context, window); gst_object_unref (window); } static void gst_gl_context_init (GstGLContext * context) { context->priv = GST_GL_CONTEXT_GET_PRIVATE (context); context->window = NULL; context->gl_vtable = g_slice_alloc0 (sizeof (GstGLFuncs)); g_mutex_init (&context->priv->render_lock); g_cond_init (&context->priv->create_cond); context->priv->created = FALSE; g_weak_ref_init (&context->priv->other_context_ref, NULL); } static void gst_gl_context_class_init (GstGLContextClass * klass) { g_type_class_add_private (klass, sizeof (GstGLContextPrivate)); klass->get_proc_address = GST_DEBUG_FUNCPTR (gst_gl_context_default_get_proc_address); G_OBJECT_CLASS (klass)->finalize = gst_gl_context_finalize; _init_debug (); } static void _init_debug (void) { static volatile gsize _init = 0; if (g_once_init_enter (&_init)) { GST_DEBUG_CATEGORY_INIT (gst_gl_context_debug, "glcontext", 0, "glcontext element"); GST_DEBUG_CATEGORY_INIT (gst_gl_debug, "gldebug", 0, "OpenGL Debugging"); g_once_init_leave (&_init, 1); } } /** * gst_gl_context_new: * @display: a #GstGLDisplay * * Create a new #GstGLContext with the specified @display * * Returns: a new #GstGLContext * * Since: 1.4 */ GstGLContext * gst_gl_context_new (GstGLDisplay * display) { GstGLContext *context = NULL; const gchar *user_choice; _init_debug (); user_choice = g_getenv ("GST_GL_PLATFORM"); GST_INFO ("creating a context for display %" GST_PTR_FORMAT ", user choice:%s", display, user_choice); #if GST_GL_HAVE_PLATFORM_CGL if (!context && (!user_choice || g_strstr_len (user_choice, 3, "cgl"))) context = GST_GL_CONTEXT (gst_gl_context_cocoa_new (display)); #endif #if GST_GL_HAVE_PLATFORM_GLX if (!context && (!user_choice || g_strstr_len (user_choice, 3, "glx"))) context = GST_GL_CONTEXT (gst_gl_context_glx_new (display)); #endif #if GST_GL_HAVE_PLATFORM_EGL if (!context && (!user_choice || g_strstr_len (user_choice, 3, "egl"))) context = GST_GL_CONTEXT (gst_gl_context_egl_new (display)); #endif #if GST_GL_HAVE_PLATFORM_WGL if (!context && (!user_choice || g_strstr_len (user_choice, 3, "wgl"))) context = GST_GL_CONTEXT (gst_gl_context_wgl_new (display)); #endif #if GST_GL_HAVE_PLATFORM_EAGL if (!context && (!user_choice || g_strstr_len (user_choice, 4, "eagl"))) context = GST_GL_CONTEXT (gst_gl_context_eagl_new (display)); #endif if (!context) { /* subclass returned a NULL context */ GST_WARNING ("Could not create context. user specified %s", user_choice ? user_choice : "(null)"); return NULL; } context->display = gst_object_ref (display); GST_DEBUG_OBJECT (context, "Done creating context for display %" GST_PTR_FORMAT " (user_choice:%s)", display, user_choice); return context; } /** * gst_gl_context_new_wrapped: * @display: a #GstGLDisplay * @handle: the OpenGL context to wrap * @context_type: a #GstGLPlatform specifying the type of context in @handle * @available_apis: a #GstGLAPI containing the available OpenGL apis in @handle * * Wraps an existing OpenGL context into a #GstGLContext. * * Returns: a #GstGLContext wrapping @handle * * Since: 1.4 */ GstGLContext * gst_gl_context_new_wrapped (GstGLDisplay * display, guintptr handle, GstGLPlatform context_type, GstGLAPI available_apis) { GstGLContext *context; GstGLWrappedContext *context_wrap = NULL; GstGLContextClass *context_class; GstGLAPI display_api; _init_debug (); display_api = gst_gl_display_get_gl_api (display); g_return_val_if_fail ((display_api & available_apis) != GST_GL_API_NONE, NULL); context_wrap = g_object_new (GST_GL_TYPE_WRAPPED_CONTEXT, NULL); if (!context_wrap) { /* subclass returned a NULL context */ GST_ERROR ("Could not wrap existing context"); return NULL; } context = (GstGLContext *) context_wrap; context->display = gst_object_ref (display); context->priv->sharegroup = _context_share_group_new (); context_wrap->handle = handle; context_wrap->platform = context_type; context_wrap->available_apis = available_apis; context_class = GST_GL_CONTEXT_GET_CLASS (context); #if GST_GL_HAVE_PLATFORM_GLX if (context_type == GST_GL_PLATFORM_GLX) { context_class->get_current_context = gst_gl_context_glx_get_current_context; context_class->get_proc_address = gst_gl_context_glx_get_proc_address; } #endif #if GST_GL_HAVE_PLATFORM_EGL if (context_type == GST_GL_PLATFORM_EGL) { context_class->get_current_context = gst_gl_context_egl_get_current_context; context_class->get_proc_address = gst_gl_context_egl_get_proc_address; } #endif #if GST_GL_HAVE_PLATFORM_CGL if (context_type == GST_GL_PLATFORM_CGL) { context_class->get_current_context = gst_gl_context_cocoa_get_current_context; context_class->get_proc_address = gst_gl_context_default_get_proc_address; } #endif #if GST_GL_HAVE_PLATFORM_WGL if (context_type == GST_GL_PLATFORM_WGL) { context_class->get_current_context = gst_gl_context_wgl_get_current_context; context_class->get_proc_address = gst_gl_context_wgl_get_proc_address; } #endif #if GST_GL_HAVE_PLATFORM_EAGL if (context_type == GST_GL_PLATFORM_EAGL) { context_class->get_current_context = gst_gl_context_eagl_get_current_context; context_class->get_proc_address = gst_gl_context_default_get_proc_address; } #endif if (!context_class->get_current_context) { /* we don't have API support */ gst_object_unref (context); return NULL; } return context; } /** * gst_gl_context_get_current_gl_context: * @context_type: a #GstGLPlatform specifying the type of context to retrieve * * Returns: The OpenGL context handle current in the calling thread or %NULL * * Since: 1.6 */ guintptr gst_gl_context_get_current_gl_context (GstGLPlatform context_type) { guintptr handle = 0; _init_debug (); #if GST_GL_HAVE_PLATFORM_GLX if (!handle && (context_type & GST_GL_PLATFORM_GLX) != 0) handle = gst_gl_context_glx_get_current_context (); #endif #if GST_GL_HAVE_PLATFORM_EGL if (!handle && (context_type & GST_GL_PLATFORM_EGL) != 0) handle = gst_gl_context_egl_get_current_context (); #endif #if GST_GL_HAVE_PLATFORM_CGL if (!handle && (context_type & GST_GL_PLATFORM_CGL) != 0) handle = gst_gl_context_cocoa_get_current_context (); #endif #if GST_GL_HAVE_PLATFORM_WGL if (!handle && (context_type & GST_GL_PLATFORM_WGL) != 0) handle = gst_gl_context_wgl_get_current_context (); #endif #if GST_GL_HAVE_PLATFORM_EAGL if (!handle && (context_type & GST_GL_PLATFORM_EAGL) != 0) handle = gst_gl_context_eagl_get_current_context (); #endif if (!handle) GST_WARNING ("Could not retrieve current context"); return handle; } gpointer gst_gl_context_get_proc_address_with_platform (GstGLPlatform context_type, GstGLAPI gl_api, const gchar * name) { gpointer ret = NULL; #if GST_GL_HAVE_PLATFORM_GLX if (!ret && (context_type & GST_GL_PLATFORM_GLX) != 0) ret = gst_gl_context_glx_get_proc_address (gl_api, name); #endif #if GST_GL_HAVE_PLATFORM_EGL if (!ret && (context_type & GST_GL_PLATFORM_EGL) != 0) ret = gst_gl_context_egl_get_proc_address (gl_api, name); #endif #if GST_GL_HAVE_PLATFORM_WGL if (!ret && (context_type & GST_GL_PLATFORM_WGL) != 0) ret = gst_gl_context_wgl_get_proc_address (gl_api, name); #endif /* CGL and EAGL rely on the default impl */ if (!ret) ret = gst_gl_context_default_get_proc_address (gl_api, name); return ret; } /** * gst_gl_context_get_current_gl_api: * @platform: the #GstGLPlatform to retrieve the API for * @major: (out): (allow-none): the major version * @minor: (out): (allow-none): the minor version * * If an error occurs, @major and @minor aren't modified and %GST_GL_API_NONE is * returned. * * Returns: The version supported by the OpenGL context current in the calling * thread or %GST_GL_API_NONE * * Since: 1.6 */ GstGLAPI gst_gl_context_get_current_gl_api (GstGLPlatform platform, guint * major, guint * minor) { const GLubyte *(GSTGLAPI *GetString) (GLenum name); #if GST_GL_HAVE_OPENGL void (GSTGLAPI *GetIntegerv) (GLenum name, GLuint * n); #endif const gchar *version; gint maj, min, n; GstGLAPI ret = (1 << 31); _init_debug (); while (ret != GST_GL_API_NONE) { /* FIXME: attempt to delve into the platform specific GetProcAddress */ GetString = gst_gl_context_get_proc_address_with_platform (platform, ret, "glGetString"); #if GST_GL_HAVE_OPENGL GetIntegerv = gst_gl_context_get_proc_address_with_platform (platform, ret, "glGetIntegerv"); #endif if (!GetString) { goto next; } version = (const gchar *) GetString (GL_VERSION); if (!version) goto next; /* strlen (x.x) == 3 */ n = strlen (version); if (n < 3) goto next; if (g_strstr_len (version, 9, "OpenGL ES")) { /* strlen (OpenGL ES x.x) == 13 */ if (n < 13) goto next; sscanf (&version[10], "%d.%d", &maj, &min); if (maj <= 0 || min < 0) goto next; if (maj == 1) { ret = GST_GL_API_GLES1; break; } else if (maj == 2 || maj == 3) { ret = GST_GL_API_GLES2; break; } goto next; } else { sscanf (version, "%d.%d", &maj, &min); if (maj <= 0 || min < 0) goto next; #if GST_GL_HAVE_OPENGL if (GetIntegerv && (maj > 3 || (maj == 3 && min > 1))) { GLuint context_flags = 0; ret = GST_GL_API_NONE; GetIntegerv (GL_CONTEXT_PROFILE_MASK, &context_flags); if (context_flags & GL_CONTEXT_CORE_PROFILE_BIT) ret |= GST_GL_API_OPENGL3; if (context_flags & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) ret |= GST_GL_API_OPENGL; break; } #endif ret = GST_GL_API_OPENGL; break; } next: /* iterate through the apis */ ret >>= 1; } if (ret == GST_GL_API_NONE) return GST_GL_API_NONE; if (major) *major = maj; if (minor) *minor = min; return ret; } static void gst_gl_context_finalize (GObject * object) { GstGLContext *context = GST_GL_CONTEXT (object); if (context->window) { gst_gl_window_set_resize_callback (context->window, NULL, NULL, NULL); gst_gl_window_set_draw_callback (context->window, NULL, NULL, NULL); if (context->priv->alive) { GST_INFO_OBJECT (context, "send quit gl window loop"); gst_gl_window_quit (context->window); GST_INFO_OBJECT (context, "joining gl thread"); g_thread_join (context->priv->gl_thread); GST_INFO_OBJECT (context, "gl thread joined"); context->priv->gl_thread = NULL; } gst_gl_window_set_close_callback (context->window, NULL, NULL, NULL); gst_object_unref (context->window); } if (context->priv->sharegroup) _context_share_group_unref (context->priv->sharegroup); gst_object_unref (context->display); if (context->gl_vtable) { g_slice_free (GstGLFuncs, context->gl_vtable); context->gl_vtable = NULL; } g_mutex_clear (&context->priv->render_lock); g_cond_clear (&context->priv->create_cond); g_free (context->priv->gl_exts); g_weak_ref_clear (&context->priv->other_context_ref); GST_DEBUG_OBJECT (context, "End of finalize"); G_OBJECT_CLASS (gst_gl_context_parent_class)->finalize (object); } /** * gst_gl_context_activate: * @context: a #GstGLContext * @activate: %TRUE to activate, %FALSE to deactivate * * (De)activate the OpenGL context represented by this @context. * * In OpenGL terms, calls eglMakeCurrent or similar with this context and the * currently set window. See gst_gl_context_set_window() for details. * * Returns: Whether the activation succeeded * * Since: 1.4 */ gboolean gst_gl_context_activate (GstGLContext * context, gboolean activate) { GstGLContextClass *context_class; gboolean result; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_val_if_fail (context_class->activate != NULL, FALSE); GST_DEBUG_OBJECT (context, "activate:%d", activate); GST_OBJECT_LOCK (context); result = context_class->activate (context, activate); if (result && activate) { context->priv->active_thread = g_thread_self (); g_private_set (¤t_context_key, context); } else { context->priv->active_thread = NULL; g_private_set (¤t_context_key, NULL); } GST_OBJECT_UNLOCK (context); return result; } /** * gst_gl_context_get_thread: * @context: a #GstGLContext * * Returns: (transfer full): The #GThread, @context is current in or NULL * * Since: 1.6 */ GThread * gst_gl_context_get_thread (GstGLContext * context) { GThread *ret; GST_OBJECT_LOCK (context); ret = context->priv->active_thread; if (ret) g_thread_ref (ret); GST_OBJECT_UNLOCK (context); return ret; } /** * gst_gl_context_get_gl_api: * @context: a #GstGLContext * * Get the currently enabled OpenGL api. * * The currently available API may be limited by the #GstGLDisplay in use and/or * the #GstGLWindow chosen. * * Returns: the available OpenGL api * * Since: 1.4 */ GstGLAPI gst_gl_context_get_gl_api (GstGLContext * context) { GstGLContextClass *context_class; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), GST_GL_API_NONE); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_val_if_fail (context_class->get_gl_api != NULL, GST_GL_API_NONE); return context_class->get_gl_api (context); } /** * gst_gl_context_get_proc_address: * @context: a #GstGLContext * @name: an opengl function name * * Get a function pointer to a specified opengl function, @name. If the the * specific function does not exist, NULL is returned instead. * * Platform specfic functions (names starting 'egl', 'glX', 'wgl', etc) can also * be retrieved using this method. * * Returns: a function pointer or NULL * * Since: 1.4 */ gpointer gst_gl_context_get_proc_address (GstGLContext * context, const gchar * name) { gpointer ret; GstGLContextClass *context_class; GstGLAPI gl_api; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_val_if_fail (context_class->get_proc_address != NULL, NULL); gl_api = gst_gl_context_get_gl_api (context); ret = context_class->get_proc_address (gl_api, name); return ret; } gpointer gst_gl_context_default_get_proc_address (GstGLAPI gl_api, const gchar * name) { gpointer ret = NULL; /* First try to load symbol from the selected GL API for this context */ #if GST_GL_HAVE_GLES2 if (!ret && (gl_api & GST_GL_API_GLES2)) { g_once (&module_gles2_gonce, load_gles2_module, NULL); if (module_gles2) g_module_symbol (module_gles2, name, &ret); } #endif #if GST_GL_HAVE_OPENGL if (!ret && (gl_api & (GST_GL_API_OPENGL | GST_GL_API_OPENGL3))) { g_once (&module_opengl_gonce, load_opengl_module, NULL); if (module_opengl) g_module_symbol (module_opengl, name, &ret); } #endif /* Otherwise fall back to the current module */ g_once (&module_self_gonce, load_self_module, NULL); if (!ret) g_module_symbol (module_self, name, &ret); return ret; } /** * gst_gl_context_set_window: * @context: a #GstGLContext * @window: (transfer full): a #GstGLWindow * * Set's the current window on @context to @window. The window can only be * changed before gst_gl_context_create() has been called and the @window is not * already running. * * Returns: Whether the window was successfully updated * * Since: 1.4 */ gboolean gst_gl_context_set_window (GstGLContext * context, GstGLWindow * window) { g_return_val_if_fail (!GST_IS_GL_WRAPPED_CONTEXT (context), FALSE); GST_DEBUG_OBJECT (context, "window:%" GST_PTR_FORMAT, window); /* we can't change the window while we are running */ if (context->priv->alive) return FALSE; if (window) { if (gst_gl_window_is_running (window)) return FALSE; g_weak_ref_set (&window->context_ref, context); } if (context->window) gst_object_unref (context->window); context->window = window ? gst_object_ref (window) : NULL; return TRUE; } /** * gst_gl_context_get_window: * @context: a #GstGLContext * * Returns: the currently set window * * Since: 1.4 */ GstGLWindow * gst_gl_context_get_window (GstGLContext * context) { g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL); if (GST_IS_GL_WRAPPED_CONTEXT (context)) { GST_WARNING_OBJECT (context, "context is not toplevel, returning NULL"); return NULL; } _ensure_window (context); return gst_object_ref (context->window); } /** * gst_gl_context_can_share: * @context: a #GstGLContext * @other_context: another #GstGLContext * * Note: This will always fail for two wrapped #GstGLContext's * * Returns: whether @context and @other_context are able to share OpenGL * resources. * * Since: 1.6 */ gboolean gst_gl_context_can_share (GstGLContext * context, GstGLContext * other_context) { g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); g_return_val_if_fail (GST_IS_GL_CONTEXT (other_context), FALSE); /* check if the contexts are descendants or the root nodes are the same */ return context->priv->sharegroup != NULL && context->priv->sharegroup == other_context->priv->sharegroup; } /** * gst_gl_context_create: * @context: a #GstGLContext: * @other_context: (allow-none): a #GstGLContext to share OpenGL objects with * @error: (allow-none): a #GError * * Creates an OpenGL context in the current thread with the specified * @other_context as a context to share shareable OpenGL objects with. See the * OpenGL specification for what is shared between contexts. * * If an error occurs, and @error is not %NULL, then error will contain details * of the error and %FALSE will be returned. * * Should only be called once. * * Returns: whether the context could successfully be created * * Since: 1.4 */ gboolean gst_gl_context_create (GstGLContext * context, GstGLContext * other_context, GError ** error) { gboolean alive = FALSE; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); g_return_val_if_fail (!GST_IS_GL_WRAPPED_CONTEXT (context), FALSE); GST_DEBUG_OBJECT (context, " other_context:%" GST_PTR_FORMAT, other_context); _ensure_window (context); g_mutex_lock (&context->priv->render_lock); if (!context->priv->created) { g_weak_ref_set (&context->priv->other_context_ref, other_context); context->priv->error = error; if (other_context == NULL) context->priv->sharegroup = _context_share_group_new (); else context->priv->sharegroup = _context_share_group_ref (other_context->priv->sharegroup); context->priv->gl_thread = g_thread_new ("gstglcontext", (GThreadFunc) gst_gl_context_create_thread, context); while (!context->priv->created) g_cond_wait (&context->priv->create_cond, &context->priv->render_lock); GST_INFO_OBJECT (context, "gl thread created"); } alive = context->priv->alive; g_mutex_unlock (&context->priv->render_lock); return alive; } static gboolean _create_context_info (GstGLContext * context, GstGLAPI gl_api, gint * gl_major, gint * gl_minor, GError ** error) { const GstGLFuncs *gl; guint maj = 0, min = 0; GLenum gl_err = GL_NO_ERROR; const gchar *opengl_version = NULL; gl = context->gl_vtable; if (!gl->GetString || !gl->GetString (GL_VERSION)) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_FAILED, "glGetString not defined or returned invalid value"); return FALSE; } if (!gl->GetString (GL_SHADING_LANGUAGE_VERSION)) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_FAILED, "No GL shader support available"); return FALSE; } GST_INFO_OBJECT (context, "GL_VERSION: %s", GST_STR_NULL ((const gchar *) gl->GetString (GL_VERSION))); GST_INFO_OBJECT (context, "GL_SHADING_LANGUAGE_VERSION: %s", GST_STR_NULL ((const gchar *) gl->GetString (GL_SHADING_LANGUAGE_VERSION))); GST_INFO_OBJECT (context, "GL_VENDOR: %s", GST_STR_NULL ((const gchar *) gl->GetString (GL_VENDOR))); GST_INFO_OBJECT (context, "GL_RENDERER: %s", GST_STR_NULL ((const gchar *) gl->GetString (GL_RENDERER))); gl_err = gl->GetError (); if (gl_err != GL_NO_ERROR) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_FAILED, "glGetString error: 0x%x", gl_err); return FALSE; } opengl_version = (const gchar *) gl->GetString (GL_VERSION); if (opengl_version && gl_api & GST_GL_API_GLES2) /* gles starts with "OpenGL ES " */ opengl_version = &opengl_version[10]; if (opengl_version) sscanf (opengl_version, "%d.%d", &maj, &min); /* OpenGL > 1.2.0 */ if (gl_api & GST_GL_API_OPENGL || gl_api & GST_GL_API_OPENGL3) { if ((maj < 1) || (maj < 2 && maj >= 1 && min < 2)) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_OLD_LIBS, "OpenGL >= 1.2.0 required, found %u.%u", maj, min); return FALSE; } } if (gl_major) *gl_major = maj; if (gl_minor) *gl_minor = min; return TRUE; } static GstGLAPI _compiled_api (void) { GstGLAPI ret = GST_GL_API_NONE; #if GST_GL_HAVE_OPENGL ret |= GST_GL_API_OPENGL | GST_GL_API_OPENGL3; #endif #if GST_GL_HAVE_GLES2 ret |= GST_GL_API_GLES2; #endif return ret; } static void _unlock_create_thread (GstGLContext * context) { context->priv->created = TRUE; GST_INFO_OBJECT (context, "gl thread running"); g_cond_signal (&context->priv->create_cond); g_mutex_unlock (&context->priv->render_lock); } static GString * _build_extension_string (GstGLContext * context) { const GstGLFuncs *gl = context->gl_vtable; GString *ext_g_str = g_string_sized_new (1024); const gchar *ext_const_c_str = NULL; GLint i = 0; GLint n = 0; gl->GetIntegerv (GL_NUM_EXTENSIONS, &n); for (i = 0; i < n; i++) { ext_const_c_str = (const gchar *) gl->GetStringi (GL_EXTENSIONS, i); if (ext_const_c_str) g_string_append_printf (ext_g_str, "%s ", ext_const_c_str); } return ext_g_str; } //gboolean //gst_gl_context_create (GstGLContext * context, GstGLContext * other_context, GError ** error) static gpointer gst_gl_context_create_thread (GstGLContext * context) { GstGLContextClass *context_class; GstGLWindowClass *window_class; GstGLAPI compiled_api, user_api, gl_api, display_api; gchar *api_string; gchar *compiled_api_s; gchar *user_api_s; gchar *display_api_s; const gchar *user_choice; GError **error; GstGLContext *other_context; g_mutex_lock (&context->priv->render_lock); GST_DEBUG_OBJECT (context, "Creating thread"); error = context->priv->error; other_context = g_weak_ref_get (&context->priv->other_context_ref); context_class = GST_GL_CONTEXT_GET_CLASS (context); window_class = GST_GL_WINDOW_GET_CLASS (context->window); display_api = gst_gl_display_get_gl_api_unlocked (context->display); if (display_api == GST_GL_API_NONE) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_WRONG_API, "Cannot create context with satisfying requested apis " "(display has no GL api!)"); goto failure; } if (window_class->open) { if (!window_class->open (context->window, error)) { GST_WARNING_OBJECT (context, "Failed to open window"); g_assert (error == NULL || *error != NULL); goto failure; } } compiled_api = _compiled_api (); compiled_api_s = gst_gl_api_to_string (compiled_api); user_choice = g_getenv ("GST_GL_API"); user_api = gst_gl_api_from_string (user_choice); user_api_s = gst_gl_api_to_string (user_api); display_api_s = gst_gl_api_to_string (display_api); if ((user_api & compiled_api & display_api) == GST_GL_API_NONE) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_WRONG_API, "Cannot create context with the user requested api (%s). " "We have support for (%s), display api (%s)", user_api_s, compiled_api_s, display_api_s); g_free (user_api_s); g_free (compiled_api_s); g_free (display_api_s); goto failure; } if (context_class->choose_format && !context_class->choose_format (context, error)) { GST_WARNING_OBJECT (context, "Failed to choose format"); g_assert (error == NULL || *error != NULL); g_free (compiled_api_s); g_free (user_api_s); g_free (display_api_s); goto failure; } GST_INFO_OBJECT (context, "Attempting to create opengl context. user chosen api(s) (%s), " "compiled api support (%s) display api (%s)", user_api_s, compiled_api_s, display_api_s); if (!context_class->create_context (context, compiled_api & user_api & display_api, other_context, error)) { GST_WARNING_OBJECT (context, "Failed to create context"); g_assert (error == NULL || *error != NULL); g_free (compiled_api_s); g_free (user_api_s); g_free (display_api_s); goto failure; } GST_INFO_OBJECT (context, "created context"); if (!gst_gl_context_activate (context, TRUE)) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_RESOURCE_UNAVAILABLE, "Failed to activate the GL Context"); g_free (compiled_api_s); g_free (user_api_s); g_free (display_api_s); goto failure; } gl_api = gst_gl_context_get_gl_api (context); g_assert (gl_api != GST_GL_API_NONE && gl_api != GST_GL_API_ANY); api_string = gst_gl_api_to_string (gl_api); GST_INFO_OBJECT (context, "available GL APIs: %s", api_string); if (((compiled_api & gl_api & display_api) & user_api) == GST_GL_API_NONE) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_WRONG_API, "failed to create context, context " "could not provide correct api. user (%s), compiled (%s), context (%s)", user_api_s, compiled_api_s, api_string); g_free (api_string); g_free (compiled_api_s); g_free (user_api_s); g_free (display_api_s); goto failure; } g_free (api_string); g_free (compiled_api_s); g_free (user_api_s); g_free (display_api_s); GST_DEBUG_OBJECT (context, "Filling info"); if (!gst_gl_context_fill_info (context, error)) { g_assert (error == NULL || *error != NULL); goto failure; } context->priv->alive = TRUE; #if !defined(GST_DISABLE_GST_DEBUG) _gst_gl_debug_enable (context); #endif if (other_context) { GST_DEBUG_OBJECT (context, "Unreffing other_context %" GST_PTR_FORMAT, other_context); gst_object_unref (other_context); } /* unlocking of the render_lock happens when the * context's loop is running from inside that loop */ gst_gl_window_send_message_async (context->window, (GstGLWindowCB) _unlock_create_thread, context, NULL); gst_gl_window_run (context->window); GST_INFO_OBJECT (context, "loop exited"); g_mutex_lock (&context->priv->render_lock); context->priv->alive = FALSE; gst_gl_context_activate (context, FALSE); context_class->destroy_context (context); /* User supplied callback */ if (context->window->close) context->window->close (context->window->close_data); /* window specific shutdown */ if (window_class->close) { window_class->close (context->window); } context->priv->created = FALSE; g_mutex_unlock (&context->priv->render_lock); return NULL; failure: { if (other_context) gst_object_unref (other_context); /* A context that fails to be created is considered created but not alive * and will never be able to be alive as creation can't happen */ context->priv->created = TRUE; g_cond_signal (&context->priv->create_cond); g_mutex_unlock (&context->priv->render_lock); return NULL; } } /** * gst_gl_context_destroy: * @context: a #GstGLContext: * * Destroys an OpenGL context. * * Should only be called after gst_gl_context_create() has been successfully * called for this context. * * Since: 1.6 */ void gst_gl_context_destroy (GstGLContext * context) { GstGLContextClass *context_class; g_return_if_fail (GST_IS_GL_CONTEXT (context)); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_if_fail (context_class->destroy_context != NULL); context_class->destroy_context (context); } /** * gst_gl_context_fill_info: * @context: a #GstGLContext: * * Fills @context's info (version, extensions, vtable, etc) from the GL * context in the current thread. Typically used with wrapped contexts to * allow wrapped contexts to be used as regular #GstGLContext's. * * Since: 1.6 */ gboolean gst_gl_context_fill_info (GstGLContext * context, GError ** error) { GstGLFuncs *gl; GString *ext_g_str = NULL; const gchar *ext_const_c_str = NULL; GstGLAPI gl_api; gboolean ret; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); g_return_val_if_fail (context->priv->active_thread == g_thread_self (), FALSE); gl = context->gl_vtable; gl_api = gst_gl_context_get_gl_api (context); gl->GetError = gst_gl_context_get_proc_address (context, "glGetError"); gl->GetString = gst_gl_context_get_proc_address (context, "glGetString"); gl->GetStringi = gst_gl_context_get_proc_address (context, "glGetStringi"); gl->GetIntegerv = gst_gl_context_get_proc_address (context, "glGetIntegerv"); if (!gl->GetError || !gl->GetString) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_FAILED, "could not GetProcAddress core opengl functions"); goto failure; } /* gl api specific code */ ret = _create_context_info (context, gl_api, &context->priv->gl_major, &context->priv->gl_minor, error); if (!ret) { g_assert (error == NULL || *error != NULL); goto failure; } /* GL core contexts and GLES3 */ if (gl->GetIntegerv && gl->GetStringi && context->priv->gl_major >= 3) ext_g_str = _build_extension_string (context); if (ext_g_str && ext_g_str->len) { GST_DEBUG_OBJECT (context, "GL_EXTENSIONS: %s", ext_g_str->str); _gst_gl_feature_check_ext_functions (context, context->priv->gl_major, context->priv->gl_minor, ext_g_str->str); context->priv->gl_exts = g_string_free (ext_g_str, FALSE); } else { ext_const_c_str = (const gchar *) gl->GetString (GL_EXTENSIONS); if (!ext_const_c_str) ext_const_c_str = ""; GST_DEBUG_OBJECT (context, "GL_EXTENSIONS: %s", ext_const_c_str); _gst_gl_feature_check_ext_functions (context, context->priv->gl_major, context->priv->gl_minor, ext_const_c_str); context->priv->gl_exts = g_strdup (ext_const_c_str); } if (gl_api & GST_GL_API_OPENGL3 && !gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 4, 1) && !gst_gl_check_extension ("GL_ARB_ES2_compatibility", context->priv->gl_exts)) { g_set_error (error, GST_GL_CONTEXT_ERROR, GST_GL_CONTEXT_ERROR_FAILED, "An opengl3 context created but the required ES2 compatibility was not found"); goto failure; } /* Does not implement OES_vertex_array_object properly, see * https://bugzilla.gnome.org/show_bug.cgi?id=750185 */ if (g_strcmp0 ((const gchar *) gl->GetString (GL_VENDOR), "Imagination Technologies") == 0 && g_strcmp0 ((const gchar *) gl->GetString (GL_RENDERER), "PowerVR SGX 544MP") == 0) { gl->GenVertexArrays = NULL; gl->DeleteVertexArrays = NULL; gl->BindVertexArray = NULL; gl->IsVertexArray = NULL; } return TRUE; failure: return FALSE; } /** * gst_gl_context_get_gl_context: * @context: a #GstGLContext: * * Gets the backing OpenGL context used by @context. * * Returns: The platform specific backing OpenGL context * * Since: 1.4 */ guintptr gst_gl_context_get_gl_context (GstGLContext * context) { GstGLContextClass *context_class; guintptr result; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), 0); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_val_if_fail (context_class->get_gl_context != NULL, 0); result = context_class->get_gl_context (context); return result; } /** * gst_gl_context_get_gl_platform: * @context: a #GstGLContext: * * Gets the OpenGL platform that used by @context. * * Returns: The platform specific backing OpenGL context * * Since: 1.4 */ GstGLPlatform gst_gl_context_get_gl_platform (GstGLContext * context) { GstGLContextClass *context_class; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), 0); context_class = GST_GL_CONTEXT_GET_CLASS (context); g_return_val_if_fail (context_class->get_gl_platform != NULL, 0); return context_class->get_gl_platform (context); } /** * gst_gl_context_get_display: * @context: a #GstGLContext: * * Returns: the #GstGLDisplay associated with this @context * * Since: 1.4 */ GstGLDisplay * gst_gl_context_get_display (GstGLContext * context) { g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL); return gst_object_ref (context->display); } typedef struct { GstGLContext *context; GstGLContextThreadFunc func; gpointer data; } RunGenericData; static void _gst_gl_context_thread_run_generic (RunGenericData * data) { GST_TRACE_OBJECT (data->context, "running function:%p data:%p", data->func, data->data); data->func (data->context, data->data); } /** * gst_gl_context_thread_add: * @context: a #GstGLContext * @func: a #GstGLContextThreadFunc * @data: (closure): user data to call @func with * * Execute @func in the OpenGL thread of @context with @data * * MT-safe * * Since: 1.4 */ void gst_gl_context_thread_add (GstGLContext * context, GstGLContextThreadFunc func, gpointer data) { GstGLWindow *window; RunGenericData rdata; g_return_if_fail (GST_IS_GL_CONTEXT (context)); g_return_if_fail (func != NULL); if (GST_IS_GL_WRAPPED_CONTEXT (context)) g_return_if_fail (context->priv->active_thread == g_thread_self ()); if (context->priv->active_thread == g_thread_self ()) { func (context, data); return; } rdata.context = context; rdata.data = data; rdata.func = func; window = gst_gl_context_get_window (context); gst_gl_window_send_message (window, GST_GL_WINDOW_CB (_gst_gl_context_thread_run_generic), &rdata); gst_object_unref (window); } /** * gst_gl_context_get_gl_version: * @context: a #GstGLContext * @maj: (out): resulting major version * @min: (out): resulting minor version * * Returns the OpenGL version implemented by @context. See * gst_gl_context_get_gl_api() for retreiving the OpenGL api implemented by * @context. * * Since: 1.4 */ void gst_gl_context_get_gl_version (GstGLContext * context, gint * maj, gint * min) { g_return_if_fail (GST_IS_GL_CONTEXT (context)); g_return_if_fail (maj != NULL && min != NULL); if (maj) *maj = context->priv->gl_major; if (min) *min = context->priv->gl_minor; } /** * gst_gl_context_check_gl_version: * @context: a #GstGLContext * @api: api type required * @maj: major version required * @min: minor version required * * Returns: whether OpenGL context implements the required api and specified * version. * * Since: 1.4 */ gboolean gst_gl_context_check_gl_version (GstGLContext * context, GstGLAPI api, gint maj, gint min) { g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); if (maj > context->priv->gl_major) return FALSE; if ((gst_gl_context_get_gl_api (context) & api) == GST_GL_API_NONE) return FALSE; if (maj < context->priv->gl_major) return TRUE; if (min > context->priv->gl_minor) return FALSE; return TRUE; } /** * gst_gl_context_check_feature: * @context: a #GstGLContext * @feature: a platform specific feature * * Some features require that the context be created before it is possible to * determine their existence and so will fail if that is not the case. * * Returns: Whether @feature is supported by @context * * Since: 1.4 */ gboolean gst_gl_context_check_feature (GstGLContext * context, const gchar * feature) { GstGLContextClass *context_class; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); g_return_val_if_fail (feature != NULL, FALSE); context_class = GST_GL_CONTEXT_GET_CLASS (context); if (g_strstr_len (feature, 3, "GL_")) return gst_gl_check_extension (feature, context->priv->gl_exts); if (!context_class->check_feature) return FALSE; return context_class->check_feature (context, feature); } /** * gst_gl_context_get_current: * * See also gst_gl_context_activate(). * * Returns: the #GstGLContext active in the current thread or %NULL * * Since: 1.6 */ GstGLContext * gst_gl_context_get_current (void) { return g_private_get (¤t_context_key); } /** * gst_gl_context_is_shared: * @context: a #GstGLContext * * Returns: Whether the #GstGLContext has been shared with another #GstGLContext * * Since: 1.8 */ gboolean gst_gl_context_is_shared (GstGLContext * context) { g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE); if (GST_IS_GL_WRAPPED_CONTEXT (context)) g_return_val_if_fail (context->priv->active_thread, FALSE); else g_return_val_if_fail (context->priv->alive, FALSE); return _context_share_group_is_shared (context->priv->sharegroup); } /** * gst_gl_context_set_shared_with: * @context: a wrapped #GstGLContext * @share: another #GstGLContext * * Will internally set @context as shared with @share * * Since: 1.8 */ void gst_gl_context_set_shared_with (GstGLContext * context, GstGLContext * share) { g_return_if_fail (GST_IS_GL_CONTEXT (context)); g_return_if_fail (GST_IS_GL_CONTEXT (share)); g_return_if_fail (!gst_gl_context_is_shared (context)); /* XXX: may be a little too strict */ g_return_if_fail (GST_IS_GL_WRAPPED_CONTEXT (context)); if (context->priv->sharegroup) _context_share_group_unref (context->priv->sharegroup); context->priv->sharegroup = _context_share_group_ref (share->priv->sharegroup); } static GstGLAPI gst_gl_wrapped_context_get_gl_api (GstGLContext * context) { GstGLWrappedContext *context_wrap = GST_GL_WRAPPED_CONTEXT (context); return context_wrap->available_apis; } static guintptr gst_gl_wrapped_context_get_gl_context (GstGLContext * context) { GstGLWrappedContext *context_wrap = GST_GL_WRAPPED_CONTEXT (context); return context_wrap->handle; } static GstGLPlatform gst_gl_wrapped_context_get_gl_platform (GstGLContext * context) { GstGLWrappedContext *context_wrap = GST_GL_WRAPPED_CONTEXT (context); return context_wrap->platform; } static gboolean gst_gl_wrapped_context_activate (GstGLContext * context, gboolean activate) { if (activate) context->priv->gl_thread = g_thread_self (); else context->priv->gl_thread = NULL; return TRUE; } static void gst_gl_wrapped_context_class_init (GstGLWrappedContextClass * klass) { GstGLContextClass *context_class = (GstGLContextClass *) klass; context_class->get_gl_context = GST_DEBUG_FUNCPTR (gst_gl_wrapped_context_get_gl_context); context_class->get_gl_api = GST_DEBUG_FUNCPTR (gst_gl_wrapped_context_get_gl_api); context_class->get_gl_platform = GST_DEBUG_FUNCPTR (gst_gl_wrapped_context_get_gl_platform); context_class->activate = GST_DEBUG_FUNCPTR (gst_gl_wrapped_context_activate); } static void gst_gl_wrapped_context_init (GstGLWrappedContext * context) { }