| /* |
| * # Copyright 2020 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <cairo.h> |
| #include <libdrm/drm_fourcc.h> |
| #include <librsvg/rsvg.h> |
| |
| #include <gst/allocators/gstdmabuf.h> |
| #include <gst/gl/gstglfuncs.h> |
| |
| #include "gstglsvgoverlay.h" |
| |
| #define GST_CAT_DEFAULT gst_gl_svg_overlay_debug |
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
| |
| #define DEBUG_INIT \ |
| GST_DEBUG_CATEGORY_INIT (gst_gl_svg_overlay_debug, \ |
| "glsvgoverlay", 0, "glsvgoverlay element"); |
| #define gst_gl_svg_overlay_parent_class parent_class |
| G_DEFINE_TYPE_WITH_CODE (GstGLSvgOverlay, gst_gl_svg_overlay, |
| GST_TYPE_GL_FILTER, DEBUG_INIT); |
| |
| #define USING_OPENGL3(context) (gst_gl_context_check_gl_version(context, \ |
| GST_GL_API_OPENGL3, 3, 1)) |
| #define USING_GLES2(context) (gst_gl_context_check_gl_version (context, \ |
| GST_GL_API_GLES2, 2, 0)) |
| #define USING_GLES3(context) (gst_gl_context_check_gl_version (context, \ |
| GST_GL_API_GLES2, 3, 0)) |
| |
| enum |
| { |
| PROP_0, |
| PROP_DATA, |
| PROP_SVG, |
| PROP_SYNC, |
| PROP_ION, |
| }; |
| |
| #define DEFAULT_PROP_SYNC TRUE |
| #define DEFAULT_PROP_ION TRUE |
| |
| #define QNAME "GstGLSvgOverlayImage" |
| |
| static void gst_gl_svg_overlay_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_gl_svg_overlay_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static GstStateChangeReturn gst_gl_svg_overlay_change_state ( |
| GstElement * element, GstStateChange transition); |
| static gboolean gst_gl_svg_overlay_gl_start (GstGLBaseFilter * base_filter); |
| static void gst_gl_svg_overlay_gl_stop (GstGLBaseFilter * base_filter); |
| static gboolean gst_gl_svg_overlay_gl_set_caps (GstGLBaseFilter * base_filter, |
| GstCaps * incaps, GstCaps * outcaps); |
| static GstFlowReturn gst_gl_svg_overlay_transform (GstBaseTransform * bt, |
| GstBuffer * inbuf, GstBuffer * outbuf); |
| static gboolean gst_gl_svg_overlay_filter_texture (GstGLFilter * filter, |
| GstGLMemory * in_tex, GstGLMemory * out_tex); |
| static gboolean gst_gl_svg_overlay_set_svg (GstGLSvgOverlay * overlay, |
| const gchar * svg, GstClockTime pts); |
| static void gst_gl_svg_overlay_finalize (GObject * object); |
| static void gst_gl_svg_overlay_task (gpointer data, gpointer user_data); |
| static void gst_gl_svg_overlay_task (); |
| |
| enum |
| { |
| SIGNAL_SET_SVG, |
| LAST_SIGNAL |
| }; |
| |
| static guint gst_gl_svg_overlay_signals[LAST_SIGNAL] = { 0 }; |
| |
| static void |
| gst_gl_svg_overlay_class_init (GstGLSvgOverlayClass * klass) |
| { |
| gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass)); |
| |
| G_OBJECT_CLASS (klass)->set_property = gst_gl_svg_overlay_set_property; |
| G_OBJECT_CLASS (klass)->get_property = gst_gl_svg_overlay_get_property; |
| G_OBJECT_CLASS (klass)->finalize = gst_gl_svg_overlay_finalize; |
| |
| /* Aliased property for compability reasons. */ |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DATA, |
| g_param_spec_string ("data", "data", "SVG data", "", |
| G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SVG, |
| g_param_spec_string ("svg", "svg", "SVG data", "", |
| G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SYNC, |
| g_param_spec_boolean ("sync", "sync", |
| "Require one SVG overlay per frame, or use the last" |
| "available.", |
| DEFAULT_PROP_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ION, |
| g_param_spec_boolean ("ion", "ion", |
| "Use the ION allocator for overlays if available.", |
| DEFAULT_PROP_ION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_set_metadata (GST_ELEMENT_CLASS (klass), |
| "OpenGL SVG overlay", |
| "Filter/Effect/Video", |
| "Overlay GL video texture with a SVG image", |
| "Coral <coral-support@google.com>"); |
| GST_ELEMENT_CLASS (klass)->change_state = gst_gl_svg_overlay_change_state; |
| |
| gst_gl_svg_overlay_signals[SIGNAL_SET_SVG] = |
| g_signal_new ("set-svg", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstGLSvgOverlayClass, set_svg), |
| NULL, NULL, NULL, |
| G_TYPE_BOOLEAN, 2, G_TYPE_STRING, G_TYPE_UINT64); |
| klass->set_svg = gst_gl_svg_overlay_set_svg; |
| |
| GST_BASE_TRANSFORM_CLASS (klass)->passthrough_on_same_caps = FALSE; |
| GST_BASE_TRANSFORM_CLASS (klass)->transform = gst_gl_svg_overlay_transform; |
| |
| GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_svg_overlay_gl_start; |
| GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_svg_overlay_gl_stop; |
| GST_GL_BASE_FILTER_CLASS (klass)->gl_set_caps = |
| gst_gl_svg_overlay_gl_set_caps; |
| GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = |
| GST_GL_API_GLES2 | GST_GL_API_OPENGL3; |
| GST_GL_FILTER_CLASS (klass)->filter_texture = |
| gst_gl_svg_overlay_filter_texture; |
| } |
| |
| static void |
| gst_gl_svg_overlay_init (GstGLSvgOverlay * overlay) |
| { |
| GST_DEBUG_OBJECT (overlay, "init"); |
| overlay->sync = DEFAULT_PROP_SYNC; |
| overlay->use_ion = DEFAULT_PROP_ION; |
| overlay->num_tasks = 0; |
| overlay->started = FALSE; |
| overlay->shader = NULL; |
| overlay->thread_pool = NULL; |
| overlay->buffer_pool = NULL; |
| overlay->svg_queue = NULL; |
| overlay->gl_queue = NULL; |
| overlay->next_pts = 0; |
| overlay->current = NULL; |
| g_mutex_init (&overlay->mutex); |
| g_cond_init (&overlay->cond); |
| |
| /* ION allocator is in the "coral" plugin, load it. */ |
| gst_object_unref (gst_plugin_load_by_name ("coral")); |
| overlay->ion_allocator = gst_allocator_find ("ion"); |
| if (overlay->ion_allocator) { |
| GST_INFO_OBJECT (overlay, "Have ION allocator"); |
| } else { |
| GST_INFO_OBJECT (overlay, "ION allocator not present/usable"); |
| } |
| } |
| |
| static void |
| gst_gl_svg_overlay_finalize (GObject * object) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (object); |
| |
| GST_DEBUG_OBJECT (overlay, "finalize"); |
| |
| if (overlay->ion_allocator) { |
| gst_object_unref (overlay->ion_allocator); |
| overlay->ion_allocator = NULL; |
| } |
| if (overlay->buffer_pool) { |
| gst_buffer_pool_set_active (overlay->buffer_pool, FALSE); |
| gst_object_unref (overlay->buffer_pool); |
| overlay->buffer_pool = NULL; |
| } |
| |
| g_mutex_clear (&overlay->mutex); |
| g_cond_clear (&overlay->cond); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_gl_svg_overlay_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (object); |
| |
| switch (prop_id) { |
| case PROP_DATA: |
| case PROP_SVG: |
| gst_gl_svg_overlay_set_svg (GST_GL_SVG_OVERLAY (object), |
| g_value_get_string (value), GST_CLOCK_TIME_NONE); |
| break; |
| case PROP_SYNC: |
| g_mutex_lock (&overlay->mutex); |
| overlay->sync = g_value_get_boolean (value); |
| g_mutex_unlock (&overlay->mutex); |
| break; |
| case PROP_ION: |
| overlay->use_ion = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_gl_svg_overlay_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (object); |
| |
| switch (prop_id) { |
| case PROP_SYNC: |
| g_mutex_lock (&overlay->mutex); |
| g_value_set_boolean (value, overlay->sync); |
| g_mutex_unlock (&overlay->mutex); |
| break; |
| case PROP_ION: |
| g_value_set_boolean (value, overlay->use_ion); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| _free_egl_image_texture_gl (GstGLContext * context, gpointer data) |
| { |
| EglImageTexture *tex = (EglImageTexture *) data; |
| const GstGLFuncs *gl = context->gl_vtable; |
| |
| if (!tex || !tex->tex) { |
| return; |
| } |
| |
| gl->DeleteTextures (1, &tex->tex); |
| tex->tex = 0; |
| } |
| |
| static void |
| _free_egl_image_texture (gpointer data) |
| { |
| EglImageTexture *tex = (EglImageTexture *) data; |
| EGLBoolean (*_eglDestroyImage) (EGLDisplay dpy, EGLImageKHR image); |
| GstGLDisplayEGL *gst_egl_display; |
| EGLDisplay egl_display; |
| GstGLSvgOverlay *overlay = tex->overlay; |
| GstGLContext *context = overlay->context; |
| |
| gst_gl_context_thread_add (context, _free_egl_image_texture_gl, tex); |
| |
| gst_egl_display = gst_gl_display_egl_from_gl_display (context->display); |
| egl_display = (EGLDisplay) gst_gl_display_get_handle (GST_GL_DISPLAY ( |
| gst_egl_display)); |
| gst_object_unref (gst_egl_display); |
| |
| _eglDestroyImage = gst_gl_context_get_proc_address (context, |
| "eglDestroyImage"); |
| if (!_eglDestroyImage) { |
| _eglDestroyImage = gst_gl_context_get_proc_address (context, |
| "eglDestroyImageKHR"); |
| if (!_eglDestroyImage) { |
| g_free (tex); |
| GST_ERROR_OBJECT (overlay, "eglDestroyImage not exposed"); |
| return; |
| } |
| } |
| |
| if (!_eglDestroyImage (egl_display, tex->image)) { |
| GST_ERROR_OBJECT (overlay, "free %p failed", tex); |
| } |
| |
| g_free (tex); |
| } |
| |
| static EglImageTexture * |
| _get_cached_egl_image_texture (GstGLSvgOverlay * overlay, GstBuffer * buf) { |
| EglImageTexture *tex = (EglImageTexture *) gst_mini_object_get_qdata ( |
| GST_MINI_OBJECT (buf), g_quark_from_static_string (QNAME)); |
| return tex; |
| } |
| |
| static void |
| _create_egl_image_texture_gl (GstGLContext * context, gpointer data) |
| { |
| EglImageTexture *tex = (EglImageTexture *) data; |
| GstGLSvgOverlay *overlay = tex->overlay; |
| const GstGLFuncs *gl = context->gl_vtable; |
| |
| g_assert (!tex->tex); |
| gl->GenTextures (1, &tex->tex); |
| gl->BindTexture (GL_TEXTURE_2D, tex->tex); |
| gl->TexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gl->TexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl->EGLImageTargetTexture2D (GL_TEXTURE_2D, tex->image); |
| } |
| |
| static EglImageTexture * |
| _create_egl_image_texture (GstGLSvgOverlay * overlay, GstBuffer * buf) { |
| /* EglImageTexture is cached in GstBuffer qdata. */ |
| EglImageTexture *tex; |
| EGLImageKHR *image; |
| EGLDisplay egl_display; |
| GstGLDisplayEGL *gst_egl_display; |
| GstMemory *mem; |
| gint atti = 0, fourcc, i, fd; |
| guintptr attribs[16]; |
| EGLImageKHR (*_eglCreateImage) (EGLDisplay dpy, EGLContext ctx, |
| EGLenum target, EGLClientBuffer buffer, const EGLint * attribs); |
| GstGLContext *context = overlay->context; |
| |
| tex = _get_cached_egl_image_texture (overlay, buf); |
| if (tex) { |
| return tex; |
| } |
| |
| mem = gst_buffer_peek_memory (buf, 0); |
| g_assert (gst_is_dmabuf_memory (mem)); |
| fd = gst_dmabuf_memory_get_fd (mem); |
| |
| gst_egl_display = gst_gl_display_egl_from_gl_display (context->display); |
| egl_display = (EGLDisplay) gst_gl_display_get_handle (GST_GL_DISPLAY ( |
| gst_egl_display)); |
| gst_object_unref (gst_egl_display); |
| |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| fourcc = DRM_FORMAT_ARGB8888; |
| #else |
| fourcc = DRM_FORMAT_ABGR8888; |
| #endif |
| |
| _eglCreateImage = gst_gl_context_get_proc_address (context, |
| "eglCreateImage"); |
| if (!_eglCreateImage) { |
| _eglCreateImage = gst_gl_context_get_proc_address (context, |
| "eglCreateImageKHR"); |
| } |
| g_assert (_eglCreateImage); |
| |
| GST_DEBUG_OBJECT (overlay, "create image fd=%d w=%d h=%d s=%d", fd, |
| GST_VIDEO_INFO_WIDTH (&overlay->info), |
| GST_VIDEO_INFO_HEIGHT (&overlay->info), |
| GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0)); |
| |
| attribs[atti++] = EGL_WIDTH; |
| attribs[atti++] = GST_VIDEO_INFO_WIDTH (&overlay->info); |
| attribs[atti++] = EGL_HEIGHT; |
| attribs[atti++] = GST_VIDEO_INFO_HEIGHT (&overlay->info); |
| attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; |
| attribs[atti++] = fourcc; |
| attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; |
| attribs[atti++] = fd; |
| attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; |
| attribs[atti++] = 0; |
| attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; |
| attribs[atti++] = GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0); |
| attribs[atti] = EGL_NONE; |
| |
| image = _eglCreateImage (egl_display, EGL_NO_CONTEXT, |
| EGL_LINUX_DMA_BUF_EXT, NULL, (const EGLint *) attribs); |
| GST_DEBUG_OBJECT (overlay, "eglCreateImage %p", image); |
| g_assert (image != EGL_NO_IMAGE_KHR); |
| |
| tex = g_malloc0 (sizeof (EglImageTexture)); |
| tex->overlay = overlay; |
| tex->image = image; |
| |
| gst_mini_object_set_qdata (GST_MINI_OBJECT (buf), |
| g_quark_from_static_string (QNAME), tex, _free_egl_image_texture); |
| |
| gst_gl_context_thread_add (context, _create_egl_image_texture_gl, tex); |
| |
| return tex; |
| } |
| |
| static gint _task_data_compare (gconstpointer a, gconstpointer b, |
| gpointer user_data) |
| { |
| const TaskData *t1 = a; |
| const TaskData *t2 = b; |
| |
| if (t1->pts < t2->pts) { |
| return -1; |
| } else if (t1->pts > t2->pts) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static void |
| _task_data_map (TaskData * tdata) |
| { |
| gboolean ret; |
| if (tdata->map.data) { |
| return; |
| } |
| |
| ret = gst_buffer_map (tdata->buf, &tdata->map, GST_MAP_READ | GST_MAP_WRITE); |
| g_assert (ret); |
| } |
| |
| static void |
| _task_data_unmap (TaskData * tdata) |
| { |
| if (!tdata->map.data) { |
| return; |
| } |
| |
| gst_buffer_unmap (tdata->buf, &tdata->map); |
| memset (&tdata->map, 0, sizeof (tdata->map)); |
| } |
| |
| static void |
| _task_data_free_gl (GstGLContext * context, gpointer data) |
| { |
| TaskData *tdata = data; |
| const GstGLFuncs *gl = context->gl_vtable; |
| |
| if (!tdata || !tdata->tex) { |
| return; |
| } |
| |
| gl->DeleteTextures (1, &tdata->tex); |
| tdata->tex = 0; |
| } |
| |
| static void |
| _task_data_free (gpointer data) |
| { |
| TaskData *tdata = data; |
| |
| if (!tdata) { |
| return; |
| } |
| |
| _task_data_unmap (tdata); |
| gst_gl_context_thread_add (tdata->overlay->context, _task_data_free_gl, |
| tdata); |
| |
| gst_buffer_replace (&tdata->buf, NULL); |
| g_free (tdata->svg); |
| g_free (tdata); |
| } |
| |
| static void |
| _task_data_upload_gl (GstGLContext * context, gpointer data) |
| { |
| TaskData * tdata = data; |
| GstBuffer *buf = tdata->buf; |
| GstGLSyncMeta *sync_meta = gst_buffer_get_gl_sync_meta (buf); |
| GstGLSvgOverlay *overlay = tdata->overlay; |
| const GstGLFuncs *gl = context->gl_vtable; |
| |
| /* TODO: handle non packed stride */ |
| |
| g_assert (!tdata->egl_tex); |
| |
| /* Copy pixels to GPU. */ |
| gl->GenTextures (1, &tdata->tex); |
| gl->BindTexture (GL_TEXTURE_2D, tdata->tex); |
| gl->TexImage2D(GL_TEXTURE_2D, 0, overlay->internal_format, |
| GST_VIDEO_INFO_WIDTH (&overlay->info), |
| GST_VIDEO_INFO_HEIGHT (&overlay->info), |
| 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, tdata->map.data); |
| gl->GenerateMipmap (GL_TEXTURE_2D); |
| |
| if (!sync_meta) { |
| sync_meta = gst_buffer_add_gl_sync_meta (context, buf); |
| } |
| gst_gl_sync_meta_set_sync_point (sync_meta, context); |
| } |
| |
| static void |
| _task_push_locked (GstGLSvgOverlay *overlay, gpointer data) |
| { |
| g_thread_pool_push (overlay->thread_pool, data, NULL); |
| overlay->num_tasks++; |
| } |
| |
| static GstStateChangeReturn |
| gst_gl_svg_overlay_change_state (GstElement * element, |
| GstStateChange transition) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| g_mutex_lock (&overlay->mutex); |
| overlay->svg_queue = g_queue_new (); |
| overlay->gl_queue = g_queue_new (); |
| overlay->thread_pool = g_thread_pool_new (gst_gl_svg_overlay_task, |
| overlay, g_get_num_processors (), FALSE, NULL); |
| g_mutex_unlock (&overlay->mutex); |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| g_mutex_lock (&overlay->mutex); |
| overlay->started = FALSE; |
| g_cond_broadcast (&overlay->cond); |
| while (overlay->num_tasks) { |
| g_cond_wait (&overlay->cond, &overlay->mutex); |
| } |
| g_thread_pool_free (overlay->thread_pool, FALSE, FALSE); |
| overlay->thread_pool = NULL; |
| g_queue_free_full (overlay->svg_queue, _task_data_free); |
| overlay->svg_queue = NULL; |
| g_queue_free_full (overlay->gl_queue, _task_data_free); |
| overlay->gl_queue = NULL; |
| _task_data_free (overlay->current); |
| overlay->current = NULL; |
| if (overlay->buffer_pool) { |
| gst_buffer_pool_set_active (overlay->buffer_pool, FALSE); |
| gst_object_unref (overlay->buffer_pool); |
| overlay->buffer_pool = NULL; |
| } |
| g_mutex_unlock (&overlay->mutex); |
| break; |
| default: |
| break; |
| } |
| |
| return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| } |
| |
| static gboolean |
| gst_gl_svg_overlay_gl_start (GstGLBaseFilter * base_filter) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (base_filter); |
| GstGLFilter *filter = GST_GL_FILTER (base_filter); |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (overlay, "gl_start"); |
| |
| overlay->context = gst_gl_context_new (gst_gl_context_get_display ( |
| base_filter->context)); |
| ret = gst_gl_context_create (overlay->context, base_filter->context, NULL); |
| g_assert (ret); |
| overlay->shader = gst_gl_shader_new_default (overlay->context, NULL); |
| |
| filter->draw_attr_position_loc = |
| gst_gl_shader_get_attribute_location (overlay->shader, "a_position"); |
| filter->draw_attr_texture_loc = |
| gst_gl_shader_get_attribute_location (overlay->shader, "a_texcoord"); |
| |
| if (USING_GLES2 (overlay->context) || USING_GLES3 (overlay->context)) { |
| gboolean bgra = gst_gl_context_check_feature (overlay->context, |
| "GL_EXT_texture_format_BGRA8888"); |
| if (bgra) { |
| GST_DEBUG_OBJECT (overlay, "GL_EXT_texture_format_BGRA8888 supported"); |
| } else { |
| GST_ERROR_OBJECT (overlay, "GL_EXT_texture_format_BGRA8888 unsupported"); |
| return FALSE; |
| } |
| |
| overlay->internal_format = GL_BGRA_EXT; |
| } else if (USING_OPENGL3 (overlay->context)) { |
| /* BGRA8888 supported */ |
| overlay->internal_format = GL_RGBA; |
| } else { |
| g_assert_not_reached (); |
| } |
| |
| if (overlay->use_ion && overlay->ion_allocator) { |
| /* TODO: Init check the dma-buf import being supported. */ |
| } else { |
| overlay->use_ion = FALSE; |
| } |
| |
| return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter); |
| } |
| |
| static void |
| gst_gl_svg_overlay_gl_stop (GstGLBaseFilter * base_filter) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (base_filter); |
| GST_DEBUG_OBJECT (overlay, "gl_stop"); |
| |
| _task_data_free (overlay->current); |
| overlay->current = NULL; |
| |
| if (overlay->shader) { |
| gst_object_unref (overlay->shader); |
| overlay->shader = NULL; |
| } |
| |
| GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter); |
| |
| gst_object_unref (GST_OBJECT (overlay->context)); |
| overlay->context = NULL; |
| } |
| |
| static gboolean |
| gst_gl_svg_overlay_gl_set_caps (GstGLBaseFilter * base_filter, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| GstGLFilter *filter = GST_GL_FILTER (base_filter); |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (base_filter); |
| GstStructure *config; |
| GstCaps *caps; |
| gsize size; |
| gboolean ret; |
| |
| ret = GST_GL_BASE_FILTER_CLASS (parent_class)->gl_set_caps (base_filter, |
| incaps, outcaps); |
| |
| if (!ret) { |
| GST_ERROR_OBJECT (filter, "GstGLBaseFilter failed to start"); |
| return ret; |
| } |
| |
| caps = gst_caps_new_simple("video/x-raw", |
| "format", G_TYPE_STRING, "BGRA", |
| "width", G_TYPE_INT, GST_VIDEO_INFO_WIDTH (&filter->out_info), |
| "height", G_TYPE_INT, GST_VIDEO_INFO_HEIGHT (&filter->out_info), |
| "framerate", GST_TYPE_FRACTION, 0, 1, |
| NULL); |
| gst_video_info_from_caps (&overlay->info, caps); |
| GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0) = |
| cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, |
| GST_VIDEO_INFO_WIDTH (&overlay->info)); |
| |
| if (GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0) != GST_VIDEO_INFO_WIDTH ( |
| &overlay->info) * 4) { |
| /* TODO: stride */ |
| GST_ERROR_OBJECT (overlay, "Unsupported stride"); |
| gst_caps_unref (caps); |
| return FALSE; |
| } |
| size = GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0) * |
| GST_VIDEO_INFO_HEIGHT (&overlay->info); |
| |
| g_mutex_lock (&overlay->mutex); |
| |
| if (overlay->buffer_pool) { |
| gst_buffer_pool_set_active (overlay->buffer_pool, FALSE); |
| gst_object_unref (overlay->buffer_pool); |
| } |
| GST_INFO_OBJECT (overlay, "New pool with max %u buffers ion=%d", |
| g_get_num_processors (), overlay->use_ion); |
| overlay->buffer_pool = gst_buffer_pool_new (); |
| config = gst_buffer_pool_get_config (overlay->buffer_pool); |
| gst_buffer_pool_config_set_params (config, caps, size, 0, |
| g_get_num_processors ()); |
| if (overlay->ion_allocator && overlay->use_ion) { |
| gst_buffer_pool_config_set_allocator (config, overlay->ion_allocator, NULL); |
| } |
| ret = gst_buffer_pool_set_config (overlay->buffer_pool, config); |
| g_assert (ret); |
| ret = gst_buffer_pool_set_active (overlay->buffer_pool, TRUE); |
| g_assert (ret); |
| gst_caps_unref (caps); |
| |
| overlay->started = TRUE; |
| |
| g_mutex_unlock (&overlay->mutex); |
| |
| GST_DEBUG_OBJECT (filter, "%ux%u px, stride %u bytes", |
| GST_VIDEO_INFO_WIDTH (&overlay->info), |
| GST_VIDEO_INFO_HEIGHT (&overlay->info), |
| GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0)); |
| |
| return ret; |
| } |
| |
| static void |
| gst_gl_svg_overlay_task (gpointer data, gpointer user_data) |
| { |
| GstGLFilter *filter = GST_GL_FILTER (user_data); |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (user_data); |
| TaskData *tdata = NULL; |
| GstClockTime start = gst_util_get_timestamp (); |
| gdouble elapsed_ms; |
| |
| /* TODO: Render SVG to dma-bufs when supported */ |
| |
| #define LOG_ELAPSED(s) \ |
| do { \ |
| elapsed_ms = ((gdouble) gst_util_get_timestamp () - start) / GST_MSECOND; \ |
| GST_LOG_OBJECT (overlay, "%s: %.2f", s, elapsed_ms); \ |
| } while (0) |
| |
| #define TASK_DONE(push) \ |
| do { \ |
| g_mutex_lock (&overlay->mutex); \ |
| if (push) { \ |
| if (overlay->started) { \ |
| _task_push_locked (overlay, tdata); \ |
| } else { \ |
| _task_data_free (tdata); \ |
| } \ |
| }\ |
| overlay->num_tasks--; \ |
| g_cond_broadcast (&overlay->cond); \ |
| g_mutex_unlock (&overlay->mutex); \ |
| return; \ |
| } while (0) |
| |
| if (data == overlay->svg_queue) { |
| g_mutex_lock (&overlay->mutex); |
| if (!overlay->sync) { |
| /* Draw only the latest SVG overlay, drop older. */ |
| while (g_queue_get_length (overlay->svg_queue) > 1) { |
| tdata = g_queue_pop_head (overlay->svg_queue); |
| _task_data_free (tdata); |
| tdata = NULL; |
| } |
| } |
| tdata = g_queue_pop_head (overlay->svg_queue); |
| |
| g_mutex_unlock (&overlay->mutex); |
| |
| TASK_DONE (TRUE); |
| } |
| |
| tdata = data; |
| |
| if (tdata->op == OP_ALLOC) { |
| GstBufferPool *pool; |
| GstFlowReturn ret; |
| |
| g_mutex_lock (&overlay->mutex); |
| pool = overlay->buffer_pool; |
| g_mutex_unlock (&overlay->mutex); |
| |
| g_assert (gst_buffer_pool_is_active (pool)); |
| |
| ret = gst_buffer_pool_acquire_buffer (pool, &tdata->buf, NULL); |
| g_assert (ret == GST_FLOW_OK); |
| |
| tdata->op = OP_DRAW; |
| TASK_DONE(tdata); |
| } |
| |
| if (tdata->op == OP_DRAW) { |
| cairo_t *cairo; |
| cairo_surface_t *surface; |
| RsvgHandle *handle; |
| GError *err = NULL; |
| gboolean ret; |
| |
| _task_data_map (tdata); |
| |
| memset (tdata->map.data, 0, tdata->map.size); |
| surface = cairo_image_surface_create_for_data (tdata->map.data, |
| CAIRO_FORMAT_ARGB32, GST_VIDEO_INFO_WIDTH (&overlay->info), |
| GST_VIDEO_INFO_HEIGHT (&overlay->info), |
| GST_VIDEO_INFO_PLANE_STRIDE (&overlay->info, 0)); |
| g_assert (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS); |
| cairo = cairo_create (surface); |
| g_assert (cairo_status (cairo) == CAIRO_STATUS_SUCCESS); |
| |
| handle = rsvg_handle_new_from_data (tdata->svg, strlen (tdata->svg), &err); |
| if (err) { |
| GST_ERROR_OBJECT (overlay, "Unable to render SVG: %s", err->message); |
| g_error_free (err); |
| } |
| g_assert (handle); |
| ret = rsvg_handle_render_cairo (handle, cairo); |
| g_assert (ret); |
| |
| rsvg_handle_close (handle, NULL); |
| g_object_unref (handle); |
| cairo_surface_flush (surface); |
| cairo_surface_destroy (surface); |
| cairo_destroy (cairo); |
| |
| tdata->op = OP_UPLOAD; |
| LOG_ELAPSED ("draw"); |
| TASK_DONE (TRUE); |
| } |
| |
| if (tdata->op == OP_UPLOAD) { |
| gboolean dma = overlay->use_ion && gst_is_dmabuf_memory ( |
| gst_buffer_peek_memory (tdata->buf, 0)); |
| if (dma) { |
| /* Unmap to flush CPU cache. */ |
| _task_data_unmap (tdata); |
| g_assert (!tdata->egl_tex); |
| tdata->egl_tex = _create_egl_image_texture (overlay, tdata->buf); |
| g_assert (tdata->egl_tex); |
| } else { |
| /* Copy pixels to GPU. */ |
| gst_gl_context_thread_add (tdata->overlay->context, _task_data_upload_gl, |
| tdata); |
| _task_data_unmap (tdata); |
| } |
| |
| tdata->op = OP_READY; |
| |
| g_mutex_lock (&overlay->mutex); |
| g_queue_push_tail (overlay->gl_queue, tdata); |
| g_queue_sort (overlay->gl_queue, _task_data_compare, NULL); |
| g_cond_broadcast (&overlay->cond); |
| g_mutex_unlock (&overlay->mutex); |
| LOG_ELAPSED ("upload"); |
| TASK_DONE (FALSE); |
| } |
| |
| g_assert_not_reached (); |
| |
| #undef LOG_ELAPSED |
| #undef TASK_DONE |
| } |
| |
| static gboolean |
| _overlay_ready (GstGLSvgOverlay * overlay, GstClockTime pts) |
| { |
| gint i; |
| |
| for (i = 0; i < g_queue_get_length (overlay->gl_queue); i++) { |
| TaskData *tdata = g_queue_peek_nth (overlay->gl_queue, i); |
| if (tdata && tdata->pts == pts && tdata->op == OP_READY) { |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| static GstFlowReturn |
| gst_gl_svg_overlay_transform (GstBaseTransform * bt, |
| GstBuffer * inbuf, GstBuffer * outbuf) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (bt); |
| |
| g_mutex_lock (&overlay->mutex); |
| if (overlay->sync) { |
| /* In this mode we require an overlay per frame. Block this thread |
| * while the texture is being prepared in the GL thread before |
| * transferring execution there. Don't block the GL thread. */ |
| while (overlay->started && !_overlay_ready (overlay, |
| GST_BUFFER_PTS (inbuf))) { |
| g_cond_wait (&overlay->cond, &overlay->mutex); |
| } |
| if (!overlay->started) { |
| g_mutex_unlock (&overlay->mutex); |
| return GST_FLOW_FLUSHING; |
| } |
| } else { |
| /* Async drawing of SVG was kicked off when property was set. |
| * When composing the final output the latest ready overlay will |
| * be used, if any. |
| */ |
| } |
| overlay->next_pts = GST_BUFFER_PTS (inbuf); |
| g_mutex_unlock (&overlay->mutex); |
| |
| return GST_BASE_TRANSFORM_CLASS (parent_class)->transform (bt, inbuf, outbuf); |
| } |
| |
| static gboolean |
| gst_gl_svg_overlay_set_svg (GstGLSvgOverlay * overlay, const gchar * svg, |
| GstClockTime pts) |
| { |
| TaskData *tdata; |
| |
| g_mutex_lock (&overlay->mutex); |
| if (!overlay->started) { |
| GST_WARNING_OBJECT (overlay, "Wrong state, dropping SVG overlay"); |
| g_mutex_unlock (&overlay->mutex); |
| return FALSE; |
| } |
| |
| tdata = g_malloc0 (sizeof (TaskData)); |
| tdata->overlay = overlay; |
| tdata->op = OP_ALLOC; |
| tdata->svg = g_strdup (svg); |
| tdata->pts = pts; |
| g_queue_push_tail (overlay->svg_queue, tdata); |
| _task_push_locked (overlay, overlay->svg_queue); |
| |
| g_mutex_unlock (&overlay->mutex); |
| |
| return TRUE; |
| } |
| |
| static TaskData * |
| gst_gl_svg_overlay_next (GstGLSvgOverlay * overlay) |
| { |
| gint i; |
| GQueue *q; |
| GstClockTime pts; |
| TaskData *tdata; |
| |
| g_mutex_lock (&overlay->mutex); |
| |
| q = overlay->gl_queue; |
| pts = overlay->next_pts; |
| |
| if (overlay->sync) { |
| /* Get overlay with matching pts, drop older. */ |
| while (!g_queue_is_empty(q) && |
| ((TaskData*) g_queue_peek_tail (q))->pts < pts) { |
| _task_data_free (g_queue_pop_head (q)); |
| } |
| |
| for (i = 0; i < g_queue_get_length (q); i++) { |
| TaskData *tmp = g_queue_peek_nth (q, i); |
| if (tmp && tmp->pts == pts) { |
| tdata = g_queue_pop_nth (q, i); |
| break; |
| } |
| } |
| g_assert (tdata); |
| } else { |
| /* Get the latest overlay, drop older. */ |
| tdata = g_queue_pop_head (q); |
| while (!g_queue_is_empty(q)) { |
| _task_data_free (g_queue_pop_head (q)); |
| } |
| } |
| |
| g_mutex_unlock (&overlay->mutex); |
| |
| return tdata; |
| } |
| |
| static gboolean |
| gst_gl_svg_overlay_draw_gl (GstGLFilter * filter, GstGLMemory * in_tex, |
| gpointer user_data) |
| { |
| GstGLSvgOverlay *overlay = GST_GL_SVG_OVERLAY (user_data); |
| GstGLContext *context = GST_GL_BASE_FILTER (filter)->context; |
| const GstGLFuncs *gl = context->gl_vtable; |
| TaskData *next, *old = NULL; |
| guint tex_id, o_tex_id = 0; |
| |
| next = gst_gl_svg_overlay_next (overlay); |
| if (next) { |
| _task_data_free (overlay->current); |
| overlay->current = next; |
| } |
| |
| tex_id = gst_gl_memory_get_texture_id (in_tex); |
| if (overlay->current) { |
| GstGLSyncMeta *sync_meta = gst_buffer_get_gl_sync_meta ( |
| overlay->current->buf); |
| if (sync_meta) { |
| gst_gl_sync_meta_wait (sync_meta, GST_GL_BASE_FILTER (filter)->context); |
| } |
| |
| if (overlay->current->egl_tex) { |
| o_tex_id = overlay->current->egl_tex->tex; |
| } else { |
| o_tex_id = overlay->current->tex; |
| } |
| } |
| |
| g_return_val_if_fail (overlay->shader, FALSE); |
| |
| gl->ClearColor (0.0, 0.0, 0.0, 1.0); |
| gl->Clear (GL_COLOR_BUFFER_BIT); |
| |
| gst_gl_shader_use (overlay->shader); |
| gst_gl_shader_set_uniform_1i (overlay->shader, "tex", 0); |
| gl->ActiveTexture (GL_TEXTURE0); |
| gl->BindTexture (GL_TEXTURE_2D, tex_id); |
| gst_gl_filter_draw_fullscreen_quad (filter); |
| |
| if (o_tex_id) { |
| gst_gl_shader_use (overlay->shader); |
| gst_gl_shader_set_uniform_1i (overlay->shader, "tex", 0); |
| gl->ActiveTexture (GL_TEXTURE0); |
| gl->BindTexture (GL_TEXTURE_2D, o_tex_id); |
| gl->Enable (GL_BLEND); |
| gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| gl->BlendEquation (GL_FUNC_ADD); |
| gst_gl_filter_draw_fullscreen_quad (filter); |
| gl->Disable (GL_BLEND); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_gl_svg_overlay_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex, |
| GstGLMemory * out_tex) |
| { |
| if (gst_gl_context_get_gl_api (GST_GL_BASE_FILTER (filter)->context)) { |
| gst_gl_filter_render_to_target (filter, in_tex, out_tex, |
| gst_gl_svg_overlay_draw_gl, filter); |
| } |
| |
| return TRUE; |
| } |