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