1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2013 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup GHOST
22  *
23  * Definition of GHOST_ContextEGL class.
24  */
25 
26 #include "GHOST_ContextEGL.h"
27 
28 #include <set>
29 #include <sstream>
30 #include <vector>
31 
32 #include <cassert>
33 #include <cstdio>
34 #include <cstring>
35 
36 #define CASE_CODE_RETURN_STR(code) \
37   case code: \
38     return #code;
39 
get_egl_error_enum_string(EGLint error)40 static const char *get_egl_error_enum_string(EGLint error)
41 {
42   switch (error) {
43     CASE_CODE_RETURN_STR(EGL_SUCCESS)
44     CASE_CODE_RETURN_STR(EGL_NOT_INITIALIZED)
45     CASE_CODE_RETURN_STR(EGL_BAD_ACCESS)
46     CASE_CODE_RETURN_STR(EGL_BAD_ALLOC)
47     CASE_CODE_RETURN_STR(EGL_BAD_ATTRIBUTE)
48     CASE_CODE_RETURN_STR(EGL_BAD_CONTEXT)
49     CASE_CODE_RETURN_STR(EGL_BAD_CONFIG)
50     CASE_CODE_RETURN_STR(EGL_BAD_CURRENT_SURFACE)
51     CASE_CODE_RETURN_STR(EGL_BAD_DISPLAY)
52     CASE_CODE_RETURN_STR(EGL_BAD_SURFACE)
53     CASE_CODE_RETURN_STR(EGL_BAD_MATCH)
54     CASE_CODE_RETURN_STR(EGL_BAD_PARAMETER)
55     CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_PIXMAP)
56     CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_WINDOW)
57     CASE_CODE_RETURN_STR(EGL_CONTEXT_LOST)
58     default:
59       return NULL;
60   }
61 }
62 
get_egl_error_message_string(EGLint error)63 static const char *get_egl_error_message_string(EGLint error)
64 {
65   switch (error) {
66     case EGL_SUCCESS:
67       return "The last function succeeded without error.";
68 
69     case EGL_NOT_INITIALIZED:
70       return (
71           "EGL is not initialized, or could not be initialized, "
72           "for the specified EGL display connection.");
73 
74     case EGL_BAD_ACCESS:
75       return (
76           "EGL cannot access a requested resource "
77           "(for example a context is bound in another thread).");
78 
79     case EGL_BAD_ALLOC:
80       return "EGL failed to allocate resources for the requested operation.";
81 
82     case EGL_BAD_ATTRIBUTE:
83       return "An unrecognized attribute or attribute value was passed in the attribute list.";
84 
85     case EGL_BAD_CONTEXT:
86       return "An EGLContext argument does not name a valid EGL rendering context.";
87 
88     case EGL_BAD_CONFIG:
89       return "An EGLConfig argument does not name a valid EGL frame buffer configuration.";
90 
91     case EGL_BAD_CURRENT_SURFACE:
92       return (
93           "The current surface of the calling thread is a window, "
94           "pixel buffer or pixmap that is no longer valid.");
95 
96     case EGL_BAD_DISPLAY:
97       return "An EGLDisplay argument does not name a valid EGL display connection.";
98 
99     case EGL_BAD_SURFACE:
100       return (
101           "An EGLSurface argument does not name a valid surface "
102           "(window, pixel buffer or pixmap) configured for GL rendering.");
103 
104     case EGL_BAD_MATCH:
105       return (
106           "Arguments are inconsistent "
107           "(for example, a valid context requires buffers not supplied by a valid surface).");
108 
109     case EGL_BAD_PARAMETER:
110       return "One or more argument values are invalid.";
111 
112     case EGL_BAD_NATIVE_PIXMAP:
113       return "A NativePixmapType argument does not refer to a valid native pixmap.";
114 
115     case EGL_BAD_NATIVE_WINDOW:
116       return "A NativeWindowType argument does not refer to a valid native window.";
117 
118     case EGL_CONTEXT_LOST:
119       return (
120           "A power management event has occurred. "
121           "The application must destroy all contexts and reinitialize OpenGL ES state "
122           "and objects to continue rendering.");
123 
124     default:
125       return NULL;
126   }
127 }
128 
egl_chk(bool result,const char * file=NULL,int line=0,const char * text=NULL)129 static bool egl_chk(bool result, const char *file = NULL, int line = 0, const char *text = NULL)
130 {
131   if (!result) {
132     const EGLint error = eglGetError();
133 
134     const char *code = get_egl_error_enum_string(error);
135     const char *msg = get_egl_error_message_string(error);
136 
137 #ifndef NDEBUG
138     fprintf(stderr,
139             "%s(%d):[%s] -> EGL Error (0x%04X): %s: %s\n",
140             file,
141             line,
142             text,
143             static_cast<unsigned int>(error),
144             code ? code : "<Unknown>",
145             msg ? msg : "<Unknown>");
146 #else
147     fprintf(stderr,
148             "EGL Error (0x%04X): %s: %s\n",
149             static_cast<unsigned int>(error),
150             code ? code : "<Unknown>",
151             msg ? msg : "<Unknown>");
152 #endif
153   }
154 
155   return result;
156 }
157 
158 #ifndef NDEBUG
159 #  define EGL_CHK(x) egl_chk((x), __FILE__, __LINE__, #  x)
160 #else
161 #  define EGL_CHK(x) egl_chk(x)
162 #endif
163 
bindAPI(EGLenum api)164 static inline bool bindAPI(EGLenum api)
165 {
166   if (EGLEW_VERSION_1_2) {
167     return (EGL_CHK(eglBindAPI(api)) == EGL_TRUE);
168   }
169 
170   return false;
171 }
172 
173 #ifdef WITH_GL_ANGLE
174 HMODULE GHOST_ContextEGL::s_d3dcompiler = NULL;
175 #endif
176 
177 EGLContext GHOST_ContextEGL::s_gl_sharedContext = EGL_NO_CONTEXT;
178 EGLint GHOST_ContextEGL::s_gl_sharedCount = 0;
179 
180 EGLContext GHOST_ContextEGL::s_gles_sharedContext = EGL_NO_CONTEXT;
181 EGLint GHOST_ContextEGL::s_gles_sharedCount = 0;
182 
183 EGLContext GHOST_ContextEGL::s_vg_sharedContext = EGL_NO_CONTEXT;
184 EGLint GHOST_ContextEGL::s_vg_sharedCount = 0;
185 
186 #pragma warning(disable : 4715)
187 
choose_api(EGLenum api,T & a,T & b,T & c)188 template<typename T> T &choose_api(EGLenum api, T &a, T &b, T &c)
189 {
190   switch (api) {
191     case EGL_OPENGL_API:
192       return a;
193     case EGL_OPENGL_ES_API:
194       return b;
195     case EGL_OPENVG_API:
196       return c;
197     default:
198       abort();
199   }
200 }
201 
GHOST_ContextEGL(bool stereoVisual,EGLNativeWindowType nativeWindow,EGLNativeDisplayType nativeDisplay,EGLint contextProfileMask,EGLint contextMajorVersion,EGLint contextMinorVersion,EGLint contextFlags,EGLint contextResetNotificationStrategy,EGLenum api)202 GHOST_ContextEGL::GHOST_ContextEGL(bool stereoVisual,
203                                    EGLNativeWindowType nativeWindow,
204                                    EGLNativeDisplayType nativeDisplay,
205                                    EGLint contextProfileMask,
206                                    EGLint contextMajorVersion,
207                                    EGLint contextMinorVersion,
208                                    EGLint contextFlags,
209                                    EGLint contextResetNotificationStrategy,
210                                    EGLenum api)
211     : GHOST_Context(stereoVisual),
212       m_nativeDisplay(nativeDisplay),
213       m_nativeWindow(nativeWindow),
214       m_contextProfileMask(contextProfileMask),
215       m_contextMajorVersion(contextMajorVersion),
216       m_contextMinorVersion(contextMinorVersion),
217       m_contextFlags(contextFlags),
218       m_contextResetNotificationStrategy(contextResetNotificationStrategy),
219       m_api(api),
220       m_context(EGL_NO_CONTEXT),
221       m_surface(EGL_NO_SURFACE),
222       m_display(EGL_NO_DISPLAY),
223       m_swap_interval(1),
224       m_sharedContext(
225           choose_api(api, s_gl_sharedContext, s_gles_sharedContext, s_vg_sharedContext)),
226       m_sharedCount(choose_api(api, s_gl_sharedCount, s_gles_sharedCount, s_vg_sharedCount))
227 {
228 }
229 
~GHOST_ContextEGL()230 GHOST_ContextEGL::~GHOST_ContextEGL()
231 {
232   if (m_display != EGL_NO_DISPLAY) {
233 
234     bindAPI(m_api);
235 
236     if (m_context != EGL_NO_CONTEXT) {
237       if (m_context == ::eglGetCurrentContext())
238         EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
239 
240       if (m_context != m_sharedContext || m_sharedCount == 1) {
241         assert(m_sharedCount > 0);
242 
243         m_sharedCount--;
244 
245         if (m_sharedCount == 0)
246           m_sharedContext = EGL_NO_CONTEXT;
247 
248         EGL_CHK(::eglDestroyContext(m_display, m_context));
249       }
250     }
251 
252     if (m_surface != EGL_NO_SURFACE)
253       EGL_CHK(::eglDestroySurface(m_display, m_surface));
254   }
255 }
256 
swapBuffers()257 GHOST_TSuccess GHOST_ContextEGL::swapBuffers()
258 {
259   return EGL_CHK(::eglSwapBuffers(m_display, m_surface)) ? GHOST_kSuccess : GHOST_kFailure;
260 }
261 
setSwapInterval(int interval)262 GHOST_TSuccess GHOST_ContextEGL::setSwapInterval(int interval)
263 {
264   if (EGLEW_VERSION_1_1) {
265     if (EGL_CHK(::eglSwapInterval(m_display, interval))) {
266       m_swap_interval = interval;
267 
268       return GHOST_kSuccess;
269     }
270     else {
271       return GHOST_kFailure;
272     }
273   }
274   else {
275     return GHOST_kFailure;
276   }
277 }
278 
getSwapInterval(int & intervalOut)279 GHOST_TSuccess GHOST_ContextEGL::getSwapInterval(int &intervalOut)
280 {
281   // This is a bit of a kludge because there does not seem to
282   // be a way to query the swap interval with EGL.
283   intervalOut = m_swap_interval;
284 
285   return GHOST_kSuccess;
286 }
287 
activateDrawingContext()288 GHOST_TSuccess GHOST_ContextEGL::activateDrawingContext()
289 {
290   if (m_display) {
291     bindAPI(m_api);
292 
293     return EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context)) ? GHOST_kSuccess :
294                                                                                    GHOST_kFailure;
295   }
296   else {
297     return GHOST_kFailure;
298   }
299 }
300 
releaseDrawingContext()301 GHOST_TSuccess GHOST_ContextEGL::releaseDrawingContext()
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)) ?
307                GHOST_kSuccess :
308                GHOST_kFailure;
309   }
310   else {
311     return GHOST_kFailure;
312   }
313 }
314 
initContextEGLEW()315 bool GHOST_ContextEGL::initContextEGLEW()
316 {
317   /* We have to manually get this function before we can call eglewInit, since
318    * it requires a display argument. glewInit() does the same, but we only want
319    * to initialize EGLEW here. */
320   eglGetDisplay = (PFNEGLGETDISPLAYPROC)eglGetProcAddress("eglGetDisplay");
321   if (eglGetDisplay == NULL) {
322     return false;
323   }
324 
325   if (!EGL_CHK((m_display = ::eglGetDisplay(m_nativeDisplay)) != EGL_NO_DISPLAY)) {
326     return false;
327   }
328 
329   if (GLEW_CHK(eglewInit(m_display)) != GLEW_OK) {
330     fprintf(stderr, "Warning! EGLEW failed to initialize properly.\n");
331     return false;
332   }
333 
334   return true;
335 }
336 
api_string(EGLenum api)337 static const std::string &api_string(EGLenum api)
338 {
339   static const std::string a("OpenGL");
340   static const std::string b("OpenGL ES");
341   static const std::string c("OpenVG");
342 
343   return choose_api(api, a, b, c);
344 }
345 
initializeDrawingContext()346 GHOST_TSuccess GHOST_ContextEGL::initializeDrawingContext()
347 {
348   // objects have to be declared here due to the use of goto
349   std::vector<EGLint> attrib_list;
350   EGLint num_config = 0;
351 
352   if (m_stereoVisual)
353     fprintf(stderr, "Warning! Stereo OpenGL ES contexts are not supported.\n");
354 
355   m_stereoVisual = false;  // It doesn't matter what the Window wants.
356 
357   if (!initContextEGLEW()) {
358     return GHOST_kFailure;
359   }
360 
361 #ifdef WITH_GL_ANGLE
362   // d3dcompiler_XX.dll needs to be loaded before ANGLE will work
363   if (s_d3dcompiler == NULL) {
364     s_d3dcompiler = LoadLibrary(D3DCOMPILER);
365 
366     WIN32_CHK(s_d3dcompiler != NULL);
367 
368     if (s_d3dcompiler == NULL) {
369       fprintf(stderr, "LoadLibrary(\"" D3DCOMPILER "\") failed!\n");
370       return GHOST_kFailure;
371     }
372   }
373 #endif
374 
375   EGLDisplay prev_display = eglGetCurrentDisplay();
376   EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
377   EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
378   EGLContext prev_context = eglGetCurrentContext();
379 
380   EGLint egl_major, egl_minor;
381 
382   if (!EGL_CHK(::eglInitialize(m_display, &egl_major, &egl_minor)))
383     goto error;
384 
385   fprintf(stderr, "EGL Version %d.%d\n", egl_major, egl_minor);
386 
387   if (!EGL_CHK(::eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)))
388     goto error;
389 
390   if (!bindAPI(m_api))
391     goto error;
392 
393   // build attribute list
394 
395   attrib_list.reserve(20);
396 
397   if (m_api == EGL_OPENGL_ES_API && EGLEW_VERSION_1_2) {
398     // According to the spec it seems that you are required to set EGL_RENDERABLE_TYPE,
399     // but some implementations (ANGLE) do not seem to care.
400 
401     if (m_contextMajorVersion == 1) {
402       attrib_list.push_back(EGL_RENDERABLE_TYPE);
403       attrib_list.push_back(EGL_OPENGL_ES_BIT);
404     }
405     else if (m_contextMajorVersion == 2) {
406       attrib_list.push_back(EGL_RENDERABLE_TYPE);
407       attrib_list.push_back(EGL_OPENGL_ES2_BIT);
408     }
409     else if (m_contextMajorVersion == 3) {
410       attrib_list.push_back(EGL_RENDERABLE_TYPE);
411       attrib_list.push_back(EGL_OPENGL_ES3_BIT_KHR);
412     }
413     else {
414       fprintf(stderr,
415               "Warning! Unable to request an ES context of version %d.%d\n",
416               m_contextMajorVersion,
417               m_contextMinorVersion);
418     }
419 
420     if (!((m_contextMajorVersion == 1) || (m_contextMajorVersion == 2 && EGLEW_VERSION_1_3) ||
421           (m_contextMajorVersion == 3 && /*EGLEW_VERSION_1_4 &&*/ EGLEW_KHR_create_context) ||
422           (m_contextMajorVersion == 3 && EGLEW_VERSION_1_5))) {
423       fprintf(stderr,
424               "Warning! May not be able to create a version %d.%d ES context with version %d.%d "
425               "of EGL\n",
426               m_contextMajorVersion,
427               m_contextMinorVersion,
428               egl_major,
429               egl_minor);
430     }
431   }
432   else {
433     attrib_list.push_back(EGL_RENDERABLE_TYPE);
434     attrib_list.push_back(EGL_OPENGL_BIT);
435   }
436 
437   attrib_list.push_back(EGL_RED_SIZE);
438   attrib_list.push_back(8);
439 
440   attrib_list.push_back(EGL_GREEN_SIZE);
441   attrib_list.push_back(8);
442 
443   attrib_list.push_back(EGL_BLUE_SIZE);
444   attrib_list.push_back(8);
445 
446 #ifdef GHOST_OPENGL_ALPHA
447   attrib_list.push_back(EGL_ALPHA_SIZE);
448   attrib_list.push_back(8);
449 #endif
450 
451   if (m_nativeWindow == 0) {
452     // off-screen surface
453     attrib_list.push_back(EGL_SURFACE_TYPE);
454     attrib_list.push_back(EGL_PBUFFER_BIT);
455   }
456 
457   attrib_list.push_back(EGL_NONE);
458 
459   EGLConfig config;
460 
461   if (!EGL_CHK(::eglChooseConfig(m_display, &(attrib_list[0]), &config, 1, &num_config)))
462     goto error;
463 
464   // A common error is to assume that ChooseConfig worked because it returned EGL_TRUE
465   if (num_config != 1)  // num_config should be exactly 1
466     goto error;
467 
468   if (m_nativeWindow != 0) {
469     m_surface = ::eglCreateWindowSurface(m_display, config, m_nativeWindow, NULL);
470   }
471   else {
472     static const EGLint pb_attrib_list[] = {
473         EGL_WIDTH,
474         1,
475         EGL_HEIGHT,
476         1,
477         EGL_NONE,
478     };
479     m_surface = ::eglCreatePbufferSurface(m_display, config, pb_attrib_list);
480   }
481 
482   if (!EGL_CHK(m_surface != EGL_NO_SURFACE))
483     goto error;
484 
485   attrib_list.clear();
486 
487   if (EGLEW_VERSION_1_5 || EGLEW_KHR_create_context) {
488     if (m_api == EGL_OPENGL_API || m_api == EGL_OPENGL_ES_API) {
489       if (m_contextMajorVersion != 0) {
490         attrib_list.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
491         attrib_list.push_back(m_contextMajorVersion);
492       }
493 
494       if (m_contextMinorVersion != 0) {
495         attrib_list.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
496         attrib_list.push_back(m_contextMinorVersion);
497       }
498 
499       if (m_contextFlags != 0) {
500         attrib_list.push_back(EGL_CONTEXT_FLAGS_KHR);
501         attrib_list.push_back(m_contextFlags);
502       }
503     }
504     else {
505       if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
506         fprintf(stderr,
507                 "Warning! Cannot request specific versions of %s contexts.",
508                 api_string(m_api).c_str());
509       }
510 
511       if (m_contextFlags != 0) {
512         fprintf(stderr, "Warning! Flags cannot be set on %s contexts.", api_string(m_api).c_str());
513       }
514     }
515 
516     if (m_api == EGL_OPENGL_API) {
517       if (m_contextProfileMask != 0) {
518         attrib_list.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
519         attrib_list.push_back(m_contextProfileMask);
520       }
521     }
522     else {
523       if (m_contextProfileMask != 0)
524         fprintf(
525             stderr, "Warning! Cannot select profile for %s contexts.", api_string(m_api).c_str());
526     }
527 
528     if (m_api == EGL_OPENGL_API || EGLEW_VERSION_1_5) {
529       if (m_contextResetNotificationStrategy != 0) {
530         attrib_list.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
531         attrib_list.push_back(m_contextResetNotificationStrategy);
532       }
533     }
534     else {
535       if (m_contextResetNotificationStrategy != 0) {
536         fprintf(stderr,
537                 "Warning! EGL %d.%d cannot set the reset notification strategy on %s contexts.",
538                 egl_major,
539                 egl_minor,
540                 api_string(m_api).c_str());
541       }
542     }
543   }
544   else {
545     if (m_api == EGL_OPENGL_ES_API) {
546       if (m_contextMajorVersion != 0) {
547         attrib_list.push_back(EGL_CONTEXT_CLIENT_VERSION);
548         attrib_list.push_back(m_contextMajorVersion);
549       }
550     }
551     else {
552       if (m_contextMajorVersion != 0 || m_contextMinorVersion != 0) {
553         fprintf(stderr,
554                 "Warning! EGL %d.%d is unable to select between versions of %s.",
555                 egl_major,
556                 egl_minor,
557                 api_string(m_api).c_str());
558       }
559     }
560 
561     if (m_contextFlags != 0) {
562       fprintf(stderr, "Warning! EGL %d.%d is unable to set context flags.", egl_major, egl_minor);
563     }
564     if (m_contextProfileMask != 0) {
565       fprintf(stderr,
566               "Warning! EGL %d.%d is unable to select between profiles.",
567               egl_major,
568               egl_minor);
569     }
570     if (m_contextResetNotificationStrategy != 0) {
571       fprintf(stderr,
572               "Warning! EGL %d.%d is unable to set the reset notification strategies.",
573               egl_major,
574               egl_minor);
575     }
576   }
577 
578   attrib_list.push_back(EGL_NONE);
579 
580   m_context = ::eglCreateContext(m_display, config, m_sharedContext, &(attrib_list[0]));
581 
582   if (!EGL_CHK(m_context != EGL_NO_CONTEXT))
583     goto error;
584 
585   if (m_sharedContext == EGL_NO_CONTEXT)
586     m_sharedContext = m_context;
587 
588   m_sharedCount++;
589 
590   if (!EGL_CHK(::eglMakeCurrent(m_display, m_surface, m_surface, m_context)))
591     goto error;
592 
593   initContextGLEW();
594 
595   initClearGL();
596   ::eglSwapBuffers(m_display, m_surface);
597 
598   return GHOST_kSuccess;
599 
600 error:
601   if (prev_display != EGL_NO_DISPLAY)
602     EGL_CHK(eglMakeCurrent(prev_display, prev_draw, prev_read, prev_context));
603 
604   return GHOST_kFailure;
605 }
606 
releaseNativeHandles()607 GHOST_TSuccess GHOST_ContextEGL::releaseNativeHandles()
608 {
609   m_nativeWindow = 0;
610   m_nativeDisplay = NULL;
611 
612   return GHOST_kSuccess;
613 }
614