From 6785461c2651ad502f46521536619ea77f04dd63 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Sep 2021 00:19:35 +0200 Subject: 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(-) create mode 100644 testsuite/gdk/texture-threads.c 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 + +#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 (); +} -- cgit v1.2.1