/* GDK - The GIMP Drawing Kit * * gdkdrawcontext.c: base class for rendering system support * * Copyright © 2016 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #include "gdkdrawcontextprivate.h" #include "gdkinternals.h" #include "gdkintl.h" #include "gdkprofilerprivate.h" /** * SECTION:gdkdrawcontext * @Title: GdkDrawContext * @Short_description: Base class for draw contexts * * #GdkDrawContext is the base object used by contexts implementing different * rendering methods, such as #GdkGLContext or #GdkVulkanContext. It provides * shared functionality between those contexts. * * You will always interact with one of those s.ubclasses. * * A GdkDrawContext is always associated with a single toplevel surface. */ /** * GdkDrawContext: * * The GdkDrawContext struct contains only private fields and should not * be accessed directly. */ typedef struct _GdkDrawContextPrivate GdkDrawContextPrivate; struct _GdkDrawContextPrivate { GdkSurface *surface; cairo_region_t *frame_region; }; enum { PROP_0, PROP_DISPLAY, PROP_SURFACE, LAST_PROP }; static GParamSpec *pspecs[LAST_PROP] = { NULL, }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrawContext, gdk_draw_context, G_TYPE_OBJECT) static void gdk_draw_context_default_surface_resized (GdkDrawContext *context) { } static void gdk_draw_context_dispose (GObject *gobject) { GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject); GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); if (priv->surface) { priv->surface->draw_contexts = g_slist_remove (priv->surface->draw_contexts, context); g_clear_object (&priv->surface); } G_OBJECT_CLASS (gdk_draw_context_parent_class)->dispose (gobject); } static void gdk_draw_context_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject); GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); switch (prop_id) { case PROP_SURFACE: priv->surface = g_value_dup_object (value); g_assert (priv->surface != NULL); priv->surface->draw_contexts = g_slist_prepend (priv->surface->draw_contexts, context); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void gdk_draw_context_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject); GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); switch (prop_id) { case PROP_DISPLAY: g_value_set_object (value, gdk_draw_context_get_display (context)); break; case PROP_SURFACE: g_value_set_object (value, priv->surface); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void gdk_draw_context_class_init (GdkDrawContextClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = gdk_draw_context_set_property; gobject_class->get_property = gdk_draw_context_get_property; gobject_class->dispose = gdk_draw_context_dispose; klass->surface_resized = gdk_draw_context_default_surface_resized; /** * GdkDrawContext:display: * * The #GdkDisplay used to create the #GdkDrawContext. */ pspecs[PROP_DISPLAY] = g_param_spec_object ("display", P_("Display"), P_("The GDK display used to create the context"), GDK_TYPE_DISPLAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * GdkDrawContext:surface: * * The #GdkSurface the gl context is bound to. */ pspecs[PROP_SURFACE] = g_param_spec_object ("surface", P_("Surface"), P_("The GDK surface bound to the context"), GDK_TYPE_SURFACE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, LAST_PROP, pspecs); } static guint pixels_counter; static void gdk_draw_context_init (GdkDrawContext *self) { if (pixels_counter == 0) pixels_counter = gdk_profiler_define_int_counter ("frame pixels", "Pixels drawn per frame"); } /** * gdk_draw_context_is_in_frame: * @context: a #GdkDrawContext * * Returns %TRUE if @context is in the process of drawing to its surface * after a call to gdk_draw_context_begin_frame() and not yet having called * gdk_draw_context_end_frame(). * In this situation, drawing commands may be effecting the contents of a * @context's surface. * * Returns: %TRUE if the context is between begin_frame() and end_frame() calls. */ gboolean gdk_draw_context_is_in_frame (GdkDrawContext *context) { GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), FALSE); return priv->frame_region != NULL; } /*< private > * gdk_draw_context_surface_resized: * @context: a #GdkDrawContext * * Called by the #GdkSurface the @context belongs to when the size of the surface * changes. */ void gdk_draw_context_surface_resized (GdkDrawContext *context) { GDK_DRAW_CONTEXT_GET_CLASS (context)->surface_resized (context); } /** * gdk_draw_context_get_display: * @context: a #GdkDrawContext * * Retrieves the #GdkDisplay the @context is created for * * Returns: (nullable) (transfer none): a #GdkDisplay or %NULL */ GdkDisplay * gdk_draw_context_get_display (GdkDrawContext *context) { GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL); return priv->surface ? gdk_surface_get_display (priv->surface) : NULL; } /** * gdk_draw_context_get_surface: * @context: a #GdkDrawContext * * Retrieves the #GdkSurface used by the @context. * * Returns: (nullable) (transfer none): a #GdkSurface or %NULL */ GdkSurface * gdk_draw_context_get_surface (GdkDrawContext *context) { GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL); return priv->surface; } /** * gdk_draw_context_begin_frame: * @context: the context used to draw the frame * @region: minimum region that should be drawn * * Indicates that you are beginning the process of redrawing @region * on the @context's surface. * * Calling this function begins a drawing operation using @context on the * surface that @context was created from. The actual requirements and * guarantees for the drawing operation vary for different implementations * of drawing, so a #GdkCairoContext and a #GdkGLContext need to be treated * differently. * * A call to this function is a requirement for drawing and must be followed * by a call to gdk_draw_context_end_frame(), which will complete the * drawing operation and ensure the contents become visible on screen. * * Note that the @region passed to this function is the minimum region that * needs to be drawn and depending on implementation, windowing system and * hardware in use, it might be necessary to draw a larger region. Drawing * implementation must use gdk_draw_context_get_frame_region() to query the * region that must be drawn. * * When using GTK+, the widget system automatically places calls to * gdk_draw_context_begin_frame() and gdk_draw_context_end_frame() via the * use of #GskRenderers, so application code does not need to call these * functions explicitly. */ 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 (region != NULL); if (GDK_SURFACE_DESTROYED (priv->surface)) return; if (priv->surface->paint_context != NULL) { if (priv->surface->paint_context == context) { g_critical ("The surface %p is already drawing. You must finish the " "previous drawing operation with gdk_draw_context_end_frame() first.", priv->surface); } else { g_critical ("The surface %p is already being drawn by %s %p. " "You cannot draw s surface wih multiple contexts at the same time.", priv->surface, G_OBJECT_TYPE_NAME (priv->surface->paint_context), priv->surface->paint_context); } return; } priv->frame_region = cairo_region_copy (region); priv->surface->paint_context = g_object_ref (context); GDK_DRAW_CONTEXT_GET_CLASS (context)->begin_frame (context, priv->frame_region); } static gint64 region_get_pixels (cairo_region_t *region) { int i, n; cairo_rectangle_int_t rect; gint64 pixels = 0; n = cairo_region_num_rectangles (region); for (i = 0; i < n; i++) { cairo_region_get_rectangle (region, i, &rect); pixels += rect.width * rect.height; } return pixels; } /** * gdk_draw_context_end_frame: * @context: a #GdkDrawContext * * Ends a drawing operation started with gdk_draw_context_begin_frame() * and makes the drawing available on screen. See that function for more * details about drawing. * * When using a #GdkGLContext, this function may call `glFlush()` * implicitly before returning; it is not recommended to call `glFlush()` * explicitly before calling this function. */ void 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)); if (GDK_SURFACE_DESTROYED (priv->surface)) return; if (priv->surface->paint_context == NULL) { g_critical ("The surface %p has no drawing context. You must call " "gdk_draw_context_begin_frame() before calling " "gdk_draw_context_end_frame().", priv->surface); return; } else if (priv->surface->paint_context != context) { g_critical ("The surface %p is not drawn by this context but by %s %p.", priv->surface, G_OBJECT_TYPE_NAME (priv->surface->paint_context), priv->surface->paint_context); return; } GDK_DRAW_CONTEXT_GET_CLASS (context)->end_frame (context, priv->frame_region); if (gdk_profiler_is_running ()) gdk_profiler_set_int_counter (pixels_counter, g_get_monotonic_time () * 1000, region_get_pixels (priv->frame_region)); g_clear_pointer (&priv->frame_region, cairo_region_destroy); g_clear_object (&priv->surface->paint_context); } /** * gdk_draw_context_get_frame_region: * @context: a #GdkDrawContext * * Retrieves the region that is currently in the process of being repainted. * * After a call to gdk_draw_context_begin_frame() this function will return * a union of the region passed to that function and the area of the surface * that the @context determined needs to be repainted. * * If @context is not inbetween calls to gdk_draw_context_begin_frame() and * gdk_draw_context_end_frame(), %NULL will be returned. * * Returns: (transfer none) (nullable): a Cairo region or %NULL if not drawing * a frame. */ const cairo_region_t * gdk_draw_context_get_frame_region (GdkDrawContext *context) { GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (context); g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL); return priv->frame_region; }