[gnome-remote-desktop] Add DMA buffer downloading support to EGL thread



commit 24193f683e8789360664b23ffd7bb090c042fb8c
Author: Jonas Ådahl <jadahl gmail com>
Date:   Sat Oct 30 16:36:35 2021 +0200

    Add DMA buffer downloading support to EGL thread
    
    It uses a dedicated thread used for downloading pixels from DMA buffer
    using OpenGL. This will ensure we use the right driver dependent method
    is used for transferring the GPU memory of the DMA buffer to CPU memory.

 meson.build             |   1 +
 src/grd-egl-thread.c    | 402 ++++++++++++++++++++++++++++++++++++++++++-
 src/grd-egl-thread.h    |  19 +++
 tests/egl-thread-test.c | 440 +++++++++++++++++++++++++++++++++++++++++++++++-
 tests/meson.build       |  10 +-
 5 files changed, 864 insertions(+), 8 deletions(-)
---
diff --git a/meson.build b/meson.build
index 7914eeb..a62975a 100644
--- a/meson.build
+++ b/meson.build
@@ -24,6 +24,7 @@ systemd_dep = dependency('systemd', required: get_option('systemd'))
 libsecret_dep = dependency('libsecret-1')
 libnotify_dep = dependency('libnotify')
 epoxy_dep = dependency('epoxy')
+drm_dep = dependency('libdrm')
 
 have_rdp = get_option('rdp')
 have_vnc = get_option('vnc')
diff --git a/src/grd-egl-thread.c b/src/grd-egl-thread.c
index 5abff48..c2c7b15 100644
--- a/src/grd-egl-thread.c
+++ b/src/grd-egl-thread.c
@@ -44,15 +44,66 @@ struct _GrdEglThread
 
     EGLDisplay egl_display;
     EGLContext egl_context;
+
+    GSource *egl_thread_source;
   } impl;
 };
 
+typedef struct _GrdEglTask
+{
+  GFunc func;
+
+  uint8_t *dst_data;
+  int dst_row_width;
+
+  uint32_t format;
+  unsigned int width;
+  unsigned int height;
+  uint32_t n_planes;
+  int *fds;
+  uint32_t *strides;
+  uint32_t *offsets;
+  uint64_t *modifiers;
+
+  GMainContext *callback_context;
+  GrdEglThreadCallback callback;
+  gpointer callback_user_data;
+  GDestroyNotify callback_destroy;
+} GrdEglTask;
+
+typedef void (* GrdEglFrameReady) (uint8_t  *data,
+                                   gpointer  user_data);
+
+static gboolean
+is_hardware_accelerated (void)
+{
+  const char *renderer;
+  gboolean is_software;
+
+  renderer = (const char *) glGetString (GL_RENDERER);
+  g_assert_cmpuint (glGetError (), ==, GL_NO_ERROR);
+  if (!renderer)
+    {
+      g_warning ("OpenGL driver returned NULL as the renderer, "
+                 "something is wrong");
+      return FALSE;
+    }
+
+  is_software = (strstr (renderer, "llvmpipe") ||
+                 strstr (renderer, "softpipe") ||
+                 strstr (renderer, "software rasterizer") ||
+                 strstr (renderer, "Software Rasterizer") ||
+                 strstr (renderer, "SWR"));
+  return !is_software;
+}
+
 static gboolean
 grd_egl_init_in_impl (GrdEglThread  *egl_thread,
                       GError       **error)
 {
   EGLDisplay egl_display;
   EGLint major, minor;
+  EGLint *attrs;
   EGLContext egl_context;
 
   if (!epoxy_has_egl_extension (NULL, "EGL_EXT_platform_base"))
@@ -62,7 +113,8 @@ grd_egl_init_in_impl (GrdEglThread  *egl_thread,
       return FALSE;
     }
 
-  egl_display = eglGetPlatformDisplayEXT (EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL);
+  egl_display = eglGetPlatformDisplayEXT (EGL_PLATFORM_SURFACELESS_MESA,
+                                          NULL, NULL);
   if (egl_display == EGL_NO_DISPLAY)
     {
       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
@@ -102,7 +154,14 @@ grd_egl_init_in_impl (GrdEglThread  *egl_thread,
       return FALSE;
     }
 
-  egl_context = eglCreateContext (egl_display, EGL_NO_CONFIG_KHR, NULL, NULL);
+  attrs = (EGLint[]) {
+    EGL_CONTEXT_CLIENT_VERSION, 2,
+    EGL_NONE
+  };
+  egl_context = eglCreateContext (egl_display,
+                                  EGL_NO_CONFIG_KHR,
+                                  EGL_NO_CONTEXT,
+                                  attrs);
   if (egl_context == EGL_NO_CONTEXT)
     {
       eglTerminate (egl_display);
@@ -111,12 +170,102 @@ grd_egl_init_in_impl (GrdEglThread  *egl_thread,
       return FALSE;
     }
 
+  eglMakeCurrent (egl_display,
+                  EGL_NO_SURFACE,
+                  EGL_NO_SURFACE,
+                  egl_context);
+
+  if (!is_hardware_accelerated ())
+    {
+      eglDestroyContext (egl_display, egl_context);
+      eglTerminate (egl_display);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "EGL context not hardware accelerated");
+      return FALSE;
+    }
+
   egl_thread->impl.egl_display = egl_display;
   egl_thread->impl.egl_context = egl_context;
 
   return TRUE;
 }
 
+static void
+grd_egl_task_free (GrdEglTask *task)
+{
+  g_clear_pointer (&task->callback_user_data, task->callback_destroy);
+  g_free (task->fds);
+  g_free (task->strides);
+  g_free (task->offsets);
+  g_free (task->modifiers);
+  g_free (task);
+}
+
+static void
+grd_thread_dispatch_in_impl (GrdEglThread *egl_thread)
+{
+  GrdEglTask *task;
+
+  task = g_async_queue_try_pop (egl_thread->task_queue);
+  if (!task)
+    return;
+
+  task->func (egl_thread, task);
+  grd_egl_task_free (task);
+}
+
+typedef struct _EglTaskSource
+{
+  GSource base;
+  GrdEglThread *egl_thread;
+} EglTaskSource;
+
+static gboolean
+egl_task_source_prepare (GSource *source,
+                         int     *timeout)
+{
+  EglTaskSource *task_source = (EglTaskSource *) source;
+  GrdEglThread *egl_thread = task_source->egl_thread;
+
+  g_assert (g_source_get_context (source) == egl_thread->impl.main_context);
+
+  *timeout = -1;
+
+  return g_async_queue_length (egl_thread->task_queue) > 0;
+}
+
+static gboolean
+egl_task_source_check (GSource *source)
+{
+  EglTaskSource *task_source = (EglTaskSource *) source;
+  GrdEglThread *egl_thread = task_source->egl_thread;
+
+  g_assert (g_source_get_context (source) == egl_thread->impl.main_context);
+
+  return g_async_queue_length (egl_thread->task_queue) > 0;
+}
+
+static gboolean
+egl_task_source_dispatch (GSource     *source,
+                          GSourceFunc  callback,
+                          gpointer     user_data)
+{
+  EglTaskSource *task_source = (EglTaskSource *) source;
+  GrdEglThread *egl_thread = task_source->egl_thread;
+
+  g_assert (g_source_get_context (source) == egl_thread->impl.main_context);
+
+  grd_thread_dispatch_in_impl (egl_thread);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static GSourceFuncs egl_task_source_funcs = {
+  .prepare = egl_task_source_prepare,
+  .check = egl_task_source_check,
+  .dispatch = egl_task_source_dispatch,
+};
+
 static gpointer
 grd_egl_thread_func (gpointer user_data)
 {
@@ -135,6 +284,16 @@ grd_egl_thread_func (gpointer user_data)
       return NULL;
     }
 
+  egl_thread->impl.egl_thread_source =
+    g_source_new (&egl_task_source_funcs, sizeof (EglTaskSource));
+  ((EglTaskSource *) egl_thread->impl.egl_thread_source)->egl_thread =
+    egl_thread;
+  g_source_set_name (egl_thread->impl.egl_thread_source, "EGL thread source");
+  g_source_set_ready_time (egl_thread->impl.egl_thread_source, -1);
+  g_source_attach (egl_thread->impl.egl_thread_source,
+                   egl_thread->impl.main_context);
+  egl_thread->task_queue = g_async_queue_new ();
+
   egl_thread->impl.main_loop = g_main_loop_new (egl_thread->impl.main_context,
                                                 FALSE);
   g_mutex_lock (&egl_thread->mutex);
@@ -144,8 +303,23 @@ grd_egl_thread_func (gpointer user_data)
 
   g_main_loop_run (egl_thread->impl.main_loop);
 
+  g_source_destroy (egl_thread->impl.egl_thread_source);
+  g_source_unref (egl_thread->impl.egl_thread_source);
+
   g_clear_pointer (&egl_thread->impl.main_loop, g_main_loop_unref);
 
+  while (TRUE)
+    {
+      GrdEglTask *task;
+
+      task = g_async_queue_try_pop (egl_thread->task_queue);
+      if (!task)
+        break;
+
+      grd_egl_task_free (task);
+    }
+  g_async_queue_unref (egl_thread->task_queue);
+
   eglDestroyContext (egl_thread->impl.egl_display,
                      egl_thread->impl.egl_context);
   eglTerminate (egl_thread->impl.egl_display);
@@ -164,7 +338,9 @@ grd_egl_thread_new (GError **error)
 
   g_mutex_lock (&egl_thread->mutex);
 
-  egl_thread->thread = g_thread_new ("GRD EGL thread", grd_egl_thread_func, egl_thread);
+  egl_thread->thread = g_thread_new ("GRD EGL thread",
+                                     grd_egl_thread_func,
+                                     egl_thread);
 
   while (!egl_thread->impl.initialized)
     g_cond_wait (&egl_thread->cond, &egl_thread->mutex);
@@ -209,3 +385,223 @@ grd_egl_thread_free (GrdEglThread *egl_thread)
 
   g_free (egl_thread);
 }
+
+static EGLImageKHR
+create_dmabuf_image (GrdEglThread   *egl_thread,
+                     unsigned int    width,
+                     unsigned int    height,
+                     uint32_t        format,
+                     uint32_t        n_planes,
+                     const int      *fds,
+                     const uint32_t *strides,
+                     const uint32_t *offsets,
+                     const uint64_t *modifiers)
+{
+  EGLint attribs[37];
+  int atti = 0;
+  EGLImageKHR egl_image;
+
+  attribs[atti++] = EGL_WIDTH;
+  attribs[atti++] = width;
+  attribs[atti++] = EGL_HEIGHT;
+  attribs[atti++] = height;
+  attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+  attribs[atti++] = format;
+
+  if (n_planes > 0)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+      attribs[atti++] = fds[0];
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+      attribs[atti++] = offsets[0];
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+      attribs[atti++] = strides[0];
+      if (modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+          attribs[atti++] = modifiers[0] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+          attribs[atti++] = modifiers[0] >> 32;
+        }
+    }
+
+  if (n_planes > 1)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT;
+      attribs[atti++] = fds[1];
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
+      attribs[atti++] = offsets[1];
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
+      attribs[atti++] = strides[1];
+      if (modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
+          attribs[atti++] = modifiers[1] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
+          attribs[atti++] = modifiers[1] >> 32;
+        }
+    }
+
+  if (n_planes > 2)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT;
+      attribs[atti++] = fds[2];
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
+      attribs[atti++] = offsets[2];
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
+      attribs[atti++] = strides[2];
+      if (modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
+          attribs[atti++] = modifiers[2] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
+          attribs[atti++] = modifiers[2] >> 32;
+        }
+    }
+
+  attribs[atti++] = EGL_NONE;
+  g_assert (atti <= G_N_ELEMENTS (attribs));
+
+  egl_image = eglCreateImageKHR (egl_thread->impl.egl_display,
+                                 EGL_NO_CONTEXT,
+                                 EGL_LINUX_DMA_BUF_EXT, NULL,
+                                 attribs);
+  if (egl_image == EGL_NO_IMAGE)
+    {
+      g_warning ("Failed to import DMA buffer as EGL image: %d",
+                 eglGetError ());
+    }
+
+  return egl_image;
+}
+
+static gboolean
+bind_egl_image (GrdEglThread *egl_thread,
+                EGLImageKHR   egl_image,
+                GrdEglTask   *task,
+                GLuint       *tex,
+                GLuint       *fbo)
+{
+  glGenTextures (1, tex);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glBindTexture (GL_TEXTURE_2D, *tex);
+  glPixelStorei (GL_PACK_ROW_LENGTH, task->dst_row_width);
+  glEGLImageTargetTexture2DOES (GL_TEXTURE_2D, egl_image);
+
+  glGenFramebuffers (1, fbo);
+  glBindFramebuffer (GL_FRAMEBUFFER, *fbo);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                          GL_TEXTURE_2D, *tex, 0);
+  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
+      GL_FRAMEBUFFER_COMPLETE)
+    {
+      g_warning ("Failed to bind DMA buf framebuffer");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+read_pixels (GrdEglTask *task)
+{
+  glReadPixels (0, 0, task->width, task->height,
+                GL_BGRA, GL_UNSIGNED_BYTE,
+                task->dst_data);
+}
+
+static void
+download_in_impl (gpointer data,
+                  gpointer user_data)
+{
+  GrdEglThread *egl_thread = data;
+  GrdEglTask *task = user_data;
+  EGLImageKHR egl_image;
+  gboolean success = FALSE;
+  GLuint tex = 0;
+  GLuint fbo = 0;
+
+  eglMakeCurrent (egl_thread->impl.egl_display,
+                  EGL_NO_SURFACE,
+                  EGL_NO_SURFACE,
+                  egl_thread->impl.egl_context);
+
+  egl_image =
+    create_dmabuf_image (egl_thread,
+                         task->width,
+                         task->height,
+                         task->format,
+                         task->n_planes,
+                         task->fds,
+                         task->strides,
+                         task->offsets,
+                         task->modifiers);
+  if (egl_image == EGL_NO_IMAGE)
+    goto out;
+
+  if (!bind_egl_image (egl_thread, egl_image, task, &tex, &fbo))
+    goto out;
+
+  read_pixels (task);
+
+  success = TRUE;
+
+out:
+  if (fbo)
+    {
+      glBindFramebuffer (GL_FRAMEBUFFER, 0);
+      glDeleteFramebuffers (1, &fbo);
+    }
+  if (tex)
+    {
+      glBindTexture (GL_TEXTURE_2D, 0);
+      glDeleteTextures (1, &tex);
+    }
+
+  if (egl_image != EGL_NO_IMAGE)
+    eglDestroyImageKHR (egl_thread->impl.egl_display, egl_image);
+
+  task->callback (success, task->callback_user_data);
+}
+
+void
+grd_egl_thread_download (GrdEglThread         *egl_thread,
+                         uint8_t              *dst_data,
+                         int                   dst_row_width,
+                         uint32_t              format,
+                         unsigned int          width,
+                         unsigned int          height,
+                         uint32_t              n_planes,
+                         const int            *fds,
+                         const uint32_t       *strides,
+                         const uint32_t       *offsets,
+                         const uint64_t       *modifiers,
+                         GrdEglThreadCallback  callback,
+                         gpointer              user_data,
+                         GDestroyNotify        destroy)
+{
+  GrdEglTask *task;
+
+  task = g_new0 (GrdEglTask, 1);
+
+  task->dst_data = dst_data;
+  task->dst_row_width = dst_row_width;
+
+  task->format = format;
+  task->width = width;
+  task->height = height;
+  task->n_planes = n_planes;
+  task->fds = g_memdup2 (fds, n_planes * sizeof (int));
+  task->strides = g_memdup2 (strides, n_planes * sizeof (uint32_t));
+  task->offsets = g_memdup2 (offsets, n_planes * sizeof (uint32_t));
+  task->modifiers = g_memdup2 (modifiers, n_planes * sizeof (uint64_t));
+
+  task->func = download_in_impl;
+  task->callback = callback;
+  task->callback_user_data = user_data;
+  task->callback_destroy = destroy;
+
+  g_async_queue_push (egl_thread->task_queue, task);
+  g_main_context_wakeup (egl_thread->impl.main_context);
+}
diff --git a/src/grd-egl-thread.h b/src/grd-egl-thread.h
index 0dabf1e..a382caf 100644
--- a/src/grd-egl-thread.h
+++ b/src/grd-egl-thread.h
@@ -22,11 +22,30 @@
 #define GRD_EGL_THREAD_H
 
 #include <glib.h>
+#include <stdint.h>
 
 typedef struct _GrdEglThread GrdEglThread;
 
+typedef void (* GrdEglThreadCallback) (gboolean success,
+                                       gpointer user_data);
+
 GrdEglThread * grd_egl_thread_new (GError **error);
 
 void grd_egl_thread_free (GrdEglThread *egl_thread);
 
+void grd_egl_thread_download (GrdEglThread         *egl_thread,
+                              uint8_t              *dst_data,
+                              int                   dst_row_width,
+                              uint32_t              format,
+                              unsigned int          width,
+                              unsigned int          height,
+                              uint32_t              n_planes,
+                              const int            *fds,
+                              const uint32_t       *strides,
+                              const uint32_t       *offsets,
+                              const uint64_t       *modifiers,
+                              GrdEglThreadCallback  callback,
+                              gpointer              user_data,
+                              GDestroyNotify        destroy);
+
 #endif /* GRD_EGL_THREAD_H */
diff --git a/tests/egl-thread-test.c b/tests/egl-thread-test.c
index b6d387d..34a654c 100644
--- a/tests/egl-thread-test.c
+++ b/tests/egl-thread-test.c
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2021 Red Hat Inc.
+ * Copyright (C) 2016, 2017, 2021 Red Hat Inc.
+ * Copyright (C) 2018, 2019 DisplayLink (UK) Ltd.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -20,20 +21,451 @@
 
 #include "config.h"
 
+#include <epoxy/egl.h>
+#include <epoxy/gl.h>
+#include <fcntl.h>
+#include <gbm.h>
+#include <gio/gio.h>
+#include <gudev/gudev.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xf86drm.h>
+
 #include "grd-egl-thread.h"
 
+#include <stdio.h>
+
+#define DRM_CARD_UDEV_DEVICE_TYPE "drm_minor"
+
+#define RED_FLOAT 0.5
+#define GREEN_FLOAT 0.1
+#define BLUE_FLOAT 0.8
+#define ALPHA_FLOAT 1.0
+
+#define FLOAT_ERROR_MARGIN 0.01
+
+typedef struct
+{
+  uint32_t format;
+  unsigned int width;
+  unsigned int height;
+  uint32_t n_planes;
+  uint32_t strides[4];
+  uint32_t offsets[4];
+  uint64_t modifiers[4];
+  int fds[4];
+
+  uint8_t *read_back_data;
+  int read_back_stride;
+} DmaBuf;
+
+static void
+paint_image (int width,
+             int height)
+{
+  glViewport (0, 0, width, width);
+  glClearColor (RED_FLOAT, GREEN_FLOAT, BLUE_FLOAT, ALPHA_FLOAT);
+  glClear (GL_COLOR_BUFFER_BIT);
+  glFlush ();
+  g_assert_cmpuint (glGetError (), ==, GL_NO_ERROR);
+}
+
+static EGLImageKHR
+create_dmabuf_image (EGLDisplay  egl_display,
+                     DmaBuf     *dma_buf)
+{
+  EGLint attribs[37];
+  int atti = 0;
+  EGLImageKHR egl_image;
+
+  attribs[atti++] = EGL_WIDTH;
+  attribs[atti++] = dma_buf->width;
+  attribs[atti++] = EGL_HEIGHT;
+  attribs[atti++] = dma_buf->height;
+  attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+  attribs[atti++] = dma_buf->format;
+
+  if (dma_buf->n_planes > 0)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+      attribs[atti++] = dma_buf->fds[0];
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+      attribs[atti++] = dma_buf->offsets[0];
+      attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+      attribs[atti++] = dma_buf->strides[0];
+      if (dma_buf->modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+          attribs[atti++] = dma_buf->modifiers[0] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+          attribs[atti++] = dma_buf->modifiers[0] >> 32;
+        }
+    }
+
+  if (dma_buf->n_planes > 1)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT;
+      attribs[atti++] = dma_buf->fds[1];
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
+      attribs[atti++] = dma_buf->offsets[1];
+      attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
+      attribs[atti++] = dma_buf->strides[1];
+      if (dma_buf->modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
+          attribs[atti++] = dma_buf->modifiers[1] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
+          attribs[atti++] = dma_buf->modifiers[1] >> 32;
+        }
+    }
+
+  if (dma_buf->n_planes > 2)
+    {
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT;
+      attribs[atti++] = dma_buf->fds[2];
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
+      attribs[atti++] = dma_buf->offsets[2];
+      attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
+      attribs[atti++] = dma_buf->strides[2];
+      if (dma_buf->modifiers)
+        {
+          attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
+          attribs[atti++] = dma_buf->modifiers[2] & 0xFFFFFFFF;
+          attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
+          attribs[atti++] = dma_buf->modifiers[2] >> 32;
+        }
+    }
+
+  attribs[atti++] = EGL_NONE;
+  g_assert (atti <= G_N_ELEMENTS (attribs));
+
+  egl_image = eglCreateImageKHR (egl_display, EGL_NO_CONTEXT,
+                                 EGL_LINUX_DMA_BUF_EXT, NULL,
+                                 attribs);
+  g_assert_cmpint (eglGetError (), ==, EGL_SUCCESS);
+  return egl_image;
+}
+
+static void
+bind_egl_image (EGLDisplay  egl_display,
+                EGLImageKHR egl_image)
+{
+  GLuint tex;
+  GLuint fbo;
+
+  glGenTextures (1, &tex);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glBindTexture (GL_TEXTURE_2D, tex);
+  glEGLImageTargetTexture2DOES (GL_TEXTURE_2D, egl_image);
+
+  glGenFramebuffers (1, &fbo);
+  glBindFramebuffer (GL_FRAMEBUFFER, fbo);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                          GL_TEXTURE_2D, tex, 0);
+  g_assert_cmpint (glCheckFramebufferStatus(GL_FRAMEBUFFER), ==,
+                   GL_FRAMEBUFFER_COMPLETE);
+  g_assert_cmpuint (glGetError (), ==, GL_NO_ERROR);
+}
+
+static DmaBuf *
+dma_buf_new (void)
+{
+  DmaBuf *dma_buf;
+  int i;
+
+  dma_buf = g_new0 (DmaBuf, 1);
+  for (i = 0; i < 4; i++)
+    dma_buf->fds[i] = -1;
+
+  return dma_buf;
+}
+
+static void
+dma_buf_free (DmaBuf *dma_buf)
+{
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      int fd;
+
+      fd = dma_buf->fds[i];
+      if (fd != -1)
+        close (fd);
+    }
+
+  g_free (dma_buf->read_back_data);
+  g_free (dma_buf);
+}
+
+static DmaBuf *
+generate_dma_buf (struct gbm_device *gbm_device,
+                  EGLDisplay         egl_display,
+                  int                width,
+                  int                height)
+{
+  struct gbm_bo *bo;
+  DmaBuf *dma_buf;
+  int bo_fd;
+  EGLImageKHR egl_image;
+  int i;
+
+  bo = gbm_bo_create (gbm_device,
+                      width, height,
+                      GBM_FORMAT_ARGB8888,
+                      GBM_BO_USE_RENDERING);
+  if (!bo)
+    g_error ("Couldn't allocate a 10x10 GBM buffer: %s", g_strerror (errno));
+
+  bo_fd = gbm_bo_get_fd (bo);
+  g_assert_cmpint (bo_fd, >, 0);
+
+  dma_buf = dma_buf_new ();
+
+  dma_buf->format = gbm_bo_get_format (bo);
+  dma_buf->width = gbm_bo_get_width (bo);
+  dma_buf->height = gbm_bo_get_height (bo);
+
+  dma_buf->n_planes = gbm_bo_get_plane_count (bo);
+  for (i = 0; i < dma_buf->n_planes; i++)
+    {
+      dma_buf->strides[i] = gbm_bo_get_stride_for_plane (bo, i);
+      dma_buf->offsets[i] = gbm_bo_get_offset (bo, i);
+      dma_buf->modifiers[i] = gbm_bo_get_modifier (bo);
+      dma_buf->fds[i] = bo_fd;
+    }
+
+  egl_image = create_dmabuf_image (egl_display, dma_buf);
+
+  bind_egl_image (egl_display, egl_image);
+  paint_image (dma_buf->width, dma_buf->height);
+
+  eglDestroyImageKHR (egl_display, egl_image);
+  gbm_bo_destroy (bo);
+
+  return dma_buf;
+}
+
+static void
+on_dma_buf_downloaded (gboolean success,
+                       gpointer user_data)
+{
+  DmaBuf *dma_buf = user_data;
+  int y;
+  uint8_t *pixels = dma_buf->read_back_data;
+
+  g_assert_true (success);
+
+  for (y = 0; y < dma_buf->height; y++)
+    {
+      int x;
+
+      for (x = 0; x < dma_buf->width; x++)
+        {
+          union {
+            uint32_t argb32;
+            uint8_t argb8888[4];
+          } read_color = {
+            .argb32 = ((uint32_t *) pixels)[x],
+          };
+
+          g_assert_cmpfloat_with_epsilon (read_color.argb8888[0] / 255.0,
+                                          BLUE_FLOAT,
+                                          FLOAT_ERROR_MARGIN);
+          g_assert_cmpfloat_with_epsilon (read_color.argb8888[1] / 255.0,
+                                          GREEN_FLOAT,
+                                          FLOAT_ERROR_MARGIN);
+          g_assert_cmpfloat_with_epsilon (read_color.argb8888[2] / 255.0,
+                                          RED_FLOAT,
+                                          FLOAT_ERROR_MARGIN);
+          g_assert_cmpfloat_with_epsilon (read_color.argb8888[3] / 255.0,
+                                          ALPHA_FLOAT,
+                                          FLOAT_ERROR_MARGIN);
+        }
+
+      pixels += dma_buf->read_back_stride;
+    }
+}
+
+static int
+run_dma_buf_tests (struct gbm_device *gbm_device,
+                   EGLDisplay         egl_display,
+                   GrdEglThread      *egl_thread)
+{
+  DmaBuf *dma_buf;
+  const int bpp = 4;
+  const int width = 10;
+  const int height = 15;
+  const int dst_row_width = 20;
+
+  dma_buf = generate_dma_buf (gbm_device, egl_display, width, height);
+
+  dma_buf->read_back_stride = dst_row_width * bpp;
+  dma_buf->read_back_data =
+    g_malloc0 (dma_buf->height * dma_buf->read_back_stride);
+
+  grd_egl_thread_download (egl_thread,
+                           dma_buf->read_back_data,
+                           dst_row_width,
+                           dma_buf->format,
+                           dma_buf->width,
+                           dma_buf->height,
+                           dma_buf->n_planes,
+                           dma_buf->fds,
+                           dma_buf->strides,
+                           dma_buf->offsets,
+                           dma_buf->modifiers,
+                           on_dma_buf_downloaded,
+                           dma_buf,
+                           (GDestroyNotify) dma_buf_free);
+
+  return EXIT_SUCCESS;
+}
+
+static struct gbm_device *
+try_open_gbm_device (const char *path)
+{
+  int fd;
+  struct gbm_device *gbm_device;
+
+  fd = open (path, O_RDWR | O_CLOEXEC);
+  if (fd == -1)
+    return NULL;
+
+  gbm_device = gbm_create_device (fd);
+  if (!gbm_device)
+    close (fd);
+
+  return gbm_device;
+}
+
+static struct gbm_device *
+open_gbm_device (void)
+{
+  const char *subsystems[] = { "drm", NULL };
+  g_autoptr (GUdevClient) gudev_client = NULL;
+  g_autoptr (GUdevEnumerator) enumerator = NULL;
+  g_autolist (GUdevDevice) devices = NULL;
+  GList *l;
+
+  gudev_client = g_udev_client_new (subsystems);
+  enumerator = g_udev_enumerator_new (gudev_client);
+
+  g_udev_enumerator_add_match_name (enumerator, "card*");
+  g_udev_enumerator_add_match_tag (enumerator, "seat");
+  g_udev_enumerator_add_match_subsystem (enumerator, "drm");
+
+  devices = g_udev_enumerator_execute (enumerator);
+
+  for (l = devices; l; l = l->next)
+    {
+      GUdevDevice *device = l->data;
+      const char *device_type;
+      int fd;
+      const char *path;
+      g_autofree char *render_node_path = NULL;
+      struct gbm_device *gbm_device;
+
+      if (g_udev_device_get_device_type (device) != G_UDEV_DEVICE_TYPE_CHAR)
+        continue;
+
+      device_type = g_udev_device_get_property (device, "DEVTYPE");
+      if (g_strcmp0 (device_type, DRM_CARD_UDEV_DEVICE_TYPE) != 0)
+        continue;
+
+      path = g_udev_device_get_device_file (device);
+      fd = open (path, O_RDWR);
+      if (fd == -1)
+        {
+          g_warning ("Failed to open '%s': %s", path, g_strerror (errno));
+          continue;
+        }
+
+      render_node_path = drmGetRenderDeviceNameFromFd (fd);
+      close (fd);
+
+      gbm_device = try_open_gbm_device (render_node_path);
+      if (gbm_device)
+        return gbm_device;
+    }
+
+  return NULL;
+}
+
 int
 main (int    argc,
       char **argv)
 {
   GError *error = NULL;
   GrdEglThread *egl_thread;
+  struct gbm_device *gbm_device;
+  EGLDisplay egl_display;
+  EGLContext egl_context;
+  EGLint major, minor;
+  EGLint *attrs;
+  int retval = EXIT_SUCCESS;
+
+  g_assert_cmpint (eglGetError (), ==, EGL_SUCCESS);
 
   egl_thread = grd_egl_thread_new (&error);
   if (!egl_thread)
-    g_error ("Failed to start EGL thread: %s", error->message);
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+        {
+          g_message ("EGL not hardware accelerated, skipping test");
+          retval = 77;
+          goto out;
+        }
+      else
+        {
+          g_error ("Failed to start EGL thread: %s", error->message);
+        }
+    }
 
-  grd_egl_thread_free (egl_thread);
+  g_assert_cmpint (eglGetError (), ==, EGL_SUCCESS);
 
-  return EXIT_SUCCESS;
+  gbm_device = open_gbm_device ();
+  if (!gbm_device)
+    {
+      g_warning ("No GBM device available, no way to test");
+      retval = 77;
+      goto out;
+    }
+
+  egl_display = eglGetPlatformDisplayEXT (EGL_PLATFORM_GBM_KHR,
+                                          gbm_device,
+                                          NULL);
+  if (egl_display == EGL_NO_DISPLAY)
+    {
+      g_warning ("Couldn't get EGL gbm display");
+      retval = 77;
+      goto out;
+    }
+
+  if (!eglInitialize (egl_display, &major, &minor))
+    g_error ("Failed to initialize EGL display");
+
+  if (!eglBindAPI (EGL_OPENGL_ES_API))
+    g_error ("Failed to bind GLES API");
+
+  attrs = (EGLint[]) {
+    EGL_CONTEXT_CLIENT_VERSION, 2,
+    EGL_NONE
+  };
+  egl_context = eglCreateContext (egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs);
+
+  eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
+
+  retval = run_dma_buf_tests (gbm_device, egl_display, egl_thread);
+
+  eglDestroyContext (egl_display, egl_context);
+  eglTerminate (egl_display);
+
+out:
+  g_clear_pointer (&egl_thread, grd_egl_thread_free);
+
+  return retval;
 }
diff --git a/tests/meson.build b/tests/meson.build
index a82c261..6f87c45 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -21,6 +21,9 @@ if have_vnc
   )
 endif
 
+gbm_dep = dependency('gbm')
+gudev_dep = dependency('gudev-1.0')
+
 egl_thread_test = executable(
   'egl-thread-test',
   sources: [
@@ -28,7 +31,12 @@ egl_thread_test = executable(
     '../src/grd-egl-thread.c',
     '../src/grd-egl-thread.h',
   ],
-  dependencies: deps,
+  dependencies: [
+    deps,
+    drm_dep,
+    gbm_dep,
+    gudev_dep,
+  ],
   include_directories: [
     src_includepath,
     configinc,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]