[gtk/wip/otte/texture-threads: 1/2] gltexture: Make sure downloading textures works in a different thread




commit 6785461c2651ad502f46521536619ea77f04dd63
Author: Benjamin Otte <otte redhat com>
Date:   Mon Sep 13 00:19:35 2021 +0200

    gltexture: Make sure downloading textures works in a different thread
    
    This happens in the real world when using the inspector to look at a
    node recording of a GStreamer video while the video is still playing.
    
    GStreamer will use the GL context in a different thread while we are
    busy trying to download it.
    
    A test is included.

 gdk/gdkgltexture.c              | 135 ++++++++++++++++++++++++----------------
 testsuite/gdk/meson.build       |   1 +
 testsuite/gdk/texture-threads.c | 103 ++++++++++++++++++++++++++++++
 3 files changed, 187 insertions(+), 52 deletions(-)
---
diff --git a/gdk/gdkgltexture.c b/gdk/gdkgltexture.c
index 6c91c70f70..4a50751f0e 100644
--- a/gdk/gdkgltexture.c
+++ b/gdk/gdkgltexture.c
@@ -20,6 +20,7 @@
 
 #include "gdkgltextureprivate.h"
 
+#include "gdkdisplayprivate.h"
 #include "gdkmemorytextureprivate.h"
 #include "gdktextureprivate.h"
 
@@ -69,26 +70,56 @@ gdk_gl_texture_dispose (GObject *object)
   G_OBJECT_CLASS (gdk_gl_texture_parent_class)->dispose (object);
 }
 
-static GdkTexture *
-gdk_gl_texture_download_texture (GdkTexture *texture)
+typedef struct _InvokeData
 {
-  GdkGLTexture *self = GDK_GL_TEXTURE (texture);
-  GdkTexture *result;
-  int active_texture;
+  GdkGLTexture *self;
+  volatile int spinlock;
+  GFunc func;
+  gpointer data;
+} InvokeData;
+
+static gboolean
+gdk_gl_texture_invoke_callback (gpointer data)
+{
+  InvokeData *invoke = data;
+  GdkGLContext *context;
+
+  context = gdk_display_get_gl_context (gdk_gl_context_get_display (invoke->self->context));
+
+  gdk_gl_context_make_current (context);
+  glBindTexture (GL_TEXTURE_2D, invoke->self->id);
+
+  invoke->func (invoke->self, invoke->data);
+
+  g_atomic_int_set (&invoke->spinlock, 1);
+
+  return FALSE;
+}
+
+static void
+gdk_gl_texture_run (GdkGLTexture *self,
+                    GFunc         func,
+                    gpointer      data)
+{
+  InvokeData invoke = { self, 0, func, data };
+
+  g_main_context_invoke (NULL, gdk_gl_texture_invoke_callback, &invoke);
+
+  while (g_atomic_int_get (&invoke.spinlock) == 0);
+}
+
+static void
+gdk_gl_texture_do_download_texture (gpointer texture_,
+                                    gpointer result_)
+{
+  GdkTexture *texture = texture_;
+  GdkTexture **result = result_;
   GdkMemoryFormat format;
   GLint internal_format, gl_format, gl_type;
   guchar *data;
   gsize stride;
   GBytes *bytes;
 
-  if (self->saved)
-    return g_object_ref (self->saved);
-
-  gdk_gl_context_make_current (self->context);
-
-  glGetIntegerv (GL_TEXTURE_BINDING_2D, &active_texture);
-  glBindTexture (GL_TEXTURE_2D, self->id);
-
   glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
 
   switch (internal_format)
@@ -161,45 +192,33 @@ gdk_gl_texture_download_texture (GdkTexture *texture)
                  data);
 
   bytes = g_bytes_new_take (data, stride * texture->height);
-  result = gdk_memory_texture_new (texture->width,
-                                   texture->height,
-                                   format,
-                                   bytes,
-                                   stride);
+  *result = gdk_memory_texture_new (texture->width,
+                                    texture->height,
+                                    format,
+                                    bytes,
+                                    stride);
 
   g_bytes_unref (bytes);
-
-  glBindTexture (GL_TEXTURE_2D, active_texture);
-
-  return result;
 }
 
-static void
-gdk_gl_texture_download (GdkTexture *texture,
-                         guchar     *data,
-                         gsize       stride)
+static GdkTexture *
+gdk_gl_texture_download_texture (GdkTexture *texture)
 {
   GdkGLTexture *self = GDK_GL_TEXTURE (texture);
-  GLint active_texture;
+  GdkTexture *result;
 
   if (self->saved)
-    {
-      gdk_texture_download (self->saved, data, stride);
-      return;
-    }
-
-  if (gdk_gl_context_get_use_es (self->context) ||
-      stride != texture->width * 4)
-    {
-      GDK_TEXTURE_CLASS (gdk_gl_texture_parent_class)->download (texture, data, stride);
-      return;
-    }
+    return g_object_ref (self->saved);
 
-  gdk_gl_context_make_current (self->context);
+  gdk_gl_texture_run (self, gdk_gl_texture_do_download_texture, &result);
 
-  glGetIntegerv (GL_TEXTURE_BINDING_2D, &active_texture);
-  glBindTexture (GL_TEXTURE_2D, self->id);
+  return result;
+}
 
+static void
+gdk_gl_texture_do_download (gpointer texture,
+                            gpointer data)
+{
   glGetTexImage (GL_TEXTURE_2D,
                  0,
                  GL_BGRA,
@@ -212,28 +231,40 @@ gdk_gl_texture_download (GdkTexture *texture,
 #endif
                  data);
 
-  glBindTexture (GL_TEXTURE_2D, active_texture);
 }
 
 static void
-gdk_gl_texture_do_download_float (GdkTexture *texture,
-                                  float      *data)
+gdk_gl_texture_download (GdkTexture *texture,
+                         guchar     *data,
+                         gsize       stride)
 {
   GdkGLTexture *self = GDK_GL_TEXTURE (texture);
-  int active_texture;
 
-  gdk_gl_context_make_current (self->context);
+  if (self->saved)
+    {
+      gdk_texture_download (self->saved, data, stride);
+      return;
+    }
+
+  if (gdk_gl_context_get_use_es (self->context) ||
+      stride != texture->width * 4)
+    {
+      GDK_TEXTURE_CLASS (gdk_gl_texture_parent_class)->download (texture, data, stride);
+      return;
+    }
 
-  glGetIntegerv (GL_TEXTURE_BINDING_2D, &active_texture);
-  glBindTexture (GL_TEXTURE_2D, self->id);
+  gdk_gl_texture_run (self, gdk_gl_texture_do_download, data);
+}
 
+static void
+gdk_gl_texture_do_download_float (gpointer texture,
+                                  gpointer data)
+{
   glGetTexImage (GL_TEXTURE_2D,
                  0,
                  GL_RGBA,
                  GL_FLOAT,
                  data);
-
-  glBindTexture (GL_TEXTURE_2D, active_texture);
 }
 
 static void
@@ -256,13 +287,13 @@ gdk_gl_texture_download_float (GdkTexture *texture,
 
   if (stride == width * 4)
     {
-      gdk_gl_texture_do_download_float (texture, data);
+      gdk_gl_texture_run (self, gdk_gl_texture_do_download_float, data);
       return;
     }
 
   copy = g_new (float, width * height * 4);
 
-  gdk_gl_texture_do_download_float (texture, copy);
+  gdk_gl_texture_run (self, gdk_gl_texture_do_download_float, copy);
   for (y = 0; y < height; y++)
     memcpy (data + y * stride, copy + y * 4 * width, 4 * width);
 
diff --git a/testsuite/gdk/meson.build b/testsuite/gdk/meson.build
index 5e62e98775..3eff25868d 100644
--- a/testsuite/gdk/meson.build
+++ b/testsuite/gdk/meson.build
@@ -26,6 +26,7 @@ tests = [
   'rgba',
   'seat',
   'texture',
+  'texture-threads',
 ]
 
 foreach t : tests
diff --git a/testsuite/gdk/texture-threads.c b/testsuite/gdk/texture-threads.c
new file mode 100644
index 0000000000..e4587b12a1
--- /dev/null
+++ b/testsuite/gdk/texture-threads.c
@@ -0,0 +1,103 @@
+#include <gtk/gtk.h>
+
+#include "gsk/ngl/gsknglrenderer.h"
+
+/* This function will be called from a thread and/or the main loop.
+ * Textures are threadsafe after all. */
+static void
+ensure_texture_access (GdkTexture *texture)
+{
+  /* Make sure to initialize the pixel to anything but red */
+  guint32 pixel = 0;
+  float float_pixel[4] = { INFINITY, INFINITY, INFINITY, INFINITY };
+
+  /* Just to be sure */
+  g_assert_cmpint (gdk_texture_get_width (texture), ==, 1);
+  g_assert_cmpint (gdk_texture_get_height (texture), ==, 1);
+
+  /* download the pixel */
+  gdk_texture_download (texture, (guchar *) &pixel, 4);
+  gdk_texture_download_float (texture, float_pixel, 4);
+
+  /* check the pixel is now red */
+  g_assert_cmphex (pixel, ==, 0xFFFF0000);
+  g_assert_cmpfloat (float_pixel[0], ==, 1.0);
+  g_assert_cmpfloat (float_pixel[1], ==, 0.0);
+  g_assert_cmpfloat (float_pixel[2], ==, 0.0);
+  g_assert_cmpfloat (float_pixel[3], ==, 1.0);
+}
+
+static void
+texture_download_done (GObject      *texture,
+                       GAsyncResult *res,
+                       gpointer      loop)
+{
+  ensure_texture_access (GDK_TEXTURE (texture));
+
+  g_main_loop_quit (loop);
+}
+
+static void
+texture_download_thread (GTask        *task,
+                         gpointer      texture,
+                         gpointer      unused,
+                         GCancellable *cancellable)
+{
+  ensure_texture_access (GDK_TEXTURE (texture));
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+texture_threads (void)
+{
+  GdkSurface *surface;
+  GskRenderer *gl_renderer;
+  GskRenderNode *node;
+  GMainLoop *loop;
+  GdkTexture *texture;
+  GTask *task;
+
+  /* 1. Get a GL renderer */
+  surface = gdk_surface_new_toplevel (gdk_display_get_default());
+  gl_renderer = gsk_ngl_renderer_new ();
+  g_assert_true (gsk_renderer_realize (gl_renderer, surface, NULL));
+
+  /* 2. Get a GL texture */
+  node = gsk_color_node_new (&(GdkRGBA) { 1, 0, 0, 1 }, &GRAPHENE_RECT_INIT(0, 0, 1, 1));
+  texture = gsk_renderer_render_texture (gl_renderer, node, &GRAPHENE_RECT_INIT(0, 0, 1, 1));
+  gsk_render_node_unref (node);
+
+  /* 3. This is a bit fishy, but we want to make sure that
+   * the texture's GL context is current in the main thread.
+   *
+   * If we had access to the context, we'd make_current() here.
+   */
+  ensure_texture_access (texture);
+  g_assert_nonnull (gdk_gl_context_get_current ());
+
+  /* 4. Run a thread trying to download the texture */
+  loop = g_main_loop_new (NULL, TRUE);
+  task = g_task_new (texture, NULL, texture_download_done, loop);
+  g_task_run_in_thread (task, texture_download_thread);
+  g_clear_object (&task);
+
+  /* 5. Run the main loop waiting for the thread to return */
+  g_main_loop_run (loop);
+
+  /* 6. All good */
+  gsk_renderer_unrealize (gl_renderer);
+  g_clear_pointer (&loop, g_main_loop_unref);
+  g_clear_object (&gl_renderer);
+  g_clear_object (&surface);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gtk_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/texture-threads", texture_threads);
+
+  return g_test_run ();
+}


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