libs: window: wayland: use dmabuf protocol if available
authorMichael Olbrich <m.olbrich@pengutronix.de>
Wed, 22 Jul 2020 08:01:41 +0000 (10:01 +0200)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Mon, 27 Jul 2020 15:02:19 +0000 (15:02 +0000)
Currently vaGetSurfaceBufferWl() is used to create wayland buffers.
Unfortunately this is not implemented by the 'media-driver' and Mesa VA-API
drivers. And the implementation provided by 'intel-vaapi-driver' is not
compatible with a Wayland server that uses the iris Mesa driver.

So create the Wayland buffers manually with the zwp_linux_dmabuf_v1 wayland
protocol. Formats and modifiers supported by the Wayland server are taken
into account. If necessary, VPP is enabled to convert the buffer into a
supported format.

Fall back to vaGetSurfaceBufferWl() if creating buffers via dambuf protocol
fails.

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

gst-libs/gst/vaapi/gstvaapiwindow_wayland.c

index abf492f..b1b32e6 100644 (file)
@@ -37,6 +37,8 @@
 #include "gstvaapifilter.h"
 #include "gstvaapisurfacepool.h"
 
+#include <unistd.h>
+
 GST_DEBUG_CATEGORY_EXTERN (gst_debug_vaapi_window);
 #define GST_CAT_DEFAULT gst_debug_vaapi_window
 
@@ -113,6 +115,7 @@ struct _GstVaapiWindowWaylandPrivate
   volatile guint num_frames_pending;
   gint configure_pending;
   gboolean need_vpp;
+  gboolean dmabuf_broken;
 };
 
 /**
@@ -532,6 +535,299 @@ static const struct wl_buffer_listener frame_buffer_listener = {
   frame_release_callback
 };
 
+typedef enum
+{
+  GST_VAAPI_DMABUF_SUCCESS,
+  GST_VAAPI_DMABUF_BAD_FLAGS,
+  GST_VAAPI_DMABUF_BAD_FORMAT,
+  GST_VAAPI_DMABUF_BAD_MODIFIER,
+  GST_VAAPI_DMABUF_NOT_SUPPORTED,
+  GST_VAAPI_DMABUF_FLUSH,
+
+} GstVaapiDmabufStatus;
+
+#define DRM_FORMAT_MOD_INVALID 0xffffffffffffff
+
+static GstVaapiDmabufStatus
+dmabuf_format_supported (GstVaapiDisplayWaylandPrivate * const priv_display,
+    guint format, guint64 modifier)
+{
+  GArray *formats = priv_display->dmabuf_formats;
+  gboolean linear = FALSE;
+  gint i;
+
+  for (i = 0; i < formats->len; i++) {
+    GstDRMFormat fmt = g_array_index (formats, GstDRMFormat, i);
+    if (fmt.format == format && (fmt.modifier == modifier ||
+            (fmt.modifier == DRM_FORMAT_MOD_INVALID && modifier == 0)))
+      return GST_VAAPI_DMABUF_SUCCESS;
+    if (fmt.format == format && (fmt.modifier == 0 ||
+            fmt.modifier == DRM_FORMAT_MOD_INVALID))
+      linear = TRUE;
+  }
+  if (linear)
+    return GST_VAAPI_DMABUF_BAD_MODIFIER;
+  else
+    return GST_VAAPI_DMABUF_BAD_FORMAT;
+}
+
+GstVideoFormat
+check_format (GstVaapiDisplay * const display, gint index,
+    GstVideoFormat expect)
+{
+  GstVaapiDisplayWaylandPrivate *const priv_display =
+      GST_VAAPI_DISPLAY_WAYLAND_GET_PRIVATE (display);
+  GArray *formats = priv_display->dmabuf_formats;
+  GstDRMFormat fmt = g_array_index (formats, GstDRMFormat, index);
+  GstVideoFormat format = gst_vaapi_video_format_from_drm_format (fmt.format);
+  GstVaapiSurface *surface;
+
+  /* unkown formats should be filtered out in the display */
+  g_assert (format != GST_VIDEO_FORMAT_UNKNOWN);
+
+  if ((expect != GST_VIDEO_FORMAT_UNKNOWN) && (format != expect))
+    return GST_VIDEO_FORMAT_UNKNOWN;
+
+  surface = gst_vaapi_surface_new_with_format (display, format, 64, 64,
+      fmt.modifier == 0 ? GST_VAAPI_SURFACE_ALLOC_FLAG_LINEAR_STORAGE : 0);
+  if (!surface)
+    return GST_VIDEO_FORMAT_UNKNOWN;
+
+  gst_vaapi_surface_unref (surface);
+
+  return format;
+}
+
+GstVideoFormat
+choose_next_format (GstVaapiDisplay * const display, gint * next_index)
+{
+  GstVaapiDisplayWaylandPrivate *const priv_display =
+      GST_VAAPI_DISPLAY_WAYLAND_GET_PRIVATE (display);
+  GArray *formats = priv_display->dmabuf_formats;
+  GstVideoFormat format;
+  gint i;
+
+  if (*next_index < 0) {
+    *next_index = 0;
+    /* try GST_VIDEO_FORMAT_RGBA first */
+    for (i = 0; i < formats->len; i++) {
+      format = check_format (display, i, GST_VIDEO_FORMAT_RGBA);
+      if (format != GST_VIDEO_FORMAT_UNKNOWN)
+        return format;
+    }
+  }
+
+  for (i = *next_index; i < formats->len; i++) {
+    format = check_format (display, i, GST_VIDEO_FORMAT_UNKNOWN);
+    if (format != GST_VIDEO_FORMAT_UNKNOWN) {
+      *next_index = i + 1;
+      return format;
+    }
+  }
+  *next_index = formats->len;
+  return GST_VIDEO_FORMAT_UNKNOWN;
+}
+
+static GstVaapiDmabufStatus
+dmabuf_buffer_from_surface (GstVaapiWindow * window, GstVaapiSurface * surface,
+    const GstVaapiRectangle * rect, guint va_flags,
+    struct wl_buffer **out_buffer)
+{
+  GstVaapiDisplay *const display = GST_VAAPI_WINDOW_DISPLAY (window);
+  GstVaapiDisplayWaylandPrivate *const priv_display =
+      GST_VAAPI_DISPLAY_WAYLAND_GET_PRIVATE (display);
+  struct zwp_linux_buffer_params_v1 *params;
+  struct wl_buffer *buffer = NULL;
+  VADRMPRIMESurfaceDescriptor desc;
+  VAStatus status;
+  GstVaapiDmabufStatus ret;
+  guint format, i, j, plane = 0;
+
+  if (!priv_display->dmabuf)
+    return GST_VAAPI_DMABUF_NOT_SUPPORTED;
+
+  if ((va_flags & (VA_TOP_FIELD | VA_BOTTOM_FIELD)) != VA_FRAME_PICTURE)
+    return GST_VAAPI_DMABUF_BAD_FLAGS;
+
+  GST_VAAPI_WINDOW_LOCK_DISPLAY (window);
+  status = vaExportSurfaceHandle (GST_VAAPI_DISPLAY_VADISPLAY (display),
+      GST_VAAPI_SURFACE_ID (surface), VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+      VA_EXPORT_SURFACE_SEPARATE_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, &desc);
+  /* Try again with composed layers, in case the format is supported there */
+  if (status == VA_STATUS_ERROR_INVALID_SURFACE)
+    status = vaExportSurfaceHandle (GST_VAAPI_DISPLAY_VADISPLAY (display),
+        GST_VAAPI_SURFACE_ID (surface), VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+        VA_EXPORT_SURFACE_COMPOSED_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, &desc);
+  GST_VAAPI_WINDOW_UNLOCK_DISPLAY (window);
+
+  if (!vaapi_check_status (status, "vaExportSurfaceHandle()")) {
+    if (status == VA_STATUS_ERROR_UNIMPLEMENTED)
+      return GST_VAAPI_DMABUF_NOT_SUPPORTED;
+    else
+      return GST_VAAPI_DMABUF_BAD_FORMAT;
+  }
+
+  format = gst_vaapi_drm_format_from_va_fourcc (desc.fourcc);
+  params = zwp_linux_dmabuf_v1_create_params (priv_display->dmabuf);
+  for (i = 0; i < desc.num_layers; i++) {
+    for (j = 0; j < desc.layers[i].num_planes; ++j) {
+      gint object = desc.layers[i].object_index[j];
+      guint64 modifier = desc.objects[object].drm_format_modifier;
+
+      ret = dmabuf_format_supported (priv_display, format, modifier);
+      if (ret != GST_VAAPI_DMABUF_SUCCESS) {
+        GST_DEBUG ("skipping unsupported format/modifier %s/0x%"
+            G_GINT64_MODIFIER "x", gst_video_format_to_string
+            (gst_vaapi_video_format_from_drm_format (format)), modifier);
+        goto out;
+      }
+
+      zwp_linux_buffer_params_v1_add (params,
+          desc.objects[object].fd, plane, desc.layers[i].offset[j],
+          desc.layers[i].pitch[j], modifier >> 32,
+          modifier & G_GUINT64_CONSTANT (0xffffffff));
+      plane++;
+    }
+  }
+
+  buffer = zwp_linux_buffer_params_v1_create_immed (params, rect->width,
+      rect->height, format, 0);
+
+  if (!buffer)
+    ret = GST_VAAPI_DMABUF_NOT_SUPPORTED;
+
+out:
+  zwp_linux_buffer_params_v1_destroy (params);
+
+  for (i = 0; i < desc.num_objects; i++)
+    close (desc.objects[i].fd);
+
+  *out_buffer = buffer;
+  return ret;
+}
+
+static gboolean
+buffer_from_surface (GstVaapiWindow * window, GstVaapiSurface ** surf,
+    const GstVaapiRectangle * src_rect, const GstVaapiRectangle * dst_rect,
+    guint flags, struct wl_buffer **buffer)
+{
+  GstVaapiDisplay *const display = GST_VAAPI_WINDOW_DISPLAY (window);
+  GstVaapiWindowWaylandPrivate *const priv =
+      GST_VAAPI_WINDOW_WAYLAND_GET_PRIVATE (window);
+  GstVaapiSurface *surface;
+  GstVaapiDmabufStatus ret;
+  guint va_flags;
+  VAStatus status;
+  gint format_index = -1;
+
+  va_flags = from_GstVaapiSurfaceRenderFlags (flags);
+
+again:
+  surface = *surf;
+  if (priv->need_vpp) {
+    GstVaapiSurface *vpp_surface = NULL;
+    if (window->has_vpp) {
+      GST_LOG ("VPP: %s <%d, %d, %d, %d> -> %s <%d, %d, %d, %d>",
+          gst_video_format_to_string (gst_vaapi_surface_get_format (surface)),
+          src_rect->x, src_rect->y, src_rect->width, src_rect->height,
+          gst_video_format_to_string (window->surface_pool_format),
+          dst_rect->x, dst_rect->y, dst_rect->width, dst_rect->height);
+      vpp_surface = gst_vaapi_window_vpp_convert_internal (window, surface,
+          src_rect, dst_rect, flags);
+    }
+    if (G_UNLIKELY (!vpp_surface)) {
+      /* Not all formats are supported as destination format during VPP.
+         So try again with the next format if VPP fails. */
+      GstVideoFormat format = choose_next_format (display, &format_index);
+      if ((format != GST_VIDEO_FORMAT_UNKNOWN) && window->has_vpp) {
+        GST_DEBUG ("VPP failed. Try again with format %s",
+            gst_video_format_to_string (format));
+        gst_vaapi_window_set_vpp_format_internal (window, format, 0);
+        goto again;
+      } else {
+        GST_WARNING ("VPP failed. No supported format found.");
+        priv->dmabuf_broken = TRUE;
+      }
+    } else {
+      surface = vpp_surface;
+      va_flags = VA_FRAME_PICTURE;
+    }
+  }
+  if (!priv->dmabuf_broken) {
+    ret = dmabuf_buffer_from_surface (window, surface, dst_rect, va_flags,
+        buffer);
+    switch (ret) {
+      case GST_VAAPI_DMABUF_SUCCESS:
+        goto out;
+      case GST_VAAPI_DMABUF_BAD_FLAGS:
+        /* FIXME: how should this be handed? */
+        break;
+      case GST_VAAPI_DMABUF_BAD_FORMAT:{
+        /* The Wayland server does not accept the current format or
+           vaExportSurfaceHandle() failed. Try again with a different format */
+        GstVideoFormat format = choose_next_format (display, &format_index);
+        if ((format != GST_VIDEO_FORMAT_UNKNOWN) && window->has_vpp) {
+          GST_DEBUG ("Failed to export buffer. Try again with format %s",
+              gst_video_format_to_string (format));
+          priv->need_vpp = TRUE;
+          gst_vaapi_window_set_vpp_format_internal (window, format, 0);
+          goto again;
+        }
+        if (window->has_vpp)
+          GST_WARNING ("Failed to export buffer and VPP not supported.");
+        else
+          GST_WARNING ("Failed to export buffer. No supported format found.");
+        priv->dmabuf_broken = TRUE;
+        break;
+      }
+      case GST_VAAPI_DMABUF_BAD_MODIFIER:
+        /* The format is supported by the Wayland server but not with the
+           current modifier. Try linear instead. */
+        if (window->has_vpp) {
+          GST_DEBUG ("Modifier rejected by the server. Try linear instead.");
+          priv->need_vpp = TRUE;
+          gst_vaapi_window_set_vpp_format_internal (window,
+              gst_vaapi_surface_get_format (surface),
+              GST_VAAPI_SURFACE_ALLOC_FLAG_LINEAR_STORAGE);
+          goto again;
+        }
+        GST_WARNING ("Modifier rejected by the server and VPP not supported.");
+        priv->dmabuf_broken = TRUE;
+        break;
+      case GST_VAAPI_DMABUF_NOT_SUPPORTED:
+        GST_DEBUG ("DMABuf protocol not supported");
+        priv->dmabuf_broken = TRUE;
+        break;
+      case GST_VAAPI_DMABUF_FLUSH:
+        return FALSE;
+    }
+  }
+
+  /* DMABuf is not available or does not work. Fall back to the old API.
+     There is no format negotiation so stick with NV12  */
+  gst_vaapi_window_set_vpp_format_internal (window, GST_VIDEO_FORMAT_NV12, 0);
+
+  GST_VAAPI_WINDOW_LOCK_DISPLAY (window);
+  status = vaGetSurfaceBufferWl (GST_VAAPI_DISPLAY_VADISPLAY (display),
+      GST_VAAPI_SURFACE_ID (surface),
+      va_flags & (VA_TOP_FIELD | VA_BOTTOM_FIELD), buffer);
+  GST_VAAPI_WINDOW_UNLOCK_DISPLAY (window);
+
+  if (window->has_vpp && !priv->need_vpp &&
+      (status == VA_STATUS_ERROR_FLAG_NOT_SUPPORTED ||
+          status == VA_STATUS_ERROR_UNIMPLEMENTED ||
+          status == VA_STATUS_ERROR_INVALID_IMAGE_FORMAT)) {
+    priv->need_vpp = TRUE;
+    goto again;
+  }
+  if (!vaapi_check_status (status, "vaGetSurfaceBufferWl()"))
+    return FALSE;
+
+out:
+  *surf = surface;
+  return TRUE;
+}
+
 static gboolean
 gst_vaapi_window_wayland_render (GstVaapiWindow * window,
     GstVaapiSurface * surface,
@@ -540,19 +836,18 @@ gst_vaapi_window_wayland_render (GstVaapiWindow * window,
 {
   GstVaapiWindowWaylandPrivate *const priv =
       GST_VAAPI_WINDOW_WAYLAND_GET_PRIVATE (window);
-  GstVaapiDisplay *const display = GST_VAAPI_WINDOW_DISPLAY (window);
   struct wl_display *const wl_display =
       GST_VAAPI_WINDOW_NATIVE_DISPLAY (window);
   struct wl_buffer *buffer;
   FrameState *frame;
-  guint width, height, va_flags;
-  VAStatus status;
+  guint width, height;
+  gboolean ret;
 
   /* Check that we don't need to crop source VA surface */
   gst_vaapi_surface_get_size (surface, &width, &height);
   if (src_rect->x != 0 || src_rect->y != 0)
     priv->need_vpp = TRUE;
-  if (src_rect->width != width || src_rect->height != height)
+  if (src_rect->width != width)
     priv->need_vpp = TRUE;
 
   /* Check that we don't render to a subregion of this window */
@@ -561,43 +856,15 @@ gst_vaapi_window_wayland_render (GstVaapiWindow * window,
   if (dst_rect->width != window->width || dst_rect->height != window->height)
     priv->need_vpp = TRUE;
 
-  /* Try to construct a Wayland buffer from VA surface as is (without VPP) */
-  if (!priv->need_vpp) {
-    GST_VAAPI_WINDOW_LOCK_DISPLAY (window);
-    va_flags = from_GstVaapiSurfaceRenderFlags (flags);
-    status = vaGetSurfaceBufferWl (GST_VAAPI_DISPLAY_VADISPLAY (display),
-        GST_VAAPI_SURFACE_ID (surface),
-        va_flags & (VA_TOP_FIELD | VA_BOTTOM_FIELD), &buffer);
-    GST_VAAPI_WINDOW_UNLOCK_DISPLAY (window);
-    if (status == VA_STATUS_ERROR_FLAG_NOT_SUPPORTED ||
-        status == VA_STATUS_ERROR_UNIMPLEMENTED ||
-        status == VA_STATUS_ERROR_INVALID_IMAGE_FORMAT)
-      priv->need_vpp = TRUE;
-    else if (!vaapi_check_status (status, "vaGetSurfaceBufferWl()"))
-      return FALSE;
-  }
+  ret = buffer_from_surface (window, &surface, src_rect, dst_rect, flags,
+      &buffer);
+  if (!ret)
+    return FALSE;
 
-  /* Try to construct a Wayland buffer with VPP */
+  /* if need_vpp is set then the vpp happend */
   if (priv->need_vpp) {
-    if (window->has_vpp) {
-      GstVaapiSurface *const vpp_surface =
-          gst_vaapi_window_vpp_convert_internal (window, surface, src_rect,
-          dst_rect, flags);
-      if (G_UNLIKELY (!vpp_surface))
-        priv->need_vpp = FALSE;
-      else {
-        surface = vpp_surface;
-        width = window->width;
-        height = window->height;
-      }
-    }
-
-    GST_VAAPI_WINDOW_LOCK_DISPLAY (window);
-    status = vaGetSurfaceBufferWl (GST_VAAPI_DISPLAY_VADISPLAY (display),
-        GST_VAAPI_SURFACE_ID (surface), VA_FRAME_PICTURE, &buffer);
-    GST_VAAPI_WINDOW_UNLOCK_DISPLAY (window);
-    if (!vaapi_check_status (status, "vaGetSurfaceBufferWl()"))
-      return FALSE;
+    width = window->width;
+    height = window->height;
   }
 
   /* Wait for the previous frame to complete redraw */