1/* GDK - The GIMP Drawing Kit
2 *
3 * gdkglcontext-wayland.c: Wayland specific OpenGL wrappers
4 *
5 * Copyright © 2014 Emmanuele Bassi
6 * Copyright © 2014 Alexander Larsson
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "config.h"
23
24#include "gdkglcontext-wayland.h"
25#include "gdkdisplay-wayland.h"
26
27#include "gdkwaylanddisplay.h"
28#include "gdkwaylandglcontext.h"
29#include "gdkwaylandwindow.h"
30#include "gdkprivate-wayland.h"
31
32#include "gdkinternals.h"
33
34#include "gdkintl.h"
35
36G_DEFINE_TYPE (GdkWaylandGLContext, gdk_wayland_gl_context, GDK_TYPE_GL_CONTEXT)
37
38static void gdk_x11_gl_context_dispose (GObject *gobject);
39
40void
41gdk_wayland_window_invalidate_for_new_frame (GdkWindow *window,
42 cairo_region_t *update_area)
43{
44 cairo_rectangle_int_t window_rect;
45 GdkDisplay *display = gdk_window_get_display (window);
46 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
47 GdkWaylandGLContext *context_wayland;
48 int buffer_age;
49 gboolean invalidate_all;
50 EGLSurface egl_surface;
51
52 /* Minimal update is ok if we're not drawing with gl */
53 if (window->gl_paint_context == NULL)
54 return;
55
56 context_wayland = GDK_WAYLAND_GL_CONTEXT (window->gl_paint_context);
57 buffer_age = 0;
58
59 egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window,
60 context_wayland->egl_config);
61
62 if (display_wayland->have_egl_buffer_age)
63 {
64 gdk_gl_context_make_current (window->gl_paint_context);
65 eglQuerySurface (display_wayland->egl_display, egl_surface,
66 EGL_BUFFER_AGE_EXT, &buffer_age);
67 }
68
69 invalidate_all = FALSE;
70 if (buffer_age == 0 || buffer_age >= 4)
71 invalidate_all = TRUE;
72 else
73 {
74 if (buffer_age >= 2)
75 {
76 if (window->old_updated_area[0])
77 cairo_region_union (update_area, window->old_updated_area[0]);
78 else
79 invalidate_all = TRUE;
80 }
81 if (buffer_age >= 3)
82 {
83 if (window->old_updated_area[1])
84 cairo_region_union (update_area, window->old_updated_area[1]);
85 else
86 invalidate_all = TRUE;
87 }
88 }
89
90 if (invalidate_all)
91 {
92 window_rect.x = 0;
93 window_rect.y = 0;
94 window_rect.width = gdk_window_get_width (window);
95 window_rect.height = gdk_window_get_height (window);
96
97 /* If nothing else is known, repaint everything so that the back
98 * buffer is fully up-to-date for the swapbuffer
99 */
100 cairo_region_union_rectangle (update_area, &window_rect);
101 }
102}
103
104#define N_EGL_ATTRS 16
105
106static gboolean
107gdk_wayland_gl_context_realize (GdkGLContext *context,
108 GError **error)
109{
110 GdkWaylandGLContext *context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
111 GdkDisplay *display = gdk_gl_context_get_display (context);
112 GdkGLContext *share = gdk_gl_context_get_shared_context (context);
113 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
114 EGLContext ctx;
115 EGLint context_attribs[N_EGL_ATTRS];
116 int major, minor, flags;
117 gboolean debug_bit, forward_bit, legacy_bit, use_es;
118 int i = 0;
119
120 gdk_gl_context_get_required_version (context, &major, &minor);
121 debug_bit = gdk_gl_context_get_debug_enabled (context);
122 forward_bit = gdk_gl_context_get_forward_compatible (context);
123 legacy_bit = (_gdk_gl_flags & GDK_GL_LEGACY) != 0 ||
124 (share != NULL && gdk_gl_context_is_legacy (share));
125 use_es = (_gdk_gl_flags & GDK_GL_GLES) != 0 ||
126 (share != NULL && gdk_gl_context_get_use_es (share));
127
128 flags = 0;
129
130 if (debug_bit)
131 flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
132 if (forward_bit)
133 flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
134
135 if (!use_es)
136 {
137 eglBindAPI (EGL_OPENGL_API);
138
139 /* We want a core profile, unless in legacy mode */
140 context_attribs[i++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
141 context_attribs[i++] = legacy_bit
142 ? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR
143 : EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
144
145 /* Specify the version */
146 context_attribs[i++] = EGL_CONTEXT_MAJOR_VERSION_KHR;
147 context_attribs[i++] = legacy_bit ? 3 : major;
148 context_attribs[i++] = EGL_CONTEXT_MINOR_VERSION_KHR;
149 context_attribs[i++] = legacy_bit ? 0 : minor;
150 }
151 else
152 {
153 eglBindAPI (EGL_OPENGL_ES_API);
154
155 context_attribs[i++] = EGL_CONTEXT_CLIENT_VERSION;
156 if (major == 3)
157 context_attribs[i++] = 3;
158 else
159 context_attribs[i++] = 2;
160 }
161
162 /* Specify the flags */
163 context_attribs[i++] = EGL_CONTEXT_FLAGS_KHR;
164 context_attribs[i++] = flags;
165
166 context_attribs[i++] = EGL_NONE;
167 g_assert (i < N_EGL_ATTRS);
168
169 GDK_NOTE (OPENGL, g_message ("Creating EGL context version %d.%d (debug:%s, forward:%s, legacy:%s, es:%s)",
170 major, minor,
171 debug_bit ? "yes" : "no",
172 forward_bit ? "yes" : "no",
173 legacy_bit ? "yes" : "no",
174 use_es ? "yes" : "no"));
175
176 ctx = eglCreateContext (display_wayland->egl_display,
177 context_wayland->egl_config,
178 share != NULL ? GDK_WAYLAND_GL_CONTEXT (share)->egl_context
179 : EGL_NO_CONTEXT,
180 context_attribs);
181
182 /* If context creation failed without the legacy bit, let's try again with it */
183 if (ctx == NULL && !legacy_bit)
184 {
185 /* Ensure that re-ordering does not break the offsets */
186 g_assert (context_attribs[0] == EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
187 context_attribs[1] = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR;
188 context_attribs[3] = 3;
189 context_attribs[5] = 0;
190
191 eglBindAPI (EGL_OPENGL_API);
192
193 legacy_bit = TRUE;
194 use_es = FALSE;
195
196 GDK_NOTE (OPENGL, g_message ("eglCreateContext failed, switching to legacy"));
197 ctx = eglCreateContext (display_wayland->egl_display,
198 context_wayland->egl_config,
199 share != NULL ? GDK_WAYLAND_GL_CONTEXT (share)->egl_context
200 : EGL_NO_CONTEXT,
201 context_attribs);
202 }
203
204 if (ctx == NULL)
205 {
206 g_set_error_literal (error, GDK_GL_ERROR,
207 GDK_GL_ERROR_NOT_AVAILABLE,
208 _("Unable to create a GL context"));
209 return FALSE;
210 }
211
212 GDK_NOTE (OPENGL, g_message ("Created EGL context[%p]", ctx));
213
214 context_wayland->egl_context = ctx;
215
216 gdk_gl_context_set_is_legacy (context, legacy_bit);
217 gdk_gl_context_set_use_es (context, use_es);
218
219 return TRUE;
220}
221
222static void
223gdk_wayland_gl_context_end_frame (GdkGLContext *context,
224 cairo_region_t *painted,
225 cairo_region_t *damage)
226{
227 GdkWindow *window = gdk_gl_context_get_window (context);
228 GdkDisplay *display = gdk_window_get_display (window);
229 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
230 GdkWaylandGLContext *context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
231 EGLSurface egl_surface;
232
233 gdk_gl_context_make_current (context);
234
235 egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window,
236 context_wayland->egl_config);
237
238 /* TODO: Use eglSwapBuffersWithDamageEXT if available */
239 if (display_wayland->have_egl_swap_buffers_with_damage)
240 {
241 int i, j, n_rects = cairo_region_num_rectangles (damage);
242 EGLint *rects = g_new (EGLint, n_rects * 4);
243 cairo_rectangle_int_t rect;
244 int window_height = gdk_window_get_height (window);
245
246 for (i = 0, j = 0; i < n_rects; i++)
247 {
248 cairo_region_get_rectangle (damage, i, &rect);
249 rects[j++] = rect.x;
250 rects[j++] = window_height - rect.height - rect.y;
251 rects[j++] = rect.width;
252 rects[j++] = rect.height;
253 }
254 eglSwapBuffersWithDamageEXT (display_wayland->egl_display, egl_surface, rects, n_rects);
255 g_free (rects);
256 }
257 else
258 eglSwapBuffers (display_wayland->egl_display, egl_surface);
259}
260
261static void
262gdk_wayland_gl_context_class_init (GdkWaylandGLContextClass *klass)
263{
264 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
265 GdkGLContextClass *context_class = GDK_GL_CONTEXT_CLASS (klass);
266
267 gobject_class->dispose = gdk_x11_gl_context_dispose;
268
269 context_class->realize = gdk_wayland_gl_context_realize;
270 context_class->end_frame = gdk_wayland_gl_context_end_frame;
271}
272
273static void
274gdk_wayland_gl_context_init (GdkWaylandGLContext *self)
275{
276}
277
278gboolean
279gdk_wayland_display_init_gl (GdkDisplay *display)
280{
281 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
282 EGLint major, minor;
283 EGLDisplay *dpy;
284
285 if (display_wayland->have_egl)
286 return TRUE;
287
288 dpy = eglGetDisplay ((EGLNativeDisplayType)display_wayland->wl_display);
289 if (dpy == NULL)
290 return FALSE;
291
292 if (!eglInitialize (dpy, &major, &minor))
293 return FALSE;
294
295 if (!eglBindAPI (EGL_OPENGL_API))
296 return FALSE;
297
298 display_wayland->egl_display = dpy;
299 display_wayland->egl_major_version = major;
300 display_wayland->egl_minor_version = minor;
301
302 display_wayland->have_egl = TRUE;
303
304 display_wayland->have_egl_khr_create_context =
305 epoxy_has_egl_extension (dpy, "EGL_KHR_create_context");
306
307 display_wayland->have_egl_buffer_age =
308 epoxy_has_egl_extension (dpy, "EGL_EXT_buffer_age");
309
310 display_wayland->have_egl_swap_buffers_with_damage =
311 epoxy_has_egl_extension (dpy, "EGL_EXT_swap_buffers_with_damage");
312
313 display_wayland->have_egl_surfaceless_context =
314 epoxy_has_egl_extension (dpy, "EGL_KHR_surfaceless_context");
315
316 GDK_NOTE (OPENGL,
317 g_message ("EGL API version %d.%d found\n"
318 " - Vendor: %s\n"
319 " - Version: %s\n"
320 " - Client APIs: %s\n"
321 " - Extensions:\n"
322 "\t%s",
323 display_wayland->egl_major_version,
324 display_wayland->egl_minor_version,
325 eglQueryString (dpy, EGL_VENDOR),
326 eglQueryString (dpy, EGL_VERSION),
327 eglQueryString (dpy, EGL_CLIENT_APIS),
328 eglQueryString (dpy, EGL_EXTENSIONS)));
329
330 return TRUE;
331}
332
333#define MAX_EGL_ATTRS 30
334
335static gboolean
336find_eglconfig_for_window (GdkWindow *window,
337 EGLConfig *egl_config_out,
338 GError **error)
339{
340 GdkDisplay *display = gdk_window_get_display (window);
341 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
342 GdkVisual *visual = gdk_window_get_visual (window);
343 EGLint attrs[MAX_EGL_ATTRS];
344 EGLint count;
345 EGLConfig *configs;
346 gboolean use_rgba;
347
348 int i = 0;
349
350 attrs[i++] = EGL_SURFACE_TYPE;
351 attrs[i++] = EGL_WINDOW_BIT;
352
353 attrs[i++] = EGL_COLOR_BUFFER_TYPE;
354 attrs[i++] = EGL_RGB_BUFFER;
355
356 attrs[i++] = EGL_RED_SIZE;
357 attrs[i++] = 1;
358 attrs[i++] = EGL_GREEN_SIZE;
359 attrs[i++] = 1;
360 attrs[i++] = EGL_BLUE_SIZE;
361 attrs[i++] = 1;
362
363 use_rgba = (visual == gdk_screen_get_rgba_visual (gdk_display_get_default_screen (display)));
364
365 if (use_rgba)
366 {
367 attrs[i++] = EGL_ALPHA_SIZE;
368 attrs[i++] = 1;
369 }
370 else
371 {
372 attrs[i++] = EGL_ALPHA_SIZE;
373 attrs[i++] = 0;
374 }
375
376 attrs[i++] = EGL_NONE;
377 g_assert (i < MAX_EGL_ATTRS);
378
379 if (!eglChooseConfig (display_wayland->egl_display, attrs, NULL, 0, &count) || count < 1)
380 {
381 g_set_error_literal (error, GDK_GL_ERROR,
382 GDK_GL_ERROR_UNSUPPORTED_FORMAT,
383 _("No available configurations for the given pixel format"));
384 return FALSE;
385 }
386
387 configs = g_new (EGLConfig, count);
388
389 if (!eglChooseConfig (display_wayland->egl_display, attrs, configs, count, &count) || count < 1)
390 {
391 g_set_error_literal (error, GDK_GL_ERROR,
392 GDK_GL_ERROR_UNSUPPORTED_FORMAT,
393 _("No available configurations for the given pixel format"));
394 return FALSE;
395 }
396
397 /* Pick first valid configuration i guess? */
398
399 if (egl_config_out != NULL)
400 *egl_config_out = configs[0];
401
402 g_free (configs);
403
404 return TRUE;
405}
406
407GdkGLContext *
408gdk_wayland_window_create_gl_context (GdkWindow *window,
409 gboolean attached,
410 GdkGLContext *share,
411 GError **error)
412{
413 GdkDisplay *display = gdk_window_get_display (window);
414 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
415 GdkWaylandGLContext *context;
416 EGLConfig config;
417
418 if (!gdk_wayland_display_init_gl (display))
419 {
420 g_set_error_literal (error, GDK_GL_ERROR,
421 GDK_GL_ERROR_NOT_AVAILABLE,
422 _("No GL implementation is available"));
423 return NULL;
424 }
425
426 if (!display_wayland->have_egl_khr_create_context)
427 {
428 g_set_error_literal (error, GDK_GL_ERROR,
429 GDK_GL_ERROR_UNSUPPORTED_PROFILE,
430 _("Core GL is not available on EGL implementation"));
431 return NULL;
432 }
433
434 if (!find_eglconfig_for_window (window, &config, error))
435 return NULL;
436
437 context = g_object_new (GDK_TYPE_WAYLAND_GL_CONTEXT,
438 "display", display,
439 "window", window,
440 "shared-context", share,
441 NULL);
442
443 context->egl_config = config;
444 context->is_attached = attached;
445
446 return GDK_GL_CONTEXT (context);
447}
448
449static void
450gdk_x11_gl_context_dispose (GObject *gobject)
451{
452 GdkWaylandGLContext *context_wayland = GDK_WAYLAND_GL_CONTEXT (gobject);
453
454 if (context_wayland->egl_context != NULL)
455 {
456 GdkGLContext *context = GDK_GL_CONTEXT (gobject);
457 GdkWindow *window = gdk_gl_context_get_window (context);
458 GdkDisplay *display = gdk_window_get_display (window);
459 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
460
461 if (eglGetCurrentContext () == context_wayland->egl_context)
462 eglMakeCurrent(display_wayland->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
463 EGL_NO_CONTEXT);
464
465 GDK_NOTE (OPENGL, g_message ("Destroying EGL context"));
466
467 eglDestroyContext (display_wayland->egl_display,
468 context_wayland->egl_context);
469 context_wayland->egl_context = NULL;
470 }
471
472 G_OBJECT_CLASS (gdk_wayland_gl_context_parent_class)->dispose (gobject);
473}
474
475gboolean
476gdk_wayland_display_make_gl_context_current (GdkDisplay *display,
477 GdkGLContext *context)
478{
479 GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
480 GdkWaylandGLContext *context_wayland;
481 GdkWindow *window;
482 EGLSurface egl_surface;
483
484 if (context == NULL)
485 {
486 eglMakeCurrent(display_wayland->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
487 EGL_NO_CONTEXT);
488 return TRUE;
489 }
490
491 context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
492 window = gdk_gl_context_get_window (context);
493
494 if (context_wayland->is_attached)
495 egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window, context_wayland->egl_config);
496 else
497 {
498 if (display_wayland->have_egl_surfaceless_context)
499 egl_surface = EGL_NO_SURFACE;
500 else
501 egl_surface = gdk_wayland_window_get_dummy_egl_surface (window->impl_window,
502 context_wayland->egl_config);
503 }
504
505 if (!eglMakeCurrent (display_wayland->egl_display, egl_surface,
506 egl_surface, context_wayland->egl_context))
507 {
508 g_warning ("eglMakeCurrent failed");
509 return FALSE;
510 }
511
512 return TRUE;
513}
514