Blender V4.3
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
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 bindAPI(m_api);
295 return EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context)) ? GHOST_kSuccess :
297 }
298 return GHOST_kFailure;
299}
300
302{
303 if (m_display) {
304 bindAPI(m_api);
305
306 return EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) ?
309 }
310 return GHOST_kFailure;
311}
312
313inline bool GHOST_ContextEGL::bindAPI(EGLenum api)
314{
315 if (epoxy_egl_version(m_display) >= 12) {
316 return (EGL_CHK(eglBindAPI(api)) == EGL_TRUE);
317 }
318
319 return false;
320}
321
322static const std::string &api_string(EGLenum api)
323{
324 static const std::string a("OpenGL");
325 static const std::string b("OpenGL ES");
326 static const std::string c("OpenVG");
327
328 return choose_api(api, a, b, c);
329}
330
332{
333 /* Objects have to be declared here due to the use of `goto`. */
334 std::vector<EGLint> attrib_list;
335 EGLint num_config = 0;
336
337 if (m_stereoVisual) {
338 fprintf(stderr, "Warning! Stereo OpenGL ES contexts are not supported.\n");
339 }
340 m_stereoVisual = false; /* It doesn't matter what the Window wants. */
341
342 EGLDisplay prev_display = eglGetCurrentDisplay();
343 EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
344 EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
345 EGLContext prev_context = eglGetCurrentContext();
346
347 EGLint egl_major = 0, egl_minor = 0;
348
349 if (!EGL_CHK((m_display = ::eglGetDisplay(m_nativeDisplay)) != EGL_NO_DISPLAY)) {
350 goto error;
351 }
352
353 {
354 const EGLBoolean init_display_result = ::eglInitialize(m_display, &egl_major, &egl_minor);
355 const EGLint init_display_error = (init_display_result) ? 0 : eglGetError();
356
357 if (!init_display_result || (egl_major == 0 && egl_minor == 0)) {
358 /* We failed to create a regular render window, retry and see if we can create a headless
359 * render context. */
360 ::eglTerminate(m_display);
361
362 const char *egl_extension_st = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
363 assert(egl_extension_st != nullptr);
364 assert(egl_extension_st == nullptr ||
365 strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") != nullptr);
366 if (egl_extension_st == nullptr ||
367 strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") == nullptr)
368 {
369 egl_print_error("Failed to create display GPU context: ", init_display_error);
370 fprintf(
371 stderr,
372 "Failed to create headless GPU context: No EGL_MESA_platform_surfaceless extension");
373 goto error;
374 }
375
376 m_display = eglGetPlatformDisplayEXT(
377 EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr);
378
379 const EGLBoolean headless_result = ::eglInitialize(m_display, &egl_major, &egl_minor);
380 const EGLint init_headless_error = (headless_result) ? 0 : eglGetError();
381
382 if (!headless_result) {
383 egl_print_error("Failed to create display GPU context: ", init_display_error);
384 egl_print_error("Failed to create headless GPU context: ", init_headless_error);
385 goto error;
386 }
387 }
388 }
389
390#ifdef WITH_GHOST_DEBUG
391 fprintf(stderr, "EGL Version %d.%d\n", egl_major, egl_minor);
392#endif
393
394 if (!EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))) {
395 goto error;
396 }
397 if (!bindAPI(m_api)) {
398 goto error;
399 }
400
401 /* Build attribute list. */
402
403 attrib_list.reserve(20);
404
405 if (m_api == EGL_OPENGL_ES_API && epoxy_egl_version(m_display) >= 12) {
406 /* According to the spec it seems that you are required to set EGL_RENDERABLE_TYPE,
407 * but some implementations (ANGLE) do not seem to care. */
408
409 if (m_contextMajorVersion == 1) {
410 attrib_list.push_back(EGL_RENDERABLE_TYPE);
411 attrib_list.push_back(EGL_OPENGL_ES_BIT);
412 }
413 else if (m_contextMajorVersion == 2) {
414 attrib_list.push_back(EGL_RENDERABLE_TYPE);
415 attrib_list.push_back(EGL_OPENGL_ES2_BIT);
416 }
417 else if (m_contextMajorVersion == 3) {
418 attrib_list.push_back(EGL_RENDERABLE_TYPE);
419 attrib_list.push_back(EGL_OPENGL_ES3_BIT_KHR);
420 }
421 else {
422 fprintf(stderr,
423 "Warning! Unable to request an ES context of version %d.%d\n",
424 m_contextMajorVersion,
425 m_contextMinorVersion);
426 }
427
428 if (!((m_contextMajorVersion == 1) ||
429 (m_contextMajorVersion == 2 && epoxy_egl_version(m_display) >= 13) ||
430 (m_contextMajorVersion == 3 &&
431 epoxy_has_egl_extension(m_display, "KHR_create_context")) ||
432 (m_contextMajorVersion == 3 && epoxy_egl_version(m_display) >= 15)))
433 {
434 fprintf(stderr,
435 "Warning! May not be able to create a version %d.%d ES context with version %d.%d "
436 "of EGL\n",
437 m_contextMajorVersion,
438 m_contextMinorVersion,
439 egl_major,
440 egl_minor);
441 }
442 }
443 else {
444 attrib_list.push_back(EGL_RENDERABLE_TYPE);
445 attrib_list.push_back(EGL_OPENGL_BIT);
446 }
447
448 attrib_list.push_back(EGL_RED_SIZE);
449 attrib_list.push_back(8);
450
451 attrib_list.push_back(EGL_GREEN_SIZE);
452 attrib_list.push_back(8);
453
454 attrib_list.push_back(EGL_BLUE_SIZE);
455 attrib_list.push_back(8);
456
457 if (m_nativeWindow == 0) {
458 /* Off-screen surface. */
459 attrib_list.push_back(EGL_SURFACE_TYPE);
460 attrib_list.push_back(EGL_PBUFFER_BIT);
461 }
462
463 attrib_list.push_back(EGL_NONE);
464
465 if (!EGL_CHK(::eglChooseConfig(m_display, &(attrib_list[0]), &m_config, 1, &num_config))) {
466 goto error;
467 }
468
469 /* A common error is to assume that ChooseConfig worked because it returned EGL_TRUE. */
470 if (num_config != 1) { /* `num_config` should be exactly 1. */
471 goto error;
472 }
473
474 if (m_nativeWindow != 0) {
475 std::vector<EGLint> surface_attrib_list;
476 surface_attrib_list.reserve(3);
477#ifdef WITH_GHOST_WAYLAND
478 /* Fix transparency issue on: `Wayland + Nouveau/Zink+NVK`. Due to unsupported texture formats
479 * drivers can hit transparency code-paths resulting in showing the desktop in viewports.
480 *
481 * See #102994. */
482 /* EGL_EXT_present_opaque isn't added to the latest release of epoxy, but is part of the latest
483 * EGL https://github.com/KhronosGroup/EGL-Registry/blob/main/api/egl.xml */
484 if (epoxy_has_egl_extension(m_display, "EGL_EXT_present_opaque")) {
485# ifndef EGL_PRESENT_OPAQUE_EXT
486# define EGL_PRESENT_OPAQUE_EXT 0x31DF
487# endif
488 surface_attrib_list.push_back(EGL_PRESENT_OPAQUE_EXT);
489 surface_attrib_list.push_back(EGL_TRUE);
490 }
491#endif
492 surface_attrib_list.push_back(EGL_NONE);
493
494 m_surface = ::eglCreateWindowSurface(
495 m_display, m_config, m_nativeWindow, surface_attrib_list.data());
496 m_surface_from_native_window = true;
497 }
498 else {
499 static const EGLint pb_attrib_list[] = {
500 EGL_WIDTH,
501 1,
502 EGL_HEIGHT,
503 1,
504 EGL_NONE,
505 };
506 m_surface = ::eglCreatePbufferSurface(m_display, m_config, pb_attrib_list);
507 }
508
509 if (!EGL_CHK(m_surface != EGL_NO_SURFACE)) {
510 goto error;
511 }
512 attrib_list.clear();
513
514 if (epoxy_egl_version(m_display) >= 15 ||
515 epoxy_has_egl_extension(m_display, "KHR_create_context"))
516 {
517 if (m_api == EGL_OPENGL_API || m_api == EGL_OPENGL_ES_API) {
518 if (m_contextMajorVersion != 0) {
519 attrib_list.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
520 attrib_list.push_back(m_contextMajorVersion);
521 }
522
523 if (m_contextMinorVersion != 0) {
524 attrib_list.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
525 attrib_list.push_back(m_contextMinorVersion);
526 }
527
528 if (m_contextFlags != 0) {
529 attrib_list.push_back(EGL_CONTEXT_FLAGS_KHR);
530 attrib_list.push_back(m_contextFlags);
531 }
532 }
533 else {
534 if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
535 fprintf(stderr,
536 "Warning! Cannot request specific versions of %s contexts.",
537 api_string(m_api).c_str());
538 }
539
540 if (m_contextFlags != 0) {
541 fprintf(stderr, "Warning! Flags cannot be set on %s contexts.", api_string(m_api).c_str());
542 }
543 }
544
545 if (m_api == EGL_OPENGL_API) {
546 if (m_contextProfileMask != 0) {
547 attrib_list.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
548 attrib_list.push_back(m_contextProfileMask);
549 }
550 }
551 else {
552 if (m_contextProfileMask != 0) {
553 fprintf(
554 stderr, "Warning! Cannot select profile for %s contexts.", api_string(m_api).c_str());
555 }
556 }
557
558 if (m_api == EGL_OPENGL_API || epoxy_egl_version(m_display) >= 15) {
559 if (m_contextResetNotificationStrategy != 0) {
560 attrib_list.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
561 attrib_list.push_back(m_contextResetNotificationStrategy);
562 }
563 }
564 else {
565 if (m_contextResetNotificationStrategy != 0) {
566 fprintf(stderr,
567 "Warning! EGL %d.%d cannot set the reset notification strategy on %s contexts.",
568 egl_major,
569 egl_minor,
570 api_string(m_api).c_str());
571 }
572 }
573 }
574 else {
575 if (m_api == EGL_OPENGL_ES_API) {
576 if (m_contextMajorVersion != 0) {
577 attrib_list.push_back(EGL_CONTEXT_CLIENT_VERSION);
578 attrib_list.push_back(m_contextMajorVersion);
579 }
580 }
581 else {
582 if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
583 fprintf(stderr,
584 "Warning! EGL %d.%d is unable to select between versions of %s.",
585 egl_major,
586 egl_minor,
587 api_string(m_api).c_str());
588 }
589 }
590
591 if (m_contextFlags != 0) {
592 fprintf(stderr, "Warning! EGL %d.%d is unable to set context flags.", egl_major, egl_minor);
593 }
594 if (m_contextProfileMask != 0) {
595 fprintf(stderr,
596 "Warning! EGL %d.%d is unable to select between profiles.",
597 egl_major,
598 egl_minor);
599 }
600 if (m_contextResetNotificationStrategy != 0) {
601 fprintf(stderr,
602 "Warning! EGL %d.%d is unable to set the reset notification strategies.",
603 egl_major,
604 egl_minor);
605 }
606 }
607
608 attrib_list.push_back(EGL_NONE);
609
610 m_context = ::eglCreateContext(m_display, m_config, m_sharedContext, &(attrib_list[0]));
611
612 if (!EGL_CHK(m_context != EGL_NO_CONTEXT)) {
613 goto error;
614 }
615
616 if (m_sharedContext == EGL_NO_CONTEXT) {
617 m_sharedContext = m_context;
618 }
619
620 m_sharedCount++;
621
622 if (!EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context))) {
623 goto error;
624 }
625
626 if (m_nativeWindow != 0) {
627 initClearGL();
628 ::eglSwapBuffers(m_display, m_surface);
629 }
630
631 return GHOST_kSuccess;
632
633error:
634 if (prev_display != EGL_NO_DISPLAY) {
635 EGL_CHK(eglMakeCurrent(prev_display, prev_draw, prev_read, prev_context));
636 }
637 return GHOST_kFailure;
638}
639
641{
642 m_nativeDisplay = nullptr;
643
644 m_nativeWindow = 0;
645 if (m_surface_from_native_window) {
646 m_surface = EGL_NO_SURFACE;
647 }
648
649 return GHOST_kSuccess;
650}
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:87
@ GHOST_kFailure
Definition GHOST_Types.h:87
@ GHOST_kSuccess
Definition GHOST_Types.h:87
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
local_group_size(16, 16) .push_constant(Type b
static void error(const char *str)