// Copyright (C) 2013 Patryk Nadrowski // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in Irrlicht.h #include "CEGLManager.h" #ifdef _IRR_COMPILE_WITH_EGL_MANAGER_ #include "irrString.h" #include "irrArray.h" #include "os.h" #include #if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) #include #endif namespace irr { namespace video { CEGLManager::CEGLManager() : IContextManager(), EglWindow(0), EglDisplay(EGL_NO_DISPLAY), EglSurface(EGL_NO_SURFACE), EglContext(EGL_NO_CONTEXT), EglConfig(0), MajorVersion(0), MinorVersion(0), libHandle(NULL) { #ifdef _DEBUG setDebugName("CEGLManager"); #endif } CEGLManager::~CEGLManager() { destroyContext(); destroySurface(); terminate(); } bool CEGLManager::initialize(const SIrrlichtCreationParameters& params, const SExposedVideoData& data) { // store new data Params=params; Data=data; if (EglWindow != 0 && EglDisplay != EGL_NO_DISPLAY) return true; // Window is depend on platform. #if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) EglWindow = (NativeWindowType)Data.OpenGLWin32.HWnd; Data.OpenGLWin32.HDc = GetDC((HWND)EglWindow); EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLWin32.HDc); #elif defined(_IRR_EMSCRIPTEN_PLATFORM_) EglWindow = 0; EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); #elif defined(_IRR_COMPILE_WITH_X11_DEVICE_) EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window; EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLLinux.X11Display); #elif defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) EglWindow = (ANativeWindow*)Data.OGLESAndroid.Window; EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); #elif defined(_IRR_COMPILE_WITH_FB_DEVICE_) EglWindow = (NativeWindowType)Data.OpenGLFB.Window; EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); #endif // We must check if EGL display is valid. if (EglDisplay == EGL_NO_DISPLAY) { os::Printer::log("Could not get EGL display."); terminate(); return false; } // Initialize EGL here. if (!eglInitialize(EglDisplay, &MajorVersion, &MinorVersion)) { os::Printer::log("Could not initialize EGL display."); EglDisplay = EGL_NO_DISPLAY; terminate(); return false; } else os::Printer::log("EGL version", core::stringc(MajorVersion+(MinorVersion*0.1f)).c_str()); return true; } void CEGLManager::terminate() { if (EglWindow == 0 && EglDisplay == EGL_NO_DISPLAY) return; if (EglDisplay != EGL_NO_DISPLAY) { // We should unbind current EGL context before terminate EGL. eglMakeCurrent(EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(EglDisplay); EglDisplay = EGL_NO_DISPLAY; } #if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) if (Data.OpenGLWin32.HDc) { ReleaseDC((HWND)EglWindow, (HDC)Data.OpenGLWin32.HDc); Data.OpenGLWin32.HDc = 0; } #endif MajorVersion = 0; MinorVersion = 0; if (libHandle) dlclose(libHandle); } bool CEGLManager::generateSurface() { if (EglDisplay == EGL_NO_DISPLAY) return false; if (EglSurface != EGL_NO_SURFACE) return true; // We should assign new WindowID on platforms, where WindowID may change at runtime, // at this time only Android support this feature. // this needs an update method instead! #if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) EglWindow = (ANativeWindow*)Data.OGLESAndroid.Window; #endif #if defined(_IRR_EMSCRIPTEN_PLATFORM_) // eglChooseConfig is currently only implemented as stub in emscripten (version 1.37.22 at point of writing) // But the other solution would also be fine as it also only generates a single context so there is not much to choose from. EglConfig = chooseConfig(ECS_IRR_CHOOSE); #else EglConfig = chooseConfig(ECS_EGL_CHOOSE_FIRST_LOWER_EXPECTATIONS); #endif if ( EglConfig == 0 ) { os::Printer::log("Could not get config for EGL display."); return false; } #if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) EGLint Format = 0; eglGetConfigAttrib(EglDisplay, EglConfig, EGL_NATIVE_VISUAL_ID, &Format); ANativeWindow_setBuffersGeometry(EglWindow, 0, 0, Format); #endif // Now we are able to create EGL surface. EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0); if (EGL_NO_SURFACE == EglSurface) EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, 0, 0); if (EGL_NO_SURFACE == EglSurface) os::Printer::log("Could not create EGL surface."); #ifdef EGL_VERSION_1_2 if (MinorVersion > 1) eglBindAPI(EGL_OPENGL_ES_API); #endif if (Params.Vsync) eglSwapInterval(EglDisplay, 1); return true; } EGLConfig CEGLManager::chooseConfig(EConfigStyle confStyle) { EGLConfig configResult = 0; // Find proper OpenGL BIT. EGLint eglOpenGLBIT = 0; switch (Params.DriverType) { case EDT_OGLES1: eglOpenGLBIT = EGL_OPENGL_ES_BIT; break; case EDT_OGLES2: case EDT_WEBGL1: eglOpenGLBIT = EGL_OPENGL_ES2_BIT; break; default: break; } if ( confStyle == ECS_EGL_CHOOSE_FIRST_LOWER_EXPECTATIONS ) { EGLint Attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, Params.WithAlphaChannel ? 1:0, EGL_BUFFER_SIZE, Params.Bits, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_DEPTH_SIZE, Params.ZBufferBits, EGL_STENCIL_SIZE, Params.Stencilbuffer, EGL_SAMPLE_BUFFERS, Params.AntiAlias ? 1:0, EGL_SAMPLES, Params.AntiAlias, #ifdef EGL_VERSION_1_3 EGL_RENDERABLE_TYPE, eglOpenGLBIT, #endif EGL_NONE, 0 }; EGLint numConfigs = 0; u32 steps = 5; // Choose the best EGL config. // TODO: We should also have a confStyle ECS_EGL_CHOOSE_CLOSEST // which doesn't take first result of eglChooseConfigs, // but the closest to requested parameters. eglChooseConfigs // can return more than 1 result and first one might have // "better" values than requested (more bits per pixel etc). // So this returns the config which can do most, not the // config which is closest to the requested parameters. // while (!eglChooseConfig(EglDisplay, Attribs, &configResult, 1, &numConfigs) || !numConfigs) { switch (steps) { case 5: // samples if (Attribs[19] > 2) // Params.AntiAlias --Attribs[19]; else { Attribs[17] = 0; // Params.Stencilbuffer Attribs[19] = 0; // Params.AntiAlias --steps; } break; case 4: // alpha if (Attribs[7]) // Params.WithAlphaChannel { Attribs[7] = 0; if (Params.AntiAlias) { Attribs[17] = 1; Attribs[19] = Params.AntiAlias; steps = 5; } } else --steps; break; case 3: // stencil if (Attribs[15]) // Params.Stencilbuffer { Attribs[15] = 0; if (Params.AntiAlias) { Attribs[17] = 1; Attribs[19] = Params.AntiAlias; steps = 5; } } else --steps; break; case 2: // depth size if (Attribs[13] > 16) // Params.ZBufferBits { Attribs[13] -= 8; } else --steps; break; case 1: // buffer size if (Attribs[9] > 16) // Params.Bits { Attribs[9] -= 8; } else --steps; break; default: return 0; } } if (Params.AntiAlias && !Attribs[17]) os::Printer::log("No multisampling."); if (Params.WithAlphaChannel && !Attribs[7]) os::Printer::log("No alpha."); if (Params.Stencilbuffer && !Attribs[15]) os::Printer::log("No stencil buffer."); if (Params.ZBufferBits > Attribs[13]) os::Printer::log("No full depth buffer."); if (Params.Bits > Attribs[9]) os::Printer::log("No full color buffer."); } else if ( confStyle == ECS_IRR_CHOOSE ) { // find number of available configs EGLint numConfigs; if ( eglGetConfigs( EglDisplay, NULL, 0, &numConfigs) == EGL_FALSE ) { testEGLError(); return 0; } if ( numConfigs <= 0 ) return 0; // Get all available configs. EGLConfig * configs = new EGLConfig[numConfigs]; if ( eglGetConfigs( EglDisplay, configs, numConfigs, &numConfigs) == EGL_FALSE ) { testEGLError(); return 0; } // Find the best one. core::array ratings((u32)numConfigs); for ( u32 i=0; i < (u32)numConfigs; ++i ) { SConfigRating r; r.config = configs[i]; r.rating = rateConfig(r.config, eglOpenGLBIT); if ( r.rating >= 0 ) ratings.push_back(r); } if ( ratings.size() > 0 ) { ratings.sort(); configResult = ratings[0].config; if ( ratings[0].rating != 0 ) { // This is just to print some log info (it also rates again while doing that, but rating is cheap enough, so that doesn't matter here). rateConfig(ratings[0].config, eglOpenGLBIT, true); } } delete[] configs; } return configResult; } irr::s32 CEGLManager::rateConfig(EGLConfig config, EGLint eglOpenGLBIT, bool log) { // some values must be there or we ignore the config #ifdef EGL_VERSION_1_3 EGLint attribRenderableType = 0; eglGetConfigAttrib( EglDisplay, config, EGL_RENDERABLE_TYPE, &attribRenderableType); if ( attribRenderableType != eglOpenGLBIT ) { if ( log ) os::Printer::log("EGL_RENDERABLE_TYPE != eglOpenGLBIT"); return -1; } #endif EGLint attribSurfaceType = 0; eglGetConfigAttrib( EglDisplay, config, EGL_SURFACE_TYPE, &attribSurfaceType); if ( attribSurfaceType != EGL_WINDOW_BIT ) { if ( log ) os::Printer::log("EGL_SURFACE_TYPE!= EGL_WINDOW_BIT"); return -1; } // Generally we give a really bad rating if attributes are worse than requested // We give a slight worse rating if attributes are not exact as requested // And we use some priorities which might make sense (but not really fine-tuned, // so if you think other priorities would be better don't worry about changing the values. int rating = 0; EGLint attribBufferSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_BUFFER_SIZE, &attribBufferSize); if ( attribBufferSize < Params.Bits ) { if ( log ) os::Printer::log("No full color buffer."); rating += 100; } if ( attribBufferSize > Params.Bits ) { if ( log ) os::Printer::log("Larger color buffer.", ELL_DEBUG); ++rating; } EGLint attribRedSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_RED_SIZE, &attribRedSize); if ( attribRedSize < 5 && Params.Bits >= 4 ) rating += 100; else if ( attribRedSize < 8 && Params.Bits >= 24) rating += 10; else if ( attribRedSize >= 8 && Params.Bits < 24 ) rating ++; EGLint attribGreenSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_GREEN_SIZE, &attribGreenSize); if ( attribGreenSize < 5 && Params.Bits >= 4 ) rating += 100; else if ( attribGreenSize < 8 && Params.Bits >= 24) rating += 10; else if ( attribGreenSize >= 8 && Params.Bits < 24 ) rating ++; EGLint attribBlueSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_BLUE_SIZE, &attribBlueSize); if ( attribBlueSize < 5 && Params.Bits >= 4 ) rating += 100; else if ( attribBlueSize < 8 && Params.Bits >= 24) rating += 10; else if ( attribBlueSize >= 8 && Params.Bits < 24 ) rating ++; EGLint attribAlphaSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_ALPHA_SIZE, &attribAlphaSize); if ( Params.WithAlphaChannel && attribAlphaSize == 0 ) { if ( log ) os::Printer::log("No alpha."); rating += 10; } else if ( !Params.WithAlphaChannel && attribAlphaSize > 0 ) { if ( log ) os::Printer::log("Got alpha (unrequested).", ELL_DEBUG); rating ++; } EGLint attribStencilSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_STENCIL_SIZE, &attribStencilSize); if ( Params.Stencilbuffer && attribStencilSize == 0 ) { if ( log ) os::Printer::log("No stencil buffer."); rating += 10; } else if ( !Params.Stencilbuffer && attribStencilSize > 0 ) { if ( log ) os::Printer::log("Got a stencil buffer (unrequested).", ELL_DEBUG); rating ++; } EGLint attribDepthSize = 0; eglGetConfigAttrib( EglDisplay, config, EGL_DEPTH_SIZE, &attribDepthSize); if ( attribDepthSize < Params.ZBufferBits ) { if ( log ) { if (attribDepthSize > 0) os::Printer::log("No full depth buffer."); else os::Printer::log("No depth buffer."); } rating += 50; } else if ( attribDepthSize != Params.ZBufferBits ) { if ( log ) { if ( Params.ZBufferBits == 0 ) os::Printer::log("Got a depth buffer (unrequested).", ELL_DEBUG); else os::Printer::log("Got a larger depth buffer.", ELL_DEBUG); } rating ++; } EGLint attribSampleBuffers=0, attribSamples = 0; eglGetConfigAttrib( EglDisplay, config, EGL_SAMPLE_BUFFERS, &attribSampleBuffers); eglGetConfigAttrib( EglDisplay, config, EGL_SAMPLES, &attribSamples); if ( Params.AntiAlias && attribSampleBuffers == 0 ) { if ( log ) os::Printer::log("No multisampling."); rating += 20; } else if ( Params.AntiAlias && attribSampleBuffers && attribSamples < Params.AntiAlias ) { if ( log ) os::Printer::log("Multisampling with less samples than requested.", ELL_DEBUG); rating += 10; } else if ( Params.AntiAlias && attribSampleBuffers && attribSamples > Params.AntiAlias ) { if ( log ) os::Printer::log("Multisampling with more samples than requested.", ELL_DEBUG); rating += 5; } else if ( !Params.AntiAlias && attribSampleBuffers > 0 ) { if ( log ) os::Printer::log("Got multisampling (unrequested).", ELL_DEBUG); rating += 3; } return rating; } void CEGLManager::destroySurface() { if (EglSurface == EGL_NO_SURFACE) return; // We should unbind current EGL context before destroy EGL surface. eglMakeCurrent(EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(EglDisplay, EglSurface); EglSurface = EGL_NO_SURFACE; } bool CEGLManager::generateContext() { if (EglDisplay == EGL_NO_DISPLAY || EglSurface == EGL_NO_SURFACE) return false; if (EglContext != EGL_NO_CONTEXT) return true; EGLint OpenGLESVersion = 0; switch (Params.DriverType) { case EDT_OGLES1: OpenGLESVersion = 1; break; case EDT_OGLES2: case EDT_WEBGL1: OpenGLESVersion = 2; break; default: break; } EGLint ContextAttrib[] = { #ifdef EGL_VERSION_1_3 EGL_CONTEXT_CLIENT_VERSION, OpenGLESVersion, #endif EGL_NONE, 0 }; EglContext = eglCreateContext(EglDisplay, EglConfig, EGL_NO_CONTEXT, ContextAttrib); if (testEGLError()) { os::Printer::log("Could not create EGL context.", ELL_ERROR); return false; } os::Printer::log("EGL context created with OpenGLESVersion: ", core::stringc((int)OpenGLESVersion), ELL_DEBUG); return true; } void CEGLManager::destroyContext() { if (EglContext == EGL_NO_CONTEXT) return; // We must unbind current EGL context before destroy it. eglMakeCurrent(EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(EglDisplay, EglContext); EglContext = EGL_NO_CONTEXT; } bool CEGLManager::activateContext(const SExposedVideoData& videoData, bool restorePrimaryOnZero) { eglMakeCurrent(EglDisplay, EglSurface, EglSurface, EglContext); if (testEGLError()) { os::Printer::log("Could not make EGL context current."); return false; } return true; } const SExposedVideoData& CEGLManager::getContext() const { return Data; } void* CEGLManager::getProcAddress(const std::string &procName) { void* proc = NULL; proc = (void*)eglGetProcAddress(procName.c_str()); if (!proc) { // fallback if (!libHandle) libHandle = dlopen("libGLESv2.so", RTLD_LAZY); if (libHandle) proc = dlsym(libHandle, procName.c_str()); } return proc; } bool CEGLManager::swapBuffers() { return (eglSwapBuffers(EglDisplay, EglSurface)==EGL_TRUE); } bool CEGLManager::testEGLError() { #if defined(EGL_VERSION_1_0) && defined(_DEBUG) EGLint status = eglGetError(); switch (status) { case EGL_SUCCESS: return false; case EGL_NOT_INITIALIZED : os::Printer::log("Not Initialized", ELL_ERROR); break; case EGL_BAD_ACCESS: os::Printer::log("Bad Access", ELL_ERROR); break; case EGL_BAD_ALLOC: os::Printer::log("Bad Alloc", ELL_ERROR); break; case EGL_BAD_ATTRIBUTE: os::Printer::log("Bad Attribute", ELL_ERROR); break; case EGL_BAD_CONTEXT: os::Printer::log("Bad Context", ELL_ERROR); break; case EGL_BAD_CONFIG: os::Printer::log("Bad Config", ELL_ERROR); break; case EGL_BAD_CURRENT_SURFACE: os::Printer::log("Bad Current Surface", ELL_ERROR); break; case EGL_BAD_DISPLAY: os::Printer::log("Bad Display", ELL_ERROR); break; case EGL_BAD_SURFACE: os::Printer::log("Bad Surface", ELL_ERROR); break; case EGL_BAD_MATCH: os::Printer::log("Bad Match", ELL_ERROR); break; case EGL_BAD_PARAMETER: os::Printer::log("Bad Parameter", ELL_ERROR); break; case EGL_BAD_NATIVE_PIXMAP: os::Printer::log("Bad Native Pixmap", ELL_ERROR); break; case EGL_BAD_NATIVE_WINDOW: os::Printer::log("Bad Native Window", ELL_ERROR); break; case EGL_CONTEXT_LOST: os::Printer::log("Context Lost", ELL_ERROR); break; default: break; }; return true; #else return false; #endif } } } #endif