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 | |
38 | struct _GskGLRendererClass |
39 | { |
40 | GskRendererClass parent_class; |
41 | }; |
42 | |
43 | struct _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 | |
68 | G_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 | */ |
79 | GskRenderer * |
80 | gsk_gl_renderer_new (void) |
81 | { |
82 | return g_object_new (GSK_TYPE_GL_RENDERER, NULL); |
83 | } |
84 | |
85 | static gboolean |
86 | gsk_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 | |
155 | failure: |
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 | |
164 | static void |
165 | gsk_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 | |
178 | static cairo_region_t * |
179 | get_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 | |
211 | static gboolean |
212 | update_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 | |
270 | static void |
271 | gsk_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 | |
321 | static GdkTexture * |
322 | gsk_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 | |
403 | static void |
404 | gsk_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 | |
415 | static void |
416 | gsk_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 | |
429 | static void |
430 | gsk_gl_renderer_init (GskGLRenderer *self) |
431 | { |
432 | } |
433 | |
434 | gboolean |
435 | gsk_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 | |
449 | typedef struct { |
450 | GskRenderer parent_instance; |
451 | } GskNglRenderer; |
452 | |
453 | typedef struct { |
454 | GskRendererClass parent_class; |
455 | } GskNglRendererClass; |
456 | |
457 | G_DEFINE_TYPE (GskNglRenderer, gsk_ngl_renderer, GSK_TYPE_RENDERER) |
458 | |
459 | static void |
460 | gsk_ngl_renderer_init (GskNglRenderer *renderer) |
461 | { |
462 | } |
463 | |
464 | static gboolean |
465 | gsk_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 | |
475 | static void |
476 | gsk_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 | */ |
490 | GskRenderer * |
491 | gsk_ngl_renderer_new (void) |
492 | { |
493 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
494 | return g_object_new (object_type: gsk_ngl_renderer_get_type (), NULL); |
495 | G_GNUC_END_IGNORE_DEPRECATIONS |
496 | } |
497 | |