--- /dev/null
+From fc0da8b8d4c2e15258ffc55710e495c967c69c11 Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 20:42:32 +0200
+Subject: [PATCH kmscube 01/10] output more and improved information about EGL
+ and OpenGL ES 2.x
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ common.c | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/common.c b/common.c
+index 4bf3c5a..4d29eef 100644
+--- a/common.c
++++ b/common.c
+@@ -97,9 +97,12 @@ int init_egl(struct egl *egl, const struct gbm *gbm)
+ printf("Using display %p with EGL version %d.%d\n",
+ egl->display, major, minor);
+
+- printf("EGL Version \"%s\"\n", eglQueryString(egl->display, EGL_VERSION));
+- printf("EGL Vendor \"%s\"\n", eglQueryString(egl->display, EGL_VENDOR));
+- printf("EGL Extensions \"%s\"\n", eglQueryString(egl->display, EGL_EXTENSIONS));
++ printf("===================================\n");
++ printf("EGL information:\n");
++ printf(" version: \"%s\"\n", eglQueryString(egl->display, EGL_VERSION));
++ printf(" vendor: \"%s\"\n", eglQueryString(egl->display, EGL_VENDOR));
++ printf(" extensions: \"%s\"\n", eglQueryString(egl->display, EGL_EXTENSIONS));
++ printf("===================================\n");
+
+ if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+ printf("failed to bind api EGL_OPENGL_ES_API\n");
+@@ -128,7 +131,13 @@ int init_egl(struct egl *egl, const struct gbm *gbm)
+ /* connect the context to the surface */
+ eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context);
+
+- printf("GL Extensions: \"%s\"\n", glGetString(GL_EXTENSIONS));
++ printf("OpenGL ES 2.x information:\n");
++ printf(" version: \"%s\"\n", glGetString(GL_VERSION));
++ printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
++ printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR));
++ printf(" renderer: \"%s\"\n", glGetString(GL_RENDERER));
++ printf(" extensions: \"%s\"\n", glGetString(GL_EXTENSIONS));
++ printf("===================================\n");
+
+ return 0;
+ }
+--
+2.7.4
+
--- /dev/null
+From 6d6ff5895e2a86f2c4a7684da97085aa7c5b2b2a Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 23:05:22 +0200
+Subject: [PATCH kmscube 02/10] add "kmscube" GStreamer debug category
+
+Without this, the various GST_* log macros won't output anything.
+To enable, add "kmscube:<loglevel>" to the GST_DEBUG environment variable.
+Example: GST_DEBUG=kmscube:5
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 3 +++
+ kmscube.c | 4 +++-
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 1140213..22dc068 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -39,6 +39,9 @@
+ #include <gst/app/gstappsink.h>
+ #include <gst/video/gstvideometa.h>
+
++GST_DEBUG_CATEGORY_EXTERN(kmscube_debug);
++#define GST_CAT_DEFAULT kmscube_debug
++
+ struct decoder {
+ GMainLoop *loop;
+ GstElement *pipeline;
+diff --git a/kmscube.c b/kmscube.c
+index 54f4328..8e2d008 100644
+--- a/kmscube.c
++++ b/kmscube.c
+@@ -32,7 +32,8 @@
+ #include "drm-common.h"
+
+ #ifdef HAVE_GST
+-# include <gst/gst.h>
++#include <gst/gst.h>
++GST_DEBUG_CATEGORY(kmscube_debug);
+ #endif
+
+ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+@@ -77,6 +78,7 @@ int main(int argc, char *argv[])
+
+ #ifdef HAVE_GST
+ gst_init(&argc, &argv);
++ GST_DEBUG_CATEGORY_INIT(kmscube_debug, "kmscube", 0, "kmscube video pipeline");
+ #endif
+
+ while ((opt = getopt_long_only(argc, argv, shortopts, longopts, NULL)) != -1) {
+--
+2.7.4
+
--- /dev/null
+From 2cb0be7d7e52180a2403d9fa1f2d58b964878f9f Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 20:43:45 +0200
+Subject: [PATCH kmscube 03/10] gst-decoder.c: add support for YUY2 pixel
+ format
+
+This format is used for example by the i.MX6 CODA hardware video codec
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 22dc068..fd28201 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -91,6 +91,9 @@ pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+ case GST_VIDEO_FORMAT_NV12:
+ dec->format = DRM_FORMAT_NV12;
+ break;
++ case GST_VIDEO_FORMAT_YUY2:
++ dec->format = DRM_FORMAT_YUYV;
++ break;
+ default:
+ GST_ERROR("unknown format\n");
+ return GST_PAD_PROBE_OK;
+--
+2.7.4
+
--- /dev/null
+From 88a7a4427e34db3288a0b43b5e111f108caa3ce3 Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 21:44:25 +0200
+Subject: [PATCH kmscube 04/10] gst-decoder.c: look at the caps event instead
+ of the allocation query
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 13 ++++++-------
+ 1 file changed, 6 insertions(+), 7 deletions(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index fd28201..51304a2 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -63,24 +63,23 @@ static GstPadProbeReturn
+ pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+ {
+ struct decoder *dec = user_data;
+- GstQuery *query = GST_PAD_PROBE_INFO_QUERY(info);
+- gboolean need_pool;
++ GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
+ GstCaps *caps;
+
+ (void)pad;
+
+- if (GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION)
++ if (GST_EVENT_TYPE(event) != GST_EVENT_CAPS)
+ return GST_PAD_PROBE_OK;
+
+- gst_query_parse_allocation(query, &caps, &need_pool);
++ gst_event_parse_caps(event, &caps);
+
+ if (!caps) {
+- GST_ERROR("allocation query without caps");
++ GST_ERROR("caps event without caps");
+ return GST_PAD_PROBE_OK;
+ }
+
+ if (!gst_video_info_from_caps(&dec->info, caps)) {
+- GST_ERROR("allocation query with invalid caps");
++ GST_ERROR("caps event with invalid video caps");
+ return GST_PAD_PROBE_OK;
+ }
+
+@@ -156,7 +155,7 @@ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ g_object_set(G_OBJECT(dec->sink), "max-buffers", 2, NULL);
+
+ gst_pad_add_probe(gst_element_get_static_pad(dec->sink, "sink"),
+- GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
++ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ pad_probe, dec, NULL);
+
+ /* hack to make sure we get dmabuf's from v4l2video0dec.. */
+--
+2.7.4
+
--- /dev/null
+From 0e42a19481dac6a0d971eb7ae7503c3db0f349e0 Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 21:45:27 +0200
+Subject: [PATCH kmscube 05/10] gst-decoder.c: Use element factory name to
+ detect V4L2 video decoder
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 51304a2..05d73b7 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -115,15 +115,24 @@ gst_thread_func(void *args)
+ static void
+ element_added_cb(GstBin *bin, GstElement *element, gpointer user_data)
+ {
++ GstElementFactory *elem_factory;
++ gchar const *factory_name;
++
+ (void)user_data;
+ (void)bin;
+
+- printf("added: %s\n", GST_OBJECT_NAME(element));
++ elem_factory = gst_element_get_factory(element);
++ factory_name = gst_plugin_feature_get_name(elem_factory);
++
++ GST_DEBUG("added element %s (created with factory %s)", GST_OBJECT_NAME(element), factory_name);
+
+- // XXX is there a better way to do this, like match class name?
+- if (strstr(GST_OBJECT_NAME(element), "v4l2video0dec") == GST_OBJECT_NAME(element)) {
++ /* v4l2 video decoder factories are generated by the GStreamer v4l probe.
++ * The format is v4l2videoNdec, where N is an integer. So, check if the
++ * element's factory name fits this pattern. */
++ if (g_str_has_prefix(factory_name, "v4l2video") && g_str_has_suffix(factory_name, "dec")) {
+ /* yes, "capture" rather than "output" because v4l2 is bonkers */
+ gst_util_set_object_arg(G_OBJECT(element), "capture-io-mode", "dmabuf");
++ printf("found GStreamer V4L2 video decoder element with name \"%s\"\n", GST_OBJECT_NAME(element));
+ }
+ }
+
+--
+2.7.4
+
--- /dev/null
+From 42e67ce706bf02fa6643fa5822610091c466dbfb Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 21:49:52 +0200
+Subject: [PATCH kmscube 06/10] gst-decoder.c: add bus watch
+
+The bus watch is useful for logging state changes, printing out
+info/warning/error messages and handling common GStreamer activities like
+latency redistribution and state change requests (that are sent by
+elements since they are not allowed to directly change the state).
+
+State changes and error messages can also cause a dot graph of the
+pipeline to be generated if the GST_DEBUG_DUMP_DOT_DIR environment
+variable is set. See the GST_DEBUG_BIN_TO_DOT_FILE documentation for more.
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 103 insertions(+)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 05d73b7..d1cb18c 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -136,11 +136,108 @@ element_added_cb(GstBin *bin, GstElement *element, gpointer user_data)
+ }
+ }
+
++static gboolean
++bus_watch_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
++{
++ struct decoder *dec = (struct decoder *)user_data;
++
++ (void)bus;
++
++ switch (GST_MESSAGE_TYPE(msg)) {
++ case GST_MESSAGE_STATE_CHANGED: {
++ gchar *dotfilename;
++ GstState old_gst_state, cur_gst_state, pending_gst_state;
++
++ /* Only consider state change messages coming from
++ * the toplevel element. */
++ if (GST_MESSAGE_SRC(msg) != GST_OBJECT(dec->pipeline))
++ break;
++
++ gst_message_parse_state_changed(msg, &old_gst_state, &cur_gst_state, &pending_gst_state);
++
++ printf(
++ "GStreamer state change: old: %s current: %s pending: %s\n",
++ gst_element_state_get_name(old_gst_state),
++ gst_element_state_get_name(cur_gst_state),
++ gst_element_state_get_name(pending_gst_state)
++ );
++
++ dotfilename = g_strdup_printf(
++ "statechange__old-%s__cur-%s__pending-%s",
++ gst_element_state_get_name(old_gst_state),
++ gst_element_state_get_name(cur_gst_state),
++ gst_element_state_get_name(pending_gst_state)
++ );
++ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(dec->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dotfilename);
++ g_free(dotfilename);
++
++ break;
++ }
++ case GST_MESSAGE_REQUEST_STATE: {
++ GstState requested_state;
++ gst_message_parse_request_state(msg, &requested_state);
++ printf(
++ "state change to %s was requested by %s\n",
++ gst_element_state_get_name(requested_state),
++ GST_MESSAGE_SRC_NAME(msg)
++ );
++ gst_element_set_state(GST_ELEMENT(dec->pipeline), requested_state);
++ break;
++ }
++ case GST_MESSAGE_LATENCY: {
++ printf("redistributing latency\n");
++ gst_bin_recalculate_latency(GST_BIN(dec->pipeline));
++ break;
++ }
++ case GST_MESSAGE_INFO:
++ case GST_MESSAGE_WARNING:
++ case GST_MESSAGE_ERROR: {
++ GError *error = NULL;
++ gchar *debug_info = NULL;
++ gchar const *prefix;
++
++ switch (GST_MESSAGE_TYPE(msg)) {
++ case GST_MESSAGE_INFO:
++ gst_message_parse_info(msg, &error, &debug_info);
++ prefix = "INFO";
++ break;
++ case GST_MESSAGE_WARNING:
++ gst_message_parse_warning(msg, &error, &debug_info);
++ prefix = "WARNING";
++ break;
++ case GST_MESSAGE_ERROR:
++ gst_message_parse_error(msg, &error, &debug_info);
++ prefix = "ERROR";
++ break;
++ default:
++ g_assert_not_reached();
++ }
++ printf("GStreamer %s: %s; debug info: %s", prefix, error->message, debug_info);
++
++ g_clear_error(&error);
++ g_free(debug_info);
++
++ if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
++ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(dec->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "error");
++ }
++
++ // TODO: stop mainloop in case of an error
++
++ break;
++ }
++ default:
++ break;
++ }
++
++ return TRUE;
++}
++
+ struct decoder *
+ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ {
+ struct decoder *dec;
+ GstElement *src, *decodebin;
++ GstBus *bus;
+
+ dec = calloc(1, sizeof(*dec));
+ dec->loop = g_main_loop_new(NULL, FALSE);
+@@ -171,6 +268,12 @@ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ decodebin = gst_bin_get_by_name(GST_BIN(dec->pipeline), "decode");
+ g_signal_connect(decodebin, "element-added", G_CALLBACK(element_added_cb), dec);
+
++ /* add bus to be able to receive error message, handle latency
++ * requests, produce pipeline dumps, etc. */
++ bus = gst_pipeline_get_bus(GST_PIPELINE(dec->pipeline));
++ gst_bus_add_watch(bus, bus_watch_cb, dec);
++ gst_object_unref(GST_OBJECT(bus));
++
+ /* let 'er rip! */
+ gst_element_set_state(dec->pipeline, GST_STATE_PLAYING);
+
+--
+2.7.4
+
--- /dev/null
+From eebd58fde0a5142afe0a871a3aa4e255faac9c18 Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 23:09:29 +0200
+Subject: [PATCH kmscube 07/10] gst-decoder.c: minor cleanup
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index d1cb18c..768aa1b 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -42,6 +42,8 @@
+ GST_DEBUG_CATEGORY_EXTERN(kmscube_debug);
+ #define GST_CAT_DEFAULT kmscube_debug
+
++#define MAX_NUM_PLANES 3
++
+ struct decoder {
+ GMainLoop *loop;
+ GstElement *pipeline;
+@@ -83,7 +85,7 @@ pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+ return GST_PAD_PROBE_OK;
+ }
+
+- switch (dec->info.finfo->format) {
++ switch (GST_VIDEO_INFO_FORMAT(&(dec->info))) {
+ case GST_VIDEO_FORMAT_I420:
+ dec->format = DRM_FORMAT_YUV420;
+ break;
+@@ -264,7 +266,7 @@ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ pad_probe, dec, NULL);
+
+- /* hack to make sure we get dmabuf's from v4l2video0dec.. */
++ /* callback needed to make sure we get dmabuf's from v4l2videoNdec.. */
+ decodebin = gst_bin_get_by_name(GST_BIN(dec->pipeline), "decode");
+ g_signal_connect(decodebin, "element-added", G_CALLBACK(element_added_cb), dec);
+
+@@ -322,7 +324,7 @@ buf_to_fd(const struct gbm *gbm, int size, void *ptr)
+ static EGLImage
+ buffer_to_image(struct decoder *dec, GstBuffer *buf)
+ {
+- struct { int fd, offset, stride; } planes[3];
++ struct { int fd, offset, stride; } planes[MAX_NUM_PLANES];
+ GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
+ EGLImage image;
+ unsigned nmems = gst_buffer_n_memory(buf);
+@@ -412,6 +414,7 @@ buffer_to_image(struct decoder *dec, GstBuffer *buf)
+ EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+ }
+
++ /* Cleanup */
+ for (unsigned i = 0; i < nmems; i++)
+ close(planes[i].fd);
+
+@@ -426,8 +429,10 @@ video_frame(struct decoder *dec)
+ EGLImage frame = NULL;
+
+ samp = gst_app_sink_pull_sample(GST_APP_SINK(dec->sink));
+- if (!samp)
++ if (!samp) {
++ GST_DEBUG("got no appsink sample");
+ return NULL;
++ }
+
+ buf = gst_sample_get_buffer(samp);
+
+--
+2.7.4
+
--- /dev/null
+From a5b27790efb40775209d0e079d4bf021ef1c953c Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 22:55:58 +0200
+Subject: [PATCH kmscube 08/10] gst-decoder.c: use a custom appsink subclass to
+ make sure videometa exists
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ Makefile.am | 2 +-
+ gst-decoder.c | 20 ++++++++-
+ gst-video-appsink.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ gst-video-appsink.h | 31 +++++++++++++
+ kmscube.c | 2 +
+ 5 files changed, 178 insertions(+), 3 deletions(-)
+ create mode 100644 gst-video-appsink.c
+ create mode 100644 gst-video-appsink.h
+
+diff --git a/Makefile.am b/Makefile.am
+index a36087d..0407e89 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -57,5 +57,5 @@ kmscube_SOURCES = \
+ if ENABLE_GST
+ kmscube_LDADD += $(GST_LIBS)
+ kmscube_CFLAGS += $(GST_CFLAGS)
+-kmscube_SOURCES += cube-video.c gst-decoder.c
++kmscube_SOURCES += cube-video.c gst-decoder.c gst-video-appsink.c
+ endif
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 768aa1b..188fe4b 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -246,9 +246,25 @@ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ dec->gbm = gbm;
+ dec->egl = egl;
+
+- /* Setup pipeline: */
++ /* Setup pipeline. Note that we use video_appsink here, not appsink.
++ * video_appsink is an appsink subclass that adds the videometa to
++ * the allocation query's list of metas supported by the sink.
++ *
++ * In some cases, sinks that do not declare videometa as supported
++ * can trigger unexpected behavior when used with video decoders.
++ * For example, with the v4l2videoNdec decoder, if the sink did not
++ * declare videometa as supported, and if the decoder detects that
++ * video frames have padding rows/columns (for example, a 1920x1088
++ * frame whose last 8 rows are padding rows), then it will perform
++ * an internal CPU-based copy that does not have these padding pixels.
++ *
++ * To avoid such CPU-based copies, make sure the sink declares
++ * videometa as supported. This has the side benefit that the code
++ * inside buffer_to_image() can also make use of the videmeta
++ * information.
++ */
+ static const char *pipeline =
+- "filesrc name=\"src\" ! decodebin name=\"decode\" ! video/x-raw ! appsink sync=false name=\"sink\"";
++ "filesrc name=\"src\" ! decodebin name=\"decode\" ! video/x-raw ! video_appsink name=\"sink\"";
+ dec->pipeline = gst_parse_launch(pipeline, NULL);
+
+ dec->sink = gst_bin_get_by_name(GST_BIN(dec->pipeline), "sink");
+diff --git a/gst-video-appsink.c b/gst-video-appsink.c
+new file mode 100644
+index 0000000..2c5c15b
+--- /dev/null
++++ b/gst-video-appsink.c
+@@ -0,0 +1,126 @@
++/*
++ * Copyright (c) 2017 Carlos Rafael Giani <dv@pseudoterminal.org>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sub license,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the
++ * next paragraph) shall be included in all copies or substantial portions
++ * of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#include <gst/gst.h>
++#include <gst/app/app.h>
++#include <gst/video/video.h>
++#include "gst-video-appsink.h"
++
++
++typedef struct _GstVideoAppsink GstVideoAppsink;
++typedef struct _GstVideoAppsinkClass GstVideoAppsinkClass;
++
++
++#define GST_TYPE_VIDEO_APPSINK (gst_video_appsink_get_type())
++#define GST_VIDEO_APPSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_VIDEO_APPSINK,GstVideoAppsink))
++#define GST_VIDEO_APPSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_VIDEO_APPSINK, GstVideoAppsinkClass))
++#define GST_IS_VIDEO_APPSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_VIDEO_APPSINK))
++#define GST_IS_VIDEO_APPSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_VIDEO_APPSINK))
++
++
++struct _GstVideoAppsink
++{
++ GstAppSink parent;
++};
++
++
++struct _GstVideoAppsinkClass
++{
++ GstAppSinkClass parent_class;
++};
++
++
++GType gst_video_appsink_get_type(void);
++
++
++static gboolean gst_video_appsink_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query);
++
++
++G_DEFINE_TYPE(GstVideoAppsink, gst_video_appsink, GST_TYPE_APP_SINK);
++
++
++static void
++gst_video_appsink_class_init(GstVideoAppsinkClass *klass)
++{
++ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
++ GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS(klass);
++
++ base_sink_class->propose_allocation =
++ GST_DEBUG_FUNCPTR(gst_video_appsink_sink_propose_allocation);
++
++ gst_element_class_set_static_metadata(
++ element_class,
++ "video appsink",
++ "Video/Sink",
++ "Appsink subclass for video streams (adds video meta)",
++ "Carlos Rafael Giani <dv@pseudoterminal.org>"
++ );
++}
++
++
++static void
++gst_video_appsink_init(GstVideoAppsink *video_appsink)
++{
++ /* QoS and max-lateness lines taken from gstvideosink.c */
++ gst_base_sink_set_max_lateness(GST_BASE_SINK(video_appsink), 20 * GST_MSECOND);
++ gst_base_sink_set_qos_enabled(GST_BASE_SINK(video_appsink), TRUE);
++}
++
++
++static gboolean
++gst_video_appsink_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query)
++{
++ (void)bsink;
++
++ gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);
++
++ return TRUE;
++}
++
++
++
++
++static gboolean
++init_internal_video_appsink_plugin(GstPlugin *plugin)
++{
++ gst_element_register(plugin, "video_appsink", GST_RANK_NONE, gst_video_appsink_get_type());
++ return TRUE;
++}
++
++
++void
++register_gst_video_appsink(void)
++{
++ gst_plugin_register_static(
++ GST_VERSION_MAJOR,
++ GST_VERSION_MINOR,
++ "internal video appsink plugin",
++ "internal video appsink plugin",
++ init_internal_video_appsink_plugin,
++ "1.0",
++ "BSD",
++ "kmscube",
++ "kmscube",
++ "kmscube"
++ );
++}
+diff --git a/gst-video-appsink.h b/gst-video-appsink.h
+new file mode 100644
+index 0000000..d47484a
+--- /dev/null
++++ b/gst-video-appsink.h
+@@ -0,0 +1,31 @@
++/*
++ * Copyright (c) 2017 Carlos Rafael Giani <dv@pseudoterminal.org>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sub license,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the
++ * next paragraph) shall be included in all copies or substantial portions
++ * of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef _GST_VIDEO_APPSINK_H
++#define _GST_VIDEO_APPSINK_H
++
++
++void register_gst_video_appsink(void);
++
++
++#endif /* _GST_VIDEO_APPSINK_H */
+diff --git a/kmscube.c b/kmscube.c
+index 8e2d008..61509ef 100644
+--- a/kmscube.c
++++ b/kmscube.c
+@@ -33,6 +33,7 @@
+
+ #ifdef HAVE_GST
+ #include <gst/gst.h>
++#include "gst-video-appsink.h"
+ GST_DEBUG_CATEGORY(kmscube_debug);
+ #endif
+
+@@ -79,6 +80,7 @@ int main(int argc, char *argv[])
+ #ifdef HAVE_GST
+ gst_init(&argc, &argv);
+ GST_DEBUG_CATEGORY_INIT(kmscube_debug, "kmscube", 0, "kmscube video pipeline");
++ register_gst_video_appsink();
+ #endif
+
+ while ((opt = getopt_long_only(argc, argv, shortopts, longopts, NULL)) != -1) {
+--
+2.7.4
+
--- /dev/null
+From 82b273fae95ef5c501eb9d81d62ce8d1db49eede Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 22:57:42 +0200
+Subject: [PATCH kmscube 09/10] gst-decoder.c: Improve synchronicity of video
+ output
+
+sync=false causes a sink to output data as fast as it can. This explains
+why without max-buffers=2 the pipeline used to use up 100's of MB of
+buffers.
+
+Instead, set sync to true, and enable frame dropping, to make sure the
+video playback speed remains intact.
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 188fe4b..67efbfd 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -273,10 +273,19 @@ video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+ g_object_set(G_OBJECT(src), "location", filename, NULL);
+ gst_object_unref(src);
+
+- /* if we don't limit max-buffers then we can let the decoder outrun
+- * vsync and quickly chew up 100's of MB of buffers:
++ /* If frames cannot be displayed on time, drop them. This makes
++ * sure playback speed remains intact even if the EGL display
++ * intervals don't match the pipeline's decoding intervals.
++ * sync=TRUE makes sure the sink blocks the pipeline until the
++ * video frame is scheduled to be output (based on its PTS).
++ * This also avoids excessive CPU usage, since the blocking
++ * wait puts the streaming thread to sleep until the video
++ * frame is due.
++ * max-buffers=1 makes sure only one frame is queued (more are
++ * not necessary).
+ */
+- g_object_set(G_OBJECT(dec->sink), "max-buffers", 2, NULL);
++ g_object_set(G_OBJECT(dec->sink), "drop", (gboolean)TRUE, "max-buffers", 1,
++ "sync", (gboolean)TRUE, NULL);
+
+ gst_pad_add_probe(gst_element_get_static_pad(dec->sink, "sink"),
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+--
+2.7.4
+
--- /dev/null
+From f8c5cf72af8add2e0db6d3f73443f1adcff28e4a Mon Sep 17 00:00:00 2001
+From: Carlos Rafael Giani <dv@pseudoterminal.org>
+Date: Fri, 7 Apr 2017 22:58:11 +0200
+Subject: [PATCH kmscube 10/10] gst-decoder.c: improve buffer_to_image()
+ function
+
+* Make EGL image attribute specification code more generic, and not
+ specific to certain pixel formats, implicitely gaining support for YUY2
+* Better handling of gstbuffers with multiple memory blocks
+* Print out more information about the stream
+* Use the GST_VIDEO_INFO_* macros instead of directly accessing the
+ GstVideoInfo fields; this is what the GStreamer documentation recommends
+
+Upstream-Status: Submitted [mesa-dev mailing list]
+
+Signed-off-by: Carlos Rafael Giani <dv@pseudoterminal.org>
+---
+ gst-decoder.c | 196 +++++++++++++++++++++++++++++++++++-----------------------
+ 1 file changed, 119 insertions(+), 77 deletions(-)
+
+diff --git a/gst-decoder.c b/gst-decoder.c
+index 67efbfd..cc5c3b2 100644
+--- a/gst-decoder.c
++++ b/gst-decoder.c
+@@ -1,5 +1,6 @@
+ /*
+ * Copyright (c) 2017 Rob Clark <rclark@redhat.com>
++ * Copyright (c) 2017 Carlos Rafael Giani <dv@pseudoterminal.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+@@ -44,6 +45,12 @@ GST_DEBUG_CATEGORY_EXTERN(kmscube_debug);
+
+ #define MAX_NUM_PLANES 3
+
++inline static const char *
++yesno(int yes)
++{
++ return yes ? "yes" : "no";
++}
++
+ struct decoder {
+ GMainLoop *loop;
+ GstElement *pipeline;
+@@ -100,9 +107,6 @@ pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+ return GST_PAD_PROBE_OK;
+ }
+
+- GST_DEBUG("got: %ux%u@%4.4s\n", dec->info.width, dec->info.height,
+- (char *)&dec->format);
+-
+ return GST_PAD_PROBE_OK;
+ }
+
+@@ -352,89 +356,127 @@ buffer_to_image(struct decoder *dec, GstBuffer *buf)
+ struct { int fd, offset, stride; } planes[MAX_NUM_PLANES];
+ GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
+ EGLImage image;
+- unsigned nmems = gst_buffer_n_memory(buf);
+- unsigned nplanes = (dec->format == DRM_FORMAT_YUV420) ? 3 : 2;
+- unsigned i;
+-
+- if (nmems == nplanes) {
+- // XXX TODO..
+- } else if (nmems == 1) {
+- GstMemory *mem = gst_buffer_peek_memory(buf, 0);
+- int fd;
+-
+- if (dec->frame == 0) {
+- printf("%s zero-copy\n", gst_is_dmabuf_memory(mem) ? "using" : "not");
++ guint nmems = gst_buffer_n_memory(buf);
++ guint nplanes = GST_VIDEO_INFO_N_PLANES(&(dec->info));
++ guint i;
++ guint width, height;
++ gboolean is_dmabuf_mem;
++ GstMemory *mem;
++ int dmabuf_fd = -1;
++
++ static const EGLint egl_dmabuf_plane_fd_attr[MAX_NUM_PLANES] = {
++ EGL_DMA_BUF_PLANE0_FD_EXT,
++ EGL_DMA_BUF_PLANE1_FD_EXT,
++ EGL_DMA_BUF_PLANE2_FD_EXT,
++ };
++ static const EGLint egl_dmabuf_plane_offset_attr[MAX_NUM_PLANES] = {
++ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
++ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
++ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
++ };
++ static const EGLint egl_dmabuf_plane_pitch_attr[MAX_NUM_PLANES] = {
++ EGL_DMA_BUF_PLANE0_PITCH_EXT,
++ EGL_DMA_BUF_PLANE1_PITCH_EXT,
++ EGL_DMA_BUF_PLANE2_PITCH_EXT,
++ };
++
++ /* Query gst_is_dmabuf_memory() here, since the gstmemory
++ * block might get merged below by gst_buffer_map(), meaning
++ * that the mem pointer would become invalid */
++ mem = gst_buffer_peek_memory(buf, 0);
++ is_dmabuf_mem = gst_is_dmabuf_memory(mem);
++
++ if (nmems > 1) {
++ if (is_dmabuf_mem) {
++ /* this case currently is not defined */
++
++ GST_FIXME("gstbuffers with multiple memory blocks and DMABUF "
++ "memory currently are not supported");
++ return EGL_NO_IMAGE_KHR;
+ }
+
+- if (gst_is_dmabuf_memory(mem)) {
+- fd = dup(gst_dmabuf_memory_get_fd(mem));
+- } else {
+- GstMapInfo info;
+- gst_memory_map(mem, &info, GST_MAP_READ);
+- fd = buf_to_fd(dec->gbm, info.size, info.data);
+- gst_memory_unmap(mem, &info);
+- }
++ /* if this is not DMABUF memory, then the gst_buffer_map()
++ * call below will automatically merge the memory blocks
++ */
++ }
+
+- // XXX why don't we get meta??
+- if (meta) {
+- for (i = 0; i < nplanes; i++) {
+- planes[i].fd = fd;
+- planes[i].offset = meta->offset[i];
+- planes[i].stride = meta->stride[i];
+- }
+- } else {
+- int offset = 0, stride = dec->info.width, height = dec->info.height;
+-
+- for (i = 0; i < nplanes; i++) {
+-
+- if (i == 1) {
+- height /= 2;
+- if (nplanes == 3)
+- stride /= 2;
+- }
+-
+- planes[i].fd = fd;
+- planes[i].offset = offset;
+- planes[i].stride = stride;
+-
+- offset += stride * height;
+- }
+- }
++ if (is_dmabuf_mem) {
++ dmabuf_fd = dup(gst_dmabuf_memory_get_fd(mem));
++ } else {
++ GstMapInfo map_info;
++ gst_buffer_map(buf, &map_info, GST_MAP_READ);
++ dmabuf_fd = buf_to_fd(dec->gbm, map_info.size, map_info.data);
++ gst_buffer_unmap(buf, &map_info);
+ }
+
+- if (dec->format == DRM_FORMAT_NV12) {
+- const EGLint attr[] = {
+- EGL_WIDTH, dec->info.width,
+- EGL_HEIGHT, dec->info.height,
+- EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+- EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+- EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+- EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+- EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+- EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+- EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+- EGL_NONE
+- };
++ if (dmabuf_fd < 0) {
++ GST_ERROR("could not obtain DMABUF FD");
++ return EGL_NO_IMAGE_KHR;
++ }
+
+- image = dec->egl->eglCreateImageKHR(dec->egl->display, EGL_NO_CONTEXT,
+- EGL_LINUX_DMA_BUF_EXT, NULL, attr);
++ /* Usually, a videometa should be present, since by using the internal kmscube
++ * video_appsink element instead of the regular appsink, it is guaranteed that
++ * video meta support is declared in the video_appsink's allocation query.
++ * However, this assumes that upstream elements actually look at the allocation
++ * query's contents properly, or that they even send a query at all. If this
++ * is not the case, then upstream might decide to push frames without adding
++ * a meta. It can happen, and in this case, look at the video info data as
++ * a fallback (it is computed out of the input caps).
++ */
++ if (meta) {
++ for (i = 0; i < nplanes; i++) {
++ planes[i].fd = dmabuf_fd;
++ planes[i].offset = meta->offset[i];
++ planes[i].stride = meta->stride[i];
++ }
+ } else {
+- const EGLint attr[] = {
+- EGL_WIDTH, dec->info.width,
+- EGL_HEIGHT, dec->info.height,
+- EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+- EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+- EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+- EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+- EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+- EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+- EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+- EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd,
+- EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset,
+- EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].stride,
+- EGL_NONE
++ for (i = 0; i < nplanes; i++) {
++ planes[i].fd = dmabuf_fd;
++ planes[i].offset = GST_VIDEO_INFO_PLANE_OFFSET(&(dec->info), i);
++ planes[i].stride = GST_VIDEO_INFO_PLANE_STRIDE(&(dec->info), i);
++ }
++ }
++
++ width = GST_VIDEO_INFO_WIDTH(&(dec->info));
++ height = GST_VIDEO_INFO_HEIGHT(&(dec->info));
++
++ /* output some information at the beginning (= when the first frame is handled) */
++ if (dec->frame == 0) {
++ GstVideoFormat pixfmt;
++ const char *pixfmt_str;
++
++ pixfmt = GST_VIDEO_INFO_FORMAT(&(dec->info));
++ pixfmt_str = gst_video_format_to_string(pixfmt);
++
++ printf("===================================\n");
++ printf("GStreamer video stream information:\n");
++ printf(" size: %u x %u pixel\n", width, height);
++ printf(" pixel format: %s number of planes: %u\n", pixfmt_str, nplanes);
++ printf(" can use zero-copy: %s\n", yesno(is_dmabuf_mem));
++ printf(" video meta found: %s\n", yesno(meta != NULL));
++ printf("===================================\n");
++ }
++
++ {
++ /* Initialize the first 6 attributes with values that are
++ * plane invariant (width, height, format) */
++ EGLint attr[6 + 6*(MAX_NUM_PLANES) + 1] = {
++ EGL_WIDTH, width,
++ EGL_HEIGHT, height,
++ EGL_LINUX_DRM_FOURCC_EXT, dec->format
+ };
+
++ for (i = 0; i < nplanes; i++) {
++ attr[6 + 6*i + 0] = egl_dmabuf_plane_fd_attr[i];
++ attr[6 + 6*i + 1] = planes[i].fd;
++ attr[6 + 6*i + 2] = egl_dmabuf_plane_offset_attr[i];
++ attr[6 + 6*i + 3] = planes[i].offset;
++ attr[6 + 6*i + 4] = egl_dmabuf_plane_pitch_attr[i];
++ attr[6 + 6*i + 5] = planes[i].stride;
++ }
++
++ attr[6 + 6*nplanes] = EGL_NONE;
++
+ image = dec->egl->eglCreateImageKHR(dec->egl->display, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+ }
+--
+2.7.4
+
--- /dev/null
+On i.MX devices, etnaviv is located at /dev/dri/card1 . For convenience, make
+this the default instead of card0.
+
+Upstream-Status: Inappropriate [i.MX specific]
+
+diff --git a/kmscube.c b/kmscube.c
+index 61509ef..9cc05d7 100644
+--- a/kmscube.c
++++ b/kmscube.c
+@@ -71,7 +71,7 @@ static void usage(const char *name)
+
+ int main(int argc, char *argv[])
+ {
+- const char *device = "/dev/dri/card0";
++ const char *device = "/dev/dri/card1";
+ const char *video = NULL;
+ enum mode mode = SMOOTH;
+ int atomic = 0;
--- /dev/null
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+SRC_URI_append = " file://0001-output-more-and-improved-information-about-EGL-and-O.patch \
+ file://0002-add-kmscube-GStreamer-debug-category.patch \
+ file://0003-gst-decoder.c-add-support-for-YUY2-pixel-format.patch \
+ file://0004-gst-decoder.c-look-at-the-caps-event-instead-of-the-.patch \
+ file://0005-gst-decoder.c-Use-element-factory-name-to-detect-V4L.patch \
+ file://0006-gst-decoder.c-add-bus-watch.patch \
+ file://0007-gst-decoder.c-minor-cleanup.patch \
+ file://0008-gst-decoder.c-use-a-custom-appsink-subclass-to-make-.patch \
+ file://0009-gst-decoder.c-Improve-synchronicity-of-video-output.patch \
+ file://0010-gst-decoder.c-improve-buffer_to_image-function.patch \
+ file://use-dri-card1-by-default.patch \
+"