1/* GDK - The GIMP Drawing Kit
2 *
3 * gdkdrawcontext.c: base class for rendering system support
4 *
5 * Copyright © 2016 Benjamin Otte
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "config.h"
22
23#include "gdkdrawcontextprivate.h"
24
25#include "gdkdebug.h"
26#include "gdkintl.h"
27#include "gdkprofilerprivate.h"
28#include "gdksurfaceprivate.h"
29
30/**
31 * GdkDrawContext:
32 *
33 * Base class for objects implementing different rendering methods.
34 *
35 * `GdkDrawContext` is the base object used by contexts implementing different
36 * rendering methods, such as [class@Gdk.CairoContext] or [class@Gdk.GLContext].
37 * It provides shared functionality between those contexts.
38 *
39 * You will always interact with one of those subclasses.
40 *
41 * A `GdkDrawContext` is always associated with a single toplevel surface.
42 */
43
44typedef struct _GdkDrawContextPrivate GdkDrawContextPrivate;
45
46struct _GdkDrawContextPrivate {
47 GdkDisplay *display;
48 GdkSurface *surface;
49
50 cairo_region_t *frame_region;
51};
52
53enum {
54 PROP_0,
55
56 PROP_DISPLAY,
57 PROP_SURFACE,
58
59 LAST_PROP
60};
61
62static GParamSpec *pspecs[LAST_PROP] = { NULL, };
63
64G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrawContext, gdk_draw_context, G_TYPE_OBJECT)
65
66static void
67gdk_draw_context_default_surface_resized (GdkDrawContext *context)
68{
69}
70
71static void
72gdk_draw_context_dispose (GObject *gobject)
73{
74 GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject);
75 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
76
77 if (priv->surface)
78 {
79 priv->surface->draw_contexts = g_slist_remove (list: priv->surface->draw_contexts, data: context);
80 g_clear_object (&priv->surface);
81 }
82 g_clear_object (&priv->display);
83
84 G_OBJECT_CLASS (gdk_draw_context_parent_class)->dispose (gobject);
85}
86
87static void
88gdk_draw_context_set_property (GObject *gobject,
89 guint prop_id,
90 const GValue *value,
91 GParamSpec *pspec)
92{
93 GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject);
94 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
95
96 switch (prop_id)
97 {
98 case PROP_DISPLAY:
99 if (priv->display != NULL)
100 {
101 g_assert (g_value_get_object (value) == NULL);
102 }
103 else
104 {
105 priv->display = g_value_dup_object (value);
106 }
107 break;
108
109 case PROP_SURFACE:
110 priv->surface = g_value_dup_object (value);
111 if (priv->surface)
112 {
113 priv->surface->draw_contexts = g_slist_prepend (list: priv->surface->draw_contexts, data: context);
114 if (priv->display)
115 {
116 g_assert (priv->display == gdk_surface_get_display (priv->surface));
117 }
118 else
119 {
120 priv->display = g_object_ref (gdk_surface_get_display (priv->surface));
121 }
122 }
123 break;
124
125 default:
126 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
127 }
128}
129
130static void
131gdk_draw_context_get_property (GObject *gobject,
132 guint prop_id,
133 GValue *value,
134 GParamSpec *pspec)
135{
136 GdkDrawContext *context = GDK_DRAW_CONTEXT (gobject);
137 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
138
139 switch (prop_id)
140 {
141 case PROP_DISPLAY:
142 g_value_set_object (value, v_object: gdk_draw_context_get_display (context));
143 break;
144
145 case PROP_SURFACE:
146 g_value_set_object (value, v_object: priv->surface);
147 break;
148
149 default:
150 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
151 }
152}
153
154static void
155gdk_draw_context_class_init (GdkDrawContextClass *klass)
156{
157 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
158
159 gobject_class->set_property = gdk_draw_context_set_property;
160 gobject_class->get_property = gdk_draw_context_get_property;
161 gobject_class->dispose = gdk_draw_context_dispose;
162
163 klass->surface_resized = gdk_draw_context_default_surface_resized;
164
165 /**
166 * GdkDrawContext:display: (attributes org.gtk.Property.get=gdk_draw_context_get_display)
167 *
168 * The `GdkDisplay` used to create the `GdkDrawContext`.
169 */
170 pspecs[PROP_DISPLAY] =
171 g_param_spec_object (name: "display",
172 P_("Display"),
173 P_("The GDK display used to create the context"),
174 GDK_TYPE_DISPLAY,
175 flags: G_PARAM_READWRITE |
176 G_PARAM_CONSTRUCT_ONLY |
177 G_PARAM_STATIC_STRINGS);
178
179 /**
180 * GdkDrawContext:surface: (attributes org.gtk.Property.get=gdk_draw_context_get_surface)
181 *
182 * The `GdkSurface` the context is bound to.
183 */
184 pspecs[PROP_SURFACE] =
185 g_param_spec_object (name: "surface",
186 P_("Surface"),
187 P_("The GDK surface bound to the context"),
188 GDK_TYPE_SURFACE,
189 flags: G_PARAM_READWRITE |
190 G_PARAM_CONSTRUCT_ONLY |
191 G_PARAM_STATIC_STRINGS);
192
193 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs);
194}
195
196static guint pixels_counter;
197
198static void
199gdk_draw_context_init (GdkDrawContext *self)
200{
201 if (pixels_counter == 0)
202 pixels_counter = gdk_profiler_define_int_counter ("frame pixels", "Pixels drawn per frame");
203}
204
205/**
206 * gdk_draw_context_is_in_frame:
207 * @context: a `GdkDrawContext`
208 *
209 * Returns %TRUE if @context is in the process of drawing to its surface.
210 *
211 * This is the case between calls to [method@Gdk.DrawContext.begin_frame]
212 * and [method@Gdk.DrawContext.end_frame]. In this situation, drawing commands
213 * may be effecting the contents of the @context's surface.
214 *
215 * Returns: %TRUE if the context is between [method@Gdk.DrawContext.begin_frame]
216 * and [method@Gdk.DrawContext.end_frame] calls.
217 */
218gboolean
219gdk_draw_context_is_in_frame (GdkDrawContext *context)
220{
221 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
222
223 g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), FALSE);
224
225 return priv->frame_region != NULL;
226}
227
228/*< private >
229 * gdk_draw_context_surface_resized:
230 * @context: a `GdkDrawContext`
231 *
232 * Called by the surface the @context belongs to when the size of the surface
233 * changes.
234 */
235void
236gdk_draw_context_surface_resized (GdkDrawContext *context)
237{
238 GDK_DRAW_CONTEXT_GET_CLASS (context)->surface_resized (context);
239}
240
241/**
242 * gdk_draw_context_get_display: (attributes org.gtk.Method.get_property=display)
243 * @context: a `GdkDrawContext`
244 *
245 * Retrieves the `GdkDisplay` the @context is created for
246 *
247 * Returns: (nullable) (transfer none): the `GdkDisplay`
248 */
249GdkDisplay *
250gdk_draw_context_get_display (GdkDrawContext *context)
251{
252 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
253
254 g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL);
255
256 return priv->display;
257}
258
259/**
260 * gdk_draw_context_get_surface: (attributes org.gtk.Method.get_property=surface)
261 * @context: a `GdkDrawContext`
262 *
263 * Retrieves the surface that @context is bound to.
264 *
265 * Returns: (nullable) (transfer none): a `GdkSurface`
266 */
267GdkSurface *
268gdk_draw_context_get_surface (GdkDrawContext *context)
269{
270 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
271
272 g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL);
273
274 return priv->surface;
275}
276
277/**
278 * gdk_draw_context_begin_frame:
279 * @context: the `GdkDrawContext` used to draw the frame. The context must
280 * have a surface.
281 * @region: minimum region that should be drawn
282 *
283 * Indicates that you are beginning the process of redrawing @region
284 * on the @context's surface.
285 *
286 * Calling this function begins a drawing operation using @context on the
287 * surface that @context was created from. The actual requirements and
288 * guarantees for the drawing operation vary for different implementations
289 * of drawing, so a [class@Gdk.CairoContext] and a [class@Gdk.GLContext]
290 * need to be treated differently.
291 *
292 * A call to this function is a requirement for drawing and must be
293 * followed by a call to [method@Gdk.DrawContext.end_frame], which will
294 * complete the drawing operation and ensure the contents become visible
295 * on screen.
296 *
297 * Note that the @region passed to this function is the minimum region that
298 * needs to be drawn and depending on implementation, windowing system and
299 * hardware in use, it might be necessary to draw a larger region. Drawing
300 * implementation must use [method@Gdk.DrawContext.get_frame_region] to
301 * query the region that must be drawn.
302 *
303 * When using GTK, the widget system automatically places calls to
304 * gdk_draw_context_begin_frame() and gdk_draw_context_end_frame() via the
305 * use of [class@Gsk.Renderer]s, so application code does not need to call
306 * these functions explicitly.
307 */
308void
309gdk_draw_context_begin_frame (GdkDrawContext *context,
310 const cairo_region_t *region)
311{
312 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
313
314 g_return_if_fail (GDK_IS_DRAW_CONTEXT (context));
315 g_return_if_fail (priv->surface != NULL);
316 g_return_if_fail (region != NULL);
317
318 gdk_draw_context_begin_frame_full (context, FALSE, region);
319}
320
321/*
322 * @prefers_high_depth: %TRUE to request a higher bit depth
323 *
324 * If high depth is preferred, GDK will see about providing a rendering target
325 * that supports higher bit depth than 8 bits per channel. Typically this means
326 * a target supporting 16bit floating point pixels, but that is not guaranteed.
327 *
328 * This is only a request and if the GDK backend does not support HDR rendering
329 * or does not consider it worthwhile, it may choose to not honor the request.
330 * It may also choose to provide high depth even if it was not requested.
331 * Typically the steps undertaken by a backend are:
332 * 1. Check if high depth is supported by this drawing backend.
333 * 2. Check if the compositor supports high depth.
334 * 3. Check if the compositor prefers regular bit depth. This is usually the case
335 * when the attached monitors do not support high depth content or when the
336 * system is resource constrained.
337 * In either of those cases, the context will usually choose to not honor the request.
338 *
339 * The rendering code must be able to deal with content in any bit depth, no matter
340 * the preference. The prefers_high_depth argument is only a hint and GDK is free
341 * to choose.
342 */
343void
344gdk_draw_context_begin_frame_full (GdkDrawContext *context,
345 gboolean prefers_high_depth,
346 const cairo_region_t *region)
347{
348 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
349
350 if (GDK_SURFACE_DESTROYED (priv->surface))
351 return;
352
353 if (priv->surface->paint_context != NULL)
354 {
355 if (priv->surface->paint_context == context)
356 {
357 g_critical ("The surface %p is already drawing. You must finish the "
358 "previous drawing operation with gdk_draw_context_end_frame() first.",
359 priv->surface);
360 }
361 else
362 {
363 g_critical ("The surface %p is already being drawn by %s %p. "
364 "You cannot draw a surface with multiple contexts at the same time.",
365 priv->surface,
366 G_OBJECT_TYPE_NAME (priv->surface->paint_context), priv->surface->paint_context);
367 }
368 return;
369 }
370
371 if (GDK_DISPLAY_DEBUG_CHECK (priv->display, HIGH_DEPTH))
372 prefers_high_depth = TRUE;
373
374 priv->frame_region = cairo_region_copy (original: region);
375 priv->surface->paint_context = g_object_ref (context);
376
377 GDK_DRAW_CONTEXT_GET_CLASS (context)->begin_frame (context, prefers_high_depth, priv->frame_region);
378}
379
380#ifdef HAVE_SYSPROF
381static gint64
382region_get_pixels (cairo_region_t *region)
383{
384 int i, n;
385 cairo_rectangle_int_t rect;
386 gint64 pixels = 0;
387
388 n = cairo_region_num_rectangles (region);
389 for (i = 0; i < n; i++)
390 {
391 cairo_region_get_rectangle (region, i, &rect);
392 pixels += rect.width * rect.height;
393 }
394
395 return pixels;
396}
397#endif
398
399/**
400 * gdk_draw_context_end_frame:
401 * @context: a `GdkDrawContext`
402 *
403 * Ends a drawing operation started with gdk_draw_context_begin_frame().
404 *
405 * This makes the drawing available on screen.
406 * See [method@Gdk.DrawContext.begin_frame] for more details about drawing.
407 *
408 * When using a [class@Gdk.GLContext], this function may call `glFlush()`
409 * implicitly before returning; it is not recommended to call `glFlush()`
410 * explicitly before calling this function.
411 */
412void
413gdk_draw_context_end_frame (GdkDrawContext *context)
414{
415 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
416
417 g_return_if_fail (GDK_IS_DRAW_CONTEXT (context));
418 g_return_if_fail (priv->surface != NULL);
419
420 if (GDK_SURFACE_DESTROYED (priv->surface))
421 return;
422
423 if (priv->surface->paint_context == NULL)
424 {
425 g_critical ("The surface %p has no drawing context. You must call "
426 "gdk_draw_context_begin_frame() before calling "
427 "gdk_draw_context_end_frame().", priv->surface);
428 return;
429 }
430 else if (priv->surface->paint_context != context)
431 {
432 g_critical ("The surface %p is not drawn by this context but by %s %p.",
433 priv->surface,
434 G_OBJECT_TYPE_NAME (priv->surface->paint_context), priv->surface->paint_context);
435 return;
436 }
437
438 GDK_DRAW_CONTEXT_GET_CLASS (context)->end_frame (context, priv->frame_region);
439
440 gdk_profiler_set_int_counter (pixels_counter, region_get_pixels (priv->frame_region));
441
442 g_clear_pointer (&priv->frame_region, cairo_region_destroy);
443 g_clear_object (&priv->surface->paint_context);
444}
445
446/**
447 * gdk_draw_context_get_frame_region:
448 * @context: a `GdkDrawContext`
449 *
450 * Retrieves the region that is currently being repainted.
451 *
452 * After a call to [method@Gdk.DrawContext.begin_frame] this function will
453 * return a union of the region passed to that function and the area of the
454 * surface that the @context determined needs to be repainted.
455 *
456 * If @context is not in between calls to [method@Gdk.DrawContext.begin_frame]
457 * and [method@Gdk.DrawContext.end_frame], %NULL will be returned.
458 *
459 * Returns: (transfer none) (nullable): a Cairo region
460 */
461const cairo_region_t *
462gdk_draw_context_get_frame_region (GdkDrawContext *context)
463{
464 GdkDrawContextPrivate *priv = gdk_draw_context_get_instance_private (self: context);
465
466 g_return_val_if_fail (GDK_IS_DRAW_CONTEXT (context), NULL);
467
468 return priv->frame_region;
469}
470

source code of gtk/gdk/gdkdrawcontext.c