The open source OpenXR runtime
1// Copyright 2019-2021, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Glue code to EGL client side glue code.
6 * @author Drew DeVault <sir@cmpwn.com>
7 * @author Simon Ser <contact@emersion.fr>
8 * @author Jakob Bornecrantz <jakob@collabora.com>
9 * @ingroup comp_client
10 */
11
12#include "xrt/xrt_config_os.h"
13#include "xrt/xrt_config_have.h"
14#include "xrt/xrt_gfx_egl.h"
15#include "xrt/xrt_handles.h"
16
17#include "util/u_misc.h"
18#include "util/u_logging.h"
19#include "util/u_debug.h"
20
21#include "ogl/egl_api.h"
22#include "ogl/ogl_api.h"
23
24#include "client/comp_gl_client.h"
25#include "client/comp_egl_client.h"
26#include "client/comp_gl_memobj_swapchain.h"
27#include "client/comp_gl_eglimage_swapchain.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31
32#ifndef XRT_HAVE_EGL
33#error "This file shouldn't be compiled without EGL"
34#endif
35
36
37/*
38 *
39 * Logging.
40 *
41 */
42
43static enum u_logging_level log_level;
44
45#define EGL_TRACE(...) U_LOG_IFL_T(log_level, __VA_ARGS__)
46#define EGL_DEBUG(...) U_LOG_IFL_D(log_level, __VA_ARGS__)
47#define EGL_INFO(...) U_LOG_IFL_I(log_level, __VA_ARGS__)
48#define EGL_WARN(...) U_LOG_IFL_W(log_level, __VA_ARGS__)
49#define EGL_ERROR(...) U_LOG_IFL_E(log_level, __VA_ARGS__)
50
51DEBUG_GET_ONCE_LOG_OPTION(egl_log, "EGL_LOG", U_LOGGING_INFO)
52
53
54/*
55 *
56 * Declarations.
57 *
58 */
59
60#ifdef XRT_OS_ANDROID
61typedef const char *EGLAPIENTRY (*PFNEGLQUERYSTRINGIMPLEMENTATIONANDROIDPROC)(EGLDisplay dpy, EGLint name);
62#endif
63
64// Not forward declared by mesa
65typedef EGLBoolean
66 EGLAPIENTRY (*PFNEGLMAKECURRENTPROC)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
67
68static xrt_result_t
69client_egl_insert_fence(struct xrt_compositor *xc, xrt_graphics_sync_handle_t *out_handle);
70
71
72/*
73 *
74 * Old helper.
75 *
76 */
77
78static inline void
79save_context(struct client_egl_context *ctx)
80{
81 ctx->dpy = eglGetCurrentDisplay();
82 ctx->ctx = EGL_NO_CONTEXT;
83 ctx->read = EGL_NO_SURFACE;
84 ctx->draw = EGL_NO_SURFACE;
85
86 if (ctx->dpy != EGL_NO_DISPLAY) {
87 ctx->ctx = eglGetCurrentContext();
88 ctx->read = eglGetCurrentSurface(EGL_READ);
89 ctx->draw = eglGetCurrentSurface(EGL_DRAW);
90 }
91}
92
93static inline bool
94restore_context(struct client_egl_context *ctx)
95{
96 /* We're using the current display if we're trying to restore a null context */
97 EGLDisplay dpy = ctx->dpy == EGL_NO_DISPLAY ? eglGetCurrentDisplay() : ctx->dpy;
98
99 if (dpy == EGL_NO_DISPLAY) {
100 /* If the current display is also null then the call is a no-op */
101 return true;
102 }
103
104 return eglMakeCurrent(dpy, ctx->draw, ctx->read, ctx->ctx);
105}
106
107
108/*
109 *
110 * Helper functions.
111 *
112 */
113
114static const char *
115egl_error_str(EGLint ret)
116{
117 switch (ret) {
118 case EGL_SUCCESS: return "EGL_SUCCESS";
119 case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
120 case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
121 case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
122 case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
123 case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
124 case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
125 case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
126 case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
127 case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
128 case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
129 case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER";
130 case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP";
131 case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
132 case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
133 default: return "EGL_<UNKNOWN>";
134 }
135}
136
137static inline void
138destroy_context_with_check(EGLDisplay display, EGLContext context, const char *func)
139{
140 EGLBoolean eret = eglDestroyContext(display, context);
141 if (eret == EGL_FALSE) {
142 U_LOG_E("eglDestroyContext: %s (%s)", egl_error_str(eglGetError()), func);
143 }
144}
145
146#define DESTROY_CONTEXT(DPY, CTX) destroy_context_with_check(DPY, CTX, __func__)
147
148XRT_MAYBE_UNUSED static bool
149has_extension(const char *extensions, const char *ext)
150{
151 const char *loc = NULL;
152 const char *terminator = NULL;
153
154 if (extensions == NULL) {
155 return false;
156 }
157
158 while (1) {
159 loc = strstr(extensions, ext);
160 if (loc == NULL) {
161 return false;
162 }
163
164 terminator = loc + strlen(ext);
165 if ((loc == extensions || *(loc - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) {
166 return true;
167 }
168 extensions = terminator;
169 }
170}
171
172
173/*
174 *
175 * Creation helper functions.
176 *
177 */
178
179static void
180ensure_native_fence_is_loaded(EGLDisplay dpy, PFNEGLGETPROCADDRESSPROC get_gl_procaddr)
181{
182#ifdef XRT_OS_ANDROID
183 // clang-format off
184 PFNEGLQUERYSTRINGIMPLEMENTATIONANDROIDPROC eglQueryStringImplementationANDROID;
185 // clang-format on
186
187 eglQueryStringImplementationANDROID =
188 (PFNEGLQUERYSTRINGIMPLEMENTATIONANDROIDPROC)get_gl_procaddr("eglQueryStringImplementationANDROID");
189
190 // On Android, EGL_ANDROID_native_fence_sync only shows up in this
191 // extension list, not the normal one.
192 const char *ext = eglQueryStringImplementationANDROID(dpy, EGL_EXTENSIONS);
193 if (!has_extension(ext, "EGL_ANDROID_native_fence_sync")) {
194 return;
195 }
196
197 GLAD_EGL_ANDROID_native_fence_sync = true;
198 glad_eglDupNativeFenceFDANDROID =
199 (PFNEGLDUPNATIVEFENCEFDANDROIDPROC)get_gl_procaddr("eglDupNativeFenceFDANDROID");
200#endif
201}
202
203static xrt_result_t
204create_context(
205 EGLDisplay display, EGLConfig config, EGLContext app_context, EGLint api_type, EGLContext *out_our_context)
206{
207 EGLint old_api_type = eglQueryAPI();
208
209 eglBindAPI(api_type);
210
211 size_t attrc = 0;
212 EGLint attrs[9] = {0};
213
214 attrs[attrc++] = EGL_CONTEXT_MAJOR_VERSION;
215 attrs[attrc++] = 3;
216 // Panfrost only supports 3.1
217 attrs[attrc++] = EGL_CONTEXT_MINOR_VERSION;
218 attrs[attrc++] = 1;
219
220 if (api_type == EGL_OPENGL_API) {
221 attrs[attrc++] = EGL_CONTEXT_OPENGL_PROFILE_MASK;
222 attrs[attrc++] = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT;
223 }
224
225 EGLint strategy;
226 if (api_type == EGL_OPENGL_ES_API &&
227 eglQueryContext(display, app_context, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, &strategy)) {
228 attrs[attrc++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT;
229 attrs[attrc++] = strategy;
230 }
231
232 attrs[attrc++] = EGL_NONE;
233 assert(attrc <= ARRAY_SIZE(attrs));
234
235 EGLContext our_context = eglCreateContext(display, config, app_context, attrs);
236
237 // Restore old API type.
238 if (old_api_type == EGL_NONE) {
239 eglBindAPI(old_api_type);
240 }
241
242 if (our_context == EGL_NO_CONTEXT) {
243 EGL_ERROR("eglCreateContext: %s", egl_error_str(eglGetError()));
244 return XRT_ERROR_OPENGL;
245 }
246
247 *out_our_context = our_context;
248
249 return XRT_SUCCESS;
250}
251
252static xrt_result_t
253load_gl_functions(EGLint egl_client_type, PFNEGLGETPROCADDRESSPROC get_gl_procaddr)
254{
255 switch (egl_client_type) {
256 case EGL_OPENGL_API:
257#if defined(XRT_HAVE_OPENGL)
258 EGL_DEBUG("Loading GL functions");
259 gladLoadGL(get_gl_procaddr);
260 break;
261#else
262 EGL_ERROR("OpenGL support not including in this runtime build");
263 return XRT_ERROR_OPENGL;
264#endif
265
266 case EGL_OPENGL_ES_API:
267#if defined(XRT_HAVE_OPENGLES)
268 EGL_DEBUG("Loading GLES2 functions");
269 gladLoadGLES2(get_gl_procaddr);
270 break;
271#else
272 EGL_ERROR("OpenGL|ES support not including in this runtime build");
273 return XRT_ERROR_OPENGL;
274#endif
275 default: EGL_ERROR("Unsupported EGL client type: 0x%x", egl_client_type); return XRT_ERROR_OPENGL;
276 }
277
278 if (glGetString == NULL) {
279 EGL_ERROR("glGetString not loaded!");
280 return XRT_ERROR_OPENGL;
281 }
282
283 return XRT_SUCCESS;
284}
285
286static xrt_result_t
287check_context_and_debug_print(EGLint egl_client_type)
288{
289 EGL_DEBUG( //
290 "OpenGL context:" //
291 "\n\tGL_VERSION: %s" //
292 "\n\tGL_RENDERER: %s" //
293 "\n\tGL_VENDOR: %s", //
294 glGetString(GL_VERSION), //
295 glGetString(GL_RENDERER), //
296 glGetString(GL_VENDOR)); //
297
298
299 /*
300 * If a renderer is old enough to not support OpenGL(ES) 3 or above
301 * it won't support Monado at all, it's not a hard requirement and
302 * lets us detect weird errors early on some platforms.
303 */
304 if (!GLAD_GL_VERSION_3_0 && !GLAD_GL_ES_VERSION_3_0) {
305 switch (egl_client_type) {
306 default: EGL_ERROR("Unknown OpenGL version!"); break;
307 case EGL_OPENGL_API: EGL_ERROR("Must have OpenGL 3.0 or above!"); break;
308 case EGL_OPENGL_ES_API: EGL_ERROR("Must have OpenGL ES 3.0 or above!"); break;
309 }
310
311 return XRT_ERROR_OPENGL;
312 }
313
314
315 EGL_DEBUG("Extension availability:");
316#define DUMP_EXTENSION_STATUS(EXT) EGL_DEBUG(" - " #EXT ": %s", GLAD_##EXT ? "true" : "false")
317
318 DUMP_EXTENSION_STATUS(GL_EXT_memory_object);
319 DUMP_EXTENSION_STATUS(GL_EXT_memory_object_fd);
320 DUMP_EXTENSION_STATUS(GL_EXT_memory_object_win32);
321 DUMP_EXTENSION_STATUS(GL_OES_EGL_image_external);
322
323 DUMP_EXTENSION_STATUS(EGL_ANDROID_get_native_client_buffer);
324 DUMP_EXTENSION_STATUS(EGL_ANDROID_native_fence_sync);
325 DUMP_EXTENSION_STATUS(EGL_EXT_image_dma_buf_import_modifiers);
326 DUMP_EXTENSION_STATUS(EGL_KHR_fence_sync);
327 DUMP_EXTENSION_STATUS(EGL_KHR_image);
328 DUMP_EXTENSION_STATUS(EGL_KHR_image_base);
329 DUMP_EXTENSION_STATUS(EGL_KHR_reusable_sync);
330 DUMP_EXTENSION_STATUS(EGL_KHR_wait_sync);
331
332#undef DUMP_EXTENSION_STATUS
333
334
335 return XRT_SUCCESS;
336}
337
338static xrt_result_t
339get_client_gl_functions(client_gl_swapchain_create_func_t *out_sc_create_func,
340 client_gl_insert_fence_func_t *out_insert_fence)
341{
342 client_gl_swapchain_create_func_t sc_create_func = NULL;
343 client_gl_insert_fence_func_t insert_fence_func = NULL;
344
345
346#if defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_FD)
347
348 if (GLAD_GL_EXT_memory_object && GLAD_GL_EXT_memory_object_fd) {
349 EGL_DEBUG("Using GL memory object swapchain implementation");
350 sc_create_func = client_gl_memobj_swapchain_create;
351 }
352
353 if (sc_create_func == NULL && GLAD_EGL_EXT_image_dma_buf_import) {
354 EGL_DEBUG("Using EGL_Image swapchain implementation");
355 sc_create_func = client_gl_eglimage_swapchain_create;
356 }
357
358 if (sc_create_func == NULL) {
359 EGL_ERROR(
360 "Could not find a required extension: need either EGL_EXT_image_dma_buf_import or "
361 "GL_EXT_memory_object_fd");
362 return XRT_ERROR_OPENGL;
363 }
364
365#elif defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_AHARDWAREBUFFER)
366
367 EGL_DEBUG("Using EGL_Image swapchain implementation with AHardwareBuffer");
368 sc_create_func = client_gl_eglimage_swapchain_create;
369
370#endif
371
372 /*
373 * For now, only use the insert_fence callback only if
374 * EGL_ANDROID_native_fence_sync is available, revisit this when a more
375 * generic synchronization mechanism is implemented.
376 */
377 if (GLAD_EGL_ANDROID_native_fence_sync) {
378 insert_fence_func = client_egl_insert_fence;
379 }
380
381 *out_sc_create_func = sc_create_func;
382 *out_insert_fence = insert_fence_func;
383
384 return XRT_SUCCESS;
385}
386
387
388/*
389 *
390 * GL callback functions.
391 *
392 */
393
394static xrt_result_t
395client_egl_insert_fence(struct xrt_compositor *xc, xrt_graphics_sync_handle_t *out_handle)
396{
397 struct client_egl_compositor *ceglc = client_egl_compositor(xc);
398
399 *out_handle = XRT_GRAPHICS_SYNC_HANDLE_INVALID;
400 EGLDisplay dpy = ceglc->current.dpy;
401
402#ifdef XRT_GRAPHICS_SYNC_HANDLE_IS_FD
403 // https://registry.khronos.org/EGL/extensions/ANDROID/EGL_ANDROID_native_fence_sync.txt
404 // create also inserts the fence in the command stream
405 EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
406 if (sync == EGL_NO_SYNC_KHR) {
407 EGL_ERROR("Failed to insert fence!");
408 return XRT_ERROR_FENCE_CREATE_FAILED;
409 }
410
411 // Flush needed to create native FD
412 glFlush();
413
414 int fence_fd = eglDupNativeFenceFDANDROID(dpy, sync);
415 eglDestroySyncKHR(dpy, sync);
416
417 if (fence_fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
418 EGL_ERROR("Failed to get FD from fence!");
419 return XRT_ERROR_NATIVE_HANDLE_FENCE_ERROR;
420 }
421
422 *out_handle = fence_fd;
423
424#else
425 (void)cglc;
426#endif
427
428 return XRT_SUCCESS;
429}
430
431static xrt_result_t
432client_egl_context_begin(struct xrt_compositor *xc, enum client_gl_context_reason reason)
433{
434 struct client_egl_compositor *eglc = client_egl_compositor(xc);
435
436 //! @todo Handle this better, don't just assume that the context is current.
437 if (reason == CLIENT_GL_CONTEXT_REASON_SYNCHRONIZE) {
438 return XRT_SUCCESS;
439 }
440
441 save_context(&eglc->previous);
442 struct client_egl_context *cur = &eglc->current;
443
444 if (!eglMakeCurrent(cur->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, cur->ctx)) {
445 return XRT_ERROR_OPENGL;
446 }
447 return XRT_SUCCESS;
448}
449
450static void
451client_egl_context_end(struct xrt_compositor *xc, enum client_gl_context_reason reason)
452{
453 struct client_egl_compositor *eglc = client_egl_compositor(xc);
454
455 //! @todo Handle this better, don't just assume that the context is current.
456 if (reason == CLIENT_GL_CONTEXT_REASON_SYNCHRONIZE) {
457 return;
458 }
459
460 restore_context(&eglc->previous);
461}
462
463static void
464client_egl_compositor_destroy(struct xrt_compositor *xc)
465{
466 struct client_egl_compositor *ceglc = client_egl_compositor(xc);
467
468 client_gl_compositor_fini(&ceglc->base);
469
470 DESTROY_CONTEXT(ceglc->current.dpy, ceglc->current.ctx);
471 ceglc->current.ctx = EGL_NO_CONTEXT;
472 ceglc->current.dpy = EGL_NO_DISPLAY;
473
474 free(ceglc);
475}
476
477
478/*
479 *
480 * 'Exported' functions.
481 *
482 */
483
484xrt_result_t
485xrt_gfx_provider_create_gl_egl(struct xrt_compositor_native *xcn,
486 EGLDisplay display,
487 EGLConfig config,
488 EGLContext context,
489 PFNEGLGETPROCADDRESSPROC get_gl_procaddr,
490 bool renderdoc_enabled,
491 struct xrt_compositor_gl **out_xcgl)
492{
493 log_level = debug_get_log_option_egl_log();
494 xrt_result_t xret;
495
496
497 /*
498 * Init EGL functions
499 */
500
501 gladLoadEGL(display, get_gl_procaddr);
502
503 if (config == EGL_NO_CONFIG_KHR && !EGL_KHR_no_config_context) {
504 EGL_ERROR("config == EGL_NO_CONFIG_KHR && !EGL_KHR_no_config_context");
505 return XRT_ERROR_EGL_CONFIG_MISSING;
506 }
507
508 // On Android this extension is 'hidden'.
509 ensure_native_fence_is_loaded(display, get_gl_procaddr);
510
511
512 /*
513 * Get client type.
514 */
515
516 EGLint egl_client_type;
517 if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_TYPE, &egl_client_type)) {
518 EGL_ERROR("Could not query EGL client API type from context: %p", (void *)context);
519 return XRT_ERROR_OPENGL;
520 }
521
522
523 /*
524 * Create context.
525 */
526
527 xret = create_context(display, config, context, egl_client_type, &context);
528 if (xret != XRT_SUCCESS) {
529 return xret;
530 }
531
532
533 /*
534 * Make current.
535 */
536
537 // Save old EGL display, context and drawables.
538 struct client_egl_context old = {0};
539 save_context(&old);
540
541 if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) {
542 EGL_ERROR(
543 "eglMakeCurrent: %s"
544 "\n\tFailed to make EGL context current"
545 "\n\told - dpy: %p, ctx: %p, read: %p, draw: %p"
546 "\n\tnew - dpy: %p, ctx: %p, read: %p, draw: %p",
547 egl_error_str(eglGetError()), //
548 (void *)old.dpy, (void *)old.ctx, (void *)old.read, (void *)old.draw, //
549 (void *)display, (void *)context, NULL, NULL); //
550
551 DESTROY_CONTEXT(display, context);
552
553 // No need to restore on failure.
554 return XRT_ERROR_OPENGL;
555 }
556
557
558 /*
559 * Use helpers to do all setup.
560 */
561
562 // Load GL functions, only EGL functions where loaded above.
563 xret = load_gl_functions(egl_client_type, get_gl_procaddr);
564 if (xret != XRT_SUCCESS) {
565 restore_context(&old);
566 DESTROY_CONTEXT(display, context);
567 return xret;
568 }
569
570 // Some consistency/extension availability checking.
571 xret = check_context_and_debug_print(egl_client_type);
572 if (xret != XRT_SUCCESS) {
573 restore_context(&old);
574 DESTROY_CONTEXT(display, context);
575 return xret;
576 }
577
578 // Get functions.
579 client_gl_swapchain_create_func_t sc_create_func = NULL;
580 client_gl_insert_fence_func_t insert_fence_func = NULL;
581
582 xret = get_client_gl_functions(&sc_create_func, &insert_fence_func);
583 if (xret != XRT_SUCCESS) {
584 restore_context(&old);
585 DESTROY_CONTEXT(display, context);
586 return xret;
587 }
588
589
590 /*
591 * Now do the allocation and init.
592 */
593
594 struct client_egl_compositor *ceglc = U_TYPED_CALLOC(struct client_egl_compositor);
595 ceglc->current.dpy = display;
596 ceglc->current.ctx = context;
597 ceglc->base.renderdoc_enabled = renderdoc_enabled;
598
599 bool bret = client_gl_compositor_init( //
600 &ceglc->base, // c
601 xcn, // xcn
602 client_egl_context_begin, // context_begin
603 client_egl_context_end, // context_end
604 sc_create_func, // create_swapchain
605 insert_fence_func); // insert_fence
606 if (!bret) {
607 free(ceglc);
608 EGL_ERROR("Failed to initialize compositor");
609 restore_context(&old);
610 DESTROY_CONTEXT(display, context);
611 return XRT_ERROR_OPENGL;
612 }
613
614 ceglc->base.base.base.destroy = client_egl_compositor_destroy;
615 restore_context(&old);
616 *out_xcgl = &ceglc->base.base;
617
618 return XRT_SUCCESS;
619}