Blender V5.0
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 const GHOST_ContextParams &context_params,
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(context_params),
200 system_(system),
201 native_display_(nativeDisplay),
202 native_window_(nativeWindow),
203 context_profile_mask_(contextProfileMask),
204 context_major_version_(contextMajorVersion),
205 context_minor_version_(contextMinorVersion),
206 context_flags_(contextFlags),
207 context_reset_notification_strategy_(contextResetNotificationStrategy),
208 api_(api),
209 context_(EGL_NO_CONTEXT),
210 surface_(EGL_NO_SURFACE),
211 display_(EGL_NO_DISPLAY),
212 config_(EGL_NO_CONFIG_KHR),
213 swap_interval_(1),
214 shared_context_(
215 choose_api(api, s_gl_sharedContext, s_gles_sharedContext, s_vg_sharedContext)),
216 shared_count_(choose_api(api, s_gl_sharedCount, s_gles_sharedCount, s_vg_sharedCount)),
217 surface_from_native_window_(false)
218{
219}
220
222{
223 if (display_ != EGL_NO_DISPLAY) {
224
225 bindAPI(api_);
226
227 if (context_ != EGL_NO_CONTEXT) {
228 if (context_ == ::eglGetCurrentContext()) {
229 EGL_CHK(::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
230 }
231 if (context_ != shared_context_ || shared_count_ == 1) {
232 assert(shared_count_ > 0);
233
234 shared_count_--;
235
236 if (shared_count_ == 0) {
237 shared_context_ = EGL_NO_CONTEXT;
238 }
239 EGL_CHK(::eglDestroyContext(display_, context_));
240 }
241 }
242
243 if (surface_ != EGL_NO_SURFACE) {
244 EGL_CHK(::eglDestroySurface(display_, surface_));
245 }
246 }
247}
248
250{
251 return EGL_CHK(::eglSwapBuffers(display_, surface_)) ? GHOST_kSuccess : GHOST_kFailure;
252}
253
255{
256 if (epoxy_egl_version(display_) >= 11) {
257 if (EGL_CHK(::eglSwapInterval(display_, interval))) {
258 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 interval_out = swap_interval_;
272
273 return GHOST_kSuccess;
274}
275
277{
278 return display_;
279}
280
282{
283 return config_;
284}
285
287{
288 return context_;
289}
290
292{
293 if (display_) {
294 active_context_ = this;
295 bindAPI(api_);
296 return EGL_CHK(::eglMakeCurrent(display_, surface_, surface_, context_)) ? GHOST_kSuccess :
298 }
299 return GHOST_kFailure;
300}
301
303{
304 if (display_) {
305 active_context_ = nullptr;
306 bindAPI(api_);
307
308 return EGL_CHK(::eglMakeCurrent(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(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 (context_params_.is_stereo_visual) {
340 fprintf(stderr, "Warning! Stereo OpenGL ES contexts are not supported.\n");
341 }
342 context_params_.is_stereo_visual = 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((display_ = ::eglGetDisplay(native_display_)) != EGL_NO_DISPLAY)) {
352 goto error;
353 }
354
355 {
356 const EGLBoolean init_display_result = ::eglInitialize(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(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 display_ = eglGetPlatformDisplayEXT(
379 EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr);
380
381 const EGLBoolean headless_result = ::eglInitialize(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(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))) {
397 goto error;
398 }
399 if (!bindAPI(api_)) {
400 goto error;
401 }
402
403 /* Build attribute list. */
404
405 attrib_list.reserve(20);
406
407 if (api_ == EGL_OPENGL_ES_API && epoxy_egl_version(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 (context_major_version_ == 1) {
412 attrib_list.push_back(EGL_RENDERABLE_TYPE);
413 attrib_list.push_back(EGL_OPENGL_ES_BIT);
414 }
415 else if (context_major_version_ == 2) {
416 attrib_list.push_back(EGL_RENDERABLE_TYPE);
417 attrib_list.push_back(EGL_OPENGL_ES2_BIT);
418 }
419 else if (context_major_version_ == 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 context_major_version_,
427 context_minor_version_);
428 }
429
430 if (!((context_major_version_ == 1) ||
431 (context_major_version_ == 2 && epoxy_egl_version(display_) >= 13) ||
432 (context_major_version_ == 3 &&
433 epoxy_has_egl_extension(display_, "KHR_create_context")) ||
434 (context_major_version_ == 3 && epoxy_egl_version(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 context_major_version_,
440 context_minor_version_,
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 (native_window_ == 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(display_, &(attrib_list[0]), &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 (native_window_ != 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(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 surface_ = ::eglCreateWindowSurface(
497 display_, config_, native_window_, surface_attrib_list.data());
498 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 surface_ = ::eglCreatePbufferSurface(display_, config_, pb_attrib_list);
509 }
510
511 if (!EGL_CHK(surface_ != EGL_NO_SURFACE)) {
512 goto error;
513 }
514 attrib_list.clear();
515
516 if (epoxy_egl_version(display_) >= 15 || epoxy_has_egl_extension(display_, "KHR_create_context"))
517 {
518 if (api_ == EGL_OPENGL_API || api_ == EGL_OPENGL_ES_API) {
519 if (context_major_version_ != 0) {
520 attrib_list.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
521 attrib_list.push_back(context_major_version_);
522 }
523
524 if (context_minor_version_ != 0) {
525 attrib_list.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
526 attrib_list.push_back(context_minor_version_);
527 }
528
529 if (context_flags_ != 0) {
530 attrib_list.push_back(EGL_CONTEXT_FLAGS_KHR);
531 attrib_list.push_back(context_flags_);
532 }
533 }
534 else {
535 if (context_major_version_ != 0 || context_minor_version_ != 0) {
536 fprintf(stderr,
537 "Warning! Cannot request specific versions of %s contexts.",
538 api_string(api_).c_str());
539 }
540
541 if (context_flags_ != 0) {
542 fprintf(stderr, "Warning! Flags cannot be set on %s contexts.", api_string(api_).c_str());
543 }
544 }
545
546 if (api_ == EGL_OPENGL_API) {
547 if (context_profile_mask_ != 0) {
548 attrib_list.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
549 attrib_list.push_back(context_profile_mask_);
550 }
551 }
552 else {
553 if (context_profile_mask_ != 0) {
554 fprintf(
555 stderr, "Warning! Cannot select profile for %s contexts.", api_string(api_).c_str());
556 }
557 }
558
559 if (api_ == EGL_OPENGL_API || epoxy_egl_version(display_) >= 15) {
560 if (context_reset_notification_strategy_ != 0) {
561 attrib_list.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
562 attrib_list.push_back(context_reset_notification_strategy_);
563 }
564 }
565 else {
566 if (context_reset_notification_strategy_ != 0) {
567 fprintf(stderr,
568 "Warning! EGL %d.%d cannot set the reset notification strategy on %s contexts.",
569 egl_major,
570 egl_minor,
571 api_string(api_).c_str());
572 }
573 }
574 }
575 else {
576 if (api_ == EGL_OPENGL_ES_API) {
577 if (context_major_version_ != 0) {
578 attrib_list.push_back(EGL_CONTEXT_CLIENT_VERSION);
579 attrib_list.push_back(context_major_version_);
580 }
581 }
582 else {
583 if (context_major_version_ != 0 || context_minor_version_ != 0) {
584 fprintf(stderr,
585 "Warning! EGL %d.%d is unable to select between versions of %s.",
586 egl_major,
587 egl_minor,
588 api_string(api_).c_str());
589 }
590 }
591
592 if (context_flags_ != 0) {
593 fprintf(stderr, "Warning! EGL %d.%d is unable to set context flags.", egl_major, egl_minor);
594 }
595 if (context_profile_mask_ != 0) {
596 fprintf(stderr,
597 "Warning! EGL %d.%d is unable to select between profiles.",
598 egl_major,
599 egl_minor);
600 }
601 if (context_reset_notification_strategy_ != 0) {
602 fprintf(stderr,
603 "Warning! EGL %d.%d is unable to set the reset notification strategies.",
604 egl_major,
605 egl_minor);
606 }
607 }
608
609 attrib_list.push_back(EGL_NONE);
610
611 context_ = ::eglCreateContext(display_, config_, shared_context_, &(attrib_list[0]));
612
613 if (!EGL_CHK(context_ != EGL_NO_CONTEXT)) {
614 goto error;
615 }
616
617 if (shared_context_ == EGL_NO_CONTEXT) {
618 shared_context_ = context_;
619 }
620
621 shared_count_++;
622
623 if (!EGL_CHK(::eglMakeCurrent(display_, surface_, surface_, context_))) {
624 goto error;
625 }
626
627 {
628 const GHOST_TVSyncModes vsync = getVSync();
629 if (vsync != GHOST_kVSyncModeUnset) {
630 setSwapInterval(int(vsync));
631 }
632 }
633
634 if (native_window_ != 0) {
635 initClearGL();
636 ::eglSwapBuffers(display_, surface_);
637 }
638
639 active_context_ = this;
640 return GHOST_kSuccess;
641
642error:
643 if (prev_display != EGL_NO_DISPLAY) {
644 EGL_CHK(eglMakeCurrent(prev_display, prev_draw, prev_read, prev_context));
645 }
646 return GHOST_kFailure;
647}
648
650{
651 native_display_ = nullptr;
652
653 native_window_ = 0;
654 if (surface_from_native_window_) {
655 surface_ = EGL_NO_SURFACE;
656 }
657
658 return GHOST_kSuccess;
659}
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:57
@ GHOST_kFailure
Definition GHOST_Types.h:57
@ GHOST_kSuccess
Definition GHOST_Types.h:57
GHOST_TVSyncModes
@ GHOST_kVSyncModeUnset
GHOST_TSuccess activateDrawingContext() override
GHOST_TSuccess releaseDrawingContext() override
GHOST_ContextEGL(const GHOST_System *const system, const GHOST_ContextParams &context_params, EGLNativeWindowType nativeWindow, EGLNativeDisplayType nativeDisplay, EGLint contextProfileMask, EGLint contextMajorVersion, EGLint contextMinorVersion, EGLint contextFlags, EGLint contextResetNotificationStrategy, EGLenum api)
GHOST_TSuccess initializeDrawingContext() override
EGLConfig getConfig() const
GHOST_TSuccess setSwapInterval(int interval) override
GHOST_TSuccess getSwapInterval(int &interval_out) override
EGLDisplay getDisplay() const
~GHOST_ContextEGL() override
EGLContext getContext() const
GHOST_TSuccess swapBufferRelease() override
GHOST_TSuccess releaseNativeHandles() override
GHOST_Context(const GHOST_ContextParams &context_params)
static GHOST_Context * active_context_
virtual GHOST_TVSyncModes getVSync()
GHOST_ContextParams context_params_
#define assert(assertion)
#define T
static void error(const char *str)