add new plugin for Qt 6 rendering inside a QML scene
authorMatthew Waters <matthew@centricular.com>
Thu, 6 Oct 2022 06:08:54 +0000 (17:08 +1100)
committerMatthew Waters <matthew@centricular.com>
Thu, 24 Nov 2022 05:11:04 +0000 (16:11 +1100)
- Based heavily on the existing Qt5 integration however:
  - The sharing of OpenGL resources is slightly different
  - The integration with the scengraph is a bit different
- Wayland, XCB and KMS have been smoke tested.  Android, MacOS/iOS,
  Windows may or may not work.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3281>

22 files changed:
subprojects/gst-plugins-good/ext/meson.build
subprojects/gst-plugins-good/ext/qt6/gstplugin.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.h [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.h [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqt6element.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqt6elements.h [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqt6gl.h [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.h [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/meson.build [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/qt6glitem.cc [new file with mode: 0644]
subprojects/gst-plugins-good/ext/qt6/qt6glitem.h [new file with mode: 0644]
subprojects/gst-plugins-good/meson_options.txt
subprojects/gst-plugins-good/tests/examples/meson.build
subprojects/gst-plugins-good/tests/examples/qt6/meson.build [new file with mode: 0644]
subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.cpp [new file with mode: 0644]
subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.qml [new file with mode: 0644]
subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/meson.build [new file with mode: 0644]
subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/play.pro [new file with mode: 0644]
subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/qmlsink.qrc [new file with mode: 0644]

index 9bf2240d8231fb5aada41a723bb8e465a709c652..00ce467f84e9545cdf72a5cd4ce5bf7b6f670778 100644 (file)
@@ -13,6 +13,7 @@ subdir('libpng')
 subdir('mpg123')
 subdir('raw1394')
 subdir('qt')
+subdir('qt6')
 subdir('pulse')
 subdir('shout2')
 subdir('soup')
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstplugin.cc b/subprojects/gst-plugins-good/ext/qt6/gstplugin.cc
new file mode 100644 (file)
index 0000000..3ab7925
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 "gstqt6elements.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = FALSE;
+
+  ret |= GST_ELEMENT_REGISTER (qml6glsink, plugin);
+
+  return ret;
+}
+
+#ifndef GST_PACKAGE_NAME
+#define GST_PACKAGE_NAME   "GStreamer Bad Plug-ins (qmake)"
+#define GST_PACKAGE_ORIGIN "Unknown package origin"
+#define GST_LICENSE        "LGPL"
+#define PACKAGE            "gst-plugins-bad (qmake)"
+#define PACKAGE_VERSION    "1.21.0.1"
+#endif
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    qml6,
+    "Qt6 Qml plugin",
+    plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+    GST_PACKAGE_ORIGIN)
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.cc b/subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.cc
new file mode 100644 (file)
index 0000000..cff6277
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * 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:gstqml6glsink
+ *
+ * qml6glsink provides a way to render a video stream as a Qml object inside
+ * the Qml scene graph.  This is achieved by providing the incoming OpenGL
+ * textures to Qt as a scene graph object.
+ *
+ * qml6glsink will attempt to retrieve the windowing system display connection
+ * that Qt is using (#GstGLDisplay).  This may be different to any already
+ * existing window system display connection already in use in the pipeline for
+ * a number of reasons.  A couple of examples of this are:
+ *
+ * 1. Adding qml6glsink to an already running pipeline
+ * 2. Not having any qml6glsink element start up before any
+ *    other OpenGL-based element in the pipeline.
+ *
+ * If one of these scenarios occurs, then there will be multiple OpenGL contexts
+ * in use in the pipeline.  This means that either the pipeline will fail to
+ * start up correctly, a downstream element may reject buffers, or a complete
+ * GPU->System memory->GPU transfer is performed for every buffer.
+ *
+ * The requirement to avoid this is that all elements share the same
+ * #GstGLDisplay object and as Qt cannot currently share an existing window
+ * system display connection, GStreamer must use the window system display
+ * connection provided by Qt.  This window system display connection can be
+ * retrieved by either a qmlglsink element or a qmlgloverlay element. The
+ * recommended usage is to have either element (qmlglsink or qmlgloverlay)
+ * be the first to propagate the #GstGLDisplay for the entire pipeline to use by
+ * setting either element to the READY element state before any other OpenGL
+ * element in the pipeline.
+ *
+ * In a dynamically adding qmlglsink (or qmlgloverlay) to a pipeline case,
+ * there are some considerations for ensuring that the window system display
+ * and OpenGL contexts are compatible with Qt.  When the qmlgloverlay (or
+ * qmlglsink) element is added and brought up to READY, it will propagate it's
+ * own #GstGLDisplay using the #GstContext mechanism regardless of any existing
+ * #GstGLDisplay used by the pipeline previously.  In order for the new
+ * #GstGLDisplay to be used, the application must then set the provided
+ * #GstGLDisplay containing #GstContext on the pipeline.  This may effectively
+ * cause each OpenGL element to replace the window system display and also the
+ * OpenGL context it is using.  As such this process may take a significant
+ * amount of time and resources as objects are recreated in the new OpenGL
+ * context.
+ *
+ * All instances of qmlglsink and qmlgloverlay will return the exact same
+ * #GstGLDisplay object while the pipeline is running regardless of whether
+ * any qmlglsink or qmlgloverlay elements are added or removed from the
+ * pipeline.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstqt6elements.h"
+#include "gstqml6glsink.h"
+#include <QtGui/QGuiApplication>
+
+#include <gst/gl/gstglfuncs.h>
+
+#define GST_CAT_DEFAULT gst_debug_qml6_gl_sink
+GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
+
+static void gst_qml6_gl_sink_navigation_interface_init (GstNavigationInterface * iface);
+static void gst_qml6_gl_sink_finalize (GObject * object);
+static void gst_qml6_gl_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * param_spec);
+static void gst_qml6_gl_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * param_spec);
+
+static gboolean gst_qml6_gl_sink_stop (GstBaseSink * bsink);
+
+static gboolean gst_qml6_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
+
+static GstStateChangeReturn
+gst_qml6_gl_sink_change_state (GstElement * element, GstStateChange transition);
+
+static void gst_qml6_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+    GstClockTime * start, GstClockTime * end);
+static gboolean gst_qml6_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
+static GstFlowReturn gst_qml6_gl_sink_show_frame (GstVideoSink * bsink,
+    GstBuffer * buf);
+static gboolean gst_qml6_gl_sink_propose_allocation (GstBaseSink * bsink,
+    GstQuery * query);
+
+static GstStaticPadTemplate gst_qt_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) { RGB, RGBA }, "
+    "width = " GST_VIDEO_SIZE_RANGE ", "
+    "height = " GST_VIDEO_SIZE_RANGE ", "
+    "framerate = " GST_VIDEO_FPS_RANGE ", "
+    "texture-target = (string) 2D"));
+
+#define DEFAULT_FORCE_ASPECT_RATIO  TRUE
+#define DEFAULT_PAR_N               0
+#define DEFAULT_PAR_D               1
+
+enum
+{
+  ARG_0,
+  PROP_WIDGET,
+  PROP_FORCE_ASPECT_RATIO,
+  PROP_PIXEL_ASPECT_RATIO,
+};
+
+enum
+{
+  SIGNAL_0,
+  LAST_SIGNAL
+};
+
+#define gst_qml6_gl_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstQml6GLSink, gst_qml6_gl_sink,
+    GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT,
+        "qtsink", 0, "Qt Video Sink");
+    G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+        gst_qml6_gl_sink_navigation_interface_init));
+GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qml6glsink, "qml6glsink",
+    GST_RANK_NONE, GST_TYPE_QML6_GL_SINK, qt6_element_init (plugin));
+
+static void
+gst_qml6_gl_sink_class_init (GstQml6GLSinkClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBaseSinkClass *gstbasesink_class;
+  GstVideoSinkClass *gstvideosink_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gstbasesink_class = (GstBaseSinkClass *) klass;
+  gstvideosink_class = (GstVideoSinkClass *) klass;
+
+  gobject_class->set_property = gst_qml6_gl_sink_set_property;
+  gobject_class->get_property = gst_qml6_gl_sink_get_property;
+
+  gst_element_class_set_metadata (gstelement_class, "Qt6 Video Sink",
+      "Sink/Video", "A video sink that renders to a QQuickItem for Qt6",
+      "Matthew Waters <matthew@centricular.com>");
+
+  g_object_class_install_property (gobject_class, PROP_WIDGET,
+      g_param_spec_pointer ("widget", "QQuickItem",
+          "The QQuickItem to place in the object hierarchy",
+          (GParamFlags) (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,
+          (GParamFlags) (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", DEFAULT_PAR_N, DEFAULT_PAR_D,
+          G_MAXINT, 1, 1, 1,
+          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+  gst_element_class_add_static_pad_template (gstelement_class, &gst_qt_sink_template);
+
+  gobject_class->finalize = gst_qml6_gl_sink_finalize;
+
+  gstelement_class->change_state = gst_qml6_gl_sink_change_state;
+  gstbasesink_class->query = gst_qml6_gl_sink_query;
+  gstbasesink_class->set_caps = gst_qml6_gl_sink_set_caps;
+  gstbasesink_class->get_times = gst_qml6_gl_sink_get_times;
+  gstbasesink_class->propose_allocation = gst_qml6_gl_sink_propose_allocation;
+  gstbasesink_class->stop = gst_qml6_gl_sink_stop;
+
+  gstvideosink_class->show_frame = gst_qml6_gl_sink_show_frame;
+}
+
+static void
+gst_qml6_gl_sink_init (GstQml6GLSink * qt_sink)
+{
+  qt_sink->widget = QSharedPointer<Qt6GLVideoItemInterface>();
+  if (qt_sink->widget)
+    qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+}
+
+static void
+gst_qml6_gl_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+  switch (prop_id) {
+    case PROP_WIDGET: {
+      Qt6GLVideoItem *qt_item = static_cast<Qt6GLVideoItem *> (g_value_get_pointer (value));
+      if (qt_item) {
+        qt_sink->widget = qt_item->getInterface();
+        if (qt_sink->widget) {
+          qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+        }
+      } else {
+        qt_sink->widget.clear();
+      }
+      break;
+    }
+    case PROP_FORCE_ASPECT_RATIO:
+      g_return_if_fail (qt_sink->widget);
+      qt_sink->widget->setForceAspectRatio (g_value_get_boolean (value));
+      break;
+    case PROP_PIXEL_ASPECT_RATIO:
+      g_return_if_fail (qt_sink->widget);
+      qt_sink->widget->setDAR (gst_value_get_fraction_numerator (value),
+          gst_value_get_fraction_denominator (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+_reset (GstQml6GLSink * qt_sink)
+{
+  if (qt_sink->display) {
+    gst_object_unref (qt_sink->display);
+    qt_sink->display = NULL;
+  }
+
+  if (qt_sink->context) {
+    gst_object_unref (qt_sink->context);
+    qt_sink->context = NULL;
+  }
+
+  if (qt_sink->qt_context) {
+    gst_object_unref (qt_sink->qt_context);
+    qt_sink->qt_context = NULL;
+  }
+}
+
+static void
+gst_qml6_gl_sink_finalize (GObject * object)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+  _reset (qt_sink);
+
+  qt_sink->widget.clear();
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_qml6_gl_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (object);
+
+  switch (prop_id) {
+    case PROP_WIDGET:
+      /* This is not really safe - the app needs to be
+       * sure the widget is going to be kept alive or
+       * this can crash */
+      if (qt_sink->widget)
+        g_value_set_pointer (value, qt_sink->widget->videoItem());
+      else
+        g_value_set_pointer (value, NULL);
+      break;
+    case PROP_FORCE_ASPECT_RATIO:
+      if (qt_sink->widget)
+        g_value_set_boolean (value, qt_sink->widget->getForceAspectRatio ());
+      else
+        g_value_set_boolean (value, DEFAULT_FORCE_ASPECT_RATIO);
+      break;
+    case PROP_PIXEL_ASPECT_RATIO:
+      if (qt_sink->widget) {
+        gint num, den;
+        qt_sink->widget->getDAR (&num, &den);
+        gst_value_set_fraction (value, num, den);
+      } else {
+        gst_value_set_fraction (value, DEFAULT_PAR_N, DEFAULT_PAR_D);
+      }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+gst_qml6_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+  gboolean res = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CONTEXT:
+    {
+      if (gst_gl_handle_context_query ((GstElement *) qt_sink, query,
+          qt_sink->display, qt_sink->context, qt_sink->qt_context))
+        return TRUE;
+
+      /* fallthrough */
+    }
+    default:
+      res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
+      break;
+  }
+
+  return res;
+}
+
+static gboolean
+gst_qml6_gl_sink_stop (GstBaseSink * bsink)
+{
+  return TRUE;
+}
+
+static GstStateChangeReturn
+gst_qml6_gl_sink_change_state (GstElement * element, GstStateChange transition)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (element);
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  QGuiApplication *app;
+
+  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:
+      app = static_cast<QGuiApplication *> (QCoreApplication::instance ());
+      if (!app) {
+        GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+            ("%s", "Failed to connect to Qt"),
+            ("%s", "Could not retrieve QGuiApplication instance"));
+        return GST_STATE_CHANGE_FAILURE;
+      }
+
+      if (!qt_sink->widget) {
+        GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+            ("%s", "Required property \'widget\' not set"),
+            (NULL));
+        return GST_STATE_CHANGE_FAILURE;
+      }
+
+      if (!qt_sink->widget->initWinSys()) {
+        GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+            ("%s", "Could not initialize window system"),
+            (NULL));
+        return GST_STATE_CHANGE_FAILURE;
+      }
+
+      qt_sink->display = qt_sink->widget->getDisplay();
+      qt_sink->context = qt_sink->widget->getContext();
+      qt_sink->qt_context = qt_sink->widget->getQtContext();
+
+      if (!qt_sink->display || !qt_sink->context || !qt_sink->qt_context) {
+        GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
+            ("%s", "Could not retrieve window system OpenGL configuration"),
+            (NULL));
+        return GST_STATE_CHANGE_FAILURE;
+      }
+
+      GST_OBJECT_LOCK (qt_sink->display);
+      gst_gl_display_add_context (qt_sink->display, qt_sink->context);
+      GST_OBJECT_UNLOCK (qt_sink->display);
+
+      gst_gl_element_propagate_display_context (GST_ELEMENT (qt_sink), qt_sink->display);
+
+      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:
+      if (qt_sink->widget)
+        qt_sink->widget->setBuffer(NULL);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_qml6_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+    GstClockTime * start, GstClockTime * end)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_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 (&qt_sink->v_info) > 0) {
+        *end = *start +
+            gst_util_uint64_scale_int (GST_SECOND,
+            GST_VIDEO_INFO_FPS_D (&qt_sink->v_info),
+            GST_VIDEO_INFO_FPS_N (&qt_sink->v_info));
+      }
+    }
+  }
+}
+
+gboolean
+gst_qml6_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+
+  GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
+
+  if (!gst_video_info_from_caps (&qt_sink->v_info, caps))
+    return FALSE;
+
+  if (!qt_sink->widget)
+    return FALSE;
+
+  return qt_sink->widget->setCaps(caps);
+}
+
+static GstFlowReturn
+gst_qml6_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (vsink);
+
+  GST_TRACE ("rendering buffer:%p", buf);
+
+  if (qt_sink->widget)
+    qt_sink->widget->setBuffer(buf);
+
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_qml6_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (bsink);
+  GstBufferPool *pool;
+  GstStructure *config;
+  GstCaps *caps;
+  guint size;
+  gboolean need_pool;
+
+  if (!qt_sink->display || !qt_sink->context)
+    return FALSE;
+
+  gst_query_parse_allocation (query, &caps, &need_pool);
+
+  if (caps == NULL)
+    goto no_caps;
+
+  /* FIXME re-using buffer pool breaks renegotiation */
+  if ((pool = qt_sink->pool))
+    gst_object_ref (pool);
+
+  if (pool != NULL) {
+    GstCaps *pcaps;
+
+    /* we had a pool, check caps */
+    GST_DEBUG_OBJECT (qt_sink, "check existing pool caps");
+    config = gst_buffer_pool_get_config (pool);
+    gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
+
+    if (!gst_caps_is_equal (caps, pcaps)) {
+      GST_DEBUG_OBJECT (qt_sink, "pool has different caps");
+      /* different caps, we can't use this pool */
+      gst_object_unref (pool);
+      pool = NULL;
+    }
+    gst_structure_free (config);
+  } else {
+    GstVideoInfo info;
+
+    if (!gst_video_info_from_caps (&info, caps))
+      goto invalid_caps;
+
+    /* the normal size of a frame */
+    size = info.size;
+  }
+
+  if (pool == NULL && need_pool) {
+  
+    GST_DEBUG_OBJECT (qt_sink, "create new pool");
+    pool = gst_gl_buffer_pool_new (qt_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);
+
+  /* we also support various metadata */
+  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
+
+  if (qt_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;
+  }
+}
+
+static void
+gst_qml6_gl_sink_navigation_send_event (GstNavigation * navigation,
+                                   GstEvent * event)
+{
+  GstQml6GLSink *qt_sink = GST_QML6_GL_SINK (navigation);
+  GstPad *pad;
+
+  pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (qt_sink));
+
+  GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT,
+      gst_event_get_structure(event));
+
+  if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+    if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+      /* If upstream didn't handle the event we'll post a message with it
+       * for the application in case it wants to do something with it */
+      gst_element_post_message (GST_ELEMENT_CAST (qt_sink),
+                                gst_navigation_message_new_event (GST_OBJECT_CAST (qt_sink), event));
+    }
+    gst_event_unref (event);
+    gst_object_unref (pad);
+  }
+}
+
+static void gst_qml6_gl_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+  iface->send_event_simple = gst_qml6_gl_sink_navigation_send_event;
+}
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.h b/subprojects/gst-plugins-good/ext/qt6/gstqml6glsink.h
new file mode 100644 (file)
index 0000000..4eeeddb
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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_QT6_SINK_H__
+#define __GST_QT6_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+#include "qt6glitem.h"
+
+typedef struct _GstQml6GLSinkPrivate GstQml6GLSinkPrivate;
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_QML6_GL_SINK (gst_qml6_gl_sink_get_type())
+G_DECLARE_FINAL_TYPE (GstQml6GLSink, gst_qml6_gl_sink, GST, QML6_GL_SINK, GstVideoSink)
+#define GST_QML6_GL_SINK_CAST(obj) ((GstQml6GLSink*)(obj))
+
+/**
+ * GstQml6GLSink:
+ *
+ * Opaque #GstQml6GLSink object
+ */
+struct _GstQml6GLSink
+{
+  /* <private> */
+  GstVideoSink          parent;
+
+  GstVideoInfo          v_info;
+  GstBufferPool        *pool;
+
+  GstGLDisplay         *display;
+  GstGLContext         *context;
+  GstGLContext         *qt_context;
+
+  QSharedPointer<Qt6GLVideoItemInterface> widget;
+};
+
+GstQml6GLSink *    gst_qml6_gl_sink_new (void);
+
+G_END_DECLS
+
+#endif /* __GST_QT6_SINK_H__ */
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.cc b/subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.cc
new file mode 100644 (file)
index 0000000..f36cb2a
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * GStreamer
+ * Copyright (C) 2022 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.
+ */
+
+#include "gstqsg6glnode.h"
+
+#include <QtQuick/QSGTextureProvider>
+#include <QtQuick/QSGSimpleTextureNode>
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/QSGTexture>
+
+#define GST_CAT_DEFAULT gst_qsg_texture_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+GstQSG6OpenGLNode::GstQSG6OpenGLNode(QQuickItem * item)
+{
+  static gsize _debug;
+
+  if (g_once_init_enter (&_debug)) {
+    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtqsgtexture", 0,
+        "Qt Scenegraph Texture");
+    g_once_init_leave (&_debug, 1);
+  }
+
+  gst_video_info_init (&this->v_info);
+
+  this->buffer_ = NULL;
+  this->sync_buffer_ = gst_buffer_new ();
+  this->dummy_tex_ = nullptr;
+  // TODO; handle windowChanged?
+  this->window_ = item->window();
+}
+
+GstQSG6OpenGLNode::~GstQSG6OpenGLNode()
+{
+  gst_buffer_replace (&this->buffer_, NULL);
+  gst_buffer_replace (&this->sync_buffer_, NULL);
+  this->buffer_was_bound = FALSE;
+  delete this->dummy_tex_;
+  this->dummy_tex_ = nullptr;
+}
+
+QSGTexture *
+GstQSG6OpenGLNode::texture() const
+{
+  return QSGSimpleTextureNode::texture();
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+void
+GstQSG6OpenGLNode::setCaps (GstCaps * caps)
+{
+  GST_LOG ("%p setCaps %" GST_PTR_FORMAT, this, caps);
+
+  if (caps)
+    gst_video_info_from_caps (&this->v_info, caps);
+  else
+    gst_video_info_init (&this->v_info);
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+GstBuffer *
+GstQSG6OpenGLNode::getBuffer ()
+{
+  GstBuffer *buffer = NULL;
+
+  if (this->buffer_)
+    buffer = gst_buffer_ref (this->buffer_);
+
+  return buffer;
+}
+
+/* only called from the streaming thread with scene graph thread blocked */
+void
+GstQSG6OpenGLNode::setBuffer (GstBuffer * buffer)
+{
+  GstGLContext *qt_context = NULL;
+  gboolean buffer_changed;
+
+  GST_LOG ("%p setBuffer %" GST_PTR_FORMAT, this, buffer);
+  /* FIXME: update more state here */
+  buffer_changed = gst_buffer_replace (&this->buffer_, buffer);
+
+  if (buffer_changed) {
+    GstGLContext *context;
+    GstGLSyncMeta *sync_meta;
+    GstMemory *mem;
+    guint tex_id;
+    QQuickWindow::CreateTextureOptions options = QQuickWindow::TextureHasAlphaChannel;
+    QSGTexture *texture = nullptr;
+    QSize texSize;
+
+    qt_context = gst_gl_context_get_current();
+    if (!qt_context)
+      goto use_dummy_tex;
+
+    if (!this->buffer_)
+      goto use_dummy_tex;
+    if (GST_VIDEO_INFO_FORMAT (&this->v_info) == GST_VIDEO_FORMAT_UNKNOWN)
+      goto use_dummy_tex;
+
+    this->mem_ = gst_buffer_peek_memory (this->buffer_, 0);
+    if (!this->mem_)
+      goto use_dummy_tex;
+
+    /* FIXME: should really lock the memory to prevent write access */
+    if (!gst_video_frame_map (&this->v_frame, &this->v_info, this->buffer_,
+          (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
+      g_assert_not_reached ();
+      goto use_dummy_tex;
+    }
+
+    mem = gst_buffer_peek_memory (this->buffer_, 0);
+    g_assert (gst_is_gl_memory (mem));
+
+    context = ((GstGLBaseMemory *)mem)->context;
+
+    sync_meta = gst_buffer_get_gl_sync_meta (this->sync_buffer_);
+    if (!sync_meta)
+      sync_meta = gst_buffer_add_gl_sync_meta (context, this->sync_buffer_);
+
+    gst_gl_sync_meta_set_sync_point (sync_meta, context);
+
+    gst_gl_sync_meta_wait (sync_meta, qt_context);
+
+    tex_id = *(guint *) this->v_frame.data[0];
+    GST_LOG ("%p binding Qt texture %u", this, tex_id);
+
+    texSize = QSize(GST_VIDEO_FRAME_WIDTH (&this->v_frame), GST_VIDEO_FRAME_HEIGHT (&this->v_frame));
+    // XXX: ideally, we would like to subclass the relevant texture object
+    // ourselves but this is good enough for now
+    texture = QNativeInterface::QSGOpenGLTexture::fromNative(tex_id, this->window_, texSize, options);
+
+    setTexture(texture);
+    setOwnsTexture(true);
+    markDirty(QSGNode::DirtyMaterial);
+
+    gst_video_frame_unmap (&this->v_frame);
+
+    /* Texture was successfully bound, so we do not need
+     * to use the dummy texture */
+  }
+
+  if (!texture()) {
+use_dummy_tex:
+    /* Create dummy texture if not already present. */
+    if (this->dummy_tex_ == nullptr) {
+      /* Make this a black 64x64 pixel RGBA texture.
+       * This size and format is supported pretty much everywhere, so these
+       * are a safe pick. (64 pixel sidelength must be supported according
+       * to the GLES2 spec, table 6.18.)
+       * Set min/mag filters to GL_LINEAR to make sure no mipmapping is used. */
+      const int tex_sidelength = 64;
+      QImage image(tex_sidelength, tex_sidelength, QImage::Format_ARGB32);
+      image.fill(QColor(0, 0, 0, 255));
+
+      this->dummy_tex_ = this->window_->createTextureFromImage(image);
+    }
+
+    g_assert (this->dummy_tex_ != nullptr);
+
+    if (texture() != this->dummy_tex_) {
+      setTexture(this->dummy_tex_);
+      setOwnsTexture(false);
+      markDirty(QSGNode::DirtyMaterial);
+    }
+
+    GST_LOG ("%p binding fallback dummy Qt texture %p", this, this->dummy_tex_);
+  }
+}
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.h b/subprojects/gst-plugins-good/ext/qt6/gstqsg6glnode.h
new file mode 100644 (file)
index 0000000..0428fa5
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * GStreamer
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include "gstqt6gl.h"
+#include <QtQuick/QQuickItem>
+#include <QtQuick/QSGTexture>
+#include <QtQuick/QSGTextureProvider>
+#include <QtQuick/QSGSimpleTextureNode>
+#include <QtGui/QOpenGLFunctions>
+
+class GstQSG6OpenGLNode : public QSGTextureProvider, public QSGSimpleTextureNode, protected QOpenGLFunctions
+{
+  Q_OBJECT
+
+public:
+  GstQSG6OpenGLNode(QQuickItem *item);
+  ~GstQSG6OpenGLNode();
+
+  QSGTexture *texture() const override;
+
+  void setCaps(GstCaps *caps);
+  void setBuffer(GstBuffer *buffer);
+  GstBuffer *getBuffer();
+
+  void updateQSGTexture();
+
+private:
+  QQuickWindow *window_;
+  GstBuffer * buffer_;
+  gboolean buffer_was_bound;
+  GstBuffer * sync_buffer_;
+  GstMemory * mem_;
+  QSGTexture *dummy_tex_;
+  GstVideoInfo v_info;
+  GstVideoFrame v_frame;
+};
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqt6element.cc b/subprojects/gst-plugins-good/ext/qt6/gstqt6element.cc
new file mode 100644 (file)
index 0000000..aa93471
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 "gstqt6elements.h"
+#include "qt6glitem.h"
+#include <QtQml/QQmlApplicationEngine>
+
+void
+qt6_element_init (GstPlugin * plugin)
+{
+  static gsize res = FALSE;
+  if (g_once_init_enter (&res)) {
+    /* this means the plugin must be loaded before the qml engine is loaded */
+    qmlRegisterType<Qt6GLVideoItem> ("org.freedesktop.gstreamer.Qt6GLVideoItem", 1, 0, "GstGLQt6VideoItem");
+    g_once_init_leave (&res, TRUE);
+  }
+}
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqt6elements.h b/subprojects/gst-plugins-good/ext/qt6/gstqt6elements.h
new file mode 100644 (file)
index 0000000..7b18455
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 Huawei Technologies Co., Ltd.
+ *   @Author: Julian Bouzas <julian.bouzas@collabora.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_QT6_ELEMENTS_H__
+#define __GST_QT6_ELEMENTS_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+void qt6_element_init (GstPlugin * plugin);
+
+GST_ELEMENT_REGISTER_DECLARE (qml6glsink);
+
+G_END_DECLS
+
+#endif /* __GST_QT6_ELEMENTS_H__ */
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqt6gl.h b/subprojects/gst-plugins-good/ext/qt6/gstqt6gl.h
new file mode 100644 (file)
index 0000000..a1babaa
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+#include <QtCore/qglobal.h>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
+#include <QtGui/qtgui-config.h>
+#endif
+
+#include <gst/gl/gstglconfig.h>
+
+/* The glext.h guard was renamed in 2018, but some software which
+ * includes their own copy of the GL headers (such as qt) might have
+ * older version which use the old guard. This would result in the
+ * header being included again (and symbols redefined).
+ *
+ * To avoid this, we define the "old" guard if the "new" guard is
+ * defined.*/
+#if GST_GL_HAVE_OPENGL
+#ifdef __gl_glext_h_
+#ifndef __glext_h_
+#define __glext_h_ 1
+#endif
+#endif
+#endif
+
+/* pulls in GLsync, see below */
+#include <QtGui/qopengl.h>
+
+/* qt uses the same trick as us to typedef GLsync on GLES2 but to a different
+ * type which confuses the preprocessor. Instead of trying to reconcile the
+ * two, we instead use the GLsync definition from Qt from above, and ensure
+ * that we don't typedef GLsync in gstglfuncs.h */
+#undef GST_GL_HAVE_GLSYNC
+#define GST_GL_HAVE_GLSYNC 1
+#include <gst/gl/gstglfuncs.h>
+
+#if defined(QT_OPENGL_ES_2)
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
+#endif /* defined(QT_OPENGL_ES_2) */
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.cc b/subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.cc
new file mode 100644 (file)
index 0000000..ae21904
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * 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 "gstqt6glutility.h"
+#include <QtGui/QGuiApplication>
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+#include <gst/gl/x11/gstgldisplay_x11.h>
+//#include <QtPlatformHeaders/QGLXNativeContext>
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && (defined (HAVE_QT_WAYLAND) || defined (HAVE_QT_EGLFS) || defined (HAVE_QT_ANDROID))
+#include <gst/gl/egl/gstegl.h>
+#ifdef HAVE_QT_QPA_HEADER
+#include QT_QPA_HEADER
+#endif
+//#include <QtPlatformHeaders/QEGLNativeContext>
+#include <gst/gl/egl/gstgldisplay_egl.h>
+#endif
+
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+#include <gst/gl/wayland/gstgldisplay_wayland.h>
+#endif
+#if 0
+#if GST_GL_HAVE_WINDOW_VIV_FB
+#include <gst/gl/viv-fb/gstgldisplay_viv_fb.h>
+#endif
+
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+#include <windows.h>
+#include <QtPlatformHeaders/QWGLNativeContext>
+#endif
+#endif
+#include <gst/gl/gstglfuncs.h>
+
+#define GST_CAT_DEFAULT qml6_gl_utils_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+G_LOCK_DEFINE_STATIC (display_lock);
+static GWeakRef qt_display;
+static gboolean sink_retrieved = FALSE;
+
+GstGLDisplay *
+gst_qml6_get_gl_display (gboolean sink)
+{
+  GstGLDisplay *display = NULL;
+  QGuiApplication *app = static_cast<QGuiApplication *> (QCoreApplication::instance ());
+  static gsize _debug;
+
+  g_assert (app != NULL);
+
+  if (g_once_init_enter (&_debug)) {
+    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglutility", 0,
+        "Qt gl utility functions");
+    g_once_init_leave (&_debug, 1);
+  }
+
+  G_LOCK (display_lock);
+  /* XXX: this assumes that only one display will ever be created by Qt */
+  display = static_cast<GstGLDisplay *>(g_weak_ref_get (&qt_display));
+  if (display) {
+    if (sink_retrieved) {
+      GST_INFO ("returning previously created display");
+      G_UNLOCK (display_lock);
+      return display;
+    }
+    gst_clear_object (&display);
+  }
+  if (sink)
+    sink_retrieved = sink;
+
+  GST_INFO ("QGuiApplication::instance()->platformName() %s", app->platformName().toUtf8().data());
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+  if (QString::fromUtf8 ("xcb") == app->platformName()) {
+    auto x11_native = app->nativeInterface<QNativeInterface::QX11Application>();
+    if (x11_native) {
+      display = (GstGLDisplay *)
+          gst_gl_display_x11_new_with_display (x11_native->display());
+    }
+  }
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_WAYLAND)
+  if (QString::fromUtf8 ("wayland") == app->platformName()
+        || QString::fromUtf8 ("wayland-egl") == app->platformName()){
+    struct wl_display * wayland_display;
+    QPlatformNativeInterface *native =
+        QGuiApplication::platformNativeInterface();
+    wayland_display = (struct wl_display *)
+        native->nativeResourceForWindow("display", NULL);
+    display = (GstGLDisplay *)
+        gst_gl_display_wayland_new_with_display (wayland_display);
+  }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && GST_GL_HAVE_WINDOW_ANDROID
+  if (QString::fromUtf8 ("android") == app->platformName()) {
+    EGLDisplay egl_display = (EGLDisplay) gst_gl_display_egl_get_from_native (GST_GL_DISPLAY_TYPE_ANY, 0);
+    display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+  }
+#elif GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_EGLFS)
+  if (QString::fromUtf8("eglfs") == app->platformName()) {
+#if GST_GL_HAVE_WINDOW_VIV_FB
+    /* FIXME: Could get the display directly from Qt like this
+     * QPlatformNativeInterface *native =
+     *     QGuiApplication::platformNativeInterface();
+     * EGLDisplay egl_display = (EGLDisplay)
+     *     native->nativeResourceForWindow("egldisplay", NULL);
+     *
+     * However we seem to have no way for getting the EGLNativeDisplayType, aka
+     * native_display, via public API. As such we have to assume that display 0
+     * is always used. Only way around that is parsing the index the same way as
+     * Qt does in QEGLDeviceIntegration::fbDeviceName(), so let's do that.
+     */
+    const gchar *fb_dev;
+    gint disp_idx = 0;
+
+    fb_dev = g_getenv ("QT_QPA_EGLFS_FB");
+    if (fb_dev) {
+      if (sscanf (fb_dev, "/dev/fb%d", &disp_idx) != 1)
+        disp_idx = 0;
+    }
+
+    display = (GstGLDisplay *) gst_gl_display_viv_fb_new (disp_idx);
+#elif defined(HAVE_QT_QPA_HEADER)
+    QPlatformNativeInterface *native =
+        QGuiApplication::platformNativeInterface();
+    EGLDisplay egl_display = (EGLDisplay)
+        native->nativeResourceForWindow("egldisplay", NULL);
+    if (egl_display != EGL_NO_DISPLAY)
+      display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+#else
+    EGLDisplay egl_display = (EGLDisplay) gst_gl_display_egl_get_from_native (GST_GL_DISPLAY_TYPE_ANY, 0);
+    display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (egl_display);
+#endif
+  }
+#endif
+#if GST_GL_HAVE_WINDOW_COCOA && GST_GL_HAVE_PLATFORM_CGL && defined (HAVE_QT_MAC)
+  if (QString::fromUtf8 ("cocoa") == app->platformName())
+    display = (GstGLDisplay *) gst_gl_display_new ();
+#endif
+#if GST_GL_HAVE_WINDOW_EAGL && GST_GL_HAVE_PLATFORM_EAGL && defined (HAVE_QT_IOS)
+  if (QString::fromUtf8 ("ios") == app->platformName())
+    display = gst_gl_display_new ();
+#endif
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+  if (QString::fromUtf8 ("windows") == app->platformName())
+    display = gst_gl_display_new ();
+#endif
+
+  if (!display)
+    display = gst_gl_display_new ();
+
+  g_weak_ref_set (&qt_display, display);
+  G_UNLOCK (display_lock);
+
+  return display;
+}
+
+gboolean
+gst_qml6_get_gl_wrapcontext (GstGLDisplay * display,
+    GstGLContext **wrap_glcontext, GstGLContext **context)
+{
+  GstGLPlatform G_GNUC_UNUSED platform = (GstGLPlatform) 0;
+  GstGLAPI G_GNUC_UNUSED gl_api;
+  guintptr G_GNUC_UNUSED gl_handle;
+  GstGLContext *current;
+  GError *error = NULL;
+
+  g_return_val_if_fail (display != NULL && wrap_glcontext != NULL, FALSE);
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+  if (GST_IS_GL_DISPLAY_X11 (display)) {
+#if GST_GL_HAVE_PLATFORM_GLX
+    platform = GST_GL_PLATFORM_GLX;
+#elif GST_GL_HAVE_PLATFORM_EGL
+    platform = GST_GL_PLATFORM_EGL;
+#endif
+  }
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+  if (GST_IS_GL_DISPLAY_WAYLAND (display)) {
+    platform = GST_GL_PLATFORM_EGL;
+  }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_EGLFS)
+#if GST_GL_HAVE_WINDOW_VIV_FB
+  if (GST_IS_GL_DISPLAY_VIV_FB (display)) {
+#else
+  if (GST_IS_GL_DISPLAY_EGL (display)) {
+#endif
+    platform = GST_GL_PLATFORM_EGL;
+  }
+#endif
+  if (platform == 0) {
+#if GST_GL_HAVE_WINDOW_COCOA && GST_GL_HAVE_PLATFORM_CGL && defined (HAVE_QT_MAC)
+    platform = GST_GL_PLATFORM_CGL;
+#elif GST_GL_HAVE_WINDOW_EAGL && GST_GL_HAVE_PLATFORM_EAGL && defined (HAVE_QT_IOS)
+    platform = GST_GL_PLATFORM_EAGL;
+#elif GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+    platform = GST_GL_PLATFORM_WGL;
+#elif GST_GL_HAVE_WINDOW_ANDROID && GST_GL_HAVE_PLATFORM_EGL && defined (HAVE_QT_ANDROID)
+    platform = GST_GL_PLATFORM_EGL;
+#else
+    GST_ERROR ("Unknown platform");
+    return FALSE;
+#endif
+  }
+
+  gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
+  gl_handle = gst_gl_context_get_current_gl_context (platform);
+
+  /* see if we already have a current GL context in GStreamer for this thread */
+  current = gst_gl_context_get_current ();
+  if (current && current->display == display) {
+    /* just use current context we found */
+    *wrap_glcontext = static_cast<GstGLContext *> (gst_object_ref (current));
+  }
+  else {
+    if (gl_handle)
+      *wrap_glcontext =
+          gst_gl_context_new_wrapped (display, gl_handle,
+          platform, gl_api);
+
+    if (!*wrap_glcontext) {
+      GST_ERROR ("cannot wrap qt OpenGL context");
+      return FALSE;
+    }
+
+    gst_gl_context_activate(*wrap_glcontext, TRUE);
+    if (!gst_gl_context_fill_info (*wrap_glcontext, &error)) {
+      GST_ERROR ("failed to retrieve qt context info: %s", error->message);
+      gst_clear_object (wrap_glcontext);
+      return FALSE;
+    }
+
+    gst_gl_display_filter_gl_api (display, gst_gl_context_get_gl_api (*wrap_glcontext));
+    gst_gl_context_activate (*wrap_glcontext, FALSE);
+  }
+#if 0
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+  g_return_val_if_fail (context != NULL, FALSE);
+
+  G_STMT_START {
+    /* If there's no wglCreateContextAttribsARB() support, then we would fallback to
+     * wglShareLists() which will fail with ERROR_BUSY (0xaa) if either of the GL
+     * contexts are current in any other thread.
+     *
+     * The workaround here is to temporarily disable Qt's GL context while we
+     * set up our own.
+     *
+     * Sometimes wglCreateContextAttribsARB()
+     * exists, but isn't functional (some Intel drivers), so it's easiest to do this
+     * unconditionally.
+     */
+
+    /* retrieve Qt's GL device context as current device context */
+    HDC device = wglGetCurrentDC ();
+
+    *context = gst_gl_context_new (display);
+
+    wglMakeCurrent (NULL, NULL);
+    if (!gst_gl_context_create (*context, *wrap_glcontext, &error)) {
+      GST_ERROR ("failed to create shared GL context: %s", error->message);
+      gst_clear_object (wrap_glcontext);
+      gst_clear_object (context);
+    }
+    wglMakeCurrent (device, (HGLRC) gl_handle);
+
+    if (!*context)
+      return FALSE;
+
+  } G_STMT_END;
+#endif
+#endif
+  return TRUE;
+}
+#if 0
+QVariant
+qt_opengl_native_context_from_gst_gl_context (GstGLContext * context)
+{
+    guintptr handle;
+    GstGLPlatform platform;
+
+    handle = gst_gl_context_get_gl_context (context);
+    platform = gst_gl_context_get_gl_platform (context);
+
+#if GST_GL_HAVE_WINDOW_X11 && defined (HAVE_QT_X11)
+    if (platform == GST_GL_PLATFORM_GLX) {
+        GstGLDisplay *display = gst_gl_context_get_display (context);
+        GstGLWindow *window = gst_gl_context_get_window (context);
+        Display *xdisplay = (Display *) gst_gl_display_get_handle (display);
+        Window win = gst_gl_window_get_window_handle (window);
+        gst_object_unref (window);
+        gst_object_unref (display);
+        return QVariant::fromValue(QGLXNativeContext((GLXContext) handle, xdisplay, win));
+    }
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL && (defined (HAVE_QT_WAYLAND) || defined (HAVE_QT_EGLFS) || defined (HAVE_QT_ANDROID))
+    if (platform == GST_GL_PLATFORM_EGL) {
+        EGLDisplay egl_display = EGL_DEFAULT_DISPLAY;
+        GstGLDisplay *display = gst_gl_context_get_display (context);
+        GstGLDisplayEGL *display_egl = gst_gl_display_egl_from_gl_display (display);
+#if GST_GL_HAVE_WINDOW_WAYLAND && defined (HAVE_QT_WAYLAND)
+        if (gst_gl_display_get_handle_type (display) == GST_GL_DISPLAY_TYPE_WAYLAND) {
+#if 1
+            g_warning ("Qt does not support wrapping native OpenGL contexts "
+                "on wayland. See https://bugreports.qt.io/browse/QTBUG-82528");
+            gst_object_unref (display_egl);
+            gst_object_unref (display);
+            return QVariant::fromValue(nullptr);
+#else
+            if (display_egl)
+                egl_display = (EGLDisplay) gst_gl_display_get_handle ((GstGLDisplay *) display_egl);
+#endif
+        }
+#endif
+        gst_object_unref (display_egl);
+        gst_object_unref (display);
+        return QVariant::fromValue(QEGLNativeContext((EGLContext) handle, egl_display));
+    }
+#endif
+#if GST_GL_HAVE_WINDOW_WIN32 && GST_GL_HAVE_PLATFORM_WGL && defined (HAVE_QT_WIN32)
+    if (platform == GST_GL_PLATFORM_WGL) {
+        GstGLWindow *window = gst_gl_context_get_window (context);
+        guintptr hwnd = gst_gl_window_get_window_handle (window);
+        gst_object_unref (window);
+        return QVariant::fromValue(QWGLNativeContext((HGLRC) handle, (HWND) hwnd));
+    }
+#endif
+    {
+      gchar *platform_s = gst_gl_platform_to_string (platform);
+      g_warning ("Unimplemented configuration!  This means either:\n"
+          "1. The qmlgl plugin was built without support for your platform.\n"
+          "2. The necessary code to convert from a GstGLContext to Qt's "
+          "native context type for \'%s\' currently does not exist.",
+          platform_s);
+      g_free (platform_s);
+    }
+    return QVariant::fromValue(nullptr);
+}
+#endif
diff --git a/subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.h b/subprojects/gst-plugins-good/ext/qt6/gstqt6glutility.h
new file mode 100644 (file)
index 0000000..ba43623
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * GStreamer
+ * Copyright (C) 2016 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __QML6_GL_UTILS_H__
+#define __QML6_GL_UTILS_H__
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include <QVariant>
+#include <QRunnable>
+
+G_BEGIN_DECLS
+
+struct RenderJob : public QRunnable {
+    using Callable = std::function<void()>;
+
+    explicit RenderJob(Callable c) : _c(c) { }
+
+    void run() { _c(); }
+
+private:
+    Callable _c;
+};
+
+GstGLDisplay * gst_qml6_get_gl_display (gboolean sink);
+gboolean       gst_qml6_get_gl_wrapcontext (GstGLDisplay * display,
+    GstGLContext **wrap_glcontext, GstGLContext **context);
+
+G_END_DECLS
+
+#if 0
+#if defined(__cplusplus)
+QVariant       qt_opengl_native_context_from_gst_gl_context     (GstGLContext * context);
+#endif
+#endif
+
+#endif /* __QML6_GL_UTILS_H__ */
diff --git a/subprojects/gst-plugins-good/ext/qt6/meson.build b/subprojects/gst-plugins-good/ext/qt6/meson.build
new file mode 100644 (file)
index 0000000..4523d2d
--- /dev/null
@@ -0,0 +1,144 @@
+sources = [
+  'gstplugin.cc',
+  'gstqt6element.cc',
+  'gstqsg6glnode.cc',
+  'gstqt6glutility.cc',
+  'gstqml6glsink.cc',
+  'qt6glitem.cc',
+]
+
+moc_headers = [
+  'qt6glitem.h',
+  'gstqsg6glnode.h',
+]
+
+qt6qml_dep = dependency('', required: false)
+qt6_option = get_option('qt6')
+
+if qt6_option.disabled()
+  subdir_done()
+endif
+
+if not have_gstgl
+  if qt6_option.enabled()
+    error('qt6 qmlglsink plugin is enabled, but gstreamer-gl-1.0 was not found')
+  endif
+  subdir_done()
+endif
+
+if not add_languages('cpp', native: false, required: qt6_option)
+  subdir_done()
+endif
+
+qt6_mod = import('qt6')
+if not qt6_mod.has_tools()
+  if qt6_option.enabled()
+    error('qt6 qmlglsink plugin is enabled, but qt specific tools were not found')
+  endif
+  subdir_done()
+endif
+
+qt6qml_dep = dependency('qt6', modules : ['Core', 'Gui', 'Qml', 'Quick'],
+                        required: qt6_option, static: host_machine.system() == 'ios')
+if not qt6qml_dep.found()
+  subdir_done()
+endif
+
+optional_deps = []
+qt_defines = []
+have_qpa_include = false
+have_qt_windowing = false
+
+# Look for the QPA platform native interface header
+qpa_header_path = join_paths(qt6qml_dep.version(), 'QtGui')
+qpa_header = join_paths(qpa_header_path, 'qpa/qplatformnativeinterface.h')
+if cxx.has_header(qpa_header, dependencies : qt6qml_dep)
+  qt_defines += '-DHAVE_QT_QPA_HEADER'
+  qt_defines += '-DQT_QPA_HEADER=' + '<@0@>'.format(qpa_header)
+  have_qpa_include = true
+  message('Found QtGui QPA header in ' + qpa_header_path)
+endif
+
+# Try to come up with all the platform/winsys combinations that will work
+
+if gst_gl_have_window_x11 and gst_gl_have_platform_glx
+  # FIXME: automagic
+  qt_defines += ['-DHAVE_QT_X11']
+  have_qt_windowing = true
+endif
+
+if gst_gl_have_platform_egl
+  # Embedded linux (e.g. i.MX6) with or without windowing support
+  qt_defines += ['-DHAVE_QT_EGLFS']
+  optional_deps += gstglegl_dep
+  have_qt_windowing = true
+  if have_qpa_include
+    # Wayland windowing
+    if gst_gl_have_window_wayland
+      # FIXME: automagic
+      qt6waylandextras = dependency('qt6', modules : ['WaylandClient'], required : false)
+      if qt6waylandextras.found()
+        optional_deps += [qt6waylandextras, gstglwayland_dep]
+        qt_defines += ['-DHAVE_QT_WAYLAND']
+        have_qt_windowing = true
+      endif
+    endif
+    # Android windowing
+#    if gst_gl_have_window_android
+      # FIXME: automagic
+#      qt5androidextras = dependency('qt5', modules : ['AndroidExtras'], required : false)
+      # for gl functions in QtGui/qopenglfunctions.h
+      # FIXME: automagic
+#      glesv2_dep = cc.find_library('GLESv2', required : false)
+#      if glesv2_dep.found() and qt5androidextras.found()
+#        optional_deps += [qt5androidextras, glesv2_dep]
+#        qt_defines += ['-DHAVE_QT_ANDROID']
+#        have_qt_windowing = true
+        # Needed for C++11 support in Cerbero. People building with Android
+        # in some other way need to add the necessary bits themselves.
+#        optional_deps += dependency('gnustl', required : false)
+#      endif
+#    endif
+  endif
+endif
+
+#if gst_gl_have_platform_wgl and gst_gl_have_window_win32
+  # for wglMakeCurrent()
+  # FIXME: automagic
+#  opengl32_dep = cc.find_library('opengl32', required : false)
+#  if opengl32_dep.found()
+#    qt_defines += ['-DHAVE_QT_WIN32']
+#    optional_deps += opengl32_dep
+#    have_qt_windowing = true
+#  endif
+#endif
+
+if gst_gl_have_window_cocoa and gst_gl_have_platform_cgl
+  # FIXME: automagic
+  if host_machine.system() == 'darwin'
+    qt_defines += ['-DHAVE_QT_MAC']
+    have_qt_windowing = true
+  endif
+endif
+
+if gst_gl_have_window_eagl and gst_gl_have_platform_eagl
+  if host_machine.system() == 'ios'
+    qt_defines += ['-DHAVE_QT_IOS']
+    have_qt_windowing = true
+  endif
+endif
+
+if have_qt_windowing
+  # Build it!
+  moc_files = qt6_mod.preprocess(moc_headers : moc_headers)
+  gstqml6gl = library('gstqml6', sources, moc_files,
+    cpp_args : gst_plugins_good_args + qt_defines,
+    link_args : noseh_link_args,
+    include_directories: [configinc, libsinc],
+    dependencies : [gst_dep, gstvideo_dep, gstgl_dep, gstglproto_dep, qt6qml_dep, optional_deps],
+    override_options : ['cpp_std=c++17'],
+    install: true,
+    install_dir : plugins_install_dir)
+  pkgconfig.generate(gstqml6gl, install_dir : plugins_pkgconfig_install_dir)
+  plugins += [gstqml6gl]
+endif
diff --git a/subprojects/gst-plugins-good/ext/qt6/qt6glitem.cc b/subprojects/gst-plugins-good/ext/qt6/qt6glitem.cc
new file mode 100644 (file)
index 0000000..dc0abed
--- /dev/null
@@ -0,0 +1,905 @@
+/*
+ * 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/video/video.h>
+#include "qt6glitem.h"
+#include "gstqsg6glnode.h"
+#include "gstqt6glutility.h"
+
+#include <QtCore/QMutexLocker>
+#include <QtCore/QPointer>
+#include <QtGui/QGuiApplication>
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/QSGSimpleTextureNode>
+
+/**
+ * SECTION:Qt6GLVideoItem
+ * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers
+ *
+ * #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers.
+ */
+
+#define GST_CAT_DEFAULT qt_item_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define DEFAULT_FORCE_ASPECT_RATIO  TRUE
+#define DEFAULT_PAR_N               0
+#define DEFAULT_PAR_D               1
+
+enum
+{
+  PROP_0,
+  PROP_FORCE_ASPECT_RATIO,
+  PROP_PIXEL_ASPECT_RATIO,
+};
+
+struct _Qt6GLVideoItemPrivate
+{
+  GMutex lock;
+
+  /* properties */
+  gboolean force_aspect_ratio;
+  gint par_n, par_d;
+
+  GWeakRef sink;
+
+  gint display_width;
+  gint display_height;
+
+  GstBuffer *buffer;
+  GstCaps *new_caps;
+  GstCaps *caps;
+  GstVideoInfo new_v_info;
+  GstVideoInfo v_info;
+
+  gboolean initted;
+  GstGLDisplay *display;
+  QOpenGLContext *qt_context;
+  GstGLContext *other_context;
+  GstGLContext *context;
+
+  /* buffers with textures that were bound by QML */
+  GQueue bound_buffers;
+  /* buffers that were previously bound but in the meantime a new one was
+   * bound so this one is most likely not used anymore
+   * FIXME: Ideally we would use fences for this but there seems to be no
+   * way to reliably "try wait" on a fence */
+  GQueue potentially_unbound_buffers;
+
+  GstQSG6OpenGLNode *m_node;
+};
+
+Qt6GLVideoItem::Qt6GLVideoItem()
+{
+  static gsize _debug;
+
+  if (g_once_init_enter (&_debug)) {
+    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglwidget", 0, "Qt GL Widget");
+    g_once_init_leave (&_debug, 1);
+  }
+
+  this->setFlag (QQuickItem::ItemHasContents, true);
+
+  this->priv = g_new0 (Qt6GLVideoItemPrivate, 1);
+
+  this->priv->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
+  this->priv->par_n = DEFAULT_PAR_N;
+  this->priv->par_d = DEFAULT_PAR_D;
+
+  this->priv->initted = FALSE;
+
+  g_mutex_init (&this->priv->lock);
+
+  g_weak_ref_init (&priv->sink, NULL);
+
+  this->priv->display = gst_qml6_get_gl_display(TRUE);
+
+  connect(this, SIGNAL(windowChanged(QQuickWindow*)), this,
+          SLOT(handleWindowChanged(QQuickWindow*)));
+
+  this->proxy = QSharedPointer<Qt6GLVideoItemInterface>(new Qt6GLVideoItemInterface(this));
+
+  setFlag(ItemHasContents, true);
+  setAcceptedMouseButtons(Qt::AllButtons);
+  setAcceptHoverEvents(true);
+
+  setAcceptTouchEvents(true);
+
+  GST_DEBUG ("%p init Qt6 Video Item", this);
+}
+
+Qt6GLVideoItem::~Qt6GLVideoItem()
+{
+  GstBuffer *tmp_buffer;
+
+  /* Before destroying the priv info, make sure
+   * no qmlglsink's will call in again, and that
+   * any ongoing calls are done by invalidating the proxy
+   * pointer */
+  GST_INFO ("%p Destroying QtGLVideoItem and invalidating the proxy %p", this, proxy.data());
+  proxy->invalidateRef();
+  proxy.clear();
+
+  g_mutex_clear (&this->priv->lock);
+  if (this->priv->context)
+    gst_object_unref(this->priv->context);
+  if (this->priv->other_context)
+    gst_object_unref(this->priv->other_context);
+  if (this->priv->display)
+    gst_object_unref(this->priv->display);
+
+  while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) {
+    GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+    gst_buffer_unref (tmp_buffer);
+  }
+  while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) {
+    GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+    gst_buffer_unref (tmp_buffer);
+  }
+
+  gst_buffer_replace (&this->priv->buffer, NULL);
+
+  gst_caps_replace (&this->priv->caps, NULL);
+  gst_caps_replace (&this->priv->new_caps, NULL);
+
+  g_weak_ref_clear (&this->priv->sink);
+
+  g_free (this->priv);
+  this->priv = NULL;
+}
+
+void
+Qt6GLVideoItem::setDAR(gint num, gint den)
+{
+  this->priv->par_n = num;
+  this->priv->par_d = den;
+}
+
+void
+Qt6GLVideoItem::getDAR(gint * num, gint * den)
+{
+  if (num)
+    *num = this->priv->par_n;
+  if (den)
+    *den = this->priv->par_d;
+}
+
+void
+Qt6GLVideoItem::setForceAspectRatio(bool force_aspect_ratio)
+{
+  this->priv->force_aspect_ratio = !!force_aspect_ratio;
+
+  emit forceAspectRatioChanged(force_aspect_ratio);
+}
+
+bool
+Qt6GLVideoItem::getForceAspectRatio()
+{
+  return this->priv->force_aspect_ratio;
+}
+
+bool
+Qt6GLVideoItem::itemInitialized()
+{
+  return this->priv->initted;
+}
+
+static gboolean
+_calculate_par (Qt6GLVideoItem * widget, GstVideoInfo * info)
+{
+  gboolean ok;
+  gint width, height;
+  gint par_n, par_d;
+  gint display_par_n, display_par_d;
+  guint display_ratio_num, display_ratio_den;
+
+  width = GST_VIDEO_INFO_WIDTH (info);
+  height = GST_VIDEO_INFO_HEIGHT (info);
+
+  par_n = GST_VIDEO_INFO_PAR_N (info);
+  par_d = GST_VIDEO_INFO_PAR_D (info);
+
+  if (!par_n)
+    par_n = 1;
+
+  /* get display's PAR */
+  if (widget->priv->par_n != 0 && widget->priv->par_d != 0) {
+    display_par_n = widget->priv->par_n;
+    display_par_d = widget->priv->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;
+
+  widget->setImplicitWidth (width);
+  widget->setImplicitHeight (height);
+
+  GST_LOG ("%p PAR: %u/%u DAR:%u/%u", widget, par_n, par_d, display_par_n,
+      display_par_d);
+
+  if (height % display_ratio_den == 0) {
+    GST_DEBUG ("%p keeping video height", widget);
+    widget->priv->display_width = (guint)
+        gst_util_uint64_scale_int (height, display_ratio_num,
+        display_ratio_den);
+    widget->priv->display_height = height;
+  } else if (width % display_ratio_num == 0) {
+    GST_DEBUG ("%p keeping video width", widget);
+    widget->priv->display_width = width;
+    widget->priv->display_height = (guint)
+        gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+  } else {
+    GST_DEBUG ("%p approximating while keeping video height", widget);
+    widget->priv->display_width = (guint)
+        gst_util_uint64_scale_int (height, display_ratio_num,
+        display_ratio_den);
+    widget->priv->display_height = height;
+  }
+  GST_DEBUG ("%p scaling to %dx%d", widget, widget->priv->display_width,
+      widget->priv->display_height);
+
+  return TRUE;
+}
+
+QSGNode *
+Qt6GLVideoItem::updatePaintNode(QSGNode * oldNode,
+    UpdatePaintNodeData * updatePaintNodeData)
+{
+  GstBuffer *old_buffer;
+
+  if (!this->priv->initted)
+    return oldNode;
+
+  GstQSG6OpenGLNode *texNode = static_cast<GstQSG6OpenGLNode *> (oldNode);
+  GstVideoRectangle src, dst, result;
+
+  g_mutex_lock (&this->priv->lock);
+
+  GST_TRACE ("%p updatePaintNode", this);
+
+  if (gst_gl_context_get_current() == NULL)
+    gst_gl_context_activate (this->priv->other_context, TRUE);
+
+  if (!texNode) {
+    texNode = new GstQSG6OpenGLNode (this);
+    this->priv->m_node = texNode;
+  }
+
+  if ((old_buffer = texNode->getBuffer())) {
+    if (old_buffer == this->priv->buffer) {
+      /* same buffer */
+      gst_buffer_unref (old_buffer);
+    } else {
+      GstBuffer *tmp_buffer;
+
+      GST_TRACE ("old buffer %p was bound, queueing up for later", old_buffer);
+      /* Unref all buffers that were previously not bound anymore. At least
+       * one more buffer was bound in the meantime so this one is most likely
+       * not in use anymore. */
+      while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) {
+        GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer);
+        gst_buffer_unref (tmp_buffer);
+      }
+
+      /* Move previous bound buffers to the next queue. We now know that
+       * another buffer was bound in the meantime and will free them on
+       * the next iteration above. */
+      while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) {
+        GST_TRACE ("old buffer %p is potentially unbound now", tmp_buffer);
+        g_queue_push_tail (&this->priv->potentially_unbound_buffers, tmp_buffer);
+      }
+      g_queue_push_tail (&this->priv->bound_buffers, old_buffer);
+    }
+    old_buffer = NULL;
+  }
+
+  texNode->setCaps (this->priv->caps);
+  texNode->setBuffer (this->priv->buffer);
+
+  if (this->priv->force_aspect_ratio && this->priv->caps) {
+    src.w = this->priv->display_width;
+    src.h = this->priv->display_height;
+
+    dst.x = boundingRect().x();
+    dst.y = boundingRect().y();
+    dst.w = boundingRect().width();
+    dst.h = boundingRect().height();
+
+    gst_video_sink_center_rect (src, dst, &result, TRUE);
+  } else {
+    result.x = boundingRect().x();
+    result.y = boundingRect().y();
+    result.w = boundingRect().width();
+    result.h = boundingRect().height();
+  }
+
+  texNode->setRect (QRectF (result.x, result.y, result.w, result.h));
+
+  g_mutex_unlock (&this->priv->lock);
+
+  return texNode;
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+void
+Qt6GLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result)
+{
+  if (this->priv->force_aspect_ratio) {
+    GstVideoRectangle src, dst;
+
+    src.x = 0;
+    src.y = 0;
+    src.w = this->priv->display_width;
+    src.h = this->priv->display_height;
+
+    dst.x = 0;
+    dst.y = 0;
+    dst.w = width();
+    dst.h = height();
+
+    gst_video_sink_center_rect (src, dst, result, TRUE);
+  } else {
+    result->x = 0;
+    result->y = 0;
+    result->w = width();
+    result->h = height();
+  }
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+QPointF
+Qt6GLVideoItem::mapPointToStreamSize(QPointF pos)
+{
+  gdouble stream_width, stream_height;
+  GstVideoRectangle result;
+  double stream_x, stream_y;
+  double x, y;
+
+  fitStreamToAllocatedSize(&result);
+
+  stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info);
+  stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info);
+  x = pos.x();
+  y = pos.y();
+
+  /* from display coordinates to stream coordinates */
+  if (result.w > 0)
+    stream_x = (x - result.x) / result.w * stream_width;
+  else
+    stream_x = 0.;
+
+  /* clip to stream size */
+  stream_x = CLAMP(stream_x, 0., stream_width);
+
+  /* same for y-axis */
+  if (result.h > 0)
+    stream_y = (y - result.y) / result.h * stream_height;
+  else
+    stream_y = 0.;
+
+  stream_y = CLAMP(stream_y, 0., stream_height);
+  GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y);
+
+  return QPointF(stream_x, stream_y);
+}
+
+static GstNavigationModifierType
+translateModifiers(Qt::KeyboardModifiers modifiers)
+{
+  return (GstNavigationModifierType)(
+    ((modifiers & Qt::KeyboardModifier::ShiftModifier) ? GST_NAVIGATION_MODIFIER_SHIFT_MASK : 0) |
+    ((modifiers & Qt::KeyboardModifier::ControlModifier) ? GST_NAVIGATION_MODIFIER_CONTROL_MASK : 0) |
+    ((modifiers & Qt::KeyboardModifier::AltModifier) ? GST_NAVIGATION_MODIFIER_ALT_MASK : 0) |
+    ((modifiers & Qt::KeyboardModifier::MetaModifier) ? GST_NAVIGATION_MODIFIER_META_MASK : 0));
+}
+
+static GstNavigationModifierType
+translateMouseButtons(Qt::MouseButtons buttons)
+{
+  return (GstNavigationModifierType)(
+    ((buttons & Qt::LeftButton) ? GST_NAVIGATION_MODIFIER_BUTTON1_MASK : 0) |
+    ((buttons & Qt::RightButton) ? GST_NAVIGATION_MODIFIER_BUTTON2_MASK : 0) |
+    ((buttons & Qt::MiddleButton) ? GST_NAVIGATION_MODIFIER_BUTTON3_MASK : 0) |
+    ((buttons & Qt::BackButton) ? GST_NAVIGATION_MODIFIER_BUTTON4_MASK : 0) |
+    ((buttons & Qt::ForwardButton) ? GST_NAVIGATION_MODIFIER_BUTTON5_MASK : 0));
+}
+
+void
+Qt6GLVideoItem::wheelEvent(QWheelEvent * event)
+{
+  g_mutex_lock (&this->priv->lock);
+  QPoint delta = event->angleDelta();
+  GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+  if (element != NULL) {
+    auto position = event->position();
+    gst_navigation_send_event_simple (GST_NAVIGATION (element),
+        gst_navigation_event_new_mouse_scroll (position.x(), position.y(),
+                                               delta.x(), delta.y(),
+                                               (GstNavigationModifierType) (
+                                                 translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))));
+    g_object_unref (element);
+  }
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::hoverEnterEvent(QHoverEvent *)
+{
+  mouseHovering = true;
+}
+
+void
+Qt6GLVideoItem::hoverLeaveEvent(QHoverEvent *)
+{
+  mouseHovering = false;
+}
+
+void
+Qt6GLVideoItem::hoverMoveEvent(QHoverEvent * event)
+{
+  if (!mouseHovering)
+    return;
+
+  g_mutex_lock (&this->priv->lock);
+
+  /* can't do anything when we don't have input format */
+  if (!this->priv->caps) {
+    g_mutex_unlock (&this->priv->lock);
+    return;
+  }
+
+  if (event->position() != event->oldPos()) {
+    QPointF pos = mapPointToStreamSize(event->position());
+    GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+    if (element != NULL) {
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          gst_navigation_event_new_mouse_move (pos.x(), pos.y(),
+                                               translateModifiers(event->modifiers())));
+      g_object_unref (element);
+    }
+  }
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::touchEvent(QTouchEvent * event)
+{
+  g_mutex_lock (&this->priv->lock);
+
+  /* can't do anything when we don't have input format */
+  if (!this->priv->caps) {
+    g_mutex_unlock (&this->priv->lock);
+    return;
+  }
+
+  GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+  if (element == NULL)
+    return;
+
+  if (event->type() == QEvent::TouchCancel) {
+    gst_navigation_send_event_simple (GST_NAVIGATION (element),
+        gst_navigation_event_new_touch_cancel (translateModifiers(event->modifiers())));
+  } else {
+    const QList<QTouchEvent::TouchPoint> points = event->points();
+    gboolean sent_event = FALSE;
+
+    for (int i = 0; i < points.count(); i++) {
+      GstEvent *nav_event;
+      QPointF pos = mapPointToStreamSize(points[i].position());
+
+      switch (points[i].state()) {
+        case QEventPoint::Pressed:
+          nav_event = gst_navigation_event_new_touch_down ((guint) points[i].id(),
+              pos.x(), pos.y(), (gdouble) points[i].pressure(), translateModifiers(event->modifiers()));
+          break;
+        case QEventPoint::Updated:
+          nav_event = gst_navigation_event_new_touch_motion ((guint) points[i].id(),
+              pos.x(), pos.y(), (gdouble) points[i].pressure(), translateModifiers(event->modifiers()));
+          break;
+        case QEventPoint::Released:
+          nav_event = gst_navigation_event_new_touch_up ((guint) points[i].id(),
+              pos.x(), pos.y(), translateModifiers(event->modifiers()));
+          break;
+        /* Don't send an event if the point did not change */
+        default:
+          nav_event = NULL;
+          break;
+      }
+
+      if (nav_event) {
+        gst_navigation_send_event_simple (GST_NAVIGATION (element), nav_event);
+        sent_event = TRUE;
+      }
+    }
+
+    /* Group simultaneos touch events with a frame event */
+    if (sent_event) {
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          gst_navigation_event_new_touch_frame (translateModifiers(event->modifiers())));
+    }
+  }
+
+  g_object_unref (element);
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::sendMouseEvent(QMouseEvent * event, gboolean is_press)
+{
+  quint32 button = 0;
+
+  switch (event->button()) {
+  case Qt::LeftButton:
+    button = 1;
+    break;
+  case Qt::RightButton:
+    button = 2;
+    break;
+  default:
+    break;
+  }
+
+  mousePressedButton = button;
+
+  g_mutex_lock (&this->priv->lock);
+
+  /* can't do anything when we don't have input format */
+  if (!this->priv->caps) {
+    g_mutex_unlock (&this->priv->lock);
+    return;
+  }
+
+  QPointF pos = mapPointToStreamSize(event->pos());
+  GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+  if (element != NULL) {
+    gst_navigation_send_event_simple (GST_NAVIGATION (element),
+        (is_press) ? gst_navigation_event_new_mouse_button_press (button,
+                pos.x(), pos.y(),
+                (GstNavigationModifierType) (
+                  translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))) :
+            gst_navigation_event_new_mouse_button_release (button, pos.x(),
+                pos.y(),
+                (GstNavigationModifierType) (
+                  translateModifiers(event->modifiers()) | translateMouseButtons(event->buttons()))));
+    g_object_unref (element);
+  }
+
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+Qt6GLVideoItem::mousePressEvent(QMouseEvent * event)
+{
+  forceActiveFocus();
+  sendMouseEvent(event, TRUE);
+}
+
+void
+Qt6GLVideoItem::mouseReleaseEvent(QMouseEvent * event)
+{
+  sendMouseEvent(event, FALSE);
+}
+
+void
+Qt6GLVideoItemInterface::setSink (GstElement * sink)
+{
+  QMutexLocker locker(&lock);
+  if (qt_item == NULL)
+    return;
+
+  g_mutex_lock (&qt_item->priv->lock);
+  g_weak_ref_set (&qt_item->priv->sink, sink);
+  g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
+Qt6GLVideoItemInterface::setBuffer (GstBuffer * buffer)
+{
+  QMutexLocker locker(&lock);
+
+  if (qt_item == NULL) {
+    GST_WARNING ("%p actual item is NULL. setBuffer call ignored", this);
+    return;
+  }
+
+  if (!qt_item->priv->caps && !qt_item->priv->new_caps) {
+    GST_WARNING ("%p Got buffer on unnegotiated QtGLVideoItem. Dropping", this);
+    return;
+  }
+
+  g_mutex_lock (&qt_item->priv->lock);
+
+  if (qt_item->priv->new_caps) {
+    GST_DEBUG ("%p caps change from %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT,
+        this, qt_item->priv->caps, qt_item->priv->new_caps);
+    gst_caps_take (&qt_item->priv->caps, qt_item->priv->new_caps);
+    qt_item->priv->new_caps = NULL;
+    qt_item->priv->v_info = qt_item->priv->new_v_info;
+
+    if (!_calculate_par (qt_item, &qt_item->priv->v_info)) {
+      g_mutex_unlock (&qt_item->priv->lock);
+      return;
+    }
+  }
+
+  gst_buffer_replace (&qt_item->priv->buffer, buffer);
+
+  QMetaObject::invokeMethod(qt_item, "update", Qt::QueuedConnection);
+
+  g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
+Qt6GLVideoItem::onSceneGraphInitialized ()
+{
+  QSGRendererInterface *renderer;
+  QOpenGLContext *gl_context;
+
+  if (this->window() == NULL)
+    return;
+
+  renderer = this->window()->rendererInterface();
+  if (!renderer)
+    return;
+
+  if (renderer->graphicsApi() != QSGRendererInterface::GraphicsApi::OpenGL) {
+    GST_WARNING ("%p scene graph initialized with a non-OpenGL renderer interface", this);
+    return;
+  }
+
+  gl_context =
+      static_cast<QOpenGLContext *> (
+      renderer->getResource(
+          this->window(),
+          QSGRendererInterface::Resource::OpenGLContextResource));
+
+  GST_DEBUG ("%p scene graph initialization with Qt GL context %p", this,
+      gl_context);
+
+  if (this->priv->qt_context == gl_context)
+    return;
+
+  this->priv->qt_context = gl_context;
+  if (this->priv->qt_context == NULL) {
+    g_assert_not_reached ();
+    return;
+  }
+
+  this->priv->initted = gst_qml6_get_gl_wrapcontext (this->priv->display,
+      &this->priv->other_context, &this->priv->context);
+
+  GST_DEBUG ("%p created wrapped GL context %" GST_PTR_FORMAT, this,
+      this->priv->other_context);
+
+  emit itemInitializedChanged();
+}
+
+void
+Qt6GLVideoItem::onSceneGraphInvalidated ()
+{
+  this->priv->m_node = nullptr;
+  GST_FIXME ("%p scene graph invalidated", this);
+}
+
+/**
+ * Retrieve and populate the GL context information from the current
+ * OpenGL context.
+ */
+gboolean
+Qt6GLVideoItemInterface::initWinSys ()
+{
+  QMutexLocker locker(&lock);
+
+  GError *error = NULL;
+
+  if (qt_item == NULL)
+    return FALSE;
+
+  g_mutex_lock (&qt_item->priv->lock);
+
+  if (qt_item->priv->display && qt_item->priv->qt_context
+      && qt_item->priv->other_context && qt_item->priv->context) {
+    /* already have the necessary state */
+    g_mutex_unlock (&qt_item->priv->lock);
+    return TRUE;
+  }
+
+  if (!GST_IS_GL_DISPLAY (qt_item->priv->display)) {
+    GST_ERROR ("%p failed to retrieve display connection %" GST_PTR_FORMAT,
+        qt_item, qt_item->priv->display);
+    g_mutex_unlock (&qt_item->priv->lock);
+    return FALSE;
+  }
+
+  if (!GST_IS_GL_CONTEXT (qt_item->priv->other_context)) {
+    GST_ERROR ("%p failed to retrieve wrapped context %" GST_PTR_FORMAT, qt_item,
+        qt_item->priv->other_context);
+    g_mutex_unlock (&qt_item->priv->lock);
+    return FALSE;
+  }
+
+  qt_item->priv->context = gst_gl_context_new (qt_item->priv->display);
+
+  if (!qt_item->priv->context) {
+    g_mutex_unlock (&qt_item->priv->lock);
+    return FALSE;
+  }
+
+  if (!gst_gl_context_create (qt_item->priv->context, qt_item->priv->other_context,
+        &error)) {
+    GST_ERROR ("%s", error->message);
+    g_mutex_unlock (&qt_item->priv->lock);
+    return FALSE;
+  }
+
+  g_mutex_unlock (&qt_item->priv->lock);
+  return TRUE;
+}
+
+void
+Qt6GLVideoItem::handleWindowChanged (QQuickWindow * win)
+{
+  if (win) {
+    if (win->isSceneGraphInitialized ())
+      win->scheduleRenderJob (new RenderJob (std::
+              bind (&Qt6GLVideoItem::onSceneGraphInitialized, this)),
+          QQuickWindow::BeforeSynchronizingStage);
+    else
+      connect (win, SIGNAL (sceneGraphInitialized ()), this,
+          SLOT (onSceneGraphInitialized ()), Qt::DirectConnection);
+
+    connect (win, SIGNAL (sceneGraphInvalidated ()), this,
+        SLOT (onSceneGraphInvalidated ()), Qt::DirectConnection);
+  } else {
+    this->priv->qt_context = NULL;
+    this->priv->initted = FALSE;
+  }
+  this->priv->m_node = nullptr;
+}
+
+void
+Qt6GLVideoItem::releaseResources()
+{
+  this->priv->m_node = nullptr;
+}
+
+gboolean
+Qt6GLVideoItemInterface::setCaps (GstCaps * caps)
+{
+  QMutexLocker locker(&lock);
+  GstVideoInfo v_info;
+
+  g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
+  g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
+
+  if (qt_item == NULL)
+    return FALSE;
+
+  if (qt_item->priv->caps && gst_caps_is_equal_fixed (qt_item->priv->caps, caps))
+    return TRUE;
+
+  if (!gst_video_info_from_caps (&v_info, caps))
+    return FALSE;
+
+  g_mutex_lock (&qt_item->priv->lock);
+
+  GST_DEBUG ("%p set caps %" GST_PTR_FORMAT, qt_item, caps);
+
+  gst_caps_replace (&qt_item->priv->new_caps, caps);
+
+  qt_item->priv->new_v_info = v_info;
+
+  g_mutex_unlock (&qt_item->priv->lock);
+
+  return TRUE;
+}
+
+GstGLContext *
+Qt6GLVideoItemInterface::getQtContext ()
+{
+  QMutexLocker locker(&lock);
+
+  if (!qt_item || !qt_item->priv->other_context)
+    return NULL;
+
+  return (GstGLContext *) gst_object_ref (qt_item->priv->other_context);
+}
+
+GstGLContext *
+Qt6GLVideoItemInterface::getContext ()
+{
+  QMutexLocker locker(&lock);
+
+  if (!qt_item || !qt_item->priv->context)
+    return NULL;
+
+  return (GstGLContext *) gst_object_ref (qt_item->priv->context);
+}
+
+GstGLDisplay *
+Qt6GLVideoItemInterface::getDisplay()
+{
+  QMutexLocker locker(&lock);
+
+  if (!qt_item || !qt_item->priv->display)
+    return NULL;
+
+  return (GstGLDisplay *) gst_object_ref (qt_item->priv->display);
+}
+
+void
+Qt6GLVideoItemInterface::setDAR(gint num, gint den)
+{
+  QMutexLocker locker(&lock);
+  if (!qt_item)
+    return;
+  qt_item->setDAR(num, den);
+}
+
+void
+Qt6GLVideoItemInterface::getDAR(gint * num, gint * den)
+{
+  QMutexLocker locker(&lock);
+  if (!qt_item)
+    return;
+  qt_item->getDAR (num, den);
+}
+
+void
+Qt6GLVideoItemInterface::setForceAspectRatio(bool force_aspect_ratio)
+{
+  QMutexLocker locker(&lock);
+  if (!qt_item)
+    return;
+  qt_item->setForceAspectRatio(force_aspect_ratio);
+}
+
+bool
+Qt6GLVideoItemInterface::getForceAspectRatio()
+{
+  QMutexLocker locker(&lock);
+  if (!qt_item)
+    return FALSE;
+  return qt_item->getForceAspectRatio();
+}
+
+void
+Qt6GLVideoItemInterface::invalidateRef()
+{
+  QMutexLocker locker(&lock);
+  qt_item = NULL;
+}
+
diff --git a/subprojects/gst-plugins-good/ext/qt6/qt6glitem.h b/subprojects/gst-plugins-good/ext/qt6/qt6glitem.h
new file mode 100644 (file)
index 0000000..bfe28a1
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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 __QT6_GL_ITEM_H__
+#define __QT6_GL_ITEM_H__
+
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include "gstqt6gl.h"
+#include <QtCore/QMutex>
+#include <QtQuick/QQuickItem>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
+
+typedef struct _Qt6GLVideoItemPrivate Qt6GLVideoItemPrivate;
+
+class Qt6GLVideoItem;
+
+class Qt6GLVideoItemInterface : public QObject
+{
+    Q_OBJECT
+    QML_ELEMENT
+public:
+    Qt6GLVideoItemInterface (Qt6GLVideoItem *w) : qt_item (w), lock() {};
+
+    void invalidateRef();
+
+    void setSink (GstElement * sink);
+    void setBuffer (GstBuffer * buffer);
+    gboolean setCaps (GstCaps *caps);
+    gboolean initWinSys ();
+    GstGLContext *getQtContext();
+    GstGLContext *getContext();
+    GstGLDisplay *getDisplay();
+    Qt6GLVideoItem *videoItem () { return qt_item; };
+
+    void setDAR(gint, gint);
+    void getDAR(gint *, gint *);
+    void setForceAspectRatio(bool);
+    bool getForceAspectRatio();
+private:
+    Qt6GLVideoItem *qt_item;
+    QMutex lock;
+};
+
+class Qt6GLVideoItem : public QQuickItem, protected QOpenGLFunctions
+{
+    Q_OBJECT
+    QML_ELEMENT
+
+    Q_PROPERTY(bool itemInitialized
+               READ itemInitialized
+               NOTIFY itemInitializedChanged)
+    Q_PROPERTY(bool forceAspectRatio
+               READ getForceAspectRatio
+               WRITE setForceAspectRatio
+               NOTIFY forceAspectRatioChanged)
+
+public:
+    Qt6GLVideoItem();
+    ~Qt6GLVideoItem();
+
+    void setDAR(gint, gint);
+    void getDAR(gint *, gint *);
+    void setForceAspectRatio(bool);
+    bool getForceAspectRatio();
+    bool itemInitialized();
+
+    QSharedPointer<Qt6GLVideoItemInterface> getInterface() { return proxy; };
+    /* private for C interface ... */
+    Qt6GLVideoItemPrivate *priv;
+
+Q_SIGNALS:
+    void itemInitializedChanged();
+    void forceAspectRatioChanged(bool);
+
+private Q_SLOTS:
+    void handleWindowChanged(QQuickWindow * win);
+    void onSceneGraphInitialized();
+    void onSceneGraphInvalidated();
+
+protected:
+    QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData) override;
+    void releaseResources() override;
+    void wheelEvent(QWheelEvent *) override;
+    void hoverEnterEvent(QHoverEvent *) override;
+    void hoverLeaveEvent (QHoverEvent *) override;
+    void hoverMoveEvent (QHoverEvent *) override;
+    void mousePressEvent(QMouseEvent*) override;
+    void mouseReleaseEvent(QMouseEvent*) override;
+    void touchEvent(QTouchEvent*) override;
+
+private:
+
+    void setViewportSize(const QSize &size);
+    void shareContext();
+
+    void fitStreamToAllocatedSize(GstVideoRectangle * result);
+    QPointF mapPointToStreamSize(QPointF);
+
+    void sendMouseEvent(QMouseEvent * event, gboolean is_press);
+
+    quint32 mousePressedButton;
+    bool mouseHovering;
+
+    QSharedPointer<Qt6GLVideoItemInterface> proxy;
+};
+
+#endif /* __QT_GL_ITEM_H__ */
index 4c2d1990a2c651fc21b6b691380be8fe9e578a3f..81b78d0a88399b7e9f1285ea1455962555afe97e 100644 (file)
@@ -67,6 +67,7 @@ option('osxvideo', type : 'feature', value : 'auto', description : 'macOS Cocoa
 option('png', type : 'feature', value : 'auto', description : 'PNG image codec plugin')
 option('pulse', type : 'feature', value : 'auto', description : 'Pulseaudio audio source/sink plugin')
 option('qt5', type : 'feature', value : 'auto', yield : true, description : 'Qt5 QML video sink plugin')
+option('qt6', type : 'feature', value : 'auto', yield : true, description : 'Qt6 QML video sink plugin')
 option('shout2', type : 'feature', value : 'auto', description : 'Shout-casting network sink plugin based on libshout2')
 option('soup', type : 'feature', value : 'auto', description : 'libsoup HTTP client source/sink plugin')
 option('speex', type : 'feature', value : 'auto', description : 'Speex audio codec plugin')
index d5fa1279fdf4fad1d951a5dadd7c2cc3d0935176..0af2aa64a338a8071a79ac44fdc4d1fd66e0c7a5 100644 (file)
@@ -2,6 +2,7 @@ subdir('audiofx')
 subdir('cairo')
 subdir('level')
 subdir('qt')
+subdir('qt6')
 
 if is_variable('gstrpicamsrc')
   subdir('rpicamsrc')
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/meson.build b/subprojects/gst-plugins-good/tests/examples/qt6/meson.build
new file mode 100644 (file)
index 0000000..23d3fb6
--- /dev/null
@@ -0,0 +1,17 @@
+if qt6_option.disabled()
+  subdir_done()
+endif
+
+# We already did all the checks when building the qt6 plugin
+if not qt6qml_dep.found()
+  subdir_done()
+endif
+
+qt6qml_example_deps = dependency('qt6', modules : ['Core', 'Gui', 'Widgets', 'Qml', 'Quick'],
+                         required: get_option('examples'))
+
+if not qt6qml_example_deps.found()
+  subdir_done()
+endif
+
+subdir('qmlsink')
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.cpp b/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.cpp
new file mode 100644 (file)
index 0000000..81f3052
--- /dev/null
@@ -0,0 +1,85 @@
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QRunnable>
+#include <gst/gst.h>
+
+class SetPlaying : public QRunnable
+{
+public:
+  SetPlaying(GstElement *);
+  ~SetPlaying();
+
+  void run ();
+
+private:
+  GstElement * pipeline_;
+};
+
+SetPlaying::SetPlaying (GstElement * pipeline)
+{
+  this->pipeline_ = pipeline ? static_cast<GstElement *> (gst_object_ref (pipeline)) : NULL;
+}
+
+SetPlaying::~SetPlaying ()
+{
+  if (this->pipeline_)
+    gst_object_unref (this->pipeline_);
+}
+
+void
+SetPlaying::run ()
+{
+  if (this->pipeline_)
+    gst_element_set_state (this->pipeline_, GST_STATE_PLAYING);
+}
+
+int main(int argc, char *argv[])
+{
+  int ret;
+
+  gst_init (&argc, &argv);
+
+  {
+    QGuiApplication app(argc, argv);
+
+    QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
+
+    GstElement *pipeline = gst_pipeline_new (NULL);
+    GstElement *src = gst_element_factory_make ("videotestsrc", NULL);
+    GstElement *glupload = gst_element_factory_make ("glupload", NULL);
+    /* the plugin must be loaded before loading the qml file to register the
+     * GstGLVideoItem qml item */
+    GstElement *sink = gst_element_factory_make ("qml6glsink", NULL);
+
+    g_assert (src && glupload && sink);
+
+    gst_bin_add_many (GST_BIN (pipeline), src, glupload, sink, NULL);
+    gst_element_link_many (src, glupload, sink, NULL);
+
+    QQmlApplicationEngine engine;
+    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+    QQuickItem *videoItem;
+    QQuickWindow *rootObject;
+
+    /* find and set the videoItem on the sink */
+    rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());
+    videoItem = rootObject->findChild<QQuickItem *> ("videoItem");
+    g_assert (videoItem);
+    g_object_set(sink, "widget", videoItem, NULL);
+
+    rootObject->scheduleRenderJob (new SetPlaying (pipeline),
+        QQuickWindow::BeforeSynchronizingStage);
+
+    ret = app.exec();
+
+    gst_element_set_state (pipeline, GST_STATE_NULL);
+    gst_object_unref (pipeline);
+  }
+
+  gst_deinit ();
+
+  return ret;
+}
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.qml b/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/main.qml
new file mode 100644 (file)
index 0000000..bf182b3
--- /dev/null
@@ -0,0 +1,59 @@
+import QtQuick 6.0
+import QtQuick.Controls 6.0
+import QtQuick.Dialogs 6.0
+import QtQuick.Window 6.0
+
+import org.freedesktop.gstreamer.Qt6GLVideoItem 1.0
+
+ApplicationWindow {
+    id: window
+    visible: true
+    width: 640
+    height: 480
+    x: 30
+    y: 30
+    color: "black"
+
+    Item {
+        anchors.fill: parent
+
+        GstGLQt6VideoItem {
+            id: video
+            objectName: "videoItem"
+            anchors.centerIn: parent
+            width: parent.width
+            height: parent.height
+        }
+
+        Rectangle {
+            color: Qt.rgba(1, 1, 1, 0.7)
+            border.width: 1
+            border.color: "white"
+            anchors.bottom: video.bottom
+            anchors.bottomMargin: 15
+            anchors.horizontalCenter: parent.horizontalCenter
+            width : parent.width - 30
+            height: parent.height - 30
+            radius: 8
+
+            MouseArea {
+                id: mousearea
+                anchors.fill: parent
+                hoverEnabled: true
+                onEntered: {
+                    parent.opacity = 1.0
+                    hidetimer.start()
+                }
+            }
+
+            Timer {
+                id: hidetimer
+                interval: 5000
+                onTriggered: {
+                    parent.opacity = 0.0
+                    stop()
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/meson.build b/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/meson.build
new file mode 100644 (file)
index 0000000..93bf6aa
--- /dev/null
@@ -0,0 +1,12 @@
+sources = [
+  'main.cpp',
+]
+
+
+qt_preprocessed = qt6_mod.preprocess(qresources : 'qmlsink.qrc')
+executable('qml6sink', sources, qt_preprocessed,
+    dependencies : [gst_dep, qt6qml_example_deps],
+    override_options : ['cpp_std=c++17'],
+    c_args : gst_plugins_good_args,
+    include_directories : [configinc],
+    install: false)
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/play.pro b/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/play.pro
new file mode 100644 (file)
index 0000000..9ecaf87
--- /dev/null
@@ -0,0 +1,20 @@
+TEMPLATE = app
+
+QT += qml quick widgets
+
+QT_CONFIG -= no-pkg-config
+CONFIG += link_pkgconfig debug
+PKGCONFIG = \
+    gstreamer-1.0 \
+    gstreamer-video-1.0
+
+DEFINES += GST_USE_UNSTABLE_API
+
+INCLUDEPATH += ../lib
+
+SOURCES += main.cpp
+
+RESOURCES += qmlsink.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
diff --git a/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/qmlsink.qrc b/subprojects/gst-plugins-good/tests/examples/qt6/qmlsink/qmlsink.qrc
new file mode 100644 (file)
index 0000000..5f6483a
--- /dev/null
@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+    </qresource>
+</RCC>