Browse Source

Platform: only initialize/terminate EGLDisplay once in a shared set.

Co-authored-by: Vladimír Vondruš <mosra@centrum.cz>
pull/432/merge
aspioupiou 6 years ago committed by Vladimír Vondruš
parent
commit
64d0702277
  1. 215
      src/Magnum/Platform/WindowlessEglApplication.cpp
  2. 46
      src/Magnum/Platform/WindowlessEglApplication.h

215
src/Magnum/Platform/WindowlessEglApplication.cpp

@ -83,98 +83,111 @@ bool extensionSupported(const char* const extensions, Containers::ArrayView<cons
WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, GLContext* const magnumContext) {
#ifndef MAGNUM_TARGET_WEBGL
/* 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<EGLint(*)(EGLDEBUGPROCKHR, const EGLAttrib*)>(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<EGLBoolean(*)(EGLint, EGLDeviceEXT*, EGLint*)>(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<EGLDeviceEXT> devices{configuration.device() + 1};
CORRADE_INTERNAL_ASSERT_OUTPUT(eglQueryDevices(configuration.device() + 1, devices, &count));
if(!(_display = reinterpret_cast<EGLDisplay(*)(EGLenum, void*, const EGLint*)>(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<EGLint(*)(EGLDEBUGPROCKHR, const EGLAttrib*)>(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<EGLBoolean(*)(EGLint, EGLDeviceEXT*, EGLint*)>(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<EGLDeviceEXT> devices{configuration.device() + 1};
CORRADE_INTERNAL_ASSERT_OUTPUT(eglQueryDevices(configuration.device() + 1, devices, &count));
if(!(_display = reinterpret_cast<EGLDisplay(*)(EGLenum, void*, const EGLint*)>(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;
}}

46
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:

Loading…
Cancel
Save