Cogs.Core
EGLContext.cpp
1#ifdef COGS_EGL
2
3#include "GLFuncPointers.h"
4#include "GLContext.h"
5
6#include "../IGraphicsDevice.h"
7#include "Foundation/Logging/Logger.h"
8#include "Foundation/StringView.h"
9#include "Foundation/Platform/WindowData.h"
10
11#ifndef __EMSCRIPTEN__
12#include <dlfcn.h>
13
14PFNEGLGETPROCADDRESSPROC _eglGetProcAddress = nullptr;
15#endif
16
17namespace {
18
19 Cogs::Logging::Log logger = Cogs::Logging::getLogger("EGLContext");
20
21static bool initialized = false;
22
23#ifdef __EMSCRIPTEN__
24
25#define _eglBindAPI eglBindAPI
26#define _eglChooseConfig eglChooseConfig
27#define _eglCreateContext eglCreateContext
28#define _eglCreateWindowSurface eglCreateWindowSurface
29#define _eglDestroyContext eglDestroyContext
30#define _eglDestroySurface eglDestroySurface
31#define _eglGetConfigAttrib eglGetConfigAttrib
32#define _eglGetDisplay eglGetDisplay
33#define _eglGetError eglGetError
34#define _eglGetPlatformDisplay eglGetPlatformDisplay
35#define _eglGetProcAddress eglGetProcAddress
36#define _eglInitialize eglInitialize
37#define _eglMakeCurrent eglMakeCurrent
38#define _eglQueryString eglQueryString
39#define _eglSwapBuffers eglSwapBuffers
40#define _eglTerminate eglTerminate
41
42 bool initialize() { return true; }
43
44#else
45
46 PFNEGLBINDAPIPROC _eglBindAPI = nullptr;
47 PFNEGLCHOOSECONFIGPROC _eglChooseConfig = nullptr;
48 PFNEGLCREATECONTEXTPROC _eglCreateContext = nullptr;
49 PFNEGLCREATEWINDOWSURFACEPROC _eglCreateWindowSurface = nullptr;
50 PFNEGLDESTROYCONTEXTPROC _eglDestroyContext = nullptr;
51 PFNEGLDESTROYSURFACEPROC _eglDestroySurface = nullptr;
52 PFNEGLGETCONFIGATTRIBPROC _eglGetConfigAttrib = nullptr;
53 PFNEGLGETDISPLAYPROC _eglGetDisplay = nullptr;
54 PFNEGLGETPLATFORMDISPLAYPROC _eglGetPlatformDisplay = nullptr;
55 PFNEGLGETERRORPROC _eglGetError = nullptr;
56 PFNEGLINITIALIZEPROC _eglInitialize = nullptr;
57 PFNEGLMAKECURRENTPROC _eglMakeCurrent = nullptr;
58 PFNEGLQUERYSTRINGPROC _eglQueryString = nullptr;
59 PFNEGLSWAPBUFFERSPROC _eglSwapBuffers = nullptr;
60 PFNEGLTERMINATEPROC _eglTerminate = nullptr;
61
62 PFNEGLGETDISPLAYDRIVERCONFIGPROC _eglGetDisplayDriverConfig = nullptr;
63 PFNEGLGETDISPLAYDRIVERNAMEPROC _eglGetDisplayDriverName = nullptr;
64
65 void* libEGL = nullptr;
66 static const char* libEGLNames[] = {
67 "libEGL.so"
68 };
69 struct {
70 void** ptr;
71 const char* name;
72 } libEGLFuncs[] = {
73 { (void**)&_eglBindAPI, "eglBindAPI" },
74 { (void**)&_eglChooseConfig, "eglChooseConfig" },
75 { (void**)&_eglCreateContext, "eglCreateContext" },
76 { (void**)&_eglCreateWindowSurface, "eglCreateWindowSurface" },
77 { (void**)&_eglDestroyContext, "eglDestroyContext" },
78 { (void**)&_eglDestroySurface, "eglDestroySurface" },
79 { (void**)&_eglGetConfigAttrib, "eglGetConfigAttrib" },
80 { (void**)&_eglGetDisplay, "eglGetDisplay" },
81 { (void**)&_eglGetError, "eglGetError" },
82 { (void**)&_eglGetPlatformDisplay, "eglGetPlatformDisplay" },
83 { (void**)&_eglInitialize, "eglInitialize" },
84 { (void**)&_eglMakeCurrent, "eglMakeCurrent" },
85 { (void**)&_eglQueryString, "eglQueryString" },
86 { (void**)&_eglSwapBuffers, "eglSwapBuffers" },
87 { (void**)&_eglTerminate, "eglTerminate" },
88 };
89
90 bool initialize()
91 {
92 // Open libEGL
93 for (const char* libEGLName : libEGLNames) {
94 libEGL = dlopen(libEGLName, RTLD_LAZY | RTLD_LOCAL);
95 if (libEGL) { goto egl_loaded; }
96 }
97 LOG_FATAL(logger, "Failed to load libEGL");
98 return false;
99
100 egl_loaded:
101
102 _eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC)dlsym(libEGL, "eglGetProcAddress");
103 if(_eglGetProcAddress == nullptr) {
104 LOG_FATAL(logger, "Failed to lookup libEGL function eglGetProcAddress");
105 //return false;
106 }
107
108 for (const auto& libFunc : libEGLFuncs) {
109 *libFunc.ptr = (void*)_eglGetProcAddress(libFunc.name);
110 if(*libFunc.ptr == nullptr) {
111 LOG_FATAL(logger, "Failed to look up libEGL function %s", libFunc.name);
112 return false;
113 }
114 }
115 return true;
116 }
117
118#endif
119
120
121 const char* eglErrorString(EGLint error)
122 {
123 switch (error) {
124 case EGL_SUCCESS: return "EGL_SUCCESS";
125 case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
126 case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
127 case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
128 case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
129 case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
130 case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
131 case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
132 case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
133 case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
134 case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
135 case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER";
136 case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP";
137 case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
138 case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
139 default:
140 return "<unknown>";
141 }
142 }
143
144
145
146 EGLConfig chooseConfig(EGLDisplay eglDisplay, EGLint surfaceType, EGLint requestedSampleCount)
147 {
148 EGLint attribList[] =
149 {
150 EGL_DEPTH_SIZE, 24,
151 EGL_SURFACE_TYPE, surfaceType,
152 //EGL_SAMPLE_BUFFERS, 1,
153 //EGL_SAMPLES, 4,
154 EGL_NONE
155 };
156 EGLint numConfigs = 0;
157 if (!_eglChooseConfig(eglDisplay, attribList, nullptr, 0, &numConfigs)) {
158 LOG_ERROR(logger, "Failed to choose EGL config: %s", eglErrorString(_eglGetError()));
159 return nullptr;
160 }
161 if(numConfigs == 0) {
162 LOG_FATAL(logger, "eglChooseConfig return 0 matching configs");
163 return nullptr;
164 }
165 std::vector<EGLConfig> configs(numConfigs);
166 if (!_eglChooseConfig(eglDisplay, attribList, configs.data(), numConfigs, &numConfigs)) {
167 LOG_FATAL(logger, "Failed to choose EGL config: %s", eglErrorString(_eglGetError()));
168 return nullptr;
169 }
170
171 EGLint bestFbc = -1;
172 EGLint bestSampleCount = -1;
173 LOG_DEBUG(logger, "eglChooseConfig returned %d configs:", numConfigs);
174 for(EGLint i=0; i<numConfigs; i++) {
175 struct {
176 GLint a;
177 GLint v = 0;
178 } a[] = {
179 { EGL_NATIVE_VISUAL_ID },
180 { EGL_RED_SIZE },
181 { EGL_GREEN_SIZE },
182 { EGL_BLUE_SIZE },
183 { EGL_ALPHA_SIZE },
184 { EGL_DEPTH_SIZE },
185 { EGL_SURFACE_TYPE },
186 { EGL_SAMPLE_BUFFERS },
187 { EGL_SAMPLES },
188 };
189 for(auto& attrib : a) {
190 if(!_eglGetConfigAttrib(eglDisplay, configs[i], attrib.a, &attrib.v)) {
191 LOG_ERROR(logger, "eglGetConfigAttrib failed for attribute 0x%x: %s", attrib.a, eglErrorString(_eglGetError()));
192 }
193 }
194
195 EGLint sampleBuffers = a[7].v;
196 EGLint sampleCount = a[8].v;
197 if ((bestFbc < 0) || (sampleBuffers && (bestSampleCount < sampleCount) && (sampleCount <= requestedSampleCount))) {
198 bestFbc = i;
199 bestSampleCount = sampleCount;
200 }
201 LOG_DEBUG(logger, "%i: conf=0x%zx vid=0x%x, rgba={%d %d %d %d} d=%d surfype=0x%x sampleBufs=%d samples=%d",
202 i, (size_t)configs[i], a[0].v,
203 a[1].v, a[2].v, a[2].v, a[4].v, a[5].v,
204 a[6].v, sampleBuffers, sampleCount);
205 }
206 LOG_DEBUG(logger, "Requested %d samples, chose config %d", requestedSampleCount, bestFbc);
207
208 // Just choose the first for now
209 return configs[0];
210 }
211
212 static bool has_EGL_KHR_no_config_context = false; // https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_no_config_context.txt
213 static bool has_EGL_MESA_query_driver = false; // https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_query_driver.txt
214
215 void queryDisplayExtensions(EGLDisplay eglDisplay)
216 {
217 if(const char* eglExtensions = _eglQueryString(eglDisplay, EGL_EXTENSIONS); eglExtensions) {
218 for(const char* a=eglExtensions; a && *a!='\0'; ) {
219 const char* b = a;
220 while(*b != ' ' && *b != '\0') b++;
221 Cogs::StringView extension(a, b-a);
222 LOG_DEBUG(logger, "EGL extension %.*s", StringViewFormat(extension));
223 switch(Cogs::hash(extension)) {
224 case Cogs::hash("EGL_KHR_no_config_context"):
225 has_EGL_KHR_no_config_context = true;
226 break;
227#ifndef __EMSCRIPTEN__
228 case Cogs::hash("EGL_MESA_query_driver"):
229 _eglGetDisplayDriverName = (PFNEGLGETDISPLAYDRIVERNAMEPROC)_eglGetProcAddress("eglGetDisplayDriverName");
230 _eglGetDisplayDriverConfig = (PFNEGLGETDISPLAYDRIVERCONFIGPROC)_eglGetProcAddress("eglGetDisplayDriverConfig");
231 has_EGL_MESA_query_driver = true;
232 break;
233#endif
234 }
235 while(*b == ' ' && *b != '\0') b++;
236 a = b;
237 }
238 }
239 else {
240 LOG_DEBUG(logger, "No EGL extensions");
241 }
242 if(has_EGL_MESA_query_driver) {
243#ifndef __EMSCRIPTEN__
244 LOG_DEBUG(logger, "EGL display driver: %s", _eglGetDisplayDriverName(eglDisplay));
245 //LOG_DEBUG(logger, "EGL display config: %s", _eglGetDisplayDriverConfig(eglDisplay));
246#endif
247 }
248
249 }
250
251}
252
253
254bool Cogs::GLContext::create(NativeDisplay display, WindowData* windowData, GLContext* shareContext, const GraphicsDeviceSettings* settings, Platform desiredPlatform, bool debug)
255{
256 assert(eglDisplay == nullptr);
257 assert(eglContext == nullptr);
258 assert(eglSurface == nullptr);
259 if (!initialized) {
260 if (!initialize()) return false;
261 }
262
263 // ------- Set up display connection ---------------------------------------
264 if(shareContext && shareContext->eglDisplay != EGL_NO_DISPLAY) {
265 eglDisplay = shareContext->eglDisplay;
266 }
267
268 else {
269
270 if(display) {
271#ifdef __linux__
272 // Requires EGL 1.5
273 eglDisplay = _eglGetPlatformDisplay(EGL_PLATFORM_X11_EXT, display, NULL);
274#else
275 eglDisplay = _eglGetDisplay(display);
276#endif
277 }
278 else {
279 #if 0
280 PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = reinterpret_cast<PFNEGLQUERYDEVICESEXTPROC>(_eglGetProcAddress("eglQueryDevicesEXT"));
281 PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(_eglGetProcAddress("eglGetPlatformDisplayEXT"));
282
283 if(eglQueryDevicesEXT && eglGetPlatformDisplayEXT){
284 constexpr int maxDevices = 4;
285 EGLDeviceEXT eglDevs[maxDevices];
286 EGLint numDevices;
287
288 _eglQueryDevicesEXT(maxDevices, eglDevs, &numDevices);
289 eglDisplay = _eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, eglDevs[0], 0);
290 }
291 else{
292 eglDisplay = _eglGetDisplay(EGL_DEFAULT_DISPLAY);
293 }
294 #endif
295 eglDisplay = _eglGetDisplay(EGL_DEFAULT_DISPLAY);
296 }
297
298 if (eglDisplay == EGL_NO_DISPLAY) {
299 LOG_FATAL(logger, "Failed to aquire EGL display: %s", eglErrorString(_eglGetError()));
300 return false;
301 }
302 ownDisplay = true;
303
304 // ------- Initialize EGL and bind to either GL or GLES --------------------
305 EGLint major = 0;
306 EGLint minor = 0;
307 if (!_eglInitialize(eglDisplay, &major, &minor)) {
308 LOG_FATAL(logger, "Failed to initialize EGL: %s", eglErrorString(_eglGetError()));
309 return false;
310 }
311 LOG_DEBUG(logger, "EGL %d.%d initialized", major, minor);
312 LOG_DEBUG(logger, "EGL vendor: %s", _eglQueryString(eglDisplay, EGL_VENDOR));
313 LOG_DEBUG(logger, "EGL version: %s", _eglQueryString(eglDisplay, EGL_VERSION));
314 LOG_DEBUG(logger, "EGL client APIs: %s", _eglQueryString(eglDisplay, EGL_CLIENT_APIS));
315 // Version specified implies ES
316 if(desiredPlatform == Platform::ES3) {
317 if((major == 1) && (minor < 2)) {
318 LOG_FATAL(logger, "Insufficient EGL version, minimum supported is 1.2");
319 return false;
320 }
321 if (!_eglBindAPI(EGL_OPENGL_ES_API)) {
322 LOG_FATAL(logger, "Failed to bind OPENGL ES API");
323 return false;
324 }
325 }
326 else {
327 if((major == 1) && (minor < 4)) {
328 LOG_FATAL(logger, "Insufficient EGL version for OpenGL usage, minimum supported is 1.4");
329 return false;
330 }
331 if (!_eglBindAPI(EGL_OPENGL_API)) {
332 LOG_FATAL(logger, "Failed to bind OPENGL API");
333 return false;
334 }
335 }
336 }
337
338
339 if(!initialized) {
340 queryDisplayExtensions(eglDisplay);
341 }
342
343 EGLConfig config = nullptr;
344
345 // ------- Create surface if needed ----------------------------------------
346 if(windowData && windowData->windowHandle) {
347
348 if(!config) {
349 config = chooseConfig(eglDisplay, EGL_WINDOW_BIT, settings ? settings->numSamples : 0);
350 if(!config) return false;
351 }
352
353 eglSurface = _eglCreateWindowSurface(eglDisplay, config, windowData->windowHandle, nullptr);
354 if (eglSurface == EGL_NO_SURFACE) {
355 LOG_FATAL(logger, "Failed to create EGL window surface: %s", eglErrorString(_eglGetError()));
356 return false;
357 }
358 }
359
360 else {
361 // No window handle provided — create no surface. EGL supports surfaceless contexts
362 // (EGL_KHR_surfaceless_context) which allows rendering to FBOs without a window surface,
363 // enabling true headless operation. The pbuffer alternative below also works on drivers
364 // that do not support surfaceless contexts. To enable headless: also remove the
365 // windowHandle guard in GraphicsDeviceGLES30::initialize().
366#if 0
367 // Not necessary for dummy contexts
368 static constexpr EGLint attributes[] = {
369 EGL_WIDTH, 32,
370 EGL_HEIGHT, 32,
371 //EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGB,
372 //EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
373 EGL_NONE
374 };
375 eglSurface = _eglCreatePbufferSurface(eglDisplay, config, attributes);
376 if (eglSurface == EGL_NO_SURFACE) {
377 LOG_FATAL(logger, "Failed to create EGL pbuffer surface: %s", eglErrorString(_eglGetError()));
378 return false;
379 }
380#endif
381 }
382
383 // ------- Create context --------------------------------------------------
384 if(has_EGL_KHR_no_config_context && shareContext && shareContext->eglContext) {
385 eglContext = shareContext->eglContext;
386 }
387 else {
388 std::vector<EGLint> contextAttributes;
389 switch (desiredPlatform) {
390 case Platform::ES3:
391 contextAttributes.push_back(EGL_CONTEXT_MAJOR_VERSION);
392 contextAttributes.push_back(3);
393 break;
394
395 case Platform::GL:
396 contextAttributes.push_back(EGL_CONTEXT_MAJOR_VERSION);
397 contextAttributes.push_back(4);
398 contextAttributes.push_back(EGL_CONTEXT_MINOR_VERSION);
399 contextAttributes.push_back(3);
400 //contextAttributes.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK);
401 //contextAttributes.push_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT);
402 break;
403
404 default:
405 assert(false);
406 return false;
407 }
408
409 #ifndef __EMSCRIPTEN__
410 if (debug) {
411 contextAttributes.push_back(EGL_CONTEXT_FLAGS_KHR);
412 contextAttributes.push_back(EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR);
413 }
414 #endif // of not __EMSCRIPTEN__
415 contextAttributes.push_back(EGL_NONE);
416 contextAttributes.push_back(EGL_NONE);
417
418 if(!config && !has_EGL_KHR_no_config_context) {
419 config = chooseConfig(eglDisplay, 0, settings ? settings->numSamples : 0); // EGL_PBUFFER_BIT ?
420 if(!config) return false;
421 }
422
423 eglContext = _eglCreateContext(eglDisplay,
424 has_EGL_KHR_no_config_context ? nullptr : config,
425 shareContext ? shareContext->eglContext : EGL_CAST(::EGLContext, 0),
426 contextAttributes.data());
427 if (!eglContext) {
428 LOG_ERROR(logger, "Failed to create EGL context: %s", eglErrorString(_eglGetError()));
429 return false;
430 }
431 LOG_DEBUG(logger, "Created EGL context");
432 ownContext = true;
433 }
434
435 makeCurrent();
436#ifndef __EMSCRIPTEN__
437 if (!initialized) {
438 getGLFuncPointers();
439 }
440 if (debug) {
441 Cogs::setUpGLDebugging(nullptr, platform);
442 }
443#else
444 (void)debug;
445#endif // of else not __EMSCRIPTEN__
446
447 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
448
449 initialized = true;
450 return true;
451}
452
453bool Cogs::GLContext::makeCurrent()
454{
455 if (!_eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
456 LOG_ERROR_ONCE(logger, "Failed to make EGL context current: %s", eglErrorString(_eglGetError()));
457 return false;
458 }
459
460 return true;
461}
462
463void Cogs::GLContext::swapBuffers(uint32_t /*syncInterval*/, PresentFlags /*presentFlags*/)
464{
465#ifndef __EMSCRIPTEN__
466 //_eglSwapInterval(this->eglDisplay, 1);
467 if(!_eglSwapBuffers(eglDisplay, eglSurface)) {
468 LOG_ERROR_ONCE(logger, "Failed to swap EGL buffers: %s", eglErrorString(_eglGetError()));
469 }
470#else
471 glFlush();
472#endif
473}
474
475void Cogs::GLContext::destroy()
476{
477 if(eglDisplay) {
478 if(eglSurface) {
479 _eglDestroySurface(eglDisplay, eglSurface);
480 eglSurface = nullptr;
481 }
482 if(eglContext) {
483 if(ownContext) {
484 _eglDestroyContext(eglDisplay, eglContext);
485 }
486 eglContext = nullptr;
487 }
488 if(ownDisplay) {
489 _eglTerminate(eglDisplay);
490 }
491 eglDisplay = nullptr;
492 }
493}
494
495#endif // of COGS_EGL
Log implementation class.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62