summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.com>2017-12-19 12:00:43 +0000
committerTim-Philipp Müller <tim@centricular.com>2017-12-19 12:00:43 +0000
commitcdbb261d9c46fe55cf1e3263bb1781c94ccefd42 (patch)
tree59e4a3fc08929b22016823704346fbe60441155a /ext
parent5affe09c4802142ed945fcbe39bca5e2f6622d94 (diff)
parent8f0bf85cfe20af24e84fc6c978225ba856cc5af5 (diff)
Move OpenGL library and plugin from -bad
Merge branch 'opengl-move' https://bugzilla.gnome.org/show_bug.cgi?id=754094
Diffstat (limited to 'ext')
-rw-r--r--ext/gl/Makefile.am164
-rw-r--r--ext/gl/caopengllayersink.h98
-rw-r--r--ext/gl/caopengllayersink.m1012
-rw-r--r--ext/gl/effects/gstgleffectblur.c68
-rw-r--r--ext/gl/effects/gstgleffectbulge.c36
-rw-r--r--ext/gl/effects/gstgleffectfisheye.c36
-rw-r--r--ext/gl/effects/gstgleffectglow.c86
-rw-r--r--ext/gl/effects/gstgleffectidentity.c49
-rw-r--r--ext/gl/effects/gstgleffectlaplacian.c52
-rw-r--r--ext/gl/effects/gstgleffectlumatocurve.c92
-rw-r--r--ext/gl/effects/gstgleffectlumatocurve.h35
-rw-r--r--ext/gl/effects/gstgleffectmirror.c36
-rw-r--r--ext/gl/effects/gstgleffectrgbtocurve.c78
-rw-r--r--ext/gl/effects/gstgleffectscurves.c210
-rw-r--r--ext/gl/effects/gstgleffectscurves.h47
-rw-r--r--ext/gl/effects/gstgleffectsin.c36
-rw-r--r--ext/gl/effects/gstgleffectsobel.c61
-rw-r--r--ext/gl/effects/gstgleffectsquare.c36
-rw-r--r--ext/gl/effects/gstgleffectsqueeze.c36
-rw-r--r--ext/gl/effects/gstgleffectssources.c548
-rw-r--r--ext/gl/effects/gstgleffectssources.h52
-rw-r--r--ext/gl/effects/gstgleffectstretch.c36
-rw-r--r--ext/gl/effects/gstgleffecttunnel.c36
-rw-r--r--ext/gl/effects/gstgleffecttwirl.c36
-rw-r--r--ext/gl/effects/gstgleffectxray.c124
-rw-r--r--ext/gl/gltestsrc.c1168
-rw-r--r--ext/gl/gltestsrc.h81
-rw-r--r--ext/gl/gstglbasemixer.c473
-rw-r--r--ext/gl/gstglbasemixer.h98
-rw-r--r--ext/gl/gstglbumper.c546
-rw-r--r--ext/gl/gstglbumper.h57
-rw-r--r--ext/gl/gstglcolorbalance.c551
-rw-r--r--ext/gl/gstglcolorbalance.h73
-rw-r--r--ext/gl/gstglcolorconvertelement.c245
-rw-r--r--ext/gl/gstglcolorconvertelement.h59
-rw-r--r--ext/gl/gstglcolorscale.c187
-rw-r--r--ext/gl/gstglcolorscale.h58
-rw-r--r--ext/gl/gstgldeinterlace.c517
-rw-r--r--ext/gl/gstgldeinterlace.h61
-rw-r--r--ext/gl/gstgldifferencematte.c569
-rw-r--r--ext/gl/gstgldifferencematte.h62
-rw-r--r--ext/gl/gstgldownloadelement.c460
-rw-r--r--ext/gl/gstgldownloadelement.h60
-rw-r--r--ext/gl/gstgleffects.c688
-rw-r--r--ext/gl/gstgleffects.h119
-rw-r--r--ext/gl/gstglfilterapp.c229
-rw-r--r--ext/gl/gstglfilterapp.h51
-rw-r--r--ext/gl/gstglfilterbin.c270
-rw-r--r--ext/gl/gstglfilterbin.h80
-rw-r--r--ext/gl/gstglfiltercube.c496
-rw-r--r--ext/gl/gstglfiltercube.h73
-rw-r--r--ext/gl/gstglfilterglass.c412
-rw-r--r--ext/gl/gstglfilterglass.h57
-rw-r--r--ext/gl/gstglfilterreflectedscreen.c486
-rw-r--r--ext/gl/gstglfilterreflectedscreen.h61
-rw-r--r--ext/gl/gstglfiltershader.c556
-rw-r--r--ext/gl/gstglfiltershader.h63
-rw-r--r--ext/gl/gstglimagesink.c2381
-rw-r--r--ext/gl/gstglimagesink.h157
-rw-r--r--ext/gl/gstglmixer.c720
-rw-r--r--ext/gl/gstglmixer.h110
-rw-r--r--ext/gl/gstglmixerbin.c620
-rw-r--r--ext/gl/gstglmixerbin.h72
-rw-r--r--ext/gl/gstglmosaic.c358
-rw-r--r--ext/gl/gstglmosaic.h55
-rw-r--r--ext/gl/gstgloverlay.c832
-rw-r--r--ext/gl/gstgloverlay.h84
-rw-r--r--ext/gl/gstglsinkbin.c599
-rw-r--r--ext/gl/gstglsinkbin.h76
-rw-r--r--ext/gl/gstglsrcbin.c267
-rw-r--r--ext/gl/gstglsrcbin.h71
-rw-r--r--ext/gl/gstglstereomix.c697
-rw-r--r--ext/gl/gstglstereomix.h87
-rw-r--r--ext/gl/gstglstereosplit.c735
-rw-r--r--ext/gl/gstglstereosplit.h64
-rw-r--r--ext/gl/gstgltestsrc.c771
-rw-r--r--ext/gl/gstgltestsrc.h94
-rw-r--r--ext/gl/gstgltransformation.c970
-rw-r--r--ext/gl/gstgltransformation.h99
-rw-r--r--ext/gl/gstgluploadelement.c270
-rw-r--r--ext/gl/gstgluploadelement.h69
-rw-r--r--ext/gl/gstglutils.c178
-rw-r--r--ext/gl/gstglutils.h38
-rw-r--r--ext/gl/gstglvideoflip.c529
-rw-r--r--ext/gl/gstglvideoflip.h97
-rw-r--r--ext/gl/gstglvideomixer.c1586
-rw-r--r--ext/gl/gstglvideomixer.h143
-rw-r--r--ext/gl/gstglviewconvert.c359
-rw-r--r--ext/gl/gstglviewconvert.h53
-rw-r--r--ext/gl/gstopengl.c289
-rw-r--r--ext/gl/meson.build119
91 files changed, 25715 insertions, 0 deletions
diff --git a/ext/gl/Makefile.am b/ext/gl/Makefile.am
new file mode 100644
index 000000000..adeadbf60
--- /dev/null
+++ b/ext/gl/Makefile.am
@@ -0,0 +1,164 @@
+plugin_LTLIBRARIES = libgstopengl.la
+
+libgstopengl_la_SOURCES = \
+ gstopengl.c \
+ gstglbasemixer.c \
+ gstgluploadelement.c \
+ gstgldownloadelement.c \
+ gstglcolorconvertelement.c \
+ gstglfilterbin.c \
+ gstglmixerbin.c \
+ gstglsinkbin.c \
+ gstglsrcbin.c \
+ gstglimagesink.c \
+ gstglfiltercube.c \
+ gstgleffects.c \
+ effects/gstgleffectscurves.c \
+ effects/gstgleffectssources.c \
+ effects/gstgleffectidentity.c \
+ effects/gstgleffectmirror.c \
+ effects/gstgleffectsqueeze.c \
+ effects/gstgleffectstretch.c \
+ effects/gstgleffectfisheye.c \
+ effects/gstgleffecttwirl.c \
+ effects/gstgleffectbulge.c \
+ effects/gstgleffecttunnel.c \
+ effects/gstgleffectsquare.c \
+ effects/gstgleffectlumatocurve.c \
+ effects/gstgleffectrgbtocurve.c \
+ effects/gstgleffectsin.c \
+ effects/gstgleffectxray.c \
+ effects/gstgleffectglow.c \
+ effects/gstgleffectblur.c \
+ effects/gstgleffectsobel.c \
+ effects/gstgleffectlaplacian.c \
+ gstglcolorscale.c \
+ gstglcolorbalance.c \
+ gstglmixer.c \
+ gstglvideomixer.c \
+ gstglfiltershader.c \
+ gstglfilterapp.c \
+ gstglviewconvert.c \
+ gstglstereosplit.c \
+ gstgldeinterlace.c \
+ gstglstereomix.c \
+ gltestsrc.c \
+ gstgltestsrc.c \
+ gstglutils.c
+
+noinst_HEADERS = \
+ gstglbasemixer.h \
+ gstgluploadelement.h \
+ gstgldownloadelement.h \
+ gstglcolorconvertelement.h \
+ gstglfilterbin.h \
+ gstglmixerbin.h \
+ gstglsinkbin.h \
+ gstglsrcbin.h \
+ gstglimagesink.h \
+ gstglfiltercube.h \
+ gstgleffects.h \
+ effects/gstgleffectssources.h \
+ gstglcolorscale.h \
+ gstglcolorbalance.h \
+ gstglmixer.h \
+ gstglvideomixer.h \
+ gstglfiltershader.h \
+ gstglfilterapp.h \
+ gstglstereosplit.h \
+ gstglstereomix.h \
+ gstgldeinterlace.h \
+ gstglviewconvert.h \
+ gltestsrc.h \
+ gstgltestsrc.h \
+ gstglutils.h
+
+# full opengl required
+if USE_OPENGL
+libgstopengl_la_SOURCES += \
+ gstglfilterglass.c \
+ gstglmosaic.c
+
+noinst_HEADERS += \
+ gstglfilterglass.h \
+ gstglmosaic.h \
+ effects/gstgleffectscurves.h \
+ effects/gstgleffectlumatocurve.h
+
+if HAVE_PNG
+libgstopengl_la_SOURCES += \
+ gstgldifferencematte.c
+
+noinst_HEADERS += \
+ gstgldifferencematte.h
+endif
+endif
+
+if HAVE_GRAPHENE
+libgstopengl_la_SOURCES += \
+ gstgltransformation.c \
+ gstglvideoflip.c
+
+noinst_HEADERS += \
+ gstgltransformation.h \
+ gstglvideoflip.h
+endif
+
+if HAVE_JPEG
+if HAVE_PNG
+libgstopengl_la_SOURCES += \
+ gstgloverlay.c
+
+noinst_HEADERS += \
+ gstgloverlay.h
+endif
+endif
+
+if HAVE_WINDOW_COCOA
+libgstopengl_la_SOURCES += \
+ caopengllayersink.m
+
+noinst_HEADERS += \
+ caopengllayersink.h
+endif
+
+libgstopengl_la_OBJCFLAGS = \
+ -I$(top_srcdir)/gst-libs \
+ -I$(top_builddir)/gst-libs \
+ -fobjc-arc \
+ $(GST_OBJCFLAGS) \
+ $(GST_BASE_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_CONTROLLER_CFLAGS) \
+ $(GL_OBJCFLAGS)
+
+# check order of CFLAGS and LIBS, shouldn't the order be the other way around
+# (like in AM_CFLAGS)?
+libgstopengl_la_CFLAGS = \
+ -I$(top_srcdir)/gst-libs \
+ -I$(top_builddir)/gst-libs \
+ $(GST_CFLAGS) \
+ $(GST_BASE_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_CONTROLLER_CFLAGS) \
+ $(GL_CFLAGS) \
+ $(LIBPNG_CFLAGS) \
+ $(GRAPHENE_CFLAGS)
+
+libgstopengl_la_LIBADD = \
+ $(top_builddir)/gst-libs/gst/gl/libgstgl-$(GST_API_VERSION).la \
+ $(top_builddir)/gst-libs/gst/video/libgstbadvideo-$(GST_API_VERSION).la \
+ $(GST_BASE_LIBS) \
+ $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_API_VERSION) \
+ -lgstpbutils-$(GST_API_VERSION) \
+ $(GST_CONTROLLER_LIBS) \
+ $(GL_LIBS) \
+ $(LIBPNG_LIBS) \
+ $(JPEG_LIBS) \
+ $(LIBM) \
+ $(GRAPHENE_LIBS)
+
+libgstopengl_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstopengl_la_LIBTOOLFLAGS = --tag=CC
+
+
diff --git a/ext/gl/caopengllayersink.h b/ext/gl/caopengllayersink.h
new file mode 100644
index 000000000..c2710033d
--- /dev/null
+++ b/ext/gl/caopengllayersink.h
@@ -0,0 +1,98 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __CA_OPENGL_LAYER_SINK_H__
+#define __CA_OPENGL_LAYER_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include <gst/gl/gl.h>
+#include <gst/gl/gstglfuncs.h>
+#include <gst/gl/cocoa/gstglcaopengllayer.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_CA_OPENGL_LAYER_SINK \
+ (gst_ca_opengl_layer_sink_get_type())
+#define GST_CA_OPENGL_LAYER_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CA_OPENGL_LAYER_SINK,GstCAOpenGLLayerSink))
+#define GST_CA_OPENGL_LAYER_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CA_OPENGL_LAYER_SINK,GstCAOpenGLLayerSinkClass))
+#define GST_IS_CA_OPENGL_LAYER_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CA_OPENGL_LAYER_SINK))
+#define GST_IS_CA_OPENGL_LAYER_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CA_OPENGL_LAYER_SINK))
+
+typedef struct _GstCAOpenGLLayerSink GstCAOpenGLLayerSink;
+typedef struct _GstCAOpenGLLayerSinkClass GstCAOpenGLLayerSinkClass;
+
+struct _GstCAOpenGLLayerSink
+{
+ GstVideoSink video_sink;
+
+ /* caps */
+ GstVideoInfo info;
+ GstCaps *gl_caps;
+
+ /* gl state */
+ GstGLDisplay *display;
+ GstGLContext *other_context;
+ GstGLContext *context;
+
+ guint next_tex;
+ GstBuffer *next_buffer;
+ GstBuffer *next_sync;
+
+ gpointer layer;
+
+ gboolean keep_aspect_ratio;
+
+ /* avoid replacing the stored_buffer while drawing */
+ GMutex drawing_lock;
+ GstBuffer *stored_buffer;
+ GstBuffer *stored_sync;
+ GLuint redisplay_texture;
+
+ gboolean caps_change;
+ guint window_width;
+ guint window_height;
+
+ /* gl state */
+ GstGLShader *redisplay_shader;
+ GLuint vao;
+ GLuint vertex_buffer;
+ GLuint vbo_indices;
+ GLint attr_position;
+ GLint attr_texture;
+};
+
+struct _GstCAOpenGLLayerSinkClass
+{
+ GstVideoSinkClass video_sink_class;
+};
+
+GType gst_ca_opengl_layer_sink_get_type(void);
+GType gst_ca_opengl_layer_sink_bin_get_type (void);
+
+G_END_DECLS
+
+#endif /* __CA_OPENGL_LAYER_SINK__ */
diff --git a/ext/gl/caopengllayersink.m b/ext/gl/caopengllayersink.m
new file mode 100644
index 000000000..1cb72e5b9
--- /dev/null
+++ b/ext/gl/caopengllayersink.m
@@ -0,0 +1,1012 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-caopengllayersink
+ *
+ * caopengllayersink renders incoming video frames to CAOpenGLLayer that
+ * can be retrieved through the layer property and placed in the Core
+ * Animation render tree.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "caopengllayersink.h"
+#include "gstglsinkbin.h"
+#include <QuartzCore/QuartzCore.h>
+
+GST_DEBUG_CATEGORY (gst_debug_ca_sink);
+#define GST_CAT_DEFAULT gst_debug_ca_sink
+
+typedef GstGLSinkBin GstCAOpenGLLayerSinkBin;
+typedef GstGLSinkBinClass GstCAOpenGLLayerSinkBinClass;
+
+G_DEFINE_TYPE (GstCAOpenGLLayerSinkBin, gst_ca_opengl_layer_sink_bin,
+ GST_TYPE_GL_SINK_BIN);
+
+enum
+{
+ PROP_BIN_0,
+ PROP_BIN_QOS,
+ PROP_BIN_FORCE_ASPECT_RATIO,
+ PROP_BIN_LAST_SAMPLE,
+ PROP_BIN_LAYER,
+};
+
+static void
+_on_notify_layer (GObject * object, GParamSpec *pspec, gpointer user_data)
+{
+ GstCAOpenGLLayerSinkBin *self = user_data;
+
+ g_object_notify (G_OBJECT (self), "layer");
+}
+
+static void
+gst_ca_opengl_layer_sink_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec)
+{
+ g_object_set_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
+ param_spec->name, value);
+}
+
+static void
+gst_ca_opengl_layer_sink_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec)
+{
+ g_object_get_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
+ param_spec->name, value);
+}
+
+static void
+gst_ca_opengl_layer_sink_bin_init (GstCAOpenGLLayerSinkBin * self)
+{
+ gpointer *sink = g_object_new (GST_TYPE_CA_OPENGL_LAYER_SINK, NULL);
+
+ g_signal_connect (sink, "notify::layer", G_CALLBACK (_on_notify_layer), self);
+
+ gst_gl_sink_bin_finish_init_with_element (GST_GL_SINK_BIN (self),
+ GST_ELEMENT (sink));
+}
+
+static void
+gst_ca_opengl_layer_sink_bin_class_init (GstCAOpenGLLayerSinkBinClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_ca_opengl_layer_sink_bin_get_property;
+ gobject_class->set_property = gst_ca_opengl_layer_sink_bin_set_property;
+
+ g_object_class_install_property (gobject_class, PROP_BIN_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio", TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BIN_LAST_SAMPLE,
+ g_param_spec_boxed ("last-sample", "Last Sample",
+ "The last sample received in the sink", GST_TYPE_SAMPLE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BIN_LAYER,
+ g_param_spec_pointer ("layer", "CAOpenGLLayer",
+ "OpenGL Core Animation layer",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BIN_QOS,
+ g_param_spec_boolean ("qos", "Quality of Service",
+ "Generate Quality-of-Service events upstream", TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+#define GST_CA_OPENGL_LAYER_SINK_GET_LOCK(glsink) \
+ (GST_CA_OPENGL_LAYER_SINK(glsink)->drawing_lock)
+#define GST_CA_OPENGL_LAYER_SINK_LOCK(glsink) \
+ (g_mutex_lock(&GST_CA_OPENGL_LAYER_SINK_GET_LOCK (glsink)))
+#define GST_CA_OPENGL_LAYER_SINK_UNLOCK(glsink) \
+ (g_mutex_unlock(&GST_CA_OPENGL_LAYER_SINK_GET_LOCK (glsink)))
+
+#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
+#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
+#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
+#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))
+
+#define SUPPORTED_GL_APIS GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3
+
+static void gst_ca_opengl_layer_sink_thread_init_redisplay (GstCAOpenGLLayerSink * ca_sink);
+static void gst_ca_opengl_layer_sink_cleanup_glthread (GstCAOpenGLLayerSink * ca_sink);
+static void gst_ca_opengl_layer_sink_on_resize (GstCAOpenGLLayerSink * ca_sink,
+ gint width, gint height);
+static void gst_ca_opengl_layer_sink_on_draw (GstCAOpenGLLayerSink * ca_sink);
+
+static void gst_ca_opengl_layer_sink_finalize (GObject * object);
+static void gst_ca_opengl_layer_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_ca_opengl_layer_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static gboolean gst_ca_opengl_layer_sink_stop (GstBaseSink * bsink);
+
+static gboolean gst_ca_opengl_layer_sink_query (GstBaseSink * bsink, GstQuery * query);
+static void gst_ca_opengl_layer_sink_set_context (GstElement * element,
+ GstContext * context);
+
+static GstStateChangeReturn gst_ca_opengl_layer_sink_change_state (GstElement *
+ element, GstStateChange transition);
+
+static void gst_ca_opengl_layer_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end);
+static gboolean gst_ca_opengl_layer_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
+static GstFlowReturn gst_ca_opengl_layer_sink_prepare (GstBaseSink * bsink,
+ GstBuffer * buf);
+static GstFlowReturn gst_ca_opengl_layer_sink_show_frame (GstVideoSink * bsink,
+ GstBuffer * buf);
+static gboolean gst_ca_opengl_layer_sink_propose_allocation (GstBaseSink * bsink,
+ GstQuery * query);
+
+static GstStaticPadTemplate gst_ca_opengl_layer_sink_template =
+ GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ","
+ "texture-target = (string) 2D")
+ );
+
+enum
+{
+ PROP_0,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_CONTEXT,
+ PROP_LAYER,
+};
+
+#define gst_ca_opengl_layer_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstCAOpenGLLayerSink, gst_ca_opengl_layer_sink,
+ GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_ca_sink,
+ "caopengllayersink", 0, "CAOpenGLLayer Video Sink"));
+
+static void
+gst_ca_opengl_layer_sink_class_init (GstCAOpenGLLayerSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+ GstVideoSinkClass *gstvideosink_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+ gstvideosink_class = (GstVideoSinkClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->set_property = gst_ca_opengl_layer_sink_set_property;
+ gobject_class->get_property = gst_ca_opengl_layer_sink_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio", TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CONTEXT,
+ g_param_spec_object ("context",
+ "OpenGL context",
+ "Get OpenGL context",
+ GST_TYPE_GL_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_LAYER,
+ g_param_spec_pointer ("layer", "CAOpenGLLayer",
+ "OpenGL Core Animation layer",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "CAOpenGLLayer video sink",
+ "Sink/Video", "A video sink based on CAOpenGLLayer",
+ "Matthew Waters <matthew@centricular.com>");
+
+ gst_element_class_add_static_pad_template (element_class, &gst_ca_opengl_layer_sink_template);
+
+ gobject_class->finalize = gst_ca_opengl_layer_sink_finalize;
+
+ gstelement_class->change_state = gst_ca_opengl_layer_sink_change_state;
+ gstelement_class->set_context = gst_ca_opengl_layer_sink_set_context;
+ gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_ca_opengl_layer_sink_query);
+ gstbasesink_class->set_caps = gst_ca_opengl_layer_sink_set_caps;
+ gstbasesink_class->get_times = gst_ca_opengl_layer_sink_get_times;
+ gstbasesink_class->prepare = gst_ca_opengl_layer_sink_prepare;
+ gstbasesink_class->propose_allocation = gst_ca_opengl_layer_sink_propose_allocation;
+ gstbasesink_class->stop = gst_ca_opengl_layer_sink_stop;
+
+ gstvideosink_class->show_frame =
+ GST_DEBUG_FUNCPTR (gst_ca_opengl_layer_sink_show_frame);
+}
+
+static void
+gst_ca_opengl_layer_sink_init (GstCAOpenGLLayerSink * ca_sink)
+{
+ ca_sink->display = NULL;
+ ca_sink->keep_aspect_ratio = TRUE;
+ ca_sink->stored_buffer = NULL;
+ ca_sink->redisplay_texture = 0;
+
+ g_mutex_init (&ca_sink->drawing_lock);
+}
+
+static void
+gst_ca_opengl_layer_sink_finalize (GObject * object)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+
+ g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
+
+ g_mutex_clear (&ca_sink->drawing_lock);
+
+ if (ca_sink->layer) {
+ CFRelease(ca_sink->layer);
+ ca_sink->layer = NULL;
+ }
+
+ GST_DEBUG ("finalized");
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_ca_opengl_layer_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+
+ g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
+
+ switch (prop_id) {
+ case PROP_FORCE_ASPECT_RATIO:
+ {
+ ca_sink->keep_aspect_ratio = g_value_get_boolean (value);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ca_opengl_layer_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+
+ g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
+
+ switch (prop_id) {
+ case PROP_FORCE_ASPECT_RATIO:
+ g_value_set_boolean (value, ca_sink->keep_aspect_ratio);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, ca_sink->context);
+ break;
+ case PROP_LAYER:
+ g_value_set_pointer (value, ca_sink->layer);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_create_layer (gpointer data)
+{
+ GstCAOpenGLLayerSink *ca_sink = data;
+ id layer;
+
+ if (!ca_sink->layer) {
+ layer = [[NSClassFromString(@"GstGLCAOpenGLLayer") alloc]
+ initWithGstGLContext:ca_sink->context];
+
+ ca_sink->layer = (__bridge_retained gpointer)layer;
+ [layer setDrawCallback:(GstGLWindowCB)gst_ca_opengl_layer_sink_on_draw
+ data:ca_sink notify:NULL];
+ [layer setResizeCallback:(GstGLWindowResizeCB)gst_ca_opengl_layer_sink_on_resize
+ data:ca_sink notify:NULL];
+ g_object_notify (G_OBJECT (ca_sink), "layer");
+ }
+}
+
+static void
+_invoke_on_main (GstGLWindowCB func, gpointer data)
+{
+ if ([NSThread isMainThread]) {
+ func (data);
+ } else {
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ func (data);
+ });
+ }
+}
+
+static gboolean
+_ensure_gl_setup (GstCAOpenGLLayerSink * ca_sink)
+{
+ GError *error = NULL;
+
+ if (!gst_gl_ensure_element_data (ca_sink, &ca_sink->display,
+ &ca_sink->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (ca_sink->display, SUPPORTED_GL_APIS);
+
+ if (!ca_sink->context) {
+ if (!gst_gl_display_create_context (ca_sink->display,
+ ca_sink->other_context, &ca_sink->context, &error)) {
+ goto context_error;
+ }
+ }
+
+ if (!ca_sink->layer)
+ _invoke_on_main ((GstGLWindowCB) _create_layer, ca_sink);
+
+ return TRUE;
+
+context_error:
+ {
+ GST_ELEMENT_ERROR (ca_sink, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+ gst_object_unref (ca_sink->context);
+ ca_sink->context = NULL;
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_ca_opengl_layer_sink_query (GstBaseSink * bsink, GstQuery * query)
+{
+ GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) ca_sink, query,
+ ca_sink->display, ca_sink->context, ca_sink->other_context))
+ return TRUE;
+ break;
+ }
+ case GST_QUERY_DRAIN:
+ {
+ GstBuffer *buf = NULL;
+
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+ ca_sink->redisplay_texture = 0;
+ buf = ca_sink->stored_buffer;
+ ca_sink->stored_buffer = NULL;
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+
+ if (buf)
+ gst_buffer_unref (buf);
+
+ gst_buffer_replace (&ca_sink->next_buffer, NULL);
+ gst_buffer_replace (&ca_sink->next_sync, NULL);
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
+}
+
+static gboolean
+gst_ca_opengl_layer_sink_stop (GstBaseSink * bsink)
+{
+ GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+
+ if (ca_sink->gl_caps) {
+ gst_caps_unref (ca_sink->gl_caps);
+ ca_sink->gl_caps = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+gst_ca_opengl_layer_sink_set_context (GstElement * element, GstContext * context)
+{
+ GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (element);
+
+ gst_gl_handle_set_context (element, context, &ca_sink->display,
+ &ca_sink->other_context);
+
+ if (ca_sink->display)
+ gst_gl_display_filter_gl_api (ca_sink->display, SUPPORTED_GL_APIS);
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static GstStateChangeReturn
+gst_ca_opengl_layer_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ _ensure_gl_setup (ca_sink);
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ {
+ /* mark the redisplay_texture as unavailable (=0)
+ * to avoid drawing
+ */
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+ ca_sink->redisplay_texture = 0;
+
+ gst_buffer_replace (&ca_sink->stored_sync, NULL);
+
+ if (ca_sink->stored_buffer) {
+ gst_buffer_unref (ca_sink->stored_buffer);
+ ca_sink->stored_buffer = NULL;
+ }
+ gst_buffer_replace (&ca_sink->next_buffer, NULL);
+ gst_buffer_replace (&ca_sink->next_sync, NULL);
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+
+ GST_VIDEO_SINK_WIDTH (ca_sink) = 1;
+ GST_VIDEO_SINK_HEIGHT (ca_sink) = 1;
+ if (ca_sink->context) {
+ gst_object_unref (ca_sink->context);
+ ca_sink->context = NULL;
+ }
+
+ if (ca_sink->display) {
+ gst_object_unref (ca_sink->display);
+ ca_sink->display = NULL;
+ }
+ break;
+ }
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (ca_sink->layer) {
+ CFRelease(ca_sink->layer);
+ ca_sink->layer = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_ca_opengl_layer_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+ *start = GST_BUFFER_TIMESTAMP (buf);
+ if (GST_BUFFER_DURATION_IS_VALID (buf))
+ *end = *start + GST_BUFFER_DURATION (buf);
+ else {
+ if (GST_VIDEO_INFO_FPS_N (&ca_sink->info) > 0) {
+ *end = *start +
+ gst_util_uint64_scale_int (GST_SECOND,
+ GST_VIDEO_INFO_FPS_D (&ca_sink->info),
+ GST_VIDEO_INFO_FPS_N (&ca_sink->info));
+ }
+ }
+ }
+}
+
+static gboolean
+gst_ca_opengl_layer_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+ gint width;
+ gint height;
+ gboolean ok;
+ gint par_n, par_d;
+ gint display_par_n, display_par_d;
+ guint display_ratio_num, display_ratio_den;
+ GstVideoInfo vinfo;
+
+ GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+
+ ok = gst_video_info_from_caps (&vinfo, caps);
+ if (!ok)
+ return FALSE;
+
+ width = GST_VIDEO_INFO_WIDTH (&vinfo);
+ height = GST_VIDEO_INFO_HEIGHT (&vinfo);
+
+ par_n = GST_VIDEO_INFO_PAR_N (&vinfo);
+ par_d = GST_VIDEO_INFO_PAR_D (&vinfo);
+
+ if (!par_n)
+ par_n = 1;
+
+ display_par_n = 1;
+ display_par_d = 1;
+
+ ok = gst_video_calculate_display_ratio (&display_ratio_num,
+ &display_ratio_den, width, height, par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (!ok)
+ return FALSE;
+
+ GST_TRACE ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (height % display_ratio_den == 0) {
+ GST_DEBUG ("keeping video height");
+ GST_VIDEO_SINK_WIDTH (ca_sink) = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ GST_VIDEO_SINK_HEIGHT (ca_sink) = height;
+ } else if (width % display_ratio_num == 0) {
+ GST_DEBUG ("keeping video width");
+ GST_VIDEO_SINK_WIDTH (ca_sink) = width;
+ GST_VIDEO_SINK_HEIGHT (ca_sink) = (guint)
+ gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+ } else {
+ GST_DEBUG ("approximating while keeping video height");
+ GST_VIDEO_SINK_WIDTH (ca_sink) = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ GST_VIDEO_SINK_HEIGHT (ca_sink) = height;
+ }
+ GST_DEBUG ("scaling to %dx%d", GST_VIDEO_SINK_WIDTH (ca_sink),
+ GST_VIDEO_SINK_HEIGHT (ca_sink));
+
+ ca_sink->info = vinfo;
+ if (!_ensure_gl_setup (ca_sink))
+ return FALSE;
+
+ ca_sink->caps_change = TRUE;
+
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_ca_opengl_layer_sink_prepare (GstBaseSink * bsink, GstBuffer * buf)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+ GstBuffer *next_sync, *old_sync, *old_buffer;
+ GstVideoFrame gl_frame;
+ GstGLSyncMeta *sync_meta;
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+
+ GST_TRACE ("preparing buffer:%p", buf);
+
+ if (GST_VIDEO_SINK_WIDTH (ca_sink) < 1 ||
+ GST_VIDEO_SINK_HEIGHT (ca_sink) < 1) {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+
+ if (!_ensure_gl_setup (ca_sink))
+ return GST_FLOW_NOT_NEGOTIATED;
+
+ if (!gst_video_frame_map (&gl_frame, &ca_sink->info, buf,
+ GST_MAP_READ | GST_MAP_GL)) {
+ goto upload_failed;
+ }
+
+ ca_sink->next_tex = *(guint *) gl_frame.data[0];
+
+ next_sync = gst_buffer_new ();
+ sync_meta = gst_buffer_add_gl_sync_meta (ca_sink->context, next_sync);
+ gst_gl_sync_meta_set_sync_point (sync_meta, ca_sink->context);
+
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+ ca_sink->next_tex = *(guint *) gl_frame.data[0];
+
+ old_buffer = ca_sink->next_buffer;
+ ca_sink->next_buffer = gst_buffer_ref (buf);
+
+ old_sync = ca_sink->next_sync;
+ ca_sink->next_sync = next_sync;
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+
+ if (old_buffer)
+ gst_buffer_unref (old_buffer);
+ if (old_sync)
+ gst_buffer_unref (old_sync);
+
+ gst_video_frame_unmap (&gl_frame);
+
+ return GST_FLOW_OK;
+
+upload_failed:
+ {
+ GST_ELEMENT_ERROR (ca_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Failed to upload buffer"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+}
+
+static GstFlowReturn
+gst_ca_opengl_layer_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+ GstCAOpenGLLayerSink *ca_sink;
+ GstBuffer *stored_buffer, *old_sync;
+
+ GST_TRACE ("rendering buffer:%p", buf);
+
+ ca_sink = GST_CA_OPENGL_LAYER_SINK (vsink);
+
+ GST_TRACE ("redisplay texture:%u of size:%ux%u, window size:%ux%u",
+ ca_sink->next_tex, GST_VIDEO_INFO_WIDTH (&ca_sink->info),
+ GST_VIDEO_INFO_HEIGHT (&ca_sink->info),
+ GST_VIDEO_SINK_WIDTH (ca_sink),
+ GST_VIDEO_SINK_HEIGHT (ca_sink));
+
+ /* Avoid to release the texture while drawing */
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+ ca_sink->redisplay_texture = ca_sink->next_tex;
+
+ stored_buffer = ca_sink->stored_buffer;
+ ca_sink->stored_buffer = gst_buffer_ref (ca_sink->next_buffer);
+
+ old_sync = ca_sink->stored_sync;
+ ca_sink->stored_sync = gst_buffer_ref (ca_sink->next_sync);
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+
+ /* The layer will automatically call the draw callback to draw the new
+ * content */
+ [CATransaction begin];
+ [(__bridge GstGLCAOpenGLLayer *)(ca_sink->layer) setNeedsDisplay];
+ [CATransaction commit];
+
+ GST_TRACE ("post redisplay");
+
+ if (stored_buffer)
+ gst_buffer_unref (stored_buffer);
+ if (old_sync)
+ gst_buffer_unref (old_sync);
+
+ return GST_FLOW_OK;
+}
+
+static gboolean
+gst_ca_opengl_layer_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+ GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
+ GstBufferPool *pool = NULL;
+ GstStructure *config;
+ GstCaps *caps;
+ GstVideoInfo info;
+ guint size;
+ gboolean need_pool;
+
+ if (!_ensure_gl_setup (ca_sink))
+ return FALSE;
+
+ gst_query_parse_allocation (query, &caps, &need_pool);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ if (!gst_video_info_from_caps (&info, caps))
+ goto invalid_caps;
+
+ /* the normal size of a frame */
+ size = info.size;
+
+ if (need_pool) {
+ GST_DEBUG_OBJECT (ca_sink, "create new pool");
+
+ pool = gst_gl_buffer_pool_new (ca_sink->context);
+ config = gst_buffer_pool_get_config (pool);
+ gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+
+ if (!gst_buffer_pool_set_config (pool, config))
+ goto config_failed;
+ }
+
+ /* we need at least 2 buffer because we hold on to the last one */
+ gst_query_add_allocation_pool (query, pool, size, 2, 0);
+ if (pool)
+ gst_object_unref (pool);
+
+ if (ca_sink->context->gl_vtable->FenceSync)
+ gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
+
+ return TRUE;
+
+ /* ERRORS */
+no_caps:
+ {
+ GST_DEBUG_OBJECT (bsink, "no caps specified");
+ return FALSE;
+ }
+invalid_caps:
+ {
+ GST_DEBUG_OBJECT (bsink, "invalid caps specified");
+ return FALSE;
+ }
+config_failed:
+ {
+ GST_DEBUG_OBJECT (bsink, "failed setting config");
+ return FALSE;
+ }
+}
+
+/* *INDENT-OFF* */
+static const GLfloat vertices[] = {
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 1.0f
+};
+
+static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
+/* *INDENT-ON* */
+
+static void
+_bind_buffer (GstCAOpenGLLayerSink * ca_sink)
+{
+ const GstGLFuncs *gl = ca_sink->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, ca_sink->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, ca_sink->vertex_buffer);
+
+ /* Load the vertex position */
+ gl->VertexAttribPointer (ca_sink->attr_position, 3, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) 0);
+
+ /* Load the texture coordinate */
+ gl->VertexAttribPointer (ca_sink->attr_texture, 2, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+
+ gl->EnableVertexAttribArray (ca_sink->attr_position);
+ gl->EnableVertexAttribArray (ca_sink->attr_texture);
+}
+
+static void
+_unbind_buffer (GstCAOpenGLLayerSink * ca_sink)
+{
+ const GstGLFuncs *gl = ca_sink->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (ca_sink->attr_position);
+ gl->DisableVertexAttribArray (ca_sink->attr_texture);
+}
+
+/* Called in the gl thread */
+static void
+gst_ca_opengl_layer_sink_thread_init_redisplay (GstCAOpenGLLayerSink * ca_sink)
+{
+ const GstGLFuncs *gl = ca_sink->context->gl_vtable;
+ GError *error = NULL;
+
+ if (!(ca_sink->redisplay_shader = gst_gl_shader_new_default (ca_sink->context, &error))) {
+ GST_ERROR_OBJECT (ca_sink, "Failed to link shader: %s", error->message);
+ gst_ca_opengl_layer_sink_cleanup_glthread (ca_sink);
+ return;
+ }
+
+ ca_sink->attr_position =
+ gst_gl_shader_get_attribute_location (ca_sink->redisplay_shader,
+ "a_position");
+ ca_sink->attr_texture =
+ gst_gl_shader_get_attribute_location (ca_sink->redisplay_shader,
+ "a_texcoord");
+
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &ca_sink->vao);
+ gl->BindVertexArray (ca_sink->vao);
+ }
+
+ if (!ca_sink->vertex_buffer) {
+ gl->GenBuffers (1, &ca_sink->vertex_buffer);
+ gl->BindBuffer (GL_ARRAY_BUFFER, ca_sink->vertex_buffer);
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+ }
+
+ if (!ca_sink->vbo_indices) {
+ gl->GenBuffers (1, &ca_sink->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, ca_sink->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+ }
+
+ if (gl->GenVertexArrays) {
+ _bind_buffer (ca_sink);
+ gl->BindVertexArray (0);
+ }
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+}
+
+static void
+gst_ca_opengl_layer_sink_cleanup_glthread (GstCAOpenGLLayerSink * ca_sink)
+{
+ const GstGLFuncs *gl = ca_sink->context->gl_vtable;
+
+ if (ca_sink->redisplay_shader) {
+ gst_object_unref (ca_sink->redisplay_shader);
+ ca_sink->redisplay_shader = NULL;
+ }
+
+ if (ca_sink->vao) {
+ gl->DeleteVertexArrays (1, &ca_sink->vao);
+ ca_sink->vao = 0;
+ }
+
+ if (ca_sink->vbo_indices) {
+ gl->DeleteBuffers (1, &ca_sink->vbo_indices);
+ ca_sink->vbo_indices = 0;
+ }
+
+ if (ca_sink->vertex_buffer) {
+ gl->DeleteBuffers (1, &ca_sink->vertex_buffer);
+ ca_sink->vertex_buffer = 0;
+ }
+}
+
+static void
+gst_ca_opengl_layer_sink_on_resize (GstCAOpenGLLayerSink * ca_sink, gint width, gint height)
+{
+ /* Here ca_sink members (ex:ca_sink->info) have a life time of set_caps.
+ * It means that they cannot not change between two set_caps
+ */
+ const GstGLFuncs *gl = ca_sink->context->gl_vtable;
+
+ GST_TRACE ("GL Window resized to %ux%u", width, height);
+
+ width = MAX (1, width);
+ height = MAX (1, height);
+
+ ca_sink->window_width = width;
+ ca_sink->window_height = height;
+
+ /* default reshape */
+ if (ca_sink->keep_aspect_ratio) {
+ GstVideoRectangle src, dst, result;
+
+ src.x = 0;
+ src.y = 0;
+ src.w = GST_VIDEO_SINK_WIDTH (ca_sink);
+ src.h = GST_VIDEO_SINK_HEIGHT (ca_sink);
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = width;
+ dst.h = height;
+
+ gst_video_sink_center_rect (src, dst, &result, TRUE);
+ gl->Viewport (result.x, result.y, result.w, result.h);
+ } else {
+ gl->Viewport (0, 0, width, height);
+ }
+}
+
+static void
+gst_ca_opengl_layer_sink_on_draw (GstCAOpenGLLayerSink * ca_sink)
+{
+ /* Here ca_sink members (ex:ca_sink->info) have a life time of set_caps.
+ * It means that they cannot not change between two set_caps as well as
+ * for the redisplay_texture size.
+ * Whereas redisplay_texture id changes every sink_render
+ */
+
+ const GstGLFuncs *gl = NULL;
+ GstGLSyncMeta *sync_meta;
+
+ g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (ca_sink));
+
+ gl = ca_sink->context->gl_vtable;
+
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+
+ if (G_UNLIKELY (!ca_sink->redisplay_shader)) {
+ gst_ca_opengl_layer_sink_thread_init_redisplay (ca_sink);
+ }
+
+ /* check if texture is ready for being drawn */
+ if (!ca_sink->redisplay_texture) {
+ gl->ClearColor (0.0f, 0.0f, 0.0f, 1.0f);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+ return;
+ }
+
+ /* opengl scene */
+ GST_TRACE ("redrawing texture:%u", ca_sink->redisplay_texture);
+
+ if (ca_sink->caps_change) {
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+ gst_ca_opengl_layer_sink_on_resize (ca_sink, ca_sink->window_width,
+ ca_sink->window_height);
+ GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
+ ca_sink->caps_change = FALSE;
+ }
+
+ sync_meta = gst_buffer_get_gl_sync_meta (ca_sink->stored_sync);
+ if (sync_meta)
+ gst_gl_sync_meta_wait (sync_meta, gst_gl_context_get_current ());
+
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->ClearColor (0.0, 0.0, 0.0, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+
+ gst_gl_shader_use (ca_sink->redisplay_shader);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (ca_sink->vao);
+ _bind_buffer (ca_sink);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, ca_sink->redisplay_texture);
+ gst_gl_shader_set_uniform_1i (ca_sink->redisplay_shader, "tex", 0);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (ca_sink);
+
+ /* end default opengl scene */
+ GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
+}
diff --git a/ext/gl/effects/gstgleffectblur.c b/ext/gl/effects/gstgleffectblur.c
new file mode 100644
index 000000000..074519ad4
--- /dev/null
+++ b/ext/gl/effects/gstgleffectblur.c
@@ -0,0 +1,68 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ * Copyright (C) 2015 Michał Dębski <debski.mi.zd@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+static float *
+gst_gl_effects_blur_kernel (void)
+{
+ /* gaussian kernel (well, actually vector), size 9, standard
+ * deviation 3.0 */
+ /* FIXME: make this a runtime property */
+ static gfloat *kernel = NULL;
+ if (G_UNLIKELY (NULL == kernel)) {
+ /* 3x3 matrix */
+ kernel = g_malloc (sizeof (gfloat) * 9);
+ fill_gaussian_kernel (kernel, 7, 3.f);
+ }
+ return kernel;
+}
+
+void
+gst_gl_effects_blur (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "hconv0",
+ hconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_width",
+ GST_VIDEO_INFO_WIDTH (&filter->in_info));
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 7,
+ gst_gl_effects_blur_kernel ());
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->midtexture[0], shader);
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "vconv0",
+ vconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_height",
+ GST_VIDEO_INFO_HEIGHT (&filter->in_info));
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 7,
+ gst_gl_effects_blur_kernel ());
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[0],
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectbulge.c b/ext/gl/effects/gstgleffectbulge.c
new file mode 100644
index 000000000..d2a9a14cb
--- /dev/null
+++ b/ext/gl/effects/gstgleffectbulge.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_bulge (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "bulge",
+ bulge_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectfisheye.c b/ext/gl/effects/gstgleffectfisheye.c
new file mode 100644
index 000000000..fe3ebdcde
--- /dev/null
+++ b/ext/gl/effects/gstgleffectfisheye.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_fisheye (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "fisheye",
+ fisheye_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectglow.c b/ext/gl/effects/gstgleffectglow.c
new file mode 100644
index 000000000..6309ce774
--- /dev/null
+++ b/ext/gl/effects/gstgleffectglow.c
@@ -0,0 +1,86 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+static gboolean kernel_ready = FALSE;
+static float gauss_kernel[7];
+
+void
+gst_gl_effects_glow (GstGLEffects * effects)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (effects)->context->gl_vtable;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ if (!kernel_ready) {
+ fill_gaussian_kernel (gauss_kernel, 7, 10.0);
+ kernel_ready = TRUE;
+ }
+
+ /* threshold */
+ shader = gst_gl_effects_get_fragment_shader (effects, "luma_threshold",
+ luma_threshold_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->midtexture[0], shader);
+
+ /* blur */
+ shader = gst_gl_effects_get_fragment_shader (effects, "hconv7",
+ hconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 7, gauss_kernel);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[0],
+ effects->midtexture[1], shader);
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "vconv7",
+ vconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 7, gauss_kernel);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[1],
+ effects->midtexture[2], shader);
+
+ /* add blurred luma to intexture */
+ shader = gst_gl_effects_get_fragment_shader (effects, "sum",
+ sum_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+
+ gl->ActiveTexture (GL_TEXTURE2);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (effects->intexture));
+
+ gst_gl_shader_set_uniform_1f (shader, "alpha", 1.0f);
+ gst_gl_shader_set_uniform_1i (shader, "base", 2);
+
+ gl->ActiveTexture (GL_TEXTURE1);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (effects->midtexture[2]));
+
+ gst_gl_shader_set_uniform_1f (shader, "beta", (gfloat) 1 / 3.5f);
+ gst_gl_shader_set_uniform_1i (shader, "blend", 1);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[2],
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectidentity.c b/ext/gl/effects/gstgleffectidentity.c
new file mode 100644
index 000000000..b81c32df2
--- /dev/null
+++ b/ext/gl/effects/gstgleffectidentity.c
@@ -0,0 +1,49 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_identity (GstGLEffects * effects)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (effects)->context;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = g_hash_table_lookup (effects->shaderstable, "identity0");
+ if (!shader) {
+ GError *error = NULL;
+
+ if (!(shader = gst_gl_shader_new_default (context, &error))) {
+ GST_ELEMENT_ERROR (effects, RESOURCE, NOT_FOUND,
+ ("Failed to initialize identity shader: %s", error->message), (NULL));
+ return;
+ }
+
+ g_hash_table_insert (effects->shaderstable, (gchar *) "identity0", shader);
+ }
+ gst_gl_shader_use (shader);
+
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectlaplacian.c b/ext/gl/effects/gstgleffectlaplacian.c
new file mode 100644
index 000000000..4652b0fe5
--- /dev/null
+++ b/ext/gl/effects/gstgleffectlaplacian.c
@@ -0,0 +1,52 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008-2010 Filippo Argiolas <filippo.argiolas@gmail.com>
+ * Copyright (C) 2015 Michał Dębski <debski.mi.zd@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+static gfloat kernel[9] = {
+ 0.0, -1.0, 0.0,
+ -1.0, 4.0, -1.0,
+ 0.0, -1.0, 0.0
+};
+
+void
+gst_gl_effects_laplacian (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "conv0",
+ conv9_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->in_info));
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->in_info));
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 9, kernel);
+ gst_gl_shader_set_uniform_1i (shader, "invert", effects->invert);
+
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectlumatocurve.c b/ext/gl/effects/gstgleffectlumatocurve.c
new file mode 100644
index 000000000..a9c2a5b31
--- /dev/null
+++ b/ext/gl/effects/gstgleffectlumatocurve.c
@@ -0,0 +1,92 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+#include "gstgleffectlumatocurve.h"
+
+void
+gst_gl_effects_luma_to_curve (GstGLEffects * effects,
+ const GstGLEffectsCurve * curve, gint curve_index, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (effects)->context;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ const GstGLFuncs *gl = context->gl_vtable;
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "luma_to_curve",
+ luma_to_curve_fragment_source_gles2);
+
+ if (!shader)
+ return;
+
+#if GST_GL_HAVE_OPENGL
+ if (USING_OPENGL (context)) {
+ gl->MatrixMode (GL_PROJECTION);
+ gl->LoadIdentity ();
+ }
+#endif
+
+ if (effects->curve[curve_index] == 0) {
+ /* this parameters are needed to have a right, predictable, mapping */
+ gl->GenTextures (1, &effects->curve[curve_index]);
+
+ gl->BindTexture (GL_TEXTURE_2D, effects->curve[curve_index]);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ 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->TexImage2D (GL_TEXTURE_2D, 0, GL_RGB,
+ curve->width, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, curve->pixel_data);
+ }
+
+ gst_gl_shader_use (shader);
+ gl->ActiveTexture (GL_TEXTURE2);
+ gl->BindTexture (GL_TEXTURE_2D, effects->curve[curve_index]);
+
+ gst_gl_shader_set_uniform_1i (shader, "curve", 2);
+
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex, shader);
+}
+
+void
+gst_gl_effects_heat (GstGLEffects * effects)
+{
+ gst_gl_effects_luma_to_curve (effects, &heat_curve,
+ GST_GL_EFFECTS_CURVE_HEAT, effects->intexture, effects->outtexture);
+}
+
+void
+gst_gl_effects_sepia (GstGLEffects * effects)
+{
+ gst_gl_effects_luma_to_curve (effects, &sepia_curve,
+ GST_GL_EFFECTS_CURVE_SEPIA, effects->intexture, effects->outtexture);
+}
+
+void
+gst_gl_effects_luma_xpro (GstGLEffects * effects)
+{
+ gst_gl_effects_luma_to_curve (effects, &luma_xpro_curve,
+ GST_GL_EFFECTS_CURVE_LUMA_XPRO, effects->intexture, effects->outtexture);
+}
diff --git a/ext/gl/effects/gstgleffectlumatocurve.h b/ext/gl/effects/gstgleffectlumatocurve.h
new file mode 100644
index 000000000..f90577f38
--- /dev/null
+++ b/ext/gl/effects/gstgleffectlumatocurve.h
@@ -0,0 +1,35 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_LUMA_TO_CURVE_H__
+#define __GST_GL_LUMA_TO_CURVE_H__
+
+#include "gstgleffectscurves.h"
+
+G_BEGIN_DECLS
+
+void gst_gl_effects_luma_to_curve (GstGLEffects *effects,
+ const GstGLEffectsCurve *curve,
+ gint curve_index,
+ GstGLMemory *in_tex,
+ GstGLMemory *out_tex);
+G_END_DECLS
+
+#endif /* __GST_GL_LUMA_TO_CURVE_H__ */
diff --git a/ext/gl/effects/gstgleffectmirror.c b/ext/gl/effects/gstgleffectmirror.c
new file mode 100644
index 000000000..a25d8745d
--- /dev/null
+++ b/ext/gl/effects/gstgleffectmirror.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_mirror (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "mirror",
+ mirror_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectrgbtocurve.c b/ext/gl/effects/gstgleffectrgbtocurve.c
new file mode 100644
index 000000000..5bead68e4
--- /dev/null
+++ b/ext/gl/effects/gstgleffectrgbtocurve.c
@@ -0,0 +1,78 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+#include "gstgleffectscurves.h"
+
+static void
+gst_gl_effects_rgb_to_curve (GstGLEffects * effects,
+ const GstGLEffectsCurve * curve, gint curve_index, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (effects)->context;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ const GstGLFuncs *gl = context->gl_vtable;
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "rgb_to_curve",
+ rgb_to_curve_fragment_source_gles2);
+
+ if (!shader)
+ return;
+
+#if GST_GL_HAVE_OPENGL
+ if (USING_OPENGL (context)) {
+ gl->MatrixMode (GL_PROJECTION);
+ gl->LoadIdentity ();
+ }
+#endif
+
+ if (effects->curve[curve_index] == 0) {
+ /* this parameters are needed to have a right, predictable, mapping */
+ gl->GenTextures (1, &effects->curve[curve_index]);
+
+ gl->BindTexture (GL_TEXTURE_2D, effects->curve[curve_index]);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ 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->TexImage2D (GL_TEXTURE_2D, 0, GL_RGB,
+ curve->width, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, curve->pixel_data);
+ }
+
+ gst_gl_shader_use (shader);
+ gl->ActiveTexture (GL_TEXTURE2);
+ gl->BindTexture (GL_TEXTURE_2D, effects->curve[curve_index]);
+
+ gst_gl_shader_set_uniform_1i (shader, "curve", 2);
+
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex, shader);
+}
+
+void
+gst_gl_effects_xpro (GstGLEffects * effects)
+{
+ gst_gl_effects_rgb_to_curve (effects, &xpro_curve, GST_GL_EFFECTS_CURVE_XPRO,
+ effects->intexture, effects->outtexture);
+}
diff --git a/ext/gl/effects/gstgleffectscurves.c b/ext/gl/effects/gstgleffectscurves.c
new file mode 100644
index 000000000..d16361bea
--- /dev/null
+++ b/ext/gl/effects/gstgleffectscurves.c
@@ -0,0 +1,210 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgleffectscurves.h"
+
+/* CURVE for the heat signature effect */
+const GstGLEffectsCurve xpro_curve = {
+ 256, 1, 3,
+ "\0\0\37\0\0\37\0\1\40\0\2!\0\2\"\0\3\"\1\4%\1\4%\1\5%\1\5'\1\7'\1\7(\1\7"
+ "(\1\10*\1\11+\1\11,\1\12,\1\13/\1\14/\1\14" "1\2\15" "1\2\15" "1\2\16"
+ "4\2\17" "" "4\3\17" "5\3\22" "7\3\22" "7\3\23" "8\3\24"
+ "9\3\25;\3\26;\3\27<\3\27=\4\31"
+ "=\4\33?\4\34@\5\34B\5\35C\5\36D\5\40D\5\40G\5!G\6\"H\6$H\7&J\7&K\7*M\7*M"
+ "\10+N\10-P\11-P\11/R\11" "3R\11" "3T\12" "4U\12" "5U\13" "7W\14" "8Y\14"
+ "9Y\14"
+ "<Y\16=[\16@^\16@^\17C^\17D`\20F`\20Jb\22Jb\22Kc\23Me\24Nf\25Qg\26Rg\27Ti"
+ "\27Wj\30Xl\31Yl\33\\m\34^p\35`p\40bp\40fq!fr$gt$lt%lu'mv(px*qy-ty/uz/x|0"
+ "y}3|}4}~5\177\2018\203\2019\203\201;\204\202=\207\203?\210\204@\214\204C"
+ "\214\206D\216\207G\217\210H\223\211K\223\211M\225\212P\226\214Q\231\215T"
+ "\232\215U\234\216X\235\217Y\240\220\\\241\220^\243\221`\244\223b\246\224"
+ "e\250\224f\252\225i\253\226l\255\227m\256\231p\261\231q\262\232t\264\233"
+ "v\265\234x\267\234z\270\235|\271\236~\274\240\201\275\240\202\277\241\204"
+ "\300\242\207\302\243\210\303\243\212\305\244\214\306\245\216\307\246\220"
+ "\311\250\221\313\250\224\315\251\226\316\252\227\317\253\232\321\253\234"
+ "\322\254\235\323\255\240\325\256\242\326\256\242\330\256\245\331\261\250"
+ "\331\262\251\332\262\253\334\263\255\335\264\256\336\265\261\340\266\263"
+ "\341\266\264\342\267\266\343\270\270\344\271\271\344\271\271\346\273\276"
+ "\347\274\277\350\275\277\351\275\302\352\276\304\353\277\306\353\300\307"
+ "\355\300\311\356\301\314\356\302\315\357\303\317\360\304\320\360\304\322"
+ "\361\305\323\362\306\325\362\307\327\363\307\330\363\310\330\364\311\333"
+ "\364\313\334\365\313\336\365\314\340\365\314\342\366\316\342\366\316\346"
+ "\367\317\347\367\320\351\367\320\353\370\322\354\370\322\356\370\323\356"
+ "\370\324\360\371\325\360\371\325\363\371\326\363\371\327\363\372\330\365"
+ "\372\330\366\372\331\366\372\331\370\372\332\371\373\332\371\373\333\372"
+ "\373\334\373\373\335\373\373\336\374\373\336\374\374\337\374\374\340\375"
+ "\374\341\375\374\341\376\374\342\376\374\343\376\374\344\376\374\344\377"
+ "\374\345\377\374\346\377\375\346\377\375\346\377\375\347\377\375\350\377"
+ "\375\351\377\375\352\377\375\352\377\375\352\377\375\353\377\375\353\377"
+ "\376\354\377\376\354\377\376\356\377\376\356\377\376\356\377\376\357\377"
+ "\376\360\377\376\360\377\376\360\377\376\360\377\376\362\377\376\362\377"
+ "\376\363\377\376\363\377\376\363\377\376\363\377\376\364\377\376\364\377"
+ "\376\365\377\377\365\377\377\366\377\377\366\377\377\366\377\377\367\377"
+ "\377\367\377\377\367\377\377\370",
+};
+
+const GstGLEffectsCurve luma_xpro_curve = {
+ 256, 1, 3,
+ "\0\0\1\0\1\1\0\1\2\0\1\2\0\1\2\0\1\2\0\1\3\0\2\3\0\2\4\0\2\4\0\2\5\0\2\6"
+ "\0\3\6\0\3\6\0\3\7\0\3\10\0\4\10\0\4\11\0\4\12\0\4\12\1\4\13\1\5\14\1\5\15"
+ "\1\5\15\1\5\16\1\5\17\1\6\17\1\6\20\1\7\21\1\7\23\2\10\23\2\10\24\2\10\24"
+ "\2\11\26\2\11\27\2\11\30\3\11\31\3\12\31\3\13\32\3\13\33\4\13\34\4\14\35"
+ "\4\14\36\4\15\37\5\15\40\5\15\"\6\16#\6\17#\6\20$\7\20%\7\20%\10\21&\10\22"
+ "'\10\22)\11\24*\11\24*\12\24+\12\26.\13\26.\14\27.\14\30/\15\31"
+ "1\15\31" "" "2\16\32" "3\16\33" "3\20\33" "5\20\35" "6\20\36" "7\22\37"
+ "7\23\"9\23#9\24$" ":\25$<\27&<\27'=\31'?\31)?\32*@\35+B\35-C\36.C\37"
+ "1E\"2F#3H$5H&6I'7I)7K"
+ "+:M-<N-<N1@N1BQ2BQ6CQ6EU7HU<IU=IV@NX@NXBQZCS[FU[HU]IV_MZ_N[_Q]`S_bU`dXbe"
+ "Zbe]gg]ig`ji`jjdljenlgpljqnpwppwpqxqszqw|sx}u|\201u}\203w\177\204x\177\206"
+ "x\204\210x\204\212z\212\213|\212\217}\217\221}\221\222}\222\224\177\226\224"
+ "\201\226\226\203\230\233\203\235\235\204\236\235\204\236\236\210\244\242"
+ "\210\244\242\210\245\244\212\251\245\213\252\251\213\254\252\215\257\254"
+ "\215\261\256\217\261\257\221\266\261\221\267\261\222\273\264\224\273\267"
+ "\224\274\267\224\276\273\230\301\273\230\302\274\231\304\276\231\307\302"
+ "\233\312\302\235\312\304\235\314\306\236\316\307\240\320\311\240\321\314"
+ "\242\324\315\242\325\316\244\327\320\245\332\321\245\333\321\245\334\323"
+ "\251\335\325\251\335\330\252\341\332\254\341\332\256\344\334\256\346\334"
+ "\256\347\337\257\347\340\261\350\340\263\351\342\263\352\344\264\354\345"
+ "\266\355\346\266\356\347\267\357\350\271\360\351\273\361\352\273\362\352"
+ "\274\362\355\276\364\356\277\364\357\277\365\360\277\365\360\302\366\361"
+ "\302\367\361\304\370\363\304\370\363\307\370\364\307\371\364\311\371\366"
+ "\312\372\367\312\372\367\314\372\367\316\373\370\316\373\370\320\373\371"
+ "\320\374\371\321\374\372\323\374\372\324\375\372\325\375\372\325\375\373"
+ "\327\375\373\330\375\374\332\375\374\333\375\375\334\376\375\334\376\375"
+ "\335\376\375\337\376\375\337\376\375\340\376\376\342\376\376\344\376\376"
+ "\344\376\376\345\376\376\346\376\376\347\376\376\350\377\376\351\377\377"
+ "\353\377\377\354\377\377\356\377\377\357\377\377\361\377\377\361\377\377"
+ "\361\377\377\364\377\377\365\377\377\366\377\377\367\377\377\367\377\377"
+ "\371\377\377\371\377\377\372\377\377\373\377\377\373\377\377\374\377\377"
+ "\375\377\377\375\377\377\375\377\377\376",
+};
+
+/* CURVE for the heat signature effect */
+const GstGLEffectsCurve heat_curve = {
+ 256, 1, 3,
+ "\0\0\0\0\0\0\0\1\0\0\1\0\0\1\1\0\2\1\0\2\1\1\2\1\1\2\2\1\2\2\1\3\2\1\3\3"
+ "\1\3\3\1\4\3\1\4\4\1\5\4\1\5\5\2\5\6\2\6\6\2\6\7\2\6\7\2\7\7\2\7\11\2\10"
+ "\11\2\10\12\3\11\13\3\11\13\3\11\14\3\12\15\3\12\17\3\13\17\3\14\20\3\14"
+ "\22\4\15\23\4\16\24\4\16\26\4\16\27\4\17\31\4\20\34\4\21\34\5\21\40\5\22"
+ "\40\5\22$\5\23$\5\25&\6\25(\6\26-\6\26-\6\27" "0\6\31" "2\7\31"
+ "5\7\32;\7\34"
+ ";\7\34?\10\35C\10\36G\10\37L\10\40V\11!V\11\"[\11$a\11&l\12&l\12'r\12(~\13"
+ "*~\13,\204\14,\213\14.\221\14/\227\14" "1\236\15" "2\244\15" "4\252\15"
+ "5\260" "\16" "7\267\16"
+ "8\275\17:\302\17;\310\17=\323\20?\323\21@\330\21D\335\21D"
+ "\342\22E\346\22I\353\23I\356\23K\362\24M\365\24N\370\25P\372\26R\374\26T"
+ "\376\26V\377\27X\377\27Z\377\30\\\376\31`\376\31`\375\32b\373\32d\371\33"
+ "f\366\34j\363\34j\360\35l\354\36n\350\36r\344\37r\337\40t\333\40w\326!y\321"
+ "\"|\314#~\307$\201\301$\204\267%\207\267&\212\261'\214\254(\217\247(\222"
+ "\241)\226\234*\231\227+\234\222,\237\216-\242\211.\245\205/\251\2010\254"
+ "}1\257z2\262w3\266t4\271p5\274m6\277j7\302f8\305c9\310`:\314\\;\317Y<\321"
+ "V>\324S?\327P@\332LA\335IB\337FC\342CE\344@F\347=G\351;I\3538I\3558M\357"
+ "3P\3610S\363.V\365+Y\366)\\\370'`\371%d\372#g\373\"l\374\40p\374\37t\374"
+ "\35t\375\34}\376\33\202\376\32\202\375\31\213\375\30\220\375\27\225\375\27"
+ "\232\373\26\237\372\25\244\371\24\251\370\23\256\367\23\262\367\22\267\364"
+ "\21\274\362\20\300\361\20\305\357\17\311\355\16\311\353\16\322\351\15\326"
+ "\346\15\332\346\14\336\344\14\341\337\13\341\335\13\350\332\12\353\330\11"
+ "\356\330\11\360\322\10\362\320\10\364\320\10\364\312\7\366\307\7\366\304"
+ "\7\367\302\6\367\277\6\370\274\5\367\271\5\367\271\5\367\263\4\365\260\4"
+ "\364\255\4\363\253\3\362\250\3\361\245\3\360\242\3\357\240\3\357\235\2\355"
+ "\232\2\355\227\2\354\225\2\353\221\1\353\216\1\353\216\1\353\213\1\353\204"
+ "\1\353\201\1\354}\1\354y\0\354v\0\355r\0\355n\0\355j\0\356f\0\356b\0\357"
+ "_\0\357[\0\357W\0\357S\0\360O\0\360O\0\361K\0\361C\0\362@\0\363<\0\3638\0"
+ "\3648\0\3641\0\365.\0\366+\0\366'\0\367'\0\370!\0\370\36\0\370\33\0\371\30"
+ "\0\371\26\0\373\26\0\373\23\0\374\15\0\374\13\0\375\10\0\375\5\0\376\3\0",
+};
+
+const GstGLEffectsCurve sepia_curve = {
+ 256, 1, 3,
+ "\0\0\0\0\0\0\0\0\0\0\1\0\1\1\0\1\1\0\1\1\1\2\1\1\2\2\1\3\2\1\3\2\1\3\2\1"
+ "\4\3\2\4\3\2\4\3\2\6\4\2\6\4\2\6\4\2\7\5\2\7\5\3\11\6\3\11\6\3\12\7\3\13"
+ "\10\3\15\10\4\16\11\4\17\11\4\21\12\4\22\13\4\22\13\5\23\14\5\24\15\5\26"
+ "\16\6\31\20\6\31\21\6\32\22\7\34\22\7\35\23\7\40\24\10\40\26\10!\26\11#\30"
+ "\11&\31\12&\32\12'\34\13)\34\13*\37\13,\37\13-\40\14.\"\15" "0\"\15"
+ "2#\17" "" "3&\17" "4&\17" "5'\20" "8(\21"
+ "9)\21:*\23<,\23=-\23A.\24A0\25B0\25C2\26D3"
+ "\30H4\30H7\31K7\32K8\32L9\33M:\34P<\34Q=\35S>\37T?\37UA\40VB!XC!ZD#\\F#^"
+ "G#^J$`J&bK'bM'eM(fO)gP)iQ*kS,mT-mU-nV.oX/rY0sZ2u]2v]3w^3x`4za5{c7|c8~e8\177"
+ "f9\200i:\203i<\204j<\206k=\207m>\210n?\211o?\213qA\214rC\215sC\217uD\220"
+ "vD\221wF\223xG\224zH\225{J\227|K\230~K\231\177L\232\200M\234\202O\235\203"
+ "P\236\204Q\240\206Q\241\207S\242\210T\243\211U\245\213V\246\214X\247\215"
+ "Y\250\217Y\252\220Z\253\221\\\254\223]\254\224^\255\225`\257\227a\260\230"
+ "b\261\231c\262\232e\264\234e\265\235f\266\236g\267\240i\267\241i\272\242"
+ "k\273\243m\274\245n\274\246o\276\247q\277\250r\300\252s\301\253u\302\254"
+ "v\304\255w\305\257x\306\257z\306\261{\307\262|\310\264~\310\265\177\313\266"
+ "\200\314\267\202\315\267\203\316\272\204\317\273\206\317\274\207\320\276"
+ "\210\322\277\211\323\277\213\324\301\214\325\302\215\326\304\217\326\305"
+ "\220\327\306\221\327\307\223\331\310\224\333\311\225\334\311\227\334\313"
+ "\227\335\315\231\335\316\231\337\317\234\340\320\235\341\320\235\341\323"
+ "\240\342\324\241\343\324\242\343\326\243\345\327\245\345\330\245\346\331"
+ "\250\346\333\252\347\334\253\351\335\254\351\335\255\351\337\257\352\340"
+ "\260\353\341\260\354\342\262\355\343\264\355\344\265\355\345\266\356\346"
+ "\266\356\347\272\357\350\273\360\351\274\360\351\276\361\352\277\361\353"
+ "\300\362\353\301\362\354\302\362\355\304\362\356\305\364\357\305\364\357"
+ "\310\364\360\311\365\361\313\365\361\314\366\362\315\366\362\316\366\363"
+ "\316\367\364\320\367\364\320\367\365\324\367\365\324\370\366\326\370\366"
+ "\327\371\366\330\371\367\331\371\367\333\371\370\333\372\370\336\372\370"
+ "\336\372\371\340\373\371\341\373\372\342\373\372\343\374\372\344\374\373"
+ "\344\374\373\347\374\374\350\375\374\351\375\374\351\375\374\352\375\375"
+ "\352\376\375\353\376\376\355\376\376\356\376\376\357\377\377\357",
+};
+
+const GstGLEffectsCurve xray_curve = {
+ 256, 1, 3,
+ "\377\377\377\377\377\377\376\376\376\375\375\376\374\375\375\373\374\375"
+ "\372\374\374\371\374\374\370\373\373\366\373\372\366\372\372\365\372\371"
+ "\363\371\371\363\371\370\362\370\370\360\370\367\360\367\366\357\367\365"
+ "\356\366\365\355\366\364\353\365\363\353\365\363\352\364\362\351\363\362"
+ "\347\363\361\346\362\361\345\362\361\344\362\360\343\361\357\343\361\356"
+ "\342\360\356\341\360\356\340\357\355\336\356\354\336\356\354\335\355\353"
+ "\334\355\353\333\355\352\331\354\351\331\353\351\330\353\350\327\353\350"
+ "\325\352\347\325\351\347\324\350\346\323\350\345\322\347\344\321\347\344"
+ "\320\347\344\317\346\343\316\346\342\315\345\341\314\344\341\313\344\340"
+ "\312\344\340\311\343\337\310\342\337\307\342\335\306\341\335\305\341\335"
+ "\303\340\334\303\337\333\302\337\333\301\337\332\300\336\331\276\335\331"
+ "\276\334\330\274\334\330\274\334\327\273\333\327\272\333\326\271\332\325"
+ "\270\332\325\267\331\324\266\330\323\265\330\323\264\327\322\263\327\321"
+ "\262\326\320\261\325\320\257\325\317\257\324\317\256\324\316\254\323\315"
+ "\254\322\315\253\322\314\252\321\313\251\321\313\250\320\312\246\317\311"
+ "\245\317\311\245\316\310\244\316\307\243\315\307\242\314\306\241\314\305"
+ "\240\312\305\237\312\304\236\312\303\235\311\303\234\311\302\233\307\301"
+ "\232\307\300\231\307\300\230\306\277\227\305\276\226\305\276\225\304\275"
+ "\224\303\274\223\303\273\222\302\273\221\301\272\220\301\271\217\300\270"
+ "\216\277\270\215\277\267\214\276\266\213\275\265\212\275\265\211\274\264"
+ "\210\273\263\207\273\262\206\272\262\205\271\261\204\270\260\203\270\257"
+ "\202\267\257\201\266\256\200\266\255\177\265\254~\264\253}\263\253|\263\252"
+ "{\262\251z\261\250y\260\247x\260\247w\257\246v\256\245u\255\244t\255\243"
+ "s\254\243r\253\242q\252\241p\252\240o\251\237n\250\236m\247\235l\246\235"
+ "l\246\235j\245\233i\244\232h\243\231g\242\230f\242\227e\241\226d\240\226"
+ "c\237\225b\236\224a\235\223`\234\222_\234\221_\233\220]\232\217\\\231\216"
+ "\\\230\215Z\227\214Y\226\214X\226\213W\225\212V\224\211U\223\210T\222\207"
+ "S\221\206R\221\205Q\217\204P\216\203O\215\202N\215\201M\214\200M\213\177"
+ "K\212~J\211}I\211|H\210|G\206zG\205zE\204xD\203vC\203vB\201tA\200s@\200q"
+ "@~p>}o>|o<{l<yk;xi9wh8wg8te6sd5qd4pa3n_2m]1k\\0j\\0hY.fW-dU,cT+aR*_P)_O("
+ "]M'YK'XI%VI$TF$RD\"OB!M@\40K?\37I=\37G=\35E9\34C9\34A5\33>5\31<2\31<0\27"
+ ":.\27" "5,\26" "3*\24"
+ "1*\23.&\22.&\22*\"\21'\40\17%\36\16\"\34\15\"\32\14"
+ "\36\32\13\33\26\13\31\24\11\26\22\11\24\20\7\24\16\6\21\16\5\14\14\4\12\10"
+ "\3\7\6\3\5\4\1\2\2",
+};
diff --git a/ext/gl/effects/gstgleffectscurves.h b/ext/gl/effects/gstgleffectscurves.h
new file mode 100644
index 000000000..9b566a50e
--- /dev/null
+++ b/ext/gl/effects/gstgleffectscurves.h
@@ -0,0 +1,47 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_EFFECTS_TEXTURES_H__
+#define __GST_GL_EFFECTS_TEXTURES_H__
+
+#include <glib.h>
+
+struct _GstGLEffectsCurve {
+ guint width;
+ guint height;
+ guint bytes_per_pixel; /* 3:RGB */
+ guint8 pixel_data[256 * 1 * 3 + 1];
+};
+
+typedef struct _GstGLEffectsCurve GstGLEffectsCurve;
+
+/* CURVE for the heat signature effect */
+extern const GstGLEffectsCurve xpro_curve;
+
+extern const GstGLEffectsCurve luma_xpro_curve;
+
+/* CURVE for the heat signature effect */
+extern const GstGLEffectsCurve heat_curve;
+
+extern const GstGLEffectsCurve sepia_curve;
+
+extern const GstGLEffectsCurve xray_curve;
+
+#endif
diff --git a/ext/gl/effects/gstgleffectsin.c b/ext/gl/effects/gstgleffectsin.c
new file mode 100644
index 000000000..24b66132e
--- /dev/null
+++ b/ext/gl/effects/gstgleffectsin.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_sin (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "sin",
+ sin_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectsobel.c b/ext/gl/effects/gstgleffectsobel.c
new file mode 100644
index 000000000..4fbe60f15
--- /dev/null
+++ b/ext/gl/effects/gstgleffectsobel.c
@@ -0,0 +1,61 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008-2010 Filippo Argiolas <filippo.argiolas@gmail.com>
+ * Copyright (C) 2015 Michał Dębski <debski.mi.zd@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_sobel (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "desat0",
+ desaturate_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->midtexture[0], shader);
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "hconv0",
+ sep_sobel_hconv3_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[0],
+ effects->midtexture[1], shader);
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "vconv0",
+ sep_sobel_vconv3_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[1],
+ effects->midtexture[0], shader);
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "len0",
+ sep_sobel_length_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1i (shader, "invert", effects->invert);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[0],
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectsquare.c b/ext/gl/effects/gstgleffectsquare.c
new file mode 100644
index 000000000..911d30fdf
--- /dev/null
+++ b/ext/gl/effects/gstgleffectsquare.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_square (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "square",
+ square_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectsqueeze.c b/ext/gl/effects/gstgleffectsqueeze.c
new file mode 100644
index 000000000..1999978d6
--- /dev/null
+++ b/ext/gl/effects/gstgleffectsqueeze.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_squeeze (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "squeeze",
+ squeeze_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectssources.c b/ext/gl/effects/gstgleffectssources.c
new file mode 100644
index 000000000..b83e1d7cc
--- /dev/null
+++ b/ext/gl/effects/gstgleffectssources.c
@@ -0,0 +1,548 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglconfig.h>
+
+#include "../gstgleffects.h"
+#include "gstgleffectssources.h"
+#include <math.h>
+
+/* A common file for sources is needed since shader sources can be
+ * generic and reused by several effects */
+
+/* FIXME */
+/* Move sooner or later into single .frag .vert files and either bake
+ * them into a c file at compile time or load them at run time */
+
+
+/* fill a normalized and zero centered gaussian vector for separable
+ * gaussian convolution */
+
+void
+fill_gaussian_kernel (float *kernel, int size, float sigma)
+{
+ int i;
+ float sum;
+ int l;
+
+ /* need an odd sized vector to center it at zero */
+ g_return_if_fail ((size % 2) != 0);
+
+ sum = 0.0;
+ l = (size - 1) / 2;
+
+ for (i = 0; i < size; i++) {
+ kernel[i] = expf (-0.5 * pow ((i - l) / sigma, 2.0));
+ sum += kernel[i];
+ }
+
+ for (i = 0; i < size; i++) {
+ kernel[i] /= sum;
+ }
+}
+
+/* *INDENT-OFF* */
+
+/* Mirror effect */
+const gchar *mirror_fragment_source_opengl =
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = gl_TexCoord[0].xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " normcoord.x *= sign (normcoord.x);"
+ " texturecoord = normcoord + 0.5;"
+ " vec4 color = texture2D (tex, texturecoord);"
+ " gl_FragColor = color * gl_Color;"
+ "}";
+
+const gchar *mirror_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " float normcoord = texturecoord.x - 0.5;"
+ " normcoord *= sign (normcoord);"
+ " texturecoord.x = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *squeeze_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord = texturecoord - 0.5;"
+ /* Add a very small value to length otherwise it could be 0 */
+ " float r = length (normcoord)+0.01;"
+ " r = pow(r, 0.40)*1.3;"
+ " normcoord = normcoord / r;"
+ " texturecoord = (normcoord + 0.5);"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *stretch_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " float r = length (normcoord);"
+ " normcoord *= 2.0 - smoothstep(0.0, 0.35, r);"
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *tunnel_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ /* little trick with normalized coords to obtain a circle with
+ * rect textures */
+ " normcoord = (texturecoord - 0.5);"
+ " float r = length(normcoord);"
+ " if (r > 0.0)"
+ " normcoord *= clamp (r, 0.0, 0.275) / r;"
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *fisheye_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " float r = length (normcoord);"
+ " normcoord *= r * 1.41421;" /* sqrt (2) */
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *twirl_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " float r = length (normcoord);"
+ /* calculate rotation angle: maximum (about pi/2) at the origin and
+ * gradually decrease it up to 0.6 of each quadrant */
+ " float phi = (1.0 - smoothstep (0.0, 0.3, r)) * 1.6;"
+ /* precalculate sin phi and cos phi, save some alu */
+ " float s = sin(phi);"
+ " float c = cos(phi);"
+ /* rotate */
+ " normcoord *= mat2(c, s, -s, c);"
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *bulge_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " float r = length (normcoord);"
+ " normcoord *= smoothstep (-0.05, 0.25, r);"
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *square_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec2 normcoord;"
+ " normcoord = texturecoord - 0.5;"
+ " float r = length (normcoord);"
+ " normcoord *= 1.0 + smoothstep(0.125, 0.25, abs(normcoord));"
+ " normcoord /= 2.0; /* zoom amount */"
+ " texturecoord = normcoord + 0.5;"
+ " gl_FragColor = texture2D (tex, texturecoord);"
+ "}";
+
+const gchar *luma_threshold_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec4 color = texture2D(tex, texturecoord);"
+ " float luma = dot(color.rgb, vec3(0.2125, 0.7154, 0.0721));" /* BT.709 (from orange book) */
+ " gl_FragColor = vec4 (vec3 (smoothstep (0.30, 0.50, luma)), color.a);"
+ "}";
+
+const gchar *sep_sobel_length_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform bool invert;"
+ "void main () {"
+ " vec4 g = texture2D (tex, v_texcoord.xy);"
+ /* restore black background with grey edges */
+ " g -= vec4(0.5, 0.5, 0.0, 0.0);"
+ " float len = length (g);"
+ /* little trick to avoid IF operator */
+ /* TODO: test if a standalone inverting pass is worth */
+ " gl_FragColor = abs(vec4(vec3(float(invert) - len), 1.0));"
+ "}";
+
+const gchar *desaturate_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec4 color = texture2D (tex, v_texcoord.xy);"
+ " float luma = dot(color.rgb, vec3(0.2125, 0.7154, 0.0721));"
+ " gl_FragColor = vec4(vec3(luma), color.a);"
+ "}";
+
+const gchar *sep_sobel_hconv3_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform float width;"
+ "void main () {"
+ " float w = 1.0 / width;"
+ " vec2 texturecoord[3];"
+ " texturecoord[1] = v_texcoord.xy;"
+ " texturecoord[0] = texturecoord[1] - vec2(w, 0.0);"
+ " texturecoord[2] = texturecoord[1] + vec2(w, 0.0);"
+ " float grad_kern[3];"
+ " grad_kern[0] = 1.0;"
+ " grad_kern[1] = 0.0;"
+ " grad_kern[2] = -1.0;"
+ " float blur_kern[3];"
+ " blur_kern[0] = 0.25;"
+ " blur_kern[1] = 0.5;"
+ " blur_kern[2] = 0.25;"
+ " int i;"
+ " vec4 sum = vec4 (0.0);"
+ " for (i = 0; i < 3; i++) { "
+ " vec4 neighbor = texture2D(tex, texturecoord[i]); "
+ " sum.r = neighbor.r * blur_kern[i] + sum.r;"
+ " sum.g = neighbor.g * grad_kern[i] + sum.g;"
+ " }"
+ " gl_FragColor = sum + vec4(0.0, 0.5, 0.0, 0.0);"
+ "}";
+
+const gchar *sep_sobel_vconv3_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform float height;"
+ "void main () {"
+ " float h = 1.0 / height;"
+ " vec2 texturecoord[3];"
+ " texturecoord[1] = v_texcoord.xy;"
+ " texturecoord[0] = texturecoord[1] - vec2(0.0, h);"
+ " texturecoord[2] = texturecoord[1] + vec2(0.0, h);"
+ " float grad_kern[3];"
+ " grad_kern[0] = 1.0;"
+ " grad_kern[1] = 0.0;"
+ " grad_kern[2] = -1.0;"
+ " float blur_kern[3];"
+ " blur_kern[0] = 0.25;"
+ " blur_kern[1] = 0.5;"
+ " blur_kern[2] = 0.25;"
+ " int i;"
+ " vec4 sum = vec4 (0.0);"
+ " for (i = 0; i < 3; i++) { "
+ " vec4 neighbor = texture2D(tex, texturecoord[i]); "
+ " sum.r = neighbor.r * grad_kern[i] + sum.r;"
+ " sum.g = neighbor.g * blur_kern[i] + sum.g;"
+ " }"
+ " gl_FragColor = sum + vec4(0.5, 0.0, 0.0, 0.0);"
+ "}";
+
+const gchar *hconv7_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform float kernel[7];"
+ "uniform float gauss_width;"
+ "void main () {"
+ " float w = 1.0 / gauss_width;"
+ " vec2 texturecoord[7];"
+ " texturecoord[3] = v_texcoord.xy;"
+ " texturecoord[2] = texturecoord[3] - vec2(w, 0.0);"
+ " texturecoord[1] = texturecoord[2] - vec2(w, 0.0);"
+ " texturecoord[0] = texturecoord[1] - vec2(w, 0.0);"
+ " texturecoord[4] = texturecoord[3] + vec2(w, 0.0);"
+ " texturecoord[5] = texturecoord[4] + vec2(w, 0.0);"
+ " texturecoord[6] = texturecoord[5] + vec2(w, 0.0);"
+ " int i;"
+ " vec4 sum = vec4 (0.0);"
+ " for (i = 0; i < 7; i++) { "
+ " vec4 neighbor = texture2D(tex, texturecoord[i]); "
+ " sum += neighbor * kernel[i];"
+ " }"
+ " gl_FragColor = sum;"
+ "}";
+
+/* vertical convolution 7x7 */
+const gchar *vconv7_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform float kernel[7];"
+ "uniform float gauss_height;"
+ "void main () {"
+ " float h = 1.0 / gauss_height;"
+ " vec2 texturecoord[7];"
+ " texturecoord[3] = v_texcoord.xy;"
+ " texturecoord[2] = texturecoord[3] - vec2(0.0, h);"
+ " texturecoord[1] = texturecoord[2] - vec2(0.0, h);"
+ " texturecoord[0] = texturecoord[1] - vec2(0.0, h);"
+ " texturecoord[4] = texturecoord[3] + vec2(0.0, h);"
+ " texturecoord[5] = texturecoord[4] + vec2(0.0, h);"
+ " texturecoord[6] = texturecoord[5] + vec2(0.0, h);"
+ " int i;"
+ " vec4 sum = vec4 (0.0);"
+ " for (i = 0; i < 7; i++) { "
+ " vec4 neighbor = texture2D(tex, texturecoord[i]);"
+ " sum += neighbor * kernel[i];"
+ " }"
+ " gl_FragColor = sum;"
+ "}";
+
+/* TODO: support several blend modes */
+const gchar *sum_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D base;"
+ "uniform sampler2D blend;"
+ "uniform float alpha;"
+ "uniform float beta;"
+ "void main () {"
+ " vec4 basecolor = texture2D (base, v_texcoord.xy);"
+ " vec4 blendcolor = texture2D (blend, v_texcoord.xy);"
+ " gl_FragColor = alpha * basecolor + beta * blendcolor;"
+ "}";
+
+const gchar *multiply_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D base;"
+ "uniform sampler2D blend;"
+ "uniform float alpha;"
+ "void main () {"
+ " vec4 basecolor = texture2D (base, v_texcoord.xy);"
+ " vec4 blendcolor = texture2D (blend, v_texcoord.xy);"
+ " gl_FragColor = (1.0 - alpha) * basecolor + alpha * basecolor * blendcolor;"
+ "}";
+
+/* lut operations, map luma to tex1d, see orange book (chapter 19) */
+const gchar *luma_to_curve_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform sampler2D curve;"
+ "void main () {"
+ " vec2 texturecoord = v_texcoord.xy;"
+ " vec4 color = texture2D (tex, texturecoord);"
+ " float luma = dot(color.rgb, vec3(0.2125, 0.7154, 0.0721));"
+ " color = texture2D (curve, vec2(luma, 0.0));"
+ " gl_FragColor = color;"
+ "}";
+
+/* lut operations, map rgb to tex1d, see orange book (chapter 19) */
+const gchar *rgb_to_curve_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform sampler2D curve;"
+ "void main () {"
+ " vec4 color = texture2D (tex, v_texcoord.xy);"
+ " vec4 outcolor;"
+ " outcolor.r = texture2D (curve, vec2(color.r, 0.0)).r;"
+ " outcolor.g = texture2D (curve, vec2(color.g, 0.0)).g;"
+ " outcolor.b = texture2D (curve, vec2(color.b, 0.0)).b;"
+ " outcolor.a = color.a;"
+ " gl_FragColor = outcolor;"
+ "}";
+
+const gchar *sin_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec4 color = texture2D (tex, vec2(v_texcoord.xy));"
+ " float luma = dot(color.rgb, vec3(0.2125, 0.7154, 0.0721));"
+/* calculate hue with the Preucil formula */
+ " float cosh = color.r - 0.5*(color.g + color.b);"
+/* sqrt(3)/2 = 0.866 */
+ " float sinh = 0.866*(color.g - color.b);"
+/* hue = atan2 h */
+ " float sch = (1.0-sinh)*cosh;"
+/* ok this is a little trick I came up because I didn't find any
+ * detailed proof of the Preucil formula. The issue is that tan(h) is
+ * pi-periodic so the smoothstep thing gives both reds (h = 0) and
+ * cyans (h = 180). I don't want to use atan since it requires
+ * branching and doesn't work on i915. So take only the right half of
+ * the circle where cosine is positive */
+/* take a slightly purple color trying to get rid of human skin reds */
+/* tanh = +-1.0 for h = +-45, where yellow=60, magenta=-60 */
+ " float a = smoothstep (0.3, 1.0, sch);"
+ " float b = smoothstep (-0.4, -0.1, sinh);"
+ " float mix = a * b;"
+ " gl_FragColor = color * mix + luma * (1.0 - mix);"
+ "}";
+
+const gchar *interpolate_fragment_source =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D base;"
+ "uniform sampler2D blend;"
+ "void main () {"
+ "vec4 basecolor = texture2D (base, v_texcoord);"
+ "vec4 blendcolor = texture2D (blend, v_texcoord);"
+ "vec4 white = vec4(1.0);"
+ "gl_FragColor = blendcolor + (1.0 - blendcolor.a) * basecolor;"
+ "}";
+
+const gchar *texture_interp_fragment_source =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D base;"
+ "uniform sampler2D blend;"
+ "uniform sampler2D alpha;"
+ "void main () {"
+ " vec4 basecolor = texture2D (base, v_texcoord);"
+ " vec4 blendcolor = texture2D (blend, v_texcoord);"
+ " vec4 alphacolor = texture2D (alpha, v_texcoord);"
+ " gl_FragColor = (alphacolor * blendcolor) + (1.0 - alphacolor) * basecolor;"
+ "}";
+
+const gchar *difference_fragment_source =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D saved;"
+ "uniform sampler2D current;"
+ "void main () {"
+ "vec4 savedcolor = texture2D (saved, v_texcoord);"
+ "vec4 currentcolor = texture2D (current, v_texcoord);"
+ "gl_FragColor = vec4 (step (0.12, length (savedcolor - currentcolor)));"
+ "}";
+
+/* This filter is meant as a demo of gst-plugins-gl + glsl
+ capabilities. So I'm keeping this shader readable enough. If and
+ when this shader will be used in production be careful to hard code
+ kernel into the shader and remove unneeded zero multiplications in
+ the convolution */
+const gchar *conv9_fragment_source_gles2 =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord;"
+ "uniform sampler2D tex;"
+ "uniform float kernel[9];"
+ "uniform float width, height;"
+ "uniform bool invert;"
+ "void main () {"
+ " float w = 1.0 / width;"
+ " float h = 1.0 / height;"
+ " vec2 texturecoord[9];"
+ " texturecoord[4] = v_texcoord.xy;" /* 0 0 */
+ " texturecoord[5] = texturecoord[4] + vec2(w, 0.0);" /* 1 0 */
+ " texturecoord[2] = texturecoord[5] - vec2(0.0, h);" /* 1 -1 */
+ " texturecoord[1] = texturecoord[2] - vec2(w, 0.0);" /* 0 -1 */
+ " texturecoord[0] = texturecoord[1] - vec2(w, 0.0);" /* -1 -1 */
+ " texturecoord[3] = texturecoord[0] + vec2(0.0, h);" /* -1 0 */
+ " texturecoord[6] = texturecoord[3] + vec2(0.0, h);" /* -1 1 */
+ " texturecoord[7] = texturecoord[6] + vec2(w, 0.0);" /* 0 1 */
+ " texturecoord[8] = texturecoord[7] + vec2(w, 0.0);" /* 1 1 */
+ " int i;"
+ " vec3 sum = vec3 (0.0);"
+ " for (i = 0; i < 9; i++) { "
+ " vec4 neighbor = texture2D (tex, texturecoord[i]);"
+ " sum += neighbor.xyz * kernel[i];"
+ " }"
+ " gl_FragColor = vec4 (abs(sum - vec3(float(invert))), 1.0);"
+ "}";
+
+/* *INDENT-ON* */
diff --git a/ext/gl/effects/gstgleffectssources.h b/ext/gl/effects/gstgleffectssources.h
new file mode 100644
index 000000000..fcd5c934b
--- /dev/null
+++ b/ext/gl/effects/gstgleffectssources.h
@@ -0,0 +1,52 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_EFFECTS_SOURCES_H__
+#define __GST_GL_EFFECTS_SOURCES_H__
+
+extern const gchar *mirror_fragment_source_gles2;
+extern const gchar *squeeze_fragment_source_gles2;
+extern const gchar *stretch_fragment_source_gles2;
+extern const gchar *fisheye_fragment_source_gles2;
+extern const gchar *twirl_fragment_source_gles2;
+extern const gchar *bulge_fragment_source_gles2;
+extern const gchar *tunnel_fragment_source_gles2;
+extern const gchar *square_fragment_source_gles2;
+extern const gchar *luma_threshold_fragment_source_gles2;
+extern const gchar *hconv7_fragment_source_gles2;
+extern const gchar *vconv7_fragment_source_gles2;
+extern const gchar *sum_fragment_source_gles2;
+extern const gchar *luma_to_curve_fragment_source_gles2;
+extern const gchar *rgb_to_curve_fragment_source_gles2;
+extern const gchar *sin_fragment_source_gles2;
+extern const gchar *desaturate_fragment_source_gles2;
+extern const gchar *sep_sobel_hconv3_fragment_source_gles2;
+extern const gchar *sep_sobel_vconv3_fragment_source_gles2;
+extern const gchar *sep_sobel_length_fragment_source_gles2;
+extern const gchar *multiply_fragment_source_gles2;
+extern const gchar *conv9_fragment_source_gles2;
+
+extern const gchar *interpolate_fragment_source;
+extern const gchar *texture_interp_fragment_source;
+extern const gchar *difference_fragment_source;
+
+void fill_gaussian_kernel (float *kernel, int size, float sigma);
+
+#endif /* __GST_GL_EFFECTS_SOURCES_H__ */
diff --git a/ext/gl/effects/gstgleffectstretch.c b/ext/gl/effects/gstgleffectstretch.c
new file mode 100644
index 000000000..bbc0c4c71
--- /dev/null
+++ b/ext/gl/effects/gstgleffectstretch.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_stretch (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "stretch",
+ stretch_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffecttunnel.c b/ext/gl/effects/gstgleffecttunnel.c
new file mode 100644
index 000000000..4990b832d
--- /dev/null
+++ b/ext/gl/effects/gstgleffecttunnel.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_tunnel (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "tunnel",
+ tunnel_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffecttwirl.c b/ext/gl/effects/gstgleffecttwirl.c
new file mode 100644
index 000000000..57a9b534d
--- /dev/null
+++ b/ext/gl/effects/gstgleffecttwirl.c
@@ -0,0 +1,36 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+
+void
+gst_gl_effects_twirl (GstGLEffects * effects)
+{
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ shader = gst_gl_effects_get_fragment_shader (effects, "twirl",
+ twirl_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/effects/gstgleffectxray.c b/ext/gl/effects/gstgleffectxray.c
new file mode 100644
index 000000000..a7c9b36a1
--- /dev/null
+++ b/ext/gl/effects/gstgleffectxray.c
@@ -0,0 +1,124 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../gstgleffects.h"
+#include "gstgleffectscurves.h"
+#include "gstgleffectlumatocurve.h"
+
+static gboolean kernel_ready = FALSE;
+static float gauss_kernel[7];
+
+void
+gst_gl_effects_xray (GstGLEffects * effects)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (effects)->context->gl_vtable;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLShader *shader;
+
+ if (!kernel_ready) {
+ fill_gaussian_kernel (gauss_kernel, 7, 1.5);
+ kernel_ready = TRUE;
+ }
+
+ /* map luma to xray curve */
+ gst_gl_effects_luma_to_curve (effects, &xray_curve, GST_GL_EFFECTS_CURVE_XRAY,
+ effects->intexture, effects->midtexture[0]);
+
+ /* horizontal blur */
+ shader = gst_gl_effects_get_fragment_shader (effects, "hconv7",
+ hconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 9, gauss_kernel);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_width",
+ GST_VIDEO_INFO_WIDTH (&filter->in_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[0],
+ effects->midtexture[1], shader);
+
+ /* vertical blur */
+ shader = gst_gl_effects_get_fragment_shader (effects, "vconv7",
+ vconv7_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1fv (shader, "kernel", 9, gauss_kernel);
+ gst_gl_shader_set_uniform_1f (shader, "gauss_height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[1],
+ effects->midtexture[2], shader);
+
+ /* detect edges with Sobel */
+ /* the old version used edges from the blurred texture, this uses
+ * the ones from original texture, still not sure what I like
+ * more. This one gives better edges obviously but behaves badly
+ * with noise */
+ /* desaturate */
+ shader = gst_gl_effects_get_fragment_shader (effects, "desaturate",
+ desaturate_fragment_source_gles2);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->intexture,
+ effects->midtexture[3], shader);
+
+ /* horizonal convolution */
+ shader = gst_gl_effects_get_fragment_shader (effects, "sobel_hconv3",
+ sep_sobel_hconv3_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[3],
+ effects->midtexture[4], shader);
+
+ /* vertical convolution */
+ shader = gst_gl_effects_get_fragment_shader (effects, "sobel_vconv3",
+ sep_sobel_vconv3_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[4],
+ effects->midtexture[3], shader);
+
+ /* gradient length */
+ shader = gst_gl_effects_get_fragment_shader (effects, "sobel_length",
+ sep_sobel_length_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+ gst_gl_shader_set_uniform_1i (shader, "invert", TRUE);
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[3],
+ effects->midtexture[4], shader);
+
+ /* multiply edges with the blurred image */
+ shader = gst_gl_effects_get_fragment_shader (effects, "multiply",
+ multiply_fragment_source_gles2);
+ gst_gl_shader_use (shader);
+
+ gl->ActiveTexture (GL_TEXTURE2);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (effects->midtexture[2]));
+
+ gst_gl_shader_set_uniform_1i (shader, "base", 2);
+
+ gl->ActiveTexture (GL_TEXTURE1);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (effects->midtexture[4]));
+
+ gst_gl_shader_set_uniform_1f (shader, "alpha", (gfloat) 0.5f);
+ gst_gl_shader_set_uniform_1i (shader, "blend", 1);
+
+ gst_gl_filter_render_to_target_with_shader (filter, effects->midtexture[4],
+ effects->outtexture, shader);
+}
diff --git a/ext/gl/gltestsrc.c b/ext/gl/gltestsrc.c
new file mode 100644
index 000000000..f6faea3b6
--- /dev/null
+++ b/ext/gl/gltestsrc.c
@@ -0,0 +1,1168 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2016> Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglfuncs.h>
+
+#include "gltestsrc.h"
+
+#define MAX_ATTRIBUTES 4
+
+struct vts_color_struct
+{
+ gfloat R, G, B;
+};
+
+struct XYZWRGB
+{
+ gfloat X, Y, Z, W, R, G, B;
+};
+
+enum
+{
+ COLOR_WHITE = 0,
+ COLOR_YELLOW,
+ COLOR_CYAN,
+ COLOR_GREEN,
+ COLOR_MAGENTA,
+ COLOR_RED,
+ COLOR_BLUE,
+ COLOR_BLACK,
+ COLOR_NEG_I,
+ COLOR_POS_Q,
+ COLOR_SUPER_BLACK,
+ COLOR_DARK_GREY
+};
+
+static const struct vts_color_struct vts_colors[] = {
+ /* 100% white */
+ {1.0f, 1.0f, 1.0f},
+ /* yellow */
+ {1.0f, 1.0f, 0.0f},
+ /* cyan */
+ {0.0f, 1.0f, 1.0f},
+ /* green */
+ {0.0f, 1.0f, 0.0f},
+ /* magenta */
+ {1.0f, 0.0f, 1.0f},
+ /* red */
+ {1.0f, 0.0f, 0.0f},
+ /* blue */
+ {0.0f, 0.0f, 1.0f},
+ /* black */
+ {0.0f, 0.0f, 0.0f},
+ /* -I */
+ {0.0, 0.0f, 0.5f},
+ /* +Q */
+ {0.0f, 0.5, 1.0f},
+ /* superblack */
+ {0.0f, 0.0f, 0.0f},
+ /* 7.421875% grey */
+ {19. / 256.0f, 19. / 256.0f, 19. / 256.0},
+};
+
+/* *INDENT-OFF* */
+static const GLfloat positions[] = {
+ -1.0, 1.0, 0.0, 1.0,
+ 1.0, 1.0, 0.0, 1.0,
+ 1.0, -1.0, 0.0, 1.0,
+ -1.0, -1.0, 0.0, 1.0,
+};
+
+static const GLushort indices_quad[] = { 0, 1, 2, 0, 2, 3 };
+/* *INDENT-ON* */
+
+struct attribute
+{
+ const gchar *name;
+ gint location;
+ guint n_elements;
+ GLenum element_type;
+ guint offset; /* in bytes */
+ guint stride; /* in bytes */
+};
+
+struct SrcShader
+{
+ struct BaseSrcImpl base;
+
+ GstGLShader *shader;
+
+ guint vao;
+ guint vbo;
+ guint vbo_indices;
+
+ guint n_attributes;
+ struct attribute attributes[MAX_ATTRIBUTES];
+
+ gconstpointer vertices;
+ gsize vertices_size;
+ const gushort *indices;
+ guint index_offset;
+ guint n_indices;
+};
+
+static void
+_bind_buffer (struct SrcShader *src)
+{
+ GstGLContext *context = src->base.context;
+ const GstGLFuncs *gl = context->gl_vtable;
+ gint i;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, src->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, src->vbo);
+
+ /* Load the vertex position */
+ for (i = 0; i < src->n_attributes; i++) {
+ struct attribute *attr = &src->attributes[i];
+
+ if (attr->location == -1)
+ attr->location =
+ gst_gl_shader_get_attribute_location (src->shader, attr->name);
+
+ gl->VertexAttribPointer (attr->location, attr->n_elements,
+ attr->element_type, GL_FALSE, attr->stride,
+ (void *) (gintptr) attr->offset);
+
+ gl->EnableVertexAttribArray (attr->location);
+ }
+}
+
+static void
+_unbind_buffer (struct SrcShader *src)
+{
+ GstGLContext *context = src->base.context;
+ const GstGLFuncs *gl = context->gl_vtable;
+ gint i;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ for (i = 0; i < src->n_attributes; i++) {
+ struct attribute *attr = &src->attributes[i];
+
+ gl->DisableVertexAttribArray (attr->location);
+ }
+}
+
+static gboolean
+_src_shader_init (gpointer impl, GstGLContext * context, GstVideoInfo * v_info)
+{
+ struct SrcShader *src = impl;
+ const GstGLFuncs *gl = context->gl_vtable;
+
+ src->base.context = context;
+
+ if (!src->vbo) {
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &src->vao);
+ gl->BindVertexArray (src->vao);
+ }
+
+ gl->GenBuffers (1, &src->vbo);
+ gl->BindBuffer (GL_ARRAY_BUFFER, src->vbo);
+ gl->BufferData (GL_ARRAY_BUFFER, src->vertices_size,
+ src->vertices, GL_STATIC_DRAW);
+
+ gl->GenBuffers (1, &src->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, src->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, src->n_indices * sizeof (gushort),
+ src->indices, GL_STATIC_DRAW);
+
+ if (gl->GenVertexArrays) {
+ _bind_buffer (src);
+ gl->BindVertexArray (0);
+ }
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_src_shader_fill_bound_fbo (gpointer impl)
+{
+ struct SrcShader *src = impl;
+ const GstGLFuncs *gl;
+
+ g_return_val_if_fail (src->base.context, FALSE);
+ g_return_val_if_fail (src->shader, FALSE);
+ gl = src->base.context->gl_vtable;
+
+ gst_gl_shader_use (src->shader);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (src->vao);
+ _bind_buffer (src);
+
+ gl->DrawElements (GL_TRIANGLES, src->n_indices, GL_UNSIGNED_SHORT,
+ (gpointer) (gintptr) src->index_offset);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (src);
+
+ gst_gl_context_clear_shader (src->base.context);
+
+ return TRUE;
+}
+
+static void
+_src_shader_deinit (gpointer impl)
+{
+ struct SrcShader *src = impl;
+ const GstGLFuncs *gl = src->base.context->gl_vtable;
+
+ if (src->shader)
+ gst_object_unref (src->shader);
+ src->shader = NULL;
+
+ if (src->vao)
+ gl->DeleteVertexArrays (1, &src->vao);
+ src->vao = 0;
+
+ if (src->vbo)
+ gl->DeleteBuffers (1, &src->vbo);
+ src->vbo = 0;
+
+ if (src->vbo_indices)
+ gl->DeleteBuffers (1, &src->vbo_indices);
+ src->vbo_indices = 0;
+}
+
+/* *INDENT-OFF* */
+static const gchar *smpte_vertex_src =
+ "attribute vec4 position;\n"
+ "attribute vec4 a_color;\n"
+ "varying vec4 color;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ " color = a_color;\n"
+ "}";
+
+static const gchar *smpte_fragment_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec4 color;\n"
+ "void main()\n"
+ "{\n"
+ " gl_FragColor = color;\n"
+ "}";
+
+static const gchar *snow_vertex_src =
+ "attribute vec4 position;\n"
+ "varying vec2 out_uv;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ " out_uv = position.xy;\n"
+ "}";
+
+static const gchar *snow_fragment_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform float time;\n"
+ "varying vec2 out_uv;\n"
+ "\n"
+ "float rand(vec2 co){\n"
+ " return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);\n"
+ "}\n"
+ "void main()\n"
+ "{\n"
+ " gl_FragColor = vec4(rand(time * out_uv));\n"
+ "}";
+/* *INDENT-ON* */
+
+#define N_QUADS 21
+struct SrcSMPTE
+{
+ struct SrcShader base;
+
+ GstGLShader *snow_shader;
+ GstGLShader *color_shader;
+ gint attr_snow_position;
+};
+
+static gpointer
+_src_smpte_new (GstGLTestSrc * test)
+{
+ struct SrcSMPTE *src = g_new0 (struct SrcSMPTE, 1);
+
+ src->base.base.src = test;
+
+ return src;
+}
+
+static gboolean
+_src_smpte_init (gpointer impl, GstGLContext * context, GstVideoInfo * v_info)
+{
+ struct SrcSMPTE *src = impl;
+ struct XYZWRGB *coord;
+ gushort *plane_indices;
+ GError *error = NULL;
+ int color_idx = 0;
+ int i;
+
+ src->base.base.context = context;
+
+ coord = g_new0 (struct XYZWRGB, N_QUADS * 4);
+ plane_indices = g_new0 (gushort, N_QUADS * 6);
+
+ /* top row */
+ for (i = 0; i < 7; i++) {
+ coord[color_idx * 4 + 0].X = -1.0f + i * (2.0f / 7.0f);
+ coord[color_idx * 4 + 0].Y = 1.0f / 3.0f;
+ coord[color_idx * 4 + 1].X = -1.0f + (i + 1) * (2.0f / 7.0f);
+ coord[color_idx * 4 + 1].Y = 1.0f / 3.0f;
+ coord[color_idx * 4 + 2].X = -1.0f + (i + 1) * (2.0f / 7.0f);
+ coord[color_idx * 4 + 2].Y = -1.0f;
+ coord[color_idx * 4 + 3].X = -1.0f + i * (2.0f / 7.0f);
+ coord[color_idx * 4 + 3].Y = -1.0f;
+ color_idx++;
+ }
+
+ /* middle row */
+ for (i = 0; i < 7; i++) {
+ coord[color_idx * 4 + 0].X = -1.0f + i * (2.0f / 7.0f);
+ coord[color_idx * 4 + 0].Y = 0.5f;
+ coord[color_idx * 4 + 1].X = -1.0f + (i + 1) * (2.0f / 7.0f);
+ coord[color_idx * 4 + 1].Y = 0.5f;
+ coord[color_idx * 4 + 2].X = -1.0f + (i + 1) * (2.0f / 7.0f);
+ coord[color_idx * 4 + 2].Y = 1.0f / 3.0f;
+ coord[color_idx * 4 + 3].X = -1.0f + i * (2.0f / 7.0f);
+ coord[color_idx * 4 + 3].Y = 1.0f / 3.0f;
+ color_idx++;
+ }
+
+ /* bottom row, left three */
+ for (i = 0; i < 3; i++) {
+ coord[color_idx * 4 + 0].X = -1.0f + i / 3.0f;
+ coord[color_idx * 4 + 0].Y = 1.0f;
+ coord[color_idx * 4 + 1].X = -1.0f + (i + 1) / 3.0f;
+ coord[color_idx * 4 + 1].Y = 1.0f;
+ coord[color_idx * 4 + 2].X = -1.0f + (i + 1) / 3.0f;
+ coord[color_idx * 4 + 2].Y = 0.5f;
+ coord[color_idx * 4 + 3].X = -1.0f + i / 3.0f;
+ coord[color_idx * 4 + 3].Y = 0.5f;
+ color_idx++;
+ }
+
+ /* bottom row, middle three (the blacks) */
+ for (i = 0; i < 3; i++) {
+ coord[color_idx * 4 + 0].X = i / 6.0f;
+ coord[color_idx * 4 + 0].Y = 1.0f;
+ coord[color_idx * 4 + 1].X = (i + 1) / 6.0f;
+ coord[color_idx * 4 + 1].Y = 1.0f;
+ coord[color_idx * 4 + 2].X = (i + 1) / 6.0f;
+ coord[color_idx * 4 + 2].Y = 0.5f;
+ coord[color_idx * 4 + 3].X = i / 6.0f;
+ coord[color_idx * 4 + 3].Y = 0.5f;
+ color_idx++;
+ }
+
+ g_assert (color_idx < N_QUADS);
+
+ for (i = 0; i < N_QUADS - 1; i++) {
+ int j, k;
+ if (i < 7) {
+ k = i;
+ } else if ((i - 7) & 1) {
+ k = COLOR_BLACK;
+ } else {
+ k = 13 - i;
+ }
+
+ if (i == 14) {
+ k = COLOR_NEG_I;
+ } else if (i == 15) {
+ k = COLOR_WHITE;
+ } else if (i == 16) {
+ k = COLOR_POS_Q;
+ } else if (i == 17) {
+ k = COLOR_SUPER_BLACK;
+ } else if (i == 18) {
+ k = COLOR_BLACK;
+ } else if (i == 19) {
+ k = COLOR_DARK_GREY;
+ }
+
+ for (j = 0; j < 4; j++) {
+ coord[i * 4 + j].Z = 0.0f;
+ coord[i * 4 + j].W = 1.0f;
+ coord[i * 4 + j].R = vts_colors[k].R;
+ coord[i * 4 + j].G = vts_colors[k].G;
+ coord[i * 4 + j].B = vts_colors[k].B;
+ }
+
+ for (j = 0; j < 6; j++)
+ plane_indices[i * 6 + j] = i * 4 + indices_quad[j];
+ }
+
+ /* snow */
+ coord[color_idx * 4 + 0].X = 0.5f;
+ coord[color_idx * 4 + 0].Y = 1.0f;
+ coord[color_idx * 4 + 0].Z = 0.0f;
+ coord[color_idx * 4 + 0].W = 1.0f;
+ coord[color_idx * 4 + 1].X = 1.0f;
+ coord[color_idx * 4 + 1].Y = 1.0f;
+ coord[color_idx * 4 + 1].Z = 0.0f;
+ coord[color_idx * 4 + 1].W = 1.0f;
+ coord[color_idx * 4 + 2].X = 1.0f;
+ coord[color_idx * 4 + 2].Y = 0.5f;
+ coord[color_idx * 4 + 2].Z = 0.0f;
+ coord[color_idx * 4 + 2].W = 1.0f;
+ coord[color_idx * 4 + 3].X = 0.5f;
+ coord[color_idx * 4 + 3].Y = 0.5f;
+ coord[color_idx * 4 + 3].Z = 0.0f;
+ coord[color_idx * 4 + 3].W = 1.0f;
+ for (i = 0; i < 6; i++)
+ plane_indices[color_idx * 6 + i] = color_idx * 4 + indices_quad[i];
+ color_idx++;
+
+ if (src->color_shader)
+ gst_object_unref (src->color_shader);
+ src->color_shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ smpte_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ smpte_fragment_src), NULL);
+ if (!src->color_shader) {
+ GST_ERROR_OBJECT (src->base.base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ if (src->snow_shader)
+ gst_object_unref (src->snow_shader);
+ src->snow_shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ snow_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ snow_fragment_src), NULL);
+ if (!src->snow_shader) {
+ GST_ERROR_OBJECT (src->base.base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ src->attr_snow_position = -1;
+
+ src->base.n_attributes = 2;
+
+ src->base.attributes[0].name = "position";
+ src->base.attributes[0].location = -1;
+ src->base.attributes[0].n_elements = 4;
+ src->base.attributes[0].element_type = GL_FLOAT;
+ src->base.attributes[0].offset = 0;
+ src->base.attributes[0].stride = sizeof (struct XYZWRGB);
+
+ src->base.attributes[1].name = "a_color";
+ src->base.attributes[1].location = -1;
+ src->base.attributes[1].n_elements = 3;
+ src->base.attributes[1].element_type = GL_FLOAT;
+ src->base.attributes[1].offset = 4 * sizeof (gfloat);
+ src->base.attributes[1].stride = sizeof (struct XYZWRGB);
+
+ if (src->base.shader)
+ gst_object_unref (src->base.shader);
+ src->base.shader = gst_object_ref (src->color_shader);
+ src->base.vertices = (gfloat *) coord;
+ src->base.vertices_size = sizeof (struct XYZWRGB) * N_QUADS * 4;
+ src->base.indices = plane_indices;
+ src->base.n_indices = N_QUADS * 6;
+
+ return _src_shader_init (impl, context, v_info);
+}
+
+static gboolean
+_src_smpte_fill_bound_fbo (gpointer impl)
+{
+ struct SrcSMPTE *src = impl;
+ gint attr_color_position = -1;
+
+ src->base.n_attributes = 2;
+ if (src->base.shader)
+ gst_object_unref (src->base.shader);
+ src->base.shader = gst_object_ref (src->color_shader);
+ src->base.n_indices = (N_QUADS - 1) * 6;
+ src->base.index_offset = 0;
+ if (!_src_shader_fill_bound_fbo (impl))
+ return FALSE;
+ attr_color_position = src->base.attributes[0].location;
+
+ src->base.attributes[0].location = src->attr_snow_position;
+ src->base.n_attributes = 1;
+ if (src->base.shader)
+ gst_object_unref (src->base.shader);
+ src->base.shader = gst_object_ref (src->snow_shader);
+ src->base.n_indices = 6;
+ src->base.index_offset = (N_QUADS - 1) * 6 * sizeof (gushort);
+ gst_gl_shader_use (src->snow_shader);
+ gst_gl_shader_set_uniform_1f (src->snow_shader, "time",
+ (gfloat) src->base.base.src->running_time / GST_SECOND);
+ if (!_src_shader_fill_bound_fbo (impl))
+ return FALSE;
+ src->attr_snow_position = src->base.attributes[0].location;
+ src->base.attributes[0].location = attr_color_position;
+
+ return TRUE;
+}
+
+static void
+_src_smpte_free (gpointer impl)
+{
+ struct SrcSMPTE *src = impl;
+
+ if (!impl)
+ return;
+
+ _src_shader_deinit (impl);
+
+ g_free ((gpointer) src->base.vertices);
+ g_free ((gpointer) src->base.indices);
+
+ if (src->snow_shader)
+ gst_object_unref (src->snow_shader);
+ if (src->color_shader)
+ gst_object_unref (src->color_shader);
+
+ g_free (impl);
+}
+
+static const struct SrcFuncs src_smpte = {
+ GST_GL_TEST_SRC_SMPTE,
+ _src_smpte_new,
+ _src_smpte_init,
+ _src_smpte_fill_bound_fbo,
+ _src_smpte_free,
+};
+
+#undef N_QUADS
+
+struct SrcUniColor
+{
+ struct BaseSrcImpl base;
+
+ struct vts_color_struct color;
+};
+
+static gpointer
+_src_uni_color_new (GstGLTestSrc * test)
+{
+ struct SrcUniColor *src = g_new0 (struct SrcUniColor, 1);
+
+ src->base.src = test;
+
+ return src;
+}
+
+static gboolean
+_src_uni_color_init (gpointer impl, GstGLContext * context,
+ GstVideoInfo * v_info)
+{
+ struct SrcUniColor *src = impl;
+
+ src->base.context = context;
+ src->base.v_info = *v_info;
+
+ return TRUE;
+}
+
+static gboolean
+_src_uni_color_fill_bound_fbo (gpointer impl)
+{
+ struct SrcUniColor *src = impl;
+ const GstGLFuncs *gl = src->base.context->gl_vtable;
+
+ gl->ClearColor (src->color.R, src->color.G, src->color.B, 1.0f);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+
+ return TRUE;
+}
+
+static void
+_src_uni_color_free (gpointer impl)
+{
+ g_free (impl);
+}
+
+#define SRC_UNICOLOR(name, cap_name) \
+static gpointer \
+G_PASTE(G_PASTE(_src_unicolor_,name),_new) (GstGLTestSrc * test) \
+{ \
+ struct SrcUniColor *src = _src_uni_color_new (test); \
+ src->color = vts_colors[G_PASTE(COLOR_,cap_name)]; \
+ return src; \
+} \
+static const struct SrcFuncs G_PASTE (src_,name) = { \
+ G_PASTE(GST_GL_TEST_SRC_,cap_name), \
+ G_PASTE(G_PASTE(_src_unicolor_,name),_new), \
+ _src_uni_color_init, \
+ _src_uni_color_fill_bound_fbo, \
+ _src_uni_color_free, \
+}
+
+SRC_UNICOLOR (white, WHITE);
+SRC_UNICOLOR (black, BLACK);
+SRC_UNICOLOR (red, RED);
+SRC_UNICOLOR (green, GREEN);
+SRC_UNICOLOR (blue, BLUE);
+
+static gpointer
+_src_blink_new (GstGLTestSrc * test)
+{
+ struct SrcUniColor *src = _src_uni_color_new (test);
+
+ src->color = vts_colors[COLOR_WHITE];
+
+ return src;
+}
+
+static gboolean
+_src_blink_fill_bound_fbo (gpointer impl)
+{
+ struct SrcUniColor *src = impl;
+
+ if (src->color.R > 0.5) {
+ src->color = vts_colors[COLOR_BLACK];
+ } else {
+ src->color = vts_colors[COLOR_WHITE];
+ }
+
+ return _src_uni_color_fill_bound_fbo (impl);
+}
+
+static const struct SrcFuncs src_blink = {
+ GST_GL_TEST_SRC_BLINK,
+ _src_blink_new,
+ _src_uni_color_init,
+ _src_blink_fill_bound_fbo,
+ _src_uni_color_free,
+};
+
+/* *INDENT-OFF* */
+static const gchar *checkers_vertex_src = "attribute vec4 position;\n"
+ "varying vec2 uv;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ /* RPi gives incorrect results for positive uv (plus it makes us start on
+ * the right pixel color i.e. red) */
+ " uv = position.xy - 1.0;\n"
+ "}";
+
+static const gchar *checkers_fragment_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform float checker_width;\n"
+ "uniform float width;\n"
+ "uniform float height;\n"
+ "varying vec2 uv;\n"
+ "void main()\n"
+ "{\n"
+ " vec2 xy_mod = floor (0.5 * uv * vec2(width, height) / (checker_width));\n"
+ " float result = mod (xy_mod.x + xy_mod.y, 2.0);\n"
+ " gl_FragColor.r = step (result, 0.5);\n"
+ " gl_FragColor.g = 1.0 - gl_FragColor.r;\n"
+ " gl_FragColor.ba = vec2(0.0, 1.0);\n"
+ "}";
+/* *INDENT-ON* */
+
+struct SrcCheckers
+{
+ struct SrcShader base;
+
+ guint checker_width;
+};
+
+static gboolean
+_src_checkers_init (gpointer impl, GstGLContext * context,
+ GstVideoInfo * v_info)
+{
+ struct SrcCheckers *src = impl;
+ GError *error = NULL;
+
+ src->base.base.context = context;
+
+ if (src->base.shader)
+ gst_object_unref (src->base.shader);
+ src->base.shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ checkers_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ checkers_fragment_src), NULL);
+ if (!src->base.shader) {
+ GST_ERROR_OBJECT (src->base.base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ src->base.n_attributes = 1;
+
+ src->base.attributes[0].name = "position";
+ src->base.attributes[0].location = -1;
+ src->base.attributes[0].n_elements = 4;
+ src->base.attributes[0].element_type = GL_FLOAT;
+ src->base.attributes[0].offset = 0;
+ src->base.attributes[0].stride = 4 * sizeof (gfloat);
+
+ src->base.vertices = positions;
+ src->base.vertices_size = sizeof (positions);
+ src->base.indices = indices_quad;
+ src->base.n_indices = 6;
+
+ gst_gl_shader_use (src->base.shader);
+ gst_gl_shader_set_uniform_1f (src->base.shader, "checker_width",
+ src->checker_width);
+ gst_gl_shader_set_uniform_1f (src->base.shader, "width",
+ (gfloat) GST_VIDEO_INFO_WIDTH (v_info));
+ gst_gl_shader_set_uniform_1f (src->base.shader, "height",
+ (gfloat) GST_VIDEO_INFO_HEIGHT (v_info));
+ gst_gl_context_clear_shader (src->base.base.context);
+
+ return _src_shader_init (impl, context, v_info);
+}
+
+static void
+_src_checkers_free (gpointer impl)
+{
+ struct SrcCheckers *src = impl;
+
+ if (!src)
+ return;
+
+ _src_shader_deinit (impl);
+
+ g_free (impl);
+}
+
+static gpointer
+_src_checkers_new (GstGLTestSrc * test)
+{
+ struct SrcCheckers *src = g_new0 (struct SrcCheckers, 1);
+
+ src->base.base.src = test;
+
+ return src;
+}
+
+#define SRC_CHECKERS(spacing) \
+static gpointer \
+G_PASTE(G_PASTE(_src_checkers,spacing),_new) (GstGLTestSrc * test) \
+{ \
+ struct SrcCheckers *src = _src_checkers_new (test); \
+ src->checker_width = spacing; \
+ return src; \
+} \
+static const struct SrcFuncs G_PASTE(src_checkers,spacing) = { \
+ G_PASTE(GST_GL_TEST_SRC_CHECKERS,spacing), \
+ G_PASTE(G_PASTE(_src_checkers,spacing),_new), \
+ _src_checkers_init, \
+ _src_shader_fill_bound_fbo, \
+ _src_checkers_free, \
+}
+
+SRC_CHECKERS (1);
+SRC_CHECKERS (2);
+SRC_CHECKERS (4);
+SRC_CHECKERS (8);
+
+static gboolean
+_src_snow_init (gpointer impl, GstGLContext * context, GstVideoInfo * v_info)
+{
+ struct SrcShader *src = impl;
+ GError *error = NULL;
+
+ src->base.context = context;
+
+ if (src->shader)
+ gst_object_unref (src->shader);
+ src->shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ snow_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ snow_fragment_src), NULL);
+ if (!src->shader) {
+ GST_ERROR_OBJECT (src->base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ src->n_attributes = 1;
+
+ src->attributes[0].name = "position";
+ src->attributes[0].location = -1;
+ src->attributes[0].n_elements = 4;
+ src->attributes[0].element_type = GL_FLOAT;
+ src->attributes[0].offset = 0;
+ src->attributes[0].stride = 4 * sizeof (gfloat);
+
+ src->vertices = positions;
+ src->vertices_size = sizeof (positions);
+ src->indices = indices_quad;
+ src->n_indices = 6;
+
+ return _src_shader_init (impl, context, v_info);
+}
+
+static gboolean
+_src_snow_fill_bound_fbo (gpointer impl)
+{
+ struct SrcShader *src = impl;
+
+ g_return_val_if_fail (src->base.context, FALSE);
+ g_return_val_if_fail (src->shader, FALSE);
+
+ gst_gl_shader_use (src->shader);
+ gst_gl_shader_set_uniform_1f (src->shader, "time",
+ (gfloat) src->base.src->running_time / GST_SECOND);
+
+ return _src_shader_fill_bound_fbo (impl);
+}
+
+static void
+_src_snow_free (gpointer impl)
+{
+ struct SrcShader *src = impl;
+
+ if (!src)
+ return;
+
+ _src_shader_deinit (impl);
+
+ g_free (impl);
+}
+
+static gpointer
+_src_snow_new (GstGLTestSrc * test)
+{
+ struct SrcShader *src = g_new0 (struct SrcShader, 1);
+
+ src->base.src = test;
+
+ return src;
+}
+
+static const struct SrcFuncs src_snow = {
+ GST_GL_TEST_SRC_SNOW,
+ _src_snow_new,
+ _src_snow_init,
+ _src_snow_fill_bound_fbo,
+ _src_snow_free,
+};
+
+/* *INDENT-OFF* */
+static const gchar *mandelbrot_vertex_src = "attribute vec4 position;\n"
+ "uniform float aspect_ratio;\n"
+ "varying vec2 fractal_position;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ " fractal_position = vec2(position.y * 0.5 - 0.3, aspect_ratio * position.x * 0.5);\n"
+ " fractal_position *= 2.5;\n"
+ "}";
+
+static const gchar *mandelbrot_fragment_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform float time;\n"
+ "varying vec2 fractal_position;\n"
+ "const vec4 K = vec4(1.0, 0.66, 0.33, 3.0);\n"
+ "vec4 hsv_to_rgb(float hue, float saturation, float value) {\n"
+ " vec4 p = abs(fract(vec4(hue) + K) * 6.0 - K.wwww);\n"
+ " return value * mix(K.xxxx, clamp(p - K.xxxx, 0.0, 1.0), saturation);\n"
+ "}\n"
+ "vec4 i_to_rgb(int i) {\n"
+ " float hue = float(i) / 100.0 + sin(time);\n"
+ " return hsv_to_rgb(hue, 0.5, 0.8);\n"
+ "}\n"
+ "vec2 pow_2_complex(vec2 c) {\n"
+ " return vec2(c.x*c.x - c.y*c.y, 2.0 * c.x * c.y);\n"
+ "}\n"
+ "vec2 mandelbrot(vec2 c, vec2 c0) {\n"
+ " return pow_2_complex(c) + c0;\n"
+ "}\n"
+ "vec4 iterate_pixel(vec2 position) {\n"
+ " vec2 c = vec2(0);\n"
+ " for (int i=0; i < 20; i++) {\n"
+ " if (c.x*c.x + c.y*c.y > 2.0*2.0)\n"
+ " return i_to_rgb(i);\n"
+ " c = mandelbrot(c, position);\n"
+ " }\n"
+ " return vec4(0, 0, 0, 1);\n"
+ "}\n"
+ "void main() {\n"
+ " gl_FragColor = iterate_pixel(fractal_position);\n"
+ "}";
+/* *INDENT-ON* */
+
+static gboolean
+_src_mandelbrot_init (gpointer impl, GstGLContext * context,
+ GstVideoInfo * v_info)
+{
+ struct SrcShader *src = impl;
+ GError *error = NULL;
+
+ src->base.context = context;
+
+ if (src->shader)
+ gst_object_unref (src->shader);
+ src->shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ mandelbrot_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ mandelbrot_fragment_src), NULL);
+ if (!src->shader) {
+ GST_ERROR_OBJECT (src->base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ src->n_attributes = 1;
+
+ src->attributes[0].name = "position";
+ src->attributes[0].location = -1;
+ src->attributes[0].n_elements = 4;
+ src->attributes[0].element_type = GL_FLOAT;
+ src->attributes[0].offset = 0;
+ src->attributes[0].stride = 4 * sizeof (gfloat);
+
+ src->vertices = positions;
+ src->vertices_size = sizeof (positions);
+ src->indices = indices_quad;
+ src->n_indices = 6;
+
+ gst_gl_shader_use (src->shader);
+ gst_gl_shader_set_uniform_1f (src->shader, "aspect_ratio",
+ (gfloat) GST_VIDEO_INFO_WIDTH (v_info) /
+ (gfloat) GST_VIDEO_INFO_HEIGHT (v_info));
+ gst_gl_context_clear_shader (src->base.context);
+
+ return _src_shader_init (impl, context, v_info);
+}
+
+static gboolean
+_src_mandelbrot_fill_bound_fbo (gpointer impl)
+{
+ struct SrcShader *src = impl;
+
+ g_return_val_if_fail (src->base.context, FALSE);
+ g_return_val_if_fail (src->shader, FALSE);
+
+ gst_gl_shader_use (src->shader);
+ gst_gl_shader_set_uniform_1f (src->shader, "time",
+ (gfloat) src->base.src->running_time / GST_SECOND);
+
+ return _src_shader_fill_bound_fbo (impl);
+}
+
+static void
+_src_mandelbrot_free (gpointer impl)
+{
+ struct SrcShader *src = impl;
+
+ if (!src)
+ return;
+
+ _src_shader_deinit (impl);
+
+ g_free (impl);
+}
+
+static gpointer
+_src_mandelbrot_new (GstGLTestSrc * test)
+{
+ struct SrcShader *src = g_new0 (struct SrcShader, 1);
+
+ src->base.src = test;
+
+ return src;
+}
+
+static const struct SrcFuncs src_mandelbrot = {
+ GST_GL_TEST_SRC_MANDELBROT,
+ _src_mandelbrot_new,
+ _src_mandelbrot_init,
+ _src_mandelbrot_fill_bound_fbo,
+ _src_mandelbrot_free,
+};
+
+/* *INDENT-OFF* */
+static const gchar *circular_vertex_src =
+ "attribute vec4 position;\n"
+ "varying vec2 uv;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ " uv = position.xy;\n"
+ "}";
+
+static const gchar *circular_fragment_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform float aspect_ratio;\n"
+ "varying vec2 uv;\n"
+ "#define PI 3.14159265\n"
+ "void main() {\n"
+ " float dist = 0.5 * sqrt(uv.x * uv.x + uv.y / aspect_ratio * uv.y / aspect_ratio);\n"
+ " float seg = floor(dist * 16.0);\n"
+ " if (seg <= 0.0 || seg >= 8.0) {\n"
+ " gl_FragColor = vec4(vec3(0.0), 1.0);\n"
+ " } else {\n"
+ " float d = floor (256.0 * dist * 200.0 * pow (2.0, - (seg - 1.0) / 4.0) + 0.5) / 128.0;\n"
+ " gl_FragColor = vec4 (vec3(sin (d * PI) * 0.5 + 0.5), 1.0);\n"
+ " }\n"
+ "}";
+/* *INDENT-ON* */
+
+static gboolean
+_src_circular_init (gpointer impl, GstGLContext * context,
+ GstVideoInfo * v_info)
+{
+ struct SrcShader *src = impl;
+ GError *error = NULL;
+
+ src->base.context = context;
+
+ if (src->shader)
+ gst_object_unref (src->shader);
+ src->shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ circular_vertex_src),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ circular_fragment_src), NULL);
+ if (!src->shader) {
+ GST_ERROR_OBJECT (src->base.src, "%s", error->message);
+ return FALSE;
+ }
+
+ src->n_attributes = 1;
+
+ src->attributes[0].name = "position";
+ src->attributes[0].location = -1;
+ src->attributes[0].n_elements = 4;
+ src->attributes[0].element_type = GL_FLOAT;
+ src->attributes[0].offset = 0;
+ src->attributes[0].stride = 4 * sizeof (gfloat);
+
+ src->vertices = positions;
+ src->vertices_size = sizeof (positions);
+ src->indices = indices_quad;
+ src->n_indices = 6;
+
+ gst_gl_shader_use (src->shader);
+ gst_gl_shader_set_uniform_1f (src->shader, "aspect_ratio",
+ (gfloat) GST_VIDEO_INFO_WIDTH (v_info) /
+ (gfloat) GST_VIDEO_INFO_HEIGHT (v_info));
+ gst_gl_context_clear_shader (src->base.context);
+
+ return _src_shader_init (impl, context, v_info);
+}
+
+static void
+_src_circular_free (gpointer impl)
+{
+ struct SrcShader *src = impl;
+
+ if (!src)
+ return;
+
+ _src_shader_deinit (impl);
+
+ g_free (impl);
+}
+
+static gpointer
+_src_circular_new (GstGLTestSrc * test)
+{
+ struct SrcShader *src = g_new0 (struct SrcShader, 1);
+
+ src->base.src = test;
+
+ return src;
+}
+
+static const struct SrcFuncs src_circular = {
+ GST_GL_TEST_SRC_CIRCULAR,
+ _src_circular_new,
+ _src_circular_init,
+ _src_mandelbrot_fill_bound_fbo,
+ _src_circular_free,
+};
+
+static const struct SrcFuncs *src_impls[] = {
+ &src_smpte,
+ &src_snow,
+ &src_black,
+ &src_white,
+ &src_red,
+ &src_green,
+ &src_blue,
+ &src_checkers1,
+ &src_checkers2,
+ &src_checkers4,
+ &src_checkers8,
+ &src_circular,
+ &src_blink,
+ &src_mandelbrot,
+};
+
+const struct SrcFuncs *
+gst_gl_test_src_get_src_funcs_for_pattern (GstGLTestSrcPattern pattern)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (src_impls); i++) {
+ if (src_impls[i]->pattern == pattern)
+ return src_impls[i];
+ }
+
+ return NULL;
+}
diff --git a/ext/gl/gltestsrc.h b/ext/gl/gltestsrc.h
new file mode 100644
index 000000000..dbcab0cab
--- /dev/null
+++ b/ext/gl/gltestsrc.h
@@ -0,0 +1,81 @@
+/* GStreamer
+ * Copyright (C) <2003> David A. Schleef <ds@schleef.org>
+ *
+ * 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.
+ */
+
+#ifndef __GL_TEST_SRC_H__
+#define __GL_TEST_SRC_H__
+
+#include <glib.h>
+
+typedef struct _GstGLTestSrc GstGLTestSrc;
+
+/**
+ * GstGLTestSrcPattern:
+ * @GST_GL_TEST_SRC_SMPTE: A standard SMPTE test pattern
+ * @GST_GL_TEST_SRC_SNOW: Random noise
+ * @GST_GL_TEST_SRC_BLACK: A black image
+ * @GST_GL_TEST_SRC_WHITE: A white image
+ * @GST_GL_TEST_SRC_RED: A red image
+ * @GST_GL_TEST_SRC_GREEN: A green image
+ * @GST_GL_TEST_SRC_BLUE: A blue image
+ * @GST_GL_TEST_SRC_CHECKERS1: Checkers pattern (1px)
+ * @GST_GL_TEST_SRC_CHECKERS2: Checkers pattern (2px)
+ * @GST_GL_TEST_SRC_CHECKERS4: Checkers pattern (4px)
+ * @GST_GL_TEST_SRC_CHECKERS8: Checkers pattern (8px)
+ * @GST_GL_TEST_SRC_CIRCULAR: Circular pattern
+ * @GST_GL_TEST_SRC_BLINK: Alternate between black and white
+ *
+ * The test pattern to produce.
+ */
+typedef enum {
+ GST_GL_TEST_SRC_SMPTE,
+ GST_GL_TEST_SRC_SNOW,
+ GST_GL_TEST_SRC_BLACK,
+ GST_GL_TEST_SRC_WHITE,
+ GST_GL_TEST_SRC_RED,
+ GST_GL_TEST_SRC_GREEN,
+ GST_GL_TEST_SRC_BLUE,
+ GST_GL_TEST_SRC_CHECKERS1,
+ GST_GL_TEST_SRC_CHECKERS2,
+ GST_GL_TEST_SRC_CHECKERS4,
+ GST_GL_TEST_SRC_CHECKERS8,
+ GST_GL_TEST_SRC_CIRCULAR,
+ GST_GL_TEST_SRC_BLINK,
+ GST_GL_TEST_SRC_MANDELBROT
+} GstGLTestSrcPattern;
+
+#include "gstgltestsrc.h"
+
+struct BaseSrcImpl {
+ GstGLTestSrc *src;
+ GstGLContext *context;
+ GstVideoInfo v_info;
+};
+
+struct SrcFuncs
+{
+ GstGLTestSrcPattern pattern;
+ gpointer (*new) (GstGLTestSrc * src);
+ gboolean (*init) (gpointer impl, GstGLContext * context, GstVideoInfo * v_info);
+ gboolean (*fill_bound_fbo) (gpointer impl);
+ void (*free) (gpointer impl);
+};
+
+const struct SrcFuncs * gst_gl_test_src_get_src_funcs_for_pattern (GstGLTestSrcPattern pattern);
+
+#endif
diff --git a/ext/gl/gstglbasemixer.c b/ext/gl/gstglbasemixer.c
new file mode 100644
index 000000000..87cea2c4b
--- /dev/null
+++ b/ext/gl/gstglbasemixer.c
@@ -0,0 +1,473 @@
+/* Generic video mixer plugin
+ *
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include "gstglbasemixer.h"
+
+#define gst_gl_base_mixer_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE (GstGLBaseMixer, gst_gl_base_mixer,
+ GST_TYPE_VIDEO_AGGREGATOR);
+
+#define GST_CAT_DEFAULT gst_gl_base_mixer_debug
+GST_DEBUG_CATEGORY (gst_gl_base_mixer_debug);
+
+static void gst_gl_base_mixer_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_gl_base_mixer_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static void gst_gl_base_mixer_set_context (GstElement * element,
+ GstContext * context);
+static GstStateChangeReturn gst_gl_base_mixer_change_state (GstElement *
+ element, GstStateChange transition);
+
+enum
+{
+ PROP_PAD_0
+};
+
+#define GST_GL_BASE_MIXER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_GL_BASE_MIXER, GstGLBaseMixerPrivate))
+
+struct _GstGLBaseMixerPrivate
+{
+ gboolean negotiated;
+
+ GstGLContext *other_context;
+};
+
+G_DEFINE_TYPE (GstGLBaseMixerPad, gst_gl_base_mixer_pad,
+ GST_TYPE_VIDEO_AGGREGATOR_PAD);
+
+static void
+gst_gl_base_mixer_pad_class_init (GstGLBaseMixerPadClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstVideoAggregatorPadClass *vaggpad_class =
+ (GstVideoAggregatorPadClass *) klass;
+
+ gobject_class->set_property = gst_gl_base_mixer_pad_set_property;
+ gobject_class->get_property = gst_gl_base_mixer_pad_get_property;
+
+ vaggpad_class->set_info = NULL;
+ vaggpad_class->prepare_frame = NULL;
+ vaggpad_class->clean_frame = NULL;
+}
+
+static void
+gst_gl_base_mixer_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_base_mixer_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static gboolean
+_find_local_gl_context (GstGLBaseMixer * mix)
+{
+ if (gst_gl_query_local_gl_context (GST_ELEMENT (mix), GST_PAD_SRC,
+ &mix->context))
+ return TRUE;
+ if (gst_gl_query_local_gl_context (GST_ELEMENT (mix), GST_PAD_SINK,
+ &mix->context))
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+_get_gl_context (GstGLBaseMixer * mix)
+{
+ GstGLBaseMixerClass *mix_class = GST_GL_BASE_MIXER_GET_CLASS (mix);
+ GError *error = NULL;
+
+ if (!gst_gl_ensure_element_data (mix, &mix->display,
+ &mix->priv->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (mix->display, mix_class->supported_gl_api);
+
+ _find_local_gl_context (mix);
+
+ GST_OBJECT_LOCK (mix->display);
+ if (!mix->context) {
+ do {
+ if (mix->context) {
+ gst_object_unref (mix->context);
+ mix->context = NULL;
+ }
+ /* just get a GL context. we don't care */
+ mix->context =
+ gst_gl_display_get_gl_context_for_thread (mix->display, NULL);
+ if (!mix->context) {
+ if (!gst_gl_display_create_context (mix->display,
+ mix->priv->other_context, &mix->context, &error)) {
+ GST_OBJECT_UNLOCK (mix->display);
+ goto context_error;
+ }
+ }
+ } while (!gst_gl_display_add_context (mix->display, mix->context));
+ }
+ GST_OBJECT_UNLOCK (mix->display);
+
+ {
+ GstGLAPI current_gl_api = gst_gl_context_get_gl_api (mix->context);
+ if ((current_gl_api & mix_class->supported_gl_api) == 0)
+ goto unsupported_gl_api;
+ }
+
+ return TRUE;
+
+unsupported_gl_api:
+ {
+ GstGLAPI gl_api = gst_gl_context_get_gl_api (mix->context);
+ gchar *gl_api_str = gst_gl_api_to_string (gl_api);
+ gchar *supported_gl_api_str =
+ gst_gl_api_to_string (mix_class->supported_gl_api);
+ GST_ELEMENT_ERROR (mix, RESOURCE, BUSY,
+ ("GL API's not compatible context: %s supported: %s", gl_api_str,
+ supported_gl_api_str), (NULL));
+
+ g_free (supported_gl_api_str);
+ g_free (gl_api_str);
+ return FALSE;
+ }
+context_error:
+ {
+ GST_ELEMENT_ERROR (mix, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+ g_clear_error (&error);
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_gl_base_mixer_propose_allocation (GstAggregator * agg,
+ GstAggregatorPad * aggpad, GstQuery * decide_query, GstQuery * query)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (agg);
+
+ if (!_get_gl_context (mix))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_base_mixer_sink_query (GstAggregator * agg, GstAggregatorPad * bpad,
+ GstQuery * query)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (agg);
+
+ GST_TRACE ("QUERY %" GST_PTR_FORMAT, query);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) mix, query,
+ mix->display, mix->context, mix->priv->other_context))
+ return TRUE;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, bpad, query);;
+}
+
+static void
+gst_gl_base_mixer_pad_init (GstGLBaseMixerPad * mixerpad)
+{
+}
+
+/* GLBaseMixer signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_CONTEXT
+};
+
+static gboolean gst_gl_base_mixer_src_query (GstAggregator * agg,
+ GstQuery * query);
+
+static gboolean
+gst_gl_base_mixer_src_activate_mode (GstAggregator * aggregator,
+ GstPadMode mode, gboolean active);
+static gboolean gst_gl_base_mixer_stop (GstAggregator * agg);
+static gboolean gst_gl_base_mixer_start (GstAggregator * agg);
+
+static void gst_gl_base_mixer_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_base_mixer_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_base_mixer_decide_allocation (GstAggregator * agg,
+ GstQuery * query);
+
+static void
+gst_gl_base_mixer_class_init (GstGLBaseMixerClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glmixer", 0, "opengl mixer");
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GstGLBaseMixerPrivate));
+
+ gobject_class->get_property = gst_gl_base_mixer_get_property;
+ gobject_class->set_property = gst_gl_base_mixer_set_property;
+
+ element_class->set_context =
+ GST_DEBUG_FUNCPTR (gst_gl_base_mixer_set_context);
+ element_class->change_state = gst_gl_base_mixer_change_state;
+
+ agg_class->sink_query = gst_gl_base_mixer_sink_query;
+ agg_class->src_query = gst_gl_base_mixer_src_query;
+ agg_class->src_activate = gst_gl_base_mixer_src_activate_mode;
+ agg_class->stop = gst_gl_base_mixer_stop;
+ agg_class->start = gst_gl_base_mixer_start;
+ agg_class->decide_allocation = gst_gl_base_mixer_decide_allocation;
+ agg_class->propose_allocation = gst_gl_base_mixer_propose_allocation;
+
+ g_object_class_install_property (gobject_class, PROP_CONTEXT,
+ g_param_spec_object ("context",
+ "OpenGL context",
+ "Get OpenGL context",
+ GST_TYPE_GL_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /* Register the pad class */
+ g_type_class_ref (GST_TYPE_GL_BASE_MIXER_PAD);
+
+ klass->supported_gl_api = GST_GL_API_ANY;
+}
+
+static void
+gst_gl_base_mixer_init (GstGLBaseMixer * mix)
+{
+ mix->priv = GST_GL_BASE_MIXER_GET_PRIVATE (mix);
+}
+
+static void
+gst_gl_base_mixer_set_context (GstElement * element, GstContext * context)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (element);
+ GstGLBaseMixerClass *mix_class = GST_GL_BASE_MIXER_GET_CLASS (mix);
+
+ gst_gl_handle_set_context (element, context, &mix->display,
+ &mix->priv->other_context);
+
+ if (mix->display)
+ gst_gl_display_filter_gl_api (mix->display, mix_class->supported_gl_api);
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static gboolean
+gst_gl_base_mixer_activate (GstGLBaseMixer * mix, gboolean active)
+{
+ GstGLBaseMixerClass *mix_class = GST_GL_BASE_MIXER_GET_CLASS (mix);
+ gboolean result = TRUE;
+
+ if (active) {
+ if (!gst_gl_ensure_element_data (mix, &mix->display,
+ &mix->priv->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (mix->display, mix_class->supported_gl_api);
+ }
+
+ return result;
+}
+
+static gboolean
+gst_gl_base_mixer_src_activate_mode (GstAggregator * aggregator,
+ GstPadMode mode, gboolean active)
+{
+ GstGLBaseMixer *mix;
+ gboolean result = FALSE;
+
+ mix = GST_GL_BASE_MIXER (aggregator);
+
+ switch (mode) {
+ case GST_PAD_MODE_PUSH:
+ case GST_PAD_MODE_PULL:
+ result = gst_gl_base_mixer_activate (mix, active);
+ break;
+ default:
+ result = TRUE;
+ break;
+ }
+ return result;
+}
+
+static gboolean
+gst_gl_base_mixer_src_query (GstAggregator * agg, GstQuery * query)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (agg);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) mix, query,
+ mix->display, mix->context, mix->priv->other_context))
+ return TRUE;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return GST_AGGREGATOR_CLASS (parent_class)->src_query (agg, query);
+}
+
+static gboolean
+gst_gl_base_mixer_decide_allocation (GstAggregator * agg, GstQuery * query)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (agg);
+
+ if (!_get_gl_context (mix))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+gst_gl_base_mixer_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstGLBaseMixer *mixer = GST_GL_BASE_MIXER (object);
+
+ switch (prop_id) {
+ case PROP_CONTEXT:
+ g_value_set_object (value, mixer->context);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_base_mixer_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_base_mixer_start (GstAggregator * agg)
+{
+ return GST_AGGREGATOR_CLASS (parent_class)->start (agg);;
+}
+
+static gboolean
+gst_gl_base_mixer_stop (GstAggregator * agg)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (agg);
+
+ if (mix->context) {
+ gst_object_unref (mix->context);
+ mix->context = NULL;
+ }
+
+ return TRUE;
+}
+
+static GstStateChangeReturn
+gst_gl_base_mixer_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (element);
+ GstGLBaseMixerClass *mix_class = GST_GL_BASE_MIXER_GET_CLASS (mix);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG_OBJECT (mix, "changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_gl_ensure_element_data (element, &mix->display,
+ &mix->priv->other_context))
+ return GST_STATE_CHANGE_FAILURE;
+
+ gst_gl_display_filter_gl_api (mix->display, mix_class->supported_gl_api);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (mix->priv->other_context) {
+ gst_object_unref (mix->priv->other_context);
+ mix->priv->other_context = NULL;
+ }
+
+ if (mix->display) {
+ gst_object_unref (mix->display);
+ mix->display = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstglbasemixer.h b/ext/gl/gstglbasemixer.h
new file mode 100644
index 000000000..e9684abc4
--- /dev/null
+++ b/ext/gl/gstglbasemixer.h
@@ -0,0 +1,98 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_BASE_MIXER_H__
+#define __GST_GL_BASE_MIXER_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+#include <gst/video/gstvideoaggregator.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_BASE_MIXER_PAD (gst_gl_base_mixer_pad_get_type())
+#define GST_GL_BASE_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_BASE_MIXER_PAD, GstGLBaseMixerPad))
+#define GST_GL_BASE_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_BASE_MIXER_PAD, GstGLBaseMixerPadClass))
+#define GST_IS_GL_BASE_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_BASE_MIXER_PAD))
+#define GST_IS_GL_BASE_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_BASE_MIXER_PAD))
+#define GST_GL_BASE_MIXER_PAD_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_BASE_MIXER_PAD,GstGLBaseMixerPadClass))
+
+typedef struct _GstGLBaseMixerPad GstGLBaseMixerPad;
+typedef struct _GstGLBaseMixerPadClass GstGLBaseMixerPadClass;
+
+/* all information needed for one video stream */
+struct _GstGLBaseMixerPad
+{
+ GstVideoAggregatorPad parent; /* subclass the pad */
+};
+
+struct _GstGLBaseMixerPadClass
+{
+ GstVideoAggregatorPadClass parent_class;
+};
+
+GType gst_gl_base_mixer_pad_get_type (void);
+
+#define GST_TYPE_GL_BASE_MIXER (gst_gl_base_mixer_get_type())
+#define GST_GL_BASE_MIXER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_BASE_MIXER, GstGLBaseMixer))
+#define GST_GL_BASE_MIXER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_BASE_MIXER, GstGLBaseMixerClass))
+#define GST_IS_GL_BASE_MIXER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_BASE_MIXER))
+#define GST_IS_GL_BASE_MIXER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_BASE_MIXER))
+#define GST_GL_BASE_MIXER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_BASE_MIXER,GstGLBaseMixerClass))
+
+typedef struct _GstGLBaseMixer GstGLBaseMixer;
+typedef struct _GstGLBaseMixerClass GstGLBaseMixerClass;
+typedef struct _GstGLBaseMixerPrivate GstGLBaseMixerPrivate;
+
+struct _GstGLBaseMixer
+{
+ GstVideoAggregator vaggregator;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+
+ gpointer _padding[GST_PADDING];
+
+ GstGLBaseMixerPrivate *priv;
+};
+
+struct _GstGLBaseMixerClass
+{
+ GstVideoAggregatorClass parent_class;
+ GstGLAPI supported_gl_api;
+
+ gpointer _padding[GST_PADDING];
+};
+
+GType gst_gl_base_mixer_get_type(void);
+
+G_END_DECLS
+#endif /* __GST_GL_BASE_MIXER_H__ */
diff --git a/ext/gl/gstglbumper.c b/ext/gl/gstglbumper.c
new file mode 100644
index 000000000..2b7bd6ed5
--- /dev/null
+++ b/ext/gl/gstglbumper.c
@@ -0,0 +1,546 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Cyril Comparon <cyril.comparon@gmail.com>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glbumper
+ * @title: glbumper
+ *
+ * Bump mapping using the normal method.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! glupload ! glbumper location=normalmap.bmp ! glimagesink
+ * ]| A pipeline to test normal mapping.
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <png.h>
+#include "gstglbumper.h"
+
+#if PNG_LIBPNG_VER >= 10400
+#define int_p_NULL NULL
+#define png_infopp_NULL NULL
+#endif
+
+#define GST_CAT_DEFAULT gst_gl_bumper_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_bumper_debug, "glbumper", 0, "glbumper element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLBumper, gst_gl_bumper, GST_TYPE_GL_FILTER,
+ DEBUG_INIT);
+
+static void gst_gl_bumper_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_bumper_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void gst_gl_bumper_reset (GstGLFilter * filter);
+static gboolean gst_gl_bumper_init_shader (GstGLFilter * filter);
+static gboolean gst_gl_bumper_filter_texture (GstGLFilter * filter,
+ guint in_tex, guint out_tex);
+static void gst_gl_bumper_callback (gint width, gint height, guint texture,
+ gpointer stuff);
+
+//vertex source
+static const gchar *bumper_v_src =
+ "attribute vec3 aTangent;\n"
+ "\n"
+ "varying vec3 vNormal;\n"
+ "varying vec3 vTangent;\n"
+ "varying vec3 vVertexToLight0;\n"
+ "varying vec3 vVertexToLight1;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " // transform the vertex\n"
+ " gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n"
+ "\n"
+ " // transform the normal and the tangent to scene coords\n"
+ " vNormal = normalize(gl_NormalMatrix * gl_Normal);\n"
+ " vTangent = normalize(gl_NormalMatrix * aTangent);\n"
+ "\n"
+ " // transforming the vertex position to modelview-space\n"
+ " //const vec4 vertexInSceneCoords = gl_ModelViewMatrix * gl_Vertex;\n"
+ "\n"
+ " // calculate the vector from the vertex position to the light position\n"
+ " vVertexToLight0 = normalize(gl_LightSource[0].position).xyz;\n"
+ " vVertexToLight1 = normalize(gl_LightSource[1].position).xyz;\n"
+ "\n"
+ " // transit vertex color\n"
+ " gl_FrontColor = gl_BackColor = gl_Color;\n"
+ "\n"
+ " // use the two first sets of texture coordinates in the fragment shader\n"
+ " gl_TexCoord[0] = gl_MultiTexCoord0;\n"
+ " gl_TexCoord[1] = gl_MultiTexCoord1;\n" "}\n";
+
+//fragment source
+static const gchar *bumper_f_src =
+ "uniform sampler2D texture0;\n"
+ "uniform sampler2D texture1;\n"
+ "\n"
+ "varying vec3 vNormal;\n"
+ "varying vec3 vTangent;\n"
+ "varying vec3 vVertexToLight0;\n"
+ "varying vec3 vVertexToLight1;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " // get the color of the textures\n"
+ " vec4 textureColor = texture2D(texture0, gl_TexCoord[0].st);\n"
+ " vec3 normalmapItem = texture2D(texture1, gl_TexCoord[1].st).xyz * 2.0 - 1.0;\n"
+ "\n"
+ " // calculate matrix that transform from tangent space to normalmap space (contrary of intuition)\n"
+ " vec3 binormal = cross(vNormal, vTangent);\n"
+ " mat3 tangentSpace2normalmapSpaceMat = mat3(vTangent, binormal, vNormal);\n"
+ "\n"
+ " // disturb the normal\n"
+ " vec3 disturbedNormal = tangentSpace2normalmapSpaceMat * normalmapItem;\n"
+ "\n"
+ " // calculate the diffuse term and clamping it to [0;1]\n"
+ " float diffuseTerm0 = clamp(dot(disturbedNormal, vVertexToLight0), 0.0, 1.0);\n"
+ " float diffuseTerm1 = clamp(dot(disturbedNormal, vVertexToLight1), 0.0, 1.0);\n"
+ "\n"
+ " vec3 irradiance = (diffuseTerm0 * gl_LightSource[0].diffuse.rgb + diffuseTerm1 * gl_LightSource[1].diffuse.rgb);\n"
+ "\n"
+ " // calculate the final color\n"
+ " gl_FragColor = vec4(irradiance * textureColor.rgb, textureColor.w);\n"
+ "}\n";
+
+#define LOAD_ERROR(context, msg) { gst_gl_context_set_error (context, "unable to load %s: %s", bumper->location, msg); return; }
+
+//png reading error handler
+static void
+user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
+{
+ g_warning ("%s\n", warning_msg);
+}
+
+//Called in the gl thread
+static void
+gst_gl_bumper_init_resources (GstGLFilter * filter)
+{
+ GstGLBumper *bumper = GST_GL_BUMPER (filter);
+ GstGLContext *context = filter->context;
+ const GstGLFuncs *gl = context->gl_vtable;
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ gint bit_depth = 0;
+ gint color_type = 0;
+ gint interlace_type = 0;
+ png_FILE_p fp = NULL;
+ guint y = 0;
+ guchar *raw_data = NULL;
+ guchar **rows = NULL;
+ png_byte magic[8];
+ gint n_read;
+
+ if (!bumper->location) {
+ gst_gl_context_set_error (context, "A filename is required");
+ return;
+ }
+
+ /* BEGIN load png image file */
+
+ if ((fp = fopen (bumper->location, "rb")) == NULL)
+ LOAD_ERROR (context, "file not found");
+
+ /* Read magic number */
+ n_read = fread (magic, 1, sizeof (magic), fp);
+ if (n_read != sizeof (magic)) {
+ fclose (fp);
+ LOAD_ERROR (context, "can't read PNG magic number");
+ }
+
+ /* Check for valid magic number */
+ if (png_sig_cmp (magic, 0, sizeof (magic))) {
+ fclose (fp);
+ LOAD_ERROR (context, "not a valid PNG image");
+ }
+
+ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (png_ptr == NULL) {
+ fclose (fp);
+ LOAD_ERROR (context, "failed to initialize the png_struct");
+ }
+
+ png_set_error_fn (png_ptr, NULL, NULL, user_warning_fn);
+
+ info_ptr = png_create_info_struct (png_ptr);
+ if (info_ptr == NULL) {
+ fclose (fp);
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ LOAD_ERROR (context,
+ "failed to initialize the memory for image information");
+ }
+
+ png_init_io (png_ptr, fp);
+
+ png_set_sig_bytes (png_ptr, sizeof (magic));
+
+ png_read_info (png_ptr, info_ptr);
+
+ png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_type, int_p_NULL, int_p_NULL);
+
+ if (color_type != PNG_COLOR_TYPE_RGB) {
+ fclose (fp);
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ LOAD_ERROR (context, "color type is not rgb");
+ }
+
+ raw_data = (guchar *) malloc (sizeof (guchar) * width * height * 3);
+
+ rows = (guchar **) malloc (sizeof (guchar *) * height);
+
+ for (y = 0; y < height; ++y)
+ rows[y] = (guchar *) (raw_data + y * width * 3);
+
+ png_read_image (png_ptr, rows);
+
+ free (rows);
+
+ png_read_end (png_ptr, info_ptr);
+ png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
+ fclose (fp);
+
+ /* END load png image file */
+
+ bumper->bumpmap_width = width;
+ bumper->bumpmap_height = height;
+
+ gl->GenTextures (1, &bumper->bumpmap);
+ gl->BindTexture (GL_TEXTURE_2D, bumper->bumpmap);
+ gl->TexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
+ bumper->bumpmap_width, bumper->bumpmap_height, 0,
+ GL_RGB, GL_UNSIGNED_BYTE, raw_data);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
+
+ free (raw_data);
+}
+
+//Called in the gl thread
+static void
+gst_gl_bumper_reset_resources (GstGLFilter * filter)
+{
+ GstGLBumper *bumper = GST_GL_BUMPER (filter);
+
+ if (bumper->bumpmap) {
+ glDeleteTextures (1, &bumper->bumpmap);
+ bumper->bumpmap = 0;
+ }
+}
+
+static void
+gst_gl_bumper_class_init (GstGLBumperClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+ gobject_class->set_property = gst_gl_bumper_set_property;
+ gobject_class->get_property = gst_gl_bumper_get_property;
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ GST_GL_FILTER_CLASS (klass)->filter_texture = gst_gl_bumper_filter_texture;
+ GST_GL_FILTER_CLASS (klass)->display_init_cb = gst_gl_bumper_init_resources;
+ GST_GL_FILTER_CLASS (klass)->display_reset_cb = gst_gl_bumper_reset_resources;
+ GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_bumper_init_shader;
+ GST_GL_FILTER_CLASS (klass)->onReset = gst_gl_bumper_reset;
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOCATION, g_param_spec_string ("location",
+ "Normal map location",
+ "Normal map location", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "OpenGL bumper filter",
+ "Filter/Effect/Video", "Bump mapping filter",
+ "Cyril Comparon <cyril.comparon@gmail.com>, "
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = GST_GL_API_OPENGL;
+}
+
+static void
+gst_gl_bumper_init (GstGLBumper * bumper)
+{
+ bumper->shader = NULL;
+ bumper->bumpmap = 0;
+ bumper->bumpmap_width = 0;
+ bumper->bumpmap_height = 0;
+ bumper->location = NULL;
+}
+
+static void
+gst_gl_bumper_reset (GstGLFilter * filter)
+{
+ GstGLBumper *bumper_filter = GST_GL_BUMPER (filter);
+
+ //blocking call, wait the opengl thread has destroyed the shader
+ if (bumper_filter->shader)
+ gst_gl_context_del_shader (filter->context, bumper_filter->shader);
+ bumper_filter->shader = NULL;
+}
+
+static void
+gst_gl_bumper_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLBumper *bumper = GST_GL_BUMPER (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_free (bumper->location);
+ bumper->location = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_bumper_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLBumper *bumper = GST_GL_BUMPER (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_value_set_string (value, bumper->location);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_bumper_init_shader (GstGLFilter * filter)
+{
+ GstGLBumper *bumper = GST_GL_BUMPER (filter);
+
+ //blocking call, wait the opengl thread has compiled the shader
+ return gst_gl_context_gen_shader (filter->context, bumper_v_src, bumper_f_src,
+ &bumper->shader);
+}
+
+static gboolean
+gst_gl_bumper_filter_texture (GstGLFilter * filter, guint in_tex, guint out_tex)
+{
+ gpointer bumper_filter = GST_GL_BUMPER (filter);
+
+ //blocking call, use a FBO
+ gst_gl_context_use_fbo (filter->context,
+ GST_VIDEO_INFO_WIDTH (&filter->out_info),
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info),
+ filter->fbo, filter->depthbuffer, out_tex, gst_gl_bumper_callback,
+ GST_VIDEO_INFO_WIDTH (&filter->in_info),
+ GST_VIDEO_INFO_HEIGHT (&filter->in_info),
+ in_tex, 45,
+ (gdouble) GST_VIDEO_INFO_WIDTH (&filter->out_info) /
+ (gdouble) GST_VIDEO_INFO_HEIGHT (&filter->out_info), 0.1, 50,
+ GST_GL_DISPLAY_PROJECTION_PERSPECTIVE, bumper_filter);
+
+ return TRUE;
+}
+
+typedef struct _MeshData
+{
+ float x, y, z; /* Vertex */
+ float nx, ny, nz; /* Normal */
+ float s0, t0; /* TexCoord0 */
+ float s1, t1; /* TexCoord1 */
+ float va0, vb0, vc0; /* VertexAttrib */
+} MeshData;
+
+//opengl scene, params: input texture (not the output filter->texture)
+static void
+gst_gl_bumper_callback (gint width, gint height, guint texture, gpointer stuff)
+{
+ static GLfloat xrot = 0;
+ static GLfloat yrot = 0;
+ static GLfloat zrot = 0;
+
+ GstGLFuncs *gl;
+ GstGLBumper *bumper = GST_GL_BUMPER (stuff);
+ GstGLContext *context = GST_GL_FILTER (bumper)->context;
+ GLint locTangent = 0;
+
+ //choose the lights
+ GLfloat light_direction0[] = { 1.0, 0.0, -1.0, 0.0 }; // light goes along -x
+ GLfloat light_direction1[] = { -1.0, 0.0, -1.0, 0.0 }; // light goes along x
+ GLfloat light_diffuse0[] = { 1.0, 1.0, 1.0, 1.0 };
+ GLfloat light_diffuse1[] = { 1.0, 1.0, 1.0, 1.0 };
+ GLfloat mat_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
+
+/* *INDENT-OFF* */
+ MeshData mesh[] = {
+ /* | Vertex | Normal |TexCoord0|TexCoord1| VertexAttrib | */
+/*F*/ { 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0},
+/*r*/ { 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0},
+/*o*/ {-1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0},
+ {-1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0},
+/*R*/ {-1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0},
+/*i*/ {-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0},
+/*g*/ {-1.0, -1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0},
+ {-1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0},
+/*B*/ {-1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0},
+/*a*/ {-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0},
+/*c*/ { 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0},
+ { 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0},
+/*L*/ { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0},
+/*e*/ { 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0},
+/*f*/ { 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0},
+ { 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0},
+/*T*/ { 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0},
+/*o*/ { 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0},
+/*p*/ {-1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0},
+ {-1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0},
+/*B*/ { 1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0},
+/*o*/ { 1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0},
+/*t*/ {-1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, -1.0},
+ {-1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0},
+ };
+
+ GLushort indices[] = {
+ 0, 1, 2,
+ 0, 2, 3,
+ 4, 5, 6,
+ 4, 6, 7,
+ 8, 9, 10,
+ 8, 10, 11,
+ 12, 13, 14,
+ 12, 14, 15,
+ 16, 17, 18,
+ 16, 18, 19,
+ 20, 21, 22,
+ 20, 22, 23
+ };
+
+/* *INDENT-ON* */
+
+ gl = GST_GL_FILTER (bumper)->context->gl_vtable;
+
+ //eye point
+ gl->MatrixMode (GL_PROJECTION);
+ gluLookAt (0.0, 0.0, -6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
+ gl->MatrixMode (GL_MODELVIEW);
+
+ //scene conf
+ gl->Enable (GL_DEPTH_TEST);
+ gl->DepthFunc (GL_LEQUAL);
+ gl->Hint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
+
+ gl->ShadeModel (GL_SMOOTH);
+
+ //set the lights
+ gl->Lightfv (GL_LIGHT0, GL_POSITION, light_direction0);
+ gl->Lightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse0);
+ gl->Lightfv (GL_LIGHT1, GL_POSITION, light_direction1);
+ gl->Lightfv (GL_LIGHT1, GL_DIFFUSE, light_diffuse1);
+ gl->Materialfv (GL_FRONT, GL_DIFFUSE, mat_diffuse);
+ gl->ColorMaterial (GL_FRONT_AND_BACK, GL_DIFFUSE);
+ gl->Enable (GL_COLOR_MATERIAL);
+ gl->Enable (GL_LIGHTING);
+ gl->Enable (GL_LIGHT0);
+ gl->Enable (GL_LIGHT1);
+ //configure shader
+ gst_gl_shader_use (bumper->shader);
+ locTangent =
+ gst_gl_shader_get_attribute_location (bumper->shader, "aTangent");
+
+ //set the normal map
+ gl->ActiveTexture (GL_TEXTURE1);
+ gst_gl_shader_set_uniform_1i (bumper->shader, "texture1", 1);
+ gl->BindTexture (GL_TEXTURE_2D, bumper->bumpmap);
+
+ //set the video texture
+ gl->ActiveTexture (GL_TEXTURE0);
+ gst_gl_shader_set_uniform_1i (bumper->shader, "texture0", 0);
+ gl->BindTexture (GL_TEXTURE_2D, texture);
+
+ gl->Rotatef (xrot, 1.0f, 0.0f, 0.0f);
+ gl->Rotatef (yrot, 0.0f, 1.0f, 0.0f);
+ gl->Rotatef (zrot, 0.0f, 0.0f, 1.0f);
+
+ gl->EnableVertexAttribArray (locTangent);
+
+ gl->ClientActiveTexture (GL_TEXTURE0);
+ gl->EnableClientState (GL_TEXTURE_COORD_ARRAY);
+ gl->EnableClientState (GL_VERTEX_ARRAY);
+ gl->EnableClientState (GL_NORMAL_ARRAY);
+
+ gl->VertexAttribPointer (locTangent, 3, GL_FLOAT, 0, sizeof (MeshData),
+ &mesh[0].va0);
+ gl->VertexPointer (3, GL_FLOAT, sizeof (MeshData), &mesh[0].x);
+ gl->NormalPointer (GL_FLOAT, sizeof (MeshData), &mesh[0].nx);
+ gl->TexCoordPointer (2, GL_FLOAT, sizeof (MeshData), &mesh[0].s0);
+
+ gl->ClientActiveTexture (GL_TEXTURE1);
+ gl->EnableClientState (GL_TEXTURE_COORD_ARRAY);
+ gl->TexCoordPointer (2, GL_FLOAT, sizeof (MeshData), &mesh[0].s1);
+
+ gl->DrawElements (GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, indices);
+
+ gl->DisableClientState (GL_VERTEX_ARRAY);
+ gl->DisableClientState (GL_TEXTURE_COORD_ARRAY);
+ gl->DisableClientState (GL_NORMAL_ARRAY);
+
+ gl->ClientActiveTexture (GL_TEXTURE0);
+ gl->DisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ gl->DisableVertexAttribArray (locTangent);
+
+ gst_gl_context_clear_shader (context);
+
+ gl->Disable (GL_LIGHT0);
+ gl->Disable (GL_LIGHT1);
+ gl->Disable (GL_LIGHTING);
+ gl->Disable (GL_COLOR_MATERIAL);
+
+ xrot += 1.0f;
+ yrot += 0.9f;
+ zrot += 0.6f;
+}
diff --git a/ext/gl/gstglbumper.h b/ext/gl/gstglbumper.h
new file mode 100644
index 000000000..b1a1dc0e3
--- /dev/null
+++ b/ext/gl/gstglbumper.h
@@ -0,0 +1,57 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_BUMPER_H_
+#define _GST_GL_BUMPER_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_BUMPER (gst_gl_bumper_get_type())
+#define GST_GL_BUMPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_BUMPER,GstGLBumper))
+#define GST_IS_GL_BUMPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_BUMPER))
+#define GST_GL_BUMPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_BUMPER,GstGLBumperClass))
+#define GST_IS_GL_BUMPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_BUMPER))
+#define GST_GL_BUMPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_BUMPER,GstGLBumperClass))
+
+typedef struct _GstGLBumper GstGLBumper;
+typedef struct _GstGLBumperClass GstGLBumperClass;
+
+struct _GstGLBumper
+{
+ GstGLFilter filter;
+ GstGLShader *shader;
+ GLuint bumpmap;
+ gint bumpmap_width;
+ gint bumpmap_height;
+ gchar *location;
+};
+
+struct _GstGLBumperClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_bumper_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLBUMPER_H_ */
diff --git a/ext/gl/gstglcolorbalance.c b/ext/gl/gstglcolorbalance.c
new file mode 100644
index 000000000..81630469d
--- /dev/null
+++ b/ext/gl/gstglcolorbalance.c
@@ -0,0 +1,551 @@
+/* GStreamer
+ * Copyright (C) <2016> Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+/*
+ * This file was modified from videobalance and converted to OpenGL
+ */
+
+/**
+ * SECTION:element-glcolorbalance
+ * @title: glcolorbalance
+ *
+ * Adjusts brightness, contrast, hue, saturation on a video stream.
+ *
+ * ## Example launch line
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! glcolorbalance saturation=0.0 ! glcolorconvert ! gldownload ! ximagesink
+ * ]| This pipeline converts the image to black and white by setting the
+ * saturation to 0.0.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <gst/gl/gstglfuncs.h>
+#include <gst/math-compat.h>
+#include <gst/video/colorbalance.h>
+
+#include "gstglcolorbalance.h"
+
+GST_DEBUG_CATEGORY_STATIC (glcolorbalance_debug);
+#define GST_CAT_DEFAULT glcolorbalance_debug
+
+/* GstGLColorBalance properties */
+#define DEFAULT_PROP_CONTRAST 1.0
+#define DEFAULT_PROP_BRIGHTNESS 0.0
+#define DEFAULT_PROP_HUE 0.0
+#define DEFAULT_PROP_SATURATION 1.0
+
+/* *INDENT-OFF* */
+static const gchar *color_balance_frag =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform float brightness;\n"
+ "uniform float contrast;\n"
+ "uniform float saturation;\n"
+ "uniform float hue;\n"
+ "varying vec2 v_texcoord;\n"
+ "uniform sampler2D tex;\n"
+ "#define from_yuv_bt601_offset vec3(-0.0625, -0.5, -0.5)\n"
+ "#define from_yuv_bt601_rcoeff vec3(1.164, 0.000, 1.596)\n"
+ "#define from_yuv_bt601_gcoeff vec3(1.164,-0.391,-0.813)\n"
+ "#define from_yuv_bt601_bcoeff vec3(1.164, 2.018, 0.000)\n"
+ "#define from_rgb_bt601_offset vec3(0.0625, 0.5, 0.5)\n"
+ "#define from_rgb_bt601_ycoeff vec3(0.256816, 0.504154, 0.0979137)\n"
+ "#define from_rgb_bt601_ucoeff vec3(-0.148246, -0.29102, 0.439266)\n"
+ "#define from_rgb_bt601_vcoeff vec3(0.439271, -0.367833, -0.071438)\n"
+ "#define PI 3.14159265\n"
+ "\n"
+ "vec3 yuv_to_rgb (vec3 val) {\n"
+ " vec3 rgb;\n"
+ " val += from_yuv_bt601_offset;\n"
+ " rgb.r = dot(val, from_yuv_bt601_rcoeff);\n"
+ " rgb.g = dot(val, from_yuv_bt601_gcoeff);\n"
+ " rgb.b = dot(val, from_yuv_bt601_bcoeff);\n"
+ " return rgb;\n"
+ "}\n"
+ "vec3 rgb_to_yuv (vec3 val) {\n"
+ " vec3 yuv;\n"
+ " yuv.r = dot(val.rgb, from_rgb_bt601_ycoeff);\n"
+ " yuv.g = dot(val.rgb, from_rgb_bt601_ucoeff);\n"
+ " yuv.b = dot(val.rgb, from_rgb_bt601_vcoeff);\n"
+ " yuv += from_rgb_bt601_offset;\n"
+ " return yuv;\n"
+ "}\n"
+ /* 224 = 256 - (256 - 240) - 16*/
+ "float luma_to_narrow (float luma) {\n"
+ " return (luma + 16.0 / 256.0) * 219.0 / 256.0;"
+ "}\n"
+ "float luma_to_full (float luma) {\n"
+ " return (luma * 256.0 / 219.0) - 16.0 / 256.0;"
+ "}\n"
+ "void main () {\n"
+ " vec3 yuv;\n"
+ /* operations translated from videobalanceand tested with glvideomixer
+ * with one pad's paremeters blend-equation-rgb={subtract,reverse-subtract},
+ * blend-function-src-rgb=src-color and blend-function-dst-rgb=dst-color */
+ " float hue_cos = cos (PI * hue);\n"
+ " float hue_sin = sin (PI * hue);\n"
+ " vec4 rgba = texture2D (tex, v_texcoord);\n"
+ " yuv = rgb_to_yuv (rgba.rgb);\n"
+ " yuv.x = clamp (luma_to_narrow (luma_to_full(yuv.x) * contrast) + brightness, 0.0, 1.0);\n"
+ " vec2 uv = yuv.yz;\n"
+ " yuv.y = clamp (0.5 + (((uv.x - 0.5) * hue_cos + (uv.y - 0.5) * hue_sin) * saturation), 0.0, 1.0);\n"
+ " yuv.z = clamp (0.5 + (((0.5 - uv.x) * hue_sin + (uv.y - 0.5) * hue_cos) * saturation), 0.0, 1.0);\n"
+ " rgba.rgb = yuv_to_rgb (yuv);\n"
+ " gl_FragColor = rgba;\n"
+ "}\n";
+/* *INDENT-ON* */
+
+enum
+{
+ PROP_0,
+ PROP_CONTRAST,
+ PROP_BRIGHTNESS,
+ PROP_HUE,
+ PROP_SATURATION
+};
+
+static void gst_gl_color_balance_colorbalance_init (GstColorBalanceInterface *
+ iface);
+
+static void gst_gl_color_balance_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_color_balance_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+#define gst_gl_color_balance_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLColorBalance, gst_gl_color_balance,
+ GST_TYPE_GL_FILTER,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
+ gst_gl_color_balance_colorbalance_init));
+
+static gboolean
+gst_gl_color_balance_is_passthrough (GstGLColorBalance * glcolorbalance)
+{
+ return glcolorbalance->contrast == 1.0 &&
+ glcolorbalance->brightness == 0.0 &&
+ glcolorbalance->hue == 0.0 && glcolorbalance->saturation == 1.0;
+}
+
+static void
+gst_gl_color_balance_update_properties (GstGLColorBalance * glcolorbalance)
+{
+ gboolean current_passthrough, passthrough;
+ GstBaseTransform *base = GST_BASE_TRANSFORM (glcolorbalance);
+
+ GST_OBJECT_LOCK (glcolorbalance);
+ passthrough = gst_gl_color_balance_is_passthrough (glcolorbalance);
+ GST_OBJECT_UNLOCK (glcolorbalance);
+ current_passthrough = gst_base_transform_is_passthrough (base);
+
+ gst_base_transform_set_passthrough (base, passthrough);
+ if (current_passthrough != passthrough)
+ gst_base_transform_reconfigure_src (base);
+}
+
+static gboolean
+_create_shader (GstGLColorBalance * balance)
+{
+ GstGLBaseFilter *base_filter = GST_GL_BASE_FILTER (balance);
+ GstGLFilter *filter = GST_GL_FILTER (balance);
+ GError *error = NULL;
+
+ if (balance->shader)
+ gst_object_unref (balance->shader);
+
+ if (!(balance->shader =
+ gst_gl_shader_new_link_with_stages (base_filter->context, &error,
+ gst_glsl_stage_new_default_vertex (base_filter->context),
+ gst_glsl_stage_new_with_string (base_filter->context,
+ GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ color_balance_frag), NULL))) {
+ GST_ELEMENT_ERROR (balance, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to initialize colorbalance shader"), ("%s",
+ error ? error->message : "Unknown error"));
+ return FALSE;
+ }
+
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (balance->shader, "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (balance->shader, "a_texcoord");
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_color_balance_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (base_filter);
+
+ if (!_create_shader (balance))
+ return FALSE;
+
+ return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter);
+}
+
+static void
+gst_gl_color_balance_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (base_filter);
+
+ if (balance->shader)
+ gst_object_unref (balance->shader);
+ balance->shader = NULL;
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static void
+gst_gl_color_balance_before_transform (GstBaseTransform * base, GstBuffer * buf)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (base);
+ GstClockTime timestamp, stream_time;
+
+ timestamp = GST_BUFFER_TIMESTAMP (buf);
+ stream_time =
+ gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
+
+ GST_DEBUG_OBJECT (balance, "sync to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (timestamp));
+
+ if (GST_CLOCK_TIME_IS_VALID (stream_time))
+ gst_object_sync_values (GST_OBJECT (balance), stream_time);
+}
+
+static gboolean
+gst_gl_color_balance_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (filter);
+
+ if (!balance->shader)
+ _create_shader (balance);
+
+ gst_gl_shader_use (balance->shader);
+ GST_OBJECT_LOCK (balance);
+ gst_gl_shader_set_uniform_1f (balance->shader, "brightness",
+ balance->brightness);
+ gst_gl_shader_set_uniform_1f (balance->shader, "contrast", balance->contrast);
+ gst_gl_shader_set_uniform_1f (balance->shader, "saturation",
+ balance->saturation);
+ gst_gl_shader_set_uniform_1f (balance->shader, "hue", balance->hue);
+ GST_OBJECT_UNLOCK (balance);
+
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex,
+ balance->shader);
+
+ return TRUE;
+}
+
+static void
+gst_gl_color_balance_finalize (GObject * object)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (object);
+ GList *channels = NULL;
+
+ channels = balance->channels;
+ while (channels) {
+ GstColorBalanceChannel *channel = channels->data;
+
+ g_object_unref (channel);
+ channels->data = NULL;
+ channels = g_list_next (channels);
+ }
+
+ if (balance->channels)
+ g_list_free (balance->channels);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_gl_color_balance_class_init (GstGLColorBalanceClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+ GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
+ GstGLBaseFilterClass *base_filter_class = (GstGLBaseFilterClass *) klass;
+ GstGLFilterClass *filter_class = (GstGLFilterClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (glcolorbalance_debug, "glcolorbalance", 0,
+ "glcolorbalance");
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->finalize = gst_gl_color_balance_finalize;
+ gobject_class->set_property = gst_gl_color_balance_set_property;
+ gobject_class->get_property = gst_gl_color_balance_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_CONTRAST,
+ g_param_spec_double ("contrast", "Contrast", "contrast",
+ 0.0, 2.0, DEFAULT_PROP_CONTRAST,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BRIGHTNESS,
+ g_param_spec_double ("brightness", "Brightness", "brightness", -1.0, 1.0,
+ DEFAULT_PROP_BRIGHTNESS,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_HUE,
+ g_param_spec_double ("hue", "Hue", "hue", -1.0, 1.0, DEFAULT_PROP_HUE,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_SATURATION,
+ g_param_spec_double ("saturation", "Saturation", "saturation", 0.0, 2.0,
+ DEFAULT_PROP_SATURATION,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_static_metadata (gstelement_class, "Video balance",
+ "Filter/Effect/Video",
+ "Adjusts brightness, contrast, hue, saturation on a video stream",
+ "Matthew Waters <matthew@centricular.com>");
+
+ trans_class->before_transform =
+ GST_DEBUG_FUNCPTR (gst_gl_color_balance_before_transform);
+ trans_class->transform_ip_on_passthrough = FALSE;
+
+ base_filter_class->gl_start =
+ GST_DEBUG_FUNCPTR (gst_gl_color_balance_gl_start);
+ base_filter_class->gl_stop = GST_DEBUG_FUNCPTR (gst_gl_color_balance_gl_stop);
+
+ filter_class->filter_texture =
+ GST_DEBUG_FUNCPTR (gst_gl_color_balance_filter_texture);
+}
+
+static void
+gst_gl_color_balance_init (GstGLColorBalance * glcolorbalance)
+{
+ const gchar *channels[4] = { "HUE", "SATURATION",
+ "BRIGHTNESS", "CONTRAST"
+ };
+ gint i;
+
+ /* Initialize propertiews */
+ glcolorbalance->contrast = DEFAULT_PROP_CONTRAST;
+ glcolorbalance->brightness = DEFAULT_PROP_BRIGHTNESS;
+ glcolorbalance->hue = DEFAULT_PROP_HUE;
+ glcolorbalance->saturation = DEFAULT_PROP_SATURATION;
+
+ gst_gl_color_balance_update_properties (glcolorbalance);
+
+ /* Generate the channels list */
+ for (i = 0; i < G_N_ELEMENTS (channels); i++) {
+ GstColorBalanceChannel *channel;
+
+ channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
+ channel->label = g_strdup (channels[i]);
+ channel->min_value = -1000;
+ channel->max_value = 1000;
+
+ glcolorbalance->channels =
+ g_list_append (glcolorbalance->channels, channel);
+ }
+}
+
+static const GList *
+gst_gl_color_balance_colorbalance_list_channels (GstColorBalance * balance)
+{
+ GstGLColorBalance *glcolorbalance = GST_GL_COLOR_BALANCE (balance);
+
+ g_return_val_if_fail (glcolorbalance != NULL, NULL);
+ g_return_val_if_fail (GST_IS_GL_COLOR_BALANCE (glcolorbalance), NULL);
+
+ return glcolorbalance->channels;
+}
+
+static void
+gst_gl_color_balance_colorbalance_set_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel, gint value)
+{
+ GstGLColorBalance *vb = GST_GL_COLOR_BALANCE (balance);
+ gdouble new_val;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (vb != NULL);
+ g_return_if_fail (GST_IS_GL_COLOR_BALANCE (vb));
+ g_return_if_fail (channel->label != NULL);
+
+ GST_OBJECT_LOCK (vb);
+ if (!g_ascii_strcasecmp (channel->label, "HUE")) {
+ new_val = (value + 1000.0) * 2.0 / 2000.0 - 1.0;
+ changed = new_val != vb->hue;
+ vb->hue = new_val;
+ } else if (!g_ascii_strcasecmp (channel->label, "SATURATION")) {
+ new_val = (value + 1000.0) * 2.0 / 2000.0;
+ changed = new_val != vb->saturation;
+ vb->saturation = new_val;
+ } else if (!g_ascii_strcasecmp (channel->label, "BRIGHTNESS")) {
+ new_val = (value + 1000.0) * 2.0 / 2000.0 - 1.0;
+ changed = new_val != vb->brightness;
+ vb->brightness = new_val;
+ } else if (!g_ascii_strcasecmp (channel->label, "CONTRAST")) {
+ new_val = (value + 1000.0) * 2.0 / 2000.0;
+ changed = new_val != vb->contrast;
+ vb->contrast = new_val;
+ }
+ GST_OBJECT_UNLOCK (vb);
+
+ if (changed)
+ gst_gl_color_balance_update_properties (vb);
+
+ if (changed) {
+ gst_color_balance_value_changed (balance, channel,
+ gst_color_balance_get_value (balance, channel));
+ }
+}
+
+static gint
+gst_gl_color_balance_colorbalance_get_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel)
+{
+ GstGLColorBalance *vb = GST_GL_COLOR_BALANCE (balance);
+ gint value = 0;
+
+ g_return_val_if_fail (vb != NULL, 0);
+ g_return_val_if_fail (GST_IS_GL_COLOR_BALANCE (vb), 0);
+ g_return_val_if_fail (channel->label != NULL, 0);
+
+ if (!g_ascii_strcasecmp (channel->label, "HUE")) {
+ value = (vb->hue + 1) * 2000.0 / 2.0 - 1000.0;
+ } else if (!g_ascii_strcasecmp (channel->label, "SATURATION")) {
+ value = vb->saturation * 2000.0 / 2.0 - 1000.0;
+ } else if (!g_ascii_strcasecmp (channel->label, "BRIGHTNESS")) {
+ value = (vb->brightness + 1) * 2000.0 / 2.0 - 1000.0;
+ } else if (!g_ascii_strcasecmp (channel->label, "CONTRAST")) {
+ value = vb->contrast * 2000.0 / 2.0 - 1000.0;
+ }
+
+ return value;
+}
+
+static GstColorBalanceType
+gst_gl_color_balance_colorbalance_get_balance_type (GstColorBalance * balance)
+{
+ return GST_COLOR_BALANCE_HARDWARE;
+}
+
+static void
+gst_gl_color_balance_colorbalance_init (GstColorBalanceInterface * iface)
+{
+ iface->list_channels = gst_gl_color_balance_colorbalance_list_channels;
+ iface->set_value = gst_gl_color_balance_colorbalance_set_value;
+ iface->get_value = gst_gl_color_balance_colorbalance_get_value;
+ iface->get_balance_type = gst_gl_color_balance_colorbalance_get_balance_type;
+}
+
+static GstColorBalanceChannel *
+gst_gl_color_balance_find_channel (GstGLColorBalance * balance,
+ const gchar * label)
+{
+ GList *l;
+
+ for (l = balance->channels; l; l = l->next) {
+ GstColorBalanceChannel *channel = l->data;
+
+ if (g_ascii_strcasecmp (channel->label, label) == 0)
+ return channel;
+ }
+ return NULL;
+}
+
+static void
+gst_gl_color_balance_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (object);
+ gdouble d;
+ const gchar *label = NULL;
+
+ GST_OBJECT_LOCK (balance);
+ switch (prop_id) {
+ case PROP_CONTRAST:
+ d = g_value_get_double (value);
+ GST_DEBUG_OBJECT (balance, "Changing contrast from %lf to %lf",
+ balance->contrast, d);
+ if (d != balance->contrast)
+ label = "CONTRAST";
+ balance->contrast = d;
+ break;
+ case PROP_BRIGHTNESS:
+ d = g_value_get_double (value);
+ GST_DEBUG_OBJECT (balance, "Changing brightness from %lf to %lf",
+ balance->brightness, d);
+ if (d != balance->brightness)
+ label = "BRIGHTNESS";
+ balance->brightness = d;
+ break;
+ case PROP_HUE:
+ d = g_value_get_double (value);
+ GST_DEBUG_OBJECT (balance, "Changing hue from %lf to %lf", balance->hue,
+ d);
+ if (d != balance->hue)
+ label = "HUE";
+ balance->hue = d;
+ break;
+ case PROP_SATURATION:
+ d = g_value_get_double (value);
+ GST_DEBUG_OBJECT (balance, "Changing saturation from %lf to %lf",
+ balance->saturation, d);
+ if (d != balance->saturation)
+ label = "SATURATION";
+ balance->saturation = d;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ GST_OBJECT_UNLOCK (balance);
+ gst_gl_color_balance_update_properties (balance);
+
+ if (label) {
+ GstColorBalanceChannel *channel =
+ gst_gl_color_balance_find_channel (balance, label);
+ gst_color_balance_value_changed (GST_COLOR_BALANCE (balance), channel,
+ gst_color_balance_get_value (GST_COLOR_BALANCE (balance), channel));
+ }
+}
+
+static void
+gst_gl_color_balance_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLColorBalance *balance = GST_GL_COLOR_BALANCE (object);
+
+ switch (prop_id) {
+ case PROP_CONTRAST:
+ g_value_set_double (value, balance->contrast);
+ break;
+ case PROP_BRIGHTNESS:
+ g_value_set_double (value, balance->brightness);
+ break;
+ case PROP_HUE:
+ g_value_set_double (value, balance->hue);
+ break;
+ case PROP_SATURATION:
+ g_value_set_double (value, balance->saturation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
diff --git a/ext/gl/gstglcolorbalance.h b/ext/gl/gstglcolorbalance.h
new file mode 100644
index 000000000..cf0bcd7b8
--- /dev/null
+++ b/ext/gl/gstglcolorbalance.h
@@ -0,0 +1,73 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ *
+ * 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.
+ */
+
+
+#ifndef __GST_GL_COLOR_BALANCE_H__
+#define __GST_GL_COLOR_BALANCE_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_COLOR_BALANCE \
+ (gst_gl_color_balance_get_type())
+#define GST_GL_COLOR_BALANCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_COLOR_BALANCE,GstGLColorBalance))
+#define GST_GL_COLOR_BALANCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_COLOR_BALANCE,GstGLColorBalanceClass))
+#define GST_IS_GL_COLOR_BALANCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_COLOR_BALANCE))
+#define GST_IS_GL_COLOR_BALANCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_COLOR_BALANCE))
+
+typedef struct _GstGLColorBalance GstGLColorBalance;
+typedef struct _GstGLColorBalanceClass GstGLColorBalanceClass;
+
+/**
+ * GstGLColorBalance:
+ *
+ * Opaque data structure.
+ */
+struct _GstGLColorBalance {
+ GstGLFilter videofilter;
+
+ /* < private > */
+ GstGLShader *shader;
+
+ /* channels for interface */
+ GList *channels;
+
+ /* properties */
+ gdouble contrast;
+ gdouble brightness;
+ gdouble hue;
+ gdouble saturation;
+};
+
+struct _GstGLColorBalanceClass {
+ GstGLFilterClass parent_class;
+};
+
+GType gst_gl_color_balance_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_GL_COLOR_BALANCE_H__ */
diff --git a/ext/gl/gstglcolorconvertelement.c b/ext/gl/gstglcolorconvertelement.c
new file mode 100644
index 000000000..642b4942d
--- /dev/null
+++ b/ext/gl/gstglcolorconvertelement.c
@@ -0,0 +1,245 @@
+/*
+ * GStreamer
+ * Copyright (C) 2012-2014 Matthew Waters <ystree00@gmail.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gl.h>
+#include "gstglcolorconvertelement.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_gl_color_convert_element_debug);
+#define GST_CAT_DEFAULT gst_gl_color_convert_element_debug
+
+G_DEFINE_TYPE_WITH_CODE (GstGLColorConvertElement, gst_gl_color_convert_element,
+ GST_TYPE_GL_BASE_FILTER,
+ GST_DEBUG_CATEGORY_INIT (gst_gl_color_convert_element_debug,
+ "glconvertelement", 0, "convert");
+ );
+
+static gboolean gst_gl_color_convert_element_set_caps (GstBaseTransform * bt,
+ GstCaps * in_caps, GstCaps * out_caps);
+static GstCaps *gst_gl_color_convert_element_transform_caps (GstBaseTransform *
+ bt, GstPadDirection direction, GstCaps * caps, GstCaps * filter);
+static gboolean gst_gl_color_convert_element_get_unit_size (GstBaseTransform *
+ trans, GstCaps * caps, gsize * size);
+static gboolean
+gst_gl_color_convert_element_filter_meta (GstBaseTransform * trans,
+ GstQuery * query, GType api, const GstStructure * params);
+static gboolean gst_gl_color_convert_element_decide_allocation (GstBaseTransform
+ * trans, GstQuery * query);
+static GstFlowReturn
+gst_gl_color_convert_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * inbuf, GstBuffer ** outbuf);
+static GstFlowReturn gst_gl_color_convert_element_transform (GstBaseTransform *
+ bt, GstBuffer * inbuf, GstBuffer * outbuf);
+static GstCaps *gst_gl_color_convert_element_fixate_caps (GstBaseTransform *
+ bt, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
+
+static GstStaticPadTemplate gst_gl_color_convert_element_src_pad_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_GL_COLOR_CONVERT_VIDEO_CAPS));
+
+static GstStaticPadTemplate gst_gl_color_convert_element_sink_pad_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_GL_COLOR_CONVERT_VIDEO_CAPS));
+
+static gboolean
+gst_gl_color_convert_element_stop (GstBaseTransform * bt)
+{
+ GstGLColorConvertElement *convert = GST_GL_COLOR_CONVERT_ELEMENT (bt);
+
+ if (convert->convert) {
+ gst_object_unref (convert->convert);
+ convert->convert = NULL;
+ }
+
+ gst_caps_replace (&convert->in_caps, NULL);
+ gst_caps_replace (&convert->out_caps, NULL);
+
+ return
+ GST_BASE_TRANSFORM_CLASS (gst_gl_color_convert_element_parent_class)->stop
+ (bt);
+}
+
+static void
+gst_gl_color_convert_element_class_init (GstGLColorConvertElementClass * klass)
+{
+ GstBaseTransformClass *bt_class = GST_BASE_TRANSFORM_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ bt_class->transform_caps = gst_gl_color_convert_element_transform_caps;
+ bt_class->set_caps = gst_gl_color_convert_element_set_caps;
+ bt_class->get_unit_size = gst_gl_color_convert_element_get_unit_size;
+ bt_class->filter_meta = gst_gl_color_convert_element_filter_meta;
+ bt_class->decide_allocation = gst_gl_color_convert_element_decide_allocation;
+ bt_class->prepare_output_buffer =
+ gst_gl_color_convert_element_prepare_output_buffer;
+ bt_class->transform = gst_gl_color_convert_element_transform;
+ bt_class->stop = gst_gl_color_convert_element_stop;
+ bt_class->fixate_caps = gst_gl_color_convert_element_fixate_caps;
+
+ bt_class->passthrough_on_same_caps = TRUE;
+
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_color_convert_element_src_pad_template);
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_color_convert_element_sink_pad_template);
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL color converter", "Filter/Converter/Video",
+ "Converts between color spaces using OpenGL shaders",
+ "Matthew Waters <matthew@centricular.com>");
+}
+
+static void
+gst_gl_color_convert_element_init (GstGLColorConvertElement * convert)
+{
+ gst_base_transform_set_prefer_passthrough (GST_BASE_TRANSFORM (convert),
+ TRUE);
+}
+
+static gboolean
+gst_gl_color_convert_element_set_caps (GstBaseTransform * bt,
+ GstCaps * in_caps, GstCaps * out_caps)
+{
+ GstGLColorConvertElement *convert = GST_GL_COLOR_CONVERT_ELEMENT (bt);
+
+ gst_caps_replace (&convert->in_caps, in_caps);
+ gst_caps_replace (&convert->out_caps, out_caps);
+
+ if (convert->convert)
+ gst_gl_color_convert_set_caps (convert->convert, in_caps, out_caps);
+
+ return TRUE;
+}
+
+static GstCaps *
+gst_gl_color_convert_element_transform_caps (GstBaseTransform * bt,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (bt)->context;
+
+ return gst_gl_color_convert_transform_caps (context, direction, caps, filter);
+}
+
+static gboolean
+gst_gl_color_convert_element_get_unit_size (GstBaseTransform * trans,
+ GstCaps * caps, gsize * size)
+{
+ gboolean ret = FALSE;
+ GstVideoInfo info;
+
+ ret = gst_video_info_from_caps (&info, caps);
+ if (ret)
+ *size = GST_VIDEO_INFO_SIZE (&info);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_color_convert_element_filter_meta (GstBaseTransform * trans,
+ GstQuery * query, GType api, const GstStructure * params)
+{
+ /* propose all metadata upstream */
+ return TRUE;
+}
+
+static gboolean
+gst_gl_color_convert_element_decide_allocation (GstBaseTransform * trans,
+ GstQuery * query)
+{
+ GstGLColorConvertElement *convert = GST_GL_COLOR_CONVERT_ELEMENT (trans);
+ GstGLContext *context;
+
+ /* get gl context */
+ if (!GST_BASE_TRANSFORM_CLASS
+ (gst_gl_color_convert_element_parent_class)->decide_allocation (trans,
+ query))
+ return FALSE;
+
+ context = GST_GL_BASE_FILTER (trans)->context;
+
+ if (!convert->convert)
+ convert->convert = gst_gl_color_convert_new (context);
+
+ if (!gst_gl_color_convert_set_caps (convert->convert, convert->in_caps,
+ convert->out_caps))
+ return FALSE;
+
+ if (!gst_gl_color_convert_decide_allocation (convert->convert, query))
+ return FALSE;
+
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_gl_color_convert_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * inbuf, GstBuffer ** outbuf)
+{
+ GstGLColorConvertElement *convert = GST_GL_COLOR_CONVERT_ELEMENT (bt);
+ GstBaseTransformClass *bclass;
+
+ bclass = GST_BASE_TRANSFORM_GET_CLASS (bt);
+
+ if (gst_base_transform_is_passthrough (bt)) {
+ *outbuf = inbuf;
+ return GST_FLOW_OK;
+ }
+
+ if (!convert->convert)
+ return GST_FLOW_NOT_NEGOTIATED;
+
+ *outbuf = gst_gl_color_convert_perform (convert->convert, inbuf);
+ if (!*outbuf) {
+ GST_ELEMENT_ERROR (bt, RESOURCE, NOT_FOUND,
+ ("%s", "Failed to convert video buffer"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+
+ /* basetransform doesn't unref if they're the same */
+ if (inbuf == *outbuf)
+ gst_buffer_unref (*outbuf);
+ else
+ bclass->copy_metadata (bt, inbuf, *outbuf);
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_gl_color_convert_element_transform (GstBaseTransform * bt,
+ GstBuffer * inbuf, GstBuffer * outbuf)
+{
+ return GST_FLOW_OK;
+}
+
+static GstCaps *
+gst_gl_color_convert_element_fixate_caps (GstBaseTransform *
+ bt, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (bt)->context;
+
+ return gst_gl_color_convert_fixate_caps (context, direction, caps, othercaps);
+}
diff --git a/ext/gl/gstglcolorconvertelement.h b/ext/gl/gstglcolorconvertelement.h
new file mode 100644
index 000000000..2a0dd1dca
--- /dev/null
+++ b/ext/gl/gstglcolorconvertelement.h
@@ -0,0 +1,59 @@
+/*
+ * GStreamer
+ * Copyright (C) 2012 Matthew Waters <ystree00@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_COLOR_CONVERT_ELEMENT_H__
+#define __GST_GL_COLOR_CONVERT_ELEMENT_H__
+
+#include <gst/video/video.h>
+#include <gst/gstmemory.h>
+
+#include <gst/gl/gstgl_fwd.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_color_convert_element_get_type (void);
+#define GST_TYPE_GL_COLOR_CONVERT_ELEMENT (gst_gl_color_convert_element_get_type())
+#define GST_GL_COLOR_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_COLOR_CONVERT_ELEMENT,GstGLColorConvertElement))
+#define GST_GL_COLOR_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_DISPLAY,GstGLColorConvertElementClass))
+#define GST_IS_GL_COLOR_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_COLOR_CONVERT_ELEMENT))
+#define GST_IS_GL_COLOR_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_COLOR_CONVERT_ELEMENT))
+#define GST_GL_COLOR_CONVERT_ELEMENT_CAST(obj) ((GstGLColorConvertElement*)(obj))
+
+typedef struct _GstGLColorConvertElement GstGLColorConvertElement;
+typedef struct _GstGLColorConvertElementClass GstGLColorConvertElementClass;
+typedef struct _GstGLColorConvertElementPrivate GstGLColorConvertElementPrivate;
+
+struct _GstGLColorConvertElement
+{
+ GstGLBaseFilter parent;
+
+ GstGLColorConvert *convert;
+ GstCaps *in_caps;
+ GstCaps *out_caps;
+};
+
+struct _GstGLColorConvertElementClass
+{
+ GstGLBaseFilterClass object_class;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GL_COLOR_CONVERT_ELEMENT_H__ */
diff --git a/ext/gl/gstglcolorscale.c b/ext/gl/gstglcolorscale.c
new file mode 100644
index 000000000..88b71ef70
--- /dev/null
+++ b/ext/gl/gstglcolorscale.c
@@ -0,0 +1,187 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glcolorscale
+ * @title: glcolorscale
+ *
+ * video frame scaling and colorspace conversion.
+ *
+ * ## Scaling and Color space conversion
+ *
+ * Equivalent to glupload ! gldownload.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! video/x-raw ! glcolorscale ! ximagesink
+ * ]| A pipeline to test colorspace conversion.
+ * FBO is required.
+ |[
+ * gst-launch-1.0 -v videotestsrc ! video/x-raw, width=640, height=480, format=AYUV ! glcolorscale ! \
+ * video/x-raw, width=320, height=240, format=YV12 ! videoconvert ! autovideosink
+ * ]| A pipeline to test hardware scaling and colorspace conversion.
+ * FBO and GLSL are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglcolorscale.h"
+
+#define GST_CAT_DEFAULT gst_gl_colorscale_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+/* Properties */
+enum
+{
+ PROP_0
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_colorscale_debug, "glcolorscale", 0, "glcolorscale element");
+#define gst_gl_colorscale_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLColorscale, gst_gl_colorscale,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_colorscale_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_colorscale_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_colorscale_gl_start (GstGLBaseFilter * base_filter);
+static void gst_gl_colorscale_gl_stop (GstGLBaseFilter * base_filter);
+
+static gboolean gst_gl_colorscale_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static void
+gst_gl_colorscale_class_init (GstGLColorscaleClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstBaseTransformClass *basetransform_class;
+ GstGLBaseFilterClass *base_filter_class;
+ GstGLFilterClass *filter_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+ basetransform_class = GST_BASE_TRANSFORM_CLASS (klass);
+ base_filter_class = GST_GL_BASE_FILTER_CLASS (klass);
+ filter_class = GST_GL_FILTER_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_colorscale_set_property;
+ gobject_class->get_property = gst_gl_colorscale_get_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL color scale",
+ "Filter/Effect/Video", "Colorspace converter and video scaler",
+ "Julien Isorce <julien.isorce@gmail.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ basetransform_class->passthrough_on_same_caps = TRUE;
+
+ base_filter_class->gl_start = GST_DEBUG_FUNCPTR (gst_gl_colorscale_gl_start);
+ base_filter_class->gl_stop = GST_DEBUG_FUNCPTR (gst_gl_colorscale_gl_stop);
+ base_filter_class->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2;
+
+ filter_class->filter_texture = gst_gl_colorscale_filter_texture;
+}
+
+static void
+gst_gl_colorscale_init (GstGLColorscale * colorscale)
+{
+}
+
+static void
+gst_gl_colorscale_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_colorscale_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_colorscale_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLColorscale *colorscale = GST_GL_COLORSCALE (base_filter);
+ GstGLFilter *filter = GST_GL_FILTER (base_filter);
+ GstGLShader *shader;
+ GError *error = NULL;
+
+ if (!(shader = gst_gl_shader_new_default (base_filter->context, &error))) {
+ GST_ERROR_OBJECT (colorscale, "Failed to initialize shader: %s",
+ error->message);
+ gst_object_unref (shader);
+ return FALSE;
+ }
+
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_texcoord");
+
+ colorscale->shader = shader;
+
+ return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter);
+}
+
+static void
+gst_gl_colorscale_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLColorscale *colorscale = GST_GL_COLORSCALE (base_filter);
+
+ if (colorscale->shader) {
+ gst_object_unref (colorscale->shader);
+ colorscale->shader = NULL;
+ }
+
+ return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static gboolean
+gst_gl_colorscale_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLColorscale *colorscale = GST_GL_COLORSCALE (filter);
+
+ if (gst_gl_context_get_gl_api (GST_GL_BASE_FILTER (filter)->context))
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex,
+ colorscale->shader);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglcolorscale.h b/ext/gl/gstglcolorscale.h
new file mode 100644
index 000000000..933599651
--- /dev/null
+++ b/ext/gl/gstglcolorscale.h
@@ -0,0 +1,58 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GLCOLORSCALE_H_
+#define _GST_GLCOLORSCALE_H_
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_COLORSCALE (gst_gl_colorscale_get_type())
+#define GST_GL_COLORSCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_COLORSCALE,GstGLColorscale))
+#define GST_IS_GL_COLORSCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_COLORSCALE))
+#define GST_GL_COLORSCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_COLORSCALE,GstGLColorscaleClass))
+#define GST_IS_GL_COLORSCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_COLORSCALE))
+#define GST_GL_COLORSCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_COLORSCALE,GstGLColorscaleClass))
+
+typedef struct _GstGLColorscale GstGLColorscale;
+typedef struct _GstGLColorscaleClass GstGLColorscaleClass;
+
+
+struct _GstGLColorscale
+{
+ GstGLFilter filter;
+
+ GstGLShader *shader;
+};
+
+struct _GstGLColorscaleClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_colorscale_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLCOLORSCALE_H_ */
diff --git a/ext/gl/gstgldeinterlace.c b/ext/gl/gstgldeinterlace.c
new file mode 100644
index 000000000..3b26715dc
--- /dev/null
+++ b/ext/gl/gstgldeinterlace.c
@@ -0,0 +1,517 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@mail.com>
+ *
+ * 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:element-deinterlace
+ * @title: deinterlace
+ *
+ * Deinterlacing using based on fragment shaders.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! gldeinterlace ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglfuncs.h>
+
+#include "gstgldeinterlace.h"
+
+#define GST_CAT_DEFAULT gst_gl_deinterlace_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_METHOD
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_deinterlace_debug, "gldeinterlace", 0, "gldeinterlace element");
+#define gst_gl_deinterlace_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLDeinterlace, gst_gl_deinterlace,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_deinterlace_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_deinterlace_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_deinterlace_start (GstBaseTransform * trans);
+static gboolean gst_gl_deinterlace_reset (GstBaseTransform * trans);
+static gboolean gst_gl_deinterlace_init_fbo (GstGLFilter * filter);
+static gboolean gst_gl_deinterlace_filter (GstGLFilter * filter,
+ GstBuffer * inbuf, GstBuffer * outbuf);
+static gboolean gst_gl_deinterlace_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+static gboolean gst_gl_deinterlace_vfir_callback (GstGLFilter * filter,
+ GstGLMemory * in_tex, gpointer stuff);
+static gboolean gst_gl_deinterlace_greedyh_callback (GstGLFilter * filter,
+ GstGLMemory * in_tex, gpointer stuff);
+
+/* *INDENT-OFF* */
+static const gchar *greedyh_fragment_source =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform sampler2D tex;\n"
+ "uniform sampler2D tex_prev;\n"
+ "uniform float max_comb;\n"
+ "uniform float motion_threshold;\n"
+ "uniform float motion_sense;\n"
+ "uniform float width;\n"
+ "uniform float height;\n"
+ "varying vec2 v_texcoord;\n"
+
+ "void main () {\n"
+ " if (int(mod(v_texcoord.y * height, 2.0)) == 0) {\n"
+ " gl_FragColor = vec4(texture2D(tex_prev, v_texcoord).rgb, 1.0);\n"
+ " } else {\n"
+ " vec2 texcoord_L1_a1, texcoord_L3_a1, texcoord_L1, texcoord_L3, texcoord_L1_1, texcoord_L3_1;\n"
+ " vec3 L1_a1, L3_a1, L1, L3, L1_1, L3_1;\n"
+
+ " texcoord_L1 = vec2(v_texcoord.x, v_texcoord.y - 1.0 / height);\n"
+ " texcoord_L3 = vec2(v_texcoord.x, v_texcoord.y + 1.0 / height);\n"
+ " L1 = texture2D(tex_prev, texcoord_L1).rgb;\n"
+ " L3 = texture2D(tex_prev, texcoord_L3).rgb;\n"
+ " if (v_texcoord.x == 1.0 && v_texcoord.y == 1.0) {\n"
+ " L1_1 = L1;\n"
+ " L3_1 = L3;\n"
+ " } else {\n"
+ " texcoord_L1_1 = vec2(v_texcoord.x + 1.0 / width, v_texcoord.y - 1.0 / height);\n"
+ " texcoord_L3_1 = vec2(v_texcoord.x + 1.0 / width, v_texcoord.y + 1.0 / height);\n"
+ " L1_1 = texture2D(tex_prev, texcoord_L1_1).rgb;\n"
+ " L3_1 = texture2D(tex_prev, texcoord_L3_1).rgb;\n"
+ " }\n"
+
+ " if (int(ceil(v_texcoord.x + v_texcoord.y)) == 0) {\n"
+ " L1_a1 = L1;\n"
+ " L3_a1 = L3;\n"
+ " } else {\n"
+ " texcoord_L1_a1 = vec2(v_texcoord.x - 1.0 / width, v_texcoord.y - 1.0 / height);\n"
+ " texcoord_L3_a1 = vec2(v_texcoord.x - 1.0 / width, v_texcoord.y + 1.0 / height);\n"
+ " L1_a1 = texture2D(tex_prev, texcoord_L1_a1).rgb;\n"
+ " L3_a1 = texture2D(tex_prev, texcoord_L3_a1).rgb;\n"
+ " }\n"
+ //STEP 1
+ " vec3 avg_a1 = (L1_a1 + L3_a1) / 2.0;\n"
+ " vec3 avg = (L1 + L3) / 2.0;\n"
+ " vec3 avg_1 = (L1_1 + L3_1) / 2.0;\n"
+ " vec3 avg_s = (avg_a1 + avg_1) / 2.0;\n"
+ " vec3 avg_sc = (avg_s + avg) / 2.0;\n"
+ " vec3 L2 = texture2D(tex, v_texcoord).rgb;\n"
+ " vec3 LP2 = texture2D(tex_prev, v_texcoord).rgb;\n"
+ " vec3 best;\n"
+ " if (abs(L2.r - avg_sc.r) < abs(LP2.r - avg_sc.r)) {\n"
+ " best.r = L2.r;\n" " } else {\n"
+ " best.r = LP2.r;\n"
+ " }\n"
+
+ " if (abs(L2.g - avg_sc.g) < abs(LP2.g - avg_sc.g)) {\n"
+ " best.g = L2.g;\n"
+ " } else {\n"
+ " best.g = LP2.g;\n"
+ " }\n"
+
+ " if (abs(L2.b - avg_sc.b) < abs(LP2.b - avg_sc.b)) {\n"
+ " best.b = L2.b;\n"
+ " } else {\n"
+ " best.b = LP2.b;\n"
+ " }\n"
+ //STEP 2
+ " vec3 last;\n"
+ " last.r = clamp(best.r, max(min(L1.r, L3.r) - max_comb, 0.0), min(max(L1.r, L3.r) + max_comb, 1.0));\n"
+ " last.g = clamp(best.g, max(min(L1.g, L3.g) - max_comb, 0.0), min(max(L1.g, L3.g) + max_comb, 1.0));\n"
+ " last.b = clamp(best.b, max(min(L1.b, L3.b) - max_comb, 0.0), min(max(L1.b, L3.b) + max_comb, 1.0));\n"
+ //STEP 3
+ " const vec3 luma = vec3 (0.299011, 0.586987, 0.114001);"
+ " float mov = min(max(abs(dot(L2 - LP2, luma)) - motion_threshold, 0.0) * motion_sense, 1.0);\n"
+ " last = last * (1.0 - mov) + avg_sc * mov;\n"
+ " gl_FragColor = vec4(last, 1.0);\n"
+ " }\n"
+ "}\n";
+
+const gchar *vfir_fragment_source =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform sampler2D tex;\n"
+ "uniform float width;\n"
+ "uniform float height;\n"
+ "varying vec2 v_texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " vec2 topcoord, botcoord;\n"
+ " vec4 cur_color, top_color, bot_color;\n"
+ " topcoord.x = v_texcoord.x;\n"
+ " botcoord.x = v_texcoord.x;\n"
+ " if (v_texcoord.y == 0.0 || v_texcoord.y == 1.0) {\n"
+ " topcoord.y = v_texcoord.y ;\n"
+ " botcoord.y = v_texcoord.y ;\n"
+ " }\n"
+ " else {\n"
+ " topcoord.y = v_texcoord.y - 1.0/height;\n"
+ " botcoord.y = v_texcoord.y + 1.0/height;\n"
+ " }\n"
+ " cur_color = texture2D(tex, v_texcoord);\n"
+ " top_color = texture2D(tex, topcoord);\n"
+ " bot_color = texture2D(tex, botcoord);\n"
+ " gl_FragColor = 0.5*cur_color + 0.25*top_color + 0.25*bot_color;\n"
+ "}";
+/* *INDENT-ON* */
+
+/* dont' forget to edit the following when a new method is added */
+typedef enum
+{
+ GST_GL_DEINTERLACE_VFIR,
+ GST_GL_DEINTERLACE_GREEDYH
+} GstGLDeinterlaceMethod;
+
+static const GEnumValue *
+gst_gl_deinterlace_get_methods (void)
+{
+ static const GEnumValue method_types[] = {
+ {GST_GL_DEINTERLACE_VFIR, "Blur Vertical", "vfir"},
+ {GST_GL_DEINTERLACE_GREEDYH, "Motion Adaptive: Advanced Detection",
+ "greedyh"},
+ {0, NULL, NULL}
+ };
+ return method_types;
+}
+
+#define GST_TYPE_GL_DEINTERLACE_METHODS (gst_gl_deinterlace_method_get_type ())
+static GType
+gst_gl_deinterlace_method_get_type (void)
+{
+ static GType gl_deinterlace_method_type = 0;
+ if (!gl_deinterlace_method_type) {
+ gl_deinterlace_method_type =
+ g_enum_register_static ("GstGLDeinterlaceMethod",
+ gst_gl_deinterlace_get_methods ());
+ }
+ return gl_deinterlace_method_type;
+}
+
+static void
+gst_gl_deinterlace_set_method (GstGLDeinterlace * deinterlace,
+ guint method_types)
+{
+ switch (method_types) {
+ case GST_GL_DEINTERLACE_VFIR:
+ deinterlace->deinterlacefunc = gst_gl_deinterlace_vfir_callback;
+ deinterlace->current_method = method_types;
+ break;
+ case GST_GL_DEINTERLACE_GREEDYH:
+ deinterlace->deinterlacefunc = gst_gl_deinterlace_greedyh_callback;
+ deinterlace->current_method = method_types;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+gst_gl_deinterlace_class_init (GstGLDeinterlaceClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_deinterlace_set_property;
+ gobject_class->get_property = gst_gl_deinterlace_get_property;
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL deinterlacing filter", "Deinterlace",
+ "Deinterlacing based on fragment shaders",
+ "Julien Isorce <julien.isorce@mail.com>");
+
+ g_object_class_install_property (gobject_class,
+ PROP_METHOD,
+ g_param_spec_enum ("method",
+ "Deinterlace Method",
+ "Select which deinterlace method apply to GL video texture",
+ GST_TYPE_GL_DEINTERLACE_METHODS,
+ GST_GL_DEINTERLACE_VFIR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ GST_BASE_TRANSFORM_CLASS (klass)->start = gst_gl_deinterlace_start;
+ GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_deinterlace_reset;
+
+ GST_GL_FILTER_CLASS (klass)->filter = gst_gl_deinterlace_filter;
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_deinterlace_filter_texture;
+ GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_deinterlace_init_fbo;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_deinterlace_init (GstGLDeinterlace * filter)
+{
+ filter->shaderstable = NULL;
+ filter->deinterlacefunc = gst_gl_deinterlace_vfir_callback;
+ filter->current_method = GST_GL_DEINTERLACE_VFIR;
+ filter->prev_buffer = NULL;
+ filter->prev_tex = NULL;
+}
+
+static gboolean
+gst_gl_deinterlace_start (GstBaseTransform * trans)
+{
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (trans);
+
+ deinterlace_filter->shaderstable = g_hash_table_new (g_str_hash, g_str_equal);
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->start (trans);
+}
+
+static void
+gst_gl_deinterlace_ghash_func_clean (gpointer key, gpointer value,
+ gpointer data)
+{
+ GstGLShader *shader = (GstGLShader *) value;
+
+ gst_object_unref (shader);
+
+ value = NULL;
+}
+
+static gboolean
+gst_gl_deinterlace_reset (GstBaseTransform * trans)
+{
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (trans);
+
+ gst_buffer_replace (&deinterlace_filter->prev_buffer, NULL);
+
+ //blocking call, wait the opengl thread has destroyed the shader
+ if (deinterlace_filter->shaderstable) {
+ /* release shaders in the gl thread */
+ g_hash_table_foreach (deinterlace_filter->shaderstable,
+ gst_gl_deinterlace_ghash_func_clean, deinterlace_filter);
+
+ /* clean the htable without calling values destructors
+ * because shaders have been released in the glthread
+ * through the foreach func */
+ g_hash_table_unref (deinterlace_filter->shaderstable);
+ deinterlace_filter->shaderstable = NULL;
+ }
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
+}
+
+static void
+gst_gl_deinterlace_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLDeinterlace *filter = GST_GL_DEINTERLACE (object);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ gst_gl_deinterlace_set_method (filter, g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_deinterlace_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLDeinterlace *filter = GST_GL_DEINTERLACE (object);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ g_value_set_enum (value, filter->current_method);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_deinterlace_init_fbo (GstGLFilter * filter)
+{
+ return TRUE;
+}
+
+static gboolean
+gst_gl_deinterlace_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (filter);
+
+ //blocking call, use a FBO
+ gst_gl_filter_render_to_target (filter, in_tex, out_tex,
+ deinterlace_filter->deinterlacefunc, deinterlace_filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_deinterlace_filter (GstGLFilter * filter, GstBuffer * inbuf,
+ GstBuffer * outbuf)
+{
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (filter);
+
+ gst_gl_filter_filter_texture (filter, inbuf, outbuf);
+
+ gst_buffer_replace (&deinterlace_filter->prev_buffer, inbuf);
+
+ return TRUE;
+}
+
+static GstGLShader *
+gst_gl_deinterlace_get_fragment_shader (GstGLFilter * filter,
+ const gchar * shader_name, const gchar * shader_source)
+{
+ GstGLShader *shader = NULL;
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (filter);
+ GstGLContext *context = GST_GL_BASE_FILTER (filter)->context;
+
+ shader = g_hash_table_lookup (deinterlace_filter->shaderstable, shader_name);
+
+ if (!shader) {
+ GError *error = NULL;
+
+ if (!(shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ shader_source), NULL))) {
+ GST_ELEMENT_ERROR (deinterlace_filter, RESOURCE, NOT_FOUND,
+ ("Failed to initialize %s shader", shader_name), (NULL));
+ }
+
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_texcoord");
+ }
+
+ g_hash_table_insert (deinterlace_filter->shaderstable, (gchar *) shader_name,
+ shader);
+
+ return shader;
+}
+
+static gboolean
+gst_gl_deinterlace_vfir_callback (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer user_data)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (filter)->context;
+ const GstGLFuncs *gl = context->gl_vtable;
+ GstGLShader *shader;
+
+ shader = gst_gl_deinterlace_get_fragment_shader (filter, "vfir",
+ vfir_fragment_source);
+
+ if (!shader)
+ return FALSE;
+
+#if GST_GL_HAVE_OPENGL
+ if (USING_OPENGL (context)) {
+ gl->MatrixMode (GL_PROJECTION);
+ gl->LoadIdentity ();
+ }
+#endif
+
+ gst_gl_shader_use (shader);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (shader, "tex", 0);
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_deinterlace_greedyh_callback (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer user_data)
+{
+ GstGLShader *shader;
+ GstGLDeinterlace *deinterlace_filter = GST_GL_DEINTERLACE (filter);
+ GstGLContext *context = GST_GL_BASE_FILTER (filter)->context;
+ GstGLFuncs *gl = context->gl_vtable;
+
+ shader =
+ gst_gl_deinterlace_get_fragment_shader (filter, "greedhy",
+ greedyh_fragment_source);
+
+ if (!shader)
+ return FALSE;
+
+#if GST_GL_HAVE_OPENGL
+ if (USING_OPENGL (context)) {
+ gl->MatrixMode (GL_PROJECTION);
+ gl->LoadIdentity ();
+ }
+#endif
+
+ gst_gl_shader_use (shader);
+
+ if (G_LIKELY (deinterlace_filter->prev_tex != NULL)) {
+ gl->ActiveTexture (GL_TEXTURE1);
+ gst_gl_shader_set_uniform_1i (shader, "tex_prev", 1);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (deinterlace_filter->prev_tex));
+ }
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (shader, "tex", 0);
+ gst_gl_shader_set_uniform_1f (shader, "max_comb", 5.0f / 255.0f);
+ gst_gl_shader_set_uniform_1f (shader, "motion_threshold", 25.0f / 255.0f);
+ gst_gl_shader_set_uniform_1f (shader, "motion_sense", 30.0f / 255.0f);
+
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ /* we keep the previous buffer around so this is safe */
+ deinterlace_filter->prev_tex = in_tex;
+
+ return TRUE;
+}
diff --git a/ext/gl/gstgldeinterlace.h b/ext/gl/gstgldeinterlace.h
new file mode 100644
index 000000000..3d78d6609
--- /dev/null
+++ b/ext/gl/gstgldeinterlace.h
@@ -0,0 +1,61 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_DEINTERLACE_H_
+#define _GST_GL_DEINTERLACE_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_DEINTERLACE (gst_gl_deinterlace_get_type())
+#define GST_GL_DEINTERLACE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_DEINTERLACE,GstGLDeinterlace))
+#define GST_IS_GL_DEINTERLACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_DEINTERLACE))
+#define GST_GL_DEINTERLACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_DEINTERLACE,GstGLDeinterlaceClass))
+#define GST_IS_GL_DEINTERLACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_DEINTERLACE))
+#define GST_GL_DEINTERLACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_DEINTERLACE,GstGLDeinterlaceClass))
+
+#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
+
+typedef struct _GstGLDeinterlace GstGLDeinterlace;
+typedef struct _GstGLDeinterlaceClass GstGLDeinterlaceClass;
+
+struct _GstGLDeinterlace
+{
+ GstGLFilter filter;
+
+ GstGLFilterRenderFunc deinterlacefunc;
+ GHashTable *shaderstable;
+ GstBuffer *prev_buffer;
+ GstGLMemory * prev_tex;
+
+ gint current_method;
+};
+
+struct _GstGLDeinterlaceClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_deinterlace_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERLAPLACIAN_H_ */
diff --git a/ext/gl/gstgldifferencematte.c b/ext/gl/gstgldifferencematte.c
new file mode 100644
index 000000000..e80577054
--- /dev/null
+++ b/ext/gl/gstgldifferencematte.c
@@ -0,0 +1,569 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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:element-gldifferencematte.
+ * @title: gldifferencematte.
+ *
+ * Saves a background frame and replace it with a pixbuf.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! gldifferencemate location=backgroundimagefile ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <png.h>
+
+#include <gst/gl/gstglfuncs.h>
+
+#include "gstgldifferencematte.h"
+#include "effects/gstgleffectssources.h"
+
+#if PNG_LIBPNG_VER >= 10400
+#define int_p_NULL NULL
+#define png_infopp_NULL NULL
+#endif
+
+#define GST_CAT_DEFAULT gst_gl_differencematte_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_differencematte_debug, "gldifferencematte", 0, "gldifferencematte element");
+
+#define gst_gl_differencematte_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLDifferenceMatte, gst_gl_differencematte,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_differencematte_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_differencematte_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_differencematte_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static gboolean gst_gl_differencematte_loader (GstGLFilter * filter);
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION,
+};
+
+
+/* init resources that need a gl context */
+static gboolean
+gst_gl_differencematte_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (base_filter);
+ GstGLFilter *filter = GST_GL_FILTER (base_filter);
+ GstGLContext *context = base_filter->context;
+ GstGLBaseMemoryAllocator *tex_alloc;
+ GstGLAllocationParams *params;
+ GError *error = NULL;
+ gint i;
+
+ if (!GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter))
+ return FALSE;
+
+ tex_alloc = (GstGLBaseMemoryAllocator *)
+ gst_gl_memory_allocator_get_default (context);
+ params =
+ (GstGLAllocationParams *) gst_gl_video_allocation_params_new (context,
+ NULL, &filter->out_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
+
+ for (i = 0; i < 4; i++)
+ differencematte->midtexture[i] =
+ (GstGLMemory *) gst_gl_base_memory_alloc (tex_alloc, params);
+ gst_gl_allocation_params_free (params);
+ gst_object_unref (tex_alloc);
+
+ if (!(differencematte->identity_shader =
+ gst_gl_shader_new_default (context, &error))) {
+ GST_ELEMENT_ERROR (differencematte, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to compile identity shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ if (!(differencematte->shader[0] =
+ gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ difference_fragment_source), NULL))) {
+ GST_ELEMENT_ERROR (differencematte, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to compile difference shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ if (!(differencematte->shader[1] =
+ gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ hconv7_fragment_source_gles2), NULL))) {
+ GST_ELEMENT_ERROR (differencematte, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to compile convolution shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ if (!(differencematte->shader[2] =
+ gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ vconv7_fragment_source_gles2), NULL))) {
+ GST_ELEMENT_ERROR (differencematte, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to compile convolution shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ if (!(differencematte->shader[3] =
+ gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ texture_interp_fragment_source), NULL))) {
+ GST_ELEMENT_ERROR (differencematte, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to compile interpolation shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ /* FIXME: this should really be per shader */
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (differencematte->shader[2],
+ "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (differencematte->shader[2],
+ "a_texcoord");
+
+ return TRUE;
+}
+
+/* free resources that need a gl context */
+static void
+gst_gl_differencematte_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (base_filter);
+ gint i;
+
+ if (differencematte->savedbgtexture) {
+ gst_memory_unref (GST_MEMORY_CAST (differencematte->savedbgtexture));
+ differencematte->savedbgtexture = NULL;
+ }
+
+ if (differencematte->newbgtexture) {
+ gst_memory_unref (GST_MEMORY_CAST (differencematte->newbgtexture));
+ differencematte->newbgtexture = NULL;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (differencematte->identity_shader) {
+ gst_object_unref (differencematte->identity_shader);
+ differencematte->identity_shader = NULL;
+ }
+
+ if (differencematte->shader[i]) {
+ gst_object_unref (differencematte->shader[i]);
+ differencematte->shader[i] = NULL;
+ }
+
+ if (differencematte->midtexture[i]) {
+ gst_memory_unref (GST_MEMORY_CAST (differencematte->midtexture[i]));
+ differencematte->midtexture[i] = NULL;
+ }
+ }
+ differencematte->location = NULL;
+ differencematte->pixbuf = NULL;
+ differencematte->bg_has_changed = FALSE;
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static void
+gst_gl_differencematte_class_init (GstGLDifferenceMatteClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_differencematte_set_property;
+ gobject_class->get_property = gst_gl_differencematte_get_property;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_differencematte_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_differencematte_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_differencematte_filter_texture;
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOCATION,
+ g_param_spec_string ("location",
+ "Background image location",
+ "Background image location", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class,
+ "Gstreamer OpenGL DifferenceMatte", "Filter/Effect/Video",
+ "Saves a background frame and replace it with a pixbuf",
+ "Filippo Argiolas <filippo.argiolas@gmail.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2;
+}
+
+static void
+gst_gl_differencematte_init (GstGLDifferenceMatte * differencematte)
+{
+ differencematte->shader[0] = NULL;
+ differencematte->shader[1] = NULL;
+ differencematte->shader[2] = NULL;
+ differencematte->shader[3] = NULL;
+ differencematte->location = NULL;
+ differencematte->pixbuf = NULL;
+ differencematte->savedbgtexture = 0;
+ differencematte->newbgtexture = 0;
+ differencematte->bg_has_changed = FALSE;
+
+ fill_gaussian_kernel (differencematte->kernel, 7, 30.0);
+}
+
+static void
+gst_gl_differencematte_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_free (differencematte->location);
+ differencematte->bg_has_changed = TRUE;
+ differencematte->location = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_differencematte_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_value_set_string (value, differencematte->location);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+init_pixbuf_texture (GstGLDifferenceMatte * differencematte)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (differencematte)->context;
+ GstGLFilter *filter = GST_GL_FILTER (differencematte);
+ GstGLBaseMemoryAllocator *tex_alloc;
+ GstGLAllocationParams *params;
+ GstVideoInfo v_info;
+
+ tex_alloc = (GstGLBaseMemoryAllocator *)
+ gst_gl_memory_allocator_get_default (context);
+ gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_RGBA,
+ differencematte->pbuf_width, differencematte->pbuf_height);
+ params =
+ (GstGLAllocationParams *) gst_gl_video_allocation_params_new (context,
+ NULL, &v_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
+
+ differencematte->newbgtexture =
+ (GstGLMemory *) gst_gl_base_memory_alloc (tex_alloc, params);
+ gst_gl_allocation_params_free (params);
+
+ if (differencematte->savedbgtexture == NULL) {
+ params =
+ (GstGLAllocationParams *) gst_gl_video_allocation_params_new (context,
+ NULL, &filter->out_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D,
+ GST_GL_RGBA);
+
+ differencematte->savedbgtexture =
+ (GstGLMemory *) gst_gl_base_memory_alloc (tex_alloc, params);
+ gst_gl_allocation_params_free (params);
+ }
+
+ gst_object_unref (tex_alloc);
+}
+
+static gboolean
+gst_gl_differencematte_diff (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gst_gl_shader_use (differencematte->shader[0]);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[0], "current", 0);
+
+ gl->ActiveTexture (GL_TEXTURE1);
+ gl->BindTexture (GL_TEXTURE_2D,
+ gst_gl_memory_get_texture_id (differencematte->savedbgtexture));
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[0], "saved", 1);
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_differencematte_hblur (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gst_gl_shader_use (differencematte->shader[1]);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[1], "tex", 0);
+
+ gst_gl_shader_set_uniform_1fv (differencematte->shader[1], "kernel", 7,
+ differencematte->kernel);
+ gst_gl_shader_set_uniform_1f (differencematte->shader[1], "gauss_width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_differencematte_vblur (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gst_gl_shader_use (differencematte->shader[2]);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[2], "tex", 0);
+
+ gst_gl_shader_set_uniform_1fv (differencematte->shader[2], "kernel", 7,
+ differencematte->kernel);
+ gst_gl_shader_set_uniform_1f (differencematte->shader[2], "gauss_height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_differencematte_interp (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gst_gl_shader_use (differencematte->shader[3]);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[3], "blend", 0);
+
+ gl->ActiveTexture (GL_TEXTURE1);
+ gl->BindTexture (GL_TEXTURE_2D, differencematte->newbgtexture->tex_id);
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[3], "base", 1);
+
+ gl->ActiveTexture (GL_TEXTURE2);
+ gl->BindTexture (GL_TEXTURE_2D, differencematte->midtexture[2]->tex_id);
+
+ gst_gl_shader_set_uniform_1i (differencematte->shader[3], "alpha", 2);
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_differencematte_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+
+ differencematte->intexture = in_tex;
+
+ if (differencematte->bg_has_changed && (differencematte->location != NULL)) {
+
+ if (!gst_gl_differencematte_loader (filter))
+ differencematte->pixbuf = NULL;
+
+ init_pixbuf_texture (differencematte);
+
+ /* save current frame, needed to calculate difference between
+ * this frame and next ones */
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex,
+ differencematte->savedbgtexture, differencematte->identity_shader);
+
+ if (differencematte->pixbuf) {
+ free (differencematte->pixbuf);
+ differencematte->pixbuf = NULL;
+ }
+
+ differencematte->bg_has_changed = FALSE;
+ }
+
+ if (differencematte->savedbgtexture != NULL) {
+ gst_gl_filter_render_to_target (filter, in_tex,
+ differencematte->midtexture[0], gst_gl_differencematte_diff, NULL);
+ gst_gl_filter_render_to_target (filter, differencematte->midtexture[0],
+ differencematte->midtexture[1], gst_gl_differencematte_hblur, NULL);
+ gst_gl_filter_render_to_target (filter, differencematte->midtexture[1],
+ differencematte->midtexture[2], gst_gl_differencematte_vblur, NULL);
+ gst_gl_filter_render_to_target (filter, in_tex, out_tex,
+ gst_gl_differencematte_interp, NULL);
+ } else {
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex,
+ differencematte->identity_shader);
+ }
+
+ return TRUE;
+}
+
+static void
+user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
+{
+ g_warning ("%s\n", warning_msg);
+}
+
+#define LOAD_ERROR(msg) { GST_WARNING ("unable to load %s: %s", differencematte->location, msg); return FALSE; }
+
+static gboolean
+gst_gl_differencematte_loader (GstGLFilter * filter)
+{
+ GstGLDifferenceMatte *differencematte = GST_GL_DIFFERENCEMATTE (filter);
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ guint sig_read = 0;
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ gint bit_depth = 0;
+ gint color_type = 0;
+ gint interlace_type = 0;
+ png_FILE_p fp = NULL;
+ guint y = 0;
+ guchar **rows = NULL;
+ gint filler;
+
+ if (!GST_GL_BASE_FILTER (filter)->context)
+ return TRUE;
+
+ if ((fp = fopen (differencematte->location, "rb")) == NULL)
+ LOAD_ERROR ("file not found");
+
+ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (png_ptr == NULL) {
+ fclose (fp);
+ LOAD_ERROR ("failed to initialize the png_struct");
+ }
+
+ png_set_error_fn (png_ptr, NULL, NULL, user_warning_fn);
+
+ info_ptr = png_create_info_struct (png_ptr);
+ if (info_ptr == NULL) {
+ fclose (fp);
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ LOAD_ERROR ("failed to initialize the memory for image information");
+ }
+
+ png_init_io (png_ptr, fp);
+
+ png_set_sig_bytes (png_ptr, sig_read);
+
+ png_read_info (png_ptr, info_ptr);
+
+ png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_type, int_p_NULL, int_p_NULL);
+
+ if (color_type == PNG_COLOR_TYPE_RGB) {
+ filler = 0xff;
+ png_set_filler (png_ptr, filler, PNG_FILLER_AFTER);
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (color_type != PNG_COLOR_TYPE_RGB_ALPHA) {
+ fclose (fp);
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ LOAD_ERROR ("color type is not rgb");
+ }
+
+ differencematte->pbuf_width = width;
+ differencematte->pbuf_height = height;
+
+ differencematte->pixbuf =
+ (guchar *) malloc (sizeof (guchar) * width * height * 4);
+
+ rows = (guchar **) malloc (sizeof (guchar *) * height);
+
+ for (y = 0; y < height; ++y)
+ rows[y] = (guchar *) (differencematte->pixbuf + y * width * 4);
+
+ png_read_image (png_ptr, rows);
+
+ free (rows);
+
+ png_read_end (png_ptr, info_ptr);
+ png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
+ fclose (fp);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstgldifferencematte.h b/ext/gl/gstgldifferencematte.h
new file mode 100644
index 000000000..a4fd27552
--- /dev/null
+++ b/ext/gl/gstgldifferencematte.h
@@ -0,0 +1,62 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_DIFFERENCEMATTE_H_
+#define _GST_GL_DIFFERENCEMATTE_H_
+
+#include <gst/gl/gstglfilter.h>
+
+#define GST_TYPE_GL_DIFFERENCEMATTE (gst_gl_differencematte_get_type())
+#define GST_GL_DIFFERENCEMATTE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_GL_DIFFERENCEMATTE,GstGLDifferenceMatte))
+#define GST_IS_GL_DIFFERENCEMATTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GL_DIFFERENCEMATTE))
+#define GST_GL_DIFFERENCEMATTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) , GST_TYPE_GL_DIFFERENCEMATTE,GstGLDifferenceMatteClass))
+#define GST_IS_GL_DIFFERENCEMATTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) , GST_TYPE_GL_DIFFERENCEMATTE))
+#define GST_GL_DIFFERENCEMATTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) , GST_TYPE_GL_DIFFERENCEMATTE,GstGLDifferenceMatteClass))
+
+typedef struct _GstGLDifferenceMatte GstGLDifferenceMatte;
+typedef struct _GstGLDifferenceMatteClass GstGLDifferenceMatteClass;
+
+struct _GstGLDifferenceMatte
+{
+ GstGLFilter filter;
+
+ GstGLShader *identity_shader;
+ GstGLShader *shader[4];
+
+ gchar *location;
+ gboolean bg_has_changed;
+
+ guchar *pixbuf;
+ gint pbuf_width, pbuf_height;
+ GstGLMemory *savedbgtexture;
+ GstGLMemory *newbgtexture;
+ GstGLMemory *midtexture[4];
+ GstGLMemory *intexture;
+ float kernel[7];
+};
+
+struct _GstGLDifferenceMatteClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_differencematte_get_type (void);
+
+#endif /* _GST_GL_DIFFERENCEMATTE_H_ */
diff --git a/ext/gl/gstgldownloadelement.c b/ext/gl/gstgldownloadelement.c
new file mode 100644
index 000000000..c8f101eaa
--- /dev/null
+++ b/ext/gl/gstgldownloadelement.c
@@ -0,0 +1,460 @@
+/*
+ * GStreamer
+ * Copyright (C) 2012 Matthew Waters <ystree00@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gl.h>
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+#include <gst/gl/egl/gsteglimage.h>
+#include <gst/allocators/gstdmabuf.h>
+#endif
+
+#include "gstgldownloadelement.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_gl_download_element_debug);
+#define GST_CAT_DEFAULT gst_gl_download_element_debug
+
+#define gst_gl_download_element_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLDownloadElement, gst_gl_download_element,
+ GST_TYPE_GL_BASE_FILTER,
+ GST_DEBUG_CATEGORY_INIT (gst_gl_download_element_debug, "gldownloadelement",
+ 0, "download element"););
+
+static gboolean gst_gl_download_element_get_unit_size (GstBaseTransform * trans,
+ GstCaps * caps, gsize * size);
+static GstCaps *gst_gl_download_element_transform_caps (GstBaseTransform * bt,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter);
+static gboolean gst_gl_download_element_set_caps (GstBaseTransform * bt,
+ GstCaps * in_caps, GstCaps * out_caps);
+static GstFlowReturn
+gst_gl_download_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * buffer, GstBuffer ** outbuf);
+static GstFlowReturn gst_gl_download_element_transform (GstBaseTransform * bt,
+ GstBuffer * buffer, GstBuffer * outbuf);
+static gboolean gst_gl_download_element_decide_allocation (GstBaseTransform *
+ trans, GstQuery * query);
+static void gst_gl_download_element_finalize (GObject * object);
+
+static GstStaticPadTemplate gst_gl_download_element_src_pad_template =
+ GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+ "video/x-raw(" GST_CAPS_FEATURE_MEMORY_DMABUF "); "
+#endif
+ "video/x-raw; video/x-raw(memory:GLMemory)"));
+
+static GstStaticPadTemplate gst_gl_download_element_sink_pad_template =
+ GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(memory:GLMemory); video/x-raw"));
+
+static void
+gst_gl_download_element_class_init (GstGLDownloadElementClass * klass)
+{
+ GstBaseTransformClass *bt_class = GST_BASE_TRANSFORM_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ bt_class->transform_caps = gst_gl_download_element_transform_caps;
+ bt_class->set_caps = gst_gl_download_element_set_caps;
+ bt_class->get_unit_size = gst_gl_download_element_get_unit_size;
+ bt_class->prepare_output_buffer =
+ gst_gl_download_element_prepare_output_buffer;
+ bt_class->transform = gst_gl_download_element_transform;
+ bt_class->decide_allocation = gst_gl_download_element_decide_allocation;
+
+ bt_class->passthrough_on_same_caps = TRUE;
+
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_download_element_src_pad_template);
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_download_element_sink_pad_template);
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL downloader", "Filter/Video",
+ "Downloads data from OpenGL", "Matthew Waters <matthew@centricular.com>");
+
+ object_class->finalize = gst_gl_download_element_finalize;
+}
+
+static void
+gst_gl_download_element_init (GstGLDownloadElement * download)
+{
+ gst_base_transform_set_prefer_passthrough (GST_BASE_TRANSFORM (download),
+ TRUE);
+}
+
+static gboolean
+gst_gl_download_element_set_caps (GstBaseTransform * bt, GstCaps * in_caps,
+ GstCaps * out_caps)
+{
+ GstGLDownloadElement *dl = GST_GL_DOWNLOAD_ELEMENT (bt);
+ GstVideoInfo out_info;
+ GstCapsFeatures *features = NULL;
+
+ if (!gst_video_info_from_caps (&out_info, out_caps))
+ return FALSE;
+
+ features = gst_caps_get_features (out_caps, 0);
+
+ dl->do_pbo_transfers = FALSE;
+ if (dl->dmabuf_allocator) {
+ gst_object_unref (GST_OBJECT (dl->dmabuf_allocator));
+ dl->dmabuf_allocator = NULL;
+ }
+
+ if (!features) {
+ dl->do_pbo_transfers = TRUE;
+ return TRUE;
+ }
+
+ if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
+ /* do nothing with the buffer */
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+ } else if (gst_caps_features_contains (features,
+ GST_CAPS_FEATURE_MEMORY_DMABUF)) {
+ dl->dmabuf_allocator = gst_dmabuf_allocator_new ();
+#endif
+ } else if (gst_caps_features_contains (features,
+ GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY)) {
+ dl->do_pbo_transfers = TRUE;
+ }
+
+ return TRUE;
+}
+
+static GstCaps *
+_set_caps_features (const GstCaps * caps, const gchar * feature_name)
+{
+ GstCaps *tmp = gst_caps_copy (caps);
+ guint n = gst_caps_get_size (tmp);
+ guint i = 0;
+
+ for (i = 0; i < n; i++)
+ gst_caps_set_features (tmp, i,
+ gst_caps_features_from_string (feature_name));
+
+ return tmp;
+}
+
+static GstCaps *
+gst_gl_download_element_transform_caps (GstBaseTransform * bt,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+{
+ GstCaps *result, *tmp;
+
+ if (direction == GST_PAD_SRC) {
+ tmp = _set_caps_features (caps, GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ tmp = gst_caps_merge (gst_caps_ref (caps), tmp);
+ } else {
+ GstCaps *newcaps;
+ tmp = gst_caps_ref (caps);
+
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+ newcaps = _set_caps_features (caps, GST_CAPS_FEATURE_MEMORY_DMABUF);
+ tmp = gst_caps_merge (tmp, newcaps);
+#endif
+
+ newcaps = _set_caps_features (caps, GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY);
+ tmp = gst_caps_merge (tmp, newcaps);
+ }
+
+ if (filter) {
+ result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ } else {
+ result = tmp;
+ }
+
+ GST_DEBUG_OBJECT (bt, "returning caps %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+static gboolean
+gst_gl_download_element_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
+ gsize * size)
+{
+ gboolean ret = FALSE;
+ GstVideoInfo info;
+
+ ret = gst_video_info_from_caps (&info, caps);
+ if (ret)
+ *size = GST_VIDEO_INFO_SIZE (&info);
+
+ return TRUE;
+}
+
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+
+struct DmabufInfo
+{
+ GstMemory *dmabuf;
+ gint stride;
+ gsize offset;
+};
+
+static void
+_free_dmabuf_info (struct DmabufInfo *info)
+{
+ gst_memory_unref (info->dmabuf);
+ g_free (info);
+}
+
+static GQuark
+_dmabuf_info_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string ("GstGLDownloadDmabufInfo");
+ return quark;
+}
+
+static struct DmabufInfo *
+_get_cached_dmabuf_info (GstGLMemory * mem)
+{
+ return gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
+ _dmabuf_info_quark ());
+}
+
+static void
+_set_cached_dmabuf_info (GstGLMemory * mem, struct DmabufInfo *info)
+{
+ return gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
+ _dmabuf_info_quark (), info, (GDestroyNotify) _free_dmabuf_info);
+}
+
+struct DmabufTransfer
+{
+ GstGLDownloadElement *download;
+ GstGLMemory *glmem;
+ struct DmabufInfo *info;
+};
+
+static void
+_create_cached_dmabuf_info (GstGLContext * context, gpointer data)
+{
+ struct DmabufTransfer *transfer = (struct DmabufTransfer *) data;
+ GstEGLImage *image;
+
+ image = gst_egl_image_from_texture (context, transfer->glmem, NULL);
+ if (image) {
+ int fd;
+ gint stride;
+ gsize offset;
+
+ if (gst_egl_image_export_dmabuf (image, &fd, &stride, &offset)) {
+ GstGLDownloadElement *download = transfer->download;
+ struct DmabufInfo *info;
+ gsize maxsize;
+
+ gst_memory_get_sizes (GST_MEMORY_CAST (transfer->glmem), NULL, &maxsize);
+
+ info = g_new0 (struct DmabufInfo, 1);
+ info->dmabuf =
+ gst_dmabuf_allocator_alloc (download->dmabuf_allocator, fd, maxsize);
+ info->stride = stride;
+ info->offset = offset;
+
+ transfer->info = info;
+ }
+
+ gst_egl_image_unref (image);
+ }
+}
+
+static GstBuffer *
+_try_export_dmabuf (GstGLDownloadElement * download, GstBuffer * inbuf)
+{
+ GstGLMemory *glmem;
+ GstBuffer *buffer = NULL;
+ int i;
+ gsize offset[GST_VIDEO_MAX_PLANES];
+ gint stride[GST_VIDEO_MAX_PLANES];
+ GstCaps *src_caps;
+ GstVideoInfo out_info;
+ gsize total_offset;
+
+ glmem = GST_GL_MEMORY_CAST (gst_buffer_peek_memory (inbuf, 0));
+ if (glmem) {
+ GstGLContext *context = GST_GL_BASE_MEMORY_CAST (glmem)->context;
+ if (gst_gl_context_get_gl_platform (context) != GST_GL_PLATFORM_EGL)
+ return NULL;
+ }
+
+ buffer = gst_buffer_new ();
+ total_offset = 0;
+
+ for (i = 0; i < gst_buffer_n_memory (inbuf); i++) {
+ struct DmabufInfo *info;
+
+ glmem = GST_GL_MEMORY_CAST (gst_buffer_peek_memory (inbuf, i));
+ info = _get_cached_dmabuf_info (glmem);
+ if (!info) {
+ GstGLContext *context = GST_GL_BASE_MEMORY_CAST (glmem)->context;
+ struct DmabufTransfer transfer;
+
+ transfer.download = download;
+ transfer.glmem = glmem;
+ transfer.info = NULL;
+ gst_gl_context_thread_add (context, _create_cached_dmabuf_info,
+ &transfer);
+ info = transfer.info;
+
+ if (info)
+ _set_cached_dmabuf_info (glmem, info);
+ }
+
+ if (info) {
+ offset[i] = total_offset + info->offset;
+ stride[i] = info->stride;
+ total_offset += gst_memory_get_sizes (info->dmabuf, NULL, NULL);
+ gst_buffer_insert_memory (buffer, -1, gst_memory_ref (info->dmabuf));
+ } else {
+ gst_buffer_unref (buffer);
+ buffer = NULL;
+ goto export_complete;
+ }
+ }
+
+ src_caps = gst_pad_get_current_caps (GST_BASE_TRANSFORM (download)->srcpad);
+ gst_video_info_from_caps (&out_info, src_caps);
+
+ if (download->add_videometa) {
+ gst_buffer_add_video_meta_full (buffer, GST_VIDEO_FRAME_FLAG_NONE,
+ out_info.finfo->format, out_info.width, out_info.height,
+ out_info.finfo->n_planes, offset, stride);
+ } else {
+ int i;
+ gboolean match = TRUE;
+ for (i = 0; i < gst_buffer_n_memory (inbuf); i++) {
+ if (offset[i] != out_info.offset[i] || stride[i] != out_info.stride[i]) {
+ match = FALSE;
+ break;
+ }
+ }
+
+ if (!match) {
+ gst_buffer_unref (buffer);
+ buffer = NULL;
+ }
+ }
+
+export_complete:
+
+ return buffer;
+}
+#endif /* GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF */
+
+static GstFlowReturn
+gst_gl_download_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * inbuf, GstBuffer ** outbuf)
+{
+ GstGLDownloadElement *dl = GST_GL_DOWNLOAD_ELEMENT (bt);
+ gint i, n;
+
+ *outbuf = inbuf;
+
+ if (dl->do_pbo_transfers) {
+ n = gst_buffer_n_memory (*outbuf);
+ for (i = 0; i < n; i++) {
+ GstMemory *mem = gst_buffer_peek_memory (*outbuf, i);
+
+ if (gst_is_gl_memory_pbo (mem))
+ gst_gl_memory_pbo_download_transfer ((GstGLMemoryPBO *) mem);
+ }
+ }
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_DMABUF
+ else if (dl->dmabuf_allocator) {
+ GstBuffer *buffer = _try_export_dmabuf (dl, inbuf);
+ if (buffer) {
+ if (GST_BASE_TRANSFORM_GET_CLASS (bt)->copy_metadata)
+ if (!GST_BASE_TRANSFORM_GET_CLASS (bt)->copy_metadata (bt, inbuf,
+ buffer)) {
+ GST_ELEMENT_WARNING (GST_ELEMENT (bt), STREAM, NOT_IMPLEMENTED,
+ ("could not copy metadata"), (NULL));
+ }
+
+ *outbuf = buffer;
+ } else {
+ GstCaps *src_caps;
+ GstCapsFeatures *features;
+
+ gst_object_unref (dl->dmabuf_allocator);
+ dl->dmabuf_allocator = NULL;
+
+ src_caps = gst_pad_get_current_caps (bt->srcpad);
+ src_caps = gst_caps_make_writable (src_caps);
+ features = gst_caps_get_features (src_caps, 0);
+ gst_caps_features_remove (features, GST_CAPS_FEATURE_MEMORY_DMABUF);
+
+ if (!gst_base_transform_update_src_caps (bt, src_caps)) {
+ GST_ERROR_OBJECT (bt, "DMABuf exportation didn't work and system "
+ "memory is not supported.");
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+ }
+ }
+#endif
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_gl_download_element_transform (GstBaseTransform * bt,
+ GstBuffer * inbuf, GstBuffer * outbuf)
+{
+ return GST_FLOW_OK;
+}
+
+static gboolean
+gst_gl_download_element_decide_allocation (GstBaseTransform * trans,
+ GstQuery * query)
+{
+ GstGLDownloadElement *download = GST_GL_DOWNLOAD_ELEMENT_CAST (trans);
+
+ if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
+ download->add_videometa = TRUE;
+ } else {
+ download->add_videometa = FALSE;
+ }
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
+ query);
+}
+
+static void
+gst_gl_download_element_finalize (GObject * object)
+{
+ GstGLDownloadElement *download = GST_GL_DOWNLOAD_ELEMENT_CAST (object);
+
+ if (download->dmabuf_allocator) {
+ gst_object_unref (GST_OBJECT (download->dmabuf_allocator));
+ download->dmabuf_allocator = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/ext/gl/gstgldownloadelement.h b/ext/gl/gstgldownloadelement.h
new file mode 100644
index 000000000..a9fb4e165
--- /dev/null
+++ b/ext/gl/gstgldownloadelement.h
@@ -0,0 +1,60 @@
+/*
+ * GStreamer
+ * Copyright (C) 2012 Matthew Waters <ystree00@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_DOWNLOAD_ELEMENT_H__
+#define __GST_GL_DOWNLOAD_ELEMENT_H__
+
+#include <gst/video/video.h>
+#include <gst/gstmemory.h>
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_download_element_get_type (void);
+#define GST_TYPE_GL_DOWNLOAD_ELEMENT (gst_gl_download_element_get_type())
+#define GST_GL_DOWNLOAD_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_DOWNLOAD_ELEMENT,GstGLDownloadElement))
+#define GST_GL_DOWNLOAD_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_DISPLAY,GstGLDownloadElementClass))
+#define GST_IS_GL_DOWNLOAD_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_DOWNLOAD_ELEMENT))
+#define GST_IS_GL_DOWNLOAD_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_DOWNLOAD_ELEMENT))
+#define GST_GL_DOWNLOAD_ELEMENT_CAST(obj) ((GstGLDownloadElement*)(obj))
+
+typedef struct _GstGLDownloadElement GstGLDownloadElement;
+typedef struct _GstGLDownloadElementClass GstGLDownloadElementClass;
+
+struct _GstGLDownloadElement
+{
+ /* <private> */
+ GstGLBaseFilter parent;
+
+ gboolean do_pbo_transfers;
+ GstAllocator * dmabuf_allocator;
+ gboolean add_videometa;
+};
+
+struct _GstGLDownloadElementClass
+{
+ /* <private> */
+ GstGLBaseFilterClass object_class;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GL_DOWNLOAD_ELEMENT_H__ */
diff --git a/ext/gl/gstgleffects.c b/ext/gl/gstgleffects.c
new file mode 100644
index 000000000..97a8dfe8a
--- /dev/null
+++ b/ext/gl/gstgleffects.c
@@ -0,0 +1,688 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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:element-gleffects.
+ * @title: gleffects.
+ *
+ * GL Shading Language effects.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! gleffects effect=5 ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgleffects.h"
+
+#define GST_CAT_DEFAULT gst_gl_effects_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0 = 0x0,
+ PROP_EFFECT = 0x1 << 1,
+ PROP_HSWAP = 0x1 << 2,
+ PROP_INVERT = 0x1 << 3
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_effects_debug, "gleffects", 0, "gleffects element");
+
+#define gst_gl_effects_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLEffects, gst_gl_effects, GST_TYPE_GL_FILTER,
+ DEBUG_INIT);
+
+static void gst_gl_effects_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_effects_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_effects_init_resources (GstBaseTransform * trans);
+static gboolean gst_gl_effects_reset_resources (GstBaseTransform * trans);
+
+static gboolean gst_gl_effects_on_init_gl_context (GstGLFilter * filter);
+
+static void gst_gl_effects_ghash_func_clean (gpointer key, gpointer value,
+ gpointer data);
+
+static gboolean gst_gl_effects_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+static gboolean gst_gl_effects_filters_is_property_supported (const
+ GstGLEffectsFilterDescriptor *, gint property);
+
+/* dont' forget to edit the following when a new effect is added */
+typedef enum
+{
+ GST_GL_EFFECT_IDENTITY,
+ GST_GL_EFFECT_MIRROR,
+ GST_GL_EFFECT_SQUEEZE,
+ GST_GL_EFFECT_STRETCH,
+ GST_GL_EFFECT_TUNNEL,
+ GST_GL_EFFECT_FISHEYE,
+ GST_GL_EFFECT_TWIRL,
+ GST_GL_EFFECT_BULGE,
+ GST_GL_EFFECT_SQUARE,
+ GST_GL_EFFECT_HEAT,
+ GST_GL_EFFECT_SEPIA,
+ GST_GL_EFFECT_XPRO,
+ GST_GL_EFFECT_LUMA_XPRO,
+ GST_GL_EFFECT_XRAY,
+ GST_GL_EFFECT_SIN,
+ GST_GL_EFFECT_GLOW,
+ GST_GL_EFFECT_SOBEL,
+ GST_GL_EFFECT_BLUR,
+ GST_GL_EFFECT_LAPLACIAN,
+ GST_GL_N_EFFECTS
+} GstGLEffectsEffect;
+
+static const GEnumValue *
+gst_gl_effects_get_effects (void)
+{
+ static const GEnumValue effect_types[] = {
+ {GST_GL_EFFECT_IDENTITY, "Do nothing Effect", "identity"},
+ {GST_GL_EFFECT_MIRROR, "Mirror Effect", "mirror"},
+ {GST_GL_EFFECT_SQUEEZE, "Squeeze Effect", "squeeze"},
+ {GST_GL_EFFECT_STRETCH, "Stretch Effect", "stretch"},
+ {GST_GL_EFFECT_TUNNEL, "Light Tunnel Effect", "tunnel"},
+ {GST_GL_EFFECT_FISHEYE, "FishEye Effect", "fisheye"},
+ {GST_GL_EFFECT_TWIRL, "Twirl Effect", "twirl"},
+ {GST_GL_EFFECT_BULGE, "Bulge Effect", "bulge"},
+ {GST_GL_EFFECT_SQUARE, "Square Effect", "square"},
+ {GST_GL_EFFECT_HEAT, "Heat Signature Effect", "heat"},
+ {GST_GL_EFFECT_SEPIA, "Sepia Toning Effect", "sepia"},
+ {GST_GL_EFFECT_XPRO, "Cross Processing Effect", "xpro"},
+ {GST_GL_EFFECT_LUMA_XPRO, "Luma Cross Processing Effect", "lumaxpro"},
+ {GST_GL_EFFECT_XRAY, "Glowing negative effect", "xray"},
+ {GST_GL_EFFECT_SIN, "All Grey but Red Effect", "sin"},
+ {GST_GL_EFFECT_GLOW, "Glow Lighting Effect", "glow"},
+ {GST_GL_EFFECT_SOBEL, "Sobel edge detection Effect", "sobel"},
+ {GST_GL_EFFECT_BLUR, "Blur with 9x9 separable convolution Effect", "blur"},
+ {GST_GL_EFFECT_LAPLACIAN, "Laplacian Convolution Demo Effect", "laplacian"},
+ {0, NULL, NULL}
+ };
+ return effect_types;
+}
+
+#define GST_TYPE_GL_EFFECTS_EFFECT (gst_gl_effects_effect_get_type ())
+static GType
+gst_gl_effects_effect_get_type (void)
+{
+ static GType gl_effects_effect_type = 0;
+ if (!gl_effects_effect_type) {
+ gl_effects_effect_type =
+ g_enum_register_static ("GstGLEffectsEffect",
+ gst_gl_effects_get_effects ());
+ }
+ return gl_effects_effect_type;
+}
+
+static void
+gst_gl_effects_set_effect (GstGLEffects * effects, gint effect_type)
+{
+ GstGLBaseFilterClass *filter_class = GST_GL_BASE_FILTER_GET_CLASS (effects);
+
+ switch (effect_type) {
+ case GST_GL_EFFECT_IDENTITY:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_identity;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_MIRROR:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_mirror;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_SQUEEZE:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_squeeze;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_STRETCH:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_stretch;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_TUNNEL:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_tunnel;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_FISHEYE:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_fisheye;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_TWIRL:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_twirl;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_BULGE:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_bulge;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_SQUARE:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_square;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_HEAT:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_heat;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_SEPIA:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_sepia;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_XPRO:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_xpro;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_LUMA_XPRO:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_luma_xpro;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_SIN:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_sin;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_XRAY:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_xray;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_GLOW:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_glow;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_SOBEL:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_sobel;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_BLUR:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_blur;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ case GST_GL_EFFECT_LAPLACIAN:
+ effects->effect = (GstGLEffectProcessFunc) gst_gl_effects_laplacian;
+ filter_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+ effects->current_effect = effect_type;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ effects->current_effect = effect_type;
+}
+
+/* init resources that need a gl context */
+static gboolean
+gst_gl_effects_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (base_filter);
+ GstGLFilter *filter = GST_GL_FILTER (base_filter);
+ GstGLContext *context = base_filter->context;
+ GstGLBaseMemoryAllocator *base_alloc;
+ GstGLAllocationParams *params;
+ gint i;
+
+ if (!GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter))
+ return FALSE;
+
+ base_alloc = (GstGLBaseMemoryAllocator *)
+ gst_allocator_find (GST_GL_MEMORY_ALLOCATOR_NAME);
+ params =
+ (GstGLAllocationParams *) gst_gl_video_allocation_params_new (context,
+ NULL, &filter->out_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
+
+ for (i = 0; i < NEEDED_TEXTURES; i++) {
+ if (effects->midtexture[i])
+ gst_memory_unref (GST_MEMORY_CAST (effects->midtexture[i]));
+
+ effects->midtexture[i] =
+ (GstGLMemory *) gst_gl_base_memory_alloc (base_alloc, params);
+ }
+
+ gst_object_unref (base_alloc);
+ gst_gl_allocation_params_free (params);
+
+ return TRUE;
+}
+
+/* free resources that need a gl context */
+static void
+gst_gl_effects_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (base_filter);
+ const GstGLFuncs *gl = base_filter->context->gl_vtable;
+ gint i = 0;
+
+ for (i = 0; i < NEEDED_TEXTURES; i++) {
+ gst_memory_unref (GST_MEMORY_CAST (effects->midtexture[i]));
+ }
+
+ for (i = 0; i < GST_GL_EFFECTS_N_CURVES; i++) {
+ gl->DeleteTextures (1, &effects->curve[i]);
+ effects->curve[i] = 0;
+ }
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static void
+gst_gl_effects_class_init (GstGLEffectsClass * klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ GST_BASE_TRANSFORM_CLASS (klass)->start = gst_gl_effects_init_resources;
+ GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_effects_reset_resources;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_effects_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_effects_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->filter_texture = gst_gl_effects_filter_texture;
+ GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_effects_on_init_gl_context;
+
+ klass->filter_descriptor = NULL;
+
+ gst_element_class_set_metadata (element_class,
+ "Gstreamer OpenGL Effects", "Filter/Effect/Video",
+ "GL Shading Language effects",
+ "Filippo Argiolas <filippo.argiolas@gmail.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_effects_filter_class_init (GstGLEffectsClass * klass,
+ const GstGLEffectsFilterDescriptor * filter_descriptor)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ klass->filter_descriptor = filter_descriptor;
+
+ gobject_class->set_property = gst_gl_effects_set_property;
+ gobject_class->get_property = gst_gl_effects_get_property;
+
+ /* if filterDescriptor is null it's a generic gleffects */
+ if (!filter_descriptor) {
+ g_object_class_install_property (gobject_class,
+ PROP_EFFECT,
+ g_param_spec_enum ("effect",
+ "Effect",
+ "Select which effect apply to GL video texture",
+ GST_TYPE_GL_EFFECTS_EFFECT,
+ GST_GL_EFFECT_IDENTITY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ } else {
+ gchar *description = g_strdup_printf ("GL Shading Language effects - %s",
+ filter_descriptor->filter_longname);
+
+ gst_element_class_set_metadata (element_class,
+ filter_descriptor->filter_longname, "Filter/Effect/Video",
+ description, "Filippo Argiolas <filippo.argiolas@gmail.com>");
+
+ g_free (description);
+ }
+
+ g_object_class_install_property (gobject_class,
+ PROP_HSWAP,
+ g_param_spec_boolean ("hswap",
+ "Horizontal Swap",
+ "Switch video texture left to right, useful with webcams",
+ FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* FIXME: make it work on every effect */
+ if (gst_gl_effects_filters_is_property_supported (filter_descriptor,
+ PROP_INVERT)) {
+ g_object_class_install_property (gobject_class, PROP_INVERT,
+ g_param_spec_boolean ("invert", "Invert the colors for sobel effect",
+ "Invert colors to get dark edges on bright background when using sobel effect",
+ FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ }
+}
+
+static void
+set_horizontal_swap (GstGLEffects * effects)
+{
+#if GST_GL_HAVE_OPENGL
+ GstGLContext *context = GST_GL_BASE_FILTER (effects)->context;
+ const GstGLFuncs *gl = context->gl_vtable;
+
+ if (gst_gl_context_get_gl_api (context) & GST_GL_API_OPENGL) {
+ const gfloat mirrormatrix[16] = {
+ -1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0
+ };
+
+ gl->MatrixMode (GL_MODELVIEW);
+ gl->LoadMatrixf (mirrormatrix);
+ }
+#endif
+}
+
+static void
+gst_gl_effects_init (GstGLEffects * effects)
+{
+ effects->horizontal_swap = FALSE;
+ effects->invert = FALSE;
+ effects->effect = gst_gl_effects_identity;
+}
+
+static void
+gst_gl_effects_filter_init (GstGLEffects * effects)
+{
+ gst_gl_effects_set_effect (effects,
+ GST_GL_EFFECTS_GET_CLASS (effects)->filter_descriptor->effect);
+}
+
+static void
+gst_gl_effects_ghash_func_clean (gpointer key, gpointer value, gpointer data)
+{
+ GstGLShader *shader = (GstGLShader *) value;
+
+ gst_object_unref (shader);
+
+ value = NULL;
+}
+
+static gboolean
+gst_gl_effects_reset_resources (GstBaseTransform * trans)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (trans);
+
+ /* release shaders in the gl thread */
+ g_hash_table_foreach (effects->shaderstable, gst_gl_effects_ghash_func_clean,
+ effects);
+
+ /* clean the htable without calling values destructors
+ * because shaders have been released in the glthread
+ * through the foreach func */
+ g_hash_table_unref (effects->shaderstable);
+ effects->shaderstable = NULL;
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
+}
+
+static void
+gst_gl_effects_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (object);
+
+ switch (prop_id) {
+ case PROP_EFFECT:
+ gst_gl_effects_set_effect (effects, g_value_get_enum (value));
+ break;
+ case PROP_HSWAP:
+ effects->horizontal_swap = g_value_get_boolean (value);
+ break;
+ case PROP_INVERT:
+ effects->invert = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_effects_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (object);
+
+ switch (prop_id) {
+ case PROP_EFFECT:
+ g_value_set_enum (value, effects->current_effect);
+ break;
+ case PROP_HSWAP:
+ g_value_set_boolean (value, effects->horizontal_swap);
+ break;
+ case PROP_INVERT:
+ g_value_set_boolean (value, effects->invert);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_effects_init_resources (GstBaseTransform * trans)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (trans);
+ gint i;
+
+ effects->shaderstable = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < NEEDED_TEXTURES; i++) {
+ effects->midtexture[i] = 0;
+ }
+ for (i = 0; i < GST_GL_EFFECTS_N_CURVES; i++) {
+ effects->curve[i] = 0;
+ }
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->start (trans);
+}
+
+static gboolean
+gst_gl_effects_on_init_gl_context (GstGLFilter * filter)
+{
+ return TRUE;
+}
+
+static gboolean
+gst_gl_effects_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLEffects *effects = GST_GL_EFFECTS (filter);
+
+ effects->intexture = in_tex;
+ effects->outtexture = out_tex;
+
+ if (effects->horizontal_swap == TRUE)
+ set_horizontal_swap (effects);
+
+ effects->effect (effects);
+
+ return TRUE;
+}
+
+GstGLShader *
+gst_gl_effects_get_fragment_shader (GstGLEffects * effects,
+ const gchar * shader_name, const gchar * shader_source_gles2)
+{
+ GstGLShader *shader = NULL;
+ GstGLFilter *filter = GST_GL_FILTER (effects);
+ GstGLContext *context = GST_GL_BASE_FILTER (filter)->context;
+
+ shader = g_hash_table_lookup (effects->shaderstable, shader_name);
+
+ if (!shader) {
+ GError *error = NULL;
+
+ if (!(shader = gst_gl_shader_new_link_with_stages (context, &error,
+ gst_glsl_stage_new_default_vertex (context),
+ gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ shader_source_gles2), NULL))) {
+ GST_ELEMENT_ERROR (effects, RESOURCE, NOT_FOUND,
+ ("Failed to initialize %s shader", shader_name), (NULL));
+ }
+
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_texcoord");
+ }
+
+ g_hash_table_insert (effects->shaderstable, (gchar *) shader_name, shader);
+
+ return shader;
+}
+
+static const GstGLEffectsFilterDescriptor *
+gst_gl_effects_filters_supported_properties (void)
+{
+ /* Horizontal swap property is supported by all filters */
+ static const GstGLEffectsFilterDescriptor effects[] = {
+ {GST_GL_EFFECT_SOBEL, PROP_INVERT, NULL},
+ {GST_GL_EFFECT_LAPLACIAN, PROP_INVERT, NULL},
+ {0, 0, NULL}
+ };
+ return effects;
+}
+
+static inline gboolean
+gst_gl_effects_filters_is_property_supported (const GstGLEffectsFilterDescriptor
+ * descriptor, gint property)
+{
+ /* generic filter (NULL descriptor) supports all properties */
+ return !descriptor || (descriptor->supported_properties & property);
+}
+
+static const GstGLEffectsFilterDescriptor *
+gst_gl_effects_filters_descriptors (void)
+{
+ static GstGLEffectsFilterDescriptor *descriptors = NULL;
+ if (!descriptors) {
+ const GEnumValue *e;
+ const GEnumValue *effect = gst_gl_effects_get_effects ();
+ const GstGLEffectsFilterDescriptor *defined;
+ guint n_filters = 0, i;
+
+ for (e = effect; NULL != e->value_nick; ++e, ++n_filters) {
+ }
+
+ descriptors = g_new0 (GstGLEffectsFilterDescriptor, n_filters + 1);
+ for (i = 0; i < n_filters; ++i, ++effect) {
+ descriptors[i].effect = effect->value;
+ descriptors[i].filter_name = effect->value_nick;
+ descriptors[i].filter_longname = effect->value_name;
+ }
+
+ for (defined = gst_gl_effects_filters_supported_properties ();
+ 0 != defined->supported_properties; ++defined) {
+
+ for (i = 0; i < n_filters; ++i) {
+ if (descriptors[i].effect == defined->effect) {
+ descriptors[i].supported_properties = defined->supported_properties;
+ break;
+ }
+ }
+ if (i >= n_filters) {
+ GST_WARNING ("Could not match gstgleffects-%s descriptor",
+ defined->filter_name);
+ }
+ }
+ }
+ return descriptors;
+}
+
+gboolean
+gst_gl_effects_register_filters (GstPlugin * plugin, GstRank rank)
+{
+ static volatile gsize registered = 0;
+
+ if (g_once_init_enter (&registered)) {
+ GTypeInfo info = {
+ sizeof (GstGLEffectsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_gl_effects_filter_class_init,
+ NULL,
+ NULL,
+ sizeof (GstGLEffects),
+ 0,
+ NULL
+ };
+ GType generic_type =
+ g_type_register_static (GST_TYPE_GL_EFFECTS, "GstGLEffectsGeneric",
+ &info, 0);
+
+ if (gst_element_register (plugin, "gleffects", rank, generic_type)) {
+ const GstGLEffectsFilterDescriptor *filters;
+ for (filters = gst_gl_effects_filters_descriptors ();
+ NULL != filters->filter_name; ++filters) {
+ gchar *name = g_strdup_printf ("gleffects_%s", filters->filter_name);
+ GTypeInfo info = {
+ sizeof (GstGLEffectsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_gl_effects_filter_class_init,
+ NULL,
+ filters,
+ sizeof (GstGLEffects),
+ 0,
+ (GInstanceInitFunc) gst_gl_effects_filter_init
+ };
+ GType type =
+ g_type_register_static (GST_TYPE_GL_EFFECTS, name, &info, 0);
+ if (!gst_element_register (plugin, name, rank, type)) {
+ GST_WARNING ("Could not register %s", name);
+ }
+ g_free (name);
+ }
+ }
+ g_once_init_leave (&registered, generic_type);
+ }
+ return registered;
+}
diff --git a/ext/gl/gstgleffects.h b/ext/gl/gstgleffects.h
new file mode 100644
index 000000000..a090e0d57
--- /dev/null
+++ b/ext/gl/gstgleffects.h
@@ -0,0 +1,119 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_EFFECTS_H__
+#define __GST_GL_EFFECTS_H__
+
+#include <gst/gl/gstglfilter.h>
+#include <gst/gl/gstglfuncs.h>
+
+#include "effects/gstgleffectssources.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_EFFECTS (gst_gl_effects_get_type())
+#define GST_GL_EFFECTS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_GL_EFFECTS,GstGLEffects))
+#define GST_IS_GL_EFFECTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GL_EFFECTS))
+#define GST_GL_EFFECTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) , GST_TYPE_GL_EFFECTS,GstGLEffectsClass))
+#define GST_IS_GL_EFFECTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) , GST_TYPE_GL_EFFECTS))
+#define GST_GL_EFFECTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) , GST_TYPE_GL_EFFECTS,GstGLEffectsClass))
+
+#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
+#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
+#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
+#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))
+
+typedef struct _GstGLEffects GstGLEffects;
+typedef struct _GstGLEffectsClass GstGLEffectsClass;
+
+typedef struct {
+ gint effect;
+ guint supported_properties;
+ const gchar *filter_name;
+ const gchar *filter_longname;
+} GstGLEffectsFilterDescriptor;
+
+typedef void (* GstGLEffectProcessFunc) (GstGLEffects *effects);
+
+#define NEEDED_TEXTURES 5
+
+enum {
+ GST_GL_EFFECTS_CURVE_HEAT,
+ GST_GL_EFFECTS_CURVE_SEPIA,
+ GST_GL_EFFECTS_CURVE_XPRO,
+ GST_GL_EFFECTS_CURVE_LUMA_XPRO,
+ GST_GL_EFFECTS_CURVE_XRAY,
+ GST_GL_EFFECTS_N_CURVES
+};
+
+struct _GstGLEffects
+{
+ GstGLFilter filter;
+
+ GstGLEffectProcessFunc effect;
+ gint current_effect;
+
+ GstGLMemory *intexture;
+ GstGLMemory *midtexture[NEEDED_TEXTURES];
+ GstGLMemory *outtexture;
+
+ GLuint curve[GST_GL_EFFECTS_N_CURVES];
+
+ GHashTable *shaderstable;
+
+ gboolean horizontal_swap; /* switch left to right */
+ gboolean invert; /* colours */
+};
+
+struct _GstGLEffectsClass
+{
+ GstGLFilterClass filter_class;
+ const GstGLEffectsFilterDescriptor *filter_descriptor;
+};
+
+GType gst_gl_effects_get_type (void);
+gboolean gst_gl_effects_register_filters (GstPlugin *, GstRank);
+GstGLShader* gst_gl_effects_get_fragment_shader (GstGLEffects *effects,
+ const gchar * shader_name, const gchar * shader_source_gles2);
+
+void gst_gl_effects_identity (GstGLEffects *effects);
+void gst_gl_effects_mirror (GstGLEffects *effects);
+void gst_gl_effects_squeeze (GstGLEffects *effects);
+void gst_gl_effects_stretch (GstGLEffects *effects);
+void gst_gl_effects_tunnel (GstGLEffects *effects);
+void gst_gl_effects_fisheye (GstGLEffects *effects);
+void gst_gl_effects_twirl (GstGLEffects *effects);
+void gst_gl_effects_bulge (GstGLEffects *effects);
+void gst_gl_effects_square (GstGLEffects *effects);
+void gst_gl_effects_heat (GstGLEffects *effects);
+void gst_gl_effects_sepia (GstGLEffects *effects);
+void gst_gl_effects_xpro (GstGLEffects *effects);
+void gst_gl_effects_xray (GstGLEffects *effects);
+void gst_gl_effects_luma_xpro (GstGLEffects *effects);
+void gst_gl_effects_sin (GstGLEffects *effects);
+void gst_gl_effects_glow (GstGLEffects *effects);
+void gst_gl_effects_sobel (GstGLEffects *effects);
+void gst_gl_effects_blur (GstGLEffects *effects);
+void gst_gl_effects_laplacian (GstGLEffects *effects);
+
+G_END_DECLS
+
+#endif /*__GST_GL_EFFECTS_H__ */
diff --git a/ext/gl/gstglfilterapp.c b/ext/gl/gstglfilterapp.c
new file mode 100644
index 000000000..8426d0316
--- /dev/null
+++ b/ext/gl/gstglfilterapp.c
@@ -0,0 +1,229 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glfilterapp
+ * @title: glfilterapp
+ *
+ * The resize and redraw callbacks can be set from a client code.
+ *
+ * ## CLient callbacks
+ *
+ * The graphic scene can be written from a client code through the
+ * two glfilterapp properties.
+ *
+ * ## Examples
+ * see gst-plugins-gl/tests/examples/generic/recordgraphic
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglfilterapp.h"
+
+#define GST_CAT_DEFAULT gst_gl_filter_app_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ SIGNAL_0,
+ CLIENT_DRAW_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint gst_gl_filter_app_signals[LAST_SIGNAL] = { 0 };
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_filter_app_debug, "glfilterapp", 0, "glfilterapp element");
+
+#define gst_gl_filter_app_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterApp, gst_gl_filter_app,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_filter_app_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_filter_app_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_filter_app_set_caps (GstGLFilter * filter,
+ GstCaps * incaps, GstCaps * outcaps);
+static gboolean gst_gl_filter_app_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static gboolean gst_gl_filter_app_gl_start (GstGLBaseFilter * base_filter);
+static void gst_gl_filter_app_gl_stop (GstGLBaseFilter * base_filter);
+
+static void
+gst_gl_filter_app_class_init (GstGLFilterAppClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_filter_app_set_property;
+ gobject_class->get_property = gst_gl_filter_app_get_property;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_filter_app_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_filter_app_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_filter_app_set_caps;
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_filter_app_filter_texture;
+
+ /**
+ * GstGLFilterApp::client-draw:
+ * @object: the #GstGLImageSink
+ * @texture: the #guint id of the texture.
+ * @width: the #guint width of the texture.
+ * @height: the #guint height of the texture.
+ *
+ * Will be emitted before to draw the texture. The client should
+ * redraw the surface/contents with the @texture, @width and @height.
+ *
+ * Returns: whether the texture was redrawn by the signal. If not, a
+ * default redraw will occur.
+ */
+ gst_gl_filter_app_signals[CLIENT_DRAW_SIGNAL] =
+ g_signal_new ("client-draw", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL application filter", "Filter/Effect",
+ "Use client callbacks to define the scene",
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_filter_app_init (GstGLFilterApp * filter)
+{
+}
+
+static void
+gst_gl_filter_app_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filter_app_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_filter_app_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLFilter *filter = GST_GL_FILTER (base_filter);
+ GError *error = NULL;
+
+ if (!(filter->default_shader =
+ gst_gl_shader_new_default (base_filter->context, &error))) {
+ GST_ELEMENT_ERROR (filter, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to create the default shader"), ("%s", error->message));
+ return FALSE;
+ }
+
+ return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter);
+}
+
+static void
+gst_gl_filter_app_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLFilter *filter = GST_GL_FILTER (base_filter);
+
+ if (filter->default_shader)
+ gst_object_unref (filter->default_shader);
+ filter->default_shader = NULL;
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static gboolean
+gst_gl_filter_app_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ //GstGLFilterApp* app_filter = GST_GL_FILTER_APP(filter);
+
+ return TRUE;
+}
+
+struct glcb2
+{
+ GstGLFilterApp *app;
+ GstGLMemory *in_tex;
+ GstGLMemory *out_tex;
+};
+
+static gboolean
+_emit_draw_signal (gpointer data)
+{
+ struct glcb2 *cb = data;
+ gboolean drawn;
+
+ g_signal_emit (cb->app, gst_gl_filter_app_signals[CLIENT_DRAW_SIGNAL], 0,
+ cb->in_tex->tex_id, gst_gl_memory_get_texture_width (cb->out_tex),
+ gst_gl_memory_get_texture_height (cb->out_tex), &drawn);
+
+ return !drawn;
+}
+
+static gboolean
+gst_gl_filter_app_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLFilterApp *app_filter = GST_GL_FILTER_APP (filter);
+ gboolean default_draw;
+ struct glcb2 cb;
+
+ cb.app = app_filter;
+ cb.in_tex = in_tex;
+ cb.out_tex = out_tex;
+
+ default_draw =
+ gst_gl_framebuffer_draw_to_texture (filter->fbo,
+ out_tex, _emit_draw_signal, &cb);
+
+ if (default_draw) {
+ gst_gl_filter_render_to_target_with_shader (filter, in_tex, out_tex,
+ filter->default_shader);
+ }
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglfilterapp.h b/ext/gl/gstglfilterapp.h
new file mode 100644
index 000000000..d7e9a6f2a
--- /dev/null
+++ b/ext/gl/gstglfilterapp.h
@@ -0,0 +1,51 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTERAPP_H_
+#define _GST_GL_FILTERAPP_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_FILTER_APP (gst_gl_filter_app_get_type())
+#define GST_GL_FILTER_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTER_APP,GstGLFilterApp))
+#define GST_IS_GL_FILTER_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTER_APP))
+#define GST_GL_FILTER_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTER_APP,GstGLFilterAppClass))
+#define GST_IS_GL_FILTER_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTER_APP))
+#define GST_GL_FILTER_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTER_APP,GstGLFilterAppClass))
+typedef struct _GstGLFilterApp GstGLFilterApp;
+typedef struct _GstGLFilterAppClass GstGLFilterAppClass;
+
+struct _GstGLFilterApp
+{
+ GstGLFilter filter;
+};
+
+struct _GstGLFilterAppClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_filter_app_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERAPP_H_ */
diff --git a/ext/gl/gstglfilterbin.c b/ext/gl/gstglfilterbin.c
new file mode 100644
index 000000000..a3621ec7b
--- /dev/null
+++ b/ext/gl/gstglfilterbin.c
@@ -0,0 +1,270 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglfilterbin.h"
+
+#define GST_CAT_DEFAULT gst_gl_filter_bin_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_FILTER,
+};
+
+enum
+{
+ SIGNAL_0,
+ SIGNAL_CREATE_ELEMENT,
+ LAST_SIGNAL
+};
+
+static guint gst_gl_filter_bin_signals[LAST_SIGNAL] = { 0 };
+
+#define gst_gl_filter_bin_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterBin, gst_gl_filter_bin,
+ GST_TYPE_BIN, GST_DEBUG_CATEGORY_INIT (gst_gl_filter_bin_debug,
+ "glfilterbin", 0, "glfilterbin element"););
+
+static void gst_gl_filter_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_gl_filter_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn gst_gl_filter_bin_change_state (GstElement *
+ element, GstStateChange transition);
+
+static GstStaticPadTemplate _src_pad_template = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(ANY)"));
+
+static void
+gst_gl_filter_bin_class_init (GstGLFilterBinClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstCaps *upload_caps;
+
+ element_class->change_state = gst_gl_filter_bin_change_state;
+
+ gobject_class->set_property = gst_gl_filter_bin_set_property;
+ gobject_class->get_property = gst_gl_filter_bin_get_property;
+
+ gst_element_class_add_static_pad_template (element_class, &_src_pad_template);
+
+ upload_caps = gst_gl_upload_get_input_template_caps ();
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, upload_caps));
+ gst_caps_unref (upload_caps);
+
+ g_object_class_install_property (gobject_class, PROP_FILTER,
+ g_param_spec_object ("filter",
+ "GL filter element",
+ "The GL filter chain to use",
+ GST_TYPE_ELEMENT,
+ GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstFilterBin::create-element:
+ * @object: the #GstGLFilterBin
+ *
+ * Will be emitted when we need the processing element/s that this bin will use
+ *
+ * Returns: a new #GstElement
+ */
+ gst_gl_filter_bin_signals[SIGNAL_CREATE_ELEMENT] =
+ g_signal_new ("create-element", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ GST_TYPE_ELEMENT, 0);
+
+ gst_element_class_set_metadata (element_class,
+ "GL Filter Bin", "Filter/Video",
+ "Infrastructure to process GL textures",
+ "Matthew Waters <matthew@centricular.com>");
+}
+
+static void
+gst_gl_filter_bin_init (GstGLFilterBin * self)
+{
+ GstPad *pad;
+
+ self->upload = gst_element_factory_make ("glupload", NULL);
+ self->in_convert = gst_element_factory_make ("glcolorconvert", NULL);
+ self->out_convert = gst_element_factory_make ("glcolorconvert", NULL);
+ self->download = gst_element_factory_make ("gldownload", NULL);
+
+ gst_bin_add (GST_BIN (self), self->upload);
+ gst_bin_add (GST_BIN (self), self->in_convert);
+ gst_bin_add (GST_BIN (self), self->out_convert);
+ gst_bin_add (GST_BIN (self), self->download);
+
+ gst_element_link_pads (self->upload, "src", self->in_convert, "sink");
+ gst_element_link_pads (self->out_convert, "src", self->download, "sink");
+
+ pad = gst_element_get_static_pad (self->download, "src");
+ if (pad) {
+ GST_DEBUG_OBJECT (self, "setting target src pad %" GST_PTR_FORMAT, pad);
+ self->srcpad = gst_ghost_pad_new ("src", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
+ gst_object_unref (pad);
+ }
+
+ pad = gst_element_get_static_pad (self->upload, "sink");
+ if (pad) {
+ GST_DEBUG_OBJECT (self, "setting target sink pad %" GST_PTR_FORMAT, pad);
+ self->sinkpad = gst_ghost_pad_new ("sink", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad);
+ gst_object_unref (pad);
+ }
+}
+
+static gboolean
+_connect_filter_element (GstGLFilterBin * self)
+{
+ gboolean res = TRUE;
+
+ gst_object_set_name (GST_OBJECT (self->filter), "filter");
+ res &= gst_bin_add (GST_BIN (self), self->filter);
+
+ res &= gst_element_link_pads (self->in_convert, "src", self->filter, "sink");
+ res &= gst_element_link_pads (self->filter, "src", self->out_convert, "sink");
+
+ if (!res)
+ GST_ERROR_OBJECT (self, "Failed to link filter element into the pipeline");
+
+ return res;
+}
+
+void
+gst_gl_filter_bin_finish_init_with_element (GstGLFilterBin * self,
+ GstElement * element)
+{
+ g_return_if_fail (GST_IS_ELEMENT (element));
+
+ self->filter = element;
+
+ if (!_connect_filter_element (self)) {
+ gst_object_unref (self->filter);
+ self->filter = NULL;
+ }
+}
+
+void
+gst_gl_filter_bin_finish_init (GstGLFilterBin * self)
+{
+ GstGLFilterBinClass *klass = GST_GL_FILTER_BIN_GET_CLASS (self);
+ GstElement *element = NULL;
+
+ if (klass->create_element)
+ element = klass->create_element ();
+
+ if (element)
+ gst_gl_filter_bin_finish_init_with_element (self, element);
+}
+
+static void
+gst_gl_filter_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterBin *self = GST_GL_FILTER_BIN (object);
+
+ switch (prop_id) {
+ case PROP_FILTER:
+ g_value_set_object (value, self->filter);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filter_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterBin *self = GST_GL_FILTER_BIN (object);
+
+ switch (prop_id) {
+ case PROP_FILTER:
+ {
+ GstElement *filter = g_value_get_object (value);
+ if (self->filter)
+ gst_bin_remove (GST_BIN (self), self->filter);
+ self->filter = filter;
+ if (filter) {
+ gst_object_ref_sink (filter);
+ _connect_filter_element (self);
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstStateChangeReturn
+gst_gl_filter_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLFilterBin *self = GST_GL_FILTER_BIN (element);
+ GstGLFilterBinClass *klass = GST_GL_FILTER_BIN_GET_CLASS (self);
+ GstStateChangeReturn ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!self->filter) {
+ if (klass->create_element)
+ self->filter = klass->create_element ();
+
+ if (!self->filter)
+ g_signal_emit (element,
+ gst_gl_filter_bin_signals[SIGNAL_CREATE_ELEMENT], 0,
+ &self->filter);
+
+ if (!self->filter) {
+ GST_ERROR_OBJECT (element, "Failed to retrieve element");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ if (!_connect_filter_element (self))
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstglfilterbin.h b/ext/gl/gstglfilterbin.h
new file mode 100644
index 000000000..4a77c6c79
--- /dev/null
+++ b/ext/gl/gstglfilterbin.h
@@ -0,0 +1,80 @@
+/*
+ * GStreamer
+ * Copyright (C) 2007 David Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTER_BIN_H_
+#define _GST_GL_FILTER_BIN_H_
+
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+#include <gst/video/video.h>
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_filter_bin_get_type(void);
+#define GST_TYPE_GL_FILTER_BIN (gst_gl_filter_bin_get_type())
+#define GST_GL_FILTER_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTER_BIN,GstGLFilterBin))
+#define GST_IS_GL_FILTER_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTER_BIN))
+#define GST_GL_FILTER_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTER_BIN,GstGLFilterBinClass))
+#define GST_IS_GL_FILTER_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTER_BIN))
+#define GST_GL_FILTER_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTER_BIN,GstGLFilterBinClass))
+
+typedef struct _GstGLFilterBin GstGLFilterBin;
+typedef struct _GstGLFilterBinClass GstGLFilterBinClass;
+
+/**
+ * GstGLFilterBin:
+ * @parent: parent #GstBin
+ */
+struct _GstGLFilterBin
+{
+ GstBin parent;
+
+ GstPad *srcpad;
+ GstPad *sinkpad;
+
+ GstElement *upload;
+ GstElement *in_convert;
+ GstElement *filter;
+ GstElement *out_convert;
+ GstElement *download;
+};
+
+/**
+ * GstGLFilterBinClass:
+ * @parent_class: parent class
+ */
+struct _GstGLFilterBinClass
+{
+ GstBinClass parent_class;
+
+ GstElement * (*create_element) (void);
+};
+
+void gst_gl_filter_bin_finish_init (GstGLFilterBin * self);
+void gst_gl_filter_bin_finish_init_with_element (GstGLFilterBin * self,
+ GstElement * element);
+
+G_END_DECLS
+
+#endif /* _GST_GL_FILTER_BIN_H_ */
diff --git a/ext/gl/gstglfiltercube.c b/ext/gl/gstglfiltercube.c
new file mode 100644
index 000000000..9e04ea081
--- /dev/null
+++ b/ext/gl/gstglfiltercube.c
@@ -0,0 +1,496 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glfiltercube
+ * @title: glfiltercube
+ *
+ * The resize and redraw callbacks can be set from a client code.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! glfiltercube ! glimagesink
+ * ]| A pipeline to mpa textures on the 6 cube faces..
+ * FBO is required.
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! glfiltercube ! video/x-raw, width=640, height=480 ! glimagesink
+ * ]| Resize scene after drawing the cube.
+ * The scene size is greater than the input video size.
+ |[
+ * gst-launch-1.0 -v videotestsrc ! video/x-raw, width=640, height=480 ! glfiltercube ! glimagesink
+ * ]| Resize scene before drawing the cube.
+ * The scene size is greater than the input video size.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglapi.h>
+#include "gstglfiltercube.h"
+#include "gstglutils.h"
+
+#define GST_CAT_DEFAULT gst_gl_filter_cube_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_RED,
+ PROP_GREEN,
+ PROP_BLUE,
+ PROP_FOVY,
+ PROP_ASPECT,
+ PROP_ZNEAR,
+ PROP_ZFAR
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_filter_cube_debug, "glfiltercube", 0, "glfiltercube element");
+#define gst_gl_filter_cube_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterCube, gst_gl_filter_cube,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_filter_cube_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_filter_cube_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_filter_cube_set_caps (GstGLFilter * filter,
+ GstCaps * incaps, GstCaps * outcaps);
+static gboolean gst_gl_filter_cube_gl_start (GstGLBaseFilter * filter);
+static void gst_gl_filter_cube_gl_stop (GstGLBaseFilter * filter);
+static gboolean _callback (gpointer stuff);
+static gboolean gst_gl_filter_cube_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+/* vertex source */
+static const gchar *cube_v_src =
+ "attribute vec4 a_position; \n"
+ "attribute vec2 a_texcoord; \n"
+ "uniform mat4 u_matrix; \n"
+ "uniform float xrot_degree, yrot_degree, zrot_degree; \n"
+ "varying vec2 v_texcoord; \n"
+ "void main() \n"
+ "{ \n"
+ " float PI = 3.14159265; \n"
+ " float xrot = xrot_degree*2.0*PI/360.0; \n"
+ " float yrot = yrot_degree*2.0*PI/360.0; \n"
+ " float zrot = zrot_degree*2.0*PI/360.0; \n"
+ " mat4 matX = mat4 ( \n"
+ " 1.0, 0.0, 0.0, 0.0, \n"
+ " 0.0, cos(xrot), sin(xrot), 0.0, \n"
+ " 0.0, -sin(xrot), cos(xrot), 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " mat4 matY = mat4 ( \n"
+ " cos(yrot), 0.0, -sin(yrot), 0.0, \n"
+ " 0.0, 1.0, 0.0, 0.0, \n"
+ " sin(yrot), 0.0, cos(yrot), 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " mat4 matZ = mat4 ( \n"
+ " cos(zrot), sin(zrot), 0.0, 0.0, \n"
+ " -sin(zrot), cos(zrot), 0.0, 0.0, \n"
+ " 0.0, 0.0, 1.0, 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " gl_Position = u_matrix * matZ * matY * matX * a_position; \n"
+ " v_texcoord = a_texcoord; \n"
+ "} \n";
+
+/* fragment source */
+static const gchar *cube_f_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "varying vec2 v_texcoord; \n"
+ "uniform sampler2D s_texture; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_FragColor = texture2D( s_texture, v_texcoord );\n"
+ "} \n";
+
+static void
+gst_gl_filter_cube_class_init (GstGLFilterCubeClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_filter_cube_set_property;
+ gobject_class->get_property = gst_gl_filter_cube_get_property;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_filter_cube_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_filter_cube_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_filter_cube_set_caps;
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_filter_cube_filter_texture;
+
+ g_object_class_install_property (gobject_class, PROP_RED,
+ g_param_spec_float ("red", "Red", "Background red color",
+ 0.0f, 1.0f, 0.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_GREEN,
+ g_param_spec_float ("green", "Green", "Background green color",
+ 0.0f, 1.0f, 0.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BLUE,
+ g_param_spec_float ("blue", "Blue", "Background blue color",
+ 0.0f, 1.0f, 0.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_FOVY,
+ g_param_spec_double ("fovy", "Fovy", "Field of view angle in degrees",
+ 0.0, 180.0, 45.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ASPECT,
+ g_param_spec_double ("aspect", "Aspect",
+ "Field of view in the x direction", 0.0, 100, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ZNEAR,
+ g_param_spec_double ("znear", "Znear",
+ "Specifies the distance from the viewer to the near clipping plane",
+ 0.0, 100.0, 0.1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ZFAR,
+ g_param_spec_double ("zfar", "Zfar",
+ "Specifies the distance from the viewer to the far clipping plane",
+ 0.0, 1000.0, 100.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "OpenGL cube filter",
+ "Filter/Effect/Video", "Map input texture on the 6 cube faces",
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_filter_cube_init (GstGLFilterCube * filter)
+{
+ filter->shader = NULL;
+ filter->fovy = 45;
+ filter->aspect = 0;
+ filter->znear = 0.1;
+ filter->zfar = 100;
+}
+
+static void
+gst_gl_filter_cube_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterCube *filter = GST_GL_FILTER_CUBE (object);
+
+ switch (prop_id) {
+ case PROP_RED:
+ filter->red = g_value_get_float (value);
+ break;
+ case PROP_GREEN:
+ filter->green = g_value_get_float (value);
+ break;
+ case PROP_BLUE:
+ filter->blue = g_value_get_float (value);
+ break;
+ case PROP_FOVY:
+ filter->fovy = g_value_get_double (value);
+ break;
+ case PROP_ASPECT:
+ filter->aspect = g_value_get_double (value);
+ break;
+ case PROP_ZNEAR:
+ filter->znear = g_value_get_double (value);
+ break;
+ case PROP_ZFAR:
+ filter->zfar = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filter_cube_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterCube *filter = GST_GL_FILTER_CUBE (object);
+
+ switch (prop_id) {
+ case PROP_RED:
+ g_value_set_float (value, filter->red);
+ break;
+ case PROP_GREEN:
+ g_value_set_float (value, filter->green);
+ break;
+ case PROP_BLUE:
+ g_value_set_float (value, filter->blue);
+ break;
+ case PROP_FOVY:
+ g_value_set_double (value, filter->fovy);
+ break;
+ case PROP_ASPECT:
+ g_value_set_double (value, filter->aspect);
+ break;
+ case PROP_ZNEAR:
+ g_value_set_double (value, filter->znear);
+ break;
+ case PROP_ZFAR:
+ g_value_set_double (value, filter->zfar);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_filter_cube_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstGLFilterCube *cube_filter = GST_GL_FILTER_CUBE (filter);
+
+ if (cube_filter->aspect == 0)
+ cube_filter->aspect = (gdouble) GST_VIDEO_INFO_WIDTH (&filter->out_info) /
+ (gdouble) GST_VIDEO_INFO_HEIGHT (&filter->out_info);
+
+ return TRUE;
+}
+
+static void
+gst_gl_filter_cube_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLFilterCube *cube_filter = GST_GL_FILTER_CUBE (base_filter);
+ const GstGLFuncs *gl = base_filter->context->gl_vtable;
+
+ if (cube_filter->vao) {
+ gl->DeleteVertexArrays (1, &cube_filter->vao);
+ cube_filter->vao = 0;
+ }
+
+ if (cube_filter->vertex_buffer) {
+ gl->DeleteBuffers (1, &cube_filter->vertex_buffer);
+ cube_filter->vertex_buffer = 0;
+ }
+
+ if (cube_filter->vbo_indices) {
+ gl->DeleteBuffers (1, &cube_filter->vbo_indices);
+ cube_filter->vbo_indices = 0;
+ }
+
+ if (cube_filter->shader) {
+ gst_object_unref (cube_filter->shader);
+ cube_filter->shader = NULL;
+ }
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static gboolean
+gst_gl_filter_cube_gl_start (GstGLBaseFilter * filter)
+{
+ GstGLFilterCube *cube_filter = GST_GL_FILTER_CUBE (filter);
+
+ /* blocking call, wait the opengl thread has compiled the shader */
+ return gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
+ cube_v_src, cube_f_src, &cube_filter->shader);
+}
+
+static gboolean
+gst_gl_filter_cube_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLFilterCube *cube_filter = GST_GL_FILTER_CUBE (filter);
+
+ cube_filter->in_tex = in_tex;
+
+ return gst_gl_framebuffer_draw_to_texture (filter->fbo, out_tex, _callback,
+ cube_filter);
+}
+
+/* *INDENT-OFF* */
+static const GLfloat vertices[] = {
+ /*| Vertex | TexCoord |*/
+ /* front face */
+ 1.0, 1.0, -1.0, 1.0, 0.0,
+ 1.0, -1.0, -1.0, 1.0, 1.0,
+ -1.0, -1.0, -1.0, 0.0, 1.0,
+ -1.0, 1.0, -1.0, 0.0, 0.0,
+ /* back face */
+ 1.0, 1.0, 1.0, 1.0, 0.0,
+ -1.0, 1.0, 1.0, 0.0, 0.0,
+ -1.0, -1.0, 1.0, 0.0, 1.0,
+ 1.0, -1.0, 1.0, 1.0, 1.0,
+ /* right face */
+ 1.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, -1.0, 1.0, 0.0, 0.0,
+ 1.0, -1.0, -1.0, 0.0, 1.0,
+ 1.0, 1.0, -1.0, 1.0, 1.0,
+ /* left face */
+ -1.0, 1.0, 1.0, 1.0, 0.0,
+ -1.0, 1.0, -1.0, 1.0, 1.0,
+ -1.0, -1.0, -1.0, 0.0, 1.0,
+ -1.0, -1.0, 1.0, 0.0, 0.0,
+ /* top face */
+ 1.0, -1.0, 1.0, 1.0, 0.0,
+ -1.0, -1.0, 1.0, 0.0, 0.0,
+ -1.0, -1.0, -1.0, 0.0, 1.0,
+ 1.0, -1.0, -1.0, 1.0, 1.0,
+ /* bottom face */
+ 1.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, 1.0, -1.0, 1.0, 1.0,
+ -1.0, 1.0, -1.0, 0.0, 1.0,
+ -1.0, 1.0, 1.0, 0.0, 0.0
+};
+
+static const GLushort indices[] = {
+ 0, 1, 2,
+ 0, 2, 3,
+ 4, 5, 6,
+ 4, 6, 7,
+ 8, 9, 10,
+ 8, 10, 11,
+ 12, 13, 14,
+ 12, 14, 15,
+ 16, 17, 18,
+ 16, 18, 19,
+ 20, 21, 22,
+ 20, 22, 23
+};
+/* *INDENT-ON* */
+
+static void
+_bind_buffer (GstGLFilterCube * cube_filter)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (cube_filter)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, cube_filter->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, cube_filter->vertex_buffer);
+
+ cube_filter->attr_position =
+ gst_gl_shader_get_attribute_location (cube_filter->shader, "a_position");
+
+ cube_filter->attr_texture =
+ gst_gl_shader_get_attribute_location (cube_filter->shader, "a_texcoord");
+
+ /* Load the vertex position */
+ gl->VertexAttribPointer (cube_filter->attr_position, 3, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) 0);
+
+ /* Load the texture coordinate */
+ gl->VertexAttribPointer (cube_filter->attr_texture, 2, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+
+ gl->EnableVertexAttribArray (cube_filter->attr_position);
+ gl->EnableVertexAttribArray (cube_filter->attr_texture);
+}
+
+static void
+_unbind_buffer (GstGLFilterCube * cube_filter)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (cube_filter)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (cube_filter->attr_position);
+ gl->DisableVertexAttribArray (cube_filter->attr_texture);
+}
+
+static gboolean
+_callback (gpointer stuff)
+{
+ GstGLFilter *filter = GST_GL_FILTER (stuff);
+ GstGLFilterCube *cube_filter = GST_GL_FILTER_CUBE (filter);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ static GLfloat xrot = 0;
+ static GLfloat yrot = 0;
+ static GLfloat zrot = 0;
+
+ const GLfloat matrix[] = {
+ 0.5f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.5f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ gl->Enable (GL_DEPTH_TEST);
+
+ gl->ClearColor (cube_filter->red, cube_filter->green, cube_filter->blue, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ gst_gl_shader_use (cube_filter->shader);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, cube_filter->in_tex->tex_id);
+ gst_gl_shader_set_uniform_1i (cube_filter->shader, "s_texture", 0);
+ gst_gl_shader_set_uniform_1f (cube_filter->shader, "xrot_degree", xrot);
+ gst_gl_shader_set_uniform_1f (cube_filter->shader, "yrot_degree", yrot);
+ gst_gl_shader_set_uniform_1f (cube_filter->shader, "zrot_degree", zrot);
+ gst_gl_shader_set_uniform_matrix_4fv (cube_filter->shader, "u_matrix", 1,
+ GL_FALSE, matrix);
+
+ if (!cube_filter->vertex_buffer) {
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &cube_filter->vao);
+ gl->BindVertexArray (cube_filter->vao);
+ }
+
+ gl->GenBuffers (1, &cube_filter->vertex_buffer);
+ gl->BindBuffer (GL_ARRAY_BUFFER, cube_filter->vertex_buffer);
+ gl->BufferData (GL_ARRAY_BUFFER, 6 * 4 * 5 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+
+ gl->GenBuffers (1, &cube_filter->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, cube_filter->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+
+ if (gl->GenVertexArrays) {
+ _bind_buffer (cube_filter);
+ gl->BindVertexArray (0);
+ }
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+ }
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (cube_filter->vao);
+ _bind_buffer (cube_filter);
+
+ gl->DrawElements (GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (cube_filter);
+
+ gl->Disable (GL_DEPTH_TEST);
+
+ xrot += 0.3f;
+ yrot += 0.2f;
+ zrot += 0.4f;
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglfiltercube.h b/ext/gl/gstglfiltercube.h
new file mode 100644
index 000000000..2dcd6c5d5
--- /dev/null
+++ b/ext/gl/gstglfiltercube.h
@@ -0,0 +1,73 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTERCUBE_H_
+#define _GST_GL_FILTERCUBE_H_
+
+#include <gst/gl/gstglfilter.h>
+#include <gst/gl/gstglfuncs.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_FILTER_CUBE (gst_gl_filter_cube_get_type())
+#define GST_GL_FILTER_CUBE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTER_CUBE,GstGLFilterCube))
+#define GST_IS_GL_FILTER_CUBE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTER_CUBE))
+#define GST_GL_FILTER_CUBE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTER_CUBE,GstGLFilterCubeClass))
+#define GST_IS_GL_FILTER_CUBE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTER_CUBE))
+#define GST_GL_FILTER_CUBE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTER_CUBE,GstGLFilterCubeClass))
+
+typedef struct _GstGLFilterCube GstGLFilterCube;
+typedef struct _GstGLFilterCubeClass GstGLFilterCubeClass;
+
+struct _GstGLFilterCube
+{
+ GstGLFilter filter;
+
+ GstGLShader *shader;
+ GstGLMemory *in_tex;
+
+ /* background color */
+ gfloat red;
+ gfloat green;
+ gfloat blue;
+
+ /* perspective */
+ gdouble fovy;
+ gdouble aspect;
+ gdouble znear;
+ gdouble zfar;
+
+ GLuint vao;
+ GLuint vbo_indices;
+ GLuint vertex_buffer;
+ GLint attr_position;
+ GLint attr_texture;
+};
+
+struct _GstGLFilterCubeClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_filter_cube_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERCUBE_H_ */
diff --git a/ext/gl/gstglfilterglass.c b/ext/gl/gstglfilterglass.c
new file mode 100644
index 000000000..31f63b542
--- /dev/null
+++ b/ext/gl/gstglfilterglass.c
@@ -0,0 +1,412 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ * Inspired from http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions
+ *
+ * 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:element-glfilterglass
+ * @title: glfilterglass
+ *
+ * Map textures on moving glass.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! glfilterglass ! glimagesink
+ * ]| A pipeline inspired from http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions
+ * FBO is required.
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! glfilterglass ! video/x-raw, width=640, height=480 ! glimagesink
+ * ]| The scene is greater than the input size.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <gst/gl/gstglfuncs.h>
+
+#include "gstglfilterglass.h"
+
+#include "gstglutils.h"
+
+#define GST_CAT_DEFAULT gst_gl_filter_glass_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_filter_glass_debug, "glfilterglass", 0, "glfilterglass element");
+#define gst_gl_filter_glass_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterGlass, gst_gl_filter_glass,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_filter_glass_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_filter_glass_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_filter_glass_reset (GstBaseTransform * trans);
+
+static gboolean gst_gl_filter_glass_init_shader (GstGLFilter * filter);
+static gboolean gst_gl_filter_glass_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static void gst_gl_filter_glass_draw_background_gradient ();
+static void gst_gl_filter_glass_draw_video_plane (GstGLFilter * filter,
+ gint width, gint height, guint texture, gfloat center_x, gfloat center_y,
+ gfloat start_alpha, gfloat stop_alpha, gboolean reversed, gfloat rotation);
+
+static gboolean gst_gl_filter_glass_callback (gpointer stuff);
+
+/* *INDENT-OFF* */
+static const gchar *glass_fragment_source =
+ "uniform sampler2D tex;\n"
+ "varying float alpha;\n"
+ "void main () {\n"
+ " float p = 0.0525;\n"
+ " float L1 = p*1.0;\n"
+ " float L2 = 1.0 - L1;\n"
+ " float L3 = 1.0 - L1;\n"
+ " float w = 1.0;\n"
+ " float r = L1;\n"
+ " if (gl_TexCoord[0].x < L1 && gl_TexCoord[0].y < L1)\n"
+ " r = sqrt( (gl_TexCoord[0].x - L1) * (gl_TexCoord[0].x - L1) + (gl_TexCoord[0].y - L1) * (gl_TexCoord[0].y - L1) );\n"
+ " else if (gl_TexCoord[0].x > L2 && gl_TexCoord[0].y < L1)\n"
+ " r = sqrt( (gl_TexCoord[0].x - L2) * (gl_TexCoord[0].x - L2) + (gl_TexCoord[0].y - L1) * (gl_TexCoord[0].y - L1) );\n"
+ " else if (gl_TexCoord[0].x > L2 && gl_TexCoord[0].y > L3)\n"
+ " r = sqrt( (gl_TexCoord[0].x - L2) * (gl_TexCoord[0].x - L2) + (gl_TexCoord[0].y - L3) * (gl_TexCoord[0].y - L3) );\n"
+ " else if (gl_TexCoord[0].x < L1 && gl_TexCoord[0].y > L3)\n"
+ " r = sqrt( (gl_TexCoord[0].x - L1) * (gl_TexCoord[0].x - L1) + (gl_TexCoord[0].y - L3) * (gl_TexCoord[0].y - L3) );\n"
+ " if (r > L1)\n"
+ " w = 0.0;\n"
+ " vec4 color = texture2D (tex, gl_TexCoord[0].st);\n"
+ " gl_FragColor = vec4(color.rgb, alpha * w);\n"
+ "}\n";
+
+static const gchar *glass_vertex_source =
+ "uniform float yrot;\n"
+ "uniform float aspect;\n"
+ "const float fovy = 80.0;\n"
+ "const float znear = 1.0;\n"
+ "const float zfar = 5000.0;\n"
+ "varying float alpha;\n"
+ "void main () {\n"
+ " float f = 1.0/(tan(radians(fovy/2.0)));\n"
+ " float rot = radians (yrot);\n"
+ " // replacement for gluPerspective\n"
+ " mat4 perspective = mat4 (\n"
+ " f/aspect, 0.0, 0.0, 0.0,\n"
+ " 0.0, f, 0.0, 0.0,\n"
+ " 0.0, 0.0, (znear+zfar)/(znear-zfar), 2.0*znear*zfar/(znear-zfar),\n"
+ " 0.0, 0.0, -1.0, 0.0 );\n"
+ " mat4 trans = mat4 (\n"
+ " 1.0, 0.0, 0.0, 0.0,\n"
+ " 0.0, 1.0, 0.0, 0.0,\n"
+ " 0.0, 0.0, 1.0, -3.0,\n"
+ " 0.0, 0.0, 0.0, 1.0 );\n"
+ " mat4 rotation = mat4 (\n"
+ " cos(rot), 0.0, sin(rot), 0.0,\n"
+ " 0.0, 1.0, 0.0, 0.0,\n"
+ " -sin(rot), 0.0, cos(rot), 0.0,\n"
+ " 0.0, 0.0, 0.0, 1.0 );\n"
+ " gl_Position = trans * perspective * rotation * gl_ModelViewProjectionMatrix * gl_Vertex;\n"
+ " gl_TexCoord[0] = gl_MultiTexCoord0;\n"
+ " alpha = gl_Color.a;\n"
+ "}\n";
+
+static const gchar * passthrough_vertex =
+ "void main () {\n"
+ " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
+ " gl_FrontColor = gl_Color;\n"
+ "}\n";
+
+static const gchar * passthrough_fragment =
+ "void main () {\n"
+ " gl_FragColor = gl_Color;\n"
+ "}\n";
+/* *INDENT-ON* */
+
+static void
+gst_gl_filter_glass_class_init (GstGLFilterGlassClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_filter_glass_set_property;
+ gobject_class->get_property = gst_gl_filter_glass_get_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL glass filter",
+ "Filter/Effect/Video", "Glass Filter",
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_filter_glass_filter_texture;
+ GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_filter_glass_init_shader;
+ GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_filter_glass_reset;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = GST_GL_API_OPENGL;
+}
+
+static void
+gst_gl_filter_glass_init (GstGLFilterGlass * filter)
+{
+ filter->shader = NULL;
+ filter->timestamp = 0;
+}
+
+static gboolean
+gst_gl_filter_glass_reset (GstBaseTransform * trans)
+{
+ GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (trans);
+
+ //blocking call, wait the opengl thread has destroyed the shader
+ if (glass_filter->shader)
+ gst_object_unref (glass_filter->shader);
+ glass_filter->shader = NULL;
+ if (glass_filter->passthrough_shader)
+ gst_object_unref (glass_filter->passthrough_shader);
+ glass_filter->passthrough_shader = NULL;
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
+}
+
+static void
+gst_gl_filter_glass_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ //GstGLFilterGlass *filter = GST_GL_FILTER_GLASS (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filter_glass_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ //GstGLFilterGlass *filter = GST_GL_FILTER_GLASS (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_filter_glass_init_shader (GstGLFilter * filter)
+{
+ gboolean ret;
+ GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
+
+ //blocking call, wait the opengl thread has compiled the shader
+ ret =
+ gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
+ glass_vertex_source, glass_fragment_source, &glass_filter->shader);
+ if (ret)
+ ret =
+ gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
+ passthrough_vertex, passthrough_fragment,
+ &glass_filter->passthrough_shader);
+
+ return ret;
+}
+
+static gboolean
+gst_gl_filter_glass_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
+
+ glass_filter->in_tex = in_tex;
+
+ gst_gl_framebuffer_draw_to_texture (filter->fbo, out_tex,
+ gst_gl_filter_glass_callback, glass_filter);
+
+ return TRUE;
+}
+
+static gint64
+get_time (void)
+{
+ static GTimeVal val;
+ g_get_current_time (&val);
+
+ return (val.tv_sec * G_USEC_PER_SEC) + val.tv_usec;
+}
+
+static void
+gst_gl_filter_glass_draw_background_gradient (GstGLFilterGlass * glass)
+{
+ GstGLFilter *filter = GST_GL_FILTER (glass);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+/* *INDENT-OFF* */
+ gfloat mesh[] = {
+ /* | Vertex | Color | */
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
+ -1.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
+ };
+/* *INDENT-ON* */
+
+ GLushort indices[] = {
+ 0, 1, 2,
+ 0, 2, 3,
+ 2, 3, 4,
+ 2, 4, 5
+ };
+
+ gl->ClientActiveTexture (GL_TEXTURE0);
+ gl->EnableClientState (GL_VERTEX_ARRAY);
+ gl->EnableClientState (GL_COLOR_ARRAY);
+
+ gl->VertexPointer (3, GL_FLOAT, 7 * sizeof (gfloat), mesh);
+ gl->ColorPointer (4, GL_FLOAT, 7 * sizeof (gfloat), &mesh[3]);
+
+ gl->DrawElements (GL_TRIANGLES, 12, GL_UNSIGNED_SHORT, indices);
+
+ gl->DisableClientState (GL_VERTEX_ARRAY);
+ gl->DisableClientState (GL_COLOR_ARRAY);
+}
+
+static void
+gst_gl_filter_glass_draw_video_plane (GstGLFilter * filter,
+ gint width, gint height, guint texture,
+ gfloat center_x, gfloat center_y,
+ gfloat start_alpha, gfloat stop_alpha, gboolean reversed, gfloat rotation)
+{
+ GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gfloat topy = reversed ? center_y - 1.0f : center_y + 1.0f;
+ gfloat bottomy = reversed ? center_y + 1.0f : center_y - 1.0f;
+
+/* *INDENT-OFF* */
+ gfloat mesh[] = {
+ /*| Vertex |TexCoord0| Colour |*/
+ center_x-1.6, topy, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, start_alpha,
+ center_x+1.6, topy, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, start_alpha,
+ center_x+1.6, bottomy, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, stop_alpha,
+ center_x-1.6, bottomy, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, stop_alpha,
+ };
+/* *INDENT-ON* */
+
+ GLushort indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, texture);
+
+ gst_gl_shader_set_uniform_1i (glass_filter->shader, "tex", 0);
+ gst_gl_shader_set_uniform_1f (glass_filter->shader, "yrot", rotation);
+ gst_gl_shader_set_uniform_1f (glass_filter->shader, "aspect",
+ (gfloat) width / (gfloat) height);
+
+ gl->ClientActiveTexture (GL_TEXTURE0);
+ gl->EnableClientState (GL_TEXTURE_COORD_ARRAY);
+ gl->EnableClientState (GL_VERTEX_ARRAY);
+ gl->EnableClientState (GL_COLOR_ARRAY);
+
+ gl->VertexPointer (3, GL_FLOAT, 9 * sizeof (gfloat), mesh);
+ gl->TexCoordPointer (2, GL_FLOAT, 9 * sizeof (gfloat), &mesh[3]);
+ gl->ColorPointer (4, GL_FLOAT, 9 * sizeof (gfloat), &mesh[5]);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
+
+ gl->DisableClientState (GL_TEXTURE_COORD_ARRAY);
+ gl->DisableClientState (GL_VERTEX_ARRAY);
+ gl->DisableClientState (GL_COLOR_ARRAY);
+}
+
+static gboolean
+gst_gl_filter_glass_callback (gpointer stuff)
+{
+ static gint64 start_time = 0;
+ gfloat rotation;
+
+ GstGLFilter *filter = GST_GL_FILTER (stuff);
+ GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (stuff);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ gint width = GST_VIDEO_INFO_WIDTH (&filter->out_info);
+ gint height = GST_VIDEO_INFO_HEIGHT (&filter->out_info);
+ guint texture = glass_filter->in_tex->tex_id;
+
+ if (start_time == 0)
+ start_time = get_time ();
+ else {
+ gint64 time_left =
+ (glass_filter->timestamp / 1000) - (get_time () - start_time);
+ time_left -= 1000000 / 25;
+ if (time_left > 2000) {
+ GST_LOG ("escape");
+ return FALSE;
+ }
+ }
+
+ gst_gl_shader_use (glass_filter->passthrough_shader);
+
+ gst_gl_filter_glass_draw_background_gradient (glass_filter);
+
+ //Rotation
+ if (start_time != 0) {
+ gint64 time_passed = get_time () - start_time;
+ rotation = sin (time_passed / 1200000.0) * 45.0f;
+ } else {
+ rotation = 0.0f;
+ }
+
+ gl->Enable (GL_BLEND);
+ gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ gst_gl_shader_use (glass_filter->shader);
+
+ //Reflection
+ gst_gl_filter_glass_draw_video_plane (filter, width, height, texture,
+ 0.0f, 2.0f, 0.3f, 0.0f, TRUE, rotation);
+
+ //Main video
+ gst_gl_filter_glass_draw_video_plane (filter, width, height, texture,
+ 0.0f, 0.0f, 1.0f, 1.0f, FALSE, rotation);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
+
+ gl->Disable (GL_BLEND);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglfilterglass.h b/ext/gl/gstglfilterglass.h
new file mode 100644
index 000000000..1f1c91d2f
--- /dev/null
+++ b/ext/gl/gstglfilterglass.h
@@ -0,0 +1,57 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTERGLASS_H_
+#define _GST_GL_FILTERGLASS_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_FILTER_GLASS (gst_gl_filter_glass_get_type())
+#define GST_GL_FILTER_GLASS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTER_GLASS,GstGLFilterGlass))
+#define GST_IS_GL_FILTER_GLASS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTER_GLASS))
+#define GST_GL_FILTER_GLASS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTER_GLASS,GstGLFilterGlassClass))
+#define GST_IS_GL_FILTER_GLASS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTER_GLASS))
+#define GST_GL_FILTER_GLASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTER_GLASS,GstGLFilterGlassClass))
+
+typedef struct _GstGLFilterGlass GstGLFilterGlass;
+typedef struct _GstGLFilterGlassClass GstGLFilterGlassClass;
+
+struct _GstGLFilterGlass
+{
+ GstGLFilter filter;
+ GstGLShader *passthrough_shader;
+ GstGLShader *shader;
+ gint64 timestamp;
+ GstGLMemory *in_tex;
+ GstGLMemory *out_tex;
+};
+
+struct _GstGLFilterGlassClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_filter_glass_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERGLASS_H_ */
diff --git a/ext/gl/gstglfilterreflectedscreen.c b/ext/gl/gstglfilterreflectedscreen.c
new file mode 100644
index 000000000..2ca090c3a
--- /dev/null
+++ b/ext/gl/gstglfilterreflectedscreen.c
@@ -0,0 +1,486 @@
+/*
+ * GStreamer
+ * Copyright (C) 2010 Pierre Pouzol<pierre.pouzol@hotmail.fr>
+ *
+ * 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:element-glfilterreflectedscreen
+ * @title: glfilterreflectedscreen
+ *
+ * Map Video Texture upon a screen, on a reflecting surface
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! glfilterreflectedscreen ! glimagesink
+ * ]|
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include "gstglfilterreflectedscreen.h"
+
+#define GST_CAT_DEFAULT gst_gl_filter_reflected_screen_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_GRAPHIC_MODE,
+ PROP_SEPARATED_SCREEN,
+ PROP_SHOW_FLOOR,
+ PROP_FOVY,
+ PROP_ASPECT,
+ PROP_ZNEAR,
+ PROP_ZFAR
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_filter_reflected_screen_debug, "glfilterreflectedscreen", 0, "glfilterreflectedscreen element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterReflectedScreen,
+ gst_gl_filter_reflected_screen, GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_filter_reflected_screen_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_filter_reflected_screen_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_filter_reflected_screen_filter_texture (GstGLFilter *
+ filter, guint in_tex, guint out_tex);
+
+static void gst_gl_filter_reflected_screen_draw_background ();
+static void gst_gl_filter_reflected_screen_draw_floor ();
+static void gst_gl_filter_reflected_screen_draw_screen (GstGLFilter * filter,
+ gint width, gint height, guint texture);
+static void gst_gl_filter_reflected_screen_draw_separated_screen (GstGLFilter *
+ filter, gint width, gint height, guint texture, gfloat alphs, gfloat alphe);
+
+static void gst_gl_filter_reflected_screen_callback (gint width, gint height,
+ guint texture, gpointer stuff);
+
+static const GLfloat LightPos[] = { 4.0f, -4.0f, 6.0f, 1.0f }; // Light Position
+static const GLfloat LightAmb[] = { 4.0f, 4.0f, 4.0f, 1.0f }; // Ambient Light
+static const GLfloat LightDif[] = { 1.0f, 1.0f, 1.0f, 1.0f }; // Diffuse Light
+
+static void
+gst_gl_filter_reflected_screen_class_init (GstGLFilterReflectedScreenClass *
+ klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_filter_reflected_screen_set_property;
+ gobject_class->get_property = gst_gl_filter_reflected_screen_get_property;
+
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_filter_reflected_screen_filter_texture;
+
+ g_object_class_install_property (gobject_class, PROP_ACTIVE_GRAPHIC_MODE,
+ g_param_spec_boolean ("active-graphic-mode",
+ "Activate graphic mode",
+ "Allow user to activate stencil buffer and blending.",
+ TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SEPARATED_SCREEN,
+ g_param_spec_boolean ("separated-screen",
+ "Create a separation space",
+ "Allow to insert a space between the two screen. Will cancel 'show floor' if active. Value are TRUE or FALSE(default)",
+ FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SHOW_FLOOR,
+ g_param_spec_boolean ("show-floor",
+ "Show the support",
+ "Allow the user to show the supportive floor. Will cancel 'separated screen' if active. Value are TRUE(default) or FALSE",
+ TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_FOVY,
+ g_param_spec_double ("fovy", "Fovy", "Field of view angle in degrees",
+ 0.0, 180.0, 60, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ASPECT,
+ g_param_spec_double ("aspect", "Aspect",
+ "Field of view in the x direction", 1.0, 100, 1.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ZNEAR,
+ g_param_spec_double ("znear", "Znear",
+ "Specifies the distance from the viewer to the near clipping plane",
+ 0.0000000001, 100.0, 0.1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ZFAR,
+ g_param_spec_double ("zfar", "Zfar",
+ "Specifies the distance from the viewer to the far clipping plane",
+ 0.0, 1000.0, 100.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL Reflected Screen filter", "Filter/Effect/Video",
+ "Reflected Screen Filter", "Pierre POUZOL <pierre.pouzol@hotmail.fr>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = GST_GL_API_OPENGL;
+}
+
+static void
+gst_gl_filter_reflected_screen_init (GstGLFilterReflectedScreen * filter)
+{
+ filter->active_graphic_mode = TRUE;
+ filter->separated_screen = FALSE;
+ filter->show_floor = TRUE;
+ filter->fovy = 90;
+ filter->aspect = 1.0;
+ filter->znear = 0.1;
+ filter->zfar = 1000;
+}
+
+static void
+gst_gl_filter_reflected_screen_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterReflectedScreen *filter = GST_GL_FILTER_REFLECTED_SCREEN (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE_GRAPHIC_MODE:
+ filter->active_graphic_mode = g_value_get_boolean (value);
+ break;
+ case PROP_SEPARATED_SCREEN:
+ filter->separated_screen = g_value_get_boolean (value);
+ break;
+ case PROP_SHOW_FLOOR:
+ filter->show_floor = g_value_get_boolean (value);
+ break;
+ case PROP_FOVY:
+ filter->fovy = g_value_get_double (value);
+ break;
+ case PROP_ASPECT:
+ filter->aspect = g_value_get_double (value);
+ break;
+ case PROP_ZNEAR:
+ filter->znear = g_value_get_double (value);
+ break;
+ case PROP_ZFAR:
+ filter->zfar = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filter_reflected_screen_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterReflectedScreen *filter = GST_GL_FILTER_REFLECTED_SCREEN (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE_GRAPHIC_MODE:
+ g_value_set_boolean (value, filter->active_graphic_mode);
+ break;
+ case PROP_SEPARATED_SCREEN:
+ g_value_set_boolean (value, filter->separated_screen);
+ break;
+ case PROP_SHOW_FLOOR:
+ g_value_set_boolean (value, filter->show_floor);
+ break;
+ case PROP_FOVY:
+ g_value_set_double (value, filter->fovy);
+ break;
+ case PROP_ASPECT:
+ g_value_set_double (value, filter->aspect);
+ break;
+ case PROP_ZNEAR:
+ g_value_set_double (value, filter->znear);
+ break;
+ case PROP_ZFAR:
+ g_value_set_double (value, filter->zfar);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_filter_reflected_screen_filter_texture (GstGLFilter * filter,
+ guint in_tex, guint out_tex)
+{
+ GstGLFilterReflectedScreen *reflected_screen_filter =
+ GST_GL_FILTER_REFLECTED_SCREEN (filter);
+
+ //blocking call, use a FBO
+ gst_gl_context_use_fbo (filter->context,
+ GST_VIDEO_INFO_WIDTH (&filter->out_info),
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info),
+ filter->fbo, filter->depthbuffer, out_tex,
+ gst_gl_filter_reflected_screen_callback,
+ GST_VIDEO_INFO_WIDTH (&filter->in_info),
+ GST_VIDEO_INFO_HEIGHT (&filter->in_info), in_tex,
+ reflected_screen_filter->fovy, reflected_screen_filter->aspect,
+ reflected_screen_filter->znear, reflected_screen_filter->zfar,
+ GST_GL_DISPLAY_PROJECTION_PERSPECTIVE,
+ (gpointer) reflected_screen_filter);
+
+ return TRUE;
+}
+
+static void
+gst_gl_filter_reflected_screen_draw_separated_screen (GstGLFilter * filter,
+ gint width, gint height, guint texture, gfloat alphs, gfloat alphe)
+{
+ //enable ARB Rectangular texturing
+ //that's necessary to have the video displayed on our screen (with gstreamer)
+ glEnable (GL_TEXTURE_2D);
+ glBindTexture (GL_TEXTURE_2D, texture);
+ //configure parameters for the texturing
+ //the two first are used to specified how the texturing will be done if the screen is greater than the texture herself
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ //the next two specified how the texture will comport near the limits
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //creating screen and setting the texture (depending on texture's height and width)
+ glBegin (GL_QUADS);
+
+ // right Face
+ glColor4f (1.0f, 1.0f, 1.0f, alphs);
+ glTexCoord2f (0.5f, 1.0f);
+ glVertex3f (-0.75f, 0.0f, -1.0f);
+ glColor4f (1.0f, 1.0f, 1.0f, alphe);
+ glTexCoord2f (0.5f, 0.0f);
+ glVertex3f (-0.75f, 1.25f, -1.0f);
+ glTexCoord2f (1.0f, 0.0f);
+ glVertex3f (1.25f, 1.25f, -1.0f);
+ glColor4f (1.0f, 1.0f, 1.0f, alphs);
+ glTexCoord2f (1.0f, 1.0f);
+ glVertex3f (1.25f, 0.0f, -1.0f);
+ // Left Face
+ glColor4f (1.0f, 1.0f, 1.0f, alphs);
+ glTexCoord2f (0.5f, 1.0f);
+ glVertex3f (-1.0f, 0.0f, -0.75f);
+ glTexCoord2f (0.0f, 1.0f);
+ glVertex3f (-1.0f, 0.0f, 1.25f);
+ glColor4f (1.0f, 1.0f, 1.0f, alphe);
+ glTexCoord2f (0.0f, 0.0f);
+ glVertex3f (-1.0f, 1.25f, 1.25f);
+ glTexCoord2f (0.5f, 0.0f);
+ glVertex3f (-1.0f, 1.25f, -0.75f);
+
+ glEnd ();
+ glDisable (GL_TEXTURE_2D);
+}
+
+static void
+gst_gl_filter_reflected_screen_draw_screen (GstGLFilter * filter,
+ gint width, gint height, guint texture)
+{
+ //enable ARB Rectangular texturing
+ //that's necessary to have the video displayed on our screen (with gstreamer)
+ glEnable (GL_TEXTURE_2D);
+ glBindTexture (GL_TEXTURE_2D, texture);
+ //configure parameters for the texturing
+ //the two first are used to specified how the texturing will be done if the screen is greater than the texture herself
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ //the next two specified how the texture will comport near the limits
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //creating screen and setting the texture (depending on texture's height and width)
+ glBegin (GL_QUADS);
+
+ glTexCoord2f (0.5f, 1.0f);
+ glVertex3f (-1.0f, 0.0f, -1.0f);
+ glTexCoord2f (0.5f, 0.0f);
+ glVertex3f (-1.0f, 1.0f, -1.0f);
+ glTexCoord2f (1.0f, 0.0f);
+ glVertex3f (1.0f, 1.0f, -1.0f);
+ glTexCoord2f (1.0f, 1.0f);
+ glVertex3f (1.0f, 0.0f, -1.0f);
+ // Left Face
+ glTexCoord2f (0.5f, 1.0f);
+ glVertex3f (-1.0f, 0.0f, -1.0f);
+ glTexCoord2f (0.0f, 1.0f);
+ glVertex3f (-1.0f, 0.0f, 1.0f);
+ glTexCoord2f (0.0f, 0.0f);
+ glVertex3f (-1.0f, 1.0f, 1.0f);
+ glTexCoord2f (0.5f, 0.0f);
+ glVertex3f (-1.0f, 1.0f, -1.0f);
+
+ glEnd ();
+
+ //disable this kind of texturing (useless for the gluDisk)
+ glDisable (GL_TEXTURE_2D);
+}
+
+static void
+gst_gl_filter_reflected_screen_draw_background (void)
+{
+ glBegin (GL_QUADS);
+
+ // right Face
+
+ glColor4f (0.0f, 0.0f, 0.0f, 1.0f);
+ glVertex3f (-10.0f, -10.0f, -1.0f);
+
+ glColor4f (0.0f, 0.0f, 0.2f, 1.0f);
+ glVertex3f (-10.0f, 10.0f, -1.0f);
+ glVertex3f (10.0f, 10.0f, -1.0f);
+ glVertex3f (10.0f, -10.0f, -1.0f);
+
+ glEnd ();
+}
+
+static void
+gst_gl_filter_reflected_screen_draw_floor (void)
+{
+ GLUquadricObj *q;
+ //create a quadric for the floor's drawing
+ q = gluNewQuadric ();
+ //configure this quadric's parameter (for lighting and texturing)
+ gluQuadricNormals (q, GL_SMOOTH);
+ gluQuadricTexture (q, GL_FALSE);
+
+ //drawing the disk. The texture are mapped thanks to the parameter we gave to the GLUquadric q
+ gluDisk (q, 0.0, 2.2, 50, 1);
+}
+
+//opengl scene, params: input texture (not the output filter->texture)
+static void
+gst_gl_filter_reflected_screen_callback (gint width, gint height, guint texture,
+ gpointer stuff)
+{
+ GstGLFilter *filter = GST_GL_FILTER (stuff);
+ GstGLFilterReflectedScreen *reflected_screen_filter =
+ GST_GL_FILTER_REFLECTED_SCREEN (stuff);
+
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ //load identity befor tracing
+ glLoadIdentity ();
+ //camera translation
+ glTranslatef (0.0f, 0.1f, -1.3f);
+ //camera configuration
+ if (reflected_screen_filter->separated_screen)
+ gluLookAt (0.1, -0.25, 2.0, 0.025, 0.0, 0.0, 0.0, 1.0, 0.0);
+ else
+ gluLookAt (0.1, -0.35, 2.0, 0.025, 0.0, 0.0, 0.0, 1.0, 0.0);
+
+ gst_gl_filter_reflected_screen_draw_background ();
+
+ if (reflected_screen_filter->separated_screen) {
+ glEnable (GL_BLEND);
+
+ glPushMatrix ();
+ glScalef (1.0f, -1.0f, 1.0f);
+ glTranslatef (0.0f, 0.0f, 1.2f);
+ glRotatef (-45.0f, 0.0, 1.0, 0.0);
+ gst_gl_filter_reflected_screen_draw_separated_screen (filter, width, height,
+ texture, 1.0f, 1.0f);
+ glPopMatrix ();
+
+ if (reflected_screen_filter->active_graphic_mode) {
+ //configuration of the transparency function
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glTranslatef (0.0f, 0.0f, 1.2f);
+ glRotatef (-45.0f, 0.0, 1.0, 0.0);
+ gst_gl_filter_reflected_screen_draw_separated_screen (filter, width,
+ height, texture, 0.5f, 0.0f);
+ glDisable (GL_BLEND);
+ }
+ }
+ if (reflected_screen_filter->show_floor) {
+ glLightfv (GL_LIGHT0, GL_AMBIENT, LightAmb);
+ glLightfv (GL_LIGHT0, GL_DIFFUSE, LightDif);
+ glLightfv (GL_LIGHT0, GL_POSITION, LightPos);
+
+ //enable lighting
+ glEnable (GL_LIGHT0);
+ glEnable (GL_LIGHTING);
+
+ if (reflected_screen_filter->active_graphic_mode) {
+ glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ //enable stencil buffer use
+ glEnable (GL_STENCIL_TEST);
+ //setting the stencil buffer. Each time a pixel will be drawn by now, this pixel value will be set to 1
+ glStencilFunc (GL_ALWAYS, 1, 1);
+ glStencilOp (GL_KEEP, GL_KEEP, GL_REPLACE);
+
+ //disable the zbuffer
+ glDisable (GL_DEPTH_TEST);
+ //make a rotation of 90 degree on x axis. By default, gluDisk draw a disk on z axis
+ glRotatef (-90.0f, 1.0, 0.0, 0.0);
+ //draw the floor. Each pixel representing this floor will now have a value of 1 on stencil buffer
+ gst_gl_filter_reflected_screen_draw_floor ();
+ //make an anti-rotation of 90 degree to draw the rest of the scene on the right angle
+ glRotatef (90.0f, 1.0, 0.0, 0.0);
+ //enable zbuffer again
+ glEnable (GL_DEPTH_TEST);
+ //enable the drawing to be shown
+ glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ //say that the next object have to be drawn ONLY where the stencil buffer's pixel's value is 1
+ glStencilFunc (GL_EQUAL, 1, 1);
+ glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
+
+ //save the actual matrix
+ glPushMatrix ();
+ glLightfv (GL_LIGHT0, GL_POSITION, LightPos);
+ //translate the object on z axis
+ glTranslatef (0.0f, 0.0f, 1.4f);
+ //rotate it (because the drawing method place the user behind the left part of the screen)
+ glRotatef (-45.0f, 0.0, 1.0, 0.0);
+ //draw the reflexion
+ gst_gl_filter_reflected_screen_draw_screen (filter, width, height,
+ texture);
+ //return to the saved matrix position
+ glPopMatrix ();
+ //end of the stencil buffer uses
+ glDisable (GL_STENCIL_TEST);
+
+ //enable the blending to mix the floor and reflexion color
+ glEnable (GL_BLEND);
+ glDisable (GL_LIGHTING);
+ //specified a white color (for the floor) with 20% transparency
+ glColor4f (1.0f, 1.0f, 1.0f, 0.8f);
+ //configuration of the transparency function
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ //draw the floor (which will appear this time)
+ glRotatef (-90.0f, 1.0, 0.0, 0.0);
+ gst_gl_filter_reflected_screen_draw_floor ();
+ glRotatef (90.0f, 1.0, 0.0, 0.0);
+ glDisable (GL_BLEND);
+ glEnable (GL_LIGHTING);
+ //draw the real object
+ //scale on y axis. The object must be drawn upside down (to suggest a reflexion)
+ glScalef (1.0f, -1.0f, 1.0f);
+ glTranslatef (0.0f, 0.0f, 1.4f);
+ glRotatef (-45.0f, 0.0, 1.0, 0.0);
+ gst_gl_filter_reflected_screen_draw_screen (filter, width, height, texture);
+ glDisable (GL_LIGHTING);
+ }
+}
diff --git a/ext/gl/gstglfilterreflectedscreen.h b/ext/gl/gstglfilterreflectedscreen.h
new file mode 100644
index 000000000..a739689e4
--- /dev/null
+++ b/ext/gl/gstglfilterreflectedscreen.h
@@ -0,0 +1,61 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Pierre Pouzol<pierre.pouzol@hotmail.fr>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTERREFLECTEDSCREEN_H_
+#define _GST_GL_FILTERREFLECTEDSCREEN_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_FILTER_REFLECTED_SCREEN (gst_gl_filter_reflected_screen_get_type())
+#define GST_GL_FILTER_REFLECTED_SCREEN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTER_REFLECTED_SCREEN,GstGLFilterReflectedScreen))
+#define GST_IS_GL_FILTER_REFLECTED_SCREEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTER_REFLECTED_SCREEN))
+#define GST_GL_FILTER_REFLECTED_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTER_REFLECTED_SCREEN,GstGLFilterReflectedScreenClass))
+#define GST_IS_GL_FILTER_REFLECTED_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTER_REFLECTED_SCREEN))
+#define GST_GL_FILTER_REFLECTED_SCREEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTER_REFLECTED_SCREEN,GstGLFilterReflectedScreenClass))
+
+typedef struct _GstGLFilterReflectedScreen GstGLFilterReflectedScreen;
+typedef struct _GstGLFilterReflectedScreenClass GstGLFilterReflectedScreenClass;
+
+struct _GstGLFilterReflectedScreen
+{
+ GstGLFilter filter;
+ gdouble fovy;
+ gdouble aspect;
+ gdouble znear;
+ gdouble zfar;
+
+ gboolean active_graphic_mode;
+ gboolean separated_screen;
+ gboolean show_floor;
+};
+
+struct _GstGLFilterReflectedScreenClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_filter_reflected_screen_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERREFLECTEDSCREEN_H_ */
+
diff --git a/ext/gl/gstglfiltershader.c b/ext/gl/gstglfiltershader.c
new file mode 100644
index 000000000..df5040fea
--- /dev/null
+++ b/ext/gl/gstglfiltershader.c
@@ -0,0 +1,556 @@
+/*
+ * glshader gstreamer plugin
+ * Copyrithg (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ * Copyright (C) 2009 Luc Deschenaux <luc.deschenaux@freesurf.ch>
+ *
+ * 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:element-glshader
+ * @title: glshader
+ *
+ * OpenGL fragment shader filter
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! glshader fragment="\"`cat myshader.frag`\"" ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ * Depending on the exact OpenGL version chosen and the exact requirements of
+ * the OpenGL implementation, a #version header may be required.
+ *
+ * The following is a simple OpenGL ES (also usable with OpenGL 3 core contexts)
+ * passthrough shader with the required inputs.
+ * |[
+ * #version 100
+ * #ifdef GL_ES
+ * precision mediump float;
+ * #endif
+ * varying vec2 v_texcoord;
+ * uniform sampler2D tex;
+ * uniform float time;
+ * uniform float width;
+ * uniform float height;
+ *
+ * void main () {
+ * gl_FragColor = texture2D( tex, v_texcoord );
+ * }
+ * ]|
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglfuncs.h>
+
+#include "gstglfiltershader.h"
+#if HAVE_GRAPHENE
+#include <graphene-gobject.h>
+#endif
+
+enum
+{
+ PROP_0,
+ PROP_SHADER,
+ PROP_VERTEX,
+ PROP_FRAGMENT,
+ PROP_UNIFORMS,
+ PROP_UPDATE_SHADER,
+ PROP_LAST,
+};
+
+enum
+{
+ SIGNAL_0,
+ SIGNAL_CREATE_SHADER,
+ SIGNAL_LAST,
+};
+
+static guint gst_gl_shader_signals[SIGNAL_LAST] = { 0 };
+
+#define GST_CAT_DEFAULT gst_gl_filtershader_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_filtershader_debug, "glshader", 0, "glshader element");
+#define gst_gl_filtershader_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLFilterShader, gst_gl_filtershader,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_filtershader_finalize (GObject * object);
+static void gst_gl_filtershader_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_filtershader_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static gboolean gst_gl_filtershader_gl_start (GstGLBaseFilter * base);
+static void gst_gl_filtershader_gl_stop (GstGLBaseFilter * base);
+static gboolean gst_gl_filtershader_filter (GstGLFilter * filter,
+ GstBuffer * inbuf, GstBuffer * outbuf);
+static gboolean gst_gl_filtershader_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+static gboolean gst_gl_filtershader_hcallback (GstGLFilter * filter,
+ GstGLMemory * in_tex, gpointer stuff);
+
+static void
+gst_gl_filtershader_class_init (GstGLFilterShaderClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->finalize = gst_gl_filtershader_finalize;
+ gobject_class->set_property = gst_gl_filtershader_set_property;
+ gobject_class->get_property = gst_gl_filtershader_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_SHADER,
+ g_param_spec_object ("shader", "Shader object",
+ "GstGLShader to use", GST_TYPE_GL_SHADER,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_VERTEX,
+ g_param_spec_string ("vertex", "Vertex Source",
+ "GLSL vertex source", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_FRAGMENT,
+ g_param_spec_string ("fragment", "Fragment Source",
+ "GLSL fragment source", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /* FIXME: add other stages */
+
+ g_object_class_install_property (gobject_class, PROP_UNIFORMS,
+ g_param_spec_boxed ("uniforms", "GLSL Uniforms",
+ "GLSL Uniforms", GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_UPDATE_SHADER,
+ g_param_spec_boolean ("update-shader", "Update Shader",
+ "Emit the \'create-shader\' signal for the next frame",
+ FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+ /*
+ * GstGLFilterShader::create-shader:
+ * @object: the #GstGLFilterShader
+ *
+ * Ask's the application for a shader to render with as a result of
+ * inititialization or setting the 'update-shader' property.
+ *
+ * Returns: a new #GstGLShader for use in the rendering pipeline
+ */
+ gst_gl_shader_signals[SIGNAL_CREATE_SHADER] =
+ g_signal_new ("create-shader", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ GST_TYPE_GL_SHADER, 0);
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL fragment shader filter", "Filter/Effect",
+ "Perform operations with a GLSL shader", "<matthew@centricular.com>");
+
+ GST_GL_FILTER_CLASS (klass)->filter = gst_gl_filtershader_filter;
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_filtershader_filter_texture;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_filtershader_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_filtershader_gl_stop;
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_filtershader_init (GstGLFilterShader * filtershader)
+{
+ filtershader->new_source = TRUE;
+}
+
+static void
+gst_gl_filtershader_finalize (GObject * object)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (object);
+
+ g_free (filtershader->vertex);
+ filtershader->vertex = NULL;
+
+ g_free (filtershader->fragment);
+ filtershader->fragment = NULL;
+
+ if (filtershader->uniforms)
+ gst_structure_free (filtershader->uniforms);
+ filtershader->uniforms = NULL;
+
+ G_OBJECT_CLASS (gst_gl_filtershader_parent_class)->finalize (object);
+}
+
+static void
+gst_gl_filtershader_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (object);
+
+ switch (prop_id) {
+ case PROP_SHADER:
+ GST_OBJECT_LOCK (filtershader);
+ gst_object_replace ((GstObject **) & filtershader->shader,
+ g_value_dup_object (value));
+ filtershader->new_source = FALSE;
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_VERTEX:
+ GST_OBJECT_LOCK (filtershader);
+ g_free (filtershader->vertex);
+ filtershader->vertex = g_value_dup_string (value);
+ filtershader->new_source = TRUE;
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_FRAGMENT:
+ GST_OBJECT_LOCK (filtershader);
+ g_free (filtershader->fragment);
+ filtershader->fragment = g_value_dup_string (value);
+ filtershader->new_source = TRUE;
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_UNIFORMS:
+ GST_OBJECT_LOCK (filtershader);
+ if (filtershader->uniforms)
+ gst_structure_free (filtershader->uniforms);
+ filtershader->uniforms = g_value_dup_boxed (value);
+ filtershader->new_uniforms = TRUE;
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_UPDATE_SHADER:
+ GST_OBJECT_LOCK (filtershader);
+ filtershader->update_shader = g_value_get_boolean (value);
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filtershader_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (object);
+
+ switch (prop_id) {
+ case PROP_SHADER:
+ GST_OBJECT_LOCK (filtershader);
+ g_value_set_object (value, filtershader->shader);
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_VERTEX:
+ GST_OBJECT_LOCK (filtershader);
+ g_value_set_string (value, filtershader->vertex);
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_FRAGMENT:
+ GST_OBJECT_LOCK (filtershader);
+ g_value_set_string (value, filtershader->fragment);
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ case PROP_UNIFORMS:
+ GST_OBJECT_LOCK (filtershader);
+ g_value_set_boxed (value, filtershader->uniforms);
+ GST_OBJECT_UNLOCK (filtershader);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_filtershader_gl_stop (GstGLBaseFilter * base)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (base);
+
+ if (filtershader->shader)
+ gst_object_unref (filtershader->shader);
+ filtershader->shader = NULL;
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base);
+}
+
+static gboolean
+gst_gl_filtershader_gl_start (GstGLBaseFilter * base)
+{
+ return GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base);
+}
+
+static inline gboolean
+_gst_clock_time_to_double (GstClockTime time, gdouble * result)
+{
+ if (!GST_CLOCK_TIME_IS_VALID (time))
+ return FALSE;
+
+ *result = (gdouble) time / GST_SECOND;
+
+ return TRUE;
+}
+
+static inline gboolean
+_gint64_time_val_to_double (gint64 time, gdouble * result)
+{
+ if (time == -1)
+ return FALSE;
+
+ *result = (gdouble) time / GST_USECOND;
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_filtershader_filter (GstGLFilter * filter, GstBuffer * inbuf,
+ GstBuffer * outbuf)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
+
+ if (!_gst_clock_time_to_double (GST_BUFFER_PTS (inbuf), &filtershader->time)) {
+ if (!_gst_clock_time_to_double (GST_BUFFER_DTS (inbuf),
+ &filtershader->time))
+ _gint64_time_val_to_double (g_get_monotonic_time (), &filtershader->time);
+ }
+
+ return gst_gl_filter_filter_texture (filter, inbuf, outbuf);
+}
+
+static gboolean
+gst_gl_filtershader_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
+
+ gst_gl_filter_render_to_target (filter, in_tex, out_tex,
+ gst_gl_filtershader_hcallback, NULL);
+
+ if (!filtershader->shader)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+_set_uniform (GQuark field_id, const GValue * value, gpointer user_data)
+{
+ GstGLShader *shader = user_data;
+ const gchar *field_name = g_quark_to_string (field_id);
+
+ if (G_TYPE_CHECK_VALUE_TYPE ((value), G_TYPE_INT)) {
+ gst_gl_shader_set_uniform_1i (shader, field_name, g_value_get_int (value));
+ } else if (G_TYPE_CHECK_VALUE_TYPE ((value), G_TYPE_FLOAT)) {
+ gst_gl_shader_set_uniform_1f (shader, field_name,
+ g_value_get_float (value));
+#if HAVE_GRAPHENE
+ } else if (G_TYPE_CHECK_VALUE_TYPE ((value), GRAPHENE_TYPE_VEC2)) {
+ graphene_vec2_t *vec2 = g_value_get_boxed (value);
+ float x = graphene_vec2_get_x (vec2);
+ float y = graphene_vec2_get_y (vec2);
+ gst_gl_shader_set_uniform_2f (shader, field_name, x, y);
+ } else if (G_TYPE_CHECK_VALUE_TYPE ((value), GRAPHENE_TYPE_VEC3)) {
+ graphene_vec3_t *vec3 = g_value_get_boxed (value);
+ float x = graphene_vec3_get_x (vec3);
+ float y = graphene_vec3_get_y (vec3);
+ float z = graphene_vec3_get_z (vec3);
+ gst_gl_shader_set_uniform_3f (shader, field_name, x, y, z);
+ } else if (G_TYPE_CHECK_VALUE_TYPE ((value), GRAPHENE_TYPE_VEC4)) {
+ graphene_vec4_t *vec4 = g_value_get_boxed (value);
+ float x = graphene_vec4_get_x (vec4);
+ float y = graphene_vec4_get_y (vec4);
+ float z = graphene_vec4_get_z (vec4);
+ float w = graphene_vec4_get_w (vec4);
+ gst_gl_shader_set_uniform_4f (shader, field_name, x, y, z, w);
+ } else if (G_TYPE_CHECK_VALUE_TYPE ((value), GRAPHENE_TYPE_MATRIX)) {
+ graphene_matrix_t *matrix = g_value_get_boxed (value);
+ float matrix_f[16];
+ graphene_matrix_to_float (matrix, matrix_f);
+ gst_gl_shader_set_uniform_matrix_4fv (shader, field_name, 1, FALSE,
+ matrix_f);
+#endif
+ } else {
+ /* FIXME: Add support for unsigned ints, non 4x4 matrices, etc */
+ GST_FIXME ("Don't know how to set the \'%s\' paramater. Unknown type",
+ field_name);
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static void
+_update_uniforms (GstGLFilterShader * filtershader)
+{
+ if (filtershader->new_uniforms && filtershader->uniforms) {
+ gst_gl_shader_use (filtershader->shader);
+
+ gst_structure_foreach (filtershader->uniforms,
+ (GstStructureForeachFunc) _set_uniform, filtershader->shader);
+ filtershader->new_uniforms = FALSE;
+ }
+}
+
+static GstGLShader *
+_maybe_recompile_shader (GstGLFilterShader * filtershader)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (filtershader)->context;
+ GstGLShader *shader;
+ GError *error = NULL;
+
+ GST_OBJECT_LOCK (filtershader);
+
+ if (!filtershader->shader || filtershader->update_shader) {
+ filtershader->update_shader = FALSE;
+ GST_OBJECT_UNLOCK (filtershader);
+ g_signal_emit (filtershader, gst_gl_shader_signals[SIGNAL_CREATE_SHADER], 0,
+ &shader);
+ GST_OBJECT_LOCK (filtershader);
+
+ if (shader) {
+ if (filtershader->shader)
+ gst_object_unref (filtershader->shader);
+ filtershader->new_source = FALSE;
+ filtershader->shader = gst_object_ref (shader);
+ filtershader->new_uniforms = TRUE;
+ _update_uniforms (filtershader);
+ GST_OBJECT_UNLOCK (filtershader);
+ return shader;
+ }
+ }
+
+ if (filtershader->shader) {
+ shader = gst_object_ref (filtershader->shader);
+ _update_uniforms (filtershader);
+ GST_OBJECT_UNLOCK (filtershader);
+ return shader;
+ }
+
+ if (filtershader->new_source) {
+ GstGLSLStage *stage;
+
+ shader = gst_gl_shader_new (context);
+
+ if (filtershader->vertex) {
+ if (!(stage = gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE, GST_GLSL_PROFILE_NONE,
+ filtershader->vertex))) {
+ g_set_error (&error, GST_GLSL_ERROR, GST_GLSL_ERROR_COMPILE,
+ "Failed to create shader vertex stage");
+ goto print_error;
+ }
+ } else {
+ stage = gst_glsl_stage_new_default_vertex (context);
+ }
+
+ if (!gst_gl_shader_compile_attach_stage (shader, stage, &error)) {
+ gst_object_unref (stage);
+ goto print_error;
+ }
+
+ if (filtershader->fragment) {
+ if (!(stage = gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE, GST_GLSL_PROFILE_NONE,
+ filtershader->fragment))) {
+ g_set_error (&error, GST_GLSL_ERROR, GST_GLSL_ERROR_COMPILE,
+ "Failed to create shader fragment stage");
+ goto print_error;
+ }
+ } else {
+ stage = gst_glsl_stage_new_default_fragment (context);
+ }
+
+ if (!gst_gl_shader_compile_attach_stage (shader, stage, &error)) {
+ gst_object_unref (stage);
+ goto print_error;
+ }
+
+ if (!gst_gl_shader_link (shader, &error)) {
+ goto print_error;
+ }
+ if (filtershader->shader)
+ gst_object_unref (filtershader->shader);
+ filtershader->shader = gst_object_ref (shader);
+ filtershader->new_source = FALSE;
+ filtershader->new_uniforms = TRUE;
+ _update_uniforms (filtershader);
+
+ GST_OBJECT_UNLOCK (filtershader);
+ return shader;
+ } else if (filtershader->shader) {
+ _update_uniforms (filtershader);
+ shader = gst_object_ref (filtershader->shader);
+ GST_OBJECT_UNLOCK (filtershader);
+ return shader;
+ }
+
+ return NULL;
+
+print_error:
+ if (shader) {
+ gst_object_unref (shader);
+ shader = NULL;
+ }
+
+ GST_OBJECT_UNLOCK (filtershader);
+ GST_ELEMENT_ERROR (filtershader, RESOURCE, NOT_FOUND,
+ ("%s", error->message), (NULL));
+ return NULL;
+}
+
+static gboolean
+gst_gl_filtershader_hcallback (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+ GstGLShader *shader;
+
+ if (!(shader = _maybe_recompile_shader (filtershader)))
+ return FALSE;
+
+ gl->ClearColor (0.0, 0.0, 0.0, 1.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+
+ gst_gl_shader_use (shader);
+
+ /* FIXME: propertise these */
+ gst_gl_shader_set_uniform_1i (shader, "tex", 0);
+ gst_gl_shader_set_uniform_1f (shader, "width",
+ GST_VIDEO_INFO_WIDTH (&filter->out_info));
+ gst_gl_shader_set_uniform_1f (shader, "height",
+ GST_VIDEO_INFO_HEIGHT (&filter->out_info));
+ gst_gl_shader_set_uniform_1f (shader, "time", filtershader->time);
+
+ /* FIXME: propertise these */
+ filter->draw_attr_position_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_position");
+ filter->draw_attr_texture_loc =
+ gst_gl_shader_get_attribute_location (shader, "a_texcoord");
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ gst_object_unref (shader);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglfiltershader.h b/ext/gl/gstglfiltershader.h
new file mode 100644
index 000000000..adf14109c
--- /dev/null
+++ b/ext/gl/gstglfiltershader.h
@@ -0,0 +1,63 @@
+/*
+ * glshader gstreamer plugin
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ * Copyright (C) 2009 Luc Deschenaux <luc.deschenaux@freesurf.ch>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_FILTERSHADER_H_
+#define _GST_GL_FILTERSHADER_H_
+
+#include <gst/gl/gstglfilter.h>
+
+#define GST_TYPE_GL_FILTERSHADER (gst_gl_filtershader_get_type())
+#define GST_GL_FILTERSHADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_FILTERSHADER,GstGLFilterShader))
+#define GST_IS_GL_FILTERSHADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_FILTERSHADER))
+#define GST_GL_FILTERSHADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_FILTERSHADER,GstGLFilterShaderClass))
+#define GST_IS_GL_FILTERSHADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_FILTERSHADER))
+#define GST_GL_FILTERSHADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_FILTERSHADER,GstGLFilterShaderClass))
+
+typedef struct _GstGLFilterShader GstGLFilterShader;
+typedef struct _GstGLFilterShaderClass GstGLFilterShaderClass;
+
+struct _GstGLFilterShader
+{
+ GstGLFilter filter;
+
+ /* properties */
+ GstGLShader *shader;
+ gchar *vertex;
+ gchar *fragment;
+ gboolean update_shader; /* update the shader on the next draw */
+ GstStructure *uniforms;
+
+ gboolean new_source;
+ gboolean new_uniforms;
+ gdouble time;
+
+ gint attr_position_loc;
+ gint attr_texture_loc;
+};
+
+struct _GstGLFilterShaderClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_filtershader_get_type (void);
+
+#endif /* _GST_GL_FILTERSHADER_H_ */
diff --git a/ext/gl/gstglimagesink.c b/ext/gl/gstglimagesink.c
new file mode 100644
index 000000000..46bfaec24
--- /dev/null
+++ b/ext/gl/gstglimagesink.c
@@ -0,0 +1,2381 @@
+/*
+ * GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005,2006,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-glimagesink
+ * @title: glimagesink
+ *
+ * glimagesink renders video frames to a drawable on a local or remote
+ * display using OpenGL. This element can receive a Window ID from the
+ * application through the VideoOverlay interface and will then render video
+ * frames in this drawable.
+ * If no Window ID was provided by the application, the element will
+ * create its own internal window and render into it.
+ *
+ * See the #GstGLDisplay documentation for a list of environment variables that
+ * can override window/platform detection.
+ *
+ * ## Scaling
+ *
+ * Depends on the driver, OpenGL handles hardware accelerated
+ * scaling of video frames. This means that the element will just accept
+ * incoming video frames no matter their geometry and will then put them to the
+ * drawable scaling them on the fly. Using the #GstGLImageSink:force-aspect-ratio
+ * property it is possible to enforce scaling with a constant aspect ratio,
+ * which means drawing black borders around the video frame.
+ *
+ * ## Events
+ *
+ * Through the gl thread, glimagesink handle some events coming from the drawable
+ * to manage its appearance even when the data is not flowing (GST_STATE_PAUSED).
+ * That means that even when the element is paused, it will receive expose events
+ * from the drawable and draw the latest frame with correct borders/aspect-ratio.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! video/x-raw ! glimagesink
+ * ]| A pipeline to test hardware scaling.
+ * No special opengl extension is used in this pipeline, that's why it should work
+ * with OpenGL >= 1.1. That's the case if you are using the MESA3D driver v1.3.
+ * |[
+ * gst-launch-1.0 -v videotestsrc ! video/x-raw,format=I420 ! glimagesink
+ * ]| A pipeline to test hardware scaling and hardware colorspace conversion.
+ * When your driver supports GLSL (OpenGL Shading Language needs OpenGL >= 2.1),
+ * the 4 following format YUY2, UYVY, I420, YV12 and AYUV are converted to RGB32
+ * through some fragment shaders and using one framebuffer (FBO extension OpenGL >= 1.4).
+ * If your driver does not support GLSL but supports MESA_YCbCr extension then
+ * the you can use YUY2 and UYVY. In this case the colorspace conversion is automatically
+ * made when loading the texture and therefore no framebuffer is used.
+ * |[
+ * gst-launch-1.0 -v gltestsrc ! glimagesink
+ * ]| A pipeline 100% OpenGL.
+ * No special opengl extension is used in this pipeline, that's why it should work
+ * with OpenGL >= 1.1. That's the case if you are using the MESA3D driver v1.3.
+ * |[
+ * gst-plugins-bas/tests/examples/gl/generic/cube
+ * ]| The graphic FPS scene can be greater than the input video FPS.
+ * The graphic scene can be written from a client code through the
+ * two glfilterapp properties.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/video/videooverlay.h>
+#include <gst/video/navigation.h>
+#include <gst/video/gstvideoaffinetransformationmeta.h>
+
+#include "gstglimagesink.h"
+#include "gstglsinkbin.h"
+#include "gstglutils.h"
+
+#include <gst/gl/gstglviewconvert.h>
+
+GST_DEBUG_CATEGORY (gst_debug_glimage_sink);
+#define GST_CAT_DEFAULT gst_debug_glimage_sink
+
+#define DEFAULT_SHOW_PREROLL_FRAME TRUE
+#define DEFAULT_HANDLE_EVENTS TRUE
+#define DEFAULT_FORCE_ASPECT_RATIO TRUE
+#define DEFAULT_IGNORE_ALPHA TRUE
+
+#define DEFAULT_MULTIVIEW_MODE GST_VIDEO_MULTIVIEW_MODE_MONO
+#define DEFAULT_MULTIVIEW_FLAGS GST_VIDEO_MULTIVIEW_FLAGS_NONE
+#define DEFAULT_MULTIVIEW_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
+
+typedef GstGLSinkBin GstGLImageSinkBin;
+typedef GstGLSinkBinClass GstGLImageSinkBinClass;
+
+G_DEFINE_TYPE (GstGLImageSinkBin, gst_gl_image_sink_bin, GST_TYPE_GL_SINK_BIN);
+
+enum
+{
+ PROP_BIN_0,
+ PROP_BIN_ROTATE_METHOD,
+ PROP_BIN_FORCE_ASPECT_RATIO,
+ PROP_BIN_PIXEL_ASPECT_RATIO,
+ PROP_BIN_HANDLE_EVENTS,
+ PROP_BIN_CONTEXT,
+ PROP_BIN_IGNORE_ALPHA,
+ PROP_BIN_SHOW_PREROLL_FRAME,
+ PROP_BIN_OUTPUT_MULTIVIEW_LAYOUT,
+ PROP_BIN_OUTPUT_MULTIVIEW_FLAGS,
+ PROP_BIN_OUTPUT_MULTIVIEW_DOWNMIX_MODE
+};
+
+enum
+{
+ SIGNAL_BIN_0,
+ SIGNAL_BIN_CLIENT_DRAW,
+ SIGNAL_BIN_CLIENT_RESHAPE,
+ SIGNAL_BIN_LAST,
+};
+
+static guint gst_gl_image_sink_bin_signals[SIGNAL_BIN_LAST] = { 0 };
+
+static void
+gst_gl_image_sink_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec)
+{
+ g_object_set_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
+ param_spec->name, value);
+}
+
+static void
+gst_gl_image_sink_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec)
+{
+ g_object_get_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
+ param_spec->name, value);
+}
+
+static gboolean
+_on_client_reshape (GstGLImageSink * sink, GstGLContext * context,
+ guint width, guint height, gpointer data)
+{
+ gboolean ret;
+
+ g_signal_emit (data, gst_gl_image_sink_bin_signals[SIGNAL_BIN_CLIENT_RESHAPE],
+ 0, context, width, height, &ret);
+
+ return ret;
+}
+
+static gboolean
+_on_client_draw (GstGLImageSink * sink, GstGLContext * context,
+ GstSample * sample, gpointer data)
+{
+ gboolean ret;
+
+ g_signal_emit (data, gst_gl_image_sink_bin_signals[SIGNAL_BIN_CLIENT_DRAW], 0,
+ context, sample, &ret);
+
+ return ret;
+}
+
+#define DEFAULT_ROTATE_METHOD GST_GL_ROTATE_METHOD_IDENTITY
+
+#define GST_TYPE_GL_ROTATE_METHOD (gst_gl_rotate_method_get_type())
+
+static const GEnumValue rotate_methods[] = {
+ {GST_GL_ROTATE_METHOD_IDENTITY, "Identity (no rotation)", "none"},
+ {GST_GL_ROTATE_METHOD_90R, "Rotate clockwise 90 degrees", "clockwise"},
+ {GST_GL_ROTATE_METHOD_180, "Rotate 180 degrees", "rotate-180"},
+ {GST_GL_ROTATE_METHOD_90L, "Rotate counter-clockwise 90 degrees",
+ "counterclockwise"},
+ {GST_GL_ROTATE_METHOD_FLIP_HORIZ, "Flip horizontally", "horizontal-flip"},
+ {GST_GL_ROTATE_METHOD_FLIP_VERT, "Flip vertically", "vertical-flip"},
+ {GST_GL_ROTATE_METHOD_FLIP_UL_LR,
+ "Flip across upper left/lower right diagonal", "upper-left-diagonal"},
+ {GST_GL_ROTATE_METHOD_FLIP_UR_LL,
+ "Flip across upper right/lower left diagonal", "upper-right-diagonal"},
+ {GST_GL_ROTATE_METHOD_AUTO,
+ "Select rotate method based on image-orientation tag", "automatic"},
+ {0, NULL, NULL},
+};
+
+static GType
+gst_gl_rotate_method_get_type (void)
+{
+ static GType rotate_method_type = 0;
+
+ if (!rotate_method_type) {
+ rotate_method_type = g_enum_register_static ("GstGLRotateMethod",
+ rotate_methods);
+ }
+ return rotate_method_type;
+}
+
+static void
+gst_gl_image_sink_bin_init (GstGLImageSinkBin * self)
+{
+ GstGLImageSink *sink = g_object_new (GST_TYPE_GLIMAGE_SINK, NULL);
+
+ g_signal_connect (sink, "client-reshape", (GCallback) _on_client_reshape,
+ self);
+ g_signal_connect (sink, "client-draw", (GCallback) _on_client_draw, self);
+
+ gst_gl_sink_bin_finish_init_with_element (GST_GL_SINK_BIN (self),
+ GST_ELEMENT (sink));
+}
+
+static void
+gst_gl_image_sink_bin_class_init (GstGLImageSinkBinClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_gl_image_sink_bin_get_property;
+ gobject_class->set_property = gst_gl_image_sink_bin_set_property;
+
+ /* gl sink */
+ g_object_class_install_property (gobject_class, PROP_BIN_ROTATE_METHOD,
+ g_param_spec_enum ("rotate-method",
+ "rotate method",
+ "rotate method",
+ GST_TYPE_GL_ROTATE_METHOD, DEFAULT_ROTATE_METHOD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BIN_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio",
+ DEFAULT_FORCE_ASPECT_RATIO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BIN_HANDLE_EVENTS,
+ g_param_spec_boolean ("handle-events", "Handle XEvents",
+ "When enabled, XEvents will be selected and handled",
+ DEFAULT_HANDLE_EVENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BIN_IGNORE_ALPHA,
+ g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
+ "When enabled, alpha will be ignored and converted to black",
+ DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BIN_CONTEXT,
+ g_param_spec_object ("context", "OpenGL context", "Get OpenGL context",
+ GST_TYPE_GL_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BIN_PIXEL_ASPECT_RATIO,
+ gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+ "The pixel aspect ratio of the device", 0, 1, G_MAXINT, 1, 1, 1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* video sink */
+ g_object_class_install_property (gobject_class, PROP_BIN_SHOW_PREROLL_FRAME,
+ g_param_spec_boolean ("show-preroll-frame", "Show preroll frame",
+ "Whether to render video frames during preroll",
+ DEFAULT_SHOW_PREROLL_FRAME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_BIN_OUTPUT_MULTIVIEW_LAYOUT,
+ g_param_spec_enum ("output-multiview-mode", "Output Multiview Mode",
+ "Choose output mode for multiview/3D video",
+ GST_TYPE_VIDEO_MULTIVIEW_MODE, DEFAULT_MULTIVIEW_MODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_BIN_OUTPUT_MULTIVIEW_FLAGS,
+ g_param_spec_flags ("output-multiview-flags", "Output Multiview Flags",
+ "Output multiview layout modifier flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, DEFAULT_MULTIVIEW_FLAGS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_BIN_OUTPUT_MULTIVIEW_DOWNMIX_MODE,
+ g_param_spec_enum ("output-multiview-downmix-mode",
+ "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_MULTIVIEW_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_gl_image_sink_bin_signals[SIGNAL_BIN_CLIENT_DRAW] =
+ g_signal_new ("client-draw", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2,
+ GST_TYPE_GL_CONTEXT, GST_TYPE_SAMPLE);
+
+ gst_gl_image_sink_bin_signals[SIGNAL_BIN_CLIENT_RESHAPE] =
+ g_signal_new ("client-reshape", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 3, GST_TYPE_GL_CONTEXT, G_TYPE_UINT, G_TYPE_UINT);
+}
+
+#define GST_GLIMAGE_SINK_GET_LOCK(glsink) \
+ (GST_GLIMAGE_SINK(glsink)->drawing_lock)
+#define GST_GLIMAGE_SINK_LOCK(glsink) \
+ (g_mutex_lock(&GST_GLIMAGE_SINK_GET_LOCK (glsink)))
+#define GST_GLIMAGE_SINK_UNLOCK(glsink) \
+ (g_mutex_unlock(&GST_GLIMAGE_SINK_GET_LOCK (glsink)))
+
+#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
+#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
+#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
+#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))
+
+#define SUPPORTED_GL_APIS GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3
+
+static void gst_glimage_sink_thread_init_redisplay (GstGLImageSink * gl_sink);
+static void gst_glimage_sink_cleanup_glthread (GstGLImageSink * gl_sink);
+static void gst_glimage_sink_on_close (GstGLImageSink * gl_sink);
+static void gst_glimage_sink_on_resize (GstGLImageSink * gl_sink,
+ gint width, gint height);
+static void gst_glimage_sink_on_draw (GstGLImageSink * gl_sink);
+static gboolean gst_glimage_sink_redisplay (GstGLImageSink * gl_sink);
+
+static void gst_glimage_sink_finalize (GObject * object);
+static void gst_glimage_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_glimage_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static gboolean gst_glimage_sink_event (GstBaseSink * sink, GstEvent * event);
+static gboolean gst_glimage_sink_query (GstBaseSink * bsink, GstQuery * query);
+static void gst_glimage_sink_set_context (GstElement * element,
+ GstContext * context);
+
+static GstStateChangeReturn
+gst_glimage_sink_change_state (GstElement * element, GstStateChange transition);
+
+static void gst_glimage_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end);
+static gboolean gst_glimage_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
+static GstCaps *gst_glimage_sink_get_caps (GstBaseSink * bsink,
+ GstCaps * filter);
+static GstFlowReturn gst_glimage_sink_prepare (GstBaseSink * bsink,
+ GstBuffer * buf);
+static GstFlowReturn gst_glimage_sink_show_frame (GstVideoSink * bsink,
+ GstBuffer * buf);
+static gboolean gst_glimage_sink_propose_allocation (GstBaseSink * bsink,
+ GstQuery * query);
+
+static void gst_glimage_sink_video_overlay_init (GstVideoOverlayInterface *
+ iface);
+static void gst_glimage_sink_set_window_handle (GstVideoOverlay * overlay,
+ guintptr id);
+static void gst_glimage_sink_expose (GstVideoOverlay * overlay);
+static void gst_glimage_sink_set_render_rectangle (GstVideoOverlay * overlay,
+ gint x, gint y, gint width, gint height);
+static void gst_glimage_sink_handle_events (GstVideoOverlay * overlay,
+ gboolean handle_events);
+static gboolean update_output_format (GstGLImageSink * glimage_sink);
+
+#define GST_GL_SINK_CAPS \
+ "video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " \
+ "format = (string) RGBA, " \
+ "width = " GST_VIDEO_SIZE_RANGE ", " \
+ "height = " GST_VIDEO_SIZE_RANGE ", " \
+ "framerate = " GST_VIDEO_FPS_RANGE ", " \
+ "texture-target = (string) { 2D, external-oes } " \
+ " ; " \
+ "video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "," \
+ GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION "), " \
+ "format = (string) RGBA, " \
+ "width = " GST_VIDEO_SIZE_RANGE ", " \
+ "height = " GST_VIDEO_SIZE_RANGE ", " \
+ "framerate = " GST_VIDEO_FPS_RANGE ", " \
+ "texture-target = (string) { 2D, external-oes } "
+
+static GstStaticPadTemplate gst_glimage_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_GL_SINK_CAPS));
+
+enum
+{
+ ARG_0,
+ ARG_DISPLAY,
+ PROP_ROTATE_METHOD,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+ PROP_CONTEXT,
+ PROP_HANDLE_EVENTS,
+ PROP_IGNORE_ALPHA,
+ PROP_OUTPUT_MULTIVIEW_LAYOUT,
+ PROP_OUTPUT_MULTIVIEW_FLAGS,
+ PROP_OUTPUT_MULTIVIEW_DOWNMIX_MODE
+};
+
+enum
+{
+ SIGNAL_0,
+ CLIENT_DRAW_SIGNAL,
+ CLIENT_RESHAPE_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint gst_glimage_sink_signals[LAST_SIGNAL] = { 0 };
+
+static void
+_display_size_to_stream_size (GstGLImageSink * gl_sink, gdouble x,
+ gdouble y, gdouble * stream_x, gdouble * stream_y)
+{
+ gdouble stream_width, stream_height;
+
+ stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&gl_sink->out_info);
+ stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&gl_sink->out_info);
+
+ /* from display coordinates to stream coordinates */
+ if (gl_sink->display_rect.w > 0)
+ *stream_x =
+ (x - gl_sink->display_rect.x) / gl_sink->display_rect.w * stream_width;
+ else
+ *stream_x = 0.;
+
+ /* clip to stream size */
+ if (*stream_x < 0.)
+ *stream_x = 0.;
+ if (*stream_x > GST_VIDEO_INFO_WIDTH (&gl_sink->out_info))
+ *stream_x = GST_VIDEO_INFO_WIDTH (&gl_sink->out_info);
+
+ /* same for y-axis */
+ if (gl_sink->display_rect.h > 0)
+ *stream_y =
+ (y - gl_sink->display_rect.y) / gl_sink->display_rect.h * stream_height;
+ else
+ *stream_y = 0.;
+
+ if (*stream_y < 0.)
+ *stream_y = 0.;
+ if (*stream_y > GST_VIDEO_INFO_HEIGHT (&gl_sink->out_info))
+ *stream_y = GST_VIDEO_INFO_HEIGHT (&gl_sink->out_info);
+
+ GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
+}
+
+/* rotate 90 */
+static const gfloat clockwise_matrix[] = {
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* rotate 180 */
+static const gfloat clockwise_180_matrix[] = {
+ -1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* rotate 270 */
+static const gfloat counterclockwise_matrix[] = {
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* horizontal-flip */
+static const gfloat horizontal_flip_matrix[] = {
+ -1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* vertical-flip */
+static const gfloat vertical_flip_matrix[] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* upper-left-diagonal */
+static const gfloat upper_left_matrix[] = {
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* upper-right-diagonal */
+static const gfloat upper_right_matrix[] = {
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+static void
+gst_glimage_sink_set_rotate_method (GstGLImageSink * gl_sink,
+ GstGLRotateMethod method, gboolean from_tag)
+{
+ GstGLRotateMethod tag_method = DEFAULT_ROTATE_METHOD;
+ GST_GLIMAGE_SINK_LOCK (gl_sink);
+ if (from_tag)
+ tag_method = method;
+ else
+ gl_sink->rotate_method = method;
+
+ if (gl_sink->rotate_method == GST_GL_ROTATE_METHOD_AUTO)
+ method = tag_method;
+ else
+ method = gl_sink->rotate_method;
+
+ if (method != gl_sink->current_rotate_method) {
+ GST_DEBUG_OBJECT (gl_sink, "Changing method from %s to %s",
+ rotate_methods[gl_sink->current_rotate_method].value_nick,
+ rotate_methods[method].value_nick);
+
+ switch (method) {
+ case GST_GL_ROTATE_METHOD_IDENTITY:
+ gl_sink->transform_matrix = NULL;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_90R:
+ gl_sink->transform_matrix = clockwise_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_180:
+ gl_sink->transform_matrix = clockwise_180_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_90L:
+ gl_sink->transform_matrix = counterclockwise_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_FLIP_HORIZ:
+ gl_sink->transform_matrix = horizontal_flip_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_FLIP_VERT:
+ gl_sink->transform_matrix = vertical_flip_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_FLIP_UL_LR:
+ gl_sink->transform_matrix = upper_left_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ case GST_GL_ROTATE_METHOD_FLIP_UR_LL:
+ gl_sink->transform_matrix = upper_right_matrix;
+ gl_sink->output_mode_changed = TRUE;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ gl_sink->current_rotate_method = method;
+ }
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+}
+
+static void
+gst_glimage_sink_navigation_send_event (GstNavigation * navigation, GstStructure
+ * structure)
+{
+ GstGLImageSink *sink = GST_GLIMAGE_SINK (navigation);
+ gboolean handled = FALSE;
+ GstEvent *event = NULL;
+ GstGLWindow *window;
+ guint width, height;
+ gdouble x, y;
+
+ if (!sink->context) {
+ gst_structure_free (structure);
+ return;
+ }
+
+ window = gst_gl_context_get_window (sink->context);
+ g_return_if_fail (GST_IS_GL_WINDOW (window));
+
+ width = GST_VIDEO_SINK_WIDTH (sink);
+ height = GST_VIDEO_SINK_HEIGHT (sink);
+ gst_gl_window_get_surface_dimensions (window, &width, &height);
+
+ /* Converting pointer coordinates to the non scaled geometry */
+ if (width != 0 && gst_structure_get_double (structure, "pointer_x", &x)
+ && height != 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
+ gdouble stream_x, stream_y;
+
+ _display_size_to_stream_size (sink, x, y, &stream_x, &stream_y);
+
+ gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
+ stream_x, "pointer_y", G_TYPE_DOUBLE, stream_y, NULL);
+ }
+
+ event = gst_event_new_navigation (structure);
+ if (event) {
+ gst_event_ref (event);
+ handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (sink), event);
+
+ if (!handled)
+ gst_element_post_message ((GstElement *) sink,
+ gst_navigation_message_new_event ((GstObject *) sink, event));
+
+ gst_event_unref (event);
+ }
+ gst_object_unref (window);
+}
+
+static void
+gst_glimage_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_glimage_sink_navigation_send_event;
+}
+
+#define gst_glimage_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLImageSink, gst_glimage_sink,
+ GST_TYPE_VIDEO_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
+ gst_glimage_sink_video_overlay_init);
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_glimage_sink_navigation_interface_init);
+ GST_DEBUG_CATEGORY_INIT (gst_debug_glimage_sink, "glimagesink", 0,
+ "OpenGL Video Sink"));
+
+static void
+gst_glimage_sink_class_init (GstGLImageSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+ GstVideoSinkClass *gstvideosink_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+ gstvideosink_class = (GstVideoSinkClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->set_property = gst_glimage_sink_set_property;
+ gobject_class->get_property = gst_glimage_sink_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
+ g_param_spec_enum ("rotate-method",
+ "rotate method",
+ "rotate method",
+ GST_TYPE_GL_ROTATE_METHOD, DEFAULT_ROTATE_METHOD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio",
+ DEFAULT_FORCE_ASPECT_RATIO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
+ gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+ "The pixel aspect ratio of the device", 0, 1, G_MAXINT, 1, 1, 1,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CONTEXT,
+ g_param_spec_object ("context", "OpenGL context", "Get OpenGL context",
+ GST_TYPE_GL_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
+ g_param_spec_boolean ("handle-events", "Handle XEvents",
+ "When enabled, XEvents will be selected and handled",
+ DEFAULT_HANDLE_EVENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA,
+ g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
+ "When enabled, alpha will be ignored and converted to black",
+ DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_MULTIVIEW_LAYOUT,
+ g_param_spec_enum ("output-multiview-mode",
+ "Output Multiview Mode",
+ "Choose output mode for multiview/3D video",
+ GST_TYPE_VIDEO_MULTIVIEW_MODE, DEFAULT_MULTIVIEW_MODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_MULTIVIEW_FLAGS,
+ g_param_spec_flags ("output-multiview-flags",
+ "Output Multiview Flags",
+ "Output multiview layout modifier flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, DEFAULT_MULTIVIEW_FLAGS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_OUTPUT_MULTIVIEW_DOWNMIX_MODE,
+ g_param_spec_enum ("output-multiview-downmix-mode",
+ "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_MULTIVIEW_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "OpenGL video sink",
+ "Sink/Video", "A videosink based on OpenGL",
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ /**
+ * GstGLImageSink::client-draw:
+ * @object: the #GstGLImageSink
+ * @texture: the #guint id of the texture.
+ * @width: the #guint width of the texture.
+ * @height: the #guint height of the texture.
+ *
+ * Will be emitted before actually drawing the texture. The client should
+ * redraw the surface/contents with the @texture, @width and @height and
+ * and return %TRUE.
+ *
+ * Returns: whether the texture was redrawn by the signal. If not, a
+ * default redraw will occur.
+ */
+ gst_glimage_sink_signals[CLIENT_DRAW_SIGNAL] =
+ g_signal_new ("client-draw", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 2, GST_TYPE_GL_CONTEXT, GST_TYPE_SAMPLE);
+
+ /**
+ * GstGLImageSink::client-reshape:
+ * @object: the #GstGLImageSink
+ * @width: the #guint width of the texture.
+ * @height: the #guint height of the texture.
+ *
+ * The client should resize the surface/window/viewport with the @width and
+ * @height and return %TRUE.
+ *
+ * Returns: whether the content area was resized by the signal. If not, a
+ * default viewport resize will occur.
+ */
+ gst_glimage_sink_signals[CLIENT_RESHAPE_SIGNAL] =
+ g_signal_new ("client-reshape", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 3, GST_TYPE_GL_CONTEXT, G_TYPE_UINT, G_TYPE_UINT);
+
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_glimage_sink_template);
+
+ gobject_class->finalize = gst_glimage_sink_finalize;
+
+ gstelement_class->change_state = gst_glimage_sink_change_state;
+ gstelement_class->set_context = gst_glimage_sink_set_context;
+ gstbasesink_class->event = gst_glimage_sink_event;
+ gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_glimage_sink_query);
+ gstbasesink_class->set_caps = gst_glimage_sink_set_caps;
+ gstbasesink_class->get_caps = gst_glimage_sink_get_caps;
+ gstbasesink_class->get_times = gst_glimage_sink_get_times;
+ gstbasesink_class->prepare = gst_glimage_sink_prepare;
+ gstbasesink_class->propose_allocation = gst_glimage_sink_propose_allocation;
+
+ gstvideosink_class->show_frame =
+ GST_DEBUG_FUNCPTR (gst_glimage_sink_show_frame);
+}
+
+static void
+gst_glimage_sink_init (GstGLImageSink * glimage_sink)
+{
+ glimage_sink->window_id = 0;
+ glimage_sink->new_window_id = 0;
+ glimage_sink->display = NULL;
+ glimage_sink->keep_aspect_ratio = TRUE;
+ glimage_sink->par_n = 0;
+ glimage_sink->par_d = 1;
+ glimage_sink->redisplay_texture = 0;
+ glimage_sink->handle_events = TRUE;
+ glimage_sink->ignore_alpha = TRUE;
+ glimage_sink->overlay_compositor = NULL;
+
+ glimage_sink->mview_output_mode = DEFAULT_MULTIVIEW_MODE;
+ glimage_sink->mview_output_flags = DEFAULT_MULTIVIEW_FLAGS;
+ glimage_sink->mview_downmix_mode = DEFAULT_MULTIVIEW_DOWNMIX;
+
+ glimage_sink->current_rotate_method = DEFAULT_ROTATE_METHOD;
+ glimage_sink->transform_matrix = NULL;
+
+ g_mutex_init (&glimage_sink->drawing_lock);
+}
+
+static void
+gst_glimage_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLImageSink *glimage_sink;
+
+ g_return_if_fail (GST_IS_GLIMAGE_SINK (object));
+
+ glimage_sink = GST_GLIMAGE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_ROTATE_METHOD:
+ gst_glimage_sink_set_rotate_method (glimage_sink,
+ g_value_get_enum (value), FALSE);
+ break;
+ case PROP_FORCE_ASPECT_RATIO:
+ {
+ glimage_sink->keep_aspect_ratio = g_value_get_boolean (value);
+ break;
+ }
+ case PROP_PIXEL_ASPECT_RATIO:
+ {
+ glimage_sink->par_n = gst_value_get_fraction_numerator (value);
+ glimage_sink->par_d = gst_value_get_fraction_denominator (value);
+ break;
+ }
+ case PROP_HANDLE_EVENTS:
+ gst_glimage_sink_handle_events (GST_VIDEO_OVERLAY (glimage_sink),
+ g_value_get_boolean (value));
+ break;
+ case PROP_IGNORE_ALPHA:
+ glimage_sink->ignore_alpha = g_value_get_boolean (value);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_LAYOUT:
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ glimage_sink->mview_output_mode = g_value_get_enum (value);
+ glimage_sink->output_mode_changed = TRUE;
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_FLAGS:
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ glimage_sink->mview_output_flags = g_value_get_flags (value);
+ glimage_sink->output_mode_changed = TRUE;
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_DOWNMIX_MODE:
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ glimage_sink->mview_downmix_mode = g_value_get_enum (value);
+ glimage_sink->output_mode_changed = TRUE;
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_glimage_sink_finalize (GObject * object)
+{
+ GstGLImageSink *glimage_sink;
+
+ g_return_if_fail (GST_IS_GLIMAGE_SINK (object));
+
+ glimage_sink = GST_GLIMAGE_SINK (object);
+ g_mutex_clear (&glimage_sink->drawing_lock);
+
+ GST_DEBUG ("finalized");
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_glimage_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLImageSink *glimage_sink;
+
+ g_return_if_fail (GST_IS_GLIMAGE_SINK (object));
+
+ glimage_sink = GST_GLIMAGE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_ROTATE_METHOD:
+ g_value_set_enum (value, glimage_sink->current_rotate_method);
+ break;
+ case PROP_FORCE_ASPECT_RATIO:
+ g_value_set_boolean (value, glimage_sink->keep_aspect_ratio);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ gst_value_set_fraction (value, glimage_sink->par_n, glimage_sink->par_d);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, glimage_sink->context);
+ break;
+ case PROP_HANDLE_EVENTS:
+ g_value_set_boolean (value, glimage_sink->handle_events);
+ break;
+ case PROP_IGNORE_ALPHA:
+ g_value_set_boolean (value, glimage_sink->ignore_alpha);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_LAYOUT:
+ g_value_set_enum (value, glimage_sink->mview_output_mode);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_FLAGS:
+ g_value_set_flags (value, glimage_sink->mview_output_flags);
+ break;
+ case PROP_OUTPUT_MULTIVIEW_DOWNMIX_MODE:
+ g_value_set_enum (value, glimage_sink->mview_downmix_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_glimage_sink_key_event_cb (GstGLWindow * window, char *event_name, char
+ *key_string, GstGLImageSink * gl_sink)
+{
+ GST_DEBUG_OBJECT (gl_sink, "event %s key %s pressed", event_name, key_string);
+ gst_navigation_send_key_event (GST_NAVIGATION (gl_sink),
+ event_name, key_string);
+}
+
+static void
+gst_glimage_sink_mouse_event_cb (GstGLWindow * window, char *event_name,
+ int button, double posx, double posy, GstGLImageSink * gl_sink)
+{
+ GST_DEBUG_OBJECT (gl_sink, "event %s at %g, %g", event_name, posx, posy);
+ gst_navigation_send_mouse_event (GST_NAVIGATION (gl_sink),
+ event_name, button, posx, posy);
+}
+
+static gboolean
+_ensure_gl_setup (GstGLImageSink * gl_sink)
+{
+ GError *error = NULL;
+
+ GST_TRACE_OBJECT (gl_sink, "Ensuring setup");
+
+ if (!gl_sink->context) {
+ GST_OBJECT_LOCK (gl_sink->display);
+ do {
+ GstGLContext *other_context = NULL;
+ GstGLWindow *window = NULL;
+
+ if (gl_sink->context) {
+ gst_object_unref (gl_sink->context);
+ gl_sink->context = NULL;
+ }
+
+ GST_DEBUG_OBJECT (gl_sink,
+ "No current context, creating one for %" GST_PTR_FORMAT,
+ gl_sink->display);
+
+ if (gl_sink->other_context) {
+ other_context = gst_object_ref (gl_sink->other_context);
+ } else {
+ other_context =
+ gst_gl_display_get_gl_context_for_thread (gl_sink->display, NULL);
+ }
+
+ if (!gst_gl_display_create_context (gl_sink->display,
+ other_context, &gl_sink->context, &error)) {
+ if (other_context)
+ gst_object_unref (other_context);
+ GST_OBJECT_UNLOCK (gl_sink->display);
+ goto context_error;
+ }
+
+ GST_DEBUG_OBJECT (gl_sink,
+ "created context %" GST_PTR_FORMAT " from other context %"
+ GST_PTR_FORMAT, gl_sink->context, gl_sink->other_context);
+
+ window = gst_gl_context_get_window (gl_sink->context);
+
+ GST_DEBUG_OBJECT (gl_sink, "got window %" GST_PTR_FORMAT, window);
+
+ if (!gl_sink->window_id && !gl_sink->new_window_id)
+ gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (gl_sink));
+
+ GST_DEBUG_OBJECT (gl_sink,
+ "window_id : %" G_GUINTPTR_FORMAT " , new_window_id : %"
+ G_GUINTPTR_FORMAT, gl_sink->window_id, gl_sink->new_window_id);
+
+ if (gl_sink->window_id != gl_sink->new_window_id) {
+ gl_sink->window_id = gl_sink->new_window_id;
+ GST_DEBUG_OBJECT (gl_sink, "Setting window handle on gl window");
+ gst_gl_window_set_window_handle (window, gl_sink->window_id);
+ }
+
+ gst_gl_window_handle_events (window, gl_sink->handle_events);
+
+ /* setup callbacks */
+ gst_gl_window_set_resize_callback (window,
+ GST_GL_WINDOW_RESIZE_CB (gst_glimage_sink_on_resize),
+ gst_object_ref (gl_sink), (GDestroyNotify) gst_object_unref);
+ gst_gl_window_set_draw_callback (window,
+ GST_GL_WINDOW_CB (gst_glimage_sink_on_draw),
+ gst_object_ref (gl_sink), (GDestroyNotify) gst_object_unref);
+ gst_gl_window_set_close_callback (window,
+ GST_GL_WINDOW_CB (gst_glimage_sink_on_close),
+ gst_object_ref (gl_sink), (GDestroyNotify) gst_object_unref);
+ gl_sink->key_sig_id = g_signal_connect (window, "key-event", G_CALLBACK
+ (gst_glimage_sink_key_event_cb), gl_sink);
+ gl_sink->mouse_sig_id =
+ g_signal_connect (window, "mouse-event",
+ G_CALLBACK (gst_glimage_sink_mouse_event_cb), gl_sink);
+
+ if (gl_sink->x >= 0 && gl_sink->y >= 0 && gl_sink->width > 0 &&
+ gl_sink->height > 0) {
+ gst_gl_window_set_render_rectangle (window, gl_sink->x, gl_sink->y,
+ gl_sink->width, gl_sink->height);
+ }
+
+ if (other_context)
+ gst_object_unref (other_context);
+ gst_object_unref (window);
+ } while (!gst_gl_display_add_context (gl_sink->display, gl_sink->context));
+ GST_OBJECT_UNLOCK (gl_sink->display);
+ } else
+ GST_TRACE_OBJECT (gl_sink, "Already have a context");
+
+ return TRUE;
+
+context_error:
+ {
+ GST_ELEMENT_ERROR (gl_sink, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+
+ if (gl_sink->context) {
+ gst_object_unref (gl_sink->context);
+ gl_sink->context = NULL;
+ }
+
+ g_clear_error (&error);
+
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_glimage_sink_event (GstBaseSink * sink, GstEvent * event)
+{
+ GstGLImageSink *gl_sink = GST_GLIMAGE_SINK (sink);
+ GstTagList *taglist;
+ gchar *orientation;
+ gboolean ret;
+
+ GST_DEBUG_OBJECT (gl_sink, "handling %s event", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_TAG:
+ gst_event_parse_tag (event, &taglist);
+
+ if (gst_tag_list_get_string (taglist, "image-orientation", &orientation)) {
+ if (!g_strcmp0 ("rotate-0", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink,
+ GST_GL_ROTATE_METHOD_IDENTITY, TRUE);
+ else if (!g_strcmp0 ("rotate-90", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_90R,
+ TRUE);
+ else if (!g_strcmp0 ("rotate-180", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_180,
+ TRUE);
+ else if (!g_strcmp0 ("rotate-270", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_90L,
+ TRUE);
+ else if (!g_strcmp0 ("flip-rotate-0", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink,
+ GST_GL_ROTATE_METHOD_FLIP_HORIZ, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-90", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink,
+ GST_GL_ROTATE_METHOD_FLIP_UR_LL, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-180", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink,
+ GST_GL_ROTATE_METHOD_FLIP_VERT, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-270", orientation))
+ gst_glimage_sink_set_rotate_method (gl_sink,
+ GST_GL_ROTATE_METHOD_FLIP_UL_LR, TRUE);
+
+ g_free (orientation);
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
+
+ return ret;
+}
+
+static gboolean
+gst_glimage_sink_query (GstBaseSink * bsink, GstQuery * query)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (bsink);
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) glimage_sink, query,
+ glimage_sink->display, glimage_sink->context,
+ glimage_sink->other_context))
+ return TRUE;
+ break;
+ }
+ case GST_QUERY_DRAIN:
+ {
+ GstBuffer *buf[2];
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ glimage_sink->redisplay_texture = 0;
+ buf[0] = glimage_sink->stored_buffer[0];
+ buf[1] = glimage_sink->stored_buffer[1];
+ glimage_sink->stored_buffer[0] = glimage_sink->stored_buffer[1] = NULL;
+ glimage_sink->stored_sync_meta = glimage_sink->next_sync_meta = NULL;
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ gst_buffer_replace (buf, NULL);
+ gst_buffer_replace (buf + 1, NULL);
+
+ gst_buffer_replace (&glimage_sink->input_buffer, NULL);
+ gst_buffer_replace (&glimage_sink->input_buffer2, NULL);
+ gst_buffer_replace (&glimage_sink->next_buffer, NULL);
+ gst_buffer_replace (&glimage_sink->next_buffer2, NULL);
+ gst_buffer_replace (&glimage_sink->next_sync, NULL);
+
+ res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
+ break;
+ }
+ default:
+ res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
+ break;
+ }
+
+ return res;
+}
+
+static void
+gst_glimage_sink_set_context (GstElement * element, GstContext * context)
+{
+ GstGLImageSink *gl_sink = GST_GLIMAGE_SINK (element);
+
+ gst_gl_handle_set_context (element, context, &gl_sink->display,
+ &gl_sink->other_context);
+
+ if (gl_sink->display)
+ gst_gl_display_filter_gl_api (gl_sink->display, SUPPORTED_GL_APIS);
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static GstStateChangeReturn
+gst_glimage_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLImageSink *glimage_sink;
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ glimage_sink = GST_GLIMAGE_SINK (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_gl_ensure_element_data (glimage_sink, &glimage_sink->display,
+ &glimage_sink->other_context))
+ return GST_STATE_CHANGE_FAILURE;
+
+ gst_gl_display_filter_gl_api (glimage_sink->display, SUPPORTED_GL_APIS);
+
+ if (!_ensure_gl_setup (glimage_sink))
+ return GST_STATE_CHANGE_FAILURE;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ g_atomic_int_set (&glimage_sink->to_quit, 0);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ {
+ GstBuffer *buf[2];
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ /* mark the redisplay_texture as unavailable (=0)
+ * to avoid drawing
+ */
+ glimage_sink->redisplay_texture = 0;
+ buf[0] = glimage_sink->stored_buffer[0];
+ buf[1] = glimage_sink->stored_buffer[1];
+ glimage_sink->stored_buffer[0] = glimage_sink->stored_buffer[1] = NULL;
+ glimage_sink->stored_sync_meta = glimage_sink->next_sync_meta = NULL;
+
+ if (glimage_sink->stored_sync)
+ gst_buffer_unref (glimage_sink->stored_sync);
+ glimage_sink->stored_sync = NULL;
+
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ gst_buffer_replace (buf, NULL);
+ gst_buffer_replace (buf + 1, NULL);
+
+ gst_object_replace ((GstObject **) & glimage_sink->convert_views, NULL);
+ gst_buffer_replace (&glimage_sink->input_buffer, NULL);
+ gst_buffer_replace (&glimage_sink->input_buffer2, NULL);
+ gst_buffer_replace (&glimage_sink->next_buffer, NULL);
+ gst_buffer_replace (&glimage_sink->next_buffer2, NULL);
+ gst_buffer_replace (&glimage_sink->next_sync, NULL);
+
+ glimage_sink->window_id = 0;
+ /* but do not reset glimage_sink->new_window_id */
+
+ GST_VIDEO_SINK_WIDTH (glimage_sink) = 1;
+ GST_VIDEO_SINK_HEIGHT (glimage_sink) = 1;
+ /* Clear cached caps */
+ if (glimage_sink->out_caps) {
+ gst_caps_unref (glimage_sink->out_caps);
+ glimage_sink->out_caps = NULL;
+ }
+ if (glimage_sink->in_caps) {
+ gst_caps_unref (glimage_sink->in_caps);
+ glimage_sink->in_caps = NULL;
+ }
+ break;
+ }
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (glimage_sink->overlay_compositor) {
+ gst_object_unref (glimage_sink->overlay_compositor);
+ glimage_sink->overlay_compositor = NULL;
+ }
+
+ if (glimage_sink->context) {
+ GstGLWindow *window = gst_gl_context_get_window (glimage_sink->context);
+
+ gst_gl_window_send_message (window,
+ GST_GL_WINDOW_CB (gst_glimage_sink_cleanup_glthread), glimage_sink);
+
+ gst_gl_window_set_resize_callback (window, NULL, NULL, NULL);
+ gst_gl_window_set_draw_callback (window, NULL, NULL, NULL);
+ gst_gl_window_set_close_callback (window, NULL, NULL, NULL);
+
+ if (glimage_sink->key_sig_id)
+ g_signal_handler_disconnect (window, glimage_sink->key_sig_id);
+ glimage_sink->key_sig_id = 0;
+ if (glimage_sink->mouse_sig_id)
+ g_signal_handler_disconnect (window, glimage_sink->mouse_sig_id);
+ glimage_sink->mouse_sig_id = 0;
+
+ gst_object_unref (window);
+ gst_object_unref (glimage_sink->context);
+ glimage_sink->context = NULL;
+ }
+
+ glimage_sink->window_id = 0;
+
+ if (glimage_sink->other_context) {
+ gst_object_unref (glimage_sink->other_context);
+ glimage_sink->other_context = NULL;
+ }
+
+ if (glimage_sink->display) {
+ gst_object_unref (glimage_sink->display);
+ glimage_sink->display = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_glimage_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end)
+{
+ GstGLImageSink *glimagesink;
+
+ glimagesink = GST_GLIMAGE_SINK (bsink);
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+ *start = GST_BUFFER_TIMESTAMP (buf);
+ if (GST_BUFFER_DURATION_IS_VALID (buf))
+ *end = *start + GST_BUFFER_DURATION (buf);
+ else {
+ if (GST_VIDEO_INFO_FPS_N (&glimagesink->out_info) > 0) {
+ *end = *start +
+ gst_util_uint64_scale_int (GST_SECOND,
+ GST_VIDEO_INFO_FPS_D (&glimagesink->out_info),
+ GST_VIDEO_INFO_FPS_N (&glimagesink->out_info));
+ }
+ }
+ }
+}
+
+static GstCaps *
+gst_glimage_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
+{
+ GstCaps *tmp = NULL;
+ GstCaps *result = NULL;
+
+ tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
+
+ if (filter) {
+ GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT,
+ filter);
+
+ result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ } else {
+ result = tmp;
+ }
+
+ result = gst_gl_overlay_compositor_add_caps (result);
+
+ GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+static gboolean
+configure_display_from_info (GstGLImageSink * glimage_sink,
+ GstVideoInfo * vinfo)
+{
+ gint width;
+ gint height;
+ gboolean ok;
+ gint par_n, par_d;
+ gint display_par_n, display_par_d;
+ guint display_ratio_num, display_ratio_den;
+
+ width = GST_VIDEO_INFO_WIDTH (vinfo);
+ height = GST_VIDEO_INFO_HEIGHT (vinfo);
+
+ par_n = GST_VIDEO_INFO_PAR_N (vinfo);
+ par_d = GST_VIDEO_INFO_PAR_D (vinfo);
+
+ if (!par_n)
+ par_n = 1;
+
+ /* get display's PAR */
+ if (glimage_sink->par_n != 0 && glimage_sink->par_d != 0) {
+ display_par_n = glimage_sink->par_n;
+ display_par_d = glimage_sink->par_d;
+ } else {
+ display_par_n = 1;
+ display_par_d = 1;
+ }
+
+ ok = gst_video_calculate_display_ratio (&display_ratio_num,
+ &display_ratio_den, width, height, par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (!ok)
+ return FALSE;
+
+ GST_TRACE ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (height % display_ratio_den == 0) {
+ GST_DEBUG ("keeping video height");
+ GST_VIDEO_SINK_WIDTH (glimage_sink) = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ GST_VIDEO_SINK_HEIGHT (glimage_sink) = height;
+ } else if (width % display_ratio_num == 0) {
+ GST_DEBUG ("keeping video width");
+ GST_VIDEO_SINK_WIDTH (glimage_sink) = width;
+ GST_VIDEO_SINK_HEIGHT (glimage_sink) = (guint)
+ gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+ } else {
+ GST_DEBUG ("approximating while keeping video height");
+ GST_VIDEO_SINK_WIDTH (glimage_sink) = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ GST_VIDEO_SINK_HEIGHT (glimage_sink) = height;
+ }
+ GST_DEBUG ("scaling to %dx%d", GST_VIDEO_SINK_WIDTH (glimage_sink),
+ GST_VIDEO_SINK_HEIGHT (glimage_sink));
+
+ return TRUE;
+}
+
+/* Called with GST_GLIMAGE_SINK lock held, to
+ * copy in_info to out_info and update out_caps */
+static gboolean
+update_output_format (GstGLImageSink * glimage_sink)
+{
+ GstVideoInfo *out_info = &glimage_sink->out_info;
+ gboolean input_is_mono = FALSE;
+ GstVideoMultiviewMode mv_mode;
+ GstGLWindow *window = NULL;
+ GstGLTextureTarget previous_target;
+ GstStructure *s;
+ const gchar *target_str;
+ GstCaps *out_caps;
+ gboolean ret;
+
+ *out_info = glimage_sink->in_info;
+ previous_target = glimage_sink->texture_target;
+
+ mv_mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&glimage_sink->in_info);
+
+ if (mv_mode == GST_VIDEO_MULTIVIEW_MODE_NONE ||
+ mv_mode == GST_VIDEO_MULTIVIEW_MODE_MONO ||
+ mv_mode == GST_VIDEO_MULTIVIEW_MODE_LEFT ||
+ mv_mode == GST_VIDEO_MULTIVIEW_MODE_RIGHT)
+ input_is_mono = TRUE;
+
+ if (input_is_mono == FALSE &&
+ glimage_sink->mview_output_mode != GST_VIDEO_MULTIVIEW_MODE_NONE) {
+ /* Input is multiview, and output wants a conversion - configure 3d converter now,
+ * otherwise defer it until either the caps or the 3D output mode changes */
+ gst_video_multiview_video_info_change_mode (out_info,
+ glimage_sink->mview_output_mode, glimage_sink->mview_output_flags);
+
+ if (glimage_sink->convert_views == NULL) {
+ glimage_sink->convert_views = gst_gl_view_convert_new ();
+ gst_gl_view_convert_set_context (glimage_sink->convert_views,
+ glimage_sink->context);
+ }
+ } else {
+ if (glimage_sink->convert_views) {
+ gst_object_unref (glimage_sink->convert_views);
+ glimage_sink->convert_views = NULL;
+ }
+ }
+
+ ret = configure_display_from_info (glimage_sink, out_info);
+
+ if (glimage_sink->convert_views) {
+ /* Match actual output window size for pixel-aligned output,
+ * even though we can't necessarily match the starting left/right
+ * view parity properly */
+ glimage_sink->out_info.width = MAX (1, glimage_sink->display_rect.w);
+ glimage_sink->out_info.height = MAX (1, glimage_sink->display_rect.h);
+ GST_LOG_OBJECT (glimage_sink, "Set 3D output scale to %d,%d",
+ glimage_sink->display_rect.w, glimage_sink->display_rect.h);
+ }
+
+ s = gst_caps_get_structure (glimage_sink->in_caps, 0);
+ target_str = gst_structure_get_string (s, "texture-target");
+
+ if (!target_str)
+ target_str = GST_GL_TEXTURE_TARGET_2D_STR;
+
+ glimage_sink->texture_target = gst_gl_texture_target_from_string (target_str);
+ if (!glimage_sink->texture_target)
+ return FALSE;
+
+ out_caps = gst_video_info_to_caps (out_info);
+ gst_caps_set_features (out_caps, 0,
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+ gst_caps_set_simple (out_caps, "texture-target", G_TYPE_STRING,
+ target_str, NULL);
+
+ if (glimage_sink->convert_views) {
+ gst_caps_set_simple (out_caps, "texture-target", G_TYPE_STRING,
+ GST_GL_TEXTURE_TARGET_2D_STR, NULL);
+
+ glimage_sink->texture_target = GST_GL_TEXTURE_TARGET_2D;
+
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ gst_gl_view_convert_set_caps (glimage_sink->convert_views,
+ glimage_sink->in_caps, out_caps);
+ g_object_set (glimage_sink->convert_views, "downmix-mode",
+ glimage_sink->mview_downmix_mode, NULL);
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ }
+
+ if (glimage_sink->out_caps)
+ gst_caps_unref (glimage_sink->out_caps);
+ glimage_sink->out_caps = out_caps;
+
+ if (previous_target != GST_GL_TEXTURE_TARGET_NONE &&
+ glimage_sink->texture_target != previous_target) {
+ /* regenerate the shader for the changed target */
+ GstGLWindow *window = gst_gl_context_get_window (glimage_sink->context);
+ gst_gl_window_send_message (window,
+ GST_GL_WINDOW_CB (gst_glimage_sink_cleanup_glthread), glimage_sink);
+ gst_object_unref (window);
+ }
+
+ glimage_sink->output_mode_changed = FALSE;
+
+ if (glimage_sink->context)
+ window = gst_gl_context_get_window (glimage_sink->context);
+ if (window) {
+ gst_gl_window_queue_resize (window);
+ gst_object_unref (window);
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_glimage_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstGLImageSink *glimage_sink;
+ gboolean ok;
+ GstVideoInfo vinfo;
+
+ GST_DEBUG_OBJECT (bsink, "set caps with %" GST_PTR_FORMAT, caps);
+
+ glimage_sink = GST_GLIMAGE_SINK (bsink);
+
+ ok = gst_video_info_from_caps (&vinfo, caps);
+ if (!ok)
+ return FALSE;
+
+ if (!_ensure_gl_setup (glimage_sink))
+ return FALSE;
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ if (glimage_sink->in_caps)
+ gst_caps_unref (glimage_sink->in_caps);
+ glimage_sink->in_caps = gst_caps_ref (caps);
+ glimage_sink->in_info = vinfo;
+ ok = update_output_format (glimage_sink);
+
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ return ok;
+}
+
+/* Take the input_buffer and run it through 3D conversion if needed.
+ * Called with glimagesink lock, but might drop it temporarily */
+static gboolean
+prepare_next_buffer (GstGLImageSink * glimage_sink)
+{
+ GstBuffer *in_buffer, *next_buffer, *old_buffer;
+ GstBuffer *in_buffer2 = NULL, *next_buffer2 = NULL, *old_buffer2;
+ GstBuffer *next_sync = NULL, *old_sync;
+ GstGLSyncMeta *sync_meta;
+ GstVideoFrame gl_frame;
+ GstGLViewConvert *convert_views = NULL;
+ GstVideoInfo *info;
+
+ if (glimage_sink->input_buffer == NULL)
+ return TRUE; /* No input buffer to process */
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (&glimage_sink->in_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ if (glimage_sink->input_buffer2 == NULL)
+ return TRUE; /* Need 2nd input buffer to process */
+ in_buffer2 = gst_buffer_ref (glimage_sink->input_buffer2);
+ }
+
+ in_buffer = gst_buffer_ref (glimage_sink->input_buffer);
+ if (glimage_sink->convert_views &&
+ (GST_VIDEO_INFO_MULTIVIEW_MODE (&glimage_sink->in_info) !=
+ GST_VIDEO_INFO_MULTIVIEW_MODE (&glimage_sink->out_info) ||
+ GST_VIDEO_INFO_MULTIVIEW_FLAGS (&glimage_sink->in_info) !=
+ GST_VIDEO_INFO_MULTIVIEW_FLAGS (&glimage_sink->out_info)))
+ convert_views = gst_object_ref (glimage_sink->convert_views);
+
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ if (convert_views) {
+ info = &glimage_sink->out_info;
+
+ if (gst_gl_view_convert_submit_input_buffer (glimage_sink->convert_views,
+ GST_BUFFER_IS_DISCONT (in_buffer), in_buffer) != GST_FLOW_OK) {
+ gst_buffer_replace (&in_buffer2, NULL);
+ goto fail;
+ }
+ if (in_buffer2) {
+ if (gst_gl_view_convert_submit_input_buffer (glimage_sink->convert_views,
+ GST_BUFFER_IS_DISCONT (in_buffer2), in_buffer2) != GST_FLOW_OK) {
+ goto fail;
+ }
+ }
+
+ if (gst_gl_view_convert_get_output (glimage_sink->convert_views,
+ &next_buffer) != GST_FLOW_OK)
+ goto fail;
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ if (gst_gl_view_convert_get_output (glimage_sink->convert_views,
+ &next_buffer2) != GST_FLOW_OK)
+ goto fail;
+ }
+ gst_object_unref (convert_views);
+ convert_views = NULL;
+
+ if (next_buffer == NULL) {
+ /* Not ready to paint a buffer yet */
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ return TRUE;
+ }
+ } else {
+ next_buffer = in_buffer;
+ info = &glimage_sink->in_info;
+ }
+
+ if (!glimage_sink->overlay_compositor) {
+ if (!(glimage_sink->overlay_compositor =
+ gst_gl_overlay_compositor_new (glimage_sink->context))) {
+ gst_buffer_unref (next_buffer);
+ goto fail;
+ }
+ }
+
+ gst_gl_overlay_compositor_upload_overlays (glimage_sink->overlay_compositor,
+ next_buffer);
+
+ sync_meta = gst_buffer_get_gl_sync_meta (next_buffer);
+
+ if (!sync_meta) {
+ next_sync = gst_buffer_new ();
+ sync_meta = gst_buffer_add_gl_sync_meta (glimage_sink->context, next_sync);
+ gst_gl_sync_meta_set_sync_point (sync_meta, glimage_sink->context);
+ }
+
+ /* in_buffer invalid now */
+ if (!gst_video_frame_map (&gl_frame, info, next_buffer,
+ GST_MAP_READ | GST_MAP_GL)) {
+ gst_buffer_unref (next_buffer);
+ GST_ERROR ("Failed to map video frame.");
+ goto fail;
+ }
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ glimage_sink->next_tex = *(guint *) gl_frame.data[0];
+
+ old_buffer = glimage_sink->next_buffer;
+ glimage_sink->next_buffer = next_buffer;
+ old_buffer2 = glimage_sink->next_buffer2;
+ glimage_sink->next_buffer2 = next_buffer2;
+
+ old_sync = glimage_sink->next_sync;
+ glimage_sink->next_sync = next_sync;
+ glimage_sink->next_sync_meta = sync_meta;
+
+ /* Need to drop the lock again, to avoid a deadlock if we're
+ * dropping the last ref on this buffer and it goes back to our
+ * allocator */
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ if (old_buffer)
+ gst_buffer_unref (old_buffer);
+ if (old_buffer2)
+ gst_buffer_unref (old_buffer2);
+ if (old_sync)
+ gst_buffer_unref (old_sync);
+ gst_video_frame_unmap (&gl_frame);
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+
+ return TRUE;
+
+fail:
+ if (convert_views)
+ gst_object_unref (convert_views);
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ return FALSE;
+}
+
+static GstFlowReturn
+gst_glimage_sink_prepare (GstBaseSink * bsink, GstBuffer * buf)
+{
+ GstGLImageSink *glimage_sink;
+ GstGLSyncMeta *sync_meta;
+ GstBuffer **target;
+ GstBuffer *old_input;
+
+ glimage_sink = GST_GLIMAGE_SINK (bsink);
+
+ GST_TRACE ("preparing buffer:%p", buf);
+
+ if (GST_VIDEO_SINK_WIDTH (glimage_sink) < 1 ||
+ GST_VIDEO_SINK_HEIGHT (glimage_sink) < 1) {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+
+ if (!_ensure_gl_setup (glimage_sink))
+ return GST_FLOW_NOT_NEGOTIATED;
+
+ sync_meta = gst_buffer_get_gl_sync_meta (buf);
+ if (sync_meta)
+ gst_gl_sync_meta_wait (sync_meta, glimage_sink->context);
+
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ if (glimage_sink->window_resized) {
+ glimage_sink->window_resized = FALSE;
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ GST_DEBUG_OBJECT (glimage_sink, "Sending reconfigure event on sinkpad.");
+ gst_pad_push_event (GST_BASE_SINK (glimage_sink)->sinkpad,
+ gst_event_new_reconfigure ());
+ GST_GLIMAGE_SINK_LOCK (glimage_sink);
+ }
+
+ target = &glimage_sink->input_buffer;
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (&glimage_sink->in_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME &&
+ !GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE)) {
+ target = &glimage_sink->input_buffer2;
+ }
+ old_input = *target;
+ *target = gst_buffer_ref (buf);
+
+ if (glimage_sink->output_mode_changed)
+ update_output_format (glimage_sink);
+
+ if (!prepare_next_buffer (glimage_sink)) {
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+ if (old_input)
+ gst_buffer_unref (old_input);
+ goto convert_views_failed;
+ }
+ GST_GLIMAGE_SINK_UNLOCK (glimage_sink);
+
+ if (old_input)
+ gst_buffer_unref (old_input);
+
+ if (glimage_sink->window_id != glimage_sink->new_window_id) {
+ GstGLWindow *window = gst_gl_context_get_window (glimage_sink->context);
+
+ glimage_sink->window_id = glimage_sink->new_window_id;
+ gst_gl_window_set_window_handle (window, glimage_sink->window_id);
+
+ gst_object_unref (window);
+ }
+
+ return GST_FLOW_OK;
+convert_views_failed:
+ {
+ GST_ELEMENT_ERROR (glimage_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Failed to convert multiview video buffer"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+}
+
+static GstFlowReturn
+gst_glimage_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+ GstGLImageSink *glimage_sink;
+
+ GST_TRACE ("rendering buffer:%p", buf);
+
+ glimage_sink = GST_GLIMAGE_SINK (vsink);
+
+ GST_TRACE ("redisplay texture:%u of size:%ux%u, window size:%ux%u",
+ glimage_sink->next_tex, GST_VIDEO_INFO_WIDTH (&glimage_sink->out_info),
+ GST_VIDEO_INFO_HEIGHT (&glimage_sink->out_info),
+ GST_VIDEO_SINK_WIDTH (glimage_sink),
+ GST_VIDEO_SINK_HEIGHT (glimage_sink));
+
+ /* Ask the underlying window to redraw its content */
+ if (!gst_glimage_sink_redisplay (glimage_sink))
+ goto redisplay_failed;
+
+ GST_TRACE ("post redisplay");
+
+ if (g_atomic_int_get (&glimage_sink->to_quit) != 0) {
+ GST_ELEMENT_ERROR (glimage_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Quit requested"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+
+ return GST_FLOW_OK;
+
+/* ERRORS */
+redisplay_failed:
+ {
+ GST_ELEMENT_ERROR (glimage_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Window redisplay failed"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+}
+
+static void
+gst_glimage_sink_video_overlay_init (GstVideoOverlayInterface * iface)
+{
+ iface->set_window_handle = gst_glimage_sink_set_window_handle;
+ iface->set_render_rectangle = gst_glimage_sink_set_render_rectangle;
+ iface->handle_events = gst_glimage_sink_handle_events;
+ iface->expose = gst_glimage_sink_expose;
+}
+
+static void
+gst_glimage_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (overlay);
+ guintptr window_id = (guintptr) id;
+
+ g_return_if_fail (GST_IS_GLIMAGE_SINK (overlay));
+
+ GST_DEBUG ("set_xwindow_id %" G_GUINT64_FORMAT, (guint64) window_id);
+
+ glimage_sink->new_window_id = window_id;
+}
+
+
+static void
+gst_glimage_sink_expose (GstVideoOverlay * overlay)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (overlay);
+
+ /* redisplay opengl scene */
+ if (glimage_sink->display) {
+ if (glimage_sink->window_id
+ && glimage_sink->window_id != glimage_sink->new_window_id) {
+ GstGLWindow *window = gst_gl_context_get_window (glimage_sink->context);
+
+ glimage_sink->window_id = glimage_sink->new_window_id;
+ gst_gl_window_set_window_handle (window, glimage_sink->window_id);
+
+ gst_object_unref (window);
+ }
+
+ gst_glimage_sink_redisplay (glimage_sink);
+ }
+}
+
+static void
+gst_glimage_sink_handle_events (GstVideoOverlay * overlay,
+ gboolean handle_events)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (overlay);
+
+ glimage_sink->handle_events = handle_events;
+ if (G_LIKELY (glimage_sink->context)) {
+ GstGLWindow *window;
+ window = gst_gl_context_get_window (glimage_sink->context);
+ gst_gl_window_handle_events (window, handle_events);
+ gst_object_unref (window);
+ }
+}
+
+static void
+gst_glimage_sink_set_render_rectangle (GstVideoOverlay * overlay,
+ gint x, gint y, gint width, gint height)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (overlay);
+
+ if (G_LIKELY (glimage_sink->context)) {
+ GstGLWindow *window;
+ window = gst_gl_context_get_window (glimage_sink->context);
+ gst_gl_window_set_render_rectangle (window, x, y, width, height);
+ gst_object_unref (window);
+ }
+
+ glimage_sink->x = x;
+ glimage_sink->y = y;
+ glimage_sink->width = width;
+ glimage_sink->height = height;
+}
+
+static gboolean
+gst_glimage_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+ GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (bsink);
+ GstStructure *config;
+ GstCaps *caps;
+ GstBufferPool *pool = NULL;
+ GstVideoInfo info;
+ guint size;
+ gboolean need_pool;
+ GstStructure *allocation_meta = NULL;
+
+ if (!_ensure_gl_setup (glimage_sink))
+ return FALSE;
+
+ gst_query_parse_allocation (query, &caps, &need_pool);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ if (!gst_video_info_from_caps (&info, caps))
+ goto invalid_caps;
+
+ /* the normal size of a frame */
+ size = info.size;
+
+ if (need_pool) {
+ GST_DEBUG_OBJECT (glimage_sink, "create new pool");
+
+ pool = gst_gl_buffer_pool_new (glimage_sink->context);
+ config = gst_buffer_pool_get_config (pool);
+ gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+ gst_buffer_pool_config_add_option (config,
+ GST_BUFFER_POOL_OPTION_GL_SYNC_META);
+
+ if (!gst_buffer_pool_set_config (pool, config)) {
+ g_object_unref (pool);
+ goto config_failed;
+ }
+ }
+
+ /* we need at least 2 buffer because we hold on to the last one */
+ gst_query_add_allocation_pool (query, pool, size, 2, 0);
+ if (pool)
+ g_object_unref (pool);
+
+ if (glimage_sink->context->gl_vtable->FenceSync)
+ gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
+
+ if (glimage_sink->window_width != 0 && glimage_sink->window_height != 0) {
+ allocation_meta =
+ gst_structure_new ("GstVideoOverlayCompositionMeta",
+ "width", G_TYPE_UINT, glimage_sink->window_width,
+ "height", G_TYPE_UINT, glimage_sink->window_height, NULL);
+ GST_DEBUG ("sending alloc query with size %dx%d",
+ glimage_sink->window_width, glimage_sink->window_height);
+ }
+
+ gst_query_add_allocation_meta (query,
+ GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta);
+ gst_query_add_allocation_meta (query,
+ GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE, 0);
+
+ if (allocation_meta)
+ gst_structure_free (allocation_meta);
+
+ return TRUE;
+
+ /* ERRORS */
+no_caps:
+ {
+ GST_WARNING_OBJECT (bsink, "no caps specified");
+ return FALSE;
+ }
+invalid_caps:
+ {
+ GST_WARNING_OBJECT (bsink, "invalid caps specified");
+ return FALSE;
+ }
+config_failed:
+ {
+ GST_WARNING_OBJECT (bsink, "failed setting config");
+ return FALSE;
+ }
+}
+
+/* *INDENT-OFF* */
+static const GLfloat vertices[] = {
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 1.0f
+};
+
+static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
+/* *INDENT-ON* */
+
+static void
+_bind_buffer (GstGLImageSink * gl_sink)
+{
+ const GstGLFuncs *gl = gl_sink->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, gl_sink->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, gl_sink->vertex_buffer);
+
+ /* Load the vertex position */
+ gl->VertexAttribPointer (gl_sink->attr_position, 3, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) 0);
+
+ /* Load the texture coordinate */
+ gl->VertexAttribPointer (gl_sink->attr_texture, 2, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+
+ gl->EnableVertexAttribArray (gl_sink->attr_position);
+ gl->EnableVertexAttribArray (gl_sink->attr_texture);
+}
+
+static void
+_unbind_buffer (GstGLImageSink * gl_sink)
+{
+ const GstGLFuncs *gl = gl_sink->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (gl_sink->attr_position);
+ gl->DisableVertexAttribArray (gl_sink->attr_texture);
+}
+
+/* Called in the gl thread */
+static void
+gst_glimage_sink_thread_init_redisplay (GstGLImageSink * gl_sink)
+{
+ const GstGLFuncs *gl = gl_sink->context->gl_vtable;
+ GError *error = NULL;
+ GstGLSLStage *frag_stage, *vert_stage;
+
+ vert_stage = gst_glsl_stage_new_with_string (gl_sink->context,
+ GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ gst_gl_shader_string_vertex_mat4_vertex_transform);
+ if (gl_sink->texture_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
+ frag_stage = gst_glsl_stage_new_with_string (gl_sink->context,
+ GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ gst_gl_shader_string_fragment_external_oes_default);
+ } else {
+ frag_stage = gst_glsl_stage_new_default_fragment (gl_sink->context);
+ }
+ if (!vert_stage || !frag_stage) {
+ GST_ERROR_OBJECT (gl_sink, "Failed to retreive fragment shader for "
+ "texture target");
+ if (vert_stage)
+ gst_object_unref (vert_stage);
+ if (frag_stage)
+ gst_object_unref (frag_stage);
+ gst_glimage_sink_cleanup_glthread (gl_sink);
+ return;
+ }
+
+ if (!(gl_sink->redisplay_shader =
+ gst_gl_shader_new_link_with_stages (gl_sink->context, &error,
+ vert_stage, frag_stage, NULL))) {
+ GST_ERROR_OBJECT (gl_sink, "Failed to link shader: %s", error->message);
+ gst_glimage_sink_cleanup_glthread (gl_sink);
+ return;
+ }
+
+ gl_sink->attr_position =
+ gst_gl_shader_get_attribute_location (gl_sink->redisplay_shader,
+ "a_position");
+ gl_sink->attr_texture =
+ gst_gl_shader_get_attribute_location (gl_sink->redisplay_shader,
+ "a_texcoord");
+
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &gl_sink->vao);
+ gl->BindVertexArray (gl_sink->vao);
+ }
+
+ if (!gl_sink->vertex_buffer) {
+ gl->GenBuffers (1, &gl_sink->vertex_buffer);
+ gl->BindBuffer (GL_ARRAY_BUFFER, gl_sink->vertex_buffer);
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+ }
+
+ if (!gl_sink->vbo_indices) {
+ gl->GenBuffers (1, &gl_sink->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, gl_sink->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+ }
+
+ if (gl->GenVertexArrays) {
+ _bind_buffer (gl_sink);
+ gl->BindVertexArray (0);
+ }
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+}
+
+static void
+gst_glimage_sink_cleanup_glthread (GstGLImageSink * gl_sink)
+{
+ const GstGLFuncs *gl = gl_sink->context->gl_vtable;
+
+ if (gl_sink->redisplay_shader) {
+ gst_object_unref (gl_sink->redisplay_shader);
+ gl_sink->redisplay_shader = NULL;
+ }
+
+ if (gl_sink->vao) {
+ gl->DeleteVertexArrays (1, &gl_sink->vao);
+ gl_sink->vao = 0;
+ }
+
+ if (gl_sink->vertex_buffer) {
+ gl->DeleteBuffers (1, &gl_sink->vertex_buffer);
+ gl_sink->vertex_buffer = 0;
+ }
+
+ if (gl_sink->vbo_indices) {
+ gl->DeleteBuffers (1, &gl_sink->vbo_indices);
+ gl_sink->vbo_indices = 0;
+ }
+
+ if (gl_sink->overlay_compositor)
+ gst_gl_overlay_compositor_free_overlays (gl_sink->overlay_compositor);
+}
+
+/* Called with object lock held */
+static void
+gst_glimage_sink_on_resize (GstGLImageSink * gl_sink, gint width, gint height)
+{
+ /* Here gl_sink members (ex:gl_sink->out_info) have a life time of set_caps.
+ * It means that they cannot change between two set_caps
+ */
+ const GstGLFuncs *gl;
+ gboolean do_reshape;
+
+ GST_DEBUG_OBJECT (gl_sink, "GL Window resized to %ux%u", width, height);
+
+ /* check if a client reshape callback is registered */
+ g_signal_emit (gl_sink, gst_glimage_sink_signals[CLIENT_RESHAPE_SIGNAL], 0,
+ gl_sink->context, width, height, &do_reshape);
+ GST_GLIMAGE_SINK_LOCK (gl_sink);
+
+ gl = gl_sink->context->gl_vtable;
+ width = MAX (1, width);
+ height = MAX (1, height);
+
+ /* Check if we would suggest a different width/height now */
+ gl_sink->window_resized = ((gl_sink->window_width != width)
+ || (gl_sink->window_height != height))
+ && (gl_sink->window_width != 0)
+ && (gl_sink->window_height != 0);
+
+ gl_sink->window_width = width;
+ gl_sink->window_height = height;
+
+ gst_gl_insert_debug_marker (gl_sink->context, "%s window resize to %ix%i",
+ GST_OBJECT_NAME (gl_sink), width, height);
+
+ /* default reshape */
+ if (!do_reshape) {
+ if (gl_sink->keep_aspect_ratio) {
+ GstVideoRectangle src, dst, result;
+
+ src.x = 0;
+ src.y = 0;
+ if (gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_90R
+ || gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_90L
+ || gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_FLIP_UL_LR
+ || gl_sink->current_rotate_method ==
+ GST_GL_ROTATE_METHOD_FLIP_UR_LL) {
+ src.h = GST_VIDEO_SINK_WIDTH (gl_sink);
+ src.w = GST_VIDEO_SINK_HEIGHT (gl_sink);
+ } else {
+ src.w = GST_VIDEO_SINK_WIDTH (gl_sink);
+ src.h = GST_VIDEO_SINK_HEIGHT (gl_sink);
+ }
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = width;
+ dst.h = height;
+
+ gst_video_sink_center_rect (src, dst, &result, TRUE);
+ gl_sink->output_mode_changed |= (result.w != gl_sink->display_rect.w);
+ gl_sink->output_mode_changed |= (result.h != gl_sink->display_rect.h);
+ gl_sink->display_rect = result;
+ } else {
+ gl_sink->output_mode_changed |= (width != gl_sink->display_rect.w);
+ gl_sink->output_mode_changed |= (height != gl_sink->display_rect.h);
+
+ gl_sink->display_rect.x = 0;
+ gl_sink->display_rect.y = 0;
+ gl_sink->display_rect.w = width;
+ gl_sink->display_rect.h = height;
+ }
+
+ gl->Viewport (gl_sink->display_rect.x, gl_sink->display_rect.y,
+ gl_sink->display_rect.w, gl_sink->display_rect.h);
+ GST_DEBUG_OBJECT (gl_sink, "GL output area now %u,%u %ux%u",
+ gl_sink->display_rect.x, gl_sink->display_rect.y,
+ gl_sink->display_rect.w, gl_sink->display_rect.h);
+ }
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+}
+
+static void
+gst_glimage_sink_on_draw (GstGLImageSink * gl_sink)
+{
+ /* Here gl_sink members (ex:gl_sink->out_info) have a life time of set_caps.
+ * It means that they cannot not change between two set_caps as well as
+ * for the redisplay_texture size.
+ * Whereas redisplay_texture id changes every sink_render
+ */
+
+ const GstGLFuncs *gl = NULL;
+ GstGLWindow *window = NULL;
+ gboolean do_redisplay = FALSE;
+ GstSample *sample = NULL;
+ guint gl_target = gst_gl_texture_target_to_gl (gl_sink->texture_target);
+
+ g_return_if_fail (GST_IS_GLIMAGE_SINK (gl_sink));
+
+ gl = gl_sink->context->gl_vtable;
+
+ GST_GLIMAGE_SINK_LOCK (gl_sink);
+
+ /* check if texture is ready for being drawn */
+ if (!gl_sink->redisplay_texture) {
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+ return;
+ }
+
+ window = gst_gl_context_get_window (gl_sink->context);
+ window->is_drawing = TRUE;
+
+ /* opengl scene */
+ gst_gl_insert_debug_marker (gl_sink->context, "%s element drawing texture %u",
+ GST_OBJECT_NAME (gl_sink), gl_sink->redisplay_texture);
+ GST_TRACE ("redrawing texture:%u", gl_sink->redisplay_texture);
+
+ if (gl_sink->stored_sync_meta)
+ gst_gl_sync_meta_wait (gl_sink->stored_sync_meta,
+ gst_gl_context_get_current ());
+
+ /* make sure that the environnement is clean */
+ gst_gl_context_clear_shader (gl_sink->context);
+ gl->BindTexture (gl_target, 0);
+
+ sample = gst_sample_new (gl_sink->stored_buffer[0],
+ gl_sink->out_caps, &GST_BASE_SINK (gl_sink)->segment, NULL);
+ g_signal_emit (gl_sink, gst_glimage_sink_signals[CLIENT_DRAW_SIGNAL], 0,
+ gl_sink->context, sample, &do_redisplay);
+ gst_sample_unref (sample);
+
+ if (gl_sink->stored_buffer[1]) {
+ sample = gst_sample_new (gl_sink->stored_buffer[1],
+ gl_sink->out_caps, &GST_BASE_SINK (gl_sink)->segment, NULL);
+ g_signal_emit (gl_sink, gst_glimage_sink_signals[CLIENT_DRAW_SIGNAL], 0,
+ gl_sink->context, sample, &do_redisplay);
+ gst_sample_unref (sample);
+ }
+
+ if (!do_redisplay) {
+ gfloat alpha = gl_sink->ignore_alpha ? 1.0f : 0.0f;
+
+ gl->ClearColor (0.0, 0.0, 0.0, alpha);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+
+ if (gl_sink->ignore_alpha) {
+ gl->BlendColor (0.0, 0.0, 0.0, alpha);
+ gl->BlendFunc (GL_SRC_ALPHA, GL_CONSTANT_COLOR);
+ gl->BlendEquation (GL_FUNC_ADD);
+ gl->Enable (GL_BLEND);
+ }
+
+ gst_gl_shader_use (gl_sink->redisplay_shader);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (gl_sink->vao);
+ _bind_buffer (gl_sink);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (gl_target, gl_sink->redisplay_texture);
+ gst_gl_shader_set_uniform_1i (gl_sink->redisplay_shader, "tex", 0);
+ {
+ GstVideoAffineTransformationMeta *af_meta;
+ gfloat matrix[16];
+
+ af_meta =
+ gst_buffer_get_video_affine_transformation_meta
+ (gl_sink->stored_buffer[0]);
+
+ if (gl_sink->transform_matrix) {
+ gfloat tmp[16];
+
+ gst_gl_get_affine_transformation_meta_as_ndc_ext (af_meta, tmp);
+ gst_gl_multiply_matrix4 (tmp, gl_sink->transform_matrix, matrix);
+ } else {
+ gst_gl_get_affine_transformation_meta_as_ndc_ext (af_meta, matrix);
+ }
+
+ gst_gl_shader_set_uniform_matrix_4fv (gl_sink->redisplay_shader,
+ "u_transformation", 1, FALSE, matrix);
+ }
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ gl->BindTexture (gl_target, 0);
+ gst_gl_context_clear_shader (gl_sink->context);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (gl_sink);
+
+ if (gl_sink->ignore_alpha)
+ gl->Disable (GL_BLEND);
+
+ gst_gl_overlay_compositor_draw_overlays (gl_sink->overlay_compositor);
+ }
+ /* end default opengl scene */
+ window->is_drawing = FALSE;
+ gst_object_unref (window);
+
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+}
+
+static void
+gst_glimage_sink_on_close (GstGLImageSink * gl_sink)
+{
+ GstGLWindow *window;
+
+ GST_WARNING_OBJECT (gl_sink, "Output window was closed");
+
+ window = gst_gl_context_get_window (gl_sink->context);
+
+ if (gl_sink->key_sig_id)
+ g_signal_handler_disconnect (window, gl_sink->key_sig_id);
+ gl_sink->key_sig_id = 0;
+ if (gl_sink->mouse_sig_id)
+ g_signal_handler_disconnect (window, gl_sink->mouse_sig_id);
+ gl_sink->mouse_sig_id = 0;
+
+ g_atomic_int_set (&gl_sink->to_quit, 1);
+
+ gst_object_unref (window);
+}
+
+static gboolean
+gst_glimage_sink_redisplay (GstGLImageSink * gl_sink)
+{
+ GstGLWindow *window;
+ GstBuffer *old_stored_buffer[2], *old_sync;
+ gulong handler_id;
+
+ window = gst_gl_context_get_window (gl_sink->context);
+ if (!window)
+ return FALSE;
+
+ handler_id =
+ g_signal_handler_find (GST_ELEMENT_PARENT (gl_sink), G_SIGNAL_MATCH_ID,
+ gst_gl_image_sink_bin_signals[SIGNAL_BIN_CLIENT_DRAW], 0,
+ NULL, NULL, NULL);
+
+ if (G_UNLIKELY (!gl_sink->redisplay_shader) && (!handler_id
+ || !gl_sink->other_context)) {
+ gst_gl_window_send_message (window,
+ GST_GL_WINDOW_CB (gst_glimage_sink_thread_init_redisplay), gl_sink);
+
+ /* if the shader is still null it means it failed to be useable */
+ if (G_UNLIKELY (!gl_sink->redisplay_shader)) {
+ gst_object_unref (window);
+ return FALSE;
+ }
+
+ gst_gl_window_set_preferred_size (window, GST_VIDEO_SINK_WIDTH (gl_sink),
+ GST_VIDEO_SINK_HEIGHT (gl_sink));
+ gst_gl_window_show (window);
+ }
+
+ /* Recreate the output texture if needed */
+ GST_GLIMAGE_SINK_LOCK (gl_sink);
+ if (gl_sink->window_resized) {
+ gl_sink->window_resized = FALSE;
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+ GST_DEBUG_OBJECT (gl_sink, "Sending reconfigure event on sinkpad.");
+ gst_pad_push_event (GST_BASE_SINK (gl_sink)->sinkpad,
+ gst_event_new_reconfigure ());
+ GST_GLIMAGE_SINK_LOCK (gl_sink);
+ }
+
+ if (gl_sink->output_mode_changed && gl_sink->input_buffer != NULL) {
+ GST_DEBUG ("Recreating output after mode/size change");
+ update_output_format (gl_sink);
+ prepare_next_buffer (gl_sink);
+ }
+
+ if (gl_sink->next_buffer == NULL) {
+ /* Nothing to display yet */
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+ gst_object_unref (window);
+ return TRUE;
+ }
+
+ /* Avoid to release the texture while drawing */
+ gl_sink->redisplay_texture = gl_sink->next_tex;
+ old_stored_buffer[0] = gl_sink->stored_buffer[0];
+ old_stored_buffer[1] = gl_sink->stored_buffer[1];
+ gl_sink->stored_buffer[0] = gst_buffer_ref (gl_sink->next_buffer);
+ if (gl_sink->next_buffer2)
+ gl_sink->stored_buffer[1] = gst_buffer_ref (gl_sink->next_buffer2);
+ else
+ gl_sink->stored_buffer[1] = NULL;
+
+ old_sync = gl_sink->stored_sync;
+ if (gl_sink->next_sync)
+ gl_sink->stored_sync = gst_buffer_ref (gl_sink->next_sync);
+ else
+ gl_sink->stored_sync = NULL;
+ gl_sink->stored_sync_meta = gl_sink->next_sync_meta;
+ GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+
+ gst_buffer_replace (old_stored_buffer, NULL);
+ gst_buffer_replace (old_stored_buffer + 1, NULL);
+ if (old_sync)
+ gst_buffer_unref (old_sync);
+
+ /* Drawing is asynchronous: gst_gl_window_draw is not blocking
+ * It means that it does not wait for stuff to be executed in other threads
+ */
+ gst_gl_window_draw (window);
+ gst_object_unref (window);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglimagesink.h b/ext/gl/gstglimagesink.h
new file mode 100644
index 000000000..12166ecd4
--- /dev/null
+++ b/ext/gl/gstglimagesink.h
@@ -0,0 +1,157 @@
+/*
+ * GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005,2006,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GLIMAGESINK_H_
+#define _GLIMAGESINK_H_
+
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include <gst/gl/gl.h>
+#include <gst/gl/gstglfuncs.h>
+
+G_BEGIN_DECLS
+
+GST_DEBUG_CATEGORY_EXTERN (gst_debug_glimage_sink);
+
+#define GST_TYPE_GLIMAGE_SINK \
+ (gst_glimage_sink_get_type())
+#define GST_GLIMAGE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GLIMAGE_SINK,GstGLImageSink))
+#define GST_GLIMAGE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GLIMAGE_SINK,GstGLImageSinkClass))
+#define GST_IS_GLIMAGE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GLIMAGE_SINK))
+#define GST_IS_GLIMAGE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GLIMAGE_SINK))
+
+typedef enum
+{
+ GST_GL_ROTATE_METHOD_IDENTITY,
+ GST_GL_ROTATE_METHOD_90R,
+ GST_GL_ROTATE_METHOD_180,
+ GST_GL_ROTATE_METHOD_90L,
+ GST_GL_ROTATE_METHOD_FLIP_HORIZ,
+ GST_GL_ROTATE_METHOD_FLIP_VERT,
+ GST_GL_ROTATE_METHOD_FLIP_UL_LR,
+ GST_GL_ROTATE_METHOD_FLIP_UR_LL,
+ GST_GL_ROTATE_METHOD_AUTO,
+}GstGLRotateMethod;
+
+typedef struct _GstGLImageSink GstGLImageSink;
+typedef struct _GstGLImageSinkClass GstGLImageSinkClass;
+
+struct _GstGLImageSink
+{
+ GstVideoSink video_sink;
+
+ guintptr window_id;
+ guintptr new_window_id;
+ gulong mouse_sig_id;
+ gulong key_sig_id;
+
+ /* GstVideoOverlay::set_render_rectangle() cache */
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+
+ /* Input info before 3d stereo output conversion, if any */
+ GstVideoInfo in_info;
+ GstCaps *in_caps;
+
+ /* format/caps we actually hand off to the app */
+ GstVideoInfo out_info;
+ GstCaps *out_caps;
+ GstGLTextureTarget texture_target;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+ GstGLContext *other_context;
+ gboolean handle_events;
+ gboolean ignore_alpha;
+
+ GstGLViewConvert *convert_views;
+
+ /* Original input RGBA buffer, ready for display,
+ * or possible reconversion through the views filter */
+ GstBuffer *input_buffer;
+ /* Secondary view buffer - when operating in frame-by-frame mode */
+ GstBuffer *input_buffer2;
+
+ guint next_tex;
+ GstBuffer *next_buffer;
+ GstBuffer *next_buffer2; /* frame-by-frame 2nd view */
+ GstBuffer *next_sync;
+ GstGLSyncMeta *next_sync_meta;
+
+ volatile gint to_quit;
+ gboolean keep_aspect_ratio;
+ gint par_n, par_d;
+
+ /* avoid replacing the stored_buffer while drawing */
+ GMutex drawing_lock;
+ GstBuffer *stored_buffer[2];
+ GstBuffer *stored_sync;
+ GstGLSyncMeta *stored_sync_meta;
+ GLuint redisplay_texture;
+
+ /* protected with drawing_lock */
+ gboolean window_resized;
+ guint window_width;
+ guint window_height;
+
+ GstVideoRectangle display_rect;
+
+ GstGLShader *redisplay_shader;
+ GLuint vao;
+ GLuint vbo_indices;
+ GLuint vertex_buffer;
+ GLint attr_position;
+ GLint attr_texture;
+
+ GstVideoMultiviewMode mview_output_mode;
+ GstVideoMultiviewFlags mview_output_flags;
+ gboolean output_mode_changed;
+ GstGLStereoDownmix mview_downmix_mode;
+
+ GstGLOverlayCompositor *overlay_compositor;
+
+ /* current video flip method */
+ GstGLRotateMethod current_rotate_method;
+ GstGLRotateMethod rotate_method;
+ const gfloat *transform_matrix;
+};
+
+struct _GstGLImageSinkClass
+{
+ GstVideoSinkClass video_sink_class;
+};
+
+GType gst_glimage_sink_get_type(void);
+GType gst_gl_image_sink_bin_get_type(void);
+
+G_END_DECLS
+
+#endif
+
diff --git a/ext/gl/gstglmixer.c b/ext/gl/gstglmixer.c
new file mode 100644
index 000000000..856a433f9
--- /dev/null
+++ b/ext/gl/gstglmixer.c
@@ -0,0 +1,720 @@
+/* Generic video mixer plugin
+ *
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include "gstglmixer.h"
+
+#define gst_gl_mixer_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE (GstGLMixer, gst_gl_mixer, GST_TYPE_GL_BASE_MIXER);
+
+#define GST_CAT_DEFAULT gst_gl_mixer_debug
+GST_DEBUG_CATEGORY (gst_gl_mixer_debug);
+
+static void gst_gl_mixer_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_gl_mixer_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+enum
+{
+ PROP_PAD_0
+};
+
+#define GST_GL_MIXER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_GL_MIXER, GstGLMixerPrivate))
+
+struct _GstGLMixerPrivate
+{
+ gboolean negotiated;
+
+ gboolean gl_resource_ready;
+ GMutex gl_resource_lock;
+ GCond gl_resource_cond;
+};
+
+G_DEFINE_TYPE (GstGLMixerPad, gst_gl_mixer_pad, GST_TYPE_GL_BASE_MIXER_PAD);
+
+static void
+gst_gl_mixer_pad_class_init (GstGLMixerPadClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstVideoAggregatorPadClass *vaggpad_class =
+ (GstVideoAggregatorPadClass *) klass;
+
+ gobject_class->set_property = gst_gl_mixer_pad_set_property;
+ gobject_class->get_property = gst_gl_mixer_pad_get_property;
+
+ vaggpad_class->set_info = NULL;
+ vaggpad_class->prepare_frame = NULL;
+ vaggpad_class->clean_frame = NULL;
+}
+
+static void
+gst_gl_mixer_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_mixer_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+_negotiated_caps (GstAggregator * agg, GstCaps * caps)
+{
+ GstGLMixer *mix = GST_GL_MIXER (agg);
+ gboolean ret;
+
+ mix->priv->negotiated = TRUE;
+
+ gst_caps_replace (&mix->out_caps, caps);
+
+ ret = GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps);
+
+ return ret;
+}
+
+static void
+_find_best_format (GstVideoAggregator * vagg, GstCaps * downstream_caps,
+ GstVideoInfo * best_info, gboolean * at_least_one_alpha)
+{
+ GstVideoInfo tmp_info;
+
+ GST_VIDEO_AGGREGATOR_CLASS (parent_class)->find_best_format (vagg,
+ downstream_caps, best_info, at_least_one_alpha);
+
+ gst_video_info_set_format (&tmp_info, GST_VIDEO_FORMAT_RGBA,
+ best_info->width, best_info->height);
+ tmp_info.par_n = best_info->par_n;
+ tmp_info.par_d = best_info->par_d;
+ tmp_info.fps_n = best_info->fps_n;
+ tmp_info.fps_d = best_info->fps_d;
+ tmp_info.flags = best_info->flags;
+ tmp_info.interlace_mode = best_info->interlace_mode;
+ *best_info = tmp_info;
+}
+
+static gboolean
+gst_gl_mixer_propose_allocation (GstAggregator * agg,
+ GstAggregatorPad * agg_pad, GstQuery * decide_query, GstQuery * query)
+{
+ GstGLMixer *mix = GST_GL_MIXER (agg);
+ GstGLBaseMixer *base_mix = GST_GL_BASE_MIXER (agg);
+ GstGLContext *context;
+ GstBufferPool *pool = NULL;
+ GstStructure *config;
+ GstCaps *caps;
+ GstVideoInfo info;
+ guint size = 0;
+ gboolean need_pool;
+
+ if (!GST_AGGREGATOR_CLASS (gst_gl_mixer_parent_class)->propose_allocation
+ (agg, agg_pad, decide_query, query))
+ return FALSE;
+
+ context = base_mix->context;
+
+ gst_query_parse_allocation (query, &caps, &need_pool);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ if (!gst_video_info_from_caps (&info, caps))
+ goto invalid_caps;
+
+ /* the normal size of a frame */
+ size = info.size;
+
+ if (need_pool) {
+ GST_DEBUG_OBJECT (mix, "create new pool");
+ pool = gst_gl_buffer_pool_new (context);
+
+ config = gst_buffer_pool_get_config (pool);
+ gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+
+ if (!gst_buffer_pool_set_config (pool, config)) {
+ g_object_unref (pool);
+ goto config_failed;
+ }
+ }
+
+ gst_query_add_allocation_pool (query, pool, size, 1, 0);
+ if (pool)
+ g_object_unref (pool);
+
+ /* we also support various metadata */
+ if (context->gl_vtable->FenceSync)
+ gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
+
+ return TRUE;
+
+ /* ERRORS */
+no_caps:
+ {
+ GST_DEBUG_OBJECT (mix, "no caps specified");
+ return FALSE;
+ }
+invalid_caps:
+ {
+ GST_DEBUG_OBJECT (mix, "invalid caps specified");
+ return FALSE;
+ }
+config_failed:
+ {
+ GST_DEBUG_OBJECT (mix, "failed setting config");
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_gl_mixer_pad_sink_acceptcaps (GstPad * pad, GstGLMixer * mix,
+ GstCaps * caps)
+{
+ gboolean ret;
+ GstCaps *template_caps;
+
+ GST_DEBUG_OBJECT (pad, "try accept caps of %" GST_PTR_FORMAT, caps);
+
+ template_caps = gst_pad_get_pad_template_caps (pad);
+ template_caps = gst_caps_make_writable (template_caps);
+
+ ret = gst_caps_can_intersect (caps, template_caps);
+ GST_DEBUG_OBJECT (pad, "%saccepted caps %" GST_PTR_FORMAT,
+ (ret ? "" : "not "), caps);
+ gst_caps_unref (template_caps);
+
+ return ret;
+}
+
+static GstCaps *
+gst_gl_mixer_pad_sink_getcaps (GstPad * pad, GstGLMixer * mix, GstCaps * filter)
+{
+ GstCaps *sinkcaps;
+ GstCaps *template_caps;
+ GstCaps *filtered_caps;
+ GstCaps *returned_caps;
+
+ template_caps = gst_pad_get_pad_template_caps (pad);
+
+ sinkcaps = gst_pad_get_current_caps (pad);
+ if (sinkcaps == NULL) {
+ sinkcaps = gst_caps_ref (template_caps);
+ } else {
+ sinkcaps = gst_caps_merge (sinkcaps, gst_caps_ref (template_caps));
+ }
+
+ if (filter) {
+ filtered_caps = gst_caps_intersect (sinkcaps, filter);
+ gst_caps_unref (sinkcaps);
+ } else {
+ filtered_caps = sinkcaps; /* pass ownership */
+ }
+
+ returned_caps = gst_caps_intersect (filtered_caps, template_caps);
+
+ gst_caps_unref (template_caps);
+ gst_caps_unref (filtered_caps);
+
+ GST_DEBUG_OBJECT (pad, "returning %" GST_PTR_FORMAT, returned_caps);
+
+ return returned_caps;
+}
+
+static gboolean
+gst_gl_mixer_sink_query (GstAggregator * agg, GstAggregatorPad * bpad,
+ GstQuery * query)
+{
+ gboolean ret = FALSE;
+ GstGLMixer *mix = GST_GL_MIXER (agg);
+
+ GST_TRACE ("QUERY %" GST_PTR_FORMAT, query);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ {
+ GstCaps *filter, *caps;
+
+ gst_query_parse_caps (query, &filter);
+ caps = gst_gl_mixer_pad_sink_getcaps (GST_PAD (bpad), mix, filter);
+ gst_query_set_caps_result (query, caps);
+ gst_caps_unref (caps);
+ ret = TRUE;
+ break;
+ }
+ case GST_QUERY_ACCEPT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_query_parse_accept_caps (query, &caps);
+ ret = gst_gl_mixer_pad_sink_acceptcaps (GST_PAD (bpad), mix, caps);
+ gst_query_set_accept_caps_result (query, ret);
+ ret = TRUE;
+ break;
+ }
+ default:
+ ret = GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, bpad, query);
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_gl_mixer_pad_init (GstGLMixerPad * mixerpad)
+{
+}
+
+/* GLMixer signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+};
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA"))
+ );
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA"))
+ );
+
+static gboolean gst_gl_mixer_src_query (GstAggregator * agg, GstQuery * query);
+static gboolean gst_gl_mixer_stop (GstAggregator * agg);
+static gboolean gst_gl_mixer_start (GstAggregator * agg);
+
+static GstFlowReturn
+gst_gl_mixer_aggregate_frames (GstVideoAggregator * vagg,
+ GstBuffer * outbuffer);
+
+static void gst_gl_mixer_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_mixer_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_mixer_decide_allocation (GstAggregator * agg,
+ GstQuery * query);
+
+static void gst_gl_mixer_finalize (GObject * object);
+
+static void
+gst_gl_mixer_class_init (GstGLMixerClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstVideoAggregatorClass *videoaggregator_class =
+ (GstVideoAggregatorClass *) klass;
+ GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glmixer", 0, "OpenGL mixer");
+
+ g_type_class_add_private (klass, sizeof (GstGLMixerPrivate));
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gl_mixer_finalize);
+
+ gobject_class->get_property = gst_gl_mixer_get_property;
+ gobject_class->set_property = gst_gl_mixer_set_property;
+
+ gst_element_class_add_static_pad_template (element_class, &src_factory);
+ gst_element_class_add_static_pad_template_with_gtype (element_class,
+ &sink_factory, GST_TYPE_GL_MIXER_PAD);
+
+ agg_class->sink_query = gst_gl_mixer_sink_query;
+ agg_class->src_query = gst_gl_mixer_src_query;
+ agg_class->stop = gst_gl_mixer_stop;
+ agg_class->start = gst_gl_mixer_start;
+ agg_class->negotiated_src_caps = _negotiated_caps;
+ agg_class->decide_allocation = gst_gl_mixer_decide_allocation;
+ agg_class->propose_allocation = gst_gl_mixer_propose_allocation;
+
+ videoaggregator_class->aggregate_frames = gst_gl_mixer_aggregate_frames;
+ videoaggregator_class->find_best_format = _find_best_format;
+
+
+ /* Register the pad class */
+ g_type_class_ref (GST_TYPE_GL_MIXER_PAD);
+
+ klass->set_caps = NULL;
+}
+
+static void
+gst_gl_mixer_reset (GstGLMixer * mix)
+{
+ mix->priv->negotiated = FALSE;
+}
+
+static void
+gst_gl_mixer_init (GstGLMixer * mix)
+{
+ mix->priv = GST_GL_MIXER_GET_PRIVATE (mix);
+
+ mix->priv->gl_resource_ready = FALSE;
+ g_mutex_init (&mix->priv->gl_resource_lock);
+ g_cond_init (&mix->priv->gl_resource_cond);
+ /* initialize variables */
+ gst_gl_mixer_reset (mix);
+}
+
+static void
+gst_gl_mixer_finalize (GObject * object)
+{
+ GstGLMixer *mix = GST_GL_MIXER (object);
+ GstGLMixerPrivate *priv = mix->priv;
+
+ if (mix->out_caps)
+ gst_caps_unref (mix->out_caps);
+
+ g_mutex_clear (&priv->gl_resource_lock);
+ g_cond_clear (&priv->gl_resource_cond);
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_gl_mixer_query_caps (GstPad * pad, GstAggregator * agg, GstQuery * query)
+{
+ GstCaps *filter, *current_caps, *retcaps, *template_caps;
+
+ gst_query_parse_caps (query, &filter);
+
+ template_caps = gst_pad_get_pad_template_caps (agg->srcpad);
+
+ current_caps = gst_pad_get_current_caps (pad);
+ if (current_caps == NULL)
+ retcaps = gst_caps_ref (template_caps);
+ else {
+ retcaps = gst_caps_merge (current_caps, template_caps);
+ template_caps = NULL;
+ }
+
+ if (filter) {
+ current_caps =
+ gst_caps_intersect_full (filter, retcaps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (retcaps);
+ retcaps = current_caps;
+ }
+
+ gst_query_set_caps_result (query, retcaps);
+ gst_caps_unref (retcaps);
+
+ if (template_caps)
+ gst_caps_unref (template_caps);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_mixer_src_query (GstAggregator * agg, GstQuery * query)
+{
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ res = gst_gl_mixer_query_caps (agg->srcpad, agg, query);
+ break;
+ default:
+ res = GST_AGGREGATOR_CLASS (parent_class)->src_query (agg, query);
+ break;
+ }
+
+ return res;
+}
+
+static void
+_mixer_create_fbo (GstGLContext * context, GstGLMixer * mix)
+{
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (mix);
+ guint out_width = GST_VIDEO_INFO_WIDTH (&vagg->info);
+ guint out_height = GST_VIDEO_INFO_HEIGHT (&vagg->info);
+
+ mix->fbo =
+ gst_gl_framebuffer_new_with_default_depth (context, out_width,
+ out_height);
+}
+
+static gboolean
+gst_gl_mixer_decide_allocation (GstAggregator * agg, GstQuery * query)
+{
+ GstGLBaseMixer *base_mix = GST_GL_BASE_MIXER (agg);
+ GstGLMixer *mix = GST_GL_MIXER (base_mix);
+ GstGLMixerClass *mixer_class = GST_GL_MIXER_GET_CLASS (mix);
+ GstGLContext *context;
+ GstBufferPool *pool = NULL;
+ GstStructure *config;
+ GstCaps *caps;
+ guint min, max, size;
+ gboolean update_pool;
+
+ if (!GST_AGGREGATOR_CLASS (gst_gl_mixer_parent_class)->decide_allocation (agg,
+ query))
+ return FALSE;
+
+ context = base_mix->context;
+
+ g_mutex_lock (&mix->priv->gl_resource_lock);
+ mix->priv->gl_resource_ready = FALSE;
+ if (mix->fbo)
+ gst_object_unref (mix->fbo);
+
+ gst_gl_context_thread_add (context,
+ (GstGLContextThreadFunc) _mixer_create_fbo, mix);
+ if (!mix->fbo) {
+ g_cond_signal (&mix->priv->gl_resource_cond);
+ g_mutex_unlock (&mix->priv->gl_resource_lock);
+ goto context_error;
+ }
+
+ gst_query_parse_allocation (query, &caps, NULL);
+
+ if (mixer_class->set_caps)
+ mixer_class->set_caps (mix, caps);
+
+ mix->priv->gl_resource_ready = TRUE;
+ g_cond_signal (&mix->priv->gl_resource_cond);
+ g_mutex_unlock (&mix->priv->gl_resource_lock);
+
+ if (gst_query_get_n_allocation_pools (query) > 0) {
+ gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+
+ update_pool = TRUE;
+ } else {
+ GstVideoInfo vinfo;
+
+ gst_video_info_init (&vinfo);
+ gst_video_info_from_caps (&vinfo, caps);
+ size = vinfo.size;
+ min = max = 0;
+ update_pool = FALSE;
+ }
+
+ if (!pool)
+ pool = gst_gl_buffer_pool_new (context);
+ config = gst_buffer_pool_get_config (pool);
+
+ gst_buffer_pool_config_set_params (config, caps, size, min, max);
+ gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
+
+ gst_buffer_pool_set_config (pool, config);
+
+ if (update_pool)
+ gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
+ else
+ gst_query_add_allocation_pool (query, pool, size, min, max);
+
+ gst_object_unref (pool);
+
+ return TRUE;
+
+context_error:
+ {
+ GST_ELEMENT_ERROR (mix, RESOURCE, NOT_FOUND, ("Context error"), (NULL));
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_gl_mixer_upload_frames (GstElement * element, GstPad * sink_pad,
+ gpointer user_data)
+{
+ GstVideoAggregatorPad *vaggpad = GST_VIDEO_AGGREGATOR_PAD (sink_pad);
+ GstGLMixerPad *pad = GST_GL_MIXER_PAD (sink_pad);
+ GstGLMixer *mix = GST_GL_MIXER (element);
+
+ pad->current_texture = 0;
+ if (vaggpad->buffer != NULL) {
+ GstVideoInfo gl_info;
+ GstVideoFrame gl_frame;
+ GstGLSyncMeta *sync_meta;
+
+ gst_video_info_set_format (&gl_info,
+ GST_VIDEO_FORMAT_RGBA,
+ GST_VIDEO_INFO_WIDTH (&vaggpad->info),
+ GST_VIDEO_INFO_HEIGHT (&vaggpad->info));
+
+ sync_meta = gst_buffer_get_gl_sync_meta (vaggpad->buffer);
+ if (sync_meta)
+ gst_gl_sync_meta_wait (sync_meta, GST_GL_BASE_MIXER (mix)->context);
+
+ if (!gst_video_frame_map (&gl_frame, &gl_info, vaggpad->buffer,
+ GST_MAP_READ | GST_MAP_GL)) {
+ GST_ERROR_OBJECT (pad, "Failed to map input frame");
+ return FALSE;
+ }
+
+ pad->current_texture = *(guint *) gl_frame.data[0];
+ gst_video_frame_unmap (&gl_frame);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gst_gl_mixer_process_textures (GstGLMixer * mix, GstBuffer * outbuf)
+{
+ GstGLMemory *out_tex;
+ gboolean res = TRUE;
+ GstVideoFrame out_frame;
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (mix);
+ GstGLMixerClass *mix_class = GST_GL_MIXER_GET_CLASS (mix);
+ GstGLMixerPrivate *priv = mix->priv;
+
+ GST_TRACE ("Processing buffers");
+
+ if (!gst_video_frame_map (&out_frame, &vagg->info, outbuf,
+ GST_MAP_WRITE | GST_MAP_GL)) {
+ return FALSE;
+ }
+
+ out_tex = (GstGLMemory *) out_frame.map[0].memory;
+
+ if (!gst_element_foreach_sink_pad (GST_ELEMENT_CAST (mix),
+ gst_gl_mixer_upload_frames, NULL)) {
+ res = FALSE;
+ goto out;
+ }
+
+ g_mutex_lock (&priv->gl_resource_lock);
+ if (!priv->gl_resource_ready)
+ g_cond_wait (&priv->gl_resource_cond, &priv->gl_resource_lock);
+
+ if (!priv->gl_resource_ready) {
+ g_mutex_unlock (&priv->gl_resource_lock);
+ GST_ERROR_OBJECT (mix,
+ "fbo used to render can't be created, do not run process_textures");
+ res = FALSE;
+ goto out;
+ }
+
+ mix_class->process_textures (mix, out_tex);
+
+ g_mutex_unlock (&priv->gl_resource_lock);
+
+out:
+ gst_video_frame_unmap (&out_frame);
+
+ return res;
+}
+
+static gboolean
+gst_gl_mixer_process_buffers (GstGLMixer * mix, GstBuffer * outbuf)
+{
+ GstGLMixerClass *mix_class = GST_GL_MIXER_GET_CLASS (mix);
+
+ return mix_class->process_buffers (mix, outbuf);
+}
+
+static GstFlowReturn
+gst_gl_mixer_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
+{
+ gboolean res = FALSE;
+ GstGLMixer *mix = GST_GL_MIXER (vagg);
+ GstGLMixerClass *mix_class = GST_GL_MIXER_GET_CLASS (vagg);
+ GstGLContext *context = GST_GL_BASE_MIXER (mix)->context;
+ GstGLSyncMeta *sync_meta;
+
+ if (mix_class->process_buffers)
+ res = gst_gl_mixer_process_buffers (mix, outbuf);
+ else if (mix_class->process_textures)
+ res = gst_gl_mixer_process_textures (mix, outbuf);
+
+ sync_meta = gst_buffer_get_gl_sync_meta (outbuf);
+ if (sync_meta)
+ gst_gl_sync_meta_set_sync_point (sync_meta, context);
+
+ return res ? GST_FLOW_OK : GST_FLOW_ERROR;
+}
+
+static void
+gst_gl_mixer_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_mixer_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_mixer_start (GstAggregator * agg)
+{
+ return GST_AGGREGATOR_CLASS (parent_class)->start (agg);
+}
+
+static gboolean
+gst_gl_mixer_stop (GstAggregator * agg)
+{
+ GstGLMixer *mix = GST_GL_MIXER (agg);
+ GstGLMixerClass *mixer_class = GST_GL_MIXER_GET_CLASS (mix);
+
+ if (mixer_class->reset)
+ mixer_class->reset (mix);
+
+ if (mix->fbo) {
+ gst_object_unref (mix->fbo);
+ mix->fbo = NULL;
+ }
+
+ gst_gl_mixer_reset (mix);
+
+ return GST_AGGREGATOR_CLASS (parent_class)->stop (agg);
+}
diff --git a/ext/gl/gstglmixer.h b/ext/gl/gstglmixer.h
new file mode 100644
index 000000000..886e00940
--- /dev/null
+++ b/ext/gl/gstglmixer.h
@@ -0,0 +1,110 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_MIXER_H__
+#define __GST_GL_MIXER_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+#include <gst/gl/gstglfuncs.h>
+#include "gstglbasemixer.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GstGLMixer GstGLMixer;
+typedef struct _GstGLMixerClass GstGLMixerClass;
+typedef struct _GstGLMixerPrivate GstGLMixerPrivate;
+
+#define GST_TYPE_GL_MIXER_PAD (gst_gl_mixer_pad_get_type())
+#define GST_GL_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_MIXER_PAD, GstGLMixerPad))
+#define GST_GL_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_MIXER_PAD, GstGLMixerPadClass))
+#define GST_IS_GL_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_MIXER_PAD))
+#define GST_IS_GL_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_MIXER_PAD))
+#define GST_GL_MIXER_PAD_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_MIXER_PAD,GstGLMixerPadClass))
+
+typedef struct _GstGLMixerPad GstGLMixerPad;
+typedef struct _GstGLMixerPadClass GstGLMixerPadClass;
+
+/* all information needed for one video stream */
+struct _GstGLMixerPad
+{
+ GstGLBaseMixerPad parent;
+
+ guint current_texture;
+};
+
+struct _GstGLMixerPadClass
+{
+ GstGLBaseMixerPadClass parent_class;
+};
+
+GType gst_gl_mixer_pad_get_type (void);
+
+#define GST_TYPE_GL_MIXER (gst_gl_mixer_get_type())
+#define GST_GL_MIXER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_MIXER, GstGLMixer))
+#define GST_GL_MIXER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_MIXER, GstGLMixerClass))
+#define GST_IS_GL_MIXER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_MIXER))
+#define GST_IS_GL_MIXER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_MIXER))
+#define GST_GL_MIXER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_MIXER,GstGLMixerClass))
+
+typedef gboolean (*GstGLMixerSetCaps) (GstGLMixer* mixer,
+ GstCaps* outcaps);
+typedef void (*GstGLMixerReset) (GstGLMixer *mixer);
+typedef gboolean (*GstGLMixerProcessFunc) (GstGLMixer *mix, GstBuffer *outbuf);
+typedef gboolean (*GstGLMixerProcessTextures) (GstGLMixer *mix, GstGLMemory *out_tex);
+
+struct _GstGLMixer
+{
+ GstGLBaseMixer vaggregator;
+
+ GstGLFramebuffer *fbo;
+
+ GstCaps *out_caps;
+
+ GstGLMixerPrivate *priv;
+};
+
+struct _GstGLMixerClass
+{
+ GstGLBaseMixerClass parent_class;
+
+ GstGLMixerSetCaps set_caps;
+ GstGLMixerReset reset;
+ GstGLMixerProcessFunc process_buffers;
+ GstGLMixerProcessTextures process_textures;
+};
+
+GType gst_gl_mixer_get_type(void);
+
+gboolean gst_gl_mixer_process_textures (GstGLMixer * mix, GstBuffer * outbuf);
+
+G_END_DECLS
+#endif /* __GST_GL_MIXER_H__ */
diff --git a/ext/gl/gstglmixerbin.c b/ext/gl/gstglmixerbin.c
new file mode 100644
index 000000000..1e6ebeb14
--- /dev/null
+++ b/ext/gl/gstglmixerbin.c
@@ -0,0 +1,620 @@
+/*
+ *
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+
+#include "gstglmixerbin.h"
+
+#define GST_CAT_DEFAULT gst_gl_mixer_bin_debug
+GST_DEBUG_CATEGORY (gst_gl_mixer_bin_debug);
+
+#define DEFAULT_LATENCY 0
+#define DEFAULT_START_TIME_SELECTION 0
+#define DEFAULT_START_TIME (-1)
+
+typedef enum
+{
+ GST_GL_MIXER_BIN_START_TIME_SELECTION_ZERO,
+ GST_GL_MIXER_BIN_START_TIME_SELECTION_FIRST,
+ GST_GL_MIXER_BIN_START_TIME_SELECTION_SET
+} GstGLMixerBinStartTimeSelection;
+
+static GType
+gst_gl_mixer_bin_start_time_selection_get_type (void)
+{
+ static GType gtype = 0;
+
+ if (gtype == 0) {
+ static const GEnumValue values[] = {
+ {GST_GL_MIXER_BIN_START_TIME_SELECTION_ZERO,
+ "Start at 0 running time (default)", "zero"},
+ {GST_GL_MIXER_BIN_START_TIME_SELECTION_FIRST,
+ "Start at first observed input running time", "first"},
+ {GST_GL_MIXER_BIN_START_TIME_SELECTION_SET,
+ "Set start time with start-time property", "set"},
+ {0, NULL, NULL}
+ };
+
+ gtype = g_enum_register_static ("GstGLMixerBinStartTimeSelection", values);
+ }
+ return gtype;
+}
+
+struct input_chain
+{
+ GstGLMixerBin *self;
+ GstGhostPad *ghost_pad;
+ GstElement *upload;
+ GstElement *in_convert;
+ GstPad *mixer_pad;
+};
+
+static void
+_free_input_chain (struct input_chain *chain)
+{
+ if (!chain)
+ return;
+
+ chain->ghost_pad = NULL;
+
+ if (chain->upload) {
+ gst_element_set_state (chain->upload, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (chain->self), chain->upload);
+ chain->upload = NULL;
+ }
+
+ if (chain->in_convert) {
+ gst_element_set_state (chain->in_convert, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (chain->self), chain->in_convert);
+ chain->in_convert = NULL;
+ }
+
+ if (chain->mixer_pad) {
+ gst_element_release_request_pad (chain->self->mixer, chain->mixer_pad);
+ gst_object_unref (chain->mixer_pad);
+ chain->mixer_pad = NULL;
+ }
+
+ g_free (chain);
+}
+
+struct _GstGLMixerBinPrivate
+{
+ gboolean running;
+
+ GList *input_chains;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MIXER,
+ PROP_LATENCY,
+ PROP_START_TIME_SELECTION,
+ PROP_START_TIME,
+};
+
+enum
+{
+ SIGNAL_0,
+ SIGNAL_CREATE_ELEMENT,
+ LAST_SIGNAL
+};
+
+static void gst_gl_mixer_bin_child_proxy_init (gpointer g_iface,
+ gpointer iface_data);
+
+#define GST_GL_MIXER_BIN_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_TYPE_GL_MIXER_BIN, GstGLMixerBinPrivate))
+G_DEFINE_TYPE_WITH_CODE (GstGLMixerBin, gst_gl_mixer_bin, GST_TYPE_BIN,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
+ gst_gl_mixer_bin_child_proxy_init));
+
+static guint gst_gl_mixer_bin_signals[LAST_SIGNAL] = { 0 };
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(ANY)")
+ );
+
+static void gst_gl_mixer_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_mixer_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_gl_mixer_bin_dispose (GObject * object);
+
+static GstPad *gst_gl_mixer_bin_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps);
+static void gst_gl_mixer_bin_release_pad (GstElement * element, GstPad * pad);
+static GstStateChangeReturn gst_gl_mixer_bin_change_state (GstElement *
+ element, GstStateChange transition);
+
+static void
+gst_gl_mixer_bin_class_init (GstGLMixerBinClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstCaps *upload_caps;
+
+ g_type_class_add_private (klass, sizeof (GstGLMixerBinPrivate));
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glmixerbin", 0,
+ "opengl mixer bin");
+
+ element_class->request_new_pad = gst_gl_mixer_bin_request_new_pad;
+ element_class->release_pad = gst_gl_mixer_bin_release_pad;
+ element_class->change_state = gst_gl_mixer_bin_change_state;
+
+ gobject_class->get_property = gst_gl_mixer_bin_get_property;
+ gobject_class->set_property = gst_gl_mixer_bin_set_property;
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_gl_mixer_bin_dispose);
+
+ g_object_class_install_property (gobject_class, PROP_MIXER,
+ g_param_spec_object ("mixer",
+ "GL mixer element",
+ "The GL mixer chain to use",
+ GST_TYPE_ELEMENT,
+ GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_LATENCY,
+ g_param_spec_int64 ("latency", "Buffer latency",
+ "Additional latency in live mode to allow upstream "
+ "to take longer to produce buffers for the current "
+ "position", 0,
+ (G_MAXLONG == G_MAXINT64) ? G_MAXINT64 : (G_MAXLONG * GST_SECOND - 1),
+ DEFAULT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_START_TIME_SELECTION,
+ g_param_spec_enum ("start-time-selection", "Start Time Selection",
+ "Decides which start time is output",
+ gst_gl_mixer_bin_start_time_selection_get_type (),
+ DEFAULT_START_TIME_SELECTION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_START_TIME,
+ g_param_spec_uint64 ("start-time", "Start Time",
+ "Start time to use if start-time-selection=set", 0,
+ G_MAXUINT64,
+ DEFAULT_START_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstMixerBin::create-element:
+ * @object: the #GstGLMixerBin
+ *
+ * Will be emitted when we need the processing element/s that this bin will use
+ *
+ * Returns: a new #GstElement
+ */
+ gst_gl_mixer_bin_signals[SIGNAL_CREATE_ELEMENT] =
+ g_signal_new ("create-element", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ GST_TYPE_ELEMENT, 0);
+
+ gst_element_class_add_static_pad_template (element_class, &src_factory);
+
+ upload_caps = gst_gl_upload_get_input_template_caps ();
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST,
+ upload_caps));
+ gst_caps_unref (upload_caps);
+
+ gst_element_class_set_metadata (element_class, "OpenGL video_mixer empty bin",
+ "Bin/Filter/Effect/Video/Mixer", "OpenGL video_mixer empty bin",
+ "Matthew Waters <matthew@centricular.com>");
+}
+
+static void
+gst_gl_mixer_bin_init (GstGLMixerBin * self)
+{
+ gboolean res = TRUE;
+ GstPad *pad;
+
+ self->priv = GST_GL_MIXER_BIN_GET_PRIVATE (self);
+
+ self->out_convert = gst_element_factory_make ("glcolorconvert", NULL);
+ self->download = gst_element_factory_make ("gldownload", NULL);
+ res &= gst_bin_add (GST_BIN (self), self->out_convert);
+ res &= gst_bin_add (GST_BIN (self), self->download);
+
+ res &=
+ gst_element_link_pads (self->out_convert, "src", self->download, "sink");
+
+ pad = gst_element_get_static_pad (self->download, "src");
+ if (!pad) {
+ res = FALSE;
+ } else {
+ GST_DEBUG_OBJECT (self, "setting target src pad %" GST_PTR_FORMAT, pad);
+ self->srcpad = gst_ghost_pad_new ("src", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
+ gst_object_unref (pad);
+ }
+
+ if (!res)
+ GST_ERROR_OBJECT (self, "failed to create output chain");
+}
+
+static void
+gst_gl_mixer_bin_dispose (GObject * object)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
+ GList *l = self->priv->input_chains;
+
+ while (l) {
+ struct input_chain *chain = l->data;
+
+ if (self->mixer && chain->mixer_pad) {
+ gst_element_release_request_pad (GST_ELEMENT (self->mixer),
+ chain->mixer_pad);
+ gst_object_unref (chain->mixer_pad);
+ chain->mixer_pad = NULL;
+ }
+
+ l = l->next;
+ }
+
+ g_list_free_full (self->priv->input_chains, (GDestroyNotify) g_free);
+
+ G_OBJECT_CLASS (gst_gl_mixer_bin_parent_class)->dispose (object);
+}
+
+static gboolean
+_create_input_chain (GstGLMixerBin * self, struct input_chain *chain,
+ GstPad * mixer_pad)
+{
+ GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
+ GstPad *pad;
+ gboolean res = TRUE;
+ gchar *name;
+
+ chain->self = self;
+ chain->mixer_pad = mixer_pad;
+
+ chain->upload = gst_element_factory_make ("glupload", NULL);
+ chain->in_convert = gst_element_factory_make ("glcolorconvert", NULL);
+
+ res &= gst_bin_add (GST_BIN (self), chain->in_convert);
+ res &= gst_bin_add (GST_BIN (self), chain->upload);
+
+ pad = gst_element_get_static_pad (chain->in_convert, "src");
+ if (gst_pad_link (pad, mixer_pad) != GST_PAD_LINK_OK) {
+ gst_object_unref (pad);
+ return FALSE;
+ }
+ gst_object_unref (pad);
+ res &=
+ gst_element_link_pads (chain->upload, "src", chain->in_convert, "sink");
+
+ pad = gst_element_get_static_pad (chain->upload, "sink");
+ if (!pad) {
+ return FALSE;
+ } else {
+ GST_DEBUG_OBJECT (self, "setting target sink pad %" GST_PTR_FORMAT, pad);
+ name = gst_object_get_name (GST_OBJECT (mixer_pad));
+ if (klass->create_input_pad) {
+ chain->ghost_pad = klass->create_input_pad (self, chain->mixer_pad);
+ gst_object_set_name (GST_OBJECT (chain->ghost_pad), name);
+ gst_ghost_pad_set_target (chain->ghost_pad, pad);
+ } else {
+ chain->ghost_pad =
+ GST_GHOST_PAD (gst_ghost_pad_new (GST_PAD_NAME (chain->mixer_pad),
+ pad));
+ }
+ g_free (name);
+
+ GST_OBJECT_LOCK (self);
+ if (self->priv->running)
+ gst_pad_set_active (GST_PAD (chain->ghost_pad), TRUE);
+ GST_OBJECT_UNLOCK (self);
+
+ gst_element_add_pad (GST_ELEMENT_CAST (self), GST_PAD (chain->ghost_pad));
+ gst_object_unref (pad);
+ }
+
+ gst_element_sync_state_with_parent (chain->upload);
+ gst_element_sync_state_with_parent (chain->in_convert);
+
+ return TRUE;
+}
+
+static GstPadTemplate *
+_find_element_pad_template (GstElement * element,
+ GstPadDirection direction, GstPadPresence presence)
+{
+ GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
+ GList *templ_list = gst_element_class_get_pad_template_list (klass);
+ GstPadTemplate *templ;
+
+ /* find suitable template */
+ while (templ_list) {
+ templ = (GstPadTemplate *) templ_list->data;
+
+ if (GST_PAD_TEMPLATE_DIRECTION (templ) != direction
+ || GST_PAD_TEMPLATE_PRESENCE (templ) != presence) {
+ templ_list = templ_list->next;
+ templ = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ return templ;
+}
+
+static gboolean
+_connect_mixer_element (GstGLMixerBin * self)
+{
+ gboolean res = TRUE;
+
+ g_return_val_if_fail (self->priv->input_chains == NULL, FALSE);
+
+ gst_object_set_name (GST_OBJECT (self->mixer), "mixer");
+ res &= gst_bin_add (GST_BIN (self), self->mixer);
+
+ res &= gst_element_link_pads (self->mixer, "src", self->out_convert, "sink");
+
+ if (!res)
+ GST_ERROR_OBJECT (self, "Failed to link mixer element into the pipeline");
+
+ gst_element_sync_state_with_parent (self->mixer);
+
+ return res;
+}
+
+void
+gst_gl_mixer_bin_finish_init_with_element (GstGLMixerBin * self,
+ GstElement * element)
+{
+ g_return_if_fail (GST_IS_ELEMENT (element));
+
+ self->mixer = element;
+
+ if (!_connect_mixer_element (self)) {
+ gst_object_unref (self->mixer);
+ self->mixer = NULL;
+ }
+}
+
+void
+gst_gl_mixer_bin_finish_init (GstGLMixerBin * self)
+{
+ GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
+ GstElement *element = NULL;
+
+ if (klass->create_element)
+ element = klass->create_element ();
+
+ if (element)
+ gst_gl_mixer_bin_finish_init_with_element (self, element);
+}
+
+static void
+gst_gl_mixer_bin_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
+
+ switch (prop_id) {
+ case PROP_MIXER:
+ g_value_set_object (value, self->mixer);
+ break;
+ default:
+ if (self->mixer)
+ g_object_get_property (G_OBJECT (self->mixer), pspec->name, value);
+ break;
+ }
+}
+
+static void
+gst_gl_mixer_bin_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
+
+ switch (prop_id) {
+ case PROP_MIXER:
+ {
+ GstElement *mixer = g_value_get_object (value);
+ /* FIXME: deal with replacing a mixer */
+ g_return_if_fail (!self->mixer || (self->mixer == mixer));
+ self->mixer = mixer;
+ if (mixer) {
+ gst_object_ref_sink (mixer);
+ _connect_mixer_element (self);
+ }
+ break;
+ }
+ default:
+ if (self->mixer)
+ g_object_set_property (G_OBJECT (self->mixer), pspec->name, value);
+ break;
+ }
+}
+
+static GstPad *
+gst_gl_mixer_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
+ const gchar * req_name, const GstCaps * caps)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
+ GstPadTemplate *mixer_templ;
+ struct input_chain *chain;
+ GstPad *mixer_pad;
+
+ chain = g_new0 (struct input_chain, 1);
+
+ mixer_templ = _find_element_pad_template (self->mixer,
+ GST_PAD_TEMPLATE_DIRECTION (templ), GST_PAD_TEMPLATE_PRESENCE (templ));
+ g_return_val_if_fail (mixer_templ, NULL);
+
+ mixer_pad =
+ gst_element_request_pad (self->mixer, mixer_templ, req_name, NULL);
+ g_return_val_if_fail (mixer_pad, NULL);
+
+ if (!_create_input_chain (self, chain, mixer_pad)) {
+ gst_element_release_request_pad (self->mixer, mixer_pad);
+ _free_input_chain (chain);
+ return NULL;
+ }
+
+ GST_OBJECT_LOCK (element);
+ self->priv->input_chains = g_list_prepend (self->priv->input_chains, chain);
+ GST_OBJECT_UNLOCK (element);
+
+ gst_child_proxy_child_added (GST_CHILD_PROXY (self),
+ G_OBJECT (chain->ghost_pad), GST_OBJECT_NAME (chain->ghost_pad));
+
+ return GST_PAD (chain->ghost_pad);
+}
+
+static void
+gst_gl_mixer_bin_release_pad (GstElement * element, GstPad * pad)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
+ GList *l = self->priv->input_chains;
+ gboolean released = FALSE;
+
+ GST_OBJECT_LOCK (element);
+ while (l) {
+ struct input_chain *chain = l->data;
+ if (GST_PAD (chain->ghost_pad) == pad) {
+ self->priv->input_chains =
+ g_list_delete_link (self->priv->input_chains, l);
+ GST_OBJECT_UNLOCK (element);
+
+ _free_input_chain (chain);
+ gst_element_remove_pad (element, pad);
+ released = TRUE;
+ break;
+ }
+ l = l->next;
+ }
+ if (!released)
+ GST_OBJECT_UNLOCK (element);
+}
+
+static GstStateChangeReturn
+gst_gl_mixer_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
+ GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
+ GstStateChangeReturn ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ GST_OBJECT_LOCK (element);
+ if (!self->mixer) {
+ if (klass->create_element)
+ self->mixer = klass->create_element ();
+
+ if (!self->mixer)
+ g_signal_emit (element,
+ gst_gl_mixer_bin_signals[SIGNAL_CREATE_ELEMENT], 0, &self->mixer);
+
+ if (!self->mixer) {
+ GST_ERROR_OBJECT (element, "Failed to retrieve element");
+ GST_OBJECT_UNLOCK (element);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ GST_OBJECT_UNLOCK (element);
+ if (!_connect_mixer_element (self))
+ return GST_STATE_CHANGE_FAILURE;
+
+ GST_OBJECT_LOCK (element);
+ }
+ self->priv->running = TRUE;
+ GST_OBJECT_UNLOCK (element);
+ break;
+ default:
+ break;
+ }
+
+ ret =
+ GST_ELEMENT_CLASS (gst_gl_mixer_bin_parent_class)->change_state (element,
+ transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ GST_OBJECT_LOCK (self);
+ self->priv->running = FALSE;
+ GST_OBJECT_UNLOCK (self);
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static GObject *
+gst_gl_mixer_bin_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
+ guint index)
+{
+ GstGLMixerBin *mixer = GST_GL_MIXER_BIN (child_proxy);
+ GstBin *bin = GST_BIN_CAST (child_proxy);
+ GObject *res = NULL;
+
+ GST_OBJECT_LOCK (bin);
+ /* XXX: not exactly thread safe with ordering */
+ if (index < bin->numchildren) {
+ if ((res = g_list_nth_data (bin->children, index)))
+ gst_object_ref (res);
+ } else {
+ struct input_chain *chain;
+ if ((chain =
+ g_list_nth_data (mixer->priv->input_chains,
+ index - bin->numchildren))) {
+ res = gst_object_ref (chain->ghost_pad);
+ }
+ }
+ GST_OBJECT_UNLOCK (bin);
+
+ return res;
+}
+
+static guint
+gst_gl_mixer_bin_child_proxy_get_children_count (GstChildProxy * child_proxy)
+{
+ GstGLMixerBin *mixer = GST_GL_MIXER_BIN (child_proxy);
+ GstBin *bin = GST_BIN_CAST (child_proxy);
+ guint num;
+
+ GST_OBJECT_LOCK (bin);
+ num = bin->numchildren + g_list_length (mixer->priv->input_chains);
+ GST_OBJECT_UNLOCK (bin);
+
+ return num;
+}
+
+static void
+gst_gl_mixer_bin_child_proxy_init (gpointer g_iface, gpointer iface_data)
+{
+ GstChildProxyInterface *iface = g_iface;
+
+ iface->get_children_count = gst_gl_mixer_bin_child_proxy_get_children_count;
+ iface->get_child_by_index = gst_gl_mixer_bin_child_proxy_get_child_by_index;
+}
diff --git a/ext/gl/gstglmixerbin.h b/ext/gl/gstglmixerbin.h
new file mode 100644
index 000000000..5e5bb60b4
--- /dev/null
+++ b/ext/gl/gstglmixerbin.h
@@ -0,0 +1,72 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_MIXER_BIN_H__
+#define __GST_GL_MIXER_BIN_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_mixer_bin_get_type(void);
+#define GST_TYPE_GL_MIXER_BIN (gst_gl_mixer_bin_get_type())
+#define GST_GL_MIXER_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_MIXER_BIN, GstGLMixerBin))
+#define GST_GL_MIXER_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_MIXER_BIN, GstGLMixerBinClass))
+#define GST_IS_GL_MIXER_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_MIXER_BIN))
+#define GST_IS_GL_MIXER_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_MIXER_BIN))
+#define GST_GL_MIXER_BIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_MIXER_BIN,GstGLMixerBinClass))
+
+typedef struct _GstGLMixerBin GstGLMixerBin;
+typedef struct _GstGLMixerBinClass GstGLMixerBinClass;
+typedef struct _GstGLMixerBinPrivate GstGLMixerBinPrivate;
+
+struct _GstGLMixerBin
+{
+ GstBin parent;
+
+ GstElement *mixer;
+ GstElement *out_convert;
+ GstElement *download;
+ GstPad *srcpad;
+
+ GstGLMixerBinPrivate *priv;
+};
+
+struct _GstGLMixerBinClass
+{
+ GstBinClass parent_class;
+
+ GstElement * (*create_element) (void);
+ GstGhostPad * (*create_input_pad) (GstGLMixerBin * self, GstPad * mixer_pad);
+};
+
+void gst_gl_mixer_bin_finish_init (GstGLMixerBin * self);
+void gst_gl_mixer_bin_finish_init_with_element (GstGLMixerBin * self,
+ GstElement * element);
+
+G_END_DECLS
+#endif /* __GST_GL_MIXER_BIN_H__ */
diff --git a/ext/gl/gstglmosaic.c b/ext/gl/gstglmosaic.c
new file mode 100644
index 000000000..77aec599c
--- /dev/null
+++ b/ext/gl/gstglmosaic.c
@@ -0,0 +1,358 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glmosaic
+ * @title: glmosaic
+ *
+ * glmixer sub element. N gl sink pads to 1 source pad.
+ * N + 1 OpenGL contexts shared together.
+ * N <= 6 because the rendering is more a like a cube than a mosaic
+ * Each opengl input stream is rendered on a cube face
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! video/x-raw, format=YUY2 ! queue ! glmosaic name=m ! glimagesink \
+ * videotestsrc pattern=12 ! video/x-raw, format=I420, framerate=5/1, width=100, height=200 ! queue ! m. \
+ * videotestsrc ! video/x-raw, framerate=15/1, width=1500, height=1500 ! gleffects effect=3 ! queue ! m. \
+ * videotestsrc ! gleffects effect=2 ! queue ! m. \
+ * videotestsrc ! glfiltercube ! queue ! m. \
+ * videotestsrc ! gleffects effect=6 ! queue ! m.
+ * ]|
+ * FBO (Frame Buffer Object) is required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglmosaic.h"
+#include "gstglutils.h"
+
+#define GST_CAT_DEFAULT gst_gl_mosaic_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_mosaic_debug, "glmosaic", 0, "glmosaic element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLMosaic, gst_gl_mosaic, GST_TYPE_GL_MIXER,
+ DEBUG_INIT);
+
+static void gst_gl_mosaic_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_mosaic_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void gst_gl_mosaic_reset (GstGLMixer * mixer);
+static gboolean gst_gl_mosaic_init_shader (GstGLMixer * mixer,
+ GstCaps * outcaps);
+
+static gboolean gst_gl_mosaic_process_textures (GstGLMixer * mixer,
+ GstGLMemory * out_tex);
+static gboolean gst_gl_mosaic_callback (gpointer stuff);
+
+//vertex source
+static const gchar *mosaic_v_src =
+ "uniform mat4 u_matrix; \n"
+ "uniform float xrot_degree, yrot_degree, zrot_degree; \n"
+ "attribute vec4 a_position; \n"
+ "attribute vec2 a_texCoord; \n"
+ "varying vec2 v_texCoord; \n"
+ "void main() \n"
+ "{ \n"
+ " float PI = 3.14159265; \n"
+ " float xrot = xrot_degree*2.0*PI/360.0; \n"
+ " float yrot = yrot_degree*2.0*PI/360.0; \n"
+ " float zrot = zrot_degree*2.0*PI/360.0; \n"
+ " mat4 matX = mat4 ( \n"
+ " 1.0, 0.0, 0.0, 0.0, \n"
+ " 0.0, cos(xrot), sin(xrot), 0.0, \n"
+ " 0.0, -sin(xrot), cos(xrot), 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " mat4 matY = mat4 ( \n"
+ " cos(yrot), 0.0, -sin(yrot), 0.0, \n"
+ " 0.0, 1.0, 0.0, 0.0, \n"
+ " sin(yrot), 0.0, cos(yrot), 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " mat4 matZ = mat4 ( \n"
+ " cos(zrot), sin(zrot), 0.0, 0.0, \n"
+ " -sin(zrot), cos(zrot), 0.0, 0.0, \n"
+ " 0.0, 0.0, 1.0, 0.0, \n"
+ " 0.0, 0.0, 0.0, 1.0 ); \n"
+ " gl_Position = u_matrix * matZ * matY * matX * a_position; \n"
+ " v_texCoord = a_texCoord; \n"
+ "} \n";
+
+//fragment source
+static const gchar *mosaic_f_src =
+ "uniform sampler2D s_texture; \n"
+ "varying vec2 v_texCoord; \n"
+ "void main() \n"
+ "{ \n"
+ //" gl_FragColor = vec4( 1.0, 0.5, 1.0, 1.0 );\n"
+ " gl_FragColor = texture2D( s_texture, v_texCoord );\n"
+ "} \n";
+
+static void
+gst_gl_mosaic_class_init (GstGLMosaicClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->set_property = gst_gl_mosaic_set_property;
+ gobject_class->get_property = gst_gl_mosaic_get_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL mosaic",
+ "Filter/Effect/Video", "OpenGL mosaic",
+ "Julien Isorce <julien.isorce@gmail.com>");
+
+ GST_GL_MIXER_CLASS (klass)->set_caps = gst_gl_mosaic_init_shader;
+ GST_GL_MIXER_CLASS (klass)->reset = gst_gl_mosaic_reset;
+ GST_GL_MIXER_CLASS (klass)->process_textures = gst_gl_mosaic_process_textures;
+
+ GST_GL_BASE_MIXER_CLASS (klass)->supported_gl_api = GST_GL_API_OPENGL;
+}
+
+static void
+gst_gl_mosaic_init (GstGLMosaic * mosaic)
+{
+ mosaic->shader = NULL;
+}
+
+static void
+gst_gl_mosaic_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ //GstGLMosaic *mixer = GST_GL_MOSAIC (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_mosaic_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ //GstGLMosaic* mixer = GST_GL_MOSAIC (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_mosaic_reset (GstGLMixer * mixer)
+{
+ GstGLMosaic *mosaic = GST_GL_MOSAIC (mixer);
+
+ //blocking call, wait the opengl thread has destroyed the shader
+ if (mosaic->shader)
+ gst_object_unref (mosaic->shader);
+ mosaic->shader = NULL;
+}
+
+static gboolean
+gst_gl_mosaic_init_shader (GstGLMixer * mixer, GstCaps * outcaps)
+{
+ GstGLMosaic *mosaic = GST_GL_MOSAIC (mixer);
+
+ g_clear_object (&mosaic->shader);
+ //blocking call, wait the opengl thread has compiled the shader
+ return gst_gl_context_gen_shader (GST_GL_BASE_MIXER (mixer)->context,
+ mosaic_v_src, mosaic_f_src, &mosaic->shader);
+}
+
+static void
+_mosaic_render (GstGLContext * context, GstGLMosaic * mosaic)
+{
+ GstGLMixer *mixer = GST_GL_MIXER (mosaic);
+
+ gst_gl_framebuffer_draw_to_texture (mixer->fbo, mosaic->out_tex,
+ gst_gl_mosaic_callback, mosaic);
+}
+
+static gboolean
+gst_gl_mosaic_process_textures (GstGLMixer * mix, GstGLMemory * out_tex)
+{
+ GstGLMosaic *mosaic = GST_GL_MOSAIC (mix);
+ GstGLContext *context = GST_GL_BASE_MIXER (mix)->context;
+
+ mosaic->out_tex = out_tex;
+
+ gst_gl_context_thread_add (context, (GstGLContextThreadFunc) _mosaic_render,
+ mosaic);
+
+ return TRUE;
+}
+
+/* opengl scene, params: input texture (not the output mixer->texture) */
+static gboolean
+gst_gl_mosaic_callback (gpointer stuff)
+{
+ GstGLMosaic *mosaic = GST_GL_MOSAIC (stuff);
+ GstGLMixer *mixer = GST_GL_MIXER (mosaic);
+ GstGLFuncs *gl = GST_GL_BASE_MIXER (mixer)->context->gl_vtable;
+ GList *walk;
+
+ static GLfloat xrot = 0;
+ static GLfloat yrot = 0;
+ static GLfloat zrot = 0;
+
+ GLint attr_position_loc = 0;
+ GLint attr_texture_loc = 0;
+
+ const GLfloat matrix[] = {
+ 0.5f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.5f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ const GLushort indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+
+ guint count = 0;
+
+ gst_gl_context_clear_shader (GST_GL_BASE_MIXER (mixer)->context);
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->Enable (GL_DEPTH_TEST);
+
+ gl->ClearColor (0.0, 0.0, 0.0, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ gst_gl_shader_use (mosaic->shader);
+
+ attr_position_loc =
+ gst_gl_shader_get_attribute_location (mosaic->shader, "a_position");
+ attr_texture_loc =
+ gst_gl_shader_get_attribute_location (mosaic->shader, "a_texCoord");
+
+ GST_OBJECT_LOCK (mosaic);
+ walk = GST_ELEMENT (mosaic)->sinkpads;
+ while (walk) {
+ GstGLMixerPad *pad = walk->data;
+ /* *INDENT-OFF* */
+ gfloat v_vertices[] = {
+ /* front face */
+ 1.0f, 1.0f,-1.0f, 1.0f, 0.0f,
+ 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,
+ -1.0f,-1.0f,-1.0f, 0.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f, 0.0f, 0.0f,
+ /* right face */
+ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+ 1.0f,-1.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f,-1.0f,-1.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,
+ /* left face */
+ -1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,
+ -1.0f,-1.0f,-1.0f, 0.0f, 1.0f,
+ -1.0f,-1.0f, 1.0f, 0.0f, 0.0f,
+ /* top face */
+ 1.0f,-1.0f, 1.0f, 1.0f, 0.0f,
+ -1.0f,-1.0f, 1.0f, 0.0f, 0.0f,
+ -1.0f,-1.0f,-1.0f, 0.0f, 1.0f,
+ 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,
+ /* bottom face */
+ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f, 0.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+ /* back face */
+ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+ -1.0f,-1.0f, 1.0f, 0.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f, 1.0f, 1.0f
+ };
+ /* *INDENT-ON* */
+ guint in_tex;
+ guint width, height;
+
+ in_tex = pad->current_texture;
+ width = GST_VIDEO_INFO_WIDTH (&GST_VIDEO_AGGREGATOR_PAD (pad)->info);
+ height = GST_VIDEO_INFO_HEIGHT (&GST_VIDEO_AGGREGATOR_PAD (pad)->info);
+
+ if (!in_tex || width <= 0 || height <= 0) {
+ GST_DEBUG ("skipping texture:%u pad:%p width:%u height %u",
+ in_tex, pad, width, height);
+ count++;
+ walk = g_list_next (walk);
+ continue;
+ }
+
+ GST_TRACE ("processing texture:%u dimensions:%ux%u", in_tex, width, height);
+
+ gl->VertexAttribPointer (attr_position_loc, 3, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), &v_vertices[5 * 4 * count]);
+
+ gl->VertexAttribPointer (attr_texture_loc, 2, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), &v_vertices[5 * 4 * count + 3]);
+
+ gl->EnableVertexAttribArray (attr_position_loc);
+ gl->EnableVertexAttribArray (attr_texture_loc);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, in_tex);
+ gst_gl_shader_set_uniform_1i (mosaic->shader, "s_texture", 0);
+ gst_gl_shader_set_uniform_1f (mosaic->shader, "xrot_degree", xrot);
+ gst_gl_shader_set_uniform_1f (mosaic->shader, "yrot_degree", yrot);
+ gst_gl_shader_set_uniform_1f (mosaic->shader, "zrot_degree", zrot);
+ gst_gl_shader_set_uniform_matrix_4fv (mosaic->shader, "u_matrix", 1,
+ GL_FALSE, matrix);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
+
+ ++count;
+
+ walk = g_list_next (walk);
+ }
+ GST_OBJECT_UNLOCK (mosaic);
+
+ gl->DisableVertexAttribArray (attr_position_loc);
+ gl->DisableVertexAttribArray (attr_texture_loc);
+
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->Disable (GL_DEPTH_TEST);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_MIXER (mixer)->context);
+
+ xrot += 0.6f;
+ yrot += 0.4f;
+ zrot += 0.8f;
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglmosaic.h b/ext/gl/gstglmosaic.h
new file mode 100644
index 000000000..40f5d6c33
--- /dev/null
+++ b/ext/gl/gstglmosaic.h
@@ -0,0 +1,55 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_MOSAIC_H_
+#define _GST_GL_MOSAIC_H_
+
+#include "gstglmixer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_MOSAIC (gst_gl_mosaic_get_type())
+#define GST_GL_MOSAIC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_MOSAIC,GstGLMosaic))
+#define GST_IS_GL_MOSAIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_MOSAIC))
+#define GST_GL_MOSAIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_MOSAIC,GstGLMosaicClass))
+#define GST_IS_GL_MOSAIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_MOSAIC))
+#define GST_GL_MOSAIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_MOSAIC,GstGLMosaicClass))
+
+typedef struct _GstGLMosaic GstGLMosaic;
+typedef struct _GstGLMosaicClass GstGLMosaicClass;
+
+struct _GstGLMosaic
+{
+ GstGLMixer mixer;
+
+ GstGLShader *shader;
+ GstGLMemory *out_tex;
+};
+
+struct _GstGLMosaicClass
+{
+ GstGLMixerClass mixer_class;
+};
+
+GType gst_gl_mosaic_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERCUBE_H_ */
diff --git a/ext/gl/gstgloverlay.c b/ext/gl/gstgloverlay.c
new file mode 100644
index 000000000..864ea64da
--- /dev/null
+++ b/ext/gl/gstgloverlay.c
@@ -0,0 +1,832 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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:element-gloverlay
+ * @title: gloverlay
+ *
+ * Overlay GL video texture with a PNG image
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! gloverlay location=image.jpg ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) is required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/base/gsttypefindhelper.h>
+#include <gst/gl/gstglconfig.h>
+
+#include "gstgloverlay.h"
+#include "effects/gstgleffectssources.h"
+#include "gstglutils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+#define HAVE_BOOLEAN
+#endif
+#include <jpeglib.h>
+#include <png.h>
+
+#if PNG_LIBPNG_VER >= 10400
+#define int_p_NULL NULL
+#define png_infopp_NULL NULL
+#endif
+
+#define GST_CAT_DEFAULT gst_gl_overlay_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_overlay_debug, "gloverlay", 0, "gloverlay element");
+
+#define gst_gl_overlay_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLOverlay, gst_gl_overlay, GST_TYPE_GL_FILTER,
+ DEBUG_INIT);
+
+static gboolean gst_gl_overlay_set_caps (GstGLFilter * filter,
+ GstCaps * incaps, GstCaps * outcaps);
+
+static void gst_gl_overlay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_overlay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void gst_gl_overlay_before_transform (GstBaseTransform * trans,
+ GstBuffer * outbuf);
+static gboolean gst_gl_overlay_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static gboolean gst_gl_overlay_load_png (GstGLOverlay * overlay, FILE * fp);
+static gboolean gst_gl_overlay_load_jpeg (GstGLOverlay * overlay, FILE * fp);
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y,
+ PROP_RELATIVE_X,
+ PROP_RELATIVE_Y,
+ PROP_OVERLAY_WIDTH,
+ PROP_OVERLAY_HEIGHT,
+ PROP_ALPHA
+};
+
+/* *INDENT-OFF* */
+/* vertex source */
+static const gchar *overlay_v_src =
+ "attribute vec4 a_position;\n"
+ "attribute vec2 a_texcoord;\n"
+ "varying vec2 v_texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = a_position;\n"
+ " v_texcoord = a_texcoord;\n"
+ "}";
+
+/* fragment source */
+static const gchar *overlay_f_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform sampler2D texture;\n"
+ "uniform float alpha;\n"
+ "varying vec2 v_texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " vec4 rgba = texture2D( texture, v_texcoord );\n"
+ " gl_FragColor = vec4(rgba.rgb, rgba.a * alpha);\n"
+ "}\n";
+/* *INDENT-ON* */
+
+/* init resources that need a gl context */
+static gboolean
+gst_gl_overlay_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (base_filter);
+
+ if (!GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter))
+ return FALSE;
+
+ return gst_gl_context_gen_shader (base_filter->context, overlay_v_src,
+ overlay_f_src, &overlay->shader);
+}
+
+/* free resources that need a gl context */
+static void
+gst_gl_overlay_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (base_filter);
+ const GstGLFuncs *gl = base_filter->context->gl_vtable;
+
+ if (overlay->shader) {
+ gst_object_unref (overlay->shader);
+ overlay->shader = NULL;
+ }
+
+ if (overlay->image_memory) {
+ gst_memory_unref ((GstMemory *) overlay->image_memory);
+ overlay->image_memory = NULL;
+ }
+
+ if (overlay->vao) {
+ gl->DeleteVertexArrays (1, &overlay->vao);
+ overlay->vao = 0;
+ }
+
+ if (overlay->vbo) {
+ gl->DeleteBuffers (1, &overlay->vbo);
+ overlay->vbo = 0;
+ }
+
+ if (overlay->vbo_indices) {
+ gl->DeleteBuffers (1, &overlay->vbo_indices);
+ overlay->vbo_indices = 0;
+ }
+
+ if (overlay->overlay_vao) {
+ gl->DeleteVertexArrays (1, &overlay->overlay_vao);
+ overlay->overlay_vao = 0;
+ }
+
+ if (overlay->overlay_vbo) {
+ gl->DeleteBuffers (1, &overlay->overlay_vbo);
+ overlay->overlay_vbo = 0;
+ }
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static void
+gst_gl_overlay_class_init (GstGLOverlayClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_overlay_set_property;
+ gobject_class->get_property = gst_gl_overlay_get_property;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_overlay_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_overlay_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_overlay_set_caps;
+ GST_GL_FILTER_CLASS (klass)->filter_texture = gst_gl_overlay_filter_texture;
+
+ GST_BASE_TRANSFORM_CLASS (klass)->before_transform =
+ GST_DEBUG_FUNCPTR (gst_gl_overlay_before_transform);
+
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location", "location",
+ "Location of image file to overlay", NULL, GST_PARAM_CONTROLLABLE
+ | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OFFSET_X,
+ g_param_spec_int ("offset-x", "X Offset",
+ "For positive value, horizontal offset of overlay image in pixels from"
+ " left of video image. For negative value, horizontal offset of overlay"
+ " image in pixels from right of video image", G_MININT, G_MAXINT, 0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OFFSET_Y,
+ g_param_spec_int ("offset-y", "Y Offset",
+ "For positive value, vertical offset of overlay image in pixels from"
+ " top of video image. For negative value, vertical offset of overlay"
+ " image in pixels from bottom of video image", G_MININT, G_MAXINT, 0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_RELATIVE_X,
+ g_param_spec_double ("relative-x", "Relative X Offset",
+ "Horizontal offset of overlay image in fractions of video image "
+ "width, from top-left corner of video image", 0.0, 1.0, 0.0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_RELATIVE_Y,
+ g_param_spec_double ("relative-y", "Relative Y Offset",
+ "Vertical offset of overlay image in fractions of video image "
+ "height, from top-left corner of video image", 0.0, 1.0, 0.0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OVERLAY_WIDTH,
+ g_param_spec_int ("overlay-width", "Overlay Width",
+ "Width of overlay image in pixels (0 = same as overlay image)", 0,
+ G_MAXINT, 0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OVERLAY_HEIGHT,
+ g_param_spec_int ("overlay-height", "Overlay Height",
+ "Height of overlay image in pixels (0 = same as overlay image)", 0,
+ G_MAXINT, 0,
+ GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_ALPHA,
+ g_param_spec_double ("alpha", "Alpha", "Global alpha of overlay image",
+ 0.0, 1.0, 1.0, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
+ | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class,
+ "Gstreamer OpenGL Overlay", "Filter/Effect/Video",
+ "Overlay GL video texture with a JPEG/PNG image",
+ "Filippo Argiolas <filippo.argiolas@gmail.com>, "
+ "Matthew Waters <matthew@centricular.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_overlay_init (GstGLOverlay * overlay)
+{
+ overlay->offset_x = 0;
+ overlay->offset_y = 0;
+
+ overlay->relative_x = 0.0;
+ overlay->relative_y = 0.0;
+
+ overlay->overlay_width = 0;
+ overlay->overlay_height = 0;
+
+ overlay->alpha = 1.0;
+}
+
+static void
+gst_gl_overlay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_free (overlay->location);
+ overlay->location_has_changed = TRUE;
+ overlay->location = g_value_dup_string (value);
+ break;
+ case PROP_OFFSET_X:
+ overlay->offset_x = g_value_get_int (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_OFFSET_Y:
+ overlay->offset_y = g_value_get_int (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_RELATIVE_X:
+ overlay->relative_x = g_value_get_double (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_RELATIVE_Y:
+ overlay->relative_y = g_value_get_double (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_OVERLAY_WIDTH:
+ overlay->overlay_width = g_value_get_int (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_OVERLAY_HEIGHT:
+ overlay->overlay_height = g_value_get_int (value);
+ overlay->geometry_change = TRUE;
+ break;
+ case PROP_ALPHA:
+ overlay->alpha = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_overlay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_value_set_string (value, overlay->location);
+ break;
+ case PROP_OFFSET_X:
+ g_value_set_int (value, overlay->offset_x);
+ break;
+ case PROP_OFFSET_Y:
+ g_value_set_int (value, overlay->offset_y);
+ break;
+ case PROP_RELATIVE_X:
+ g_value_set_double (value, overlay->relative_x);
+ break;
+ case PROP_RELATIVE_Y:
+ g_value_set_double (value, overlay->relative_y);
+ break;
+ case PROP_OVERLAY_WIDTH:
+ g_value_set_int (value, overlay->overlay_width);
+ break;
+ case PROP_OVERLAY_HEIGHT:
+ g_value_set_int (value, overlay->overlay_height);
+ break;
+ case PROP_ALPHA:
+ g_value_set_double (value, overlay->alpha);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_overlay_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
+ GstStructure *s = gst_caps_get_structure (incaps, 0);
+ gint width = 0;
+ gint height = 0;
+
+ gst_structure_get_int (s, "width", &width);
+ gst_structure_get_int (s, "height", &height);
+
+ overlay->window_width = width;
+ overlay->window_height = height;
+
+ return TRUE;
+}
+
+static void
+_unbind_buffer (GstGLOverlay * overlay)
+{
+ GstGLFilter *filter = GST_GL_FILTER (overlay);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (overlay)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (filter->draw_attr_position_loc);
+ gl->DisableVertexAttribArray (filter->draw_attr_texture_loc);
+}
+
+static void
+_bind_buffer (GstGLOverlay * overlay, GLuint vbo)
+{
+ GstGLFilter *filter = GST_GL_FILTER (overlay);
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (overlay)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, vbo);
+
+ gl->EnableVertexAttribArray (filter->draw_attr_position_loc);
+ gl->EnableVertexAttribArray (filter->draw_attr_texture_loc);
+
+ gl->VertexAttribPointer (filter->draw_attr_position_loc, 3, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), (void *) 0);
+ gl->VertexAttribPointer (filter->draw_attr_texture_loc, 2, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+}
+
+/* *INDENT-OFF* */
+float v_vertices[] = {
+/*| Vertex | TexCoord |*/
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+};
+
+static const GLushort indices[] = { 0, 1, 2, 0, 2, 3, };
+/* *INDENT-ON* */
+
+static gboolean
+gst_gl_overlay_callback (GstGLFilter * filter, GstGLMemory * in_tex,
+ gpointer stuff)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
+ GstMapInfo map_info;
+ guint image_tex;
+ gboolean memory_mapped = FALSE;
+ const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+ gboolean ret = FALSE;
+
+#if GST_GL_HAVE_OPENGL
+ if (gst_gl_context_get_gl_api (GST_GL_BASE_FILTER (filter)->context) &
+ GST_GL_API_OPENGL) {
+
+ gl->MatrixMode (GL_PROJECTION);
+ gl->LoadIdentity ();
+ }
+#endif
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, gst_gl_memory_get_texture_id (in_tex));
+
+ gst_gl_shader_use (overlay->shader);
+
+ gst_gl_shader_set_uniform_1f (overlay->shader, "alpha", 1.0f);
+ gst_gl_shader_set_uniform_1i (overlay->shader, "texture", 0);
+
+ 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");
+
+ gst_gl_filter_draw_fullscreen_quad (filter);
+
+ if (!overlay->image_memory)
+ goto out;
+
+ if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
+ GST_MAP_READ | GST_MAP_GL) || map_info.data == NULL)
+ goto out;
+
+ memory_mapped = TRUE;
+ image_tex = *(guint *) map_info.data;
+
+ if (!overlay->overlay_vbo) {
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &overlay->overlay_vao);
+ gl->BindVertexArray (overlay->overlay_vao);
+ }
+
+ gl->GenBuffers (1, &overlay->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+
+ gl->GenBuffers (1, &overlay->overlay_vbo);
+ gl->BindBuffer (GL_ARRAY_BUFFER, overlay->overlay_vbo);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, overlay->vbo_indices);
+ overlay->geometry_change = TRUE;
+ }
+
+ if (gl->GenVertexArrays) {
+ gl->BindVertexArray (overlay->overlay_vao);
+ }
+
+ if (overlay->geometry_change) {
+ gint render_width, render_height;
+ gfloat x, y, image_width, image_height;
+
+ /* *INDENT-OFF* */
+ float vertices[] = {
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 0.0f, 0.0, 1.0f,
+ };
+ /* *INDENT-ON* */
+
+ /* scale from [0, 1] -> [-1, 1] */
+ x = ((gfloat) overlay->offset_x / (gfloat) overlay->window_width +
+ overlay->relative_x) * 2.0f - 1.0;
+ y = ((gfloat) overlay->offset_y / (gfloat) overlay->window_height +
+ overlay->relative_y) * 2.0f - 1.0;
+ /* scale from [0, 1] -> [0, 2] */
+ render_width =
+ overlay->overlay_width >
+ 0 ? overlay->overlay_width : overlay->image_width;
+ render_height =
+ overlay->overlay_height >
+ 0 ? overlay->overlay_height : overlay->image_height;
+ image_width =
+ ((gfloat) render_width / (gfloat) overlay->window_width) * 2.0f;
+ image_height =
+ ((gfloat) render_height / (gfloat) overlay->window_height) * 2.0f;
+
+ vertices[0] = vertices[15] = x;
+ vertices[5] = vertices[10] = x + image_width;
+ vertices[1] = vertices[6] = y;
+ vertices[11] = vertices[16] = y + image_height;
+
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+ }
+
+ _bind_buffer (overlay, overlay->overlay_vbo);
+
+ gl->BindTexture (GL_TEXTURE_2D, image_tex);
+ gst_gl_shader_set_uniform_1f (overlay->shader, "alpha", overlay->alpha);
+
+ gl->Enable (GL_BLEND);
+ gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ gl->BlendEquation (GL_FUNC_ADD);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ gl->Disable (GL_BLEND);
+ ret = TRUE;
+
+out:
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (overlay);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
+
+ if (memory_mapped)
+ gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
+
+ overlay->geometry_change = FALSE;
+
+ return ret;
+}
+
+static gboolean
+load_file (GstGLOverlay * overlay)
+{
+ FILE *fp;
+ guint8 buff[16];
+ gsize n_read;
+ GstCaps *caps;
+ GstStructure *structure;
+ gboolean success = FALSE;
+
+ if (overlay->location == NULL)
+ return TRUE;
+
+ if ((fp = fopen (overlay->location, "rb")) == NULL) {
+ GST_ELEMENT_ERROR (overlay, RESOURCE, NOT_FOUND, ("Can't open file"),
+ ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ n_read = fread (buff, 1, sizeof (buff), fp);
+ if (n_read != sizeof (buff)) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Can't read file header"),
+ ("File: %s", overlay->location));
+ goto out;
+ }
+
+ caps = gst_type_find_helper_for_data (GST_OBJECT (overlay), buff,
+ sizeof (buff), NULL);
+
+ if (caps == NULL) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Can't find file type"),
+ ("File: %s", overlay->location));
+ goto out;
+ }
+
+ fseek (fp, 0, SEEK_SET);
+
+ structure = gst_caps_get_structure (caps, 0);
+ if (gst_structure_has_name (structure, "image/jpeg")) {
+ success = gst_gl_overlay_load_jpeg (overlay, fp);
+ } else if (gst_structure_has_name (structure, "image/png")) {
+ success = gst_gl_overlay_load_png (overlay, fp);
+ } else {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("Image type not supported"),
+ ("File: %s", overlay->location));
+ }
+
+out:
+ fclose (fp);
+ gst_caps_replace (&caps, NULL);
+
+ return success;
+}
+
+static gboolean
+gst_gl_overlay_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
+ GstGLMemory * out_tex)
+{
+ GstGLOverlay *overlay = GST_GL_OVERLAY (filter);
+
+ if (overlay->location_has_changed) {
+ if (overlay->image_memory) {
+ gst_memory_unref ((GstMemory *) overlay->image_memory);
+ overlay->image_memory = NULL;
+ }
+
+ if (!load_file (overlay))
+ return FALSE;
+
+ overlay->location_has_changed = FALSE;
+ }
+
+ gst_gl_filter_render_to_target (filter, in_tex, out_tex,
+ gst_gl_overlay_callback, overlay);
+
+ return TRUE;
+}
+
+static void
+gst_gl_overlay_before_transform (GstBaseTransform * trans, GstBuffer * outbuf)
+{
+ GstClockTime stream_time;
+
+ stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
+ GST_BUFFER_TIMESTAMP (outbuf));
+
+ if (GST_CLOCK_TIME_IS_VALID (stream_time))
+ gst_object_sync_values (GST_OBJECT (trans), stream_time);
+}
+
+static void
+user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
+{
+ g_warning ("%s\n", warning_msg);
+}
+
+static gboolean
+gst_gl_overlay_load_jpeg (GstGLOverlay * overlay, FILE * fp)
+{
+ GstGLBaseMemoryAllocator *mem_allocator;
+ GstGLVideoAllocationParams *params;
+ GstVideoInfo v_info;
+ GstVideoAlignment v_align;
+ GstMapInfo map_info;
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW j;
+ int i;
+
+ jpeg_create_decompress (&cinfo);
+ cinfo.err = jpeg_std_error (&jerr);
+ jpeg_stdio_src (&cinfo, fp);
+ jpeg_read_header (&cinfo, TRUE);
+ jpeg_start_decompress (&cinfo);
+ overlay->image_width = cinfo.image_width;
+ overlay->image_height = cinfo.image_height;
+
+ if (cinfo.num_components == 1)
+ gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_Y444,
+ overlay->image_width, overlay->image_height);
+ else
+ gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_RGB,
+ overlay->image_width, overlay->image_height);
+
+ gst_video_alignment_reset (&v_align);
+ v_align.stride_align[0] = 32 - 1;
+ gst_video_info_align (&v_info, &v_align);
+
+ mem_allocator =
+ GST_GL_BASE_MEMORY_ALLOCATOR (gst_gl_memory_allocator_get_default
+ (GST_GL_BASE_FILTER (overlay)->context));
+ params =
+ gst_gl_video_allocation_params_new (GST_GL_BASE_FILTER (overlay)->context,
+ NULL, &v_info, 0, &v_align, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
+ overlay->image_memory = (GstGLMemory *)
+ gst_gl_base_memory_alloc (mem_allocator,
+ (GstGLAllocationParams *) params);
+ gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
+ gst_object_unref (mem_allocator);
+
+ if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
+ GST_MAP_WRITE)) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE, ("failed to map memory"),
+ ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ for (i = 0; i < overlay->image_height; ++i) {
+ j = map_info.data + v_info.stride[0] * i;
+ jpeg_read_scanlines (&cinfo, &j, 1);
+ }
+ jpeg_finish_decompress (&cinfo);
+ jpeg_destroy_decompress (&cinfo);
+ gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_overlay_load_png (GstGLOverlay * overlay, FILE * fp)
+{
+ GstGLBaseMemoryAllocator *mem_allocator;
+ GstGLVideoAllocationParams *params;
+ GstVideoInfo v_info;
+ GstMapInfo map_info;
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ gint bit_depth = 0;
+ gint color_type = 0;
+ gint interlace_type = 0;
+ guint y = 0;
+ guchar **rows = NULL;
+ gint filler;
+ png_byte magic[8];
+ gint n_read;
+
+ if (!GST_GL_BASE_FILTER (overlay)->context)
+ return FALSE;
+
+ /* Read magic number */
+ n_read = fread (magic, 1, sizeof (magic), fp);
+ if (n_read != sizeof (magic)) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("can't read PNG magic number"), ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ /* Check for valid magic number */
+ if (png_sig_cmp (magic, 0, sizeof (magic))) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("not a valid PNG image"), ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (png_ptr == NULL) {
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("failed to initialize the png_struct"), ("File: %s",
+ overlay->location));
+ return FALSE;
+ }
+
+ png_set_error_fn (png_ptr, NULL, NULL, user_warning_fn);
+
+ info_ptr = png_create_info_struct (png_ptr);
+ if (info_ptr == NULL) {
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("failed to initialize the memory for image information"),
+ ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ png_init_io (png_ptr, fp);
+
+ png_set_sig_bytes (png_ptr, sizeof (magic));
+
+ png_read_info (png_ptr, info_ptr);
+
+ png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_type, int_p_NULL, int_p_NULL);
+
+ if (color_type == PNG_COLOR_TYPE_RGB) {
+ filler = 0xff;
+ png_set_filler (png_ptr, filler, PNG_FILLER_AFTER);
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (color_type != PNG_COLOR_TYPE_RGB_ALPHA) {
+ png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("color type is not rgb"), ("File: %s", overlay->location));
+ return FALSE;
+ }
+
+ overlay->image_width = width;
+ overlay->image_height = height;
+
+ gst_video_info_set_format (&v_info, GST_VIDEO_FORMAT_RGBA, width, height);
+ mem_allocator =
+ GST_GL_BASE_MEMORY_ALLOCATOR (gst_gl_memory_allocator_get_default
+ (GST_GL_BASE_FILTER (overlay)->context));
+ params =
+ gst_gl_video_allocation_params_new (GST_GL_BASE_FILTER (overlay)->context,
+ NULL, &v_info, 0, NULL, GST_GL_TEXTURE_TARGET_2D, GST_GL_RGBA);
+ overlay->image_memory = (GstGLMemory *)
+ gst_gl_base_memory_alloc (mem_allocator,
+ (GstGLAllocationParams *) params);
+ gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
+ gst_object_unref (mem_allocator);
+
+ if (!gst_memory_map ((GstMemory *) overlay->image_memory, &map_info,
+ GST_MAP_WRITE)) {
+ png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
+ GST_ELEMENT_ERROR (overlay, STREAM, DECODE,
+ ("failed to map memory"), ("File: %s", overlay->location));
+ return FALSE;
+ }
+ rows = (guchar **) malloc (sizeof (guchar *) * height);
+
+ for (y = 0; y < height; ++y)
+ rows[y] = (guchar *) (map_info.data + y * width * 4);
+
+ png_read_image (png_ptr, rows);
+
+ free (rows);
+ gst_memory_unmap ((GstMemory *) overlay->image_memory, &map_info);
+
+ png_read_end (png_ptr, info_ptr);
+ png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstgloverlay.h b/ext/gl/gstgloverlay.h
new file mode 100644
index 000000000..dad01e4eb
--- /dev/null
+++ b/ext/gl/gstgloverlay.h
@@ -0,0 +1,84 @@
+/*
+ * GStreamer
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_OVERLAY_H_
+#define _GST_GL_OVERLAY_H_
+
+#include <gst/gl/gstglfilter.h>
+#include <gst/gl/gstglfuncs.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_OVERLAY (gst_gl_overlay_get_type())
+#define GST_GL_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_GL_OVERLAY,GstGLOverlay))
+#define GST_IS_GL_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GL_OVERLAY))
+#define GST_GL_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) , GST_TYPE_GL_OVERLAY,GstGLOverlayClass))
+#define GST_IS_GL_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) , GST_TYPE_GL_OVERLAY))
+#define GST_GL_OVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) , GST_TYPE_GL_OVERLAY,GstGLOverlayClass))
+
+typedef struct _GstGLOverlay GstGLOverlay;
+typedef struct _GstGLOverlayClass GstGLOverlayClass;
+
+struct _GstGLOverlay
+{
+ GstGLFilter filter;
+
+ /* properties */
+ gchar *location;
+ gint offset_x;
+ gint offset_y;
+
+ gdouble relative_x;
+ gdouble relative_y;
+
+ gint overlay_width;
+ gint overlay_height;
+
+ gdouble alpha;
+
+ /* <private> */
+ GstGLShader *shader;
+ GstGLMemory *image_memory;
+
+ gboolean location_has_changed;
+ gint window_width, window_height;
+ gint image_width, image_height;
+
+ gboolean geometry_change;
+
+ GLuint vao;
+ GLuint overlay_vao;
+ GLuint vbo;
+ GLuint overlay_vbo;
+ GLuint vbo_indices;
+ GLuint attr_position;
+ GLuint attr_texture;
+};
+
+struct _GstGLOverlayClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_overlay_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GL_OVERLAY_H_ */
diff --git a/ext/gl/gstglsinkbin.c b/ext/gl/gstglsinkbin.c
new file mode 100644
index 000000000..310634c32
--- /dev/null
+++ b/ext/gl/gstglsinkbin.c
@@ -0,0 +1,599 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/video/videooverlay.h>
+#include <gst/video/navigation.h>
+#include <gst/controller/gstproxycontrolbinding.h>
+
+#include "gstglsinkbin.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gl_sink_bin);
+#define GST_CAT_DEFAULT gst_debug_gl_sink_bin
+
+static void gst_gl_sink_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_gl_sink_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static GstStateChangeReturn gst_gl_sink_bin_change_state (GstElement * element,
+ GstStateChange transition);
+
+static void gst_gl_sink_bin_video_overlay_init (gpointer g_iface,
+ gpointer g_iface_data);
+static void gst_gl_sink_bin_navigation_interface_init (gpointer g_iface,
+ gpointer g_iface_data);
+static void gst_gl_sink_bin_color_balance_init (gpointer g_iface,
+ gpointer g_iface_data);
+
+#define DEFAULT_SYNC TRUE
+#define DEFAULT_MAX_LATENESS -1
+#define DEFAULT_QOS FALSE
+#define DEFAULT_ASYNC TRUE
+#define DEFAULT_TS_OFFSET 0
+#define DEFAULT_BLOCKSIZE 4096
+#define DEFAULT_RENDER_DELAY 0
+#define DEFAULT_ENABLE_LAST_SAMPLE TRUE
+#define DEFAULT_THROTTLE_TIME 0
+#define DEFAULT_MAX_BITRATE 0
+
+/* GstGLColorBalance properties */
+#define DEFAULT_PROP_CONTRAST 1.0
+#define DEFAULT_PROP_BRIGHTNESS 0.0
+#define DEFAULT_PROP_HUE 0.0
+#define DEFAULT_PROP_SATURATION 1.0
+
+enum
+{
+ PROP_0,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_SINK,
+ PROP_SYNC,
+ PROP_MAX_LATENESS,
+ PROP_QOS,
+ PROP_ASYNC,
+ PROP_TS_OFFSET,
+ PROP_ENABLE_LAST_SAMPLE,
+ PROP_LAST_SAMPLE,
+ PROP_BLOCKSIZE,
+ PROP_RENDER_DELAY,
+ PROP_THROTTLE_TIME,
+ PROP_MAX_BITRATE,
+ PROP_CONTRAST,
+ PROP_BRIGHTNESS,
+ PROP_HUE,
+ PROP_SATURATION,
+};
+
+enum
+{
+ SIGNAL_0,
+ SIGNAL_CREATE_ELEMENT,
+ SIGNAL_LAST,
+};
+
+static guint gst_gl_sink_bin_signals[SIGNAL_LAST] = { 0, };
+
+#define gst_gl_sink_bin_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLSinkBin, gst_gl_sink_bin,
+ GST_TYPE_BIN, G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
+ gst_gl_sink_bin_video_overlay_init);
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_gl_sink_bin_navigation_interface_init);
+ G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
+ gst_gl_sink_bin_color_balance_init)
+ GST_DEBUG_CATEGORY_INIT (gst_debug_gl_sink_bin, "glimagesink", 0,
+ "OpenGL Video Sink Bin"));
+
+static void
+gst_gl_sink_bin_class_init (GstGLSinkBinClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstCaps *upload_caps;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ element_class->change_state = gst_gl_sink_bin_change_state;
+
+ gobject_class->set_property = gst_gl_sink_bin_set_property;
+ gobject_class->get_property = gst_gl_sink_bin_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio", TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_SINK,
+ g_param_spec_object ("sink",
+ "GL sink element",
+ "The GL sink chain to use",
+ GST_TYPE_ELEMENT,
+ GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /* base sink */
+ g_object_class_install_property (gobject_class, PROP_SYNC,
+ g_param_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_MAX_LATENESS,
+ g_param_spec_int64 ("max-lateness", "Max Lateness",
+ "Maximum number of nanoseconds that a buffer can be late before it "
+ "is dropped (-1 unlimited)", -1, G_MAXINT64, DEFAULT_MAX_LATENESS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_QOS,
+ g_param_spec_boolean ("qos", "Qos",
+ "Generate Quality-of-Service events upstream", DEFAULT_QOS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_ASYNC,
+ g_param_spec_boolean ("async", "Async",
+ "Go asynchronously to PAUSED", DEFAULT_ASYNC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_TS_OFFSET,
+ g_param_spec_int64 ("ts-offset", "TS Offset",
+ "Timestamp offset in nanoseconds", G_MININT64, G_MAXINT64,
+ DEFAULT_TS_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_ENABLE_LAST_SAMPLE,
+ g_param_spec_boolean ("enable-last-sample", "Enable Last Buffer",
+ "Enable the last-sample property", DEFAULT_ENABLE_LAST_SAMPLE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_LAST_SAMPLE,
+ g_param_spec_boxed ("last-sample", "Last Sample",
+ "The last sample received in the sink", GST_TYPE_SAMPLE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BLOCKSIZE,
+ g_param_spec_uint ("blocksize", "Block size",
+ "Size in bytes to pull per buffer (0 = default)", 0, G_MAXUINT,
+ DEFAULT_BLOCKSIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_RENDER_DELAY,
+ g_param_spec_uint64 ("render-delay", "Render Delay",
+ "Additional render delay of the sink in nanoseconds", 0, G_MAXUINT64,
+ DEFAULT_RENDER_DELAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_THROTTLE_TIME,
+ g_param_spec_uint64 ("throttle-time", "Throttle time",
+ "The time to keep between rendered buffers (0 = disabled)", 0,
+ G_MAXUINT64, DEFAULT_THROTTLE_TIME,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_MAX_BITRATE,
+ g_param_spec_uint64 ("max-bitrate", "Max Bitrate",
+ "The maximum bits per second to render (0 = disabled)", 0,
+ G_MAXUINT64, DEFAULT_MAX_BITRATE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* colorbalance */
+ g_object_class_install_property (gobject_class, PROP_CONTRAST,
+ g_param_spec_double ("contrast", "Contrast", "contrast",
+ 0.0, 2.0, DEFAULT_PROP_CONTRAST,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BRIGHTNESS,
+ g_param_spec_double ("brightness", "Brightness", "brightness", -1.0, 1.0,
+ DEFAULT_PROP_BRIGHTNESS,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_HUE,
+ g_param_spec_double ("hue", "Hue", "hue", -1.0, 1.0, DEFAULT_PROP_HUE,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_SATURATION,
+ g_param_spec_double ("saturation", "Saturation", "saturation", 0.0, 2.0,
+ DEFAULT_PROP_SATURATION,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstGLSinkBin::create-element:
+ * @object: the #GstGLSinkBin
+ *
+ * Will be emitted when we need the processing element/s that this bin will use
+ *
+ * Returns: a new #GstElement
+ */
+ gst_gl_sink_bin_signals[SIGNAL_CREATE_ELEMENT] =
+ g_signal_new ("create-element", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ GST_TYPE_ELEMENT, 0);
+
+ gst_element_class_set_metadata (element_class,
+ "GL Sink Bin", "Sink/Video",
+ "Infrastructure to process GL textures",
+ "Matthew Waters <matthew@centricular.com>");
+
+ upload_caps = gst_gl_upload_get_input_template_caps ();
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, upload_caps));
+ gst_caps_unref (upload_caps);
+}
+
+static void
+gst_gl_sink_bin_init (GstGLSinkBin * self)
+{
+ gboolean res = TRUE;
+ GstPad *pad;
+
+ self->upload = gst_element_factory_make ("glupload", NULL);
+ self->convert = gst_element_factory_make ("glcolorconvert", NULL);
+ self->balance = gst_element_factory_make ("glcolorbalance", NULL);
+
+ res &= gst_bin_add (GST_BIN (self), self->upload);
+ res &= gst_bin_add (GST_BIN (self), self->convert);
+ res &= gst_bin_add (GST_BIN (self), self->balance);
+
+ res &= gst_element_link_pads (self->upload, "src", self->convert, "sink");
+ res &= gst_element_link_pads (self->convert, "src", self->balance, "sink");
+
+ pad = gst_element_get_static_pad (self->upload, "sink");
+ if (!pad) {
+ res = FALSE;
+ } else {
+ GST_DEBUG_OBJECT (self, "setting target sink pad %" GST_PTR_FORMAT, pad);
+ self->sinkpad = gst_ghost_pad_new ("sink", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad);
+ gst_object_unref (pad);
+ }
+
+#define ADD_BINDING(obj,ref,prop) \
+ gst_object_add_control_binding (GST_OBJECT (obj), \
+ gst_proxy_control_binding_new (GST_OBJECT (obj), prop, \
+ GST_OBJECT (ref), prop));
+ ADD_BINDING (self->balance, self, "contrast");
+ ADD_BINDING (self->balance, self, "brightness");
+ ADD_BINDING (self->balance, self, "hue");
+ ADD_BINDING (self->balance, self, "saturation");
+#undef ADD_BINDING
+
+ if (!res) {
+ GST_WARNING_OBJECT (self, "Failed to add/connect the necessary machinery");
+ }
+}
+
+static gboolean
+_connect_sink_element (GstGLSinkBin * self)
+{
+ gst_object_set_name (GST_OBJECT (self->sink), "sink");
+
+ if (gst_bin_add (GST_BIN (self), self->sink) &&
+ gst_element_link_pads (self->balance, "src", self->sink, "sink"))
+ return TRUE;
+
+ GST_ERROR_OBJECT (self, "Failed to link sink element into the pipeline");
+ return FALSE;
+}
+
+static void
+gst_gl_sink_bin_set_sink (GstGLSinkBin * self, GstElement * sink)
+{
+ g_return_if_fail (GST_IS_ELEMENT (sink));
+
+ if (self->sink) {
+ gst_bin_remove (GST_BIN (self), self->sink);
+ self->sink = NULL;
+ }
+
+ /* We keep an indirect reference when the element is added */
+ self->sink = sink;
+
+ if (sink && !_connect_sink_element (self))
+ self->sink = NULL;
+}
+
+void
+gst_gl_sink_bin_finish_init_with_element (GstGLSinkBin * self,
+ GstElement * element)
+{
+ gst_gl_sink_bin_set_sink (self, element);
+}
+
+void
+gst_gl_sink_bin_finish_init (GstGLSinkBin * self)
+{
+ GstGLSinkBinClass *klass = GST_GL_SINK_BIN_GET_CLASS (self);
+ GstElement *element = NULL;
+
+ if (klass->create_element)
+ element = klass->create_element ();
+
+ if (element)
+ gst_gl_sink_bin_set_sink (self, element);
+}
+
+static void
+gst_gl_sink_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (object);
+
+ switch (prop_id) {
+ case PROP_SINK:
+ gst_gl_sink_bin_set_sink (self, g_value_get_object (value));
+ break;
+ case PROP_CONTRAST:
+ case PROP_BRIGHTNESS:
+ case PROP_HUE:
+ case PROP_SATURATION:
+ if (self->balance)
+ g_object_set_property (G_OBJECT (self->balance), pspec->name, value);
+ break;
+ default:
+ if (self->sink)
+ g_object_set_property (G_OBJECT (self->sink), pspec->name, value);
+ break;
+ }
+}
+
+static void
+gst_gl_sink_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (object);
+
+ switch (prop_id) {
+ case PROP_SINK:
+ g_value_set_object (value, self->sink);
+ break;
+ case PROP_CONTRAST:
+ case PROP_BRIGHTNESS:
+ case PROP_HUE:
+ case PROP_SATURATION:
+ if (self->balance)
+ g_object_get_property (G_OBJECT (self->balance), pspec->name, value);
+ break;
+ default:
+ if (self->sink)
+ g_object_get_property (G_OBJECT (self->sink), pspec->name, value);
+ break;
+ }
+}
+
+static GstStateChangeReturn
+gst_gl_sink_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (element);
+ GstGLSinkBinClass *klass = GST_GL_SINK_BIN_GET_CLASS (self);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!self->sink) {
+ if (klass->create_element)
+ self->sink = klass->create_element ();
+
+ if (!self->sink)
+ g_signal_emit (element,
+ gst_gl_sink_bin_signals[SIGNAL_CREATE_ELEMENT], 0, &self->sink);
+
+ if (!self->sink) {
+ GST_ERROR_OBJECT (element, "Failed to retrieve element");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ if (!_connect_sink_element (self))
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_gl_sink_bin_navigation_send_event (GstNavigation * navigation, GstStructure
+ * structure)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (navigation);
+ GstElement *nav =
+ gst_bin_get_by_interface (GST_BIN (self), GST_TYPE_NAVIGATION);
+
+ if (nav) {
+ gst_navigation_send_event (GST_NAVIGATION (nav), structure);
+ structure = NULL;
+ gst_object_unref (nav);
+ } else {
+ GstEvent *event = gst_event_new_navigation (structure);
+ structure = NULL;
+ gst_element_send_event (GST_ELEMENT (self), event);
+ }
+}
+
+static void
+gst_gl_sink_bin_navigation_interface_init (gpointer g_iface,
+ gpointer g_iface_data)
+{
+ GstNavigationInterface *iface = (GstNavigationInterface *) g_iface;
+ iface->send_event = gst_gl_sink_bin_navigation_send_event;
+}
+
+static void
+gst_gl_sink_bin_overlay_expose (GstVideoOverlay * overlay)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (overlay);
+ GstVideoOverlay *overlay_element = NULL;
+
+ overlay_element =
+ GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_VIDEO_OVERLAY));
+
+ if (overlay_element) {
+ gst_video_overlay_expose (overlay_element);
+ gst_object_unref (overlay_element);
+ }
+}
+
+static void
+gst_gl_sink_bin_overlay_handle_events (GstVideoOverlay * overlay,
+ gboolean handle_events)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (overlay);
+ GstVideoOverlay *overlay_element = NULL;
+
+ overlay_element =
+ GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_VIDEO_OVERLAY));
+
+ if (overlay_element) {
+ gst_video_overlay_handle_events (overlay_element, handle_events);
+ gst_object_unref (overlay_element);
+ }
+}
+
+static void
+gst_gl_sink_bin_overlay_set_render_rectangle (GstVideoOverlay * overlay, gint x,
+ gint y, gint width, gint height)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (overlay);
+ GstVideoOverlay *overlay_element = NULL;
+
+ overlay_element =
+ GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_VIDEO_OVERLAY));
+
+ if (overlay_element) {
+ gst_video_overlay_set_render_rectangle (overlay_element, x, y, width,
+ height);
+ gst_object_unref (overlay_element);
+ }
+}
+
+static void
+gst_gl_sink_bin_overlay_set_window_handle (GstVideoOverlay * overlay,
+ guintptr handle)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (overlay);
+ GstVideoOverlay *overlay_element = NULL;
+
+ overlay_element =
+ GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_VIDEO_OVERLAY));
+
+ if (overlay_element) {
+ gst_video_overlay_set_window_handle (overlay_element, handle);
+ gst_object_unref (overlay_element);
+ }
+}
+
+static void
+gst_gl_sink_bin_video_overlay_init (gpointer g_iface, gpointer g_iface_data)
+{
+ GstVideoOverlayInterface *iface = (GstVideoOverlayInterface *) g_iface;
+ iface->expose = gst_gl_sink_bin_overlay_expose;
+ iface->handle_events = gst_gl_sink_bin_overlay_handle_events;
+ iface->set_render_rectangle = gst_gl_sink_bin_overlay_set_render_rectangle;
+ iface->set_window_handle = gst_gl_sink_bin_overlay_set_window_handle;
+}
+
+static const GList *
+gst_gl_sink_bin_color_balance_list_channels (GstColorBalance * balance)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (balance);
+ GstColorBalance *balance_element = NULL;
+ const GList *list = NULL;
+
+ balance_element =
+ GST_COLOR_BALANCE (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_COLOR_BALANCE));
+
+ if (balance_element) {
+ list = gst_color_balance_list_channels (balance_element);
+ gst_object_unref (balance_element);
+ }
+
+ return list;
+}
+
+static void
+gst_gl_sink_bin_color_balance_set_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel, gint value)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (balance);
+ GstColorBalance *balance_element = NULL;
+
+ balance_element =
+ GST_COLOR_BALANCE (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_COLOR_BALANCE));
+
+ if (balance_element) {
+ gst_color_balance_set_value (balance_element, channel, value);
+ gst_object_unref (balance_element);
+ }
+}
+
+static gint
+gst_gl_sink_bin_color_balance_get_value (GstColorBalance * balance,
+ GstColorBalanceChannel * channel)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (balance);
+ GstColorBalance *balance_element = NULL;
+ gint val = 0;
+
+ balance_element =
+ GST_COLOR_BALANCE (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_COLOR_BALANCE));
+
+ if (balance_element) {
+ val = gst_color_balance_get_value (balance_element, channel);
+ gst_object_unref (balance_element);
+ }
+
+ return val;
+}
+
+static GstColorBalanceType
+gst_gl_sink_bin_color_balance_get_balance_type (GstColorBalance * balance)
+{
+ GstGLSinkBin *self = GST_GL_SINK_BIN (balance);
+ GstColorBalance *balance_element = NULL;
+ GstColorBalanceType type = 0;
+
+ balance_element =
+ GST_COLOR_BALANCE (gst_bin_get_by_interface (GST_BIN (self),
+ GST_TYPE_COLOR_BALANCE));
+
+ if (balance_element) {
+ type = gst_color_balance_get_balance_type (balance_element);
+ gst_object_unref (balance_element);
+ }
+
+ return type;
+}
+
+static void
+gst_gl_sink_bin_color_balance_init (gpointer g_iface, gpointer g_iface_data)
+{
+ GstColorBalanceInterface *iface = (GstColorBalanceInterface *) g_iface;
+
+ iface->list_channels = gst_gl_sink_bin_color_balance_list_channels;
+ iface->set_value = gst_gl_sink_bin_color_balance_set_value;
+ iface->get_value = gst_gl_sink_bin_color_balance_get_value;
+ iface->get_balance_type = gst_gl_sink_bin_color_balance_get_balance_type;
+}
diff --git a/ext/gl/gstglsinkbin.h b/ext/gl/gstglsinkbin.h
new file mode 100644
index 000000000..f4529d02c
--- /dev/null
+++ b/ext/gl/gstglsinkbin.h
@@ -0,0 +1,76 @@
+/*
+ * GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005,2006,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_SINK_BIN_H_
+#define _GST_GL_SINK_BIN_H_
+
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_sink_bin_get_type(void);
+#define GST_TYPE_GL_SINK_BIN \
+ (gst_gl_sink_bin_get_type())
+#define GST_GL_SINK_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_SINK_BIN,GstGLSinkBin))
+#define GST_GL_SINK_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_SINK_BIN,GstGLSinkBinClass))
+#define GST_IS_GL_SINK_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_SINK_BIN))
+#define GST_IS_GL_SINK_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_SINK_BIN))
+#define GST_GL_SINK_BIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_SINK_BIN,GstGLSinkBinClass))
+
+typedef struct _GstGLSinkBin GstGLSinkBin;
+typedef struct _GstGLSinkBinClass GstGLSinkBinClass;
+
+struct _GstGLSinkBin
+{
+ GstBin parent;
+
+ GstPad *sinkpad;
+
+ GstElement *upload;
+ GstElement *convert;
+ GstElement *balance;
+ GstElement *sink;
+};
+
+struct _GstGLSinkBinClass
+{
+ GstBinClass parent_class;
+
+ GstElement * (*create_element) (void);
+};
+
+void gst_gl_sink_bin_finish_init (GstGLSinkBin * self);
+void gst_gl_sink_bin_finish_init_with_element (GstGLSinkBin * self,
+ GstElement * element);
+
+G_END_DECLS
+
+#endif
diff --git a/ext/gl/gstglsrcbin.c b/ext/gl/gstglsrcbin.c
new file mode 100644
index 000000000..da74aaa01
--- /dev/null
+++ b/ext/gl/gstglsrcbin.c
@@ -0,0 +1,267 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglsrcbin.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gl_src_bin);
+#define GST_CAT_DEFAULT gst_debug_gl_src_bin
+
+static void gst_gl_src_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_gl_src_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static GstStateChangeReturn gst_gl_src_bin_change_state (GstElement * element,
+ GstStateChange transition);
+
+static GstStaticPadTemplate gst_gl_src_bin_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(ANY)"));
+
+enum
+{
+ PROP_0,
+ PROP_SRC,
+};
+
+enum
+{
+ SIGNAL_0,
+ SIGNAL_CREATE_ELEMENT,
+ SIGNAL_LAST,
+};
+
+static guint gst_gl_src_bin_signals[SIGNAL_LAST] = { 0, };
+
+#define gst_gl_src_bin_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLSrcBin, gst_gl_src_bin,
+ GST_TYPE_BIN,
+ GST_DEBUG_CATEGORY_INIT (gst_debug_gl_src_bin, "glsrcbin", 0,
+ "OpenGL Video Src Bin"));
+
+static void
+gst_gl_src_bin_class_init (GstGLSrcBinClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ element_class->change_state = gst_gl_src_bin_change_state;
+
+ gobject_class->set_property = gst_gl_src_bin_set_property;
+ gobject_class->get_property = gst_gl_src_bin_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_SRC,
+ g_param_spec_object ("src",
+ "GL src element",
+ "The GL src chain to use",
+ GST_TYPE_ELEMENT,
+ GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstGLSrcBin::create-element:
+ * @object: the #GstGLSrcBin
+ *
+ * Will be emitted when we need the processing element/s that this bin will use
+ *
+ * Returns: a new #GstElement
+ */
+ gst_gl_src_bin_signals[SIGNAL_CREATE_ELEMENT] =
+ g_signal_new ("create-element", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ GST_TYPE_ELEMENT, 0);
+
+ gst_element_class_set_metadata (element_class,
+ "GL Src Bin", "Src/Video",
+ "Infrastructure to process GL textures",
+ "Matthew Waters <matthew@centricular.com>");
+
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_src_bin_template);
+}
+
+static void
+gst_gl_src_bin_init (GstGLSrcBin * self)
+{
+ gboolean res = TRUE;
+ GstPad *pad;
+
+ self->download = gst_element_factory_make ("gldownload", NULL);
+ self->convert = gst_element_factory_make ("glcolorconvert", NULL);
+
+ res &= gst_bin_add (GST_BIN (self), self->download);
+ res &= gst_bin_add (GST_BIN (self), self->convert);
+
+ res &= gst_element_link_pads (self->convert, "src", self->download, "sink");
+
+ pad = gst_element_get_static_pad (self->download, "src");
+ if (!pad) {
+ res = FALSE;
+ } else {
+ GST_DEBUG_OBJECT (self, "setting target src pad %" GST_PTR_FORMAT, pad);
+ self->srcpad = gst_ghost_pad_new ("src", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
+ gst_object_unref (pad);
+ }
+
+ if (!res) {
+ GST_WARNING_OBJECT (self, "Failed to add/connect the necessary machinery");
+ }
+}
+
+static gboolean
+_connect_src_element (GstGLSrcBin * self)
+{
+ gboolean res = TRUE;
+
+ gst_object_set_name (GST_OBJECT (self->src), "src");
+ res &= gst_bin_add (GST_BIN (self), self->src);
+
+ res &= gst_element_link_pads (self->src, "src", self->convert, "sink");
+
+ if (!res)
+ GST_ERROR_OBJECT (self, "Failed to link src element into the pipeline");
+
+ return res;
+}
+
+void
+gst_gl_src_bin_finish_init_with_element (GstGLSrcBin * self,
+ GstElement * element)
+{
+ g_return_if_fail (GST_IS_ELEMENT (element));
+
+ self->src = element;
+
+ if (!_connect_src_element (self)) {
+ gst_object_unref (self->src);
+ self->src = NULL;
+ }
+}
+
+void
+gst_gl_src_bin_finish_init (GstGLSrcBin * self)
+{
+ GstGLSrcBinClass *klass = GST_GL_SRC_BIN_GET_CLASS (self);
+ GstElement *element = NULL;
+
+ if (klass->create_element)
+ element = klass->create_element ();
+
+ if (element)
+ gst_gl_src_bin_finish_init_with_element (self, element);
+}
+
+static void
+gst_gl_src_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLSrcBin *self = GST_GL_SRC_BIN (object);
+
+ switch (prop_id) {
+ case PROP_SRC:
+ {
+ GstElement *src = g_value_get_object (value);
+ if (self->src)
+ gst_bin_remove (GST_BIN (self), self->src);
+ self->src = src;
+ if (src) {
+ gst_object_ref_sink (src);
+ _connect_src_element (self);
+ }
+ break;
+ }
+ default:
+ if (self->src)
+ g_object_set_property (G_OBJECT (self->src), pspec->name, value);
+ break;
+ }
+}
+
+static void
+gst_gl_src_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLSrcBin *self = GST_GL_SRC_BIN (object);
+
+ switch (prop_id) {
+ case PROP_SRC:
+ g_value_set_object (value, self->src);
+ break;
+ default:
+ if (self->src)
+ g_object_get_property (G_OBJECT (self->src), pspec->name, value);
+ break;
+ }
+}
+
+static GstStateChangeReturn
+gst_gl_src_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLSrcBin *self = GST_GL_SRC_BIN (element);
+ GstGLSrcBinClass *klass = GST_GL_SRC_BIN_GET_CLASS (self);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!self->src) {
+ if (klass->create_element)
+ self->src = klass->create_element ();
+
+ if (!self->src)
+ g_signal_emit (element,
+ gst_gl_src_bin_signals[SIGNAL_CREATE_ELEMENT], 0, &self->src);
+
+ if (!self->src) {
+ GST_ERROR_OBJECT (element, "Failed to retrieve element");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ if (!_connect_src_element (self))
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstglsrcbin.h b/ext/gl/gstglsrcbin.h
new file mode 100644
index 000000000..42301688f
--- /dev/null
+++ b/ext/gl/gstglsrcbin.h
@@ -0,0 +1,71 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_SRC_BIN_H_
+#define _GST_GL_SRC_BIN_H_
+
+#include <gst/gst.h>
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_src_bin_get_type(void);
+#define GST_TYPE_GL_SRC_BIN \
+ (gst_gl_src_bin_get_type())
+#define GST_GL_SRC_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_SRC_BIN,GstGLSrcBin))
+#define GST_GL_SRC_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_SRC_BIN,GstGLSrcBinClass))
+#define GST_IS_GL_SRC_BIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_SRC_BIN))
+#define GST_IS_GL_SRC_BIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_SRC_BIN))
+#define GST_GL_SRC_BIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_SRC_BIN,GstGLSrcBinClass))
+
+typedef struct _GstGLSrcBin GstGLSrcBin;
+typedef struct _GstGLSrcBinClass GstGLSrcBinClass;
+
+struct _GstGLSrcBin
+{
+ GstBin parent;
+
+ GstPad *srcpad;
+
+ GstElement *src;
+ GstElement *convert;
+ GstElement *download;
+};
+
+struct _GstGLSrcBinClass
+{
+ GstBinClass parent_class;
+
+ GstElement * (*create_element) (void);
+};
+
+void gst_gl_src_bin_finish_init (GstGLSrcBin * self);
+void gst_gl_src_bin_finish_init_with_element (GstGLSrcBin * self,
+ GstElement * element);
+
+G_END_DECLS
+
+#endif
diff --git a/ext/gl/gstglstereomix.c b/ext/gl/gstglstereomix.c
new file mode 100644
index 000000000..d3fe0302f
--- /dev/null
+++ b/ext/gl/gstglstereomix.c
@@ -0,0 +1,697 @@
+/*
+ * Combine video streams to 3D stereo
+ *
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@noraisin.net>
+ *
+ * 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:element-glstereomix
+ * @title: glstereomix
+ *
+ * Combine 2 input streams to produce a stereoscopic output
+ * stream. Input views are taken from the left pad and right pad
+ * respectively, and mixed according to their timelines.
+ *
+ * If either input stream is stereoscopic, the approproriate view
+ * (left or right) is taken from each stream and placed into the output.
+ *
+ * The multiview representation on the output is chosen according to
+ * the downstream caps.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 -v videotestsrc pattern=ball name=left \
+ * videotestsrc name=right glstereomix name=mix \
+ * left. ! vid/x-raw,width=640,height=480! glupload ! mix. \
+ * right. ! video/x-raw,width=640,height=480! glupload ! mix. \
+ * mix. ! video/x-raw'(memory:GLMemory)',multiview-mode=side-by-side ! \
+ * queue ! glimagesink output-multiview-mode=side-by-side
+ * ]| Mix 2 different videotestsrc patterns into a side-by-side stereo image and display it.
+ * |[
+ * gst-launch-1.0 -ev v4l2src name=left \
+ * videotestsrc name=right \
+ * glstereomix name=mix \
+ * left. ! video/x-raw,width=640,height=480 ! glupload ! glcolorconvert ! mix. \
+ * right. ! video/x-raw,width=640,height=480 ! glupload ! mix. \
+ * mix. ! video/x-raw'(memory:GLMemory)',multiview-mode=top-bottom ! \
+ * glcolorconvert ! gldownload ! queue ! x264enc ! h264parse ! \
+ * mp4mux ! progressreport ! filesink location=output.mp4
+ * ]| Mix the input from a camera to the left view, and videotestsrc to the right view,
+ * and encode as a top-bottom frame packed H.264 video.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglstereomix.h"
+
+#define GST_CAT_DEFAULT gst_gl_stereo_mix_debug
+GST_DEBUG_CATEGORY (gst_gl_stereo_mix_debug);
+
+G_DEFINE_TYPE (GstGLStereoMixPad, gst_gl_stereo_mix_pad, GST_TYPE_GL_MIXER_PAD);
+
+static void
+gst_gl_stereo_mix_pad_class_init (GstGLStereoMixPadClass * klass)
+{
+}
+
+static void
+gst_gl_stereo_mix_pad_init (GstGLStereoMixPad * pad)
+{
+}
+
+#define gst_gl_stereo_mix_parent_class parent_class
+G_DEFINE_TYPE (GstGLStereoMix, gst_gl_stereo_mix, GST_TYPE_GL_MIXER);
+
+static GstCaps *_update_caps (GstVideoAggregator * vagg, GstCaps * caps);
+static gboolean _negotiated_caps (GstAggregator * aggregator, GstCaps * caps);
+gboolean gst_gl_stereo_mix_make_output (GstGLStereoMix * mix);
+static gboolean gst_gl_stereo_mix_process_frames (GstGLStereoMix * mixer);
+
+#define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
+
+/* GLStereoMix signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_DOWNMIX_MODE
+};
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ","
+ "texture-target = (string) 2D"
+ "; "
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META,
+ "RGBA")
+ "; " GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS))
+ );
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ","
+ "texture-target = (string) 2D"
+ "; "
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META,
+ "RGBA")
+ "; " GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS))
+ );
+
+static GstFlowReturn gst_gl_stereo_mix_get_output_buffer (GstVideoAggregator *
+ videoaggregator, GstBuffer ** outbuf);
+static gboolean gst_gl_stereo_mix_stop (GstAggregator * agg);
+static gboolean gst_gl_stereo_mix_start (GstAggregator * agg);
+static gboolean gst_gl_stereo_mix_src_query (GstAggregator * agg,
+ GstQuery * query);
+
+static void gst_gl_stereo_mix_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_stereo_mix_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void gst_gl_stereo_mix_finalize (GObject * object);
+
+static GstFlowReturn
+gst_gl_stereo_mix_aggregate_frames (GstVideoAggregator * vagg,
+ GstBuffer * outbuffer);
+
+static void
+gst_gl_stereo_mix_class_init (GstGLStereoMixClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstVideoAggregatorClass *videoaggregator_class =
+ (GstVideoAggregatorClass *) klass;
+ GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
+ GstGLBaseMixerClass *base_mix_class = (GstGLBaseMixerClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glstereomixer", 0,
+ "opengl stereoscopic mixer");
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gl_stereo_mix_finalize);
+
+ gobject_class->get_property = gst_gl_stereo_mix_get_property;
+ gobject_class->set_property = gst_gl_stereo_mix_set_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL stereo video combiner",
+ "Filter/Effect/Video", "OpenGL stereo video combiner",
+ "Jan Schmidt <jan@centricular.com>");
+
+ g_object_class_install_property (gobject_class, PROP_DOWNMIX_MODE,
+ g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_add_static_pad_template (element_class, &src_factory);
+ gst_element_class_add_static_pad_template_with_gtype (element_class,
+ &sink_factory, GST_TYPE_GL_STEREO_MIX_PAD);
+
+ agg_class->stop = gst_gl_stereo_mix_stop;
+ agg_class->start = gst_gl_stereo_mix_start;
+ agg_class->src_query = gst_gl_stereo_mix_src_query;
+ agg_class->negotiated_src_caps = _negotiated_caps;
+
+ videoaggregator_class->aggregate_frames = gst_gl_stereo_mix_aggregate_frames;
+ videoaggregator_class->update_caps = _update_caps;
+ videoaggregator_class->get_output_buffer =
+ gst_gl_stereo_mix_get_output_buffer;
+
+ base_mix_class->supported_gl_api =
+ GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_stereo_mix_init (GstGLStereoMix * mix)
+{
+}
+
+static void
+gst_gl_stereo_mix_finalize (GObject * object)
+{
+ //GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_gl_stereo_mix_query_caps (GstPad * pad, GstAggregator * agg,
+ GstQuery * query)
+{
+ GstCaps *filter, *caps;
+
+ gst_query_parse_caps (query, &filter);
+
+ caps = gst_pad_get_current_caps (agg->srcpad);
+ if (caps == NULL) {
+ caps = gst_pad_get_pad_template_caps (agg->srcpad);
+ }
+
+ if (filter)
+ caps = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+
+ gst_query_set_caps_result (query, caps);
+ gst_caps_unref (caps);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_stereo_mix_src_query (GstAggregator * agg, GstQuery * query)
+{
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ return gst_gl_stereo_mix_query_caps (agg->srcpad, agg, query);
+ break;
+ default:
+ break;
+ }
+
+ return GST_AGGREGATOR_CLASS (parent_class)->src_query (agg, query);
+}
+
+
+static GstFlowReturn
+gst_gl_stereo_mix_get_output_buffer (GstVideoAggregator * videoaggregator,
+ GstBuffer ** outbuf)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (videoaggregator);
+ GstFlowReturn ret = GST_FLOW_OK;
+
+#if 0
+
+ if (!mix->priv->pool_active) {
+ if (!gst_buffer_pool_set_active (mix->priv->pool, TRUE)) {
+ GST_ELEMENT_ERROR (mix, RESOURCE, SETTINGS,
+ ("failed to activate bufferpool"), ("failed to activate bufferpool"));
+ return GST_FLOW_ERROR;
+ }
+ mix->priv->pool_active = TRUE;
+ }
+
+ return gst_buffer_pool_acquire_buffer (mix->priv->pool, outbuf, NULL);
+#endif
+
+ if (!gst_gl_stereo_mix_make_output (mix)) {
+ gst_buffer_replace (&mix->primary_out, NULL);
+ gst_buffer_replace (&mix->auxilliary_out, NULL);
+ GST_ELEMENT_ERROR (mix, RESOURCE, SETTINGS,
+ ("Failed to generate output"), ("failed to generate output"));
+ ret = GST_FLOW_ERROR;
+ }
+
+ if (mix->auxilliary_out) {
+ *outbuf = mix->auxilliary_out;
+ mix->auxilliary_out = NULL;
+ } else {
+ *outbuf = mix->primary_out;
+ mix->primary_out = NULL;
+ }
+ return ret;
+}
+
+gboolean
+gst_gl_stereo_mix_make_output (GstGLStereoMix * mix)
+{
+ GList *walk;
+ gboolean res = FALSE;
+ GstElement *element = GST_ELEMENT (mix);
+ gboolean missing_buffer = FALSE;
+
+ GST_LOG_OBJECT (mix, "Processing buffers");
+
+ GST_OBJECT_LOCK (mix);
+ walk = element->sinkpads;
+ while (walk) {
+ GstVideoAggregatorPad *vaggpad = walk->data;
+ GstGLStereoMixPad *pad = walk->data;
+
+ GST_LOG_OBJECT (mix, "Checking pad %" GST_PTR_FORMAT, vaggpad);
+
+ if (vaggpad->buffer != NULL) {
+ pad->current_buffer = vaggpad->buffer;
+
+ GST_DEBUG_OBJECT (pad, "Got buffer %" GST_PTR_FORMAT,
+ pad->current_buffer);
+ } else {
+ GST_LOG_OBJECT (mix, "No buffer on pad %" GST_PTR_FORMAT, vaggpad);
+ pad->current_buffer = NULL;
+ missing_buffer = TRUE;
+ }
+ walk = g_list_next (walk);
+ }
+ if (missing_buffer) {
+ /* We're still waiting for a buffer to turn up on at least one input */
+ GST_WARNING_OBJECT (mix, "Not generating output - need more input buffers");
+ res = TRUE;
+ goto out;
+ }
+
+ /* Copy GL memory from each input frame to the output */
+ if (!gst_gl_stereo_mix_process_frames (mix)) {
+ GST_LOG_OBJECT (mix, "Failed to process frames to output");
+ goto out;
+ }
+
+ if (mix->primary_out == NULL)
+ goto out;
+
+ res = TRUE;
+
+out:
+ GST_OBJECT_UNLOCK (mix);
+
+ return res;
+}
+
+static GstFlowReturn
+gst_gl_stereo_mix_aggregate_frames (GstVideoAggregator * vagg,
+ GstBuffer * outbuf)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+ /* If we're operating in frame-by-frame mode, push
+ * the primary view now, and let the parent class
+ * push the remaining auxilliary view */
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (&vagg->info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ /* Transfer the timestamps video-agg put on the aux buffer */
+ gst_buffer_copy_into (mix->primary_out, outbuf,
+ GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ gst_aggregator_finish_buffer (GST_AGGREGATOR (vagg), mix->primary_out);
+ mix->primary_out = NULL;
+
+ /* And actually, we don't want timestamps on the aux buffer */
+ GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
+ }
+ return GST_FLOW_OK;
+}
+
+static void
+gst_gl_stereo_mix_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ switch (prop_id) {
+ case PROP_DOWNMIX_MODE:
+ g_value_set_enum (value, mix->downmix_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_stereo_mix_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ switch (prop_id) {
+ case PROP_DOWNMIX_MODE:
+ mix->downmix_mode = g_value_get_enum (value);
+ if (mix->viewconvert)
+ g_object_set_property (G_OBJECT (mix->viewconvert), "downmix-mode",
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_stereo_mix_start (GstAggregator * agg)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (agg);
+
+ if (!GST_AGGREGATOR_CLASS (parent_class)->start (agg))
+ return FALSE;
+
+ GST_OBJECT_LOCK (mix);
+ mix->viewconvert = gst_gl_view_convert_new ();
+ g_object_set (G_OBJECT (mix->viewconvert), "downmix-mode",
+ mix->downmix_mode, NULL);
+ GST_OBJECT_UNLOCK (mix);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_stereo_mix_stop (GstAggregator * agg)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (agg);
+
+ if (!GST_AGGREGATOR_CLASS (parent_class)->stop (agg))
+ return FALSE;
+
+ if (mix->viewconvert) {
+ gst_object_unref (mix->viewconvert);
+ mix->viewconvert = NULL;
+ }
+
+ return TRUE;
+}
+
+/* Convert to caps that can be accepted by this element... */
+static GstCaps *
+get_converted_caps (GstGLStereoMix * mix, GstCaps * caps)
+{
+#if 0
+ GstGLContext *context = GST_GL_BASE_MIXER (mix)->context;
+ GstCaps *result, *tmp;
+
+ GST_LOG_OBJECT (mix, "Converting caps %" GST_PTR_FORMAT, caps);
+ result = gst_gl_upload_transform_caps (context, GST_PAD_SINK, caps, NULL);
+ tmp = result;
+ GST_TRACE_OBJECT (mix, "transfer returned caps %" GST_PTR_FORMAT, tmp);
+
+ result =
+ gst_gl_color_convert_transform_caps (context, GST_PAD_SINK, tmp, NULL);
+ gst_caps_unref (tmp);
+ GST_TRACE_OBJECT (mix, "convert returned caps %" GST_PTR_FORMAT, tmp);
+
+ tmp = result;
+ result = gst_gl_view_convert_transform_caps (mix->viewconvert,
+ GST_PAD_SINK, tmp, NULL);
+ gst_caps_unref (tmp);
+#else
+ GstCaps *result;
+
+ GST_LOG_OBJECT (mix, "Converting caps %" GST_PTR_FORMAT, caps);
+ result = gst_gl_view_convert_transform_caps (mix->viewconvert,
+ GST_PAD_SINK, caps, NULL);
+#endif
+
+ GST_LOG_OBJECT (mix, "returning caps %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+/* Return the possible output caps based on inputs and downstream prefs */
+static GstCaps *
+_update_caps (GstVideoAggregator * vagg, GstCaps * caps)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+ GList *l;
+ gint best_width = -1, best_height = -1;
+ gdouble best_fps = -1, cur_fps;
+ gint best_fps_n = 0, best_fps_d = 1;
+ GstVideoInfo *mix_info;
+ GstCaps *blend_caps, *tmp_caps;
+ GstCaps *out_caps;
+
+ GST_OBJECT_LOCK (vagg);
+
+ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
+ GstVideoAggregatorPad *pad = l->data;
+ GstVideoInfo tmp = pad->info;
+ gint this_width, this_height;
+ gint fps_n, fps_d;
+
+ if (!pad->info.finfo)
+ continue;
+
+ /* This can happen if we release a pad and another pad hasn't been negotiated_caps yet */
+ if (GST_VIDEO_INFO_FORMAT (&pad->info) == GST_VIDEO_FORMAT_UNKNOWN)
+ continue;
+
+ /* Convert to per-view width/height for unpacked forms */
+ gst_video_multiview_video_info_change_mode (&tmp,
+ GST_VIDEO_MULTIVIEW_MODE_SEPARATED, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+
+ this_width = GST_VIDEO_INFO_WIDTH (&tmp);
+ this_height = GST_VIDEO_INFO_HEIGHT (&tmp);
+ fps_n = GST_VIDEO_INFO_FPS_N (&tmp);
+ fps_d = GST_VIDEO_INFO_FPS_D (&tmp);
+
+ GST_INFO_OBJECT (vagg, "Input pad %" GST_PTR_FORMAT
+ " w %u h %u", pad, this_width, this_height);
+
+ if (this_width == 0 || this_height == 0)
+ continue;
+
+ if (best_width < this_width)
+ best_width = this_width;
+ if (best_height < this_height)
+ best_height = this_height;
+
+ if (fps_d == 0)
+ cur_fps = 0.0;
+ else
+ gst_util_fraction_to_double (fps_n, fps_d, &cur_fps);
+
+ if (best_fps < cur_fps) {
+ best_fps = cur_fps;
+ best_fps_n = fps_n;
+ best_fps_d = fps_d;
+ }
+
+ /* FIXME: Preserve PAR for at least one input when different sized inputs */
+ }
+ GST_OBJECT_UNLOCK (vagg);
+
+ mix_info = &mix->mix_info;
+ gst_video_info_set_format (mix_info, GST_VIDEO_FORMAT_RGBA, best_width,
+ best_height);
+
+ GST_VIDEO_INFO_FPS_N (mix_info) = best_fps_n;
+ GST_VIDEO_INFO_FPS_D (mix_info) = best_fps_d;
+
+ GST_VIDEO_INFO_MULTIVIEW_MODE (mix_info) = GST_VIDEO_MULTIVIEW_MODE_SEPARATED;
+ GST_VIDEO_INFO_VIEWS (mix_info) = 2;
+
+ /* FIXME: If input is marked as flipped or flopped, preserve those flags */
+ GST_VIDEO_INFO_MULTIVIEW_FLAGS (mix_info) = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
+
+ /* Choose our output format based on downstream preferences */
+ blend_caps = gst_video_info_to_caps (mix_info);
+
+ gst_caps_set_features (blend_caps, 0,
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+
+ tmp_caps = get_converted_caps (GST_GL_STEREO_MIX (vagg), blend_caps);
+ gst_caps_unref (blend_caps);
+
+ out_caps = gst_caps_intersect (caps, tmp_caps);
+ gst_caps_unref (tmp_caps);
+
+ GST_DEBUG_OBJECT (vagg, "Possible output caps %" GST_PTR_FORMAT, out_caps);
+
+ return out_caps;
+}
+
+/* Called after videoaggregator fixates our caps */
+static gboolean
+_negotiated_caps (GstAggregator * agg, GstCaps * caps)
+{
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (agg);
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+ GstCaps *in_caps;
+
+ GST_LOG_OBJECT (mix, "Configured output caps %" GST_PTR_FORMAT, caps);
+
+ if (GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps)
+ if (!GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps))
+ return FALSE;
+
+ /* Update the glview_convert output */
+
+ /* We can configure the view_converter now */
+ gst_gl_view_convert_set_context (mix->viewconvert,
+ GST_GL_BASE_MIXER (mix)->context);
+
+ in_caps = gst_video_info_to_caps (&mix->mix_info);
+ gst_caps_set_features (in_caps, 0,
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+ gst_caps_set_simple (in_caps, "texture-target", G_TYPE_STRING,
+ GST_GL_TEXTURE_TARGET_2D_STR, NULL);
+
+ gst_gl_view_convert_set_caps (mix->viewconvert, in_caps, caps);
+
+ return TRUE;
+}
+
+/* called with the object lock held */
+static gboolean
+gst_gl_stereo_mix_process_frames (GstGLStereoMix * mixer)
+{
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (mixer);
+ GstBuffer *converted_buffer, *inbuf;
+ GstVideoInfo *out_info = &vagg->info;
+#ifndef G_DISABLE_ASSERT
+ gint n;
+#endif
+ gint v, views;
+ gint valid_views = 0;
+ GList *walk;
+
+ inbuf = gst_buffer_new ();
+ walk = GST_ELEMENT (mixer)->sinkpads;
+ while (walk) {
+ GstGLStereoMixPad *pad = walk->data;
+ GstMemory *in_mem;
+
+ GST_LOG_OBJECT (mixer, "Handling frame %d", valid_views);
+
+ if (!pad || !pad->current_buffer) {
+ GST_DEBUG ("skipping texture, null frame");
+ walk = g_list_next (walk);
+ continue;
+ }
+
+ in_mem = gst_buffer_get_memory (pad->current_buffer, 0);
+
+ GST_LOG_OBJECT (mixer,
+ "Appending memory %" GST_PTR_FORMAT " to intermediate buffer", in_mem);
+ /* Appending the memory to a 2nd buffer locks it
+ * exclusive a 2nd time, which will mark it for
+ * copy-on-write. The ref will keep the memory
+ * alive but we add a parent_buffer_meta to also
+ * prevent the input buffer from returning to any buffer
+ * pool it might belong to
+ */
+ gst_buffer_append_memory (inbuf, in_mem);
+ /* Use parent buffer meta to keep input buffer alive */
+ gst_buffer_add_parent_buffer_meta (inbuf, pad->current_buffer);
+
+ valid_views++;
+ walk = g_list_next (walk);
+ }
+
+ if (mixer->mix_info.views != valid_views) {
+ GST_WARNING_OBJECT (mixer, "Not enough input views to process");
+ return FALSE;
+ }
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (out_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_SEPARATED)
+ views = out_info->views;
+ else
+ views = 1;
+
+ if (gst_gl_view_convert_submit_input_buffer (mixer->viewconvert,
+ FALSE, inbuf) != GST_FLOW_OK)
+ return FALSE;
+
+ /* Clear any existing buffers, just in case */
+ gst_buffer_replace (&mixer->primary_out, NULL);
+ gst_buffer_replace (&mixer->auxilliary_out, NULL);
+
+ if (gst_gl_view_convert_get_output (mixer->viewconvert,
+ &mixer->primary_out) != GST_FLOW_OK)
+ return FALSE;
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (out_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ if (gst_gl_view_convert_get_output (mixer->viewconvert,
+ &mixer->auxilliary_out) != GST_FLOW_OK)
+ return FALSE;
+ }
+
+ if (mixer->primary_out == NULL)
+ return FALSE;
+
+ converted_buffer = mixer->primary_out;
+
+#ifndef G_DISABLE_ASSERT
+ n = gst_buffer_n_memory (converted_buffer);
+ g_assert (n == GST_VIDEO_INFO_N_PLANES (out_info) * views);
+#endif
+
+ for (v = 0; v < views; v++) {
+ gst_buffer_add_video_meta_full (converted_buffer, v,
+ GST_VIDEO_INFO_FORMAT (out_info),
+ GST_VIDEO_INFO_WIDTH (out_info),
+ GST_VIDEO_INFO_HEIGHT (out_info),
+ GST_VIDEO_INFO_N_PLANES (out_info), out_info->offset, out_info->stride);
+ if (mixer->auxilliary_out) {
+ gst_buffer_add_video_meta_full (mixer->auxilliary_out, v,
+ GST_VIDEO_INFO_FORMAT (out_info),
+ GST_VIDEO_INFO_WIDTH (out_info),
+ GST_VIDEO_INFO_HEIGHT (out_info),
+ GST_VIDEO_INFO_N_PLANES (out_info), out_info->offset,
+ out_info->stride);
+ }
+ }
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglstereomix.h b/ext/gl/gstglstereomix.h
new file mode 100644
index 000000000..9971a8910
--- /dev/null
+++ b/ext/gl/gstglstereomix.h
@@ -0,0 +1,87 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@noraisin.net>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_STEREO_MIX_H__
+#define __GST_GL_STEREO_MIX_H__
+
+#include "gstglmixer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_STEREO_MIX (gst_gl_stereo_mix_get_type())
+#define GST_GL_STEREO_MIX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_STEREO_MIX, GstGLStereoMix))
+#define GST_GL_STEREO_MIX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_STEREO_MIX, GstGLStereoMixClass))
+#define GST_IS_GL_STEREO_MIX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_STEREO_MIX))
+#define GST_IS_GL_STEREO_MIX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_STEREO_MIX))
+#define GST_GL_STEREO_MIX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_STEREO_MIX,GstGLStereoMixClass))
+
+typedef struct _GstGLStereoMix GstGLStereoMix;
+typedef struct _GstGLStereoMixClass GstGLStereoMixClass;
+typedef struct _GstGLStereoMixPad GstGLStereoMixPad;
+typedef struct _GstGLStereoMixPadClass GstGLStereoMixPadClass;
+
+struct _GstGLStereoMixPad
+{
+ GstGLMixerPad mixer_pad;
+
+ gboolean mapped;
+ GstBuffer *current_buffer;
+};
+
+struct _GstGLStereoMixPadClass
+{
+ GstGLMixerPadClass mixer_pad_class;
+};
+
+#define GST_TYPE_GL_STEREO_MIX_PAD (gst_gl_stereo_mix_pad_get_type ())
+GType gst_gl_stereo_mix_pad_get_type (void);
+
+
+struct _GstGLStereoMix
+{
+ GstGLMixer mixer;
+
+ GLuint out_tex_id;
+
+ GstGLViewConvert *viewconvert;
+ GstGLStereoDownmix downmix_mode;
+
+ GstVideoInfo mix_info;
+
+ GPtrArray *input_frames;
+ GstBuffer *primary_out;
+ GstBuffer *auxilliary_out;
+};
+
+struct _GstGLStereoMixClass
+{
+ GstGLMixerClass mixer_class;
+};
+
+GType gst_gl_stereo_mix_get_type(void);
+
+G_END_DECLS
+#endif /* __GST_GL_STEREO_MIX_H__ */
diff --git a/ext/gl/gstglstereosplit.c b/ext/gl/gstglstereosplit.c
new file mode 100644
index 000000000..933f3c593
--- /dev/null
+++ b/ext/gl/gstglstereosplit.c
@@ -0,0 +1,735 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Jan Schmidt <jan@centricular.com>
+ *
+ * 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:element-glstereosplit
+ * @title: glstereosplit
+ *
+ * Receive a stereoscopic video stream and split into left/right
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glstereosplit name=s ! queue ! glimagesink s. ! queue ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglstereosplit.h"
+
+#define GST_CAT_DEFAULT gst_gl_stereosplit_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define SUPPORTED_GL_APIS GST_GL_API_GLES2 | GST_GL_API_OPENGL | GST_GL_API_OPENGL3
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_stereosplit_debug, "glstereosplit", 0, "glstereosplit element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLStereoSplit, gst_gl_stereosplit,
+ GST_TYPE_ELEMENT, DEBUG_INIT);
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA"))
+ );
+
+static GstStaticPadTemplate src_left_template = GST_STATIC_PAD_TEMPLATE ("left",
+ GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA"))
+ );
+
+static GstStaticPadTemplate src_right_template =
+GST_STATIC_PAD_TEMPLATE ("right",
+ GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA"))
+ );
+
+static void stereosplit_reset (GstGLStereoSplit * self);
+static void stereosplit_finalize (GstGLStereoSplit * self);
+static void stereosplit_set_context (GstElement * element,
+ GstContext * context);
+static GstFlowReturn stereosplit_chain (GstPad * pad, GstGLStereoSplit * split,
+ GstBuffer * buf);
+static GstStateChangeReturn stereosplit_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean stereosplit_sink_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+static gboolean stereosplit_sink_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean stereosplit_src_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+static gboolean stereosplit_src_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean ensure_context (GstGLStereoSplit * self);
+
+static void
+gst_gl_stereosplit_class_init (GstGLStereoSplitClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_set_static_metadata (element_class,
+ "GLStereoSplit", "Codec/Converter",
+ "Splits a stereoscopic stream into separate left/right streams",
+ "Jan Schmidt <jan@centricular.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ gobject_class->finalize = (GObjectFinalizeFunc) (stereosplit_finalize);
+
+ element_class->change_state = stereosplit_change_state;
+ element_class->set_context = stereosplit_set_context;
+
+ gst_element_class_add_static_pad_template (element_class, &sink_template);
+ gst_element_class_add_static_pad_template (element_class, &src_left_template);
+ gst_element_class_add_static_pad_template (element_class,
+ &src_right_template);
+}
+
+static void
+gst_gl_stereosplit_init (GstGLStereoSplit * self)
+{
+ GstPad *pad;
+
+ pad = self->sink_pad =
+ gst_pad_new_from_static_template (&sink_template, "sink");
+
+ gst_pad_set_chain_function (pad, (GstPadChainFunction) (stereosplit_chain));
+ gst_pad_set_query_function (pad, stereosplit_sink_query);
+ gst_pad_set_event_function (pad, stereosplit_sink_event);
+
+ gst_element_add_pad (GST_ELEMENT (self), self->sink_pad);
+
+ pad = self->left_pad =
+ gst_pad_new_from_static_template (&src_left_template, "left");
+ gst_pad_set_query_function (pad, stereosplit_src_query);
+ gst_pad_set_event_function (pad, stereosplit_src_event);
+ gst_element_add_pad (GST_ELEMENT (self), self->left_pad);
+
+ pad = self->right_pad =
+ gst_pad_new_from_static_template (&src_right_template, "right");
+ gst_pad_set_query_function (pad, stereosplit_src_query);
+ gst_pad_set_event_function (pad, stereosplit_src_event);
+ gst_element_add_pad (GST_ELEMENT (self), self->right_pad);
+
+ self->viewconvert = gst_gl_view_convert_new ();
+}
+
+static void
+stereosplit_reset (GstGLStereoSplit * self)
+{
+ if (self->context)
+ gst_object_replace ((GstObject **) & self->context, NULL);
+ if (self->display)
+ gst_object_replace ((GstObject **) & self->display, NULL);
+}
+
+static void
+stereosplit_finalize (GstGLStereoSplit * self)
+{
+ GObjectClass *klass = G_OBJECT_CLASS (gst_gl_stereosplit_parent_class);
+
+ if (self->viewconvert)
+ gst_object_replace ((GstObject **) & self->viewconvert, NULL);
+
+ klass->finalize ((GObject *) (self));
+}
+
+static void
+stereosplit_set_context (GstElement * element, GstContext * context)
+{
+ GstGLStereoSplit *stereosplit = GST_GL_STEREOSPLIT (element);
+
+ gst_gl_handle_set_context (element, context, &stereosplit->display,
+ &stereosplit->other_context);
+
+ if (stereosplit->display)
+ gst_gl_display_filter_gl_api (stereosplit->display, SUPPORTED_GL_APIS);
+
+ GST_ELEMENT_CLASS (gst_gl_stereosplit_parent_class)->set_context (element,
+ context);
+}
+
+static GstStateChangeReturn
+stereosplit_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLStereoSplit *stereosplit = GST_GL_STEREOSPLIT (element);
+ GstStateChangeReturn result;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_gl_ensure_element_data (element, &stereosplit->display,
+ &stereosplit->other_context))
+ return GST_STATE_CHANGE_FAILURE;
+
+ gst_gl_display_filter_gl_api (stereosplit->display, SUPPORTED_GL_APIS);
+ break;
+ default:
+ break;
+ }
+
+ result =
+ GST_ELEMENT_CLASS (gst_gl_stereosplit_parent_class)->change_state
+ (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (stereosplit->other_context) {
+ gst_object_unref (stereosplit->other_context);
+ stereosplit->other_context = NULL;
+ }
+
+ if (stereosplit->display) {
+ gst_object_unref (stereosplit->display);
+ stereosplit->display = NULL;
+ }
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ stereosplit_reset (stereosplit);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static GstCaps *
+stereosplit_transform_caps (GstGLStereoSplit * self, GstPadDirection direction,
+ GstCaps * caps, GstCaps * filter)
+{
+ GstCaps *next_caps;
+
+ /* FIXME: Is this the right way to ensure a context here ? */
+ if (!ensure_context (self))
+ return NULL;
+
+ next_caps =
+ gst_gl_view_convert_transform_caps (self->viewconvert, direction, caps,
+ NULL);
+
+ return next_caps;
+}
+
+static GstCaps *
+strip_mview_fields (GstCaps * incaps, GstVideoMultiviewFlags keep_flags)
+{
+ GstCaps *outcaps = gst_caps_make_writable (incaps);
+
+ gint i, n;
+
+ n = gst_caps_get_size (outcaps);
+ for (i = 0; i < n; i++) {
+ GstStructure *st = gst_caps_get_structure (outcaps, i);
+ GstVideoMultiviewFlags flags, mask;
+
+ gst_structure_remove_field (st, "multiview-mode");
+ if (gst_structure_get_flagset (st, "multiview-flags", (guint *) & flags,
+ (guint *) & mask)) {
+ flags &= keep_flags;
+ mask = keep_flags;
+ gst_structure_set (st, "multiview-flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags, mask, NULL);
+ }
+ }
+
+ return outcaps;
+}
+
+static gboolean stereosplit_do_bufferpool (GstGLStereoSplit * self,
+ GstCaps * caps);
+
+static GstCaps *
+stereosplit_get_src_caps (GstGLStereoSplit * split,
+ GstPad * pad, GstVideoMultiviewMode preferred_mode)
+{
+ GstCaps *outcaps, *tmp, *templ_caps;
+ GValue item = G_VALUE_INIT, list = G_VALUE_INIT;
+
+ /* Get the template format */
+ templ_caps = gst_pad_get_pad_template_caps (pad);
+
+ /* And limit down to the preferred mode or mono */
+ templ_caps = gst_caps_make_writable (templ_caps);
+
+ g_value_init (&item, G_TYPE_STRING);
+ g_value_init (&list, GST_TYPE_LIST);
+ g_value_set_static_string (&item,
+ gst_video_multiview_mode_to_caps_string (preferred_mode));
+ gst_value_list_append_value (&list, &item);
+ g_value_set_static_string (&item,
+ gst_video_multiview_mode_to_caps_string (GST_VIDEO_MULTIVIEW_MODE_MONO));
+ gst_value_list_append_value (&list, &item);
+
+ gst_caps_set_value (templ_caps, "multiview-mode", &list);
+
+ g_value_unset (&list);
+ g_value_unset (&item);
+
+ /* And intersect with the peer */
+ if ((tmp = gst_pad_peer_query_caps (pad, NULL)) == NULL) {
+ gst_caps_unref (templ_caps);
+ return NULL;
+ }
+
+ outcaps = gst_caps_intersect_full (tmp, templ_caps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ gst_caps_unref (templ_caps);
+
+ GST_DEBUG_OBJECT (split, "Src pad %" GST_PTR_FORMAT " caps %" GST_PTR_FORMAT,
+ pad, outcaps);
+ return outcaps;
+}
+
+static gboolean
+stereosplit_set_output_caps (GstGLStereoSplit * split, GstCaps * sinkcaps)
+{
+ GstCaps *left = NULL, *right = NULL, *tridcaps = NULL;
+ GstCaps *tmp, *combined;
+ gboolean res = FALSE;
+
+ /* Choose some preferred output caps.
+ * Keep input width/height and PAR, preserve preferred output
+ * multiview flags for flipping/flopping if any, and set each
+ * left right pad to either left/mono and right/mono, as they prefer
+ */
+
+ /* Calculate what downstream can collectively support */
+ left =
+ stereosplit_get_src_caps (split, split->left_pad,
+ GST_VIDEO_MULTIVIEW_MODE_LEFT);
+ if (left == NULL)
+ goto fail;
+ right =
+ stereosplit_get_src_caps (split, split->right_pad,
+ GST_VIDEO_MULTIVIEW_MODE_RIGHT);
+ if (right == NULL)
+ goto fail;
+
+ tridcaps = stereosplit_transform_caps (split, GST_PAD_SINK, sinkcaps, NULL);
+
+ if (!tridcaps || gst_caps_is_empty (tridcaps)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to transform input caps %" GST_PTR_FORMAT, sinkcaps);
+ goto fail;
+ }
+
+ /* Preserve downstream preferred flipping/flopping */
+ tmp =
+ strip_mview_fields (gst_caps_ref (left),
+ GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED |
+ GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED);
+ combined = gst_caps_intersect (tridcaps, tmp);
+ gst_caps_unref (tridcaps);
+ gst_caps_unref (tmp);
+ tridcaps = combined;
+
+ tmp =
+ strip_mview_fields (gst_caps_ref (right),
+ GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED |
+ GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED);
+ combined = gst_caps_intersect (tridcaps, tmp);
+ gst_caps_unref (tridcaps);
+ gst_caps_unref (tmp);
+ tridcaps = combined;
+
+ if (G_UNLIKELY (gst_caps_is_empty (tridcaps))) {
+ gst_caps_unref (tridcaps);
+ goto fail;
+ }
+
+ /* Now generate the version for each output pad */
+ GST_DEBUG_OBJECT (split, "Attempting to set output caps %" GST_PTR_FORMAT,
+ tridcaps);
+ tmp = gst_caps_intersect (tridcaps, left);
+ gst_caps_unref (left);
+ left = tmp;
+ left = gst_caps_fixate (left);
+ if (!gst_pad_set_caps (split->left_pad, left)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to set left output caps %" GST_PTR_FORMAT, left);
+ goto fail;
+ }
+
+ tmp = gst_caps_intersect (tridcaps, right);
+ gst_caps_unref (right);
+ right = tmp;
+ right = gst_caps_fixate (right);
+ if (!gst_pad_set_caps (split->right_pad, right)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to set right output caps %" GST_PTR_FORMAT, right);
+ goto fail;
+ }
+
+ gst_gl_view_convert_set_context (split->viewconvert, split->context);
+
+ tridcaps = gst_caps_make_writable (tridcaps);
+ gst_caps_set_simple (tridcaps, "multiview-mode", G_TYPE_STRING,
+ "separated", "views", G_TYPE_INT, 2, NULL);
+ tridcaps = gst_caps_fixate (tridcaps);
+
+ if (!gst_gl_view_convert_set_caps (split->viewconvert, sinkcaps, tridcaps)) {
+ GST_ERROR_OBJECT (split, "Failed to set caps on converter");
+ goto fail;
+ }
+
+ /* FIXME: Provide left and right caps to do_bufferpool */
+ stereosplit_do_bufferpool (split, left);
+
+ res = TRUE;
+
+fail:
+ if (left)
+ gst_caps_unref (left);
+ if (right)
+ gst_caps_unref (right);
+ if (tridcaps)
+ gst_caps_unref (tridcaps);
+ return res;
+}
+
+static gboolean
+_find_local_gl_context (GstGLStereoSplit * split)
+{
+ if (gst_gl_query_local_gl_context (GST_ELEMENT (split), GST_PAD_SRC,
+ &split->context))
+ return TRUE;
+ if (gst_gl_query_local_gl_context (GST_ELEMENT (split), GST_PAD_SINK,
+ &split->context))
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+ensure_context (GstGLStereoSplit * self)
+{
+ GError *error = NULL;
+
+ if (!gst_gl_ensure_element_data (self, &self->display, &self->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (self->display, SUPPORTED_GL_APIS);
+
+ _find_local_gl_context (self);
+
+ if (!self->context) {
+ GST_OBJECT_LOCK (self->display);
+ do {
+ if (self->context)
+ gst_object_unref (self->context);
+ /* just get a GL context. we don't care */
+ self->context =
+ gst_gl_display_get_gl_context_for_thread (self->display, NULL);
+ if (!self->context) {
+ if (!gst_gl_display_create_context (self->display, self->other_context,
+ &self->context, &error)) {
+ GST_OBJECT_UNLOCK (self->display);
+ goto context_error;
+ }
+ }
+ } while (!gst_gl_display_add_context (self->display, self->context));
+ GST_OBJECT_UNLOCK (self->display);
+ }
+
+ {
+ GstGLAPI current_gl_api = gst_gl_context_get_gl_api (self->context);
+ if ((current_gl_api & (SUPPORTED_GL_APIS)) == 0)
+ goto unsupported_gl_api;
+ }
+
+ return TRUE;
+
+unsupported_gl_api:
+ {
+ GstGLAPI gl_api = gst_gl_context_get_gl_api (self->context);
+ gchar *gl_api_str = gst_gl_api_to_string (gl_api);
+ gchar *supported_gl_api_str = gst_gl_api_to_string (SUPPORTED_GL_APIS);
+ GST_ELEMENT_ERROR (self, RESOURCE, BUSY,
+ ("GL API's not compatible context: %s supported: %s", gl_api_str,
+ supported_gl_api_str), (NULL));
+
+ g_free (supported_gl_api_str);
+ g_free (gl_api_str);
+ return FALSE;
+ }
+context_error:
+ {
+ GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+ g_clear_error (&error);
+ return FALSE;
+ }
+}
+
+static gboolean
+stereosplit_decide_allocation (GstGLStereoSplit * self, GstQuery * query)
+{
+ if (!ensure_context (self))
+ return FALSE;
+
+ return TRUE;
+
+}
+
+static gboolean
+stereosplit_propose_allocation (GstGLStereoSplit * self, GstQuery * query)
+{
+
+ if (!gst_gl_ensure_element_data (self, &self->display, &self->other_context))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+stereosplit_do_bufferpool (GstGLStereoSplit * self, GstCaps * caps)
+{
+ GstQuery *query;
+
+ query = gst_query_new_allocation (caps, TRUE);
+ if (!gst_pad_peer_query (self->left_pad, query)) {
+ if (!gst_pad_peer_query (self->right_pad, query)) {
+ GST_DEBUG_OBJECT (self, "peer ALLOCATION query failed on both src pads");
+ }
+ }
+
+ if (!stereosplit_decide_allocation (self, query)) {
+ gst_query_unref (query);
+ return FALSE;
+ }
+
+ gst_query_unref (query);
+ return TRUE;
+}
+
+static GstFlowReturn
+stereosplit_chain (GstPad * pad, GstGLStereoSplit * split, GstBuffer * buf)
+{
+ GstBuffer *left, *right;
+ GstBuffer *split_buffer = NULL;
+ GstFlowReturn ret;
+ gint i, n_planes;
+
+ n_planes = GST_VIDEO_INFO_N_PLANES (&split->viewconvert->out_info);
+
+ GST_LOG_OBJECT (split, "chaining buffer %" GST_PTR_FORMAT, buf);
+
+ if (gst_gl_view_convert_submit_input_buffer (split->viewconvert,
+ GST_BUFFER_IS_DISCONT (buf), buf) != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to 3d convert buffer"),
+ ("Could not get submit input buffer"));
+ return GST_FLOW_ERROR;
+ }
+
+ ret = gst_gl_view_convert_get_output (split->viewconvert, &split_buffer);
+ if (ret != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to 3d convert buffer"), ("Could not get output buffer"));
+ return GST_FLOW_ERROR;
+ }
+ if (split_buffer == NULL)
+ return GST_FLOW_OK; /* Need another input buffer */
+
+ left = gst_buffer_new ();
+ gst_buffer_copy_into (left, buf,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ GST_BUFFER_FLAG_UNSET (left, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE);
+
+ gst_buffer_add_parent_buffer_meta (left, split_buffer);
+
+ for (i = 0; i < n_planes; i++) {
+ GstMemory *mem = gst_buffer_get_memory (split_buffer, i);
+ gst_buffer_append_memory (left, mem);
+ }
+
+ ret = gst_pad_push (split->left_pad, gst_buffer_ref (left));
+ /* Allow unlinked on the first pad - as long as the 2nd isn't unlinked */
+ gst_buffer_unref (left);
+ if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)) {
+ gst_buffer_unref (split_buffer);
+ return ret;
+ }
+
+ right = gst_buffer_new ();
+ gst_buffer_copy_into (right, buf,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ GST_BUFFER_FLAG_UNSET (left, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE);
+ gst_buffer_add_parent_buffer_meta (right, split_buffer);
+ for (i = n_planes; i < n_planes * 2; i++) {
+ GstMemory *mem = gst_buffer_get_memory (split_buffer, i);
+ gst_buffer_append_memory (right, mem);
+ }
+
+ ret = gst_pad_push (split->right_pad, gst_buffer_ref (right));
+ gst_buffer_unref (right);
+ gst_buffer_unref (split_buffer);
+ return ret;
+}
+
+static gboolean
+stereosplit_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) split, query,
+ split->display, split->context, split->other_context))
+ return TRUE;
+
+ return gst_pad_query_default (pad, parent, query);
+ }
+ /* FIXME: Handle caps query */
+ default:
+ return gst_pad_query_default (pad, parent, query);
+ }
+}
+
+static gboolean
+stereosplit_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ return gst_pad_event_default (pad, parent, event);
+}
+
+static gboolean
+stereosplit_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ GST_DEBUG_OBJECT (split, "sink query %s",
+ gst_query_type_get_name (GST_QUERY_TYPE (query)));
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) split, query,
+ split->display, split->context, split->other_context))
+ return TRUE;
+
+ return gst_pad_query_default (pad, parent, query);
+ }
+ case GST_QUERY_ALLOCATION:
+ {
+ return stereosplit_propose_allocation (split, query);
+ }
+ case GST_QUERY_ACCEPT_CAPS:
+ {
+ GstCaps *possible, *caps;
+ gboolean allowed;
+
+ gst_query_parse_accept_caps (query, &caps);
+
+ if (!(possible = gst_pad_query_caps (split->sink_pad, caps)))
+ return FALSE;
+
+ allowed = gst_caps_is_subset (caps, possible);
+ gst_caps_unref (possible);
+
+ gst_query_set_accept_caps_result (query, allowed);
+ return allowed;
+ }
+ case GST_QUERY_CAPS:
+ {
+ GstCaps *filter, *left, *right, *combined, *ret, *templ_caps;
+
+ gst_query_parse_caps (query, &filter);
+
+ /* Calculate what downstream can collectively support */
+ if (!(left = gst_pad_peer_query_caps (split->left_pad, NULL)))
+ return FALSE;
+ if (!(right = gst_pad_peer_query_caps (split->right_pad, NULL)))
+ return FALSE;
+
+ /* Strip out multiview mode and flags that might break the
+ * intersection, since we can convert.
+ * We could keep downstream preferred flip/flopping and list
+ * separated as preferred in the future which might
+ * theoretically allow us an easier conversion, but it's not essential
+ */
+ left = strip_mview_fields (left, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+ right = strip_mview_fields (right, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+
+ combined = gst_caps_intersect (left, right);
+ gst_caps_unref (left);
+ gst_caps_unref (right);
+
+ /* Intersect peer caps with our template formats */
+ templ_caps = gst_pad_get_pad_template_caps (split->left_pad);
+ ret =
+ gst_caps_intersect_full (combined, templ_caps,
+ GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (templ_caps);
+
+ gst_caps_unref (combined);
+ combined = ret;
+
+ if (!combined || gst_caps_is_empty (combined)) {
+ gst_caps_unref (combined);
+ return FALSE;
+ }
+
+ /* Convert from the src pad caps to input formats we support */
+ ret = stereosplit_transform_caps (split, GST_PAD_SRC, combined, filter);
+ gst_caps_unref (combined);
+ combined = ret;
+
+ /* Intersect with the sink pad template then */
+ templ_caps = gst_pad_get_pad_template_caps (split->sink_pad);
+ ret =
+ gst_caps_intersect_full (combined, templ_caps,
+ GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (templ_caps);
+
+ GST_LOG_OBJECT (split, "Returning sink pad caps %" GST_PTR_FORMAT, ret);
+
+ gst_query_set_caps_result (query, ret);
+ return !gst_caps_is_empty (ret);
+ }
+ default:
+ return gst_pad_query_default (pad, parent, query);
+ }
+}
+
+static gboolean
+stereosplit_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
+
+ return stereosplit_set_output_caps (split, caps);
+ }
+ default:
+ return gst_pad_event_default (pad, parent, event);
+ }
+}
diff --git a/ext/gl/gstglstereosplit.h b/ext/gl/gstglstereosplit.h
new file mode 100644
index 000000000..279bd2681
--- /dev/null
+++ b/ext/gl/gstglstereosplit.h
@@ -0,0 +1,64 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Jan Schmidt <jan@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_STEREOSPLIT_H__
+#define __GST_GL_STEREOSPLIT_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_STEREOSPLIT (gst_gl_stereosplit_get_type())
+#define GST_GL_STEREOSPLIT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplit))
+#define GST_IS_GL_STEREOSPLIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_STEREOSPLIT))
+#define GST_GL_STEREOSPLIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplitClass))
+#define GST_IS_GL_STEREOSPLIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_STEREOSPLIT))
+#define GST_GL_STEREOSPLIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplitClass))
+
+typedef struct _GstGLStereoSplit GstGLStereoSplit;
+typedef struct _GstGLStereoSplitClass GstGLStereoSplitClass;
+
+struct _GstGLStereoSplit
+{
+ GstElement parent;
+
+ GstPad *sink_pad;
+ GstPad *left_pad;
+ GstPad *right_pad;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+ GstGLContext *other_context;
+
+ GstGLViewConvert *viewconvert;
+};
+
+struct _GstGLStereoSplitClass
+{
+ GstElementClass parent_class;
+};
+
+GType gst_gl_stereosplit_get_type (void);
+
+G_END_DECLS
+
+#endif
diff --git a/ext/gl/gstgltestsrc.c b/ext/gl/gstgltestsrc.c
new file mode 100644
index 000000000..b0abea507
--- /dev/null
+++ b/ext/gl/gstgltestsrc.c
@@ -0,0 +1,771 @@
+/*
+ * GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) 2002,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-gltestsrc
+ * @title: gltestsrc
+ *
+ * The gltestsrc element is used to produce test video texture.
+ * The video test produced can be controlled with the "pattern"
+ * property.
+ *
+ * ## Example launch line
+ *
+ * |[
+ * gst-launch-1.0 -v gltestsrc pattern=smpte ! glimagesink
+ * ]|
+ * Shows original SMPTE color bars in a window.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglfuncs.h>
+#include <gst/gst-i18n-plugin.h>
+
+#include "gstgltestsrc.h"
+#include "gltestsrc.h"
+
+#define USE_PEER_BUFFERALLOC
+#define SUPPORTED_GL_APIS (GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2)
+
+GST_DEBUG_CATEGORY_STATIC (gl_test_src_debug);
+#define GST_CAT_DEFAULT gl_test_src_debug
+
+enum
+{
+ PROP_0,
+ PROP_PATTERN,
+ PROP_TIMESTAMP_OFFSET,
+ PROP_IS_LIVE
+ /* FILL ME */
+};
+
+/* *INDENT-OFF* */
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ","
+ "texture-target = (string) 2D")
+ );
+/* *INDENT-ON* */
+
+#define gst_gl_test_src_parent_class parent_class
+G_DEFINE_TYPE (GstGLTestSrc, gst_gl_test_src, GST_TYPE_PUSH_SRC);
+
+static void gst_gl_test_src_set_pattern (GstGLTestSrc * gltestsrc,
+ int pattern_type);
+static void gst_gl_test_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_test_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_test_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps);
+static GstCaps *gst_gl_test_src_fixate (GstBaseSrc * bsrc, GstCaps * caps);
+
+static gboolean gst_gl_test_src_is_seekable (GstBaseSrc * psrc);
+static gboolean gst_gl_test_src_do_seek (GstBaseSrc * bsrc,
+ GstSegment * segment);
+static gboolean gst_gl_test_src_query (GstBaseSrc * bsrc, GstQuery * query);
+static void gst_gl_test_src_set_context (GstElement * element,
+ GstContext * context);
+static GstStateChangeReturn gst_gl_test_src_change_state (GstElement * element,
+ GstStateChange transition);
+
+static void gst_gl_test_src_get_times (GstBaseSrc * basesrc,
+ GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
+static GstFlowReturn gst_gl_test_src_fill (GstPushSrc * psrc,
+ GstBuffer * buffer);
+static gboolean gst_gl_test_src_start (GstBaseSrc * basesrc);
+static gboolean gst_gl_test_src_stop (GstBaseSrc * basesrc);
+static gboolean gst_gl_test_src_decide_allocation (GstBaseSrc * basesrc,
+ GstQuery * query);
+
+static gboolean gst_gl_test_src_callback (gpointer stuff);
+
+static gboolean gst_gl_test_src_init_shader (GstGLTestSrc * gltestsrc);
+
+#define GST_TYPE_GL_TEST_SRC_PATTERN (gst_gl_test_src_pattern_get_type ())
+static GType
+gst_gl_test_src_pattern_get_type (void)
+{
+ static GType gl_test_src_pattern_type = 0;
+ static const GEnumValue pattern_types[] = {
+ {GST_GL_TEST_SRC_SMPTE, "SMPTE 100% color bars", "smpte"},
+ {GST_GL_TEST_SRC_SNOW, "Random (television snow)", "snow"},
+ {GST_GL_TEST_SRC_BLACK, "100% Black", "black"},
+ {GST_GL_TEST_SRC_WHITE, "100% White", "white"},
+ {GST_GL_TEST_SRC_RED, "Red", "red"},
+ {GST_GL_TEST_SRC_GREEN, "Green", "green"},
+ {GST_GL_TEST_SRC_BLUE, "Blue", "blue"},
+ {GST_GL_TEST_SRC_CHECKERS1, "Checkers 1px", "checkers-1"},
+ {GST_GL_TEST_SRC_CHECKERS2, "Checkers 2px", "checkers-2"},
+ {GST_GL_TEST_SRC_CHECKERS4, "Checkers 4px", "checkers-4"},
+ {GST_GL_TEST_SRC_CHECKERS8, "Checkers 8px", "checkers-8"},
+ {GST_GL_TEST_SRC_CIRCULAR, "Circular", "circular"},
+ {GST_GL_TEST_SRC_BLINK, "Blink", "blink"},
+ {GST_GL_TEST_SRC_MANDELBROT, "Mandelbrot Fractal", "mandelbrot"},
+ {0, NULL, NULL}
+ };
+
+ if (!gl_test_src_pattern_type) {
+ gl_test_src_pattern_type =
+ g_enum_register_static ("GstGLTestSrcPattern", pattern_types);
+ }
+ return gl_test_src_pattern_type;
+}
+
+static void
+gst_gl_test_src_class_init (GstGLTestSrcClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseSrcClass *gstbasesrc_class;
+ GstPushSrcClass *gstpushsrc_class;
+ GstElementClass *element_class;
+
+ GST_DEBUG_CATEGORY_INIT (gl_test_src_debug, "gltestsrc", 0,
+ "Video Test Source");
+
+ gobject_class = (GObjectClass *) klass;
+ gstbasesrc_class = (GstBaseSrcClass *) klass;
+ gstpushsrc_class = (GstPushSrcClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->set_property = gst_gl_test_src_set_property;
+ gobject_class->get_property = gst_gl_test_src_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_PATTERN,
+ g_param_spec_enum ("pattern", "Pattern",
+ "Type of test pattern to generate", GST_TYPE_GL_TEST_SRC_PATTERN,
+ GST_GL_TEST_SRC_SMPTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_TIMESTAMP_OFFSET, g_param_spec_int64 ("timestamp-offset",
+ "Timestamp offset",
+ "An offset added to timestamps set on buffers (in ns)", G_MININT64,
+ G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_IS_LIVE,
+ g_param_spec_boolean ("is-live", "Is Live",
+ "Whether to act as a live source", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "Video test source",
+ "Source/Video", "Creates a test video stream",
+ "David A. Schleef <ds@schleef.org>");
+
+ gst_element_class_add_static_pad_template (element_class, &src_factory);
+
+ element_class->set_context = gst_gl_test_src_set_context;
+ element_class->change_state = gst_gl_test_src_change_state;
+
+ gstbasesrc_class->set_caps = gst_gl_test_src_setcaps;
+ gstbasesrc_class->is_seekable = gst_gl_test_src_is_seekable;
+ gstbasesrc_class->do_seek = gst_gl_test_src_do_seek;
+ gstbasesrc_class->query = gst_gl_test_src_query;
+ gstbasesrc_class->get_times = gst_gl_test_src_get_times;
+ gstbasesrc_class->start = gst_gl_test_src_start;
+ gstbasesrc_class->stop = gst_gl_test_src_stop;
+ gstbasesrc_class->fixate = gst_gl_test_src_fixate;
+ gstbasesrc_class->decide_allocation = gst_gl_test_src_decide_allocation;
+
+ gstpushsrc_class->fill = gst_gl_test_src_fill;
+}
+
+static void
+gst_gl_test_src_init (GstGLTestSrc * src)
+{
+ gst_gl_test_src_set_pattern (src, GST_GL_TEST_SRC_SMPTE);
+
+ src->timestamp_offset = 0;
+
+ /* we operate in time */
+ gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
+ gst_base_src_set_live (GST_BASE_SRC (src), FALSE);
+}
+
+static GstCaps *
+gst_gl_test_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
+{
+ GstStructure *structure;
+
+ GST_DEBUG ("fixate");
+
+ caps = gst_caps_make_writable (caps);
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ gst_structure_fixate_field_nearest_int (structure, "width", 320);
+ gst_structure_fixate_field_nearest_int (structure, "height", 240);
+ gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
+
+ caps = GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps);
+
+ return caps;
+}
+
+static void
+gst_gl_test_src_set_pattern (GstGLTestSrc * gltestsrc, gint pattern_type)
+{
+ gltestsrc->set_pattern = pattern_type;
+}
+
+static void
+gst_gl_test_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATTERN:
+ gst_gl_test_src_set_pattern (src, g_value_get_enum (value));
+ break;
+ case PROP_TIMESTAMP_OFFSET:
+ src->timestamp_offset = g_value_get_int64 (value);
+ break;
+ case PROP_IS_LIVE:
+ gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+gst_gl_test_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATTERN:
+ g_value_set_enum (value, src->set_pattern);
+ break;
+ case PROP_TIMESTAMP_OFFSET:
+ g_value_set_int64 (value, src->timestamp_offset);
+ break;
+ case PROP_IS_LIVE:
+ g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_test_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps)
+{
+ GstGLTestSrc *gltestsrc = GST_GL_TEST_SRC (bsrc);
+
+ GST_DEBUG ("setcaps");
+
+ if (!gst_video_info_from_caps (&gltestsrc->out_info, caps))
+ goto wrong_caps;
+
+ gltestsrc->negotiated = TRUE;
+
+ gst_caps_replace (&gltestsrc->out_caps, caps);
+
+ return TRUE;
+
+/* ERRORS */
+wrong_caps:
+ {
+ GST_WARNING ("wrong caps");
+ return FALSE;
+ }
+}
+
+static void
+gst_gl_test_src_set_context (GstElement * element, GstContext * context)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (element);
+
+ gst_gl_handle_set_context (element, context, &src->display,
+ &src->other_context);
+
+ if (src->display)
+ gst_gl_display_filter_gl_api (src->display, SUPPORTED_GL_APIS);
+
+ GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static gboolean
+gst_gl_test_src_query (GstBaseSrc * bsrc, GstQuery * query)
+{
+ gboolean res = FALSE;
+ GstGLTestSrc *src;
+
+ src = GST_GL_TEST_SRC (bsrc);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ if (gst_gl_handle_context_query ((GstElement *) src, query,
+ src->display, src->context, src->other_context))
+ return TRUE;
+ break;
+ }
+ case GST_QUERY_CONVERT:
+ {
+ GstFormat src_fmt, dest_fmt;
+ gint64 src_val, dest_val;
+
+ gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
+ res =
+ gst_video_info_convert (&src->out_info, src_fmt, src_val, dest_fmt,
+ &dest_val);
+ gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
+
+ return res;
+ }
+ default:
+ break;
+ }
+
+ return GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
+}
+
+static void
+gst_gl_test_src_get_times (GstBaseSrc * basesrc, GstBuffer * buffer,
+ GstClockTime * start, GstClockTime * end)
+{
+ /* for live sources, sync on the timestamp of the buffer */
+ if (gst_base_src_is_live (basesrc)) {
+ GstClockTime timestamp = GST_BUFFER_TIMESTAMP (buffer);
+
+ if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
+ /* get duration to calculate end time */
+ GstClockTime duration = GST_BUFFER_DURATION (buffer);
+
+ if (GST_CLOCK_TIME_IS_VALID (duration))
+ *end = timestamp + duration;
+ *start = timestamp;
+ }
+ } else {
+ *start = -1;
+ *end = -1;
+ }
+}
+
+static gboolean
+gst_gl_test_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
+{
+ GstClockTime time;
+ GstGLTestSrc *src;
+
+ src = GST_GL_TEST_SRC (bsrc);
+
+ segment->time = segment->start;
+ time = segment->position;
+
+ /* now move to the time indicated */
+ if (src->out_info.fps_n) {
+ src->n_frames = gst_util_uint64_scale (time,
+ src->out_info.fps_n, src->out_info.fps_d * GST_SECOND);
+ } else
+ src->n_frames = 0;
+
+ if (src->out_info.fps_n) {
+ src->running_time = gst_util_uint64_scale (src->n_frames,
+ src->out_info.fps_d * GST_SECOND, src->out_info.fps_n);
+ } else {
+ /* FIXME : Not sure what to set here */
+ src->running_time = 0;
+ }
+
+ g_return_val_if_fail (src->running_time <= time, FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_test_src_is_seekable (GstBaseSrc * psrc)
+{
+ /* we're seekable... */
+ return TRUE;
+}
+
+static gboolean
+gst_gl_test_src_init_shader (GstGLTestSrc * gltestsrc)
+{
+ if (gst_gl_context_get_gl_api (gltestsrc->context)) {
+ /* blocking call, wait until the opengl thread has compiled the shader */
+// if (gltestsrc->vertex_src == NULL)
+// return FALSE;
+// return gst_gl_context_gen_shader (gltestsrc->context, gltestsrc->vertex_src,
+// gltestsrc->fragment_src, &gltestsrc->shader);
+ }
+ return TRUE;
+}
+
+static void
+_fill_gl (GstGLContext * context, GstGLTestSrc * src)
+{
+ src->gl_result = gst_gl_framebuffer_draw_to_texture (src->fbo, src->out_tex,
+ gst_gl_test_src_callback, src);
+}
+
+static GstFlowReturn
+gst_gl_test_src_fill (GstPushSrc * psrc, GstBuffer * buffer)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (psrc);
+ GstClockTime next_time;
+ GstVideoFrame out_frame;
+ GstGLSyncMeta *sync_meta;
+
+ if (G_UNLIKELY (!src->negotiated || !src->context))
+ goto not_negotiated;
+
+ /* 0 framerate and we are at the second frame, eos */
+ if (G_UNLIKELY (GST_VIDEO_INFO_FPS_N (&src->out_info) == 0
+ && src->n_frames == 1))
+ goto eos;
+
+ if (!gst_video_frame_map (&out_frame, &src->out_info, buffer,
+ GST_MAP_WRITE | GST_MAP_GL)) {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+
+ src->out_tex = (GstGLMemory *) out_frame.map[0].memory;
+
+ gst_gl_context_thread_add (src->context, (GstGLContextThreadFunc) _fill_gl,
+ src);
+ if (!src->gl_result) {
+ gst_video_frame_unmap (&out_frame);
+ goto gl_error;
+ }
+ gst_video_frame_unmap (&out_frame);
+ if (!src->gl_result)
+ goto gl_error;
+
+ sync_meta = gst_buffer_get_gl_sync_meta (buffer);
+ if (sync_meta)
+ gst_gl_sync_meta_set_sync_point (sync_meta, src->context);
+
+ GST_BUFFER_TIMESTAMP (buffer) = src->timestamp_offset + src->running_time;
+ GST_BUFFER_OFFSET (buffer) = src->n_frames;
+ src->n_frames++;
+ GST_BUFFER_OFFSET_END (buffer) = src->n_frames;
+ if (src->out_info.fps_n) {
+ next_time = gst_util_uint64_scale_int (src->n_frames * GST_SECOND,
+ src->out_info.fps_d, src->out_info.fps_n);
+ GST_BUFFER_DURATION (buffer) = next_time - src->running_time;
+ } else {
+ next_time = src->timestamp_offset;
+ /* NONE means forever */
+ GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
+ }
+
+ src->running_time = next_time;
+
+ return GST_FLOW_OK;
+
+gl_error:
+ {
+ GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (_("failed to draw pattern")),
+ (_("A GL error occurred")));
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+not_negotiated:
+ {
+ GST_ELEMENT_ERROR (src, CORE, NEGOTIATION, (NULL),
+ (_("format wasn't negotiated before get function")));
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+eos:
+ {
+ GST_DEBUG_OBJECT (src, "eos: 0 framerate, frame %d", (gint) src->n_frames);
+ return GST_FLOW_EOS;
+ }
+}
+
+static gboolean
+gst_gl_test_src_start (GstBaseSrc * basesrc)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (basesrc);
+
+ if (!gst_gl_ensure_element_data (src, &src->display, &src->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (src->display, SUPPORTED_GL_APIS);
+
+ src->running_time = 0;
+ src->n_frames = 0;
+ src->negotiated = FALSE;
+
+ return TRUE;
+}
+
+static void
+gst_gl_test_src_gl_stop (GstGLContext * context, GstGLTestSrc * src)
+{
+ if (src->fbo)
+ gst_object_unref (src->fbo);
+ src->fbo = NULL;
+
+ if (src->shader)
+ gst_object_unref (src->shader);
+ src->shader = NULL;
+
+
+ if (src->src_impl)
+ src->src_funcs->free (src->src_impl);
+ src->src_impl = NULL;
+}
+
+static gboolean
+gst_gl_test_src_stop (GstBaseSrc * basesrc)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (basesrc);
+
+ if (src->context)
+ gst_gl_context_thread_add (src->context,
+ (GstGLContextThreadFunc) gst_gl_test_src_gl_stop, src);
+
+ gst_caps_replace (&src->out_caps, NULL);
+
+ if (src->context)
+ gst_object_unref (src->context);
+ src->context = NULL;
+
+ return TRUE;
+}
+
+static gboolean
+_find_local_gl_context (GstGLTestSrc * src)
+{
+ if (gst_gl_query_local_gl_context (GST_ELEMENT (src), GST_PAD_SRC,
+ &src->context))
+ return TRUE;
+ return FALSE;
+}
+
+static void
+_src_generate_fbo_gl (GstGLContext * context, GstGLTestSrc * src)
+{
+ src->fbo = gst_gl_framebuffer_new_with_default_depth (src->context,
+ GST_VIDEO_INFO_WIDTH (&src->out_info),
+ GST_VIDEO_INFO_HEIGHT (&src->out_info));
+}
+
+static gboolean
+gst_gl_test_src_decide_allocation (GstBaseSrc * basesrc, GstQuery * query)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (basesrc);
+ GstBufferPool *pool = NULL;
+ GstStructure *config;
+ GstCaps *caps;
+ guint min, max, size;
+ gboolean update_pool;
+ GError *error = NULL;
+
+ if (!gst_gl_ensure_element_data (src, &src->display, &src->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (src->display, SUPPORTED_GL_APIS);
+
+ _find_local_gl_context (src);
+
+ if (!src->context) {
+ GST_OBJECT_LOCK (src->display);
+ do {
+ if (src->context) {
+ gst_object_unref (src->context);
+ src->context = NULL;
+ }
+ /* just get a GL context. we don't care */
+ src->context =
+ gst_gl_display_get_gl_context_for_thread (src->display, NULL);
+ if (!src->context) {
+ if (!gst_gl_display_create_context (src->display, src->other_context,
+ &src->context, &error)) {
+ GST_OBJECT_UNLOCK (src->display);
+ goto context_error;
+ }
+ }
+ } while (!gst_gl_display_add_context (src->display, src->context));
+ GST_OBJECT_UNLOCK (src->display);
+ }
+
+ if ((gst_gl_context_get_gl_api (src->context) & SUPPORTED_GL_APIS) == 0)
+ goto unsupported_gl_api;
+
+ gst_gl_context_thread_add (src->context,
+ (GstGLContextThreadFunc) _src_generate_fbo_gl, src);
+ if (!src->fbo)
+ goto context_error;
+
+ gst_query_parse_allocation (query, &caps, NULL);
+
+ if (gst_query_get_n_allocation_pools (query) > 0) {
+ gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+
+ update_pool = TRUE;
+ } else {
+ GstVideoInfo vinfo;
+
+ gst_video_info_init (&vinfo);
+ gst_video_info_from_caps (&vinfo, caps);
+ size = vinfo.size;
+ min = max = 0;
+ update_pool = FALSE;
+ }
+
+ if (!pool || !GST_IS_GL_BUFFER_POOL (pool)) {
+ /* can't use this pool */
+ if (pool)
+ gst_object_unref (pool);
+ pool = gst_gl_buffer_pool_new (src->context);
+ }
+ config = gst_buffer_pool_get_config (pool);
+
+ gst_buffer_pool_config_set_params (config, caps, size, min, max);
+ gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
+ if (gst_query_find_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, NULL))
+ gst_buffer_pool_config_add_option (config,
+ GST_BUFFER_POOL_OPTION_GL_SYNC_META);
+ gst_buffer_pool_config_add_option (config,
+ GST_BUFFER_POOL_OPTION_VIDEO_GL_TEXTURE_UPLOAD_META);
+
+ gst_buffer_pool_set_config (pool, config);
+
+ if (update_pool)
+ gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
+ else
+ gst_query_add_allocation_pool (query, pool, size, min, max);
+
+ gst_gl_test_src_init_shader (src);
+
+ gst_object_unref (pool);
+
+ return TRUE;
+
+unsupported_gl_api:
+ {
+ GstGLAPI gl_api = gst_gl_context_get_gl_api (src->context);
+ gchar *gl_api_str = gst_gl_api_to_string (gl_api);
+ gchar *supported_gl_api_str = gst_gl_api_to_string (SUPPORTED_GL_APIS);
+ GST_ELEMENT_ERROR (src, RESOURCE, BUSY,
+ ("GL API's not compatible context: %s supported: %s", gl_api_str,
+ supported_gl_api_str), (NULL));
+
+ g_free (supported_gl_api_str);
+ g_free (gl_api_str);
+ return FALSE;
+ }
+context_error:
+ {
+ if (error) {
+ GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+ g_clear_error (&error);
+ } else {
+ GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), (NULL));
+ }
+ if (src->context)
+ gst_object_unref (src->context);
+ src->context = NULL;
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_gl_test_src_callback (gpointer stuff)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (stuff);
+ const struct SrcFuncs *funcs;
+
+ funcs = src->src_funcs;
+
+ if (!funcs || src->set_pattern != src->active_pattern) {
+ if (src->src_impl && funcs)
+ funcs->free (src->src_impl);
+ src->src_funcs = funcs =
+ gst_gl_test_src_get_src_funcs_for_pattern (src->set_pattern);
+ if (funcs == NULL) {
+ GST_ERROR_OBJECT (src, "Could not find an implementation of the "
+ "requested pattern");
+ return FALSE;
+ }
+ src->src_impl = funcs->new (src);
+ if (!(src->gl_result =
+ funcs->init (src->src_impl, src->context, &src->out_info))) {
+ GST_ERROR_OBJECT (src, "Failed to initialize pattern");
+ return FALSE;
+ }
+ src->active_pattern = src->set_pattern;
+ }
+
+ return funcs->fill_bound_fbo (src->src_impl);
+}
+
+static GstStateChangeReturn
+gst_gl_test_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLTestSrc *src = GST_GL_TEST_SRC (element);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG_OBJECT (src, "changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_gl_ensure_element_data (element, &src->display,
+ &src->other_context))
+ return GST_STATE_CHANGE_FAILURE;
+
+ gst_gl_display_filter_gl_api (src->display, SUPPORTED_GL_APIS);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (src->other_context) {
+ gst_object_unref (src->other_context);
+ src->other_context = NULL;
+ }
+
+ if (src->display) {
+ gst_object_unref (src->display);
+ src->display = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstgltestsrc.h b/ext/gl/gstgltestsrc.h
new file mode 100644
index 000000000..df4abb74a
--- /dev/null
+++ b/ext/gl/gstgltestsrc.h
@@ -0,0 +1,94 @@
+/*
+ * GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) 2002,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_TEST_SRC_H__
+#define __GST_GL_TEST_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstpushsrc.h>
+
+#include <gst/gl/gl.h>
+
+#include "gltestsrc.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_TEST_SRC \
+ (gst_gl_test_src_get_type())
+#define GST_GL_TEST_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_TEST_SRC,GstGLTestSrc))
+#define GST_GL_TEST_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_TEST_SRC,GstGLTestSrcClass))
+#define GST_IS_GL_TEST_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_TEST_SRC))
+#define GST_IS_GL_TEST_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_TEST_SRC))
+
+typedef struct _GstGLTestSrcClass GstGLTestSrcClass;
+
+/**
+ * GstGLTestSrc:
+ *
+ * Opaque data structure.
+ */
+struct _GstGLTestSrc {
+ GstPushSrc element;
+
+ /*< private >*/
+
+ /* type of output */
+ GstGLTestSrcPattern set_pattern;
+ GstGLTestSrcPattern active_pattern;
+
+ /* video state */
+ GstVideoInfo out_info;
+
+ GstGLFramebuffer *fbo;
+ GstGLMemory *out_tex;
+
+ GstGLShader *shader;
+
+ GstBufferPool *pool;
+
+ GstGLDisplay *display;
+ GstGLContext *context, *other_context;
+ gint64 timestamp_offset; /* base offset */
+ GstClockTime running_time; /* total running time */
+ gint64 n_frames; /* total frames sent */
+ gboolean negotiated;
+
+ gboolean gl_result;
+ const struct SrcFuncs *src_funcs;
+ gpointer src_impl;
+
+ GstCaps *out_caps;
+};
+
+struct _GstGLTestSrcClass {
+ GstPushSrcClass parent_class;
+};
+
+GType gst_gl_test_src_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_GL_TEST_SRC_H__ */
diff --git a/ext/gl/gstgltransformation.c b/ext/gl/gstgltransformation.c
new file mode 100644
index 000000000..bb4bd2c6f
--- /dev/null
+++ b/ext/gl/gstgltransformation.c
@@ -0,0 +1,970 @@
+/*
+ * GStreamer
+ * Copyright (C) 2014 Lubosz Sarnecki <lubosz@gmail.com>
+ * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-gltransformation
+ * @title: gltransformation
+ *
+ * Transforms video on the GPU.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 gltestsrc ! gltransformation rotation-z=45 ! glimagesink
+ * ]| A pipeline to rotate by 45 degrees
+ * |[
+ * gst-launch-1.0 gltestsrc ! gltransformation translation-x=0.5 ! glimagesink
+ * ]| Translate the video by 0.5
+ * |[
+ * gst-launch-1.0 gltestsrc ! gltransformation scale-y=0.5 scale-x=0.5 ! glimagesink
+ * ]| Resize the video by 0.5
+ * |[
+ * gst-launch-1.0 gltestsrc ! gltransformation rotation-x=-45 ortho=True ! glimagesink
+ * ]| Rotate the video around the X-Axis by -45° with an orthographic projection
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgltransformation.h"
+
+#include <gst/gl/gstglapi.h>
+#include <graphene-gobject.h>
+#include "gstglutils.h"
+
+#define GST_CAT_DEFAULT gst_gl_transformation_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define gst_gl_transformation_parent_class parent_class
+
+#define VEC4_FORMAT "%f,%f,%f,%f"
+#define VEC4_ARGS(v) graphene_vec4_get_x (v), graphene_vec4_get_y (v), graphene_vec4_get_z (v), graphene_vec4_get_w (v)
+#define VEC3_FORMAT "%f,%f,%f"
+#define VEC3_ARGS(v) graphene_vec3_get_x (v), graphene_vec3_get_y (v), graphene_vec3_get_z (v)
+#define VEC2_FORMAT "%f,%f"
+#define VEC2_ARGS(v) graphene_vec2_get_x (v), graphene_vec2_get_y (v)
+#define POINT3D_FORMAT "%f,%f,%f"
+#define POINT3D_ARGS(p) (p)->x, (p)->y, (p)->z
+
+enum
+{
+ PROP_0,
+ PROP_FOV,
+ PROP_ORTHO,
+ PROP_TRANSLATION_X,
+ PROP_TRANSLATION_Y,
+ PROP_TRANSLATION_Z,
+ PROP_ROTATION_X,
+ PROP_ROTATION_Y,
+ PROP_ROTATION_Z,
+ PROP_SCALE_X,
+ PROP_SCALE_Y,
+ PROP_MVP,
+ PROP_PIVOT_X,
+ PROP_PIVOT_Y,
+ PROP_PIVOT_Z,
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_transformation_debug, "gltransformation", 0, "gltransformation element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLTransformation, gst_gl_transformation,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+
+static void gst_gl_transformation_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_transformation_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_transformation_set_caps (GstGLFilter * filter,
+ GstCaps * incaps, GstCaps * outcaps);
+static gboolean gst_gl_transformation_src_event (GstBaseTransform * trans,
+ GstEvent * event);
+static gboolean gst_gl_transformation_filter_meta (GstBaseTransform * trans,
+ GstQuery * query, GType api, const GstStructure * params);
+static gboolean gst_gl_transformation_decide_allocation (GstBaseTransform *
+ trans, GstQuery * query);
+
+static void gst_gl_transformation_gl_stop (GstGLBaseFilter * filter);
+static gboolean gst_gl_transformation_gl_start (GstGLBaseFilter * base_filter);
+static gboolean gst_gl_transformation_callback (gpointer stuff);
+static void gst_gl_transformation_build_mvp (GstGLTransformation *
+ transformation);
+
+static GstFlowReturn
+gst_gl_transformation_prepare_output_buffer (GstBaseTransform * trans,
+ GstBuffer * inbuf, GstBuffer ** outbuf);
+static gboolean gst_gl_transformation_filter (GstGLFilter * filter,
+ GstBuffer * inbuf, GstBuffer * outbuf);
+static gboolean gst_gl_transformation_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex);
+
+static void
+gst_gl_transformation_class_init (GstGLTransformationClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstBaseTransformClass *base_transform_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+ base_transform_class = GST_BASE_TRANSFORM_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_transformation_set_property;
+ gobject_class->get_property = gst_gl_transformation_get_property;
+
+ base_transform_class->src_event = gst_gl_transformation_src_event;
+ base_transform_class->decide_allocation =
+ gst_gl_transformation_decide_allocation;
+ base_transform_class->filter_meta = gst_gl_transformation_filter_meta;
+
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_start = gst_gl_transformation_gl_start;
+ GST_GL_BASE_FILTER_CLASS (klass)->gl_stop = gst_gl_transformation_gl_stop;
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_transformation_set_caps;
+ GST_GL_FILTER_CLASS (klass)->filter = gst_gl_transformation_filter;
+ GST_GL_FILTER_CLASS (klass)->filter_texture =
+ gst_gl_transformation_filter_texture;
+ GST_BASE_TRANSFORM_CLASS (klass)->prepare_output_buffer =
+ gst_gl_transformation_prepare_output_buffer;
+
+ g_object_class_install_property (gobject_class, PROP_FOV,
+ g_param_spec_float ("fov", "Fov", "Field of view angle in degrees",
+ 0.0, G_MAXFLOAT, 90.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ORTHO,
+ g_param_spec_boolean ("ortho", "Orthographic",
+ "Use orthographic projection", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Rotation */
+ g_object_class_install_property (gobject_class, PROP_ROTATION_X,
+ g_param_spec_float ("rotation-x", "X Rotation",
+ "Rotates the video around the X-Axis in degrees.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ROTATION_Y,
+ g_param_spec_float ("rotation-y", "Y Rotation",
+ "Rotates the video around the Y-Axis in degrees.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ROTATION_Z,
+ g_param_spec_float ("rotation-z", "Z Rotation",
+ "Rotates the video around the Z-Axis in degrees.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Translation */
+ g_object_class_install_property (gobject_class, PROP_TRANSLATION_X,
+ g_param_spec_float ("translation-x", "X Translation",
+ "Translates the video at the X-Axis, in universal [0-1] coordinate.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TRANSLATION_Y,
+ g_param_spec_float ("translation-y", "Y Translation",
+ "Translates the video at the Y-Axis, in universal [0-1] coordinate.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TRANSLATION_Z,
+ g_param_spec_float ("translation-z", "Z Translation",
+ "Translates the video at the Z-Axis, in universal [0-1] coordinate.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Scale */
+ g_object_class_install_property (gobject_class, PROP_SCALE_X,
+ g_param_spec_float ("scale-x", "X Scale",
+ "Scale multiplier for the X-Axis.",
+ -G_MAXFLOAT, G_MAXFLOAT, 1.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SCALE_Y,
+ g_param_spec_float ("scale-y", "Y Scale",
+ "Scale multiplier for the Y-Axis.",
+ -G_MAXFLOAT, G_MAXFLOAT, 1.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Pivot */
+ g_object_class_install_property (gobject_class, PROP_PIVOT_X,
+ g_param_spec_float ("pivot-x", "X Pivot",
+ "Rotation pivot point X coordinate, where 0 is the center,"
+ " -1 the left border, +1 the right border and <-1, >1 outside.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PIVOT_Y,
+ g_param_spec_float ("pivot-y", "Y Pivot",
+ "Rotation pivot point X coordinate, where 0 is the center,"
+ " -1 the left border, +1 the right border and <-1, >1 outside.",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PIVOT_Z,
+ g_param_spec_float ("pivot-z", "Z Pivot",
+ "Relevant for rotation in 3D space. You look into the negative Z axis direction",
+ -G_MAXFLOAT, G_MAXFLOAT, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* MVP */
+ g_object_class_install_property (gobject_class, PROP_MVP,
+ g_param_spec_boxed ("mvp-matrix",
+ "Modelview Projection Matrix",
+ "The final Graphene 4x4 Matrix for transformation",
+ GRAPHENE_TYPE_MATRIX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "OpenGL transformation filter",
+ "Filter/Effect/Video", "Transform video on the GPU",
+ "Lubosz Sarnecki <lubosz@gmail.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2;
+}
+
+static void
+gst_gl_transformation_init (GstGLTransformation * filter)
+{
+ filter->shader = NULL;
+ filter->fov = 90;
+ filter->aspect = 1.0;
+ filter->znear = 0.1f;
+ filter->zfar = 100.0;
+
+ filter->xscale = 1.0;
+ filter->yscale = 1.0;
+
+ filter->in_tex = 0;
+
+ gst_gl_transformation_build_mvp (filter);
+}
+
+static void
+gst_gl_transformation_build_mvp (GstGLTransformation * transformation)
+{
+ GstGLFilter *filter = GST_GL_FILTER (transformation);
+ graphene_matrix_t modelview_matrix;
+
+ if (!filter->out_info.finfo) {
+ graphene_matrix_init_identity (&transformation->model_matrix);
+ graphene_matrix_init_identity (&transformation->view_matrix);
+ graphene_matrix_init_identity (&transformation->projection_matrix);
+ } else {
+ graphene_point3d_t translation_vector =
+ GRAPHENE_POINT3D_INIT (transformation->xtranslation * 2.0 *
+ transformation->aspect,
+ transformation->ytranslation * 2.0,
+ transformation->ztranslation * 2.0);
+
+ graphene_point3d_t pivot_vector =
+ GRAPHENE_POINT3D_INIT (-transformation->xpivot * transformation->aspect,
+ transformation->ypivot,
+ -transformation->zpivot);
+
+ graphene_point3d_t negative_pivot_vector;
+
+ graphene_vec3_t center;
+ graphene_vec3_t up;
+
+ gboolean current_passthrough;
+ gboolean passthrough;
+
+ graphene_vec3_init (&transformation->camera_position, 0.f, 0.f, 1.f);
+ graphene_vec3_init (&center, 0.f, 0.f, 0.f);
+ graphene_vec3_init (&up, 0.f, 1.f, 0.f);
+
+ /* Translate into pivot origin */
+ graphene_matrix_init_translate (&transformation->model_matrix,
+ &pivot_vector);
+
+ /* Scale */
+ graphene_matrix_scale (&transformation->model_matrix,
+ transformation->xscale, transformation->yscale, 1.0f);
+
+ /* Rotation */
+ graphene_matrix_rotate (&transformation->model_matrix,
+ transformation->xrotation, graphene_vec3_x_axis ());
+ graphene_matrix_rotate (&transformation->model_matrix,
+ transformation->yrotation, graphene_vec3_y_axis ());
+ graphene_matrix_rotate (&transformation->model_matrix,
+ transformation->zrotation, graphene_vec3_z_axis ());
+
+ /* Translate back from pivot origin */
+ graphene_point3d_scale (&pivot_vector, -1.0, &negative_pivot_vector);
+ graphene_matrix_translate (&transformation->model_matrix,
+ &negative_pivot_vector);
+
+ /* Translation */
+ graphene_matrix_translate (&transformation->model_matrix,
+ &translation_vector);
+
+ if (transformation->ortho) {
+ graphene_matrix_init_ortho (&transformation->projection_matrix,
+ -transformation->aspect, transformation->aspect,
+ -1, 1, transformation->znear, transformation->zfar);
+ } else {
+ graphene_matrix_init_perspective (&transformation->projection_matrix,
+ transformation->fov,
+ transformation->aspect, transformation->znear, transformation->zfar);
+ }
+
+ graphene_matrix_init_look_at (&transformation->view_matrix,
+ &transformation->camera_position, &center, &up);
+
+ current_passthrough =
+ gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (transformation));
+ passthrough = transformation->xtranslation == 0.
+ && transformation->ytranslation == 0.
+ && transformation->ztranslation == 0. && transformation->xrotation == 0.
+ && transformation->yrotation == 0. && transformation->zrotation == 0.
+ && transformation->xscale == 1. && transformation->yscale == 1.
+ && gst_video_info_is_equal (&filter->in_info, &filter->out_info);
+ gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (transformation),
+ passthrough);
+ if (current_passthrough != passthrough) {
+ gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (transformation));
+ }
+ }
+
+ graphene_matrix_multiply (&transformation->model_matrix,
+ &transformation->view_matrix, &modelview_matrix);
+ graphene_matrix_multiply (&modelview_matrix,
+ &transformation->projection_matrix, &transformation->mvp_matrix);
+
+ graphene_matrix_inverse (&transformation->model_matrix,
+ &transformation->inv_model_matrix);
+ graphene_matrix_inverse (&transformation->view_matrix,
+ &transformation->inv_view_matrix);
+ graphene_matrix_inverse (&transformation->projection_matrix,
+ &transformation->inv_projection_matrix);
+}
+
+static void
+gst_gl_transformation_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLTransformation *filter = GST_GL_TRANSFORMATION (object);
+
+ switch (prop_id) {
+ case PROP_FOV:
+ filter->fov = g_value_get_float (value);
+ break;
+ case PROP_ORTHO:
+ filter->ortho = g_value_get_boolean (value);
+ break;
+ case PROP_TRANSLATION_X:
+ filter->xtranslation = g_value_get_float (value);
+ break;
+ case PROP_TRANSLATION_Y:
+ filter->ytranslation = g_value_get_float (value);
+ break;
+ case PROP_TRANSLATION_Z:
+ filter->ztranslation = g_value_get_float (value);
+ break;
+ case PROP_ROTATION_X:
+ filter->xrotation = g_value_get_float (value);
+ break;
+ case PROP_ROTATION_Y:
+ filter->yrotation = g_value_get_float (value);
+ break;
+ case PROP_ROTATION_Z:
+ filter->zrotation = g_value_get_float (value);
+ break;
+ case PROP_SCALE_X:
+ filter->xscale = g_value_get_float (value);
+ break;
+ case PROP_SCALE_Y:
+ filter->yscale = g_value_get_float (value);
+ break;
+ case PROP_PIVOT_X:
+ filter->xpivot = g_value_get_float (value);
+ break;
+ case PROP_PIVOT_Y:
+ filter->ypivot = g_value_get_float (value);
+ break;
+ case PROP_PIVOT_Z:
+ filter->zpivot = g_value_get_float (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ gst_gl_transformation_build_mvp (filter);
+}
+
+static void
+gst_gl_transformation_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLTransformation *filter = GST_GL_TRANSFORMATION (object);
+
+ switch (prop_id) {
+ case PROP_FOV:
+ g_value_set_float (value, filter->fov);
+ break;
+ case PROP_ORTHO:
+ g_value_set_boolean (value, filter->ortho);
+ break;
+ case PROP_TRANSLATION_X:
+ g_value_set_float (value, filter->xtranslation);
+ break;
+ case PROP_TRANSLATION_Y:
+ g_value_set_float (value, filter->ytranslation);
+ break;
+ case PROP_TRANSLATION_Z:
+ g_value_set_float (value, filter->ztranslation);
+ break;
+ case PROP_ROTATION_X:
+ g_value_set_float (value, filter->xrotation);
+ break;
+ case PROP_ROTATION_Y:
+ g_value_set_float (value, filter->yrotation);
+ break;
+ case PROP_ROTATION_Z:
+ g_value_set_float (value, filter->zrotation);
+ break;
+ case PROP_SCALE_X:
+ g_value_set_float (value, filter->xscale);
+ break;
+ case PROP_SCALE_Y:
+ g_value_set_float (value, filter->yscale);
+ break;
+ case PROP_PIVOT_X:
+ g_value_set_float (value, filter->xpivot);
+ break;
+ case PROP_PIVOT_Y:
+ g_value_set_float (value, filter->ypivot);
+ break;
+ case PROP_PIVOT_Z:
+ g_value_set_float (value, filter->zpivot);
+ break;
+ case PROP_MVP:
+ /* FIXME: need to decompose this to support navigation events */
+ g_value_set_boxed (value, (gconstpointer) & filter->mvp_matrix);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_transformation_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
+
+ transformation->aspect =
+ (gdouble) GST_VIDEO_INFO_WIDTH (&filter->out_info) /
+ (gdouble) GST_VIDEO_INFO_HEIGHT (&filter->out_info);
+
+ transformation->caps_change = TRUE;
+
+ gst_gl_transformation_build_mvp (transformation);
+
+ return TRUE;
+}
+
+static void
+_intersect_plane_and_ray (graphene_plane_t * video_plane, graphene_ray_t * ray,
+ graphene_point3d_t * result)
+{
+ float t = graphene_ray_get_distance_to_plane (ray, video_plane);
+ GST_TRACE ("Calculated a distance of %f to the plane", t);
+ graphene_ray_get_position_at (ray, t, result);
+}
+
+static void
+_screen_coord_to_world_ray (GstGLTransformation * transformation, float x,
+ float y, graphene_ray_t * ray)
+{
+ GstGLFilter *filter = GST_GL_FILTER (transformation);
+ gfloat w = (gfloat) GST_VIDEO_INFO_WIDTH (&filter->in_info);
+ gfloat h = (gfloat) GST_VIDEO_INFO_HEIGHT (&filter->in_info);
+ graphene_vec3_t ray_eye_vec3, ray_world_dir, *ray_origin, *ray_direction;
+ graphene_vec3_t ray_ortho_dir;
+ graphene_point3d_t ray_clip, ray_eye;
+ graphene_vec2_t screen_coord;
+
+ /* GL is y-flipped. i.e. 0, 0 is the bottom left corner in screen space */
+ graphene_vec2_init (&screen_coord, (2. * x / w - 1.) / transformation->aspect,
+ 1. - 2. * y / h);
+
+ graphene_point3d_init (&ray_clip, graphene_vec2_get_x (&screen_coord),
+ graphene_vec2_get_y (&screen_coord), -1.);
+ graphene_matrix_transform_point3d (&transformation->inv_projection_matrix,
+ &ray_clip, &ray_eye);
+
+ graphene_vec3_init (&ray_eye_vec3, ray_eye.x, ray_eye.y, -1.);
+
+ if (transformation->ortho) {
+ graphene_vec3_init (&ray_ortho_dir, 0., 0., 1.);
+
+ ray_origin = &ray_eye_vec3;
+ ray_direction = &ray_ortho_dir;
+ } else {
+ graphene_matrix_transform_vec3 (&transformation->inv_view_matrix,
+ &ray_eye_vec3, &ray_world_dir);
+ graphene_vec3_normalize (&ray_world_dir, &ray_world_dir);
+
+ ray_origin = &transformation->camera_position;
+ ray_direction = &ray_world_dir;
+ }
+
+ graphene_ray_init_from_vec3 (ray, ray_origin, ray_direction);
+
+ GST_TRACE_OBJECT (transformation, "Calculated ray origin: " VEC3_FORMAT
+ " direction: " VEC3_FORMAT " from screen coordinates: " VEC2_FORMAT
+ " with %s projection",
+ VEC3_ARGS (ray_origin), VEC3_ARGS (ray_direction),
+ VEC2_ARGS (&screen_coord),
+ transformation->ortho ? "ortho" : "perspection");
+}
+
+static void
+_init_world_video_plane (GstGLTransformation * transformation,
+ graphene_plane_t * video_plane)
+{
+ graphene_point3d_t bottom_left, bottom_right, top_left, top_right;
+ graphene_point3d_t world_bottom_left, world_bottom_right;
+ graphene_point3d_t world_top_left, world_top_right;
+
+ graphene_point3d_init (&top_left, -transformation->aspect, 1., 0.);
+ graphene_point3d_init (&top_right, transformation->aspect, 1., 0.);
+ graphene_point3d_init (&bottom_left, -transformation->aspect, -1., 0.);
+ graphene_point3d_init (&bottom_right, transformation->aspect, -1., 0.);
+
+ graphene_matrix_transform_point3d (&transformation->model_matrix,
+ &bottom_left, &world_bottom_left);
+ graphene_matrix_transform_point3d (&transformation->model_matrix,
+ &bottom_right, &world_bottom_right);
+ graphene_matrix_transform_point3d (&transformation->model_matrix,
+ &top_left, &world_top_left);
+ graphene_matrix_transform_point3d (&transformation->model_matrix,
+ &top_right, &world_top_right);
+
+ graphene_plane_init_from_points (video_plane, &world_bottom_left,
+ &world_top_right, &world_top_left);
+}
+
+static gboolean
+_screen_coord_to_model_coord (GstGLTransformation * transformation,
+ double x, double y, double *res_x, double *res_y)
+{
+ GstGLFilter *filter = GST_GL_FILTER (transformation);
+ double w = (double) GST_VIDEO_INFO_WIDTH (&filter->in_info);
+ double h = (double) GST_VIDEO_INFO_HEIGHT (&filter->in_info);
+ graphene_point3d_t world_point, model_coord;
+ graphene_plane_t video_plane;
+ graphene_ray_t ray;
+ double new_x, new_y;
+
+ _init_world_video_plane (transformation, &video_plane);
+ _screen_coord_to_world_ray (transformation, x, y, &ray);
+ _intersect_plane_and_ray (&video_plane, &ray, &world_point);
+ graphene_matrix_transform_point3d (&transformation->inv_model_matrix,
+ &world_point, &model_coord);
+
+ /* ndc to pixels. We render the frame Y-flipped so need to unflip the
+ * y coordinate */
+ new_x = (model_coord.x + 1.) * w / 2;
+ new_y = (1. - model_coord.y) * h / 2;
+
+ if (new_x < 0. || new_x > w || new_y < 0. || new_y > h)
+ /* coords off video surface */
+ return FALSE;
+
+ GST_DEBUG_OBJECT (transformation, "converted %f,%f to %f,%f", x, y, new_x,
+ new_y);
+
+ if (res_x)
+ *res_x = new_x;
+ if (res_y)
+ *res_y = new_y;
+
+ return TRUE;
+}
+
+#if 0
+/* debugging facilities for transforming vertices from model space to screen
+ * space */
+static void
+_ndc_to_viewport (GstGLTransformation * transformation, graphene_vec3_t * ndc,
+ int x, int y, int w, int h, float near, float far, graphene_vec3_t * result)
+{
+ GstGLFilter *filter = GST_GL_FILTER (transformation);
+ /* center of the viewport */
+ int o_x = x + w / 2;
+ int o_y = y + h / 2;
+
+ graphene_vec3_init (result, graphene_vec3_get_x (ndc) * w / 2 + o_x,
+ graphene_vec3_get_y (ndc) * h / 2 + o_y,
+ (far - near) * graphene_vec3_get_z (ndc) / 2 + (far + near) / 2);
+}
+
+static void
+_perspective_division (graphene_vec4_t * clip, graphene_vec3_t * result)
+{
+ float w = graphene_vec4_get_w (clip);
+
+ graphene_vec3_init (result, graphene_vec4_get_x (clip) / w,
+ graphene_vec4_get_y (clip) / w, graphene_vec4_get_z (clip) / w);
+}
+
+static void
+_vertex_to_screen_coord (GstGLTransformation * transformation,
+ graphene_vec4_t * vertex, graphene_vec3_t * view)
+{
+ GstGLFilter *filter = GST_GL_FILTER (transformation);
+ gint w = GST_VIDEO_INFO_WIDTH (&filter->in_info);
+ gint h = GST_VIDEO_INFO_HEIGHT (&filter->in_info);
+ graphene_vec4_t clip;
+ graphene_vec3_t ndc;
+
+ graphene_matrix_transform_vec4 (&transformation->mvp_matrix, vertex, &clip);
+ _perspective_division (&clip, &ndc);
+ _ndc_to_viewport (transformation, &ndc, 0, 0, w, h, 0., 1., view);
+}
+#endif
+
+static gboolean
+gst_gl_transformation_src_event (GstBaseTransform * trans, GstEvent * event)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
+ GstStructure *structure;
+ gboolean ret;
+
+ GST_DEBUG_OBJECT (trans, "handling %s event", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NAVIGATION:{
+ gdouble x, y;
+ event =
+ GST_EVENT (gst_mini_object_make_writable (GST_MINI_OBJECT (event)));
+
+ structure = (GstStructure *) gst_event_get_structure (event);
+ if (gst_structure_get_double (structure, "pointer_x", &x) &&
+ gst_structure_get_double (structure, "pointer_y", &y)) {
+ gdouble new_x, new_y;
+
+ if (!_screen_coord_to_model_coord (transformation, x, y, &new_x,
+ &new_y)) {
+ gst_event_unref (event);
+ return TRUE;
+ }
+
+ gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, new_x,
+ "pointer_y", G_TYPE_DOUBLE, new_y, NULL);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ ret = GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans, event);
+
+ return ret;
+}
+
+static gboolean
+gst_gl_transformation_filter_meta (GstBaseTransform * trans, GstQuery * query,
+ GType api, const GstStructure * params)
+{
+ if (api == GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE)
+ return TRUE;
+
+ if (api == GST_GL_SYNC_META_API_TYPE)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+gst_gl_transformation_decide_allocation (GstBaseTransform * trans,
+ GstQuery * query)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
+
+ if (!GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
+ query))
+ return FALSE;
+
+ if (gst_query_find_allocation_meta (query,
+ GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE, NULL)) {
+ transformation->downstream_supports_affine_meta = TRUE;
+ } else {
+ transformation->downstream_supports_affine_meta = FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gst_gl_transformation_gl_stop (GstGLBaseFilter * base_filter)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (base_filter);
+ const GstGLFuncs *gl = base_filter->context->gl_vtable;
+
+ if (transformation->vao) {
+ gl->DeleteVertexArrays (1, &transformation->vao);
+ transformation->vao = 0;
+ }
+
+ if (transformation->vertex_buffer) {
+ gl->DeleteBuffers (1, &transformation->vertex_buffer);
+ transformation->vertex_buffer = 0;
+ }
+
+ if (transformation->vbo_indices) {
+ gl->DeleteBuffers (1, &transformation->vbo_indices);
+ transformation->vbo_indices = 0;
+ }
+
+ if (transformation->shader) {
+ gst_object_unref (transformation->shader);
+ transformation->shader = NULL;
+ }
+
+ GST_GL_BASE_FILTER_CLASS (parent_class)->gl_stop (base_filter);
+}
+
+static gboolean
+gst_gl_transformation_gl_start (GstGLBaseFilter * base_filter)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (base_filter);
+
+ if (!GST_GL_BASE_FILTER_CLASS (parent_class)->gl_start (base_filter))
+ return FALSE;
+
+ if (gst_gl_context_get_gl_api (base_filter->context)) {
+ /* blocking call, wait until the opengl thread has compiled the shader */
+ return gst_gl_context_gen_shader (base_filter->context,
+ gst_gl_shader_string_vertex_mat4_vertex_transform,
+ gst_gl_shader_string_fragment_default, &transformation->shader);
+ }
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_gl_transformation_prepare_output_buffer (GstBaseTransform * trans,
+ GstBuffer * inbuf, GstBuffer ** outbuf)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
+ GstGLFilter *filter = GST_GL_FILTER (trans);
+
+ if (transformation->downstream_supports_affine_meta &&
+ gst_video_info_is_equal (&filter->in_info, &filter->out_info)) {
+ GstVideoAffineTransformationMeta *af_meta;
+ graphene_matrix_t upstream_matrix, tmp, tmp2, inv_aspect, yflip;
+ float upstream[16], downstream[16];
+
+ *outbuf = gst_buffer_make_writable (inbuf);
+
+ af_meta = gst_buffer_get_video_affine_transformation_meta (inbuf);
+ if (!af_meta)
+ af_meta = gst_buffer_add_video_affine_transformation_meta (*outbuf);
+
+ GST_LOG_OBJECT (trans, "applying transformation to existing affine "
+ "transformation meta");
+
+ gst_gl_get_affine_transformation_meta_as_ndc_ext (af_meta, upstream);
+
+ /* apply the transformation to the existing affine meta */
+ graphene_matrix_init_from_float (&upstream_matrix, upstream);
+ graphene_matrix_init_scale (&inv_aspect, transformation->aspect, -1., 1.);
+ graphene_matrix_init_scale (&yflip, 1., -1., 1.);
+
+ /* invert the aspect effects */
+ graphene_matrix_multiply (&upstream_matrix, &inv_aspect, &tmp2);
+ /* apply the transformation */
+ graphene_matrix_multiply (&tmp2, &transformation->mvp_matrix, &tmp);
+ /* and undo yflip */
+ graphene_matrix_multiply (&tmp, &yflip, &tmp2);
+
+ graphene_matrix_to_float (&tmp2, downstream);
+ gst_gl_set_affine_transformation_meta_from_ndc_ext (af_meta, downstream);
+
+ return GST_FLOW_OK;
+ }
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans,
+ inbuf, outbuf);
+}
+
+static gboolean
+gst_gl_transformation_filter (GstGLFilter * filter,
+ GstBuffer * inbuf, GstBuffer * outbuf)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
+
+ if (transformation->downstream_supports_affine_meta &&
+ gst_video_info_is_equal (&filter->in_info, &filter->out_info)) {
+ return TRUE;
+ } else {
+ return gst_gl_filter_filter_texture (filter, inbuf, outbuf);
+ }
+}
+
+static gboolean
+gst_gl_transformation_filter_texture (GstGLFilter * filter,
+ GstGLMemory * in_tex, GstGLMemory * out_tex)
+{
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
+
+ transformation->in_tex = in_tex;
+
+ gst_gl_framebuffer_draw_to_texture (filter->fbo, out_tex,
+ (GstGLFramebufferFunc) gst_gl_transformation_callback, transformation);
+
+ return TRUE;
+}
+
+static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
+
+static void
+_upload_vertices (GstGLTransformation * transformation)
+{
+ const GstGLFuncs *gl =
+ GST_GL_BASE_FILTER (transformation)->context->gl_vtable;
+
+/* *INDENT-OFF* */
+ GLfloat vertices[] = {
+ -transformation->aspect, -1.0, 0.0, 1.0, 0.0, 0.0,
+ transformation->aspect, -1.0, 0.0, 1.0, 1.0, 0.0,
+ transformation->aspect, 1.0, 0.0, 1.0, 1.0, 1.0,
+ -transformation->aspect, 1.0, 0.0, 1.0, 0.0, 1.0,
+ };
+ /* *INDENT-ON* */
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, transformation->vertex_buffer);
+
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 6 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+}
+
+static void
+_bind_buffer (GstGLTransformation * transformation)
+{
+ const GstGLFuncs *gl =
+ GST_GL_BASE_FILTER (transformation)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, transformation->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, transformation->vertex_buffer);
+
+ /* Load the vertex position */
+ gl->VertexAttribPointer (transformation->attr_position, 4, GL_FLOAT,
+ GL_FALSE, 6 * sizeof (GLfloat), (void *) 0);
+
+ /* Load the texture coordinate */
+ gl->VertexAttribPointer (transformation->attr_texture, 2, GL_FLOAT, GL_FALSE,
+ 6 * sizeof (GLfloat), (void *) (4 * sizeof (GLfloat)));
+
+ gl->EnableVertexAttribArray (transformation->attr_position);
+ gl->EnableVertexAttribArray (transformation->attr_texture);
+}
+
+static void
+_unbind_buffer (GstGLTransformation * transformation)
+{
+ const GstGLFuncs *gl =
+ GST_GL_BASE_FILTER (transformation)->context->gl_vtable;
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (transformation->attr_position);
+ gl->DisableVertexAttribArray (transformation->attr_texture);
+}
+
+static gboolean
+gst_gl_transformation_callback (gpointer stuff)
+{
+ GstGLFilter *filter = GST_GL_FILTER (stuff);
+ GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
+ GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
+
+ GLfloat temp_matrix[16];
+
+ gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->ClearColor (0.f, 0.f, 0.f, 0.f);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ gst_gl_shader_use (transformation->shader);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, transformation->in_tex->tex_id);
+ gst_gl_shader_set_uniform_1i (transformation->shader, "texture", 0);
+
+ graphene_matrix_to_float (&transformation->mvp_matrix, temp_matrix);
+ gst_gl_shader_set_uniform_matrix_4fv (transformation->shader,
+ "u_transformation", 1, GL_FALSE, temp_matrix);
+
+ if (!transformation->vertex_buffer) {
+ transformation->attr_position =
+ gst_gl_shader_get_attribute_location (transformation->shader,
+ "a_position");
+
+ transformation->attr_texture =
+ gst_gl_shader_get_attribute_location (transformation->shader,
+ "a_texcoord");
+
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &transformation->vao);
+ gl->BindVertexArray (transformation->vao);
+ }
+
+ gl->GenBuffers (1, &transformation->vertex_buffer);
+
+ gl->GenBuffers (1, &transformation->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, transformation->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+
+ transformation->caps_change = TRUE;
+ }
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (transformation->vao);
+
+ if (transformation->caps_change)
+ _upload_vertices (transformation);
+ _bind_buffer (transformation);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+ _unbind_buffer (transformation);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
+ transformation->caps_change = FALSE;
+
+ return TRUE;
+}
diff --git a/ext/gl/gstgltransformation.h b/ext/gl/gstgltransformation.h
new file mode 100644
index 000000000..205f1a48f
--- /dev/null
+++ b/ext/gl/gstgltransformation.h
@@ -0,0 +1,99 @@
+/*
+ * GStreamer
+ * Copyright (C) 2014 Lubosz Sarnecki <lubosz@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_TRANSFORMATION_H_
+#define _GST_GL_TRANSFORMATION_H_
+
+#include <gst/gl/gstglfilter.h>
+#include <gst/gl/gstglfuncs.h>
+#include <graphene.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_TRANSFORMATION (gst_gl_transformation_get_type())
+#define GST_GL_TRANSFORMATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_TRANSFORMATION,GstGLTransformation))
+#define GST_IS_GL_TRANSFORMATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_TRANSFORMATION))
+#define GST_GL_TRANSFORMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_TRANSFORMATION,GstGLTransformationClass))
+#define GST_IS_GL_TRANSFORMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_TRANSFORMATION))
+#define GST_GL_TRANSFORMATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_TRANSFORMATION,GstGLTransformationClass))
+
+typedef struct _GstGLTransformation GstGLTransformation;
+typedef struct _GstGLTransformationClass GstGLTransformationClass;
+
+struct _GstGLTransformation
+{
+ GstGLFilter filter;
+
+ GstGLShader *shader;
+ GLuint vao;
+ GLuint vbo_indices;
+ GLuint vertex_buffer;
+ GLint attr_position;
+ GLint attr_texture;
+
+ GstGLMemory *in_tex;
+ GstGLMemory *out_tex;
+
+ gfloat xrotation;
+ gfloat yrotation;
+ gfloat zrotation;
+
+ gfloat xscale;
+ gfloat yscale;
+
+ gfloat xtranslation;
+ gfloat ytranslation;
+ gfloat ztranslation;
+
+ gfloat xpivot;
+ gfloat ypivot;
+ gfloat zpivot;
+
+ /* perspective */
+ gfloat fov;
+ gfloat aspect;
+ gfloat znear;
+ gfloat zfar;
+ gboolean ortho;
+
+ graphene_matrix_t model_matrix;
+ graphene_matrix_t view_matrix;
+ graphene_matrix_t projection_matrix;
+ graphene_matrix_t inv_model_matrix;
+ graphene_matrix_t inv_view_matrix;
+ graphene_matrix_t inv_projection_matrix;
+ graphene_matrix_t mvp_matrix;
+
+ graphene_vec3_t camera_position;
+
+ gboolean downstream_supports_affine_meta;
+ gboolean caps_change;
+};
+
+struct _GstGLTransformationClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_transformation_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GL_TRANSFORMATION_H_ */
diff --git a/ext/gl/gstgluploadelement.c b/ext/gl/gstgluploadelement.c
new file mode 100644
index 000000000..f9e52f681
--- /dev/null
+++ b/ext/gl/gstgluploadelement.c
@@ -0,0 +1,270 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include <gst/gl/gl.h>
+#include "gstgluploadelement.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_gl_upload_element_debug);
+#define GST_CAT_DEFAULT gst_gl_upload_element_debug
+
+#define gst_gl_upload_element_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLUploadElement, gst_gl_upload_element,
+ GST_TYPE_GL_BASE_FILTER,
+ GST_DEBUG_CATEGORY_INIT (gst_gl_upload_element_debug, "gluploadelement", 0,
+ "glupload Element"););
+
+static gboolean gst_gl_upload_element_get_unit_size (GstBaseTransform * trans,
+ GstCaps * caps, gsize * size);
+static GstCaps *_gst_gl_upload_element_transform_caps (GstBaseTransform * bt,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter);
+static gboolean _gst_gl_upload_element_set_caps (GstBaseTransform * bt,
+ GstCaps * in_caps, GstCaps * out_caps);
+static gboolean gst_gl_upload_element_filter_meta (GstBaseTransform * trans,
+ GstQuery * query, GType api, const GstStructure * params);
+static gboolean _gst_gl_upload_element_propose_allocation (GstBaseTransform *
+ bt, GstQuery * decide_query, GstQuery * query);
+static gboolean _gst_gl_upload_element_decide_allocation (GstBaseTransform *
+ trans, GstQuery * query);
+static GstFlowReturn
+gst_gl_upload_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * buffer, GstBuffer ** outbuf);
+static GstFlowReturn gst_gl_upload_element_transform (GstBaseTransform * bt,
+ GstBuffer * buffer, GstBuffer * outbuf);
+static gboolean gst_gl_upload_element_stop (GstBaseTransform * bt);
+
+static GstStaticPadTemplate gst_gl_upload_element_src_pad_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(ANY)"));
+
+static void
+gst_gl_upload_element_finalize (GObject * object)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (object);
+
+ if (upload->upload)
+ gst_object_unref (upload->upload);
+ upload->upload = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_gl_upload_element_class_init (GstGLUploadElementClass * klass)
+{
+ GstBaseTransformClass *bt_class = GST_BASE_TRANSFORM_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstCaps *upload_caps;
+
+ bt_class->transform_caps = _gst_gl_upload_element_transform_caps;
+ bt_class->set_caps = _gst_gl_upload_element_set_caps;
+ bt_class->filter_meta = gst_gl_upload_element_filter_meta;
+ bt_class->propose_allocation = _gst_gl_upload_element_propose_allocation;
+ bt_class->decide_allocation = _gst_gl_upload_element_decide_allocation;
+ bt_class->get_unit_size = gst_gl_upload_element_get_unit_size;
+ bt_class->prepare_output_buffer = gst_gl_upload_element_prepare_output_buffer;
+ bt_class->transform = gst_gl_upload_element_transform;
+ bt_class->stop = gst_gl_upload_element_stop;
+
+ bt_class->passthrough_on_same_caps = TRUE;
+
+ gst_element_class_add_static_pad_template (element_class,
+ &gst_gl_upload_element_src_pad_template);
+
+ upload_caps = gst_gl_upload_get_input_template_caps ();
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, upload_caps));
+ gst_caps_unref (upload_caps);
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL uploader", "Filter/Video",
+ "Uploads data into OpenGL", "Matthew Waters <matthew@centricular.com>");
+
+ gobject_class->finalize = gst_gl_upload_element_finalize;
+}
+
+static void
+gst_gl_upload_element_init (GstGLUploadElement * upload)
+{
+ gst_base_transform_set_prefer_passthrough (GST_BASE_TRANSFORM (upload), TRUE);
+}
+
+static gboolean
+gst_gl_upload_element_stop (GstBaseTransform * bt)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (bt);
+
+ if (upload->upload) {
+ gst_object_unref (upload->upload);
+ upload->upload = NULL;
+ }
+
+ gst_caps_replace (&upload->in_caps, NULL);
+ gst_caps_replace (&upload->out_caps, NULL);
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt);
+}
+
+static gboolean
+gst_gl_upload_element_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
+ gsize * size)
+{
+ gboolean ret = FALSE;
+ GstVideoInfo info;
+
+ ret = gst_video_info_from_caps (&info, caps);
+ if (ret)
+ *size = GST_VIDEO_INFO_SIZE (&info);
+
+ return TRUE;
+}
+
+static GstCaps *
+_gst_gl_upload_element_transform_caps (GstBaseTransform * bt,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (bt);
+ GstGLContext *context = GST_GL_BASE_FILTER (bt)->context;
+
+ if (upload->upload == NULL)
+ upload->upload = gst_gl_upload_new (NULL);
+
+ return gst_gl_upload_transform_caps (upload->upload, context, direction, caps,
+ filter);
+}
+
+static gboolean
+gst_gl_upload_element_filter_meta (GstBaseTransform * trans, GstQuery * query,
+ GType api, const GstStructure * params)
+{
+ /* propose all metadata upstream */
+ return TRUE;
+}
+
+static gboolean
+_gst_gl_upload_element_propose_allocation (GstBaseTransform * bt,
+ GstQuery * decide_query, GstQuery * query)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (bt);
+ gboolean ret;
+
+ if (!upload->upload)
+ return FALSE;
+
+ ret = GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (bt,
+ decide_query, query);
+ gst_gl_upload_propose_allocation (upload->upload, decide_query, query);
+
+ return ret;
+}
+
+static gboolean
+_gst_gl_upload_element_decide_allocation (GstBaseTransform * trans,
+ GstQuery * query)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (trans);
+ GstGLContext *context;
+ gboolean ret;
+
+ ret =
+ GST_BASE_TRANSFORM_CLASS
+ (gst_gl_upload_element_parent_class)->decide_allocation (trans, query);
+ if (!ret)
+ return FALSE;
+
+ /* GstGLBaseFilter populates ->context in ::decide_allocation so now it's the
+ * time to set the ->upload context */
+ context = GST_GL_BASE_FILTER (trans)->context;
+ gst_gl_upload_set_context (upload->upload, context);
+
+ return gst_gl_upload_set_caps (upload->upload, upload->in_caps,
+ upload->out_caps);
+}
+
+static gboolean
+_gst_gl_upload_element_set_caps (GstBaseTransform * bt, GstCaps * in_caps,
+ GstCaps * out_caps)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (bt);
+
+ gst_caps_replace (&upload->in_caps, in_caps);
+ gst_caps_replace (&upload->out_caps, out_caps);
+
+ if (upload->upload)
+ return gst_gl_upload_set_caps (upload->upload, in_caps, out_caps);
+
+ return TRUE;
+}
+
+GstFlowReturn
+gst_gl_upload_element_prepare_output_buffer (GstBaseTransform * bt,
+ GstBuffer * buffer, GstBuffer ** outbuf)
+{
+ GstGLUploadElement *upload = GST_GL_UPLOAD_ELEMENT (bt);
+ GstGLUploadReturn ret;
+ GstBaseTransformClass *bclass;
+
+ bclass = GST_BASE_TRANSFORM_GET_CLASS (bt);
+
+ if (gst_base_transform_is_passthrough (bt)) {
+ *outbuf = buffer;
+ return GST_FLOW_OK;
+ }
+
+ if (!upload->upload)
+ return GST_FLOW_NOT_NEGOTIATED;
+
+ ret = gst_gl_upload_perform_with_buffer (upload->upload, buffer, outbuf);
+ if (ret == GST_GL_UPLOAD_RECONFIGURE) {
+ gst_base_transform_reconfigure_src (bt);
+ return GST_FLOW_OK;
+ }
+
+ if (ret != GST_GL_UPLOAD_DONE || *outbuf == NULL) {
+ GST_ELEMENT_ERROR (bt, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to upload buffer"), (NULL));
+ if (*outbuf)
+ gst_buffer_unref (*outbuf);
+ return GST_FLOW_ERROR;
+ }
+
+ /* basetransform doesn't unref if they're the same */
+ if (buffer == *outbuf)
+ gst_buffer_unref (*outbuf);
+ else
+ bclass->copy_metadata (bt, buffer, *outbuf);
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_gl_upload_element_transform (GstBaseTransform * bt, GstBuffer * buffer,
+ GstBuffer * outbuf)
+{
+ return GST_FLOW_OK;
+}
diff --git a/ext/gl/gstgluploadelement.h b/ext/gl/gstgluploadelement.h
new file mode 100644
index 000000000..615773cce
--- /dev/null
+++ b/ext/gl/gstgluploadelement.h
@@ -0,0 +1,69 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GL_UPLOAD_ELEMENT_H__
+#define __GST_GL_UPLOAD_ELEMENT_H__
+
+#include <gst/video/video.h>
+
+#include <gst/gl/gstgl_fwd.h>
+
+G_BEGIN_DECLS
+
+GType gst_gl_upload_element_get_type (void);
+#define GST_TYPE_GL_UPLOAD_ELEMENT (gst_gl_upload_element_get_type())
+#define GST_GL_UPLOAD_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_UPLOAD_ELEMENT,GstGLUploadElement))
+#define GST_GL_UPLOAD_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_UPLOAD_ELEMENT,GstGLUploadElementClass))
+#define GST_IS_GL_UPLOAD_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_UPLOAD_ELEMENT))
+#define GST_IS_GL_UPLOAD_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_UPLOAD_ELEMENT))
+#define GST_GL_UPLOAD_ELEMENT_CAST(obj) ((GstGLUploadElement*)(obj))
+
+typedef struct _GstGLUploadElement GstGLUploadElement;
+typedef struct _GstGLUploadElementClass GstGLUploadElementClass;
+typedef struct _GstGLUploadElementPrivate GstGLUploadElementPrivate;
+
+/**
+ * GstGLUploadElement
+ *
+ * Opaque #GstGLUploadElement object
+ */
+struct _GstGLUploadElement
+{
+ /* <private> */
+ GstGLBaseFilter parent;
+
+ GstGLUpload *upload;
+ GstCaps *in_caps;
+ GstCaps *out_caps;
+};
+
+/**
+ * GstGLUploadElementClass:
+ *
+ * The #GstGLUploadElementClass struct only contains private data
+ */
+struct _GstGLUploadElementClass
+{
+ GstGLBaseFilterClass object_class;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GL_UPLOAD_ELEMENT_H__ */
diff --git a/ext/gl/gstglutils.c b/ext/gl/gstglutils.c
new file mode 100644
index 000000000..3617ef93f
--- /dev/null
+++ b/ext/gl/gstglutils.c
@@ -0,0 +1,178 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gl/gstglfuncs.h>
+
+#include "gstglutils.h"
+
+struct _compile_shader
+{
+ GstGLShader **shader;
+ const gchar *vertex_src;
+ const gchar *fragment_src;
+};
+
+static void
+_compile_shader (GstGLContext * context, struct _compile_shader *data)
+{
+ GstGLShader *shader;
+ GstGLSLStage *vert, *frag;
+ GError *error = NULL;
+
+ shader = gst_gl_shader_new (context);
+
+ if (data->vertex_src) {
+ vert = gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, data->vertex_src);
+ if (!gst_glsl_stage_compile (vert, &error)) {
+ GST_ERROR_OBJECT (vert, "%s", error->message);
+ gst_object_unref (vert);
+ gst_object_unref (shader);
+ return;
+ }
+ if (!gst_gl_shader_attach (shader, vert)) {
+ gst_object_unref (shader);
+ return;
+ }
+ }
+
+ if (data->fragment_src) {
+ frag = gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER,
+ GST_GLSL_VERSION_NONE,
+ GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+ data->fragment_src);
+ if (!gst_glsl_stage_compile (frag, &error)) {
+ GST_ERROR_OBJECT (frag, "%s", error->message);
+ gst_object_unref (frag);
+ gst_object_unref (shader);
+ return;
+ }
+ if (!gst_gl_shader_attach (shader, frag)) {
+ gst_object_unref (shader);
+ return;
+ }
+ }
+
+ if (!gst_gl_shader_link (shader, &error)) {
+ GST_ERROR_OBJECT (shader, "%s", error->message);
+ g_error_free (error);
+ error = NULL;
+ gst_gl_context_clear_shader (context);
+ gst_object_unref (shader);
+ return;
+ }
+
+ *data->shader = shader;
+}
+
+/* Called by glfilter */
+gboolean
+gst_gl_context_gen_shader (GstGLContext * context, const gchar * vert_src,
+ const gchar * frag_src, GstGLShader ** shader)
+{
+ struct _compile_shader data;
+
+ g_return_val_if_fail (frag_src != NULL || vert_src != NULL, FALSE);
+ g_return_val_if_fail (shader != NULL, FALSE);
+
+ data.shader = shader;
+ data.vertex_src = vert_src;
+ data.fragment_src = frag_src;
+
+ gst_gl_context_thread_add (context, (GstGLContextThreadFunc) _compile_shader,
+ &data);
+
+ return *shader != NULL;
+}
+
+static const gfloat identity_matrix[] = {
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0,
+};
+
+static const gfloat from_ndc_matrix[] = {
+ 0.5, 0.0, 0.0, 0.0,
+ 0.0, 0.5, 0.0, 0.0,
+ 0.0, 0.0, 0.5, 0.0,
+ 0.5, 0.5, 0.5, 1.0,
+};
+
+static const gfloat to_ndc_matrix[] = {
+ 2.0, 0.0, 0.0, 0.0,
+ 0.0, 2.0, 0.0, 0.0,
+ 0.0, 0.0, 2.0, 0.0,
+ -1.0, -1.0, -1.0, 1.0,
+};
+
+void
+gst_gl_multiply_matrix4 (const gfloat * a, const gfloat * b, gfloat * result)
+{
+ int i, j, k;
+ gfloat tmp[16] = { 0.0f };
+
+ if (!a || !b || !result)
+ return;
+
+ for (i = 0; i < 4; i++) { /* column */
+ for (j = 0; j < 4; j++) { /* row */
+ for (k = 0; k < 4; k++) {
+ tmp[j + (i * 4)] += a[k + (i * 4)] * b[j + (k * 4)];
+ }
+ }
+ }
+
+ for (i = 0; i < 16; i++)
+ result[i] = tmp[i];
+}
+
+void gst_gl_get_affine_transformation_meta_as_ndc_ext
+ (GstVideoAffineTransformationMeta * meta, gfloat * matrix)
+{
+ if (!meta) {
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ matrix[i] = identity_matrix[i];
+ }
+ } else {
+ float tmp[16];
+
+ gst_gl_multiply_matrix4 (to_ndc_matrix, meta->matrix, tmp);
+ gst_gl_multiply_matrix4 (tmp, from_ndc_matrix, matrix);
+ }
+}
+
+void gst_gl_set_affine_transformation_meta_from_ndc_ext
+ (GstVideoAffineTransformationMeta * meta, const gfloat * matrix)
+{
+ float tmp[16];
+
+ g_return_if_fail (meta != NULL);
+
+ gst_gl_multiply_matrix4 (from_ndc_matrix, matrix, tmp);
+ gst_gl_multiply_matrix4 (tmp, to_ndc_matrix, meta->matrix);
+}
diff --git a/ext/gl/gstglutils.h b/ext/gl/gstglutils.h
new file mode 100644
index 000000000..bf567f603
--- /dev/null
+++ b/ext/gl/gstglutils.h
@@ -0,0 +1,38 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __EXT_GL_GST_GL_UTILS_H__
+#define __EXT_GL_GST_GL_UTILS_H__
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+gboolean gst_gl_context_gen_shader (GstGLContext * context,
+ const gchar * shader_vertex_source,
+ const gchar * shader_fragment_source, GstGLShader ** shader);
+void gst_gl_multiply_matrix4 (const gfloat * a, const gfloat * b, gfloat * result);
+void gst_gl_get_affine_transformation_meta_as_ndc_ext (GstVideoAffineTransformationMeta *
+ meta, gfloat * matrix);
+void gst_gl_set_affine_transformation_meta_from_ndc_ext (GstVideoAffineTransformationMeta * meta, const gfloat * matrix);
+
+G_END_DECLS
+
+#endif /* __EXT_GL_GST_GL_UTILS_H__ */
diff --git a/ext/gl/gstglvideoflip.c b/ext/gl/gstglvideoflip.c
new file mode 100644
index 000000000..d5bb2ac57
--- /dev/null
+++ b/ext/gl/gstglvideoflip.c
@@ -0,0 +1,529 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-glvideo_flip
+ * @title: glvideo_flip
+ *
+ * Transforms video on the GPU.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! glvideoflip method=clockwise ! glimagesinkelement
+ * ]| This pipeline flips the test image 90 degrees clockwise.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglvideoflip.h"
+
+#define GST_CAT_DEFAULT gst_gl_video_flip_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEFAULT_METHOD GST_GL_VIDEO_FLIP_METHOD_IDENTITY
+
+enum
+{
+ PROP_0,
+ PROP_METHOD,
+ PROP_VIDEO_DIRECTION
+};
+
+static GstStaticPadTemplate _sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ", "
+ "texture-target = (string) 2D"));
+
+static GstStaticPadTemplate _src_template = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
+ "format = (string) RGBA, "
+ "width = " GST_VIDEO_SIZE_RANGE ", "
+ "height = " GST_VIDEO_SIZE_RANGE ", "
+ "framerate = " GST_VIDEO_FPS_RANGE ", "
+ "texture-target = (string) 2D"));
+
+#define GST_TYPE_GL_VIDEO_FLIP_METHOD (gst_video_flip_method_get_type())
+static const GEnumValue video_flip_methods[] = {
+ {GST_GL_VIDEO_FLIP_METHOD_IDENTITY, "Identity (no rotation)", "none"},
+ {GST_GL_VIDEO_FLIP_METHOD_90R, "Rotate clockwise 90 degrees", "clockwise"},
+ {GST_GL_VIDEO_FLIP_METHOD_180, "Rotate 180 degrees", "rotate-180"},
+ {GST_GL_VIDEO_FLIP_METHOD_90L, "Rotate counter-clockwise 90 degrees",
+ "counterclockwise"},
+ {GST_GL_VIDEO_FLIP_METHOD_FLIP_HORIZ, "Flip horizontally", "horizontal-flip"},
+ {GST_GL_VIDEO_FLIP_METHOD_FLIP_VERT, "Flip vertically", "vertical-flip"},
+ {GST_GL_VIDEO_FLIP_METHOD_FLIP_UL_LR,
+ "Flip across upper left/lower right diagonal", "upper-left-diagonal"},
+ {GST_GL_VIDEO_FLIP_METHOD_FLIP_UR_LL,
+ "Flip across upper right/lower left diagonal", "upper-right-diagonal"},
+ {GST_GL_VIDEO_FLIP_METHOD_AUTO,
+ "Select flip method based on image-orientation tag", "automatic"},
+ {0, NULL, NULL},
+};
+
+static GType
+gst_video_flip_method_get_type (void)
+{
+ static GType video_flip_method_type = 0;
+
+ if (!video_flip_method_type) {
+ video_flip_method_type = g_enum_register_static ("GstGLVideoFlipMethod",
+ video_flip_methods);
+ }
+ return video_flip_method_type;
+}
+
+static void gst_gl_video_flip_finalize (GObject * object);
+static void gst_gl_video_flip_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_video_flip_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstPadProbeReturn _input_sink_probe (GstPad * pad,
+ GstPadProbeInfo * info, gpointer user_data);
+static GstPadProbeReturn _trans_src_probe (GstPad * pad, GstPadProbeInfo * info,
+ gpointer user_data);
+
+static void
+gst_gl_video_flip_video_direction_interface_init (GstVideoDirectionInterface
+ * iface);
+
+#define gst_gl_video_flip_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLVideoFlip, gst_gl_video_flip,
+ GST_TYPE_BIN, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT,
+ "glvideoflip", 0, "glvideoflip element");
+ G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_DIRECTION,
+ gst_gl_video_flip_video_direction_interface_init););
+
+static void
+gst_gl_video_flip_video_direction_interface_init (GstVideoDirectionInterface
+ * iface)
+{
+ /* We implement the video-direction property */
+}
+
+static void
+gst_gl_video_flip_class_init (GstGLVideoFlipClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->finalize = gst_gl_video_flip_finalize;
+ gobject_class->set_property = gst_gl_video_flip_set_property;
+ gobject_class->get_property = gst_gl_video_flip_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_METHOD,
+ g_param_spec_enum ("method", "method",
+ "method (deprecated, use video-direction instead)",
+ GST_TYPE_GL_VIDEO_FLIP_METHOD, DEFAULT_METHOD,
+ GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+ g_object_class_override_property (gobject_class, PROP_VIDEO_DIRECTION,
+ "video-direction");
+
+ gst_element_class_add_static_pad_template (element_class, &_src_template);
+ gst_element_class_add_static_pad_template (element_class, &_sink_template);
+
+ gst_element_class_set_metadata (element_class, "OpenGL video flip filter",
+ "Filter/Effect/Video", "Flip video on the GPU",
+ "Matthew Waters <matthew@centricular.com>");
+}
+
+static void
+gst_gl_video_flip_init (GstGLVideoFlip * flip)
+{
+ gboolean res = TRUE;
+ GstPad *pad;
+
+ flip->aspect = 1.0;
+
+ flip->input_capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ res &= gst_bin_add (GST_BIN (flip), flip->input_capsfilter);
+
+ flip->transformation = gst_element_factory_make ("gltransformation", NULL);
+ g_object_set (flip->transformation, "ortho", TRUE, NULL);
+ res &= gst_bin_add (GST_BIN (flip), flip->transformation);
+
+ flip->output_capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ res &= gst_bin_add (GST_BIN (flip), flip->output_capsfilter);
+
+ res &=
+ gst_element_link_pads (flip->input_capsfilter, "src",
+ flip->transformation, "sink");
+ res &=
+ gst_element_link_pads (flip->transformation, "src",
+ flip->output_capsfilter, "sink");
+
+ pad = gst_element_get_static_pad (flip->input_capsfilter, "sink");
+ if (!pad) {
+ res = FALSE;
+ } else {
+ GST_DEBUG_OBJECT (flip, "setting target sink pad %" GST_PTR_FORMAT, pad);
+ flip->sinkpad = gst_ghost_pad_new ("sink", pad);
+ flip->sink_probe = gst_pad_add_probe (flip->sinkpad,
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM |
+ GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ (GstPadProbeCallback) _input_sink_probe, flip, NULL);
+ gst_element_add_pad (GST_ELEMENT_CAST (flip), flip->sinkpad);
+ gst_object_unref (pad);
+ }
+
+ pad = gst_element_get_static_pad (flip->transformation, "src");
+ flip->src_probe = gst_pad_add_probe (pad,
+ GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ (GstPadProbeCallback) _trans_src_probe, flip, NULL);
+ gst_object_unref (pad);
+
+ pad = gst_element_get_static_pad (flip->output_capsfilter, "src");
+ if (!pad) {
+ res = FALSE;
+ } else {
+ GST_DEBUG_OBJECT (flip, "setting target sink pad %" GST_PTR_FORMAT, pad);
+ flip->srcpad = gst_ghost_pad_new ("src", pad);
+ gst_element_add_pad (GST_ELEMENT_CAST (flip), flip->srcpad);
+ gst_object_unref (pad);
+ }
+
+ if (!res) {
+ GST_WARNING_OBJECT (flip, "Failed to add/connect the necessary machinery");
+ }
+}
+
+static void
+gst_gl_video_flip_finalize (GObject * object)
+{
+ GstGLVideoFlip *flip = GST_GL_VIDEO_FLIP (object);
+
+ gst_caps_replace (&flip->input_caps, NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* Caps negotiation happens like this:
+ *
+ * 1. caps/accept-caps queries bypass the capsfilters on either side of the
+ * transformation element so the fixed caps don't get in the way.
+ * 2. Receiving a caps event on the sink pad will set fixed caps on either side
+ * of the transformation element.
+ */
+static GstCaps *
+_transform_caps (GstGLVideoFlip * vf, GstPadDirection direction, GstCaps * caps)
+{
+ GstCaps *output = gst_caps_copy (caps);
+ gint i;
+
+ for (i = 0; i < gst_caps_get_size (output); i++) {
+ GstStructure *structure = gst_caps_get_structure (output, i);
+ gint width, height;
+ gint par_n, par_d;
+
+ if (gst_structure_get_int (structure, "width", &width) &&
+ gst_structure_get_int (structure, "height", &height)) {
+
+ switch (vf->active_method) {
+ case GST_VIDEO_ORIENTATION_90R:
+ case GST_VIDEO_ORIENTATION_90L:
+ case GST_VIDEO_ORIENTATION_UL_LR:
+ case GST_VIDEO_ORIENTATION_UR_LL:
+ gst_structure_set (structure, "width", G_TYPE_INT, height,
+ "height", G_TYPE_INT, width, NULL);
+ if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
+ &par_n, &par_d)) {
+ if (par_n != 1 || par_d != 1) {
+ GValue val = { 0, };
+
+ g_value_init (&val, GST_TYPE_FRACTION);
+ gst_value_set_fraction (&val, par_d, par_n);
+ gst_structure_set_value (structure, "pixel-aspect-ratio", &val);
+ g_value_unset (&val);
+ }
+ }
+ break;
+ case GST_VIDEO_ORIENTATION_IDENTITY:
+ case GST_VIDEO_ORIENTATION_180:
+ case GST_VIDEO_ORIENTATION_HORIZ:
+ case GST_VIDEO_ORIENTATION_VERT:
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ }
+
+ return output;
+}
+
+/* with object lock */
+static void
+_set_active_method (GstGLVideoFlip * vf, GstVideoOrientationMethod method,
+ GstCaps * caps)
+{
+ gfloat rot_z = 0., scale_x = 1.0, scale_y = 1.0;
+ GstCaps *output_caps, *templ;
+ GstPad *srcpad;
+
+ switch (method) {
+ case GST_VIDEO_ORIENTATION_IDENTITY:
+ break;
+ case GST_VIDEO_ORIENTATION_90R:
+ scale_x *= vf->aspect;
+ scale_y *= 1. / vf->aspect;
+ rot_z = 90.;
+ break;
+ case GST_VIDEO_ORIENTATION_180:
+ rot_z = 180.;
+ break;
+ case GST_VIDEO_ORIENTATION_90L:
+ scale_x *= vf->aspect;
+ scale_y *= 1. / vf->aspect;
+ rot_z = 270.;
+ break;
+ case GST_VIDEO_ORIENTATION_HORIZ:
+ scale_x *= -1.;
+ break;
+ case GST_VIDEO_ORIENTATION_UR_LL:
+ scale_x *= -vf->aspect;
+ scale_y *= 1. / vf->aspect;
+ rot_z = 90.;
+ break;
+ case GST_VIDEO_ORIENTATION_VERT:
+ scale_x *= -1.;
+ rot_z = 180.;
+ break;
+ case GST_VIDEO_ORIENTATION_UL_LR:
+ scale_x *= -vf->aspect;
+ scale_y *= 1. / vf->aspect;
+ rot_z = 270.;
+ break;
+ default:
+ break;
+ }
+ vf->active_method = method;
+
+ output_caps = _transform_caps (vf, GST_PAD_SINK, caps);
+ gst_caps_replace (&vf->input_caps, caps);
+
+ srcpad = gst_element_get_static_pad (vf->transformation, "src");
+ templ = gst_pad_get_pad_template_caps (srcpad);
+ gst_object_unref (srcpad);
+
+ gst_caps_append (output_caps, gst_caps_ref (templ));
+ GST_OBJECT_UNLOCK (vf);
+
+ g_object_set (vf->input_capsfilter, "caps", gst_caps_ref (caps), NULL);
+ g_object_set (vf->output_capsfilter, "caps", output_caps, NULL);
+ g_object_set (vf->transformation, "rotation-z", rot_z, "scale-x", scale_x,
+ "scale-y", scale_y, NULL);
+ GST_OBJECT_LOCK (vf);
+}
+
+static void
+gst_gl_video_flip_set_method (GstGLVideoFlip * vf,
+ GstVideoOrientationMethod method, gboolean from_tag)
+{
+ GST_OBJECT_LOCK (vf);
+
+ if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
+ GST_WARNING_OBJECT (vf, "unsupported custom orientation");
+ GST_OBJECT_UNLOCK (vf);
+ return;
+ }
+
+ /* Store updated method */
+ if (from_tag)
+ vf->tag_method = method;
+ else
+ vf->method = method;
+
+ /* Get the new method */
+ if (vf->method == GST_VIDEO_ORIENTATION_AUTO)
+ method = vf->tag_method;
+ else
+ method = vf->method;
+
+ if (vf->input_caps)
+ _set_active_method (vf, method, vf->input_caps);
+ else {
+ /* just store the configured method here. The actual transform configuration
+ * will be done once caps are configured. See caps handling in
+ * _input_sink_probe. */
+ vf->active_method = method;
+ }
+
+ GST_OBJECT_UNLOCK (vf);
+}
+
+static void
+gst_gl_video_flip_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoFlip *vf = GST_GL_VIDEO_FLIP (object);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ case PROP_VIDEO_DIRECTION:
+ gst_gl_video_flip_set_method (vf, g_value_get_enum (value), FALSE);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_video_flip_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoFlip *vf = GST_GL_VIDEO_FLIP (object);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ case PROP_VIDEO_DIRECTION:
+ g_value_set_enum (value, vf->method);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstPadProbeReturn
+_input_sink_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstGLVideoFlip *vf = GST_GL_VIDEO_FLIP (user_data);
+
+ if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_TAG:{
+ GstTagList *taglist;
+ gchar *orientation;
+
+ gst_event_parse_tag (event, &taglist);
+
+ if (gst_tag_list_get_string (taglist, "image-orientation",
+ &orientation)) {
+ if (!g_strcmp0 ("rotate-0", orientation))
+ gst_gl_video_flip_set_method (vf, GST_VIDEO_ORIENTATION_IDENTITY,
+ TRUE);
+ else if (!g_strcmp0 ("rotate-90", orientation))
+ gst_gl_video_flip_set_method (vf, GST_VIDEO_ORIENTATION_90R, TRUE);
+ else if (!g_strcmp0 ("rotate-180", orientation))
+ gst_gl_video_flip_set_method (vf, GST_VIDEO_ORIENTATION_180, TRUE);
+ else if (!g_strcmp0 ("rotate-270", orientation))
+ gst_gl_video_flip_set_method (vf, GST_VIDEO_ORIENTATION_90L, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-0", orientation))
+ gst_gl_video_flip_set_method (vf,
+ GST_VIDEO_ORIENTATION_HORIZ, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-90", orientation))
+ gst_gl_video_flip_set_method (vf,
+ GST_VIDEO_ORIENTATION_UR_LL, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-180", orientation))
+ gst_gl_video_flip_set_method (vf, GST_VIDEO_ORIENTATION_VERT, TRUE);
+ else if (!g_strcmp0 ("flip-rotate-270", orientation))
+ gst_gl_video_flip_set_method (vf,
+ GST_VIDEO_ORIENTATION_UL_LR, TRUE);
+
+ g_free (orientation);
+ }
+ break;
+ }
+ case GST_EVENT_CAPS:{
+ GstCaps *caps;
+ GstVideoInfo v_info;
+
+ gst_event_parse_caps (event, &caps);
+ GST_OBJECT_LOCK (vf);
+ if (gst_video_info_from_caps (&v_info, caps))
+ vf->aspect =
+ (gfloat) GST_VIDEO_INFO_WIDTH (&v_info) /
+ (gfloat) GST_VIDEO_INFO_HEIGHT (&v_info);
+ else
+ vf->aspect = 1.0;
+ _set_active_method (vf, vf->active_method, caps);
+ GST_OBJECT_UNLOCK (vf);
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (GST_PAD_PROBE_INFO_TYPE (info) &
+ GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM) {
+ GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);
+
+ switch (GST_QUERY_TYPE (query)) {
+ /* bypass the capsfilter */
+ case GST_QUERY_CAPS:
+ case GST_QUERY_ACCEPT_CAPS:{
+ GstPad *pad = gst_element_get_static_pad (vf->transformation, "sink");
+ if (gst_pad_query (pad, query)) {
+ gst_object_unref (pad);
+ return GST_PAD_PROBE_HANDLED;
+ } else {
+ gst_object_unref (pad);
+ return GST_PAD_PROBE_DROP;
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+_trans_src_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstGLVideoFlip *vf = GST_GL_VIDEO_FLIP (user_data);
+
+ if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM) {
+ GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);
+
+ switch (GST_QUERY_TYPE (query)) {
+ /* bypass the capsfilter */
+ case GST_QUERY_CAPS:
+ case GST_QUERY_ACCEPT_CAPS:{
+ if (gst_pad_peer_query (vf->srcpad, query))
+ return GST_PAD_PROBE_HANDLED;
+ else
+ return GST_PAD_PROBE_DROP;
+ }
+ default:
+ break;
+ }
+ }
+
+ return GST_PAD_PROBE_OK;
+}
diff --git a/ext/gl/gstglvideoflip.h b/ext/gl/gstglvideoflip.h
new file mode 100644
index 000000000..36f8da07d
--- /dev/null
+++ b/ext/gl/gstglvideoflip.h
@@ -0,0 +1,97 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_VIDEO_FLIP_H_
+#define _GST_GL_VIDEO_FLIP_H_
+
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_VIDEO_FLIP (gst_gl_video_flip_get_type())
+#define GST_GL_VIDEO_FLIP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_VIDEO_FLIP,GstGLVideoFlip))
+#define GST_IS_GL_VIDEO_FLIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_VIDEO_FLIP))
+#define GST_GL_VIDEO_FLIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_VIDEO_FLIP,GstGLVideoFlipClass))
+#define GST_IS_GL_VIDEO_FLIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_VIDEO_FLIP))
+#define GST_GL_VIDEO_FLIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_VIDEO_FLIP,GstGLVideoFlipClass))
+
+/**
+ * GstVideoFlipMethod:
+ * @GST_GL_VIDEO_FLIP_METHOD_IDENTITY: Identity (no rotation)
+ * @GST_GL_VIDEO_FLIP_METHOD_90R: Rotate clockwise 90 degrees
+ * @GST_GL_VIDEO_FLIP_METHOD_180: Rotate 180 degrees
+ * @GST_GL_VIDEO_FLIP_METHOD_90L: Rotate counter-clockwise 90 degrees
+ * @GST_GL_VIDEO_FLIP_METHOD_FLIP_HORIZ: Flip horizontally
+ * @GST_GL_VIDEO_FLIP_METHOD_FLIP_VERT: Flip vertically
+ * @GST_GL_VIDEO_FLIP_METHOD_FLIP_UL_LR: Flip across upper left/lower right diagonal
+ * @GST_GL_VIDEO_FLIP_METHOD_FLIP_UR_LL: Flip across upper right/lower left diagonal
+ * @GST_GL_VIDEO_FLIP_METHOD_AUTO: Select flip method based on image-orientation tag
+ *
+ * The different flip methods.
+ */
+typedef enum {
+ GST_GL_VIDEO_FLIP_METHOD_IDENTITY,
+ GST_GL_VIDEO_FLIP_METHOD_90R,
+ GST_GL_VIDEO_FLIP_METHOD_180,
+ GST_GL_VIDEO_FLIP_METHOD_90L,
+ GST_GL_VIDEO_FLIP_METHOD_FLIP_HORIZ,
+ GST_GL_VIDEO_FLIP_METHOD_FLIP_VERT,
+ GST_GL_VIDEO_FLIP_METHOD_FLIP_UL_LR,
+ GST_GL_VIDEO_FLIP_METHOD_FLIP_UR_LL,
+ GST_GL_VIDEO_FLIP_METHOD_AUTO,
+} GstGLVideoFlipMethod;
+
+typedef struct _GstGLVideoFlip GstGLVideoFlip;
+typedef struct _GstGLVideoFlipClass GstGLVideoFlipClass;
+
+struct _GstGLVideoFlip
+{
+ GstBin bin;
+
+ GstPad *srcpad;
+ GstPad *sinkpad;
+
+ GstElement *input_capsfilter;
+ GstElement *transformation;
+ GstElement *output_capsfilter;
+
+ gulong sink_probe;
+ gulong src_probe;
+
+ GstCaps *input_caps;
+
+ /* properties */
+ GstVideoOrientationMethod method;
+ GstVideoOrientationMethod tag_method;
+ GstVideoOrientationMethod active_method;
+
+ gfloat aspect;
+};
+
+struct _GstGLVideoFlipClass
+{
+ GstBinClass filter_class;
+};
+
+GType gst_gl_video_flip_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GL_VIDEO_FLIP_H_ */
diff --git a/ext/gl/gstglvideomixer.c b/ext/gl/gstglvideomixer.c
new file mode 100644
index 000000000..487bf2fe1
--- /dev/null
+++ b/ext/gl/gstglvideomixer.c
@@ -0,0 +1,1586 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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:element-glvideomixer
+ * @title: glvideomixer
+ *
+ * Composites a number of streams into a single output scene using OpenGL in
+ * a similar fashion to compositor and videomixer. See the compositor plugin
+ * for documentation about the #GstGLVideoMixerPad properties.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 glvideomixer name=m ! glimagesink \
+ * videotestsrc ! video/x-raw, format=YUY2 ! glupload ! glcolorconvert ! m. \
+ * videotestsrc pattern=12 ! video/x-raw, format=I420, framerate=5/1, width=100, height=200 ! queue ! \
+ * glupload ! glcolorconvert ! m. \
+ * videotestsrc ! glupload ! gleffects effect=2 ! queue ! m. \
+ * videotestsrc ! glupload ! glfiltercube ! queue ! m. \
+ * videotestsrc ! glupload ! gleffects effect=6 ! queue ! m.
+ * ]|
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/controller/gstproxycontrolbinding.h>
+#include <gst/gl/gstglfuncs.h>
+#include <gst/video/gstvideoaffinetransformationmeta.h>
+
+#include "gstglvideomixer.h"
+
+#include "gstglmixerbin.h"
+#include "gstglutils.h"
+
+#define GST_CAT_DEFAULT gst_gl_video_mixer_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA"))
+ );
+
+#define GST_TYPE_GL_VIDEO_MIXER_BACKGROUND (gst_gl_video_mixer_background_get_type())
+static GType
+gst_gl_video_mixer_background_get_type (void)
+{
+ static GType mixer_background_type = 0;
+
+ static const GEnumValue mixer_background[] = {
+ {GST_GL_VIDEO_MIXER_BACKGROUND_CHECKER, "Checker pattern", "checker"},
+ {GST_GL_VIDEO_MIXER_BACKGROUND_BLACK, "Black", "black"},
+ {GST_GL_VIDEO_MIXER_BACKGROUND_WHITE, "White", "white"},
+ {GST_GL_VIDEO_MIXER_BACKGROUND_TRANSPARENT,
+ "Transparent Background to enable further compositing", "transparent"},
+ {0, NULL, NULL},
+ };
+
+ if (!mixer_background_type) {
+ mixer_background_type =
+ g_enum_register_static ("GstGLVideoMixerBackground", mixer_background);
+ }
+ return mixer_background_type;
+}
+
+#define GST_TYPE_GL_VIDEO_MIXER_BLEND_EQUATION (gst_gl_video_mixer_blend_equation_get_type())
+static GType
+gst_gl_video_mixer_blend_equation_get_type (void)
+{
+ static GType mixer_blend_equation_type = 0;
+
+ static const GEnumValue mixer_blend_equations[] = {
+ {GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD, "Add", "add"},
+ {GST_GL_VIDEO_MIXER_BLEND_EQUATION_SUBTRACT, "Subtract", "subtract"},
+ {GST_GL_VIDEO_MIXER_BLEND_EQUATION_REVERSE_SUBTRACT, "Reverse Subtract",
+ "reverse-subtract"},
+ {0, NULL, NULL},
+ };
+
+ if (!mixer_blend_equation_type) {
+ mixer_blend_equation_type =
+ g_enum_register_static ("GstGLVideoMixerBlendEquation",
+ mixer_blend_equations);
+ }
+ return mixer_blend_equation_type;
+}
+
+#define GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION (gst_gl_video_mixer_blend_function_get_type())
+static GType
+gst_gl_video_mixer_blend_function_get_type (void)
+{
+ static GType mixer_blend_function_type = 0;
+
+ static const GEnumValue mixer_blend_funcs[] = {
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ZERO, "Zero", "zero"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE, "One", "one"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_COLOR, "Source Color", "src-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_COLOR,
+ "One Minus Source Color", "one-minus-src-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_COLOR, "Destination Color",
+ "dst-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_COLOR,
+ "One Minus Destination Color", "one-minus-dst-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA, "Source Alpha", "src-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA,
+ "One Minus Source Alpha", "one-minus-src-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_ALPHA, "Destination Alpha",
+ "dst-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_ALPHA,
+ "One Minus Destination Alpha", "one-minus-dst-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_COLOR, "Constant Color",
+ "constant-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR,
+ "One Minus Constant Color", "one-minus-contant-color"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_ALPHA, "Constant Alpha",
+ "constant-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR,
+ "One Minus Constant Alpha", "one-minus-contant-alpha"},
+ {GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE,
+ "Source Alpha Saturate", "src-alpha-saturate"},
+ {0, NULL, NULL},
+ };
+
+ if (!mixer_blend_function_type) {
+ mixer_blend_function_type =
+ g_enum_register_static ("GstGLVideoMixerBlendFunction",
+ mixer_blend_funcs);
+ }
+ return mixer_blend_function_type;
+}
+
+#define DEFAULT_PAD_XPOS 0
+#define DEFAULT_PAD_YPOS 0
+#define DEFAULT_PAD_WIDTH 0
+#define DEFAULT_PAD_HEIGHT 0
+#define DEFAULT_PAD_ALPHA 1.0
+#define DEFAULT_PAD_ZORDER 0
+#define DEFAULT_PAD_IGNORE_EOS FALSE
+#define DEFAULT_PAD_BLEND_EQUATION_RGB GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD
+#define DEFAULT_PAD_BLEND_EQUATION_ALPHA GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD
+#define DEFAULT_PAD_BLEND_FUNCTION_SRC_RGB GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA
+#define DEFAULT_PAD_BLEND_FUNCTION_SRC_ALPHA GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA
+#define DEFAULT_PAD_BLEND_FUNCTION_DST_RGB GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA
+#define DEFAULT_PAD_BLEND_FUNCTION_DST_ALPHA GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA
+
+enum
+{
+ PROP_INPUT_0,
+ PROP_INPUT_XPOS,
+ PROP_INPUT_YPOS,
+ PROP_INPUT_WIDTH,
+ PROP_INPUT_HEIGHT,
+ PROP_INPUT_ALPHA,
+ PROP_INPUT_BLEND_EQUATION_RGB,
+ PROP_INPUT_BLEND_EQUATION_ALPHA,
+ PROP_INPUT_BLEND_FUNCTION_SRC_RGB,
+ PROP_INPUT_BLEND_FUNCTION_SRC_ALPHA,
+ PROP_INPUT_BLEND_FUNCTION_DST_RGB,
+ PROP_INPUT_BLEND_FUNCTION_DST_ALPHA,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_RED,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_GREEN,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_BLUE,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA,
+ PROP_INPUT_ZORDER,
+ PROP_INPUT_IGNORE_EOS,
+};
+
+static void gst_gl_video_mixer_input_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_gl_video_mixer_input_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+
+typedef struct _GstGLVideoMixerInput GstGLVideoMixerInput;
+typedef GstGhostPadClass GstGLVideoMixerInputClass;
+
+struct _GstGLVideoMixerInput
+{
+ GstGhostPad parent;
+
+ GstSegment segment;
+
+ GstPad *mixer_pad;
+};
+
+GType gst_gl_video_mixer_input_get_type (void);
+G_DEFINE_TYPE (GstGLVideoMixerInput, gst_gl_video_mixer_input,
+ GST_TYPE_GHOST_PAD);
+
+static void
+gst_gl_video_mixer_input_init (GstGLVideoMixerInput * self)
+{
+}
+
+static void
+gst_gl_video_mixer_input_class_init (GstGLVideoMixerInputClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = gst_gl_video_mixer_input_set_property;
+ gobject_class->get_property = gst_gl_video_mixer_input_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_INPUT_ZORDER,
+ g_param_spec_uint ("zorder", "Z-Order", "Z Order of the picture",
+ 0, 10000, DEFAULT_PAD_ZORDER,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_IGNORE_EOS,
+ g_param_spec_boolean ("ignore-eos", "Ignore EOS", "Aggregate the last "
+ "frame on pads that are EOS till they are released",
+ DEFAULT_PAD_IGNORE_EOS,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_XPOS,
+ g_param_spec_int ("xpos", "X Position", "X Position of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_XPOS,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_YPOS,
+ g_param_spec_int ("ypos", "Y Position", "Y Position of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_YPOS,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_WIDTH,
+ g_param_spec_int ("width", "Width", "Width of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_WIDTH,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_HEIGHT,
+ g_param_spec_int ("height", "Height", "Height of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_HEIGHT,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_ALPHA,
+ g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0,
+ DEFAULT_PAD_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_BLEND_EQUATION_RGB,
+ g_param_spec_enum ("blend-equation-rgb", "Blend Equation RGB",
+ "Blend Equation for RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_EQUATION,
+ DEFAULT_PAD_BLEND_EQUATION_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_EQUATION_ALPHA,
+ g_param_spec_enum ("blend-equation-alpha", "Blend Equation Alpha",
+ "Blend Equation for Alpha", GST_TYPE_GL_VIDEO_MIXER_BLEND_EQUATION,
+ DEFAULT_PAD_BLEND_EQUATION_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_SRC_RGB,
+ g_param_spec_enum ("blend-function-src-rgb", "Blend Function Source RGB",
+ "Blend Function for Source RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_SRC_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_SRC_ALPHA,
+ g_param_spec_enum ("blend-function-src-alpha",
+ "Blend Function Source Alpha", "Blend Function for Source Alpha",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_SRC_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_DST_RGB,
+ g_param_spec_enum ("blend-function-dst-rgb",
+ "Blend Function Destination RGB",
+ "Blend Function for Destination RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_DST_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_DST_ALPHA,
+ g_param_spec_enum ("blend-function-dst-alpha",
+ "Blend Function Destination Alpha",
+ "Blend Function for Destiniation Alpha",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_DST_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_RED,
+ g_param_spec_double ("blend-constant-color-red",
+ "Blend Constant Color Red", "Blend Constant Color Red", 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_GREEN,
+ g_param_spec_double ("blend-constant-color-green",
+ "Blend Constant Color Green", "Blend Constant Color Green", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_BLUE,
+ g_param_spec_double ("blend-constant-color-blue",
+ "Blend Constant Color Green", "Blend Constant Color Green", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA,
+ g_param_spec_double ("blend-constant-color-alpha",
+ "Blend Constant Color Alpha", "Blend Constant Color Alpha", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_gl_video_mixer_input_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixerInput *self = (GstGLVideoMixerInput *) object;
+
+ if (self->mixer_pad)
+ g_object_get_property (G_OBJECT (self->mixer_pad), pspec->name, value);
+}
+
+static void
+gst_gl_video_mixer_input_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixerInput *self = (GstGLVideoMixerInput *) object;
+
+ if (self->mixer_pad)
+ g_object_set_property (G_OBJECT (self->mixer_pad), pspec->name, value);
+}
+
+static GstGhostPad *
+_create_video_mixer_input (GstGLMixerBin * self, GstPad * mixer_pad)
+{
+ GstGLVideoMixerInput *input =
+ g_object_new (gst_gl_video_mixer_input_get_type (), "name",
+ GST_OBJECT_NAME (mixer_pad), "direction", GST_PAD_DIRECTION (mixer_pad),
+ NULL);
+
+ if (!gst_ghost_pad_construct (GST_GHOST_PAD (input))) {
+ gst_object_unref (input);
+ return NULL;
+ }
+#define ADD_BINDING(obj,ref,prop) \
+ gst_object_add_control_binding (GST_OBJECT (obj), \
+ gst_proxy_control_binding_new (GST_OBJECT (obj), prop, \
+ GST_OBJECT (ref), prop));
+ ADD_BINDING (mixer_pad, input, "zorder");
+ ADD_BINDING (mixer_pad, input, "xpos");
+ ADD_BINDING (mixer_pad, input, "ypos");
+ ADD_BINDING (mixer_pad, input, "width");
+ ADD_BINDING (mixer_pad, input, "height");
+ ADD_BINDING (mixer_pad, input, "alpha");
+ ADD_BINDING (mixer_pad, input, "blend-equation-rgb");
+ ADD_BINDING (mixer_pad, input, "blend-equation-alpha");
+ ADD_BINDING (mixer_pad, input, "blend-function-src-rgb");
+ ADD_BINDING (mixer_pad, input, "blend-function-src-alpha");
+ ADD_BINDING (mixer_pad, input, "blend-function-dst-rgb");
+ ADD_BINDING (mixer_pad, input, "blend-function-dst-alpha");
+ ADD_BINDING (mixer_pad, input, "blend-constant-color-red");
+ ADD_BINDING (mixer_pad, input, "blend-constant-color-green");
+ ADD_BINDING (mixer_pad, input, "blend-constant-color-blue");
+ ADD_BINDING (mixer_pad, input, "blend-constant-color-alpha");
+#undef ADD_BINDING
+
+ input->mixer_pad = mixer_pad;
+
+ return GST_GHOST_PAD (input);
+}
+
+enum
+{
+ PROP_BIN_0,
+ PROP_BIN_BACKGROUND,
+};
+#define DEFAULT_BACKGROUND GST_GL_VIDEO_MIXER_BACKGROUND_CHECKER
+
+static void gst_gl_video_mixer_bin_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_gl_video_mixer_bin_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+
+typedef GstGLMixerBin GstGLVideoMixerBin;
+typedef GstGLMixerBinClass GstGLVideoMixerBinClass;
+
+G_DEFINE_TYPE (GstGLVideoMixerBin, gst_gl_video_mixer_bin,
+ GST_TYPE_GL_MIXER_BIN);
+
+static void
+gst_gl_video_mixer_bin_init (GstGLVideoMixerBin * self)
+{
+ GstGLMixerBin *mix_bin = GST_GL_MIXER_BIN (self);
+
+ gst_gl_mixer_bin_finish_init_with_element (mix_bin,
+ g_object_new (GST_TYPE_GL_VIDEO_MIXER, NULL));
+}
+
+static void
+gst_gl_video_mixer_bin_class_init (GstGLVideoMixerBinClass * klass)
+{
+ GstGLMixerBinClass *mixer_class = GST_GL_MIXER_BIN_CLASS (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ mixer_class->create_input_pad = _create_video_mixer_input;
+
+ gobject_class->set_property = gst_gl_video_mixer_bin_set_property;
+ gobject_class->get_property = gst_gl_video_mixer_bin_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_BIN_BACKGROUND,
+ g_param_spec_enum ("background", "Background", "Background type",
+ GST_TYPE_GL_VIDEO_MIXER_BACKGROUND,
+ DEFAULT_BACKGROUND, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_metadata (element_class, "OpenGL video_mixer bin",
+ "Bin/Filter/Effect/Video/Compositor", "OpenGL video_mixer bin",
+ "Matthew Waters <matthew@centricular.com>");
+}
+
+static void
+gst_gl_video_mixer_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
+
+ if (self->mixer)
+ g_object_get_property (G_OBJECT (self->mixer), pspec->name, value);
+}
+
+static void
+gst_gl_video_mixer_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
+
+ if (self->mixer)
+ g_object_set_property (G_OBJECT (self->mixer), pspec->name, value);
+}
+
+enum
+{
+ PROP_0,
+ PROP_BACKGROUND,
+};
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_video_mixer_debug, "glvideomixer", 0, "glvideomixer element");
+
+#define gst_gl_video_mixer_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGLVideoMixer, gst_gl_video_mixer, GST_TYPE_GL_MIXER,
+ DEBUG_INIT);
+
+static void gst_gl_video_mixer_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_video_mixer_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstCaps *_update_caps (GstVideoAggregator * vagg, GstCaps * caps);
+static GstCaps *_fixate_caps (GstAggregator * agg, GstCaps * caps);
+static gboolean gst_gl_video_mixer_propose_allocation (GstAggregator *
+ agg, GstAggregatorPad * agg_pad, GstQuery * decide_query, GstQuery * query);
+static void gst_gl_video_mixer_reset (GstGLMixer * mixer);
+static gboolean gst_gl_video_mixer_init_shader (GstGLMixer * mixer,
+ GstCaps * outcaps);
+
+static gboolean gst_gl_video_mixer_process_textures (GstGLMixer * mixer,
+ GstGLMemory * out_tex);
+static gboolean gst_gl_video_mixer_callback (gpointer stuff);
+
+/* *INDENT-OFF* */
+
+/* fragment source */
+static const gchar *video_mixer_f_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "uniform sampler2D texture; \n"
+ "uniform float alpha;\n"
+ "varying vec2 v_texcoord; \n"
+ "void main() \n"
+ "{ \n"
+ " vec4 rgba = texture2D(texture, v_texcoord);\n"
+ " gl_FragColor = vec4(rgba.rgb, rgba.a * alpha);\n"
+ "} \n";
+
+/* checker vertex source */
+static const gchar *checker_v_src =
+ "attribute vec4 a_position;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = a_position;\n"
+ "}\n";
+
+/* checker fragment source */
+static const gchar *checker_f_src =
+ "#ifdef GL_ES\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "const float blocksize = 8.0;\n"
+ "void main ()\n"
+ "{\n"
+ " vec4 high = vec4(0.667, 0.667, 0.667, 1.0);\n"
+ " vec4 low = vec4(0.333, 0.333, 0.333, 1.0);\n"
+ " if (mod(gl_FragCoord.x, blocksize * 2.0) >= blocksize) {\n"
+ " if (mod(gl_FragCoord.y, blocksize * 2.0) >= blocksize)\n"
+ " gl_FragColor = low;\n"
+ " else\n"
+ " gl_FragColor = high;\n"
+ " } else {\n"
+ " if (mod(gl_FragCoord.y, blocksize * 2.0) < blocksize)\n"
+ " gl_FragColor = low;\n"
+ " else\n"
+ " gl_FragColor = high;\n"
+ " }\n"
+ "}\n";
+/* *INDENT-ON* */
+
+#define GST_TYPE_GL_VIDEO_MIXER_PAD (gst_gl_video_mixer_pad_get_type())
+#define GST_GL_VIDEO_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_VIDEO_MIXER_PAD, GstGLVideoMixerPad))
+#define GST_GL_VIDEO_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_VIDEO_MIXER_PAD, GstGLVideoMixerPadClass))
+#define GST_IS_GL_VIDEO_MIXER_PAD(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_VIDEO_MIXER_PAD))
+#define GST_IS_GL_VIDEO_MIXER_PAD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_VIDEO_MIXER_PAD))
+
+typedef struct _GstGLVideoMixerPad GstGLVideoMixerPad;
+typedef struct _GstGLVideoMixerPadClass GstGLVideoMixerPadClass;
+typedef struct _GstGLVideoMixerCollect GstGLVideoMixerCollect;
+
+/**
+ * GstGLVideoMixerPad:
+ *
+ * The opaque #GstGLVideoMixerPad structure.
+ */
+struct _GstGLVideoMixerPad
+{
+ GstGLMixerPad parent;
+
+ /* < private > */
+ /* properties */
+ gint xpos, ypos;
+ gint width, height;
+ gdouble alpha;
+
+ GstGLVideoMixerBlendEquation blend_equation_rgb;
+ GstGLVideoMixerBlendEquation blend_equation_alpha;
+ GstGLVideoMixerBlendFunction blend_function_src_rgb;
+ GstGLVideoMixerBlendFunction blend_function_src_alpha;
+ GstGLVideoMixerBlendFunction blend_function_dst_rgb;
+ GstGLVideoMixerBlendFunction blend_function_dst_alpha;
+ gdouble blend_constant_color_red;
+ gdouble blend_constant_color_green;
+ gdouble blend_constant_color_blue;
+ gdouble blend_constant_color_alpha;
+
+ gboolean geometry_change;
+ GLuint vertex_buffer;
+};
+
+struct _GstGLVideoMixerPadClass
+{
+ GstGLMixerPadClass parent_class;
+};
+
+GType gst_gl_video_mixer_pad_get_type (void);
+G_DEFINE_TYPE (GstGLVideoMixerPad, gst_gl_video_mixer_pad,
+ GST_TYPE_GL_MIXER_PAD);
+
+static void gst_gl_video_mixer_pad_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_video_mixer_pad_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+enum
+{
+ PROP_PAD_0,
+ PROP_PAD_XPOS,
+ PROP_PAD_YPOS,
+ PROP_PAD_WIDTH,
+ PROP_PAD_HEIGHT,
+ PROP_PAD_ALPHA,
+ PROP_PAD_BLEND_EQUATION_RGB,
+ PROP_PAD_BLEND_EQUATION_ALPHA,
+ PROP_PAD_BLEND_FUNCTION_SRC_RGB,
+ PROP_PAD_BLEND_FUNCTION_SRC_ALPHA,
+ PROP_PAD_BLEND_FUNCTION_DST_RGB,
+ PROP_PAD_BLEND_FUNCTION_DST_ALPHA,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_RED,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_GREEN,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_BLUE,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA,
+};
+
+static void
+gst_gl_video_mixer_pad_init (GstGLVideoMixerPad * pad)
+{
+ pad->alpha = DEFAULT_PAD_ALPHA;
+ pad->blend_equation_rgb = DEFAULT_PAD_BLEND_EQUATION_RGB;
+ pad->blend_equation_alpha = DEFAULT_PAD_BLEND_EQUATION_ALPHA;
+ pad->blend_function_src_rgb = DEFAULT_PAD_BLEND_FUNCTION_SRC_RGB;
+ pad->blend_function_src_alpha = DEFAULT_PAD_BLEND_FUNCTION_SRC_ALPHA;
+ pad->blend_function_dst_rgb = DEFAULT_PAD_BLEND_FUNCTION_DST_RGB;
+ pad->blend_function_dst_alpha = DEFAULT_PAD_BLEND_FUNCTION_DST_ALPHA;
+}
+
+static void
+gst_gl_video_mixer_pad_class_init (GstGLVideoMixerPadClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ gobject_class->set_property = gst_gl_video_mixer_pad_set_property;
+ gobject_class->get_property = gst_gl_video_mixer_pad_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_PAD_XPOS,
+ g_param_spec_int ("xpos", "X Position", "X Position of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_XPOS,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_PAD_YPOS,
+ g_param_spec_int ("ypos", "Y Position", "Y Position of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_YPOS,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_PAD_WIDTH,
+ g_param_spec_int ("width", "Width", "Width of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_WIDTH,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_PAD_HEIGHT,
+ g_param_spec_int ("height", "Height", "Height of the picture",
+ G_MININT, G_MAXINT, DEFAULT_PAD_HEIGHT,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_PAD_ALPHA,
+ g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0,
+ DEFAULT_PAD_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_BLEND_EQUATION_RGB,
+ g_param_spec_enum ("blend-equation-rgb", "Blend Equation RGB",
+ "Blend Equation for RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_EQUATION,
+ DEFAULT_PAD_BLEND_EQUATION_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_EQUATION_ALPHA,
+ g_param_spec_enum ("blend-equation-alpha", "Blend Equation Alpha",
+ "Blend Equation for Alpha", GST_TYPE_GL_VIDEO_MIXER_BLEND_EQUATION,
+ DEFAULT_PAD_BLEND_EQUATION_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_SRC_RGB,
+ g_param_spec_enum ("blend-function-src-rgb", "Blend Function Source RGB",
+ "Blend Function for Source RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_SRC_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_SRC_ALPHA,
+ g_param_spec_enum ("blend-function-src-alpha",
+ "Blend Function Source Alpha", "Blend Function for Source Alpha",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_SRC_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_DST_RGB,
+ g_param_spec_enum ("blend-function-dst-rgb",
+ "Blend Function Destination RGB",
+ "Blend Function for Destination RGB",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_DST_RGB,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_INPUT_BLEND_FUNCTION_DST_ALPHA,
+ g_param_spec_enum ("blend-function-dst-alpha",
+ "Blend Function Destination Alpha",
+ "Blend Function for Destiniation Alpha",
+ GST_TYPE_GL_VIDEO_MIXER_BLEND_FUNCTION,
+ DEFAULT_PAD_BLEND_FUNCTION_DST_ALPHA,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_RED,
+ g_param_spec_double ("blend-constant-color-red",
+ "Blend Constant Color Red", "Blend Constant Color Red", 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_GREEN,
+ g_param_spec_double ("blend-constant-color-green",
+ "Blend Constant Color Green", "Blend Constant Color Green", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_BLUE,
+ g_param_spec_double ("blend-constant-color-blue",
+ "Blend Constant Color Green", "Blend Constant Color Green", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class,
+ PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA,
+ g_param_spec_double ("blend-constant-color-alpha",
+ "Blend Constant Color Alpha", "Blend Constant Color Alpha", 0.0, 1.0,
+ 0.0,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_gl_video_mixer_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixerPad *pad = GST_GL_VIDEO_MIXER_PAD (object);
+
+ switch (prop_id) {
+ case PROP_PAD_XPOS:
+ g_value_set_int (value, pad->xpos);
+ break;
+ case PROP_PAD_YPOS:
+ g_value_set_int (value, pad->ypos);
+ break;
+ case PROP_PAD_WIDTH:
+ g_value_set_int (value, pad->width);
+ break;
+ case PROP_PAD_HEIGHT:
+ g_value_set_int (value, pad->height);
+ break;
+ case PROP_PAD_ALPHA:
+ g_value_set_double (value, pad->alpha);
+ break;
+ case PROP_PAD_BLEND_EQUATION_RGB:
+ g_value_set_enum (value, pad->blend_equation_rgb);
+ break;
+ case PROP_PAD_BLEND_EQUATION_ALPHA:
+ g_value_set_enum (value, pad->blend_equation_alpha);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_SRC_RGB:
+ g_value_set_enum (value, pad->blend_function_src_rgb);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_SRC_ALPHA:
+ g_value_set_enum (value, pad->blend_function_src_alpha);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_DST_RGB:
+ g_value_set_enum (value, pad->blend_function_dst_rgb);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_DST_ALPHA:
+ g_value_set_enum (value, pad->blend_function_dst_alpha);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_RED:
+ g_value_set_double (value, pad->blend_constant_color_red);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_GREEN:
+ g_value_set_double (value, pad->blend_constant_color_green);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_BLUE:
+ g_value_set_double (value, pad->blend_constant_color_blue);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA:
+ g_value_set_double (value, pad->blend_constant_color_alpha);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_video_mixer_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixerPad *pad = GST_GL_VIDEO_MIXER_PAD (object);
+ GstGLMixer *mix = GST_GL_MIXER (gst_pad_get_parent (GST_PAD (pad)));
+
+ switch (prop_id) {
+ case PROP_PAD_XPOS:
+ pad->xpos = g_value_get_int (value);
+ pad->geometry_change = TRUE;
+ break;
+ case PROP_PAD_YPOS:
+ pad->ypos = g_value_get_int (value);
+ pad->geometry_change = TRUE;
+ break;
+ case PROP_PAD_WIDTH:
+ pad->width = g_value_get_int (value);
+ pad->geometry_change = TRUE;
+ break;
+ case PROP_PAD_HEIGHT:
+ pad->height = g_value_get_int (value);
+ pad->geometry_change = TRUE;
+ break;
+ case PROP_PAD_ALPHA:
+ pad->alpha = g_value_get_double (value);
+ break;
+ case PROP_PAD_BLEND_EQUATION_RGB:
+ pad->blend_equation_rgb = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_EQUATION_ALPHA:
+ pad->blend_equation_alpha = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_SRC_RGB:
+ pad->blend_function_src_rgb = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_SRC_ALPHA:
+ pad->blend_function_src_alpha = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_DST_RGB:
+ pad->blend_function_dst_rgb = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_DST_ALPHA:
+ pad->blend_function_dst_alpha = g_value_get_enum (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_RED:
+ pad->blend_constant_color_red = g_value_get_double (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_GREEN:
+ pad->blend_constant_color_green = g_value_get_double (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_BLUE:
+ pad->blend_constant_color_blue = g_value_get_double (value);
+ break;
+ case PROP_PAD_BLEND_FUNCTION_CONSTANT_COLOR_ALPHA:
+ pad->blend_constant_color_alpha = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ gst_object_unref (mix);
+}
+
+static void
+_del_buffer (GstGLContext * context, GLuint * pBuffer)
+{
+ context->gl_vtable->DeleteBuffers (1, pBuffer);
+}
+
+static void
+gst_gl_video_mixer_release_pad (GstElement * element, GstPad * p)
+{
+ GstGLVideoMixerPad *pad = GST_GL_VIDEO_MIXER_PAD (p);
+ if (pad->vertex_buffer) {
+ GstGLBaseMixer *mix = GST_GL_BASE_MIXER (element);
+ gst_gl_context_thread_add (mix->context, (GstGLContextThreadFunc)
+ _del_buffer, &pad->vertex_buffer);
+ pad->vertex_buffer = 0;
+ }
+ GST_ELEMENT_CLASS (g_type_class_peek_parent (G_OBJECT_GET_CLASS (element)))
+ ->release_pad (element, p);
+}
+
+static void
+gst_gl_video_mixer_class_init (GstGLVideoMixerClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
+ GstVideoAggregatorClass *vagg_class = (GstVideoAggregatorClass *) klass;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+ element_class->release_pad = gst_gl_video_mixer_release_pad;
+
+ gobject_class->set_property = gst_gl_video_mixer_set_property;
+ gobject_class->get_property = gst_gl_video_mixer_get_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL video_mixer",
+ "Filter/Effect/Video/Compositor", "OpenGL video_mixer",
+ "Matthew Waters <matthew@centricular.com>");
+
+ gst_element_class_add_static_pad_template_with_gtype (element_class,
+ &sink_factory, GST_TYPE_GL_VIDEO_MIXER_PAD);
+
+ g_object_class_install_property (gobject_class, PROP_BACKGROUND,
+ g_param_spec_enum ("background", "Background", "Background type",
+ GST_TYPE_GL_VIDEO_MIXER_BACKGROUND,
+ DEFAULT_BACKGROUND, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ GST_GL_MIXER_CLASS (klass)->set_caps = gst_gl_video_mixer_init_shader;
+ GST_GL_MIXER_CLASS (klass)->reset = gst_gl_video_mixer_reset;
+ GST_GL_MIXER_CLASS (klass)->process_textures =
+ gst_gl_video_mixer_process_textures;
+
+
+ vagg_class->update_caps = _update_caps;
+
+ agg_class->fixate_src_caps = _fixate_caps;
+ agg_class->propose_allocation = gst_gl_video_mixer_propose_allocation;
+
+ GST_GL_BASE_MIXER_CLASS (klass)->supported_gl_api =
+ GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2;
+}
+
+static void
+gst_gl_video_mixer_init (GstGLVideoMixer * video_mixer)
+{
+ video_mixer->background = DEFAULT_BACKGROUND;
+ video_mixer->shader = NULL;
+}
+
+static void
+gst_gl_video_mixer_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixer *mixer = GST_GL_VIDEO_MIXER (object);
+
+ switch (prop_id) {
+ case PROP_BACKGROUND:
+ mixer->background = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_video_mixer_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLVideoMixer *mixer = GST_GL_VIDEO_MIXER (object);
+
+ switch (prop_id) {
+ case PROP_BACKGROUND:
+ g_value_set_enum (value, mixer->background);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gl_video_mixer_propose_allocation (GstAggregator * agg,
+ GstAggregatorPad * agg_pad, GstQuery * decide_query, GstQuery * query)
+{
+ if (!GST_AGGREGATOR_CLASS (parent_class)->propose_allocation (agg,
+ agg_pad, decide_query, query))
+ return FALSE;
+
+ gst_query_add_allocation_meta (query,
+ GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE, 0);
+
+ return TRUE;
+}
+
+static void
+_mixer_pad_get_output_size (GstGLVideoMixer * mix,
+ GstGLVideoMixerPad * mix_pad, gint out_par_n, gint out_par_d, gint * width,
+ gint * height)
+{
+ GstVideoAggregatorPad *vagg_pad = GST_VIDEO_AGGREGATOR_PAD (mix_pad);
+ gint pad_width, pad_height;
+ guint dar_n, dar_d;
+
+ /* FIXME: Anything better we can do here? */
+ if (!vagg_pad->info.finfo
+ || vagg_pad->info.finfo->format == GST_VIDEO_FORMAT_UNKNOWN) {
+ GST_DEBUG_OBJECT (mix_pad, "Have no caps yet");
+ *width = 0;
+ *height = 0;
+ return;
+ }
+
+ pad_width =
+ mix_pad->width <=
+ 0 ? GST_VIDEO_INFO_WIDTH (&vagg_pad->info) : mix_pad->width;
+ pad_height =
+ mix_pad->height <=
+ 0 ? GST_VIDEO_INFO_HEIGHT (&vagg_pad->info) : mix_pad->height;
+
+ if (!gst_video_calculate_display_ratio (&dar_n, &dar_d, pad_width, pad_height,
+ GST_VIDEO_INFO_PAR_N (&vagg_pad->info),
+ GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d)) {
+ GST_WARNING_OBJECT (mix_pad, "Cannot calculate display aspect ratio");
+ *width = *height = 0;
+ return;
+ }
+ GST_LOG_OBJECT (mix_pad, "scaling %ux%u by %u/%u (%u/%u / %u/%u)", pad_width,
+ pad_height, dar_n, dar_d, GST_VIDEO_INFO_PAR_N (&vagg_pad->info),
+ GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d);
+
+ if (pad_height % dar_n == 0) {
+ pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d);
+ } else if (pad_width % dar_d == 0) {
+ pad_height = gst_util_uint64_scale_int (pad_width, dar_d, dar_n);
+ } else {
+ pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d);
+ }
+
+ *width = pad_width;
+ *height = pad_height;
+}
+
+static GstCaps *
+_update_caps (GstVideoAggregator * vagg, GstCaps * caps)
+{
+ GstCaps *ret;
+ GList *l;
+
+ GST_OBJECT_LOCK (vagg);
+ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
+ GstVideoAggregatorPad *vaggpad = l->data;
+
+ if (!vaggpad->info.finfo)
+ continue;
+
+ if (GST_VIDEO_INFO_FORMAT (&vaggpad->info) == GST_VIDEO_FORMAT_UNKNOWN)
+ continue;
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (&vaggpad->info) !=
+ GST_VIDEO_MULTIVIEW_MODE_NONE
+ && GST_VIDEO_INFO_MULTIVIEW_MODE (&vaggpad->info) !=
+ GST_VIDEO_MULTIVIEW_MODE_MONO) {
+ GST_FIXME_OBJECT (vaggpad, "Multiview support is not implemented yet");
+ GST_OBJECT_UNLOCK (vagg);
+ return NULL;
+ }
+
+ }
+
+ GST_OBJECT_UNLOCK (vagg);
+
+ ret = gst_caps_ref (caps);
+
+ return ret;
+}
+
+static GstCaps *
+_fixate_caps (GstAggregator * agg, GstCaps * caps)
+{
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (agg);
+ GstGLVideoMixer *mix = GST_GL_VIDEO_MIXER (vagg);
+ gint best_width = 0, best_height = 0;
+ gint best_fps_n = 0, best_fps_d = 0;
+ gint par_n, par_d;
+ gdouble best_fps = 0.;
+ GstCaps *ret = NULL;
+ GstStructure *s;
+ GList *l;
+
+ ret = gst_caps_make_writable (caps);
+
+ /* we need this to calculate how large to make the output frame */
+ s = gst_caps_get_structure (ret, 0);
+ if (!gst_structure_has_field (s, "pixel-aspect-ratio")) {
+ gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
+ }
+ gst_structure_fixate_field_nearest_fraction (s, "pixel-aspect-ratio", 1, 1);
+ gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d);
+
+ GST_OBJECT_LOCK (vagg);
+ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
+ GstVideoAggregatorPad *vaggpad = l->data;
+ GstGLVideoMixerPad *mixer_pad = GST_GL_VIDEO_MIXER_PAD (vaggpad);
+ gint this_width, this_height;
+ gint width, height;
+ gint fps_n, fps_d;
+ gdouble cur_fps;
+
+ fps_n = GST_VIDEO_INFO_FPS_N (&vaggpad->info);
+ fps_d = GST_VIDEO_INFO_FPS_D (&vaggpad->info);
+ _mixer_pad_get_output_size (mix, mixer_pad, par_n, par_d, &width, &height);
+
+ if (width == 0 || height == 0)
+ continue;
+
+ this_width = width + MAX (mixer_pad->xpos, 0);
+ this_height = height + MAX (mixer_pad->ypos, 0);
+
+ if (best_width < this_width)
+ best_width = this_width;
+ if (best_height < this_height)
+ best_height = this_height;
+
+ if (fps_d == 0)
+ cur_fps = 0.0;
+ else
+ gst_util_fraction_to_double (fps_n, fps_d, &cur_fps);
+
+ if (best_fps < cur_fps) {
+ best_fps = cur_fps;
+ best_fps_n = fps_n;
+ best_fps_d = fps_d;
+ }
+ }
+ GST_OBJECT_UNLOCK (vagg);
+
+ if (best_fps_n <= 0 || best_fps_d <= 0 || best_fps == 0.0) {
+ best_fps_n = 25;
+ best_fps_d = 1;
+ best_fps = 25.0;
+ }
+
+ s = gst_caps_get_structure (ret, 0);
+ gst_structure_fixate_field_nearest_int (s, "width", best_width);
+ gst_structure_fixate_field_nearest_int (s, "height", best_height);
+ gst_structure_fixate_field_nearest_fraction (s, "framerate", best_fps_n,
+ best_fps_d);
+ ret = gst_caps_fixate (ret);
+
+ return ret;
+}
+
+static gboolean
+_reset_pad_gl (GstElement * agg, GstPad * aggpad, gpointer udata)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (agg)->context->gl_vtable;
+ GstGLVideoMixerPad *pad = GST_GL_VIDEO_MIXER_PAD (aggpad);
+
+ if (pad->vertex_buffer) {
+ gl->DeleteBuffers (1, &pad->vertex_buffer);
+ pad->vertex_buffer = 0;
+ }
+
+ return TRUE;
+}
+
+static void
+_reset_gl (GstGLContext * context, GstGLVideoMixer * video_mixer)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (video_mixer)->context->gl_vtable;
+
+ if (video_mixer->vao) {
+ gl->DeleteVertexArrays (1, &video_mixer->vao);
+ video_mixer->vao = 0;
+ }
+
+ if (video_mixer->vbo_indices) {
+ gl->DeleteBuffers (1, &video_mixer->vbo_indices);
+ video_mixer->vbo_indices = 0;
+ }
+
+ if (video_mixer->checker_vbo) {
+ gl->DeleteBuffers (1, &video_mixer->checker_vbo);
+ video_mixer->checker_vbo = 0;
+ }
+
+ gst_element_foreach_sink_pad (GST_ELEMENT (video_mixer), _reset_pad_gl, NULL);
+}
+
+static void
+gst_gl_video_mixer_reset (GstGLMixer * mixer)
+{
+ GstGLVideoMixer *video_mixer = GST_GL_VIDEO_MIXER (mixer);
+ GstGLContext *context = GST_GL_BASE_MIXER (mixer)->context;
+
+ GST_DEBUG_OBJECT (mixer, "context:%p", context);
+
+ if (video_mixer->shader)
+ gst_object_unref (video_mixer->shader);
+ video_mixer->shader = NULL;
+
+ if (video_mixer->checker)
+ gst_object_unref (video_mixer->checker);
+ video_mixer->checker = NULL;
+
+ if (GST_GL_BASE_MIXER (mixer)->context)
+ gst_gl_context_thread_add (context, (GstGLContextThreadFunc) _reset_gl,
+ mixer);
+}
+
+static gboolean
+gst_gl_video_mixer_init_shader (GstGLMixer * mixer, GstCaps * outcaps)
+{
+ GstGLVideoMixer *video_mixer = GST_GL_VIDEO_MIXER (mixer);
+
+ if (video_mixer->shader)
+ gst_object_unref (video_mixer->shader);
+
+ /* need reconfigure output geometry */
+ video_mixer->output_geo_change = TRUE;
+
+ return gst_gl_context_gen_shader (GST_GL_BASE_MIXER (mixer)->context,
+ gst_gl_shader_string_vertex_mat4_vertex_transform,
+ video_mixer_f_src, &video_mixer->shader);
+}
+
+static void
+_video_mixer_process_gl (GstGLContext * context, GstGLVideoMixer * video_mixer)
+{
+ GstGLMixer *mixer = GST_GL_MIXER (video_mixer);
+
+ gst_gl_framebuffer_draw_to_texture (mixer->fbo, video_mixer->out_tex,
+ gst_gl_video_mixer_callback, video_mixer);
+}
+
+static gboolean
+gst_gl_video_mixer_process_textures (GstGLMixer * mix, GstGLMemory * out_tex)
+{
+ GstGLVideoMixer *video_mixer = GST_GL_VIDEO_MIXER (mix);
+ GstGLContext *context = GST_GL_BASE_MIXER (mix)->context;
+
+ video_mixer->out_tex = out_tex;
+
+ gst_gl_context_thread_add (context,
+ (GstGLContextThreadFunc) _video_mixer_process_gl, video_mixer);
+
+ return TRUE;
+}
+
+static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
+
+static void
+_init_vbo_indices (GstGLVideoMixer * mixer)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (mixer)->context->gl_vtable;
+
+ if (!mixer->vbo_indices) {
+ gl->GenBuffers (1, &mixer->vbo_indices);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, mixer->vbo_indices);
+ gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
+ GL_STATIC_DRAW);
+ }
+}
+
+static gboolean
+_draw_checker_background (GstGLVideoMixer * video_mixer)
+{
+ GstGLMixer *mixer = GST_GL_MIXER (video_mixer);
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (mixer)->context->gl_vtable;
+ gint attr_position_loc = 0;
+
+ /* *INDENT-OFF* */
+ gfloat v_vertices[] = {
+ -1.0,-1.0,-1.0f,
+ 1.0,-1.0,-1.0f,
+ 1.0, 1.0,-1.0f,
+ -1.0, 1.0,-1.0f,
+ };
+ /* *INDENT-ON* */
+
+ if (!video_mixer->checker) {
+ if (!gst_gl_context_gen_shader (GST_GL_BASE_MIXER (mixer)->context,
+ checker_v_src, checker_f_src, &video_mixer->checker))
+ return FALSE;
+ }
+
+ gst_gl_shader_use (video_mixer->checker);
+ attr_position_loc =
+ gst_gl_shader_get_attribute_location (video_mixer->checker, "a_position");
+
+ _init_vbo_indices (video_mixer);
+
+ if (!video_mixer->checker_vbo) {
+ gl->GenBuffers (1, &video_mixer->checker_vbo);
+ gl->BindBuffer (GL_ARRAY_BUFFER, video_mixer->checker_vbo);
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 3 * sizeof (GLfloat), v_vertices,
+ GL_STATIC_DRAW);
+ } else {
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, video_mixer->vbo_indices);
+ gl->BindBuffer (GL_ARRAY_BUFFER, video_mixer->checker_vbo);
+ }
+
+ gl->VertexAttribPointer (attr_position_loc, 3, GL_FLOAT,
+ GL_FALSE, 3 * sizeof (GLfloat), (void *) 0);
+
+ gl->EnableVertexAttribArray (attr_position_loc);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ gl->DisableVertexAttribArray (attr_position_loc);
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ return TRUE;
+}
+
+static gboolean
+_draw_background (GstGLVideoMixer * video_mixer)
+{
+ GstGLMixer *mixer = GST_GL_MIXER (video_mixer);
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (mixer)->context->gl_vtable;
+
+ switch (video_mixer->background) {
+ case GST_GL_VIDEO_MIXER_BACKGROUND_BLACK:
+ gl->ClearColor (0.0, 0.0, 0.0, 1.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ break;
+ case GST_GL_VIDEO_MIXER_BACKGROUND_WHITE:
+ gl->ClearColor (1.0, 1.0, 1.0, 1.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ break;
+ case GST_GL_VIDEO_MIXER_BACKGROUND_TRANSPARENT:
+ gl->ClearColor (0.0, 0.0, 0.0, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ break;
+ case GST_GL_VIDEO_MIXER_BACKGROUND_CHECKER:
+ return _draw_checker_background (video_mixer);
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static guint
+_blend_equation_to_gl (GstGLVideoMixerBlendEquation equation)
+{
+ switch (equation) {
+ case GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD:
+ return GL_FUNC_ADD;
+ case GST_GL_VIDEO_MIXER_BLEND_EQUATION_SUBTRACT:
+ return GL_FUNC_SUBTRACT;
+ case GST_GL_VIDEO_MIXER_BLEND_EQUATION_REVERSE_SUBTRACT:
+ return GL_FUNC_REVERSE_SUBTRACT;
+ default:
+ g_assert_not_reached ();
+ return 0;
+ }
+}
+
+static guint
+_blend_function_to_gl (GstGLVideoMixerBlendFunction equation)
+{
+ switch (equation) {
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ZERO:
+ return GL_ZERO;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE:
+ return GL_ONE;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_COLOR:
+ return GL_SRC_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_COLOR:
+ return GL_ONE_MINUS_SRC_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_COLOR:
+ return GL_DST_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_COLOR:
+ return GL_ONE_MINUS_DST_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA:
+ return GL_SRC_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA:
+ return GL_ONE_MINUS_SRC_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_ALPHA:
+ return GL_DST_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_ALPHA:
+ return GL_ONE_MINUS_DST_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_COLOR:
+ return GL_CONSTANT_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR:
+ return GL_ONE_MINUS_CONSTANT_COLOR;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_ALPHA:
+ return GL_CONSTANT_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_ALPHA:
+ return GL_ONE_MINUS_CONSTANT_ALPHA;
+ case GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE:
+ return GL_SRC_ALPHA_SATURATE;
+ default:
+ g_assert_not_reached ();
+ return 0;
+ }
+}
+
+static gboolean
+_set_blend_state (GstGLVideoMixer * video_mixer, GstGLVideoMixerPad * mix_pad)
+{
+ const GstGLFuncs *gl = GST_GL_BASE_MIXER (video_mixer)->context->gl_vtable;
+ gboolean require_separate = FALSE;
+ guint gl_func_src_rgb, gl_func_src_alpha, gl_func_dst_rgb, gl_func_dst_alpha;
+ guint gl_equation_rgb, gl_equation_alpha;
+
+ require_separate =
+ mix_pad->blend_equation_rgb != mix_pad->blend_equation_alpha
+ || mix_pad->blend_function_src_rgb != mix_pad->blend_function_src_alpha
+ || mix_pad->blend_function_dst_rgb != mix_pad->blend_function_dst_alpha;
+
+ if (require_separate && (!gl->BlendFuncSeparate
+ || !gl->BlendEquationSeparate)) {
+ GST_ERROR_OBJECT (mix_pad,
+ "separated blend equations/functions requested however "
+ "glBlendFuncSeparate or glBlendEquationSeparate not available");
+ return FALSE;
+ }
+
+ if (mix_pad->blend_function_dst_rgb ==
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE) {
+ GST_ERROR_OBJECT (mix_pad,
+ "Destination RGB blend function cannot be \'SRC_ALPHA_SATURATE\'");
+ return FALSE;
+ }
+
+ if (mix_pad->blend_function_dst_alpha ==
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE) {
+ GST_ERROR_OBJECT (mix_pad,
+ "Destination alpha blend function cannot be \'SRC_ALPHA_SATURATE\'");
+ return FALSE;
+ }
+
+ gl_equation_rgb = _blend_equation_to_gl (mix_pad->blend_equation_rgb);
+ gl_equation_alpha = _blend_equation_to_gl (mix_pad->blend_equation_alpha);
+
+ gl_func_src_rgb = _blend_function_to_gl (mix_pad->blend_function_src_rgb);
+ gl_func_src_alpha = _blend_function_to_gl (mix_pad->blend_function_src_alpha);
+ gl_func_dst_rgb = _blend_function_to_gl (mix_pad->blend_function_dst_rgb);
+ gl_func_dst_alpha = _blend_function_to_gl (mix_pad->blend_function_dst_alpha);
+
+ if (gl->BlendEquationSeparate)
+ gl->BlendEquationSeparate (gl_equation_rgb, gl_equation_alpha);
+ else
+ gl->BlendEquation (gl_equation_rgb);
+
+ if (gl->BlendFuncSeparate)
+ gl->BlendFuncSeparate (gl_func_src_rgb, gl_func_dst_rgb, gl_func_src_alpha,
+ gl_func_dst_alpha);
+ else
+ gl->BlendFunc (gl_func_src_rgb, gl_func_dst_rgb);
+
+ gl->BlendColor (mix_pad->blend_constant_color_red,
+ mix_pad->blend_constant_color_green, mix_pad->blend_constant_color_blue,
+ mix_pad->blend_constant_color_alpha);
+
+ return TRUE;
+}
+
+/* opengl scene, params: input texture (not the output mixer->texture) */
+static gboolean
+gst_gl_video_mixer_callback (gpointer stuff)
+{
+ GstGLVideoMixer *video_mixer = GST_GL_VIDEO_MIXER (stuff);
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (stuff);
+ GstGLMixer *mixer = GST_GL_MIXER (video_mixer);
+ GstGLFuncs *gl = GST_GL_BASE_MIXER (mixer)->context->gl_vtable;
+ GLint attr_position_loc = 0;
+ GLint attr_texture_loc = 0;
+ guint out_width, out_height;
+ GList *walk;
+
+ out_width = GST_VIDEO_INFO_WIDTH (&vagg->info);
+ out_height = GST_VIDEO_INFO_HEIGHT (&vagg->info);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_MIXER (mixer)->context);
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->Disable (GL_DEPTH_TEST);
+ gl->Disable (GL_CULL_FACE);
+
+ if (gl->GenVertexArrays) {
+ if (!video_mixer->vao)
+ gl->GenVertexArrays (1, &video_mixer->vao);
+ gl->BindVertexArray (video_mixer->vao);
+ }
+
+ if (!_draw_background (video_mixer))
+ return FALSE;
+
+ gst_gl_shader_use (video_mixer->shader);
+
+ attr_position_loc =
+ gst_gl_shader_get_attribute_location (video_mixer->shader, "a_position");
+ attr_texture_loc =
+ gst_gl_shader_get_attribute_location (video_mixer->shader, "a_texcoord");
+
+ gl->Enable (GL_BLEND);
+
+ GST_OBJECT_LOCK (video_mixer);
+ walk = GST_ELEMENT (video_mixer)->sinkpads;
+ while (walk) {
+ GstGLMixerPad *mix_pad = walk->data;
+ GstGLVideoMixerPad *pad = walk->data;
+ GstVideoAggregatorPad *vagg_pad = walk->data;
+ GstVideoInfo *v_info;
+ guint in_tex;
+ guint in_width, in_height;
+
+ /* *INDENT-OFF* */
+ gfloat v_vertices[] = {
+ -1.0,-1.0,-1.0f, 0.0f, 0.0f,
+ 1.0,-1.0,-1.0f, 1.0f, 0.0f,
+ 1.0, 1.0,-1.0f, 1.0f, 1.0f,
+ -1.0, 1.0,-1.0f, 0.0f, 1.0f,
+ };
+ /* *INDENT-ON* */
+
+ v_info = &GST_VIDEO_AGGREGATOR_PAD (pad)->info;
+ in_width = GST_VIDEO_INFO_WIDTH (v_info);
+ in_height = GST_VIDEO_INFO_HEIGHT (v_info);
+
+ if (!mix_pad->current_texture || in_width <= 0 || in_height <= 0
+ || pad->alpha == 0.0f) {
+ GST_DEBUG ("skipping texture:%u pad:%p width:%u height:%u alpha:%f",
+ mix_pad->current_texture, pad, in_width, in_height, pad->alpha);
+ walk = g_list_next (walk);
+ continue;
+ }
+
+ if (!_set_blend_state (video_mixer, pad)) {
+ GST_FIXME_OBJECT (pad, "skipping due to incorrect blend parameters");
+ walk = g_list_next (walk);
+ continue;
+ }
+
+ in_tex = mix_pad->current_texture;
+
+ _init_vbo_indices (video_mixer);
+
+ if (video_mixer->output_geo_change
+ || pad->geometry_change || !pad->vertex_buffer) {
+ gint pad_width, pad_height;
+ gfloat w, h;
+
+ _mixer_pad_get_output_size (video_mixer, pad,
+ GST_VIDEO_INFO_PAR_N (&vagg->info),
+ GST_VIDEO_INFO_PAR_D (&vagg->info), &pad_width, &pad_height);
+
+ w = ((gfloat) pad_width / (gfloat) out_width);
+ h = ((gfloat) pad_height / (gfloat) out_height);
+
+ /* top-left */
+ v_vertices[0] = v_vertices[15] =
+ 2.0f * (gfloat) pad->xpos / (gfloat) out_width - 1.0f;
+ /* bottom-left */
+ v_vertices[1] = v_vertices[6] =
+ 2.0f * (gfloat) pad->ypos / (gfloat) out_height - 1.0f;
+ /* top-right */
+ v_vertices[5] = v_vertices[10] = v_vertices[0] + 2.0f * w;
+ /* bottom-right */
+ v_vertices[11] = v_vertices[16] = v_vertices[1] + 2.0f * h;
+ GST_TRACE ("processing texture:%u dimensions:%ux%u, at %f,%f %fx%f with "
+ "alpha:%f", in_tex, in_width, in_height, v_vertices[0], v_vertices[1],
+ v_vertices[5], v_vertices[11], pad->alpha);
+
+ if (!pad->vertex_buffer)
+ gl->GenBuffers (1, &pad->vertex_buffer);
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, pad->vertex_buffer);
+
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), v_vertices,
+ GL_STATIC_DRAW);
+
+ pad->geometry_change = FALSE;
+ } else {
+ gl->BindBuffer (GL_ARRAY_BUFFER, pad->vertex_buffer);
+ }
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, video_mixer->vbo_indices);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, in_tex);
+ gst_gl_shader_set_uniform_1i (video_mixer->shader, "texture", 0);
+ gst_gl_shader_set_uniform_1f (video_mixer->shader, "alpha", pad->alpha);
+
+ {
+ GstVideoAffineTransformationMeta *af_meta;
+ gfloat matrix[16];
+
+ af_meta =
+ gst_buffer_get_video_affine_transformation_meta (vagg_pad->buffer);
+ gst_gl_get_affine_transformation_meta_as_ndc_ext (af_meta, matrix);
+ gst_gl_shader_set_uniform_matrix_4fv (video_mixer->shader,
+ "u_transformation", 1, FALSE, matrix);
+ }
+
+ gl->EnableVertexAttribArray (attr_position_loc);
+ gl->EnableVertexAttribArray (attr_texture_loc);
+
+ gl->VertexAttribPointer (attr_position_loc, 3, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), (void *) 0);
+
+ gl->VertexAttribPointer (attr_texture_loc, 2, GL_FLOAT,
+ GL_FALSE, 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+
+ walk = g_list_next (walk);
+ }
+
+ video_mixer->output_geo_change = FALSE;
+ GST_OBJECT_UNLOCK (video_mixer);
+
+ gl->DisableVertexAttribArray (attr_position_loc);
+ gl->DisableVertexAttribArray (attr_texture_loc);
+
+ if (gl->GenVertexArrays)
+ gl->BindVertexArray (0);
+
+ gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+
+ gl->Disable (GL_BLEND);
+
+ gst_gl_context_clear_shader (GST_GL_BASE_MIXER (mixer)->context);
+
+ return TRUE;
+}
diff --git a/ext/gl/gstglvideomixer.h b/ext/gl/gstglvideomixer.h
new file mode 100644
index 000000000..d07e1880a
--- /dev/null
+++ b/ext/gl/gstglvideomixer.h
@@ -0,0 +1,143 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_VIDEO_MIXER_H_
+#define _GST_GL_VIDEO_MIXER_H_
+
+#include "gstglmixer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_VIDEO_MIXER (gst_gl_video_mixer_get_type())
+#define GST_GL_VIDEO_MIXER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_VIDEO_MIXER,GstGLVideoMixer))
+#define GST_IS_GL_VIDEO_MIXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_VIDEO_MIXER))
+#define GST_GL_VIDEO_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_VIDEO_MIXER,GstGLVideoMixerClass))
+#define GST_IS_GL_VIDEO_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_VIDEO_MIXER))
+#define GST_GL_VIDEO_MIXER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_VIDEO_MIXER,GstGLVideoMixerClass))
+
+typedef struct _GstGLVideoMixer GstGLVideoMixer;
+typedef struct _GstGLVideoMixerClass GstGLVideoMixerClass;
+
+/**
+ * GstGLVideoMixerBackground:
+ * @GST_GL_VIDEO_MIXER_BACKGROUND_CHECKER: checker pattern background
+ * @GST_GL_VIDEO_MIXER_BACKGROUND_BLACK: solid color black background
+ * @GST_GL_VIDEO_MIXER_BACKGROUND_WHITE: solid color white background
+ * @GST_GL_VIDEO_MIXER_BACKGROUND_TRANSPARENT: background is left transparent and layers are composited using "A OVER B" composition rules. This is only applicable to AYUV and ARGB (and variants) as it preserves the alpha channel and allows for further mixing.
+ *
+ * The different backgrounds compositor can blend over.
+ */
+typedef enum
+{
+ GST_GL_VIDEO_MIXER_BACKGROUND_CHECKER,
+ GST_GL_VIDEO_MIXER_BACKGROUND_BLACK,
+ GST_GL_VIDEO_MIXER_BACKGROUND_WHITE,
+ GST_GL_VIDEO_MIXER_BACKGROUND_TRANSPARENT,
+}
+GstGLVideoMixerBackground;
+
+/**
+ * GstGLVideoMixerBlendEquation:
+ * @GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD: Add the two results.
+ * @GST_GL_VIDEO_MIXER_BLEND_EQUATION_SUBTRACT: Subtract component-wise the destination from the source (S - D).
+ * @GST_GL_VIDEO_MIXER_BLEND_EQUATION_REVERSE_SUBTRACT: Subtract component-wise the source from the destination (D - S).
+ *
+ * The blending equation to use. See the opengl specificition for
+ * glBlendEquationSeparate
+ */
+typedef enum
+{
+ GST_GL_VIDEO_MIXER_BLEND_EQUATION_ADD,
+ GST_GL_VIDEO_MIXER_BLEND_EQUATION_SUBTRACT,
+ GST_GL_VIDEO_MIXER_BLEND_EQUATION_REVERSE_SUBTRACT,
+}
+GstGLVideoMixerBlendEquation;
+
+/**
+ * GstGLVideoMixerBlendFunction:
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ZERO: All components are zero
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE: All components are one
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_COLOR: Use the source color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_COLOR: One minus the source color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_COLOR: Use the destination color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_COLOR: One minus the destination color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA: All components are the source alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA: All components are one minus the source alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_ALPHA: All components are the destination alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_ALPHA: All components are one minus the destination alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_COLOR: Use the constant color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR: Use one minus the constant color/alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_ALPHA: All components are the constant alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR: All components are one minus the constant alpha
+ * @GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE: All color components
+ * are the minimum of source alpha and one minus the destination alpha.
+ * Alpha is equal to one.
+ *
+ * The blending function to use. See the opengl specificition for
+ * glBlendFuncSeparate
+ */
+typedef enum
+{
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ZERO,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_SRC_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_DST_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_DST_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_COLOR,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_CONSTANT_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_ONE_MINUS_CONSTANT_ALPHA,
+ GST_GL_VIDEO_MIXER_BLEND_FUNCTION_SRC_ALPHA_SATURATE,
+}
+GstGLVideoMixerBlendFunction;
+
+struct _GstGLVideoMixer
+{
+ GstGLMixer mixer;
+
+ GstGLVideoMixerBackground background;
+
+ GstGLShader *shader;
+ GstGLShader *checker;
+
+ GLuint vao;
+ GLuint vbo_indices;
+ GLuint checker_vbo;
+ GstGLMemory *out_tex;
+
+ gboolean output_geo_change;
+};
+
+struct _GstGLVideoMixerClass
+{
+ GstGLMixerClass mixer_class;
+};
+
+GType gst_gl_video_mixer_get_type (void);
+GType gst_gl_video_mixer_bin_get_type (void);
+
+G_END_DECLS
+
+#endif /* _GST_GLFILTERCUBE_H_ */
diff --git a/ext/gl/gstglviewconvert.c b/ext/gl/gstglviewconvert.c
new file mode 100644
index 000000000..203df6b97
--- /dev/null
+++ b/ext/gl/gstglviewconvert.c
@@ -0,0 +1,359 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@mail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-glviewconvert
+ * @title: glviewconvert
+ *
+ * Convert stereoscopic video between different representations using fragment shaders.
+ *
+ * The element can use either property settings or caps negotiation to choose the
+ * input and output formats to process.
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 videotestsrc ! glupload ! glviewconvert ! glimagesink
+ * ]| Simple placebo example demonstrating identity passthrough of mono video
+ * |[
+ * gst-launch-1.0 videotestsrc pattern=checkers-1 ! glupload ! \
+ * glviewconvert input-mode-override=side-by-side ! glimagesink -v
+ * ]| Force re-interpretation of the input checkers pattern as a side-by-side stereoscopic
+ * image and display in glimagesink.
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/base/gstbasetransform.h>
+
+#include "gstglviewconvert.h"
+
+#define GST_CAT_DEFAULT gst_gl_view_convert_element_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_INPUT_LAYOUT,
+ PROP_INPUT_FLAGS,
+ PROP_OUTPUT_LAYOUT,
+ PROP_OUTPUT_FLAGS,
+ PROP_OUTPUT_DOWNMIX_MODE
+};
+
+#define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_view_convert_element_debug, "glview_convertelement", 0, "glview_convert element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLViewConvertElement, gst_gl_view_convert_element,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+#define parent_class gst_gl_view_convert_element_parent_class
+
+static void gst_gl_view_convert_dispose (GObject * object);
+static void gst_gl_view_convert_element_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_view_convert_element_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_view_convert_element_stop (GstBaseTransform * bt);
+static gboolean
+gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps);
+static GstCaps *gst_gl_view_convert_element_transform_internal_caps (GstGLFilter
+ * filter, GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps);
+static GstCaps *gst_gl_view_convert_element_fixate_caps (GstBaseTransform *
+ trans, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
+static GstFlowReturn
+gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
+ gboolean is_discont, GstBuffer * input);
+static GstFlowReturn
+gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
+ GstBuffer ** outbuf);
+
+static void
+gst_gl_view_convert_element_class_init (GstGLViewConvertElementClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
+
+ gobject_class->set_property = gst_gl_view_convert_element_set_property;
+ gobject_class->get_property = gst_gl_view_convert_element_get_property;
+ gobject_class->dispose = gst_gl_view_convert_dispose;
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL Multiview/3D conversion filter", "Filter",
+ "Convert stereoscopic/multiview video formats",
+ "Jan Schmidt <jan@centricular.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_view_convert_element_set_caps;
+
+ GST_GL_FILTER_CLASS (klass)->transform_internal_caps =
+ gst_gl_view_convert_element_transform_internal_caps;
+ GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_view_convert_element_stop;
+ GST_BASE_TRANSFORM_CLASS (klass)->fixate_caps =
+ gst_gl_view_convert_element_fixate_caps;
+ GST_BASE_TRANSFORM_CLASS (klass)->submit_input_buffer =
+ gst_gl_view_convert_element_submit_input_buffer;
+ GST_BASE_TRANSFORM_CLASS (klass)->generate_output =
+ gst_gl_view_convert_element_generate_output_buffer;
+
+ g_object_class_install_property (gobject_class, PROP_INPUT_LAYOUT,
+ g_param_spec_enum ("input-mode-override",
+ "Input Multiview Mode Override",
+ "Override any input information about multiview layout",
+ GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
+ GST_VIDEO_MULTIVIEW_MODE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_FLAGS,
+ g_param_spec_flags ("input-flags-override",
+ "Input Multiview Flags Override",
+ "Override any input information about multiview layout flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_LAYOUT,
+ g_param_spec_enum ("output-mode-override",
+ "Output Multiview Mode Override",
+ "Override automatic output mode selection for multiview layout",
+ GST_TYPE_VIDEO_MULTIVIEW_MODE, GST_VIDEO_MULTIVIEW_MODE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_FLAGS,
+ g_param_spec_flags ("output-flags-override",
+ "Output Multiview Flags Override",
+ "Override automatic negotiation for output multiview layout flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_DOWNMIX_MODE,
+ g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_gl_view_convert_element_init (GstGLViewConvertElement * convert)
+{
+ convert->viewconvert = gst_gl_view_convert_new ();
+}
+
+static void
+gst_gl_view_convert_dispose (GObject * object)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ if (convert->viewconvert) {
+ gst_object_unref (convert->viewconvert);
+ convert->viewconvert = NULL;
+ }
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (filter);
+ GstCapsFeatures *gl_features;
+ gboolean ret;
+
+ GST_DEBUG_OBJECT (filter, "incaps %" GST_PTR_FORMAT
+ " outcaps %" GST_PTR_FORMAT, incaps, outcaps);
+ /* The view_convert component needs RGBA caps */
+ incaps = gst_caps_copy (incaps);
+ outcaps = gst_caps_copy (outcaps);
+
+ gst_caps_set_simple (incaps, "format", G_TYPE_STRING, "RGBA", NULL);
+ gl_features =
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ gst_caps_set_features (incaps, 0, gl_features);
+
+ gst_caps_set_simple (outcaps, "format", G_TYPE_STRING, "RGBA", NULL);
+ gl_features =
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ gst_caps_set_features (outcaps, 0, gl_features);
+
+ ret = gst_gl_view_convert_set_caps (viewconvert_filter->viewconvert,
+ incaps, outcaps);
+
+ gst_caps_unref (incaps);
+ gst_caps_unref (outcaps);
+
+ return ret;
+}
+
+static GstCaps *
+gst_gl_view_convert_element_transform_internal_caps (GstGLFilter * filter,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (filter);
+ GstCaps *result;
+
+ GST_DEBUG_OBJECT (filter, "dir %s transforming caps: %" GST_PTR_FORMAT,
+ direction == GST_PAD_SINK ? "sink" : "src", caps);
+
+ result =
+ gst_gl_view_convert_transform_caps (viewconvert_filter->viewconvert,
+ direction, caps, NULL);
+
+ GST_DEBUG_OBJECT (filter, "returning caps: %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+static GstCaps *
+gst_gl_view_convert_element_fixate_caps (GstBaseTransform * trans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (trans);
+
+ othercaps = gst_gl_view_convert_fixate_caps (viewconvert_filter->viewconvert,
+ direction, caps, othercaps);
+
+ if (gst_caps_is_empty (othercaps))
+ return othercaps;
+
+ /* Let GLfilter do the rest */
+ return
+ GST_BASE_TRANSFORM_CLASS
+ (gst_gl_view_convert_element_parent_class)->fixate_caps (trans, direction,
+ caps, othercaps);
+}
+
+static gboolean
+gst_gl_view_convert_element_stop (GstBaseTransform * bt)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (bt);
+
+ gst_gl_view_convert_reset (viewconvert_filter->viewconvert);
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt);
+}
+
+static void
+gst_gl_view_convert_element_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ switch (prop_id) {
+ case PROP_INPUT_LAYOUT:
+ case PROP_INPUT_FLAGS:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
+ break;
+ case PROP_OUTPUT_LAYOUT:
+ case PROP_OUTPUT_FLAGS:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
+ break;
+ case PROP_OUTPUT_DOWNMIX_MODE:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_view_convert_element_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ switch (prop_id) {
+ case PROP_INPUT_LAYOUT:
+ case PROP_INPUT_FLAGS:
+ case PROP_OUTPUT_LAYOUT:
+ case PROP_OUTPUT_FLAGS:
+ case PROP_OUTPUT_DOWNMIX_MODE:
+ g_object_get_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstFlowReturn
+gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
+ gboolean is_discont, GstBuffer * input)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (trans)->context;
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (trans);
+ GstFlowReturn ret;
+
+ ret =
+ GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (trans,
+ is_discont, input);
+ if (ret != GST_FLOW_OK || trans->queued_buf == NULL)
+ return ret;
+
+ gst_gl_view_convert_set_context (viewconvert_filter->viewconvert, context);
+
+ /* Takes the ref to the input buffer */
+ ret =
+ gst_gl_view_convert_submit_input_buffer (viewconvert_filter->viewconvert,
+ is_discont, input);
+ trans->queued_buf = NULL;
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
+ GstBuffer ** outbuf_ptr)
+{
+ GstGLFilter *filter = GST_GL_FILTER (bt);
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (bt);
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ ret = gst_gl_view_convert_get_output (viewconvert_filter->viewconvert,
+ outbuf_ptr);
+
+ if (ret != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (filter, RESOURCE, SETTINGS,
+ ("failed to perform view conversion on input buffer"), (NULL));
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstglviewconvert.h b/ext/gl/gstglviewconvert.h
new file mode 100644
index 000000000..5ccc58f61
--- /dev/null
+++ b/ext/gl/gstglviewconvert.h
@@ -0,0 +1,53 @@
+/*
+ * GStreamer
+ * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef _GST_GL_VIEW_CONVERT_ELEMENT_H_
+#define _GST_GL_VIEW_CONVERT_ELEMENT_H_
+
+#include <gst/gl/gstglfilter.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_GL_VIEW_CONVERT_ELEMENT (gst_gl_view_convert_element_get_type())
+#define GST_GL_VIEW_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElement))
+#define GST_IS_GL_VIEW_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_VIEW_CONVERT_ELEMENT))
+#define GST_GL_VIEW_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElementClass))
+#define GST_IS_GL_VIEW_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT))
+#define GST_GL_VIEW_CONVERT_ELEMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElementClass))
+
+typedef struct _GstGLViewConvertElement GstGLViewConvertElement;
+typedef struct _GstGLViewConvertElementClass GstGLViewConvertElementClass;
+
+struct _GstGLViewConvertElement
+{
+ GstGLFilter filter;
+
+ GstGLViewConvert *viewconvert;
+};
+
+struct _GstGLViewConvertElementClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_view_convert_element_get_type (void);
+
+G_END_DECLS
+#endif /* _GST_GL_VIEW_CONVERT_H_ */
diff --git a/ext/gl/gstopengl.c b/ext/gl/gstopengl.c
new file mode 100644
index 000000000..75f8d3332
--- /dev/null
+++ b/ext/gl/gstopengl.c
@@ -0,0 +1,289 @@
+/*
+ * GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005,2006,2007 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * 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:plugin-opengl
+ * @title: GstOpengl
+ *
+ * Cross-platform OpenGL plugin.
+ *
+ * ## Debugging
+ *
+ * ## Examples
+ * |[
+ * gst-launch-1.0 --gst-debug=gldisplay:3 videotestsrc ! glimagesink
+ * ]| A debugging pipeline.
+ |[
+ * GST_DEBUG=gl*:6 gst-launch-1.0 videotestsrc ! glimagesink
+ * ]| A debugging pipelines related to shaders.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglimagesink.h"
+#include "gstgluploadelement.h"
+#include "gstgldownloadelement.h"
+#include "gstglcolorconvertelement.h"
+#include "gstglcolorbalance.h"
+#include "gstglfilterbin.h"
+#include "gstglsinkbin.h"
+#include "gstglsrcbin.h"
+#include "gstglmixerbin.h"
+
+#include "gstglfiltercube.h"
+#include "gstgleffects.h"
+#include "gstglcolorscale.h"
+#include "gstglvideomixer.h"
+#include "gstglfiltershader.h"
+#include "gstglfilterapp.h"
+#include "gstglstereosplit.h"
+#include "gstglstereomix.h"
+#include "gstglviewconvert.h"
+#include "gstgltestsrc.h"
+#include "gstgldeinterlace.h"
+
+#if HAVE_GRAPHENE
+#include "gstgltransformation.h"
+#include "gstglvideoflip.h"
+#endif
+#if HAVE_JPEG
+#if HAVE_PNG
+#include "gstgloverlay.h"
+#endif /* HAVE_PNG */
+#endif /* HAVE_JPEG */
+
+#if GST_GL_HAVE_OPENGL
+#include "gstglfilterglass.h"
+/* #include "gstglfilterreflectedscreen.h" */
+#include "gstglmosaic.h"
+#if HAVE_PNG
+#include "gstgldifferencematte.h"
+/* #include "gstglbumper.h" */
+#endif /* HAVE_PNG */
+#endif /* GST_GL_HAVE_OPENGL */
+
+#if GST_GL_HAVE_WINDOW_COCOA
+/* avoid including Cocoa/CoreFoundation from a C file... */
+extern GType gst_ca_opengl_layer_sink_bin_get_type (void);
+#endif
+
+#if GST_GL_HAVE_WINDOW_DISPMANX
+extern void bcm_host_init (void);
+#endif
+
+#if GST_GL_HAVE_WINDOW_X11
+#include <X11/Xlib.h>
+#endif
+
+#define GST_CAT_DEFAULT gst_gl_gstgl_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+/* Register filters that make up the gstgl plugin */
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_gl_gstgl_debug, "gstopengl", 0, "gstopengl");
+
+#if GST_GL_HAVE_WINDOW_DISPMANX
+ GST_DEBUG ("Initialize BCM host");
+ bcm_host_init ();
+#endif
+
+#if GST_GL_HAVE_WINDOW_X11
+ if (g_getenv ("GST_GL_XINITTHREADS"))
+ XInitThreads ();
+#endif
+
+ if (!gst_element_register (plugin, "glimagesink",
+ GST_RANK_SECONDARY, gst_gl_image_sink_bin_get_type ())) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glimagesinkelement",
+ GST_RANK_NONE, gst_glimage_sink_get_type ())) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glupload",
+ GST_RANK_NONE, GST_TYPE_GL_UPLOAD_ELEMENT)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "gldownload",
+ GST_RANK_NONE, GST_TYPE_GL_DOWNLOAD_ELEMENT)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glcolorconvert",
+ GST_RANK_NONE, GST_TYPE_GL_COLOR_CONVERT_ELEMENT)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glcolorbalance",
+ GST_RANK_NONE, GST_TYPE_GL_COLOR_BALANCE)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glfilterbin",
+ GST_RANK_NONE, GST_TYPE_GL_FILTER_BIN)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glsinkbin",
+ GST_RANK_NONE, GST_TYPE_GL_SINK_BIN)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glsrcbin",
+ GST_RANK_NONE, GST_TYPE_GL_SRC_BIN)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glmixerbin",
+ GST_RANK_NONE, GST_TYPE_GL_MIXER_BIN)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glfiltercube",
+ GST_RANK_NONE, GST_TYPE_GL_FILTER_CUBE)) {
+ return FALSE;
+ }
+#if HAVE_GRAPHENE
+ if (!gst_element_register (plugin, "gltransformation",
+ GST_RANK_NONE, GST_TYPE_GL_TRANSFORMATION)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glvideoflip",
+ GST_RANK_NONE, GST_TYPE_GL_VIDEO_FLIP)) {
+ return FALSE;
+ }
+#endif
+
+ if (!gst_gl_effects_register_filters (plugin, GST_RANK_NONE)) {
+ return FALSE;
+ };
+
+ if (!gst_element_register (plugin, "glcolorscale",
+ GST_RANK_NONE, GST_TYPE_GL_COLORSCALE)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glvideomixer",
+ GST_RANK_NONE, gst_gl_video_mixer_bin_get_type ())) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glvideomixerelement",
+ GST_RANK_NONE, gst_gl_video_mixer_get_type ())) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glshader",
+ GST_RANK_NONE, gst_gl_filtershader_get_type ())) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glfilterapp",
+ GST_RANK_NONE, GST_TYPE_GL_FILTER_APP)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glviewconvert",
+ GST_RANK_NONE, GST_TYPE_GL_VIEW_CONVERT_ELEMENT)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glstereosplit",
+ GST_RANK_NONE, GST_TYPE_GL_STEREOSPLIT)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "glstereomix",
+ GST_RANK_NONE, GST_TYPE_GL_STEREO_MIX)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "gltestsrc",
+ GST_RANK_NONE, GST_TYPE_GL_TEST_SRC)) {
+ return FALSE;
+ }
+
+ if (!gst_element_register (plugin, "gldeinterlace",
+ GST_RANK_NONE, GST_TYPE_GL_DEINTERLACE)) {
+ return FALSE;
+ }
+#if HAVE_JPEG
+#if HAVE_PNG
+ if (!gst_element_register (plugin, "gloverlay",
+ GST_RANK_NONE, gst_gl_overlay_get_type ())) {
+ return FALSE;
+ }
+#endif /* HAVE_PNG */
+#endif /* HAVE_JPEG */
+#if GST_GL_HAVE_OPENGL
+ if (!gst_element_register (plugin, "glfilterglass",
+ GST_RANK_NONE, GST_TYPE_GL_FILTER_GLASS)) {
+ return FALSE;
+ }
+#if 0
+ if (!gst_element_register (plugin, "glfilterreflectedscreen",
+ GST_RANK_NONE, GST_TYPE_GL_FILTER_REFLECTED_SCREEN)) {
+ return FALSE;
+ }
+#endif
+ if (!gst_element_register (plugin, "glmosaic",
+ GST_RANK_NONE, GST_TYPE_GL_MOSAIC)) {
+ return FALSE;
+ }
+#if HAVE_PNG
+ if (!gst_element_register (plugin, "gldifferencematte",
+ GST_RANK_NONE, gst_gl_differencematte_get_type ())) {
+ return FALSE;
+ }
+#if 0
+ if (!gst_element_register (plugin, "glbumper",
+ GST_RANK_NONE, gst_gl_bumper_get_type ())) {
+ return FALSE;
+ }
+#endif
+#endif /* HAVE_PNG */
+#endif /* GST_GL_HAVE_OPENGL */
+#if GST_GL_HAVE_WINDOW_COCOA
+ if (!gst_element_register (plugin, "caopengllayersink",
+ GST_RANK_NONE, gst_ca_opengl_layer_sink_bin_get_type ())) {
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ opengl,
+ "OpenGL plugin",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/gl/meson.build b/ext/gl/meson.build
new file mode 100644
index 000000000..a3b327964
--- /dev/null
+++ b/ext/gl/meson.build
@@ -0,0 +1,119 @@
+opengl_sources = [
+ 'gstopengl.c',
+ 'gstglbasemixer.c',
+ 'gstgluploadelement.c',
+ 'gstgldownloadelement.c',
+ 'gstglcolorconvertelement.c',
+ 'gstglfilterbin.c',
+ 'gstglmixerbin.c',
+ 'gstglsinkbin.c',
+ 'gstglsrcbin.c',
+ 'gstglimagesink.c',
+ 'gstglfiltercube.c',
+ 'gstgleffects.c',
+ 'effects/gstgleffectscurves.c',
+ 'effects/gstgleffectssources.c',
+ 'effects/gstgleffectidentity.c',
+ 'effects/gstgleffectmirror.c',
+ 'effects/gstgleffectsqueeze.c',
+ 'effects/gstgleffectstretch.c',
+ 'effects/gstgleffectfisheye.c',
+ 'effects/gstgleffecttwirl.c',
+ 'effects/gstgleffectbulge.c',
+ 'effects/gstgleffecttunnel.c',
+ 'effects/gstgleffectsquare.c',
+ 'effects/gstgleffectlumatocurve.c',
+ 'effects/gstgleffectrgbtocurve.c',
+ 'effects/gstgleffectsin.c',
+ 'effects/gstgleffectxray.c',
+ 'effects/gstgleffectglow.c',
+ 'effects/gstgleffectblur.c',
+ 'effects/gstgleffectsobel.c',
+ 'effects/gstgleffectlaplacian.c',
+ 'gstglcolorscale.c',
+ 'gstglcolorbalance.c',
+ 'gstglmixer.c',
+ 'gstglvideomixer.c',
+ 'gstglfiltershader.c',
+ 'gstglfilterapp.c',
+ 'gstglviewconvert.c',
+ 'gstglstereosplit.c',
+ 'gstgldeinterlace.c',
+ 'gstglstereomix.c',
+ 'gltestsrc.c',
+ 'gstgltestsrc.c',
+ 'gstglutils.c'
+]
+
+if build_gstgl and gstgl_dep.found()
+ optional_deps = []
+ opengl_defines = ['-DGST_USE_UNSTABLE_API']
+
+ if gl_dep.found() # have desktop GL
+ opengl_sources += [
+ 'gstglfilterglass.c',
+ 'gstglmosaic.c',
+ ]
+ endif
+
+ graphene_dep = dependency('graphene-1.0', version : '>=1.4.0', required : false)
+ if graphene_dep.found()
+ optional_deps += graphene_dep
+ opengl_defines += '-DHAVE_GRAPHENE=1'
+ opengl_sources += [
+ 'gstgltransformation.c',
+ 'gstglvideoflip.c',
+ ]
+ endif
+
+ png_dep = dependency('libpng', version : '>=1.0', required : false)
+ jpeg_dep = cc.find_library('jpeg-mmx', required : false)
+ if not jpeg_dep.found()
+ jpeg_dep = cc.find_library('jpeg', required : false)
+ endif
+
+ if png_dep.found()
+ optional_deps += png_dep
+ opengl_defines += '-DHAVE_PNG=1'
+ opengl_sources += [
+ 'gstgldifferencematte.c',
+ ]
+ if jpeg_dep.found()
+ optional_deps += jpeg_dep
+ opengl_defines += '-DHAVE_JPEG=1'
+ opengl_sources += [
+ 'gstgloverlay.c',
+ ]
+ endif
+ endif
+
+ if false # have cocoa
+ opengl_sources += [
+ 'caopengllayersink.m',
+ ]
+ endif
+
+ if x11_dep.found()
+ # for XInitThreads()
+ optional_deps += x11_dep
+ endif
+
+ if bcm_host_dep.found()
+ optional_deps += bcm_host_dep
+ endif
+
+ if egl_dep.found() and cc.has_header('libdrm/drm_fourcc.h', required : false)
+ optional_deps += gstallocators_dep
+ endif
+
+ gstopengl = library('gstopengl',
+ opengl_sources,
+ c_args : gst_plugins_bad_args + opengl_defines,
+ link_args : noseh_link_args,
+ include_directories : [configinc],
+ dependencies : [gstgl_dep, gstbadvideo_dep, gstvideo_dep,
+ gstbase_dep, gstcontroller_dep, libm] + optional_deps,
+ install : true,
+ install_dir : plugins_install_dir,
+ )
+endif