blob: dfd4f0024518216a6da32eda39cb8f7f148f1899 [file] [log] [blame]
/*
* # 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;
}