From 64d0702277ac9988c3b92a495466c75f5e931135 Mon Sep 17 00:00:00 2001 From: aspioupiou Date: Thu, 30 Apr 2020 13:04:22 +0200 Subject: [PATCH] Platform: only initialize/terminate EGLDisplay once in a shared set. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vladimír Vondruš --- .../Platform/WindowlessEglApplication.cpp | 215 +++++++++++------- .../Platform/WindowlessEglApplication.h | 46 +++- 2 files changed, 166 insertions(+), 95 deletions(-) diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 1d156633f..f96a13bd1 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -83,98 +83,111 @@ bool extensionSupported(const char* const extensions, Containers::ArrayView 3 hours already). See class - docs for more info and a workaround. */ - if(extensionSupported(extensions, "EGL_KHR_debug") && magnumContext && (magnumContext->internalFlags() & GL::Context::InternalFlag::GpuValidation)) { - auto eglDebugMessageControl = reinterpret_cast(eglGetProcAddress("eglDebugMessageControlKHR")); - const EGLAttrib debugAttribs[] = { - EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, - EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, - EGL_NONE - }; - CORRADE_INTERNAL_ASSERT_OUTPUT(eglDebugMessageControl([](EGLenum, const char* const command, EGLint, EGLLabelKHR, EGLLabelKHR, const char* message) { - Debug{} << command << Debug::nospace << "():" << Utility::String::rtrim(message); - }, debugAttribs) == EGL_SUCCESS); - } - - EGLint count; - auto eglQueryDevices = reinterpret_cast(eglGetProcAddress("eglQueryDevicesEXT")); - if(!eglQueryDevices(0, nullptr, &count)) { - Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot query EGL devices:" << Implementation::eglErrorString(eglGetError()); - return; - } - - if(!count) { - Error e; - e << "Platform::WindowlessEglApplication::tryCreateContext(): no EGL devices found, likely a driver issue"; - if(!magnumContext || !(magnumContext->internalFlags() & GL::Context::InternalFlag::GpuValidation)) - e << Debug::nospace << "; enable --magnum-gpu-validation to see additional info"; - return; - } - - if(configuration.device() >= UnsignedInt(count)) { - Error{} << "Platform::WindowlessEglContext: requested EGL device" << configuration.device() << "but found only" << count; - return; - } - - if(magnumContext && (magnumContext->internalFlags() >= GL::Context::InternalFlag::DisplayVerboseInitializationLog)) { - Debug{} << "Platform::WindowlessEglApplication: found" << count << "EGL devices, choosing device" << configuration.device(); - } - - /* Assuming the same thing won't suddenly start failing when called the - second time */ - Containers::Array devices{configuration.device() + 1}; - CORRADE_INTERNAL_ASSERT_OUTPUT(eglQueryDevices(configuration.device() + 1, devices, &count)); - - if(!(_display = reinterpret_cast(eglGetProcAddress("eglGetPlatformDisplayEXT"))(EGL_PLATFORM_DEVICE_EXT, devices[configuration.device()], nullptr))) { - Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get platform display for a device:" << Implementation::eglErrorString(eglGetError()); - return; - } + /* The user provided a shared context, use the associated display + directly. We don't call eglInitialize() in this case either -- the + context we share with already did that on the provided display */ + if(configuration.sharedContext() != EGL_NO_CONTEXT && configuration.sharedDisplay() != EGL_NO_DISPLAY) { + _display = configuration.sharedDisplay(); + _sharedContext = true; } else #endif - /* Otherwise initialize the classic way. WebGL doesn't have any of the - above, so no need to compile that at all. */ + /* Otherwise find the display and initialize EGL */ { #ifndef MAGNUM_TARGET_WEBGL - if(configuration.device() != 0) { - Error{} << "Platform::WindowlessEglContext: requested EGL device" << configuration.device() << "but EGL_EXT_platform_device is not supported and there's just the default one"; - return; + /* If relevant extensions are supported, try to find some display using + those APIs, as that works reliably also when running headless. This + would ideally use EGL 1.5 APIs but since we still want to support + systems which either have old EGL headers or old EGL implementation, + we'd need to have a code path for 1.4 *and* 1.5, plus do complicated + version parsing from a string. Not feeling like doing that today, + no. */ + const char* const extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if(extensions && + /* eglQueryDevicesEXT(). NVidia exposes only EGL_EXT_device_base, + which is an older version of EGL_EXT_device_enumeration before + it got split to that and EGL_EXT_device_query, so test for both. */ + (extensionSupported(extensions, "EGL_EXT_device_enumeration") || extensionSupported(extensions, "EGL_EXT_device_base")) && + + /* eglGetPlatformDisplayEXT() */ + extensionSupported(extensions, "EGL_EXT_platform_base") && + + /* EGL_PLATFORM_DEVICE_EXT (FFS, why it has to be scattered over a + thousand extensions?!). This is supported only since Mesa 19.2. */ + extensionSupported(extensions, "EGL_EXT_platform_device") + ) { + /* When libEGL_nvidia.so is present on a system w/o a NV GPU, + eglQueryDevicesEXT() fails there with EGL_BAD_ALLOC, but that is + never propagated to the glvnd wrapper. Enable debug output if + --magnum-gpu-validation is enabled because otherwise it's + fucking hard to discover what's to blame (lost > 3 hours + already). See class docs for more info and a workaround. */ + if(extensionSupported(extensions, "EGL_KHR_debug") && magnumContext && (magnumContext->internalFlags() & GL::Context::InternalFlag::GpuValidation)) { + auto eglDebugMessageControl = reinterpret_cast(eglGetProcAddress("eglDebugMessageControlKHR")); + const EGLAttrib debugAttribs[] = { + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE + }; + CORRADE_INTERNAL_ASSERT_OUTPUT(eglDebugMessageControl([](EGLenum, const char* const command, EGLint, EGLLabelKHR, EGLLabelKHR, const char* message) { + Debug{} << command << Debug::nospace << "():" << Utility::String::rtrim(message); + }, debugAttribs) == EGL_SUCCESS); + } + + EGLint count; + auto eglQueryDevices = reinterpret_cast(eglGetProcAddress("eglQueryDevicesEXT")); + if(!eglQueryDevices(0, nullptr, &count)) { + Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot query EGL devices:" << Implementation::eglErrorString(eglGetError()); + return; + } + + if(!count) { + Error e; + e << "Platform::WindowlessEglApplication::tryCreateContext(): no EGL devices found, likely a driver issue"; + if(!magnumContext || !(magnumContext->internalFlags() & GL::Context::InternalFlag::GpuValidation)) + e << Debug::nospace << "; enable --magnum-gpu-validation to see additional info"; + return; + } + + if(configuration.device() >= UnsignedInt(count)) { + Error{} << "Platform::WindowlessEglContext: requested EGL device" << configuration.device() << "but found only" << count; + return; + } + + if(magnumContext && (magnumContext->internalFlags() >= GL::Context::InternalFlag::DisplayVerboseInitializationLog)) { + Debug{} << "Platform::WindowlessEglApplication: found" << count << "EGL devices, choosing device" << configuration.device(); + } + + /* Assuming the same thing won't suddenly start failing when called + the second time */ + Containers::Array devices{configuration.device() + 1}; + CORRADE_INTERNAL_ASSERT_OUTPUT(eglQueryDevices(configuration.device() + 1, devices, &count)); + + if(!(_display = reinterpret_cast(eglGetProcAddress("eglGetPlatformDisplayEXT"))(EGL_PLATFORM_DEVICE_EXT, devices[configuration.device()], nullptr))) { + Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get platform display for a device:" << Implementation::eglErrorString(eglGetError()); + return; + } } + /* Otherwise initialize the classic way. WebGL doesn't have any of the + above, so no need to compile that at all. */ #endif + { + #ifndef MAGNUM_TARGET_WEBGL + if(configuration.device() != 0) { + Error{} << "Platform::WindowlessEglContext: requested EGL device" << configuration.device() << "but EGL_EXT_platform_device is not supported and there's just the default one"; + return; + } + #endif - if(!(_display = eglGetDisplay(EGL_DEFAULT_DISPLAY))) { - Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError()); - return; + if(!(_display = eglGetDisplay(EGL_DEFAULT_DISPLAY))) { + Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError()); + return; + } } - } - if(!eglInitialize(_display, nullptr, nullptr)) { - Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); - return; + if(!eglInitialize(_display, nullptr, nullptr)) { + Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); + return; + } } const EGLenum api = @@ -299,7 +312,15 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G #endif } -WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other): _display{other._display}, _context{other._context} { +WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other): + #ifndef MAGNUM_TARGET_WEBGL + _sharedContext{other._sharedContext}, + #endif + _display{other._display}, _context{other._context} +{ + #ifndef MAGNUM_TARGET_WEBGL + other._sharedContext = false; + #endif other._display = {}; other._context = {}; } @@ -309,11 +330,23 @@ WindowlessEglContext::~WindowlessEglContext() { #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) if(_surface) eglDestroySurface(_display, _surface); #endif - if(_display) eglTerminate(_display); + + /* Don't terminate EGL if we're a shared context as it would kill all + others as well. In case of a shared context it's expected that the + first instance of WindowlessEglContext in the shared chain is destroyed + last, calling eglTerminate() after all others are gone. */ + if( + #ifndef MAGNUM_TARGET_WEBGL + !_sharedContext && + #endif + _display) eglTerminate(_display); } -WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext && other) { +WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& other) { using std::swap; + #ifndef MAGNUM_TARGET_WEBGL + swap(other._sharedContext, _sharedContext); + #endif swap(other._display, _display); swap(other._context, _context); return *this; @@ -390,6 +423,16 @@ bool WindowlessEglApplication::tryCreateContext(const Configuration& configurati return true; } +#ifndef MAGNUM_TARGET_WEBGL +auto WindowlessEglContext::Configuration::setSharedContext(EGLDisplay display, EGLContext context) -> Configuration& { + CORRADE_ASSERT((context == EGL_NO_CONTEXT) == (display == EGL_NO_DISPLAY), + "Platform::WindowlessEglContext::Configuration::setSharedContext(): either both the context and the display have to be valid or both null", *this); + _sharedDisplay = display; + _sharedContext = context; + return *this; +} +#endif + WindowlessEglApplication::~WindowlessEglApplication() = default; }} diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index bc023f85b..ea17d93be 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -132,6 +132,9 @@ class WindowlessEglContext { EGLContext glContext() { return _context; } private: + #ifndef MAGNUM_TARGET_WEBGL + bool _sharedContext = false; + #endif EGLDisplay _display{}; EGLContext _context{}; #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) @@ -259,7 +262,9 @@ class WindowlessEglContext::Configuration { * The device ID is expected to be smaller than the count of devices * reported by EGL. When using @ref WindowlessEglApplication, this is * also exposed as a `--magnum-device` command-line option and a - * `MAGNUM_DEVICE` environment variable. + * `MAGNUM_DEVICE` environment variable. If @ref setSharedContext() is + * set, this value is ignored and the device is picked to be the same + * as in the shared context instead. * @requires_gles Device selection is not available in WebGL. */ Configuration& setDevice(UnsignedInt id) { @@ -273,18 +278,25 @@ class WindowlessEglContext::Configuration { * @m_since_latest * * When set, the created context will share a subset of OpenGL objects - * with @p context, instead of being independent. Many caveats and - * limitations apply to shared OpenGL contexts, please consult the - * OpenGL specification for details. Default is `EGL_NO_CONTEXT`, i.e. - * no sharing. + * with @p context and its associated @p display, instead of being + * independent. Many caveats and limitations apply to shared OpenGL + * contexts, please consult the OpenGL specification for details. + * Default is `EGL_NO_CONTEXT`, i.e. no sharing. See + * @ref Platform-WindowlessEglApplication-shared-contexts for more + * information. * @see @ref WindowlessEglContext::glContext(), * @ref WindowlessEglApplication::glContext() * @requires_gles Context sharing is not available in WebGL. */ - Configuration& setSharedContext(EGLContext context) { - _sharedContext = context; - return *this; - } + Configuration& setSharedContext(EGLDisplay display, EGLContext context); + + /** + * @brief Shared display + * @m_since_latest + * + * @requires_gles Context sharing is not available in WebGL. + */ + EGLContext sharedDisplay() const { return _sharedDisplay; } /** * @brief Shared context @@ -299,6 +311,7 @@ class WindowlessEglContext::Configuration { #ifndef MAGNUM_TARGET_WEBGL Flags _flags; UnsignedInt _device; + EGLDisplay _sharedDisplay = EGL_NO_DISPLAY; EGLContext _sharedContext = EGL_NO_CONTEXT; #endif }; @@ -465,6 +478,21 @@ using the `__EGL_VENDOR_LIBRARY_FILENAMES` environment variable, for example: __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json ./my-application @endcode @endparblock + +@section Platform-WindowlessEglApplication-shared-contexts Shared EGL contexts + +Unlike with @ref WindowlessGlxApplication and @ref WindowlessWglApplication, +you're expected to supply both the display and the context in +@ref Configuration::setSharedContext(). This is done in order to ensure the +same `EGLDisplay` is used for all shared contexts, especially when a +non-default GPU device is selected via @ref Configuration::setDevice(). + +Moreover, since `eglInitialize()` and `eglTerminate()` is expected to be called +just once on a particular display, EGL initialization and termination is only +done in the case of a non-shared @ref WindowlessEglApplication (or the first +one created in a shared chain). Shared instances then reuse the already +initialized `EGLDisplay` and expect that it's terminated only after all shared +instances are gone. */ class WindowlessEglApplication { public: