From 53312cf696516cdeb1c284008992022a210bb233 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:02:26 +0200 Subject: [PATCH 1/6] surface: Remove (nullable) The function only returns NULL on error, and such a function is not considered nullable. --- gdk/gdksurface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/gdksurface.c b/gdk/gdksurface.c index 40d9c21b8f..9430a4a111 100644 --- a/gdk/gdksurface.c +++ b/gdk/gdksurface.c @@ -1171,7 +1171,7 @@ gdk_surface_get_paint_gl_context (GdkSurface *surface, * Before using the returned `GdkGLContext`, you will need to * call [method@Gdk.GLContext.make_current] or [method@Gdk.GLContext.realize]. * - * Returns: (transfer full) (nullable): the newly created `GdkGLContext` + * Returns: (transfer full): the newly created `GdkGLContext` */ GdkGLContext * gdk_surface_create_gl_context (GdkSurface *surface, From 2601c39cb2cd985f316fb03439a3a30325c3ea64 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:03:00 +0200 Subject: [PATCH 2/6] API: Add gdk_display_create_gl_context() This is an alternative to gdk_surface_create_gl_context() when the context is meant to only draw to textures. This is useful in the testsuite or in GStreamer or with GLArea, basically whenever we want to do GL stuff but don't need to actually draw anything on screen. A bunch of code will need to be updated to deal with context->surface being NULL. --- gdk/gdkdisplay.c | 32 ++++++++++++++++++++++++++++++++ gdk/gdkdisplay.h | 3 +++ gdk/gdkglcontext.c | 10 +++++++--- gdk/gdkglcontextprivate.h | 3 ++- gdk/gdksurface.c | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/gdk/gdkdisplay.c b/gdk/gdkdisplay.c index 075342a26b..d5defbeb82 100644 --- a/gdk/gdkdisplay.c +++ b/gdk/gdkdisplay.c @@ -1325,6 +1325,38 @@ gdk_display_prepare_gl (GdkDisplay *self, } } +/** + * gdk_display_create_gl_context: + * @self: a `GdkDisplay` + * @error: return location for an error + * + * Creates a new `GdkGLContext` for the `GdkDisplay`. + * + * The context is disconnected from any particular surface or surface + * and cannot be used to draw to any surface. It can only be used to + * draw to non-surface framebuffers like textures. + * + * If the creation of the `GdkGLContext` failed, @error will be set. + * Before using the returned `GdkGLContext`, you will need to + * call [method@Gdk.GLContext.make_current] or [method@Gdk.GLContext.realize]. + * + * Returns: (transfer full) (nullable): the newly created `GdkGLContext` + * + * Since: 4.6 + */ +GdkGLContext * +gdk_display_create_gl_context (GdkDisplay *self, + GError **error) +{ + g_return_val_if_fail (GDK_IS_DISPLAY (self), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!gdk_display_prepare_gl (self, error)) + return NULL; + + return gdk_gl_context_new (self, NULL); +} + /*< private > * gdk_display_get_gl_context: * @self: the `GdkDisplay` diff --git a/gdk/gdkdisplay.h b/gdk/gdkdisplay.h index 9fabac6b50..52aa502247 100644 --- a/gdk/gdkdisplay.h +++ b/gdk/gdkdisplay.h @@ -71,6 +71,9 @@ gboolean gdk_display_supports_input_shapes (GdkDisplay *display); GDK_AVAILABLE_IN_4_4 gboolean gdk_display_prepare_gl (GdkDisplay *self, GError **error); +GDK_AVAILABLE_IN_4_6 +GdkGLContext *gdk_display_create_gl_context(GdkDisplay *self, + GError **error); GDK_AVAILABLE_IN_ALL GdkDisplay *gdk_display_get_default (void); diff --git a/gdk/gdkglcontext.c b/gdk/gdkglcontext.c index 812a3cd9d8..422e0a189e 100644 --- a/gdk/gdkglcontext.c +++ b/gdk/gdkglcontext.c @@ -722,15 +722,19 @@ gdk_gl_context_init (GdkGLContext *self) /* Must have called gdk_display_prepare_gl() before */ GdkGLContext * -gdk_gl_context_new_for_surface (GdkSurface *surface) +gdk_gl_context_new (GdkDisplay *display, + GdkSurface *surface) { - GdkDisplay *display = gdk_surface_get_display (surface); - GdkGLContext *shared = gdk_display_get_gl_context (display); + GdkGLContext *shared; + + g_assert (surface == NULL || display == gdk_surface_get_display (surface)); /* assert gdk_display_prepare_gl() had been called */ + shared = gdk_display_get_gl_context (display); g_assert (shared); return g_object_new (G_OBJECT_TYPE (shared), + "display", display, "surface", surface, NULL); } diff --git a/gdk/gdkglcontextprivate.h b/gdk/gdkglcontextprivate.h index 56fe04f903..e07420f1c1 100644 --- a/gdk/gdkglcontextprivate.h +++ b/gdk/gdkglcontextprivate.h @@ -99,7 +99,8 @@ gboolean gdk_gl_backend_can_be_used (GdkGLBackend GError **error); void gdk_gl_backend_use (GdkGLBackend backend_type); -GdkGLContext * gdk_gl_context_new_for_surface (GdkSurface *surface); +GdkGLContext * gdk_gl_context_new (GdkDisplay *display, + GdkSurface *surface); gboolean gdk_gl_context_is_api_allowed (GdkGLContext *self, GdkGLAPI api, diff --git a/gdk/gdksurface.c b/gdk/gdksurface.c index 9430a4a111..5b0c947ed3 100644 --- a/gdk/gdksurface.c +++ b/gdk/gdksurface.c @@ -1183,7 +1183,7 @@ gdk_surface_create_gl_context (GdkSurface *surface, if (!gdk_display_prepare_gl (surface->display, error)) return NULL; - return gdk_gl_context_new_for_surface (surface); + return gdk_gl_context_new (surface->display, surface); } /** From ea14e94eaf96079cb18cce6bb39a74b0933771f0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:07:37 +0200 Subject: [PATCH 3/6] drawcontext: Guard begin/end_frame() against non-surface contexts This can happen now with gdk_display_create_gl_context(). --- gdk/gdkdrawcontext.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gdk/gdkdrawcontext.c b/gdk/gdkdrawcontext.c index b51e1139dd..8ab405e25b 100644 --- a/gdk/gdkdrawcontext.c +++ b/gdk/gdkdrawcontext.c @@ -276,7 +276,8 @@ gdk_draw_context_get_surface (GdkDrawContext *context) /** * gdk_draw_context_begin_frame: - * @context: the `GdkDrawContext` used to draw the frame + * @context: the `GdkDrawContext` used to draw the frame. The context must + * have a surface. * @region: minimum region that should be drawn * * Indicates that you are beginning the process of redrawing @region @@ -308,7 +309,10 @@ void gdk_draw_context_begin_frame (GdkDrawContext *context, const cairo_region_t *region) { + GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); + g_return_if_fail (GDK_IS_DRAW_CONTEXT (context)); + g_return_if_fail (priv->surface != NULL); g_return_if_fail (region != NULL); gdk_draw_context_begin_frame_full (context, FALSE, region); @@ -411,6 +415,7 @@ gdk_draw_context_end_frame (GdkDrawContext *context) GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); g_return_if_fail (GDK_IS_DRAW_CONTEXT (context)); + g_return_if_fail (priv->surface != NULL); if (GDK_SURFACE_DESTROYED (priv->surface)) return; From cb03fe8f31c0607b5488cb79f3339910600729fb Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:30:08 +0200 Subject: [PATCH 4/6] gsk: Allow gsk_renderer_realize (renderer, NULL, NULL) That way, we can use renderers without surfaces. --- gsk/gl/gskglrenderer.c | 20 ++++++++++++++------ gsk/gskcairorenderer.c | 3 ++- gsk/gskrenderer.c | 27 ++++++++++++++++++--------- gsk/vulkan/gskvulkanrenderer.c | 11 +++++++++-- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c index 0b1e9eddda..b222e2b07a 100644 --- a/gsk/gl/gskglrenderer.c +++ b/gsk/gl/gskglrenderer.c @@ -90,12 +90,10 @@ gsk_gl_renderer_realize (GskRenderer *renderer, GskGLRenderer *self = (GskGLRenderer *)renderer; GdkGLContext *context = NULL; GskGLDriver *driver = NULL; + GdkDisplay *display; gboolean ret = FALSE; gboolean debug_shaders = FALSE; - g_assert (GSK_IS_GL_RENDERER (self)); - g_assert (GDK_IS_SURFACE (surface)); - if (self->context != NULL) return TRUE; @@ -103,8 +101,18 @@ gsk_gl_renderer_realize (GskRenderer *renderer, g_assert (self->context == NULL); g_assert (self->command_queue == NULL); - if (!(context = gdk_surface_create_gl_context (surface, error)) || - !gdk_gl_context_realize (context, error)) + if (surface == NULL) + { + display = gdk_display_get_default (); /* FIXME: allow different displays somehow ? */ + context = gdk_display_create_gl_context (display, error); + } + else + { + display = gdk_surface_get_display (surface); + context = gdk_surface_create_gl_context (surface, error); + } + + if (!context || !gdk_gl_context_realize (context, error)) goto failure; #ifdef G_ENABLE_DEBUG @@ -112,7 +120,7 @@ gsk_gl_renderer_realize (GskRenderer *renderer, debug_shaders = TRUE; #endif - if (!(driver = gsk_gl_driver_for_display (gdk_surface_get_display (surface), debug_shaders, error))) + if (!(driver = gsk_gl_driver_for_display (display, debug_shaders, error))) goto failure; self->command_queue = gsk_gl_driver_create_command_queue (driver, context); diff --git a/gsk/gskcairorenderer.c b/gsk/gskcairorenderer.c index 074b54b97f..3135acfdb1 100644 --- a/gsk/gskcairorenderer.c +++ b/gsk/gskcairorenderer.c @@ -59,7 +59,8 @@ gsk_cairo_renderer_realize (GskRenderer *renderer, { GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); - self->cairo_context = gdk_surface_create_cairo_context (surface); + if (surface) + self->cairo_context = gdk_surface_create_cairo_context (surface); return TRUE; } diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c index 7c35b051f9..ff4ff976ce 100644 --- a/gsk/gskrenderer.c +++ b/gsk/gskrenderer.c @@ -103,7 +103,7 @@ static GParamSpec *gsk_renderer_properties[N_PROPS]; static gboolean gsk_renderer_real_realize (GskRenderer *self, - GdkSurface *surface, + GdkSurface *surface, GError **error) { GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, realize); @@ -189,12 +189,12 @@ gsk_renderer_class_init (GskRendererClass *klass) /** * GskRenderer:realized: (attributes org.gtk.Property.get=gsk_renderer_is_realized) * - * Whether the renderer has been associated with a surface. + * Whether the renderer has been associated with a surface or draw context. */ gsk_renderer_properties[PROP_REALIZED] = g_param_spec_boolean ("realized", "Realized", - "The renderer has been associated with a surface", + "The renderer has been associated with a surface or draw context", FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); @@ -281,11 +281,14 @@ gsk_renderer_is_realized (GskRenderer *renderer) /** * gsk_renderer_realize: * @renderer: a `GskRenderer` - * @surface: the `GdkSurface` renderer will be used on + * @surface: (nullable): the `GdkSurface` renderer will be used on * @error: return location for an error * * Creates the resources needed by the @renderer to render the scene * graph. + * + * Since GTK 4.6, the surface may be `NULL`, which allows using + * renderers without having to create a surface. */ gboolean gsk_renderer_realize (GskRenderer *renderer, @@ -296,10 +299,11 @@ gsk_renderer_realize (GskRenderer *renderer, g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE); g_return_val_if_fail (!gsk_renderer_is_realized (renderer), FALSE); - g_return_val_if_fail (GDK_IS_SURFACE (surface), FALSE); + g_return_val_if_fail (surface == NULL || GDK_IS_SURFACE (surface), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - priv->surface = g_object_ref (surface); + if (surface) + priv->surface = g_object_ref (surface); if (!GSK_RENDERER_GET_CLASS (renderer)->realize (renderer, surface, error)) { @@ -399,13 +403,15 @@ gsk_renderer_render_texture (GskRenderer *renderer, /** * gsk_renderer_render: - * @renderer: a `GskRenderer` + * @renderer: a realized `GskRenderer` * @root: a `GskRenderNode` * @region: (nullable): the `cairo_region_t` that must be redrawn or %NULL * for the whole window * - * Renders the scene graph, described by a tree of `GskRenderNode` instances, - * ensuring that the given @region gets redrawn. + * Renders the scene graph, described by a tree of `GskRenderNode` instances + * to the renderer's surface, ensuring that the given @region gets redrawn. + * + * If the renderer has no associated surface, this function does nothing. * * Renderers must ensure that changes of the contents given by the @root * node as well as the area given by @region are redrawn. They are however @@ -428,6 +434,9 @@ gsk_renderer_render (GskRenderer *renderer, g_return_if_fail (GSK_IS_RENDER_NODE (root)); g_return_if_fail (priv->root_node == NULL); + if (priv->surface == NULL) + return; + if (region == NULL || priv->prev_node == NULL || GSK_RENDERER_DEBUG_CHECK (renderer, FULL_REDRAW)) { clip = cairo_region_create_rectangle (&(GdkRectangle) { diff --git a/gsk/vulkan/gskvulkanrenderer.c b/gsk/vulkan/gskvulkanrenderer.c index c91b14a384..86f7984805 100644 --- a/gsk/vulkan/gskvulkanrenderer.c +++ b/gsk/vulkan/gskvulkanrenderer.c @@ -114,12 +114,19 @@ gsk_vulkan_renderer_update_images_cb (GdkVulkanContext *context, static gboolean gsk_vulkan_renderer_realize (GskRenderer *renderer, - GdkSurface *window, + GdkSurface *surface, GError **error) { GskVulkanRenderer *self = GSK_VULKAN_RENDERER (renderer); - self->vulkan = gdk_surface_create_vulkan_context (window, error); + if (surface == NULL) + { + g_set_error (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_UNSUPPORTED, + "The Vulkan renderer does not support surfaceless rendering yet."); + return FALSE; + } + + self->vulkan = gdk_surface_create_vulkan_context (surface, error); if (self->vulkan == NULL) return FALSE; From 869d2f281a44d2b9db5f58ec0f7d08df6fb8de0e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:30:43 +0200 Subject: [PATCH 5/6] node-editor: Create renderers surfaceless --- demos/node-editor/node-editor-window.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/demos/node-editor/node-editor-window.c b/demos/node-editor/node-editor-window.c index 4a22a0b9d8..995d2969f6 100644 --- a/demos/node-editor/node-editor-window.c +++ b/demos/node-editor/node-editor-window.c @@ -646,7 +646,6 @@ create_cairo_texture (NodeEditorWindow *self) GskRenderer *renderer; GskRenderNode *node; GdkTexture *texture; - GdkSurface *surface; paintable = gtk_picture_get_paintable (GTK_PICTURE (self->picture)); if (paintable == NULL || @@ -659,9 +658,8 @@ create_cairo_texture (NodeEditorWindow *self) if (node == NULL) return NULL; - surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (self))); renderer = gsk_cairo_renderer_new (); - gsk_renderer_realize (renderer, surface, NULL); + gsk_renderer_realize (renderer, NULL, NULL); texture = gsk_renderer_render_texture (renderer, node, NULL); gsk_render_node_unref (node); @@ -839,16 +837,18 @@ node_editor_window_add_renderer (NodeEditorWindow *self, GskRenderer *renderer, const char *description) { - GdkSurface *surface; GdkPaintable *paintable; - surface = gtk_native_get_surface (GTK_NATIVE (self)); - g_assert (surface != NULL); - - if (renderer != NULL && !gsk_renderer_realize (renderer, surface, NULL)) + if (!gsk_renderer_realize (renderer, NULL, NULL)) { - g_object_unref (renderer); - return; + GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE (self)); + g_assert (surface != NULL); + + if (!gsk_renderer_realize (renderer, surface, NULL)) + { + g_object_unref (renderer); + return; + } } paintable = gtk_renderer_paintable_new (renderer, gtk_picture_get_paintable (GTK_PICTURE (self->picture))); From 0898023e6b35e1207f399b8942dc52fcc2ee1618 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 20 Oct 2021 20:33:57 +0200 Subject: [PATCH 6/6] testsuite: Create GL renderers without surfaces --- testsuite/gdk/memorytexture.c | 6 +----- testsuite/gdk/texture-threads.c | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/testsuite/gdk/memorytexture.c b/testsuite/gdk/memorytexture.c index 03f8634c3f..712ff951cb 100644 --- a/testsuite/gdk/memorytexture.c +++ b/testsuite/gdk/memorytexture.c @@ -765,7 +765,6 @@ add_test (const char *name, int main (int argc, char *argv[]) { - GdkSurface *surface; int result; gtk_test_init (&argc, &argv, NULL); @@ -776,12 +775,10 @@ main (int argc, char *argv[]) add_test ("/memorytexture/download_float_1x1", test_download_float_1x1); add_test ("/memorytexture/download_float_4x4", test_download_float_4x4); - surface = gdk_surface_new_toplevel (gdk_display_get_default()); gl_renderer = gsk_gl_renderer_new (); - if (!gsk_renderer_realize (gl_renderer, surface, NULL)) + if (!gsk_renderer_realize (gl_renderer, NULL, NULL)) { g_clear_object (&gl_renderer); - g_clear_object (&surface); } result = g_test_run (); @@ -791,7 +788,6 @@ main (int argc, char *argv[]) gsk_renderer_unrealize (gl_renderer); g_clear_object (&gl_renderer); } - g_clear_object (&surface); return result; } diff --git a/testsuite/gdk/texture-threads.c b/testsuite/gdk/texture-threads.c index 5ca16e0ec5..17236070cf 100644 --- a/testsuite/gdk/texture-threads.c +++ b/testsuite/gdk/texture-threads.c @@ -64,7 +64,6 @@ texture_download_thread (GTask *task, static void texture_threads (void) { - GdkSurface *surface; GskRenderer *gl_renderer; GskRenderNode *node; GMainLoop *loop; @@ -73,15 +72,13 @@ texture_threads (void) GError *error = NULL; /* 1. Get a GL renderer */ - surface = gdk_surface_new_toplevel (gdk_display_get_default()); gl_renderer = gsk_gl_renderer_new (); - if (!gsk_renderer_realize (gl_renderer, surface, &error)) + if (!gsk_renderer_realize (gl_renderer, NULL, &error)) { g_test_skip (error->message); g_clear_error (&error); g_clear_object (&gl_renderer); - g_clear_object (&surface); return; } @@ -116,7 +113,6 @@ texture_threads (void) gsk_renderer_unrealize (gl_renderer); g_clear_pointer (&loop, g_main_loop_unref); g_clear_object (&gl_renderer); - g_clear_object (&surface); g_main_context_release (NULL); }