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 | |
44 | typedef struct _GdkDrawContextPrivate GdkDrawContextPrivate; |
45 | |
46 | struct _GdkDrawContextPrivate { |
47 | GdkDisplay *display; |
48 | GdkSurface *surface; |
49 | |
50 | cairo_region_t *frame_region; |
51 | }; |
52 | |
53 | enum { |
54 | PROP_0, |
55 | |
56 | PROP_DISPLAY, |
57 | PROP_SURFACE, |
58 | |
59 | LAST_PROP |
60 | }; |
61 | |
62 | static GParamSpec *pspecs[LAST_PROP] = { NULL, }; |
63 | |
64 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrawContext, gdk_draw_context, G_TYPE_OBJECT) |
65 | |
66 | static void |
67 | gdk_draw_context_default_surface_resized (GdkDrawContext *context) |
68 | { |
69 | } |
70 | |
71 | static void |
72 | gdk_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 | |
87 | static void |
88 | gdk_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 | |
130 | static void |
131 | gdk_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 | |
154 | static void |
155 | gdk_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 | |
196 | static guint pixels_counter; |
197 | |
198 | static void |
199 | gdk_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 | */ |
218 | gboolean |
219 | gdk_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 | */ |
235 | void |
236 | gdk_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 | */ |
249 | GdkDisplay * |
250 | gdk_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 | */ |
267 | GdkSurface * |
268 | gdk_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 | */ |
308 | void |
309 | gdk_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 | */ |
343 | void |
344 | gdk_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 |
381 | static gint64 |
382 | region_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 | */ |
412 | void |
413 | gdk_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 | */ |
461 | const cairo_region_t * |
462 | gdk_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 | |