Blender V4.5
GHOST_ContextEGL.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include "GHOST_ContextEGL.hh"
12
13#include <set>
14#include <sstream>
15#include <vector>
16
17#include <cassert>
18#include <cstdio>
19#include <cstring>
20
21#define CASE_CODE_RETURN_STR(code) \
22 case code: \
23 return #code;
24
25static const char *get_egl_error_enum_string(EGLint error)
26{
27 switch (error) {
28 CASE_CODE_RETURN_STR(EGL_SUCCESS)
29 CASE_CODE_RETURN_STR(EGL_NOT_INITIALIZED)
30 CASE_CODE_RETURN_STR(EGL_BAD_ACCESS)
31 CASE_CODE_RETURN_STR(EGL_BAD_ALLOC)
32 CASE_CODE_RETURN_STR(EGL_BAD_ATTRIBUTE)
33 CASE_CODE_RETURN_STR(EGL_BAD_CONTEXT)
34 CASE_CODE_RETURN_STR(EGL_BAD_CONFIG)
35 CASE_CODE_RETURN_STR(EGL_BAD_CURRENT_SURFACE)
36 CASE_CODE_RETURN_STR(EGL_BAD_DISPLAY)
37 CASE_CODE_RETURN_STR(EGL_BAD_SURFACE)
38 CASE_CODE_RETURN_STR(EGL_BAD_MATCH)
39 CASE_CODE_RETURN_STR(EGL_BAD_PARAMETER)
40 CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_PIXMAP)
41 CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_WINDOW)
42 CASE_CODE_RETURN_STR(EGL_CONTEXT_LOST)
43 default:
44 return nullptr;
45 }
46}
47
48static const char *get_egl_error_message_string(EGLint error)
49{
50 switch (error) {
51 case EGL_SUCCESS:
52 return "The last function succeeded without error.";
53
54 case EGL_NOT_INITIALIZED:
55 return (
56 "EGL is not initialized, or could not be initialized, "
57 "for the specified EGL display connection.");
58
59 case EGL_BAD_ACCESS:
60 return (
61 "EGL cannot access a requested resource "
62 "(for example a context is bound in another thread).");
63
64 case EGL_BAD_ALLOC:
65 return "EGL failed to allocate resources for the requested operation.";
66
67 case EGL_BAD_ATTRIBUTE:
68 return "An unrecognized attribute or attribute value was passed in the attribute list.";
69
70 case EGL_BAD_CONTEXT:
71 return "An EGLContext argument does not name a valid EGL rendering context.";
72
73 case EGL_BAD_CONFIG:
74 return "An EGLConfig argument does not name a valid EGL frame buffer configuration.";
75
76 case EGL_BAD_CURRENT_SURFACE:
77 return (
78 "The current surface of the calling thread is a window, "
79 "pixel buffer or pixmap that is no longer valid.");
80
81 case EGL_BAD_DISPLAY:
82 return "An EGLDisplay argument does not name a valid EGL display connection.";
83
84 case EGL_BAD_SURFACE:
85 return (
86 "An EGLSurface argument does not name a valid surface "
87 "(window, pixel buffer or pixmap) configured for GL rendering.");
88
89 case EGL_BAD_MATCH:
90 return (
91 "Arguments are inconsistent "
92 "(for example, a valid context requires buffers not supplied by a valid surface).");
93
94 case EGL_BAD_PARAMETER:
95 return "One or more argument values are invalid.";
96
97 case EGL_BAD_NATIVE_PIXMAP:
98 return "A NativePixmapType argument does not refer to a valid native pixmap.";
99
100 case EGL_BAD_NATIVE_WINDOW:
101 return "A NativeWindowType argument does not refer to a valid native window.";
102
103 case EGL_CONTEXT_LOST:
104 return (
105 "A power management event has occurred. "
106 "The application must destroy all contexts and reinitialize OpenGL ES state "
107 "and objects to continue rendering.");
108
109 default:
110 return nullptr;
111 }
112}
113
114static void egl_print_error(const char *message, const EGLint error)
115{
116 const char *code = get_egl_error_enum_string(error);
117 const char *msg = get_egl_error_message_string(error);
118
119 fprintf(stderr,
120 "%sEGL Error (0x%04X): %s: %s\n",
121 message,
122 uint(error),
123 code ? code : "<Unknown>",
124 msg ? msg : "<Unknown>");
125}
126
127static bool egl_chk(bool result,
128 const char *file = nullptr,
129 int line = 0,
130 const char *text = nullptr)
131{
132 if (!result) {
133 const EGLint error = eglGetError();
134#ifndef NDEBUG
135 const char *code = get_egl_error_enum_string(error);
136 const char *msg = get_egl_error_message_string(error);
137 fprintf(stderr,
138 "%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
139 file,
140 line,
141 text,
142 uint(error),
143 code ? code : "<Unknown>",
144 msg ? msg : "<Unknown>");
145#else
147 (void)(file);
148 (void)(line);
149 (void)(text);
150#endif
151 }
152
153 return result;
154}
155
156#ifndef NDEBUG
157# define EGL_CHK(x) egl_chk((x), __FILE__, __LINE__, #x)
158#else
159# define EGL_CHK(x) egl_chk(x)
160#endif
161
162EGLContext GHOST_ContextEGL::s_gl_sharedContext = EGL_NO_CONTEXT;
163EGLint GHOST_ContextEGL::s_gl_sharedCount = 0;
164
165EGLContext GHOST_ContextEGL::s_gles_sharedContext = EGL_NO_CONTEXT;
166EGLint GHOST_ContextEGL::s_gles_sharedCount = 0;
167
168EGLContext GHOST_ContextEGL::s_vg_sharedContext = EGL_NO_CONTEXT;
169EGLint GHOST_ContextEGL::s_vg_sharedCount = 0;
170
171#ifdef _MSC_VER
172# pragma warning(disable : 4715)
173#endif
174
175template<typename T> T &choose_api(EGLenum api, T &a, T &b, T &c)
176{
177 switch (api) {
178 case EGL_OPENGL_API:
179 return a;
180 case EGL_OPENGL_ES_API:
181 return b;
182 case EGL_OPENVG_API:
183 return c;
184 default:
185 abort();
186 }
187}
188
190 bool stereoVisual,
191 EGLNativeWindowType nativeWindow,
192 EGLNativeDisplayType nativeDisplay,
193 EGLint contextProfileMask,
194 EGLint contextMajorVersion,
195 EGLint contextMinorVersion,
196 EGLint contextFlags,
197 EGLint contextResetNotificationStrategy,
198 EGLenum api)
199 : GHOST_Context(stereoVisual),
200 m_system(system),
201 m_nativeDisplay(nativeDisplay),
202 m_nativeWindow(nativeWindow),
203 m_contextProfileMask(contextProfileMask),
204 m_contextMajorVersion(contextMajorVersion),
205 m_contextMinorVersion(contextMinorVersion),
206 m_contextFlags(contextFlags),
207 m_contextResetNotificationStrategy(contextResetNotificationStrategy),
208 m_api(api),
209 m_context(EGL_NO_CONTEXT),
210 m_surface(EGL_NO_SURFACE),
211 m_display(EGL_NO_DISPLAY),
212 m_config(EGL_NO_CONFIG_KHR),
213 m_swap_interval(1),
214 m_sharedContext(
215 choose_api(api, s_gl_sharedContext, s_gles_sharedContext, s_vg_sharedContext)),
216 m_sharedCount(choose_api(api, s_gl_sharedCount, s_gles_sharedCount, s_vg_sharedCount)),
217 m_surface_from_native_window(false)
218{
219}
220
222{
223 if (m_display != EGL_NO_DISPLAY) {
224
225 bindAPI(m_api);
226
227 if (m_context != EGL_NO_CONTEXT) {
228 if (m_context == ::eglGetCurrentContext()) {
229 EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
230 }
231 if (m_context != m_sharedContext || m_sharedCount == 1) {
232 assert(m_sharedCount > 0);
233
234 m_sharedCount--;
235
236 if (m_sharedCount == 0) {
237 m_sharedContext = EGL_NO_CONTEXT;
238 }
239 EGL_CHK(::eglDestroyContext(m_display, m_context));
240 }
241 }
242
243 if (m_surface != EGL_NO_SURFACE) {
244 EGL_CHK(::eglDestroySurface(m_display, m_surface));
245 }
246 }
247}
248
250{
251 return EGL_CHK(::eglSwapBuffers(m_display, m_surface)) ? GHOST_kSuccess : GHOST_kFailure;
252}
253
255{
256 if (epoxy_egl_version(m_display) >= 11) {
257 if (EGL_CHK(::eglSwapInterval(m_display, interval))) {
258 m_swap_interval = interval;
259
260 return GHOST_kSuccess;
261 }
262 return GHOST_kFailure;
263 }
264 return GHOST_kFailure;
265}
266
268{
269 /* This is a bit of a kludge because there does not seem to
270 * be a way to query the swap interval with EGL. */
271 intervalOut = m_swap_interval;
272
273 return GHOST_kSuccess;
274}
275
277{
278 return m_display;
279}
280
282{
283 return m_config;
284}
285
287{
288 return m_context;
289}
290
292{
293 if (m_display) {
294 active_context_ = this;
295 bindAPI(m_api);
296 return EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context)) ? GHOST_kSuccess :
298 }
299 return GHOST_kFailure;
300}
301
303{
304 if (m_display) {
305 active_context_ = nullptr;
306 bindAPI(m_api);
307
308 return EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) ?
311 }
312 return GHOST_kFailure;
313}
314
315inline bool GHOST_ContextEGL::bindAPI(EGLenum api)
316{
317 if (epoxy_egl_version(m_display) >= 12) {
318 return (EGL_CHK(eglBindAPI(api)) == EGL_TRUE);
319 }
320
321 return false;
322}
323
324static const std::string &api_string(EGLenum api)
325{
326 static const std::string a("OpenGL");
327 static const std::string b("OpenGL ES");
328 static const std::string c("OpenVG");
329
330 return choose_api(api, a, b, c);
331}
332
334{
335 /* Objects have to be declared here due to the use of `goto`. */
336 std::vector<EGLint> attrib_list;
337 EGLint num_config = 0;
338
339 if (m_stereoVisual) {
340 fprintf(stderr, "Warning! Stereo OpenGL ES contexts are not supported.\n");
341 }
342 m_stereoVisual = false; /* It doesn't matter what the Window wants. */
343
344 EGLDisplay prev_display = eglGetCurrentDisplay();
345 EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
346 EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
347 EGLContext prev_context = eglGetCurrentContext();
348
349 EGLint egl_major = 0, egl_minor = 0;
350
351 if (!EGL_CHK((m_display = ::eglGetDisplay(m_nativeDisplay)) != EGL_NO_DISPLAY)) {
352 goto error;
353 }
354
355 {
356 const EGLBoolean init_display_result = ::eglInitialize(m_display, &egl_major, &egl_minor);
357 const EGLint init_display_error = (init_display_result) ? 0 : eglGetError();
358
359 if (!init_display_result || (egl_major == 0 && egl_minor == 0)) {
360 /* We failed to create a regular render window, retry and see if we can create a headless
361 * render context. */
362 ::eglTerminate(m_display);
363
364 const char *egl_extension_st = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
365 assert(egl_extension_st != nullptr);
366 assert(egl_extension_st == nullptr ||
367 strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") != nullptr);
368 if (egl_extension_st == nullptr ||
369 strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") == nullptr)
370 {
371 egl_print_error("Failed to create display GPU context: ", init_display_error);
372 fprintf(
373 stderr,
374 "Failed to create headless GPU context: No EGL_MESA_platform_surfaceless extension");
375 goto error;
376 }
377
378 m_display = eglGetPlatformDisplayEXT(
379 EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr);
380
381 const EGLBoolean headless_result = ::eglInitialize(m_display, &egl_major, &egl_minor);
382 const EGLint init_headless_error = (headless_result) ? 0 : eglGetError();
383
384 if (!headless_result) {
385 egl_print_error("Failed to create display GPU context: ", init_display_error);
386 egl_print_error("Failed to create headless GPU context: ", init_headless_error);
387 goto error;
388 }
389 }
390 }
391
392#ifdef WITH_GHOST_DEBUG
393 fprintf(stderr, "EGL Version %d.%d\n", egl_major, egl_minor);
394#endif
395
396 if (!EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))) {
397 goto error;
398 }
399 if (!bindAPI(m_api)) {
400 goto error;
401 }
402
403 /* Build attribute list. */
404
405 attrib_list.reserve(20);
406
407 if (m_api == EGL_OPENGL_ES_API && epoxy_egl_version(m_display) >= 12) {
408 /* According to the spec it seems that you are required to set EGL_RENDERABLE_TYPE,
409 * but some implementations (ANGLE) do not seem to care. */
410
411 if (m_contextMajorVersion == 1) {
412 attrib_list.push_back(EGL_RENDERABLE_TYPE);
413 attrib_list.push_back(EGL_OPENGL_ES_BIT);
414 }
415 else if (m_contextMajorVersion == 2) {
416 attrib_list.push_back(EGL_RENDERABLE_TYPE);
417 attrib_list.push_back(EGL_OPENGL_ES2_BIT);
418 }
419 else if (m_contextMajorVersion == 3) {
420 attrib_list.push_back(EGL_RENDERABLE_TYPE);
421 attrib_list.push_back(EGL_OPENGL_ES3_BIT_KHR);
422 }
423 else {
424 fprintf(stderr,
425 "Warning! Unable to request an ES context of version %d.%d\n",
426 m_contextMajorVersion,
427 m_contextMinorVersion);
428 }
429
430 if (!((m_contextMajorVersion == 1) ||
431 (m_contextMajorVersion == 2 && epoxy_egl_version(m_display) >= 13) ||
432 (m_contextMajorVersion == 3 &&
433 epoxy_has_egl_extension(m_display, "KHR_create_context")) ||
434 (m_contextMajorVersion == 3 && epoxy_egl_version(m_display) >= 15)))
435 {
436 fprintf(stderr,
437 "Warning! May not be able to create a version %d.%d ES context with version %d.%d "
438 "of EGL\n",
439 m_contextMajorVersion,
440 m_contextMinorVersion,
441 egl_major,
442 egl_minor);
443 }
444 }
445 else {
446 attrib_list.push_back(EGL_RENDERABLE_TYPE);
447 attrib_list.push_back(EGL_OPENGL_BIT);
448 }
449
450 attrib_list.push_back(EGL_RED_SIZE);
451 attrib_list.push_back(8);
452
453 attrib_list.push_back(EGL_GREEN_SIZE);
454 attrib_list.push_back(8);
455
456 attrib_list.push_back(EGL_BLUE_SIZE);
457 attrib_list.push_back(8);
458
459 if (m_nativeWindow == 0) {
460 /* Off-screen surface. */
461 attrib_list.push_back(EGL_SURFACE_TYPE);
462 attrib_list.push_back(EGL_PBUFFER_BIT);
463 }
464
465 attrib_list.push_back(EGL_NONE);
466
467 if (!EGL_CHK(::eglChooseConfig(m_display, &(attrib_list[0]), &m_config, 1, &num_config))) {
468 goto error;
469 }
470
471 /* A common error is to assume that ChooseConfig worked because it returned EGL_TRUE. */
472 if (num_config != 1) { /* `num_config` should be exactly 1. */
473 goto error;
474 }
475
476 if (m_nativeWindow != 0) {
477 std::vector<EGLint> surface_attrib_list;
478 surface_attrib_list.reserve(3);
479#ifdef WITH_GHOST_WAYLAND
480 /* Fix transparency issue on: `Wayland + Nouveau/Zink+NVK`. Due to unsupported texture formats
481 * drivers can hit transparency code-paths resulting in showing the desktop in viewports.
482 *
483 * See #102994. */
484 /* EGL_EXT_present_opaque isn't added to the latest release of epoxy, but is part of the latest
485 * EGL https://github.com/KhronosGroup/EGL-Registry/blob/main/api/egl.xml */
486 if (epoxy_has_egl_extension(m_display, "EGL_EXT_present_opaque")) {
487# ifndef EGL_PRESENT_OPAQUE_EXT
488# define EGL_PRESENT_OPAQUE_EXT 0x31DF
489# endif
490 surface_attrib_list.push_back(EGL_PRESENT_OPAQUE_EXT);
491 surface_attrib_list.push_back(EGL_TRUE);
492 }
493#endif
494 surface_attrib_list.push_back(EGL_NONE);
495
496 m_surface = ::eglCreateWindowSurface(
497 m_display, m_config, m_nativeWindow, surface_attrib_list.data());
498 m_surface_from_native_window = true;
499 }
500 else {
501 static const EGLint pb_attrib_list[] = {
502 EGL_WIDTH,
503 1,
504 EGL_HEIGHT,
505 1,
506 EGL_NONE,
507 };
508 m_surface = ::eglCreatePbufferSurface(m_display, m_config, pb_attrib_list);
509 }
510
511 if (!EGL_CHK(m_surface != EGL_NO_SURFACE)) {
512 goto error;
513 }
514 attrib_list.clear();
515
516 if (epoxy_egl_version(m_display) >= 15 ||
517 epoxy_has_egl_extension(m_display, "KHR_create_context"))
518 {
519 if (m_api == EGL_OPENGL_API || m_api == EGL_OPENGL_ES_API) {
520 if (m_contextMajorVersion != 0) {
521 attrib_list.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
522 attrib_list.push_back(m_contextMajorVersion);
523 }
524
525 if (m_contextMinorVersion != 0) {
526 attrib_list.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
527 attrib_list.push_back(m_contextMinorVersion);
528 }
529
530 if (m_contextFlags != 0) {
531 attrib_list.push_back(EGL_CONTEXT_FLAGS_KHR);
532 attrib_list.push_back(m_contextFlags);
533 }
534 }
535 else {
536 if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
537 fprintf(stderr,
538 "Warning! Cannot request specific versions of %s contexts.",
539 api_string(m_api).c_str());
540 }
541
542 if (m_contextFlags != 0) {
543 fprintf(stderr, "Warning! Flags cannot be set on %s contexts.", api_string(m_api).c_str());
544 }
545 }
546
547 if (m_api == EGL_OPENGL_API) {
548 if (m_contextProfileMask != 0) {
549 attrib_list.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
550 attrib_list.push_back(m_contextProfileMask);
551 }
552 }
553 else {
554 if (m_contextProfileMask != 0) {
555 fprintf(
556 stderr, "Warning! Cannot select profile for %s contexts.", api_string(m_api).c_str());
557 }
558 }
559
560 if (m_api == EGL_OPENGL_API || epoxy_egl_version(m_display) >= 15) {
561 if (m_contextResetNotificationStrategy != 0) {
562 attrib_list.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
563 attrib_list.push_back(m_contextResetNotificationStrategy);
564 }
565 }
566 else {
567 if (m_contextResetNotificationStrategy != 0) {
568 fprintf(stderr,
569 "Warning! EGL %d.%d cannot set the reset notification strategy on %s contexts.",
570 egl_major,
571 egl_minor,
572 api_string(m_api).c_str());
573 }
574 }
575 }
576 else {
577 if (m_api == EGL_OPENGL_ES_API) {
578 if (m_contextMajorVersion != 0) {
579 attrib_list.push_back(EGL_CONTEXT_CLIENT_VERSION);
580 attrib_list.push_back(m_contextMajorVersion);
581 }
582 }
583 else {
584 if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
585 fprintf(stderr,
586 "Warning! EGL %d.%d is unable to select between versions of %s.",
587 egl_major,
588 egl_minor,
589 api_string(m_api).c_str());
590 }
591 }
592
593 if (m_contextFlags != 0) {
594 fprintf(stderr, "Warning! EGL %d.%d is unable to set context flags.", egl_major, egl_minor);
595 }
596 if (m_contextProfileMask != 0) {
597 fprintf(stderr,
598 "Warning! EGL %d.%d is unable to select between profiles.",
599 egl_major,
600 egl_minor);
601 }
602 if (m_contextResetNotificationStrategy != 0) {
603 fprintf(stderr,
604 "Warning! EGL %d.%d is unable to set the reset notification strategies.",
605 egl_major,
606 egl_minor);
607 }
608 }
609
610 attrib_list.push_back(EGL_NONE);
611
612 m_context = ::eglCreateContext(m_display, m_config, m_sharedContext, &(attrib_list[0]));
613
614 if (!EGL_CHK(m_context != EGL_NO_CONTEXT)) {
615 goto error;
616 }
617
618 if (m_sharedContext == EGL_NO_CONTEXT) {
619 m_sharedContext = m_context;
620 }
621
622 m_sharedCount++;
623
624 if (!EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context))) {
625 goto error;
626 }
627
628 if (m_nativeWindow != 0) {
629 initClearGL();
630 ::eglSwapBuffers(m_display, m_surface);
631 }
632
633 active_context_ = this;
634 return GHOST_kSuccess;
635
636error:
637 if (prev_display != EGL_NO_DISPLAY) {
638 EGL_CHK(eglMakeCurrent(prev_display, prev_draw, prev_read, prev_context));
639 }
640 return GHOST_kFailure;
641}
642
644{
645 m_nativeDisplay = nullptr;
646
647 m_nativeWindow = 0;
648 if (m_surface_from_native_window) {
649 m_surface = EGL_NO_SURFACE;
650 }
651
652 return GHOST_kSuccess;
653}
unsigned int uint
static const char * get_egl_error_enum_string(EGLint error)
#define EGL_CHK(x)
static const std::string & api_string(EGLenum api)
T & choose_api(EGLenum api, T &a, T &b, T &c)
static const char * get_egl_error_message_string(EGLint error)
static void egl_print_error(const char *message, const EGLint error)
static bool egl_chk(bool result, const char *file=nullptr, int line=0, const char *text=nullptr)
#define CASE_CODE_RETURN_STR(code)
GHOST_TSuccess
Definition GHOST_Types.h:80
@ GHOST_kFailure
Definition GHOST_Types.h:80
@ GHOST_kSuccess
Definition GHOST_Types.h:80
GHOST_TSuccess activateDrawingContext() override
GHOST_TSuccess getSwapInterval(int &intervalOut) override
GHOST_TSuccess releaseDrawingContext() override
GHOST_TSuccess initializeDrawingContext() override
EGLConfig getConfig() const
GHOST_TSuccess setSwapInterval(int interval) override
GHOST_TSuccess swapBuffers() override
GHOST_ContextEGL(const GHOST_System *const system, bool stereoVisual, EGLNativeWindowType nativeWindow, EGLNativeDisplayType nativeDisplay, EGLint contextProfileMask, EGLint contextMajorVersion, EGLint contextMinorVersion, EGLint contextFlags, EGLint contextResetNotificationStrategy, EGLenum api)
EGLDisplay getDisplay() const
~GHOST_ContextEGL() override
EGLContext getContext() const
GHOST_TSuccess releaseNativeHandles() override
static GHOST_Context * active_context_
GHOST_Context(bool stereoVisual)
#define assert(assertion)
#define T
static void error(const char *str)