1/* gskglrenderer.c
2 *
3 * Copyright 2020 Christian Hergert <chergert@redhat.com>
4 *
5 * This file is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License as published by the Free
7 * Software Foundation; either version 2.1 of the License, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13 * License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: LGPL-2.1-or-later
19 */
20
21#include "config.h"
22
23#include <gdk/gdkprofilerprivate.h>
24#include <gdk/gdkdisplayprivate.h>
25#include <gdk/gdkglcontextprivate.h>
26#include <gdk/gdksurfaceprivate.h>
27#include <gdk/gdkintl.h>
28#include <gsk/gskdebugprivate.h>
29#include <gsk/gskrendererprivate.h>
30#include <gsk/gskrendernodeprivate.h>
31
32#include "gskglcommandqueueprivate.h"
33#include "gskgldriverprivate.h"
34#include "gskglprogramprivate.h"
35#include "gskglrenderjobprivate.h"
36#include "gskglrendererprivate.h"
37
38struct _GskGLRendererClass
39{
40 GskRendererClass parent_class;
41};
42
43struct _GskGLRenderer
44{
45 GskRenderer parent_instance;
46
47 /* This context is used to swap buffers when we are rendering directly
48 * to a GDK surface. It is also used to locate the shared driver for
49 * the display that we use to drive the command queue.
50 */
51 GdkGLContext *context;
52
53 /* Our command queue is private to this renderer and talks to the GL
54 * context for our target surface. This ensure that framebuffer 0 matches
55 * the surface we care about. Since the context is shared with other
56 * contexts from other renderers on the display, texture atlases,
57 * programs, and other objects are available to them all.
58 */
59 GskGLCommandQueue *command_queue;
60
61 /* The driver manages our program state and command queues. It also
62 * deals with caching textures, shaders, shadows, glyph, and icon
63 * caches through various helpers.
64 */
65 GskGLDriver *driver;
66};
67
68G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
69
70/**
71 * gsk_gl_renderer_new:
72 *
73 * Creates a new `GskRenderer` using the new OpenGL renderer.
74 *
75 * Returns: a new GL renderer
76 *
77 * Since: 4.2
78 */
79GskRenderer *
80gsk_gl_renderer_new (void)
81{
82 return g_object_new (GSK_TYPE_GL_RENDERER, NULL);
83}
84
85static gboolean
86gsk_gl_renderer_realize (GskRenderer *renderer,
87 GdkSurface *surface,
88 GError **error)
89{
90 G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
91 GskGLRenderer *self = (GskGLRenderer *)renderer;
92 GdkGLContext *context = NULL;
93 GskGLDriver *driver = NULL;
94 GdkDisplay *display;
95 gboolean ret = FALSE;
96 gboolean debug_shaders = FALSE;
97 GdkGLAPI api;
98
99 if (self->context != NULL)
100 return TRUE;
101
102 g_assert (self->driver == NULL);
103 g_assert (self->context == NULL);
104 g_assert (self->command_queue == NULL);
105
106 if (surface == NULL)
107 {
108 display = gdk_display_get_default (); /* FIXME: allow different displays somehow ? */
109 context = gdk_display_create_gl_context (self: display, error);
110 }
111 else
112 {
113 display = gdk_surface_get_display (surface);
114 context = gdk_surface_create_gl_context (surface, error);
115 }
116
117 if (!context || !gdk_gl_context_realize (context, error))
118 goto failure;
119
120 api = gdk_gl_context_get_api (self: context);
121 if (api == GDK_GL_API_GLES)
122 {
123 gdk_gl_context_make_current (context);
124
125 if (!gdk_gl_context_has_vertex_half_float (self: context))
126 {
127 int major, minor;
128
129 gdk_gl_context_get_version (context, major: &major, minor: &minor);
130 g_set_error (err: error,
131 GDK_GL_ERROR, code: GDK_GL_ERROR_NOT_AVAILABLE,
132 _("This GLES %d.%d implementation does not support half-float vertex data"),
133 major, minor);
134 goto failure;
135 }
136 }
137
138#ifdef G_ENABLE_DEBUG
139 if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
140 debug_shaders = TRUE;
141#endif
142
143 if (!(driver = gsk_gl_driver_for_display (display, debug_shaders, error)))
144 goto failure;
145
146 self->command_queue = gsk_gl_driver_create_command_queue (self: driver, context);
147 self->context = g_steal_pointer (&context);
148 self->driver = g_steal_pointer (&driver);
149
150 gsk_gl_command_queue_set_profiler (self: self->command_queue,
151 profiler: gsk_renderer_get_profiler (renderer));
152
153 ret = TRUE;
154
155failure:
156 g_clear_object (&driver);
157 g_clear_object (&context);
158
159 gdk_profiler_end_mark (start_time, "realize GskGLRenderer", NULL);
160
161 return ret;
162}
163
164static void
165gsk_gl_renderer_unrealize (GskRenderer *renderer)
166{
167 GskGLRenderer *self = (GskGLRenderer *)renderer;
168
169 g_assert (GSK_IS_GL_RENDERER (renderer));
170
171 gdk_gl_context_make_current (context: self->context);
172
173 g_clear_object (&self->driver);
174 g_clear_object (&self->command_queue);
175 g_clear_object (&self->context);
176}
177
178static cairo_region_t *
179get_render_region (GdkSurface *surface,
180 GdkGLContext *context)
181{
182 const cairo_region_t *damage;
183 GdkRectangle whole_surface;
184 GdkRectangle extents;
185
186 g_assert (GDK_IS_SURFACE (surface));
187 g_assert (GDK_IS_GL_CONTEXT (context));
188
189 whole_surface.x = 0;
190 whole_surface.y = 0;
191 whole_surface.width = gdk_surface_get_width (surface);
192 whole_surface.height = gdk_surface_get_height (surface);
193
194 /* Damage does not have scale factor applied so we can compare it to
195 * @whole_surface which also doesn't have the scale factor applied.
196 */
197 damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (context));
198
199 if (cairo_region_contains_rectangle (region: damage, rectangle: &whole_surface) == CAIRO_REGION_OVERLAP_IN)
200 return NULL;
201
202 /* If the extents match the full-scene, do the same as above */
203 cairo_region_get_extents (region: damage, extents: &extents);
204 if (gdk_rectangle_equal (rect1: &extents, rect2: &whole_surface))
205 return NULL;
206
207 /* Draw clipped to the bounding-box of the region. */
208 return cairo_region_create_rectangle (rectangle: &extents);
209}
210
211static gboolean
212update_area_requires_clear (GdkSurface *surface,
213 const cairo_region_t *update_area)
214{
215 cairo_rectangle_int_t rect;
216 guint n_rects;
217
218 g_assert (GDK_IS_SURFACE (surface));
219
220 /* No opaque region, assume we have to clear */
221 if (surface->opaque_region == NULL)
222 return TRUE;
223
224 /* If the update_area is the whole surface, then clear it
225 * because many drivers optimize for this by avoiding extra
226 * work to reload any contents.
227 */
228 if (update_area == NULL)
229 return TRUE;
230
231 if (cairo_region_num_rectangles (region: update_area) == 1)
232 {
233 cairo_region_get_rectangle (region: update_area, nth: 0, rectangle: &rect);
234
235 if (rect.x == 0 &&
236 rect.y == 0 &&
237 rect.width == surface->width &&
238 rect.height == surface->height)
239 return TRUE;
240 }
241
242 /* If the entire surface is opaque, then we can skip clearing
243 * (with the exception of full surface clearing above).
244 */
245 if (cairo_region_num_rectangles (region: surface->opaque_region) == 1)
246 {
247 cairo_region_get_rectangle (region: surface->opaque_region, nth: 0, rectangle: &rect);
248
249 if (rect.x == 0 &&
250 rect.y == 0 &&
251 rect.width == surface->width &&
252 rect.height == surface->height)
253 return FALSE;
254 }
255
256 /* If any update_area rectangle overlaps our transparent
257 * regions, then we need to clear the area.
258 */
259 n_rects = cairo_region_num_rectangles (region: update_area);
260 for (guint i = 0; i < n_rects; i++)
261 {
262 cairo_region_get_rectangle (region: update_area, nth: i, rectangle: &rect);
263 if (cairo_region_contains_rectangle (region: surface->opaque_region, rectangle: &rect) != CAIRO_REGION_OVERLAP_IN)
264 return TRUE;
265 }
266
267 return FALSE;
268}
269
270static void
271gsk_gl_renderer_render (GskRenderer *renderer,
272 GskRenderNode *root,
273 const cairo_region_t *update_area)
274{
275 GskGLRenderer *self = (GskGLRenderer *)renderer;
276 cairo_region_t *render_region;
277 graphene_rect_t viewport;
278 GskGLRenderJob *job;
279 GdkSurface *surface;
280 gboolean clear_framebuffer;
281 float scale_factor;
282
283 g_assert (GSK_IS_GL_RENDERER (renderer));
284 g_assert (root != NULL);
285
286 surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context));
287 scale_factor = gdk_surface_get_scale_factor (surface);
288
289 viewport.origin.x = 0;
290 viewport.origin.y = 0;
291 viewport.size.width = gdk_surface_get_width (surface) * scale_factor;
292 viewport.size.height = gdk_surface_get_height (surface) * scale_factor;
293
294 gdk_draw_context_begin_frame_full (GDK_DRAW_CONTEXT (self->context),
295 prefers_high_depth: gsk_render_node_prefers_high_depth (node: root),
296 region: update_area);
297
298 gdk_gl_context_make_current (context: self->context);
299
300 /* Must be called *AFTER* gdk_draw_context_begin_frame() */
301 render_region = get_render_region (surface, context: self->context);
302 clear_framebuffer = update_area_requires_clear (surface, update_area: render_region);
303
304 gsk_gl_driver_begin_frame (self: self->driver, command_queue: self->command_queue);
305 job = gsk_gl_render_job_new (driver: self->driver, viewport: &viewport, scale_factor, region: render_region, framebuffer: 0, clear_framebuffer);
306#ifdef G_ENABLE_DEBUG
307 if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
308 gsk_gl_render_job_set_debug_fallback (job, TRUE);
309#endif
310 gsk_gl_render_job_render (job, root);
311 gsk_gl_driver_end_frame (self: self->driver);
312 gsk_gl_render_job_free (job);
313
314 gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->context));
315
316 gsk_gl_driver_after_frame (self: self->driver);
317
318 cairo_region_destroy (region: render_region);
319}
320
321static GdkTexture *
322gsk_gl_renderer_render_texture (GskRenderer *renderer,
323 GskRenderNode *root,
324 const graphene_rect_t *viewport)
325{
326 GskGLRenderer *self = (GskGLRenderer *)renderer;
327 GskGLRenderTarget *render_target;
328 GskGLRenderJob *job;
329 GdkTexture *texture;
330 guint texture_id;
331 int width, height, max_size;
332 int format;
333
334 g_assert (GSK_IS_GL_RENDERER (renderer));
335 g_assert (root != NULL);
336
337 width = ceilf (x: viewport->size.width);
338 height = ceilf (x: viewport->size.height);
339 max_size = self->command_queue->max_texture_size;
340 if (width > max_size || height > max_size)
341 {
342 gsize x, y, size, stride;
343 GBytes *bytes;
344 guchar *data;
345
346 stride = width * 4;
347 size = stride * height;
348 data = g_malloc_n (n_blocks: stride, n_block_bytes: height);
349
350 for (y = 0; y < height; y += max_size)
351 {
352 for (x = 0; x < width; x += max_size)
353 {
354 texture = gsk_gl_renderer_render_texture (renderer, root,
355 viewport: &GRAPHENE_RECT_INIT (x, y,
356 MIN (max_size, viewport->size.width - x),
357 MIN (max_size, viewport->size.height - y)));
358 gdk_texture_download (texture,
359 data: data + stride * y + x * 4,
360 stride);
361 g_object_unref (object: texture);
362 }
363 }
364
365 bytes = g_bytes_new_take (data, size);
366 texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
367 g_bytes_unref (bytes);
368 return texture;
369 }
370
371 format = gsk_render_node_prefers_high_depth (node: root) ? GL_RGBA32F : GL_RGBA8;
372
373 gdk_gl_context_make_current (context: self->context);
374
375 if (gsk_gl_driver_create_render_target (self: self->driver,
376 width, height,
377 format,
378 GL_NEAREST, GL_NEAREST,
379 render_target: &render_target))
380 {
381 gsk_gl_driver_begin_frame (self: self->driver, command_queue: self->command_queue);
382 job = gsk_gl_render_job_new (driver: self->driver, viewport, scale_factor: 1, NULL, framebuffer: render_target->framebuffer_id, TRUE);
383#ifdef G_ENABLE_DEBUG
384 if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
385 gsk_gl_render_job_set_debug_fallback (job, TRUE);
386#endif
387 gsk_gl_render_job_render_flipped (job, root);
388 texture_id = gsk_gl_driver_release_render_target (self: self->driver, render_target, FALSE);
389 texture = gsk_gl_driver_create_gdk_texture (self: self->driver, texture_id);
390 gsk_gl_driver_end_frame (self: self->driver);
391 gsk_gl_render_job_free (job);
392
393 gsk_gl_driver_after_frame (self: self->driver);
394 }
395 else
396 {
397 g_assert_not_reached ();
398 }
399
400 return g_steal_pointer (&texture);
401}
402
403static void
404gsk_gl_renderer_dispose (GObject *object)
405{
406#ifdef G_ENABLE_DEBUG
407 GskGLRenderer *self = (GskGLRenderer *)object;
408
409 g_assert (self->driver == NULL);
410#endif
411
412 G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (object);
413}
414
415static void
416gsk_gl_renderer_class_init (GskGLRendererClass *klass)
417{
418 GObjectClass *object_class = G_OBJECT_CLASS (klass);
419 GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
420
421 object_class->dispose = gsk_gl_renderer_dispose;
422
423 renderer_class->realize = gsk_gl_renderer_realize;
424 renderer_class->unrealize = gsk_gl_renderer_unrealize;
425 renderer_class->render = gsk_gl_renderer_render;
426 renderer_class->render_texture = gsk_gl_renderer_render_texture;
427}
428
429static void
430gsk_gl_renderer_init (GskGLRenderer *self)
431{
432}
433
434gboolean
435gsk_gl_renderer_try_compile_gl_shader (GskGLRenderer *renderer,
436 GskGLShader *shader,
437 GError **error)
438{
439 GskGLProgram *program;
440
441 g_return_val_if_fail (GSK_IS_GL_RENDERER (renderer), FALSE);
442 g_return_val_if_fail (shader != NULL, FALSE);
443
444 program = gsk_gl_driver_lookup_shader (self: renderer->driver, shader, error);
445
446 return program != NULL;
447}
448
449typedef struct {
450 GskRenderer parent_instance;
451} GskNglRenderer;
452
453typedef struct {
454 GskRendererClass parent_class;
455} GskNglRendererClass;
456
457G_DEFINE_TYPE (GskNglRenderer, gsk_ngl_renderer, GSK_TYPE_RENDERER)
458
459static void
460gsk_ngl_renderer_init (GskNglRenderer *renderer)
461{
462}
463
464static gboolean
465gsk_ngl_renderer_realize (GskRenderer *renderer,
466 GdkSurface *surface,
467 GError **error)
468{
469 g_set_error_literal (err: error,
470 G_IO_ERROR, code: G_IO_ERROR_FAILED,
471 message: "please use the GL renderer instead");
472 return FALSE;
473}
474
475static void
476gsk_ngl_renderer_class_init (GskNglRendererClass *class)
477{
478 GSK_RENDERER_CLASS (class)->realize = gsk_ngl_renderer_realize;
479}
480
481/**
482 * gsk_ngl_renderer_new:
483 *
484 * Same as gsk_gl_renderer_new().
485 *
486 * Returns: (transfer full): a new GL renderer
487 *
488 * Deprecated: 4.4: Use gsk_gl_renderer_new()
489 */
490GskRenderer *
491gsk_ngl_renderer_new (void)
492{
493G_GNUC_BEGIN_IGNORE_DEPRECATIONS
494 return g_object_new (object_type: gsk_ngl_renderer_get_type (), NULL);
495G_GNUC_END_IGNORE_DEPRECATIONS
496}
497

source code of gtk/gsk/gl/gskglrenderer.c