From d5807fc0840117a8472922d69df76ddebf633325 Mon Sep 17 00:00:00 2001
From: Colin Kinloch <colin.kinloch@collabora.com>
Date: Thu, 16 Feb 2023 16:21:33 -0500
Subject: [PATCH 11/17] wayladnsink: Add DRM Dumb allocator support

If the input is not a DMABuf, attempt to copy into a DRM Dumb
buffer and import it has a DMABuf. This will offload the
compositor from actually doing this copy (needed to handle SHM)
and may allow the software decoded stream to be rendered to
an HW layer, or even reach through some better accelerated
GL import path.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3801>
---
 ext/gtk/gstgtkwaylandsink.c  | 121 ++++++++++++++++++++++++++++++++++-
 ext/wayland/gstwaylandsink.c | 110 +++++++++++++++++++++++++++++++
 ext/wayland/gstwaylandsink.h |   3 +
 3 files changed, 233 insertions(+), 1 deletion(-)

diff --git a/ext/gtk/gstgtkwaylandsink.c b/ext/gtk/gstgtkwaylandsink.c
index c46207c..438294b 100644
--- a/ext/gtk/gstgtkwaylandsink.c
+++ b/ext/gtk/gstgtkwaylandsink.c
@@ -87,7 +87,8 @@ enum
   PROP_0,
   PROP_WIDGET,
   PROP_DISPLAY,
-  PROP_ROTATE_METHOD
+  PROP_ROTATE_METHOD,
+  PROP_DRM_DEVICE,
 };
 
 typedef struct _GstGtkWaylandSinkPrivate
@@ -119,6 +120,9 @@ typedef struct _GstGtkWaylandSinkPrivate
   GstVideoOrientationMethod current_rotate_method;
 
   struct wl_callback *callback;
+
+  gchar *drm_device;
+  gboolean skip_dumb_buffer_copy;
 } GstGtkWaylandSinkPrivate;
 
 #define gst_gtk_wayland_sink_parent_class parent_class
@@ -161,6 +165,17 @@ gst_gtk_wayland_sink_class_init (GstGtkWaylandSinkClass * klass)
           GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstGtkWaylandSink:drm-device:
+   *
+   * Since: 1.24
+   */
+  g_object_class_install_property (gobject_class, PROP_DRM_DEVICE,
+      g_param_spec_string ("drm-device", "DRM Device", "Path of the "
+          "DRM device to use for dumb buffer allocation",
+          NULL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_change_state);
 
@@ -207,6 +222,8 @@ gst_gtk_wayland_sink_finalize (GObject * object)
   g_clear_object (&priv->gtk_widget);
   gst_clear_caps (&priv->caps);
 
+  g_free (priv->drm_device);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -379,6 +396,11 @@ gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
     case PROP_ROTATE_METHOD:
       g_value_set_enum (value, priv->current_rotate_method);
       break;
+    case PROP_DRM_DEVICE:
+      GST_OBJECT_LOCK (self);
+      g_value_set_string (value, priv->drm_device);
+      GST_OBJECT_UNLOCK (self);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -390,12 +412,19 @@ gst_gtk_wayland_sink_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
 {
   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
 
   switch (prop_id) {
     case PROP_ROTATE_METHOD:
       gst_gtk_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
           FALSE);
       break;
+    case PROP_DRM_DEVICE:
+      GST_OBJECT_LOCK (self);
+      priv->drm_device = g_value_dup_string (value);
+      GST_OBJECT_UNLOCK (self);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -710,6 +739,8 @@ gst_gtk_wayland_sink_change_state (GstElement * element,
 
   switch (transition) {
     case GST_STATE_CHANGE_READY_TO_NULL:
+      g_clear_object (&priv->pool);
+      /* fallthrough */
     case GST_STATE_CHANGE_NULL_TO_NULL:
       gst_gtk_invoke_on_main ((GThreadFunc)
           gst_gtk_wayland_sink_stop_on_main, element);
@@ -860,6 +891,41 @@ gst_gtk_wayland_activate_shm_pool (GstGtkWaylandSink * self)
   return TRUE;
 }
 
+static gboolean
+gst_gtk_wayland_activate_drm_dumb_pool (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstAllocator *alloc;
+
+  if (!priv->drm_device)
+    return FALSE;
+
+  if (priv->pool && gst_buffer_pool_is_active (priv->pool)) {
+    GstStructure *config = gst_buffer_pool_get_config (priv->pool);
+    gboolean ret = FALSE;
+    gboolean is_drm_dumb = FALSE;
+
+    ret = gst_buffer_pool_config_get_allocator (config, &alloc, NULL);
+    gst_structure_free (config);
+
+    if (ret && alloc)
+      is_drm_dumb = GST_IS_DRM_DUMB_ALLOCATOR (alloc);
+
+    if (is_drm_dumb)
+      return TRUE;
+  }
+
+  alloc = gst_drm_dumb_allocator_new_with_device_path (priv->drm_device);
+  if (!alloc)
+    return FALSE;
+
+  gst_gtk_wayland_update_pool (self, alloc);
+  gst_object_unref (alloc);
+
+  return TRUE;
+}
+
 static gboolean
 gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
 {
@@ -877,6 +943,7 @@ gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
 
   format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
   priv->video_info_changed = TRUE;
+  priv->skip_dumb_buffer_copy = FALSE;
 
   /* free pooled buffer used with previous caps */
   if (priv->pool) {
@@ -1086,8 +1153,60 @@ gst_gtk_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
     if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
       wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, priv->display,
           &priv->video_info);
+
+    /* DMABuf did not work, let try and make this a dmabuf, it does not matter
+     * if it was a SHM since the compositor needs to copy that anyway, and
+     * offloading the compositor from a copy helps maintaining a smoother
+     * desktop.
+     */
+    if (!priv->skip_dumb_buffer_copy) {
+      GstVideoFrame src, dst;
+
+      if (!gst_gtk_wayland_activate_drm_dumb_pool (self)) {
+        priv->skip_dumb_buffer_copy = TRUE;
+        goto handle_shm;
+      }
+
+      ret = gst_buffer_pool_acquire_buffer (priv->pool, &to_render, NULL);
+      if (ret != GST_FLOW_OK)
+        goto no_buffer;
+
+      wlbuffer = gst_buffer_get_wl_buffer (priv->display, to_render);
+
+      /* attach a wl_buffer if there isn't one yet */
+      if (G_UNLIKELY (!wlbuffer)) {
+        wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (to_render,
+            priv->display, &priv->video_info);
+
+        if (G_UNLIKELY (!wbuf)) {
+          GST_WARNING_OBJECT (self, "failed to import DRM Dumb dmabuf");
+          gst_clear_buffer (&to_render);
+          priv->skip_dumb_buffer_copy = TRUE;
+          goto handle_shm;
+        }
+
+        wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, priv->display);
+      }
+
+      if (!gst_video_frame_map (&dst, &priv->video_info, to_render,
+              GST_MAP_WRITE))
+        goto dst_map_failed;
+
+      if (!gst_video_frame_map (&src, &priv->video_info, buffer, GST_MAP_READ)) {
+        gst_video_frame_unmap (&dst);
+        goto src_map_failed;
+      }
+
+      gst_video_frame_copy (&dst, &src);
+
+      gst_video_frame_unmap (&src);
+      gst_video_frame_unmap (&dst);
+
+      goto render;
+    }
   }
 
+handle_shm:
   if (!wbuf && gst_wl_display_check_format_for_shm (priv->display, format)) {
     if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
       wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
diff --git a/ext/wayland/gstwaylandsink.c b/ext/wayland/gstwaylandsink.c
index aa39447..bb7fcff 100644
--- a/ext/wayland/gstwaylandsink.c
+++ b/ext/wayland/gstwaylandsink.c
@@ -61,6 +61,7 @@ enum
   PROP_DISPLAY,
   PROP_FULLSCREEN,
   PROP_ROTATE_METHOD,
+  PROP_DRM_DEVICE,
   PROP_LAST
 };
 
@@ -177,6 +178,18 @@ gst_wayland_sink_class_init (GstWaylandSinkClass * klass)
           GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+ /**
+   * waylandsink:drm-device:
+   *
+   * Since: 1.24
+   */
+  g_object_class_install_property (gobject_class, PROP_DRM_DEVICE,
+      g_param_spec_string ("drm-device", "DRM Device", "Path of the "
+          "DRM device to use for dumb buffer allocation",
+          NULL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+
+
  /**
   * waylandsink:render-rectangle:
   *
@@ -266,6 +279,11 @@ gst_wayland_sink_get_property (GObject * object,
       g_value_set_enum (value, self->current_rotate_method);
       GST_OBJECT_UNLOCK (self);
       break;
+    case PROP_DRM_DEVICE:
+      GST_OBJECT_LOCK (self);
+      g_value_set_string (value, self->drm_device);
+      GST_OBJECT_UNLOCK (self);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -293,6 +311,11 @@ gst_wayland_sink_set_property (GObject * object,
       gst_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
           FALSE);
       break;
+    case PROP_DRM_DEVICE:
+      GST_OBJECT_LOCK (self);
+      self->drm_device = g_value_dup_string (value);
+      GST_OBJECT_UNLOCK (self);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -318,6 +341,7 @@ gst_wayland_sink_finalize (GObject * object)
   gst_clear_caps (&self->caps);
 
   g_free (self->display_name);
+  g_free (self->drm_device);
 
   g_mutex_clear (&self->display_lock);
   g_mutex_clear (&self->render_lock);
@@ -629,6 +653,39 @@ gst_wayland_activate_shm_pool (GstWaylandSink * self)
   return TRUE;
 }
 
+static gboolean
+gst_wayland_activate_drm_dumb_pool (GstWaylandSink * self)
+{
+  GstAllocator *alloc;
+
+  if (!self->drm_device)
+    return FALSE;
+
+  if (self->pool && gst_buffer_pool_is_active (self->pool)) {
+    GstStructure *config = gst_buffer_pool_get_config (self->pool);
+    gboolean ret = FALSE;
+    gboolean is_drm_dumb = FALSE;
+
+    ret = gst_buffer_pool_config_get_allocator (config, &alloc, NULL);
+    gst_structure_free (config);
+
+    if (ret && alloc)
+      is_drm_dumb = GST_IS_DRM_DUMB_ALLOCATOR (alloc);
+
+    if (is_drm_dumb)
+      return TRUE;
+  }
+
+  alloc = gst_drm_dumb_allocator_new_with_device_path (self->drm_device);
+  if (!alloc)
+    return FALSE;
+
+  gst_wayland_update_pool (self, alloc);
+  gst_object_unref (alloc);
+
+  return TRUE;
+}
+
 static gboolean
 gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
 {
@@ -644,6 +701,7 @@ gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
 
   format = GST_VIDEO_INFO_FORMAT (&self->video_info);
   self->video_info_changed = TRUE;
+  self->skip_dumb_buffer_copy = FALSE;
 
   /* free pooled buffer used with previous caps */
   if (self->pool) {
@@ -848,8 +906,60 @@ gst_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
     if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
       wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, self->display,
           &self->video_info);
+
+    /* DMABuf did not work, let try and make this a dmabuf, it does not matter
+     * if it was a SHM since the compositor needs to copy that anyway, and
+     * offloading the compositor from a copy helps maintaining a smoother
+     * desktop.
+     */
+    if (!self->skip_dumb_buffer_copy) {
+      GstVideoFrame src, dst;
+
+      if (!gst_wayland_activate_drm_dumb_pool (self)) {
+        self->skip_dumb_buffer_copy = TRUE;
+        goto handle_shm;
+      }
+
+      ret = gst_buffer_pool_acquire_buffer (self->pool, &to_render, NULL);
+      if (ret != GST_FLOW_OK)
+        goto no_buffer;
+
+      wlbuffer = gst_buffer_get_wl_buffer (self->display, to_render);
+
+      /* attach a wl_buffer if there isn't one yet */
+      if (G_UNLIKELY (!wlbuffer)) {
+        wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (to_render,
+            self->display, &self->video_info);
+
+        if (G_UNLIKELY (!wbuf)) {
+          GST_WARNING_OBJECT (self, "failed to import DRM Dumb dmabuf");
+          gst_clear_buffer (&to_render);
+          self->skip_dumb_buffer_copy = TRUE;
+          goto handle_shm;
+        }
+
+        wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, self->display);
+      }
+
+      if (!gst_video_frame_map (&dst, &self->video_info, to_render,
+              GST_MAP_WRITE))
+        goto dst_map_failed;
+
+      if (!gst_video_frame_map (&src, &self->video_info, buffer, GST_MAP_READ)) {
+        gst_video_frame_unmap (&dst);
+        goto src_map_failed;
+      }
+
+      gst_video_frame_copy (&dst, &src);
+
+      gst_video_frame_unmap (&src);
+      gst_video_frame_unmap (&dst);
+
+      goto render;
+    }
   }
 
+handle_shm:
   if (!wbuf && gst_wl_display_check_format_for_shm (self->display, format)) {
     if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
       wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, self->display,
diff --git a/ext/wayland/gstwaylandsink.h b/ext/wayland/gstwaylandsink.h
index 1ff50d1..ca3400e 100644
--- a/ext/wayland/gstwaylandsink.h
+++ b/ext/wayland/gstwaylandsink.h
@@ -69,6 +69,9 @@ struct _GstWaylandSink
   GstVideoOrientationMethod current_rotate_method;
 
   struct wl_callback *callback;
+
+  gchar *drm_device;
+  gboolean skip_dumb_buffer_copy;
 };
 
 struct _GstWaylandSinkClass
-- 
2.25.1

