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) { WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, GLContext* const magnumContext) {
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
/* If relevant extensions are supported, try to find some display using /* The user provided a shared context, use the associated display
those APIs, as that works reliably also when running headless. This directly. We don't call eglInitialize() in this case either -- the
would ideally use EGL 1.5 APIs but since we still want to support context we share with already did that on the provided display */
systems which either have old EGL headers or old EGL implementation, if(configuration.sharedContext() != EGL_NO_CONTEXT && configuration.sharedDisplay() != EGL_NO_DISPLAY) {
we'd need to have a code path for 1.4 *and* 1.5, plus do complicated _display = configuration.sharedDisplay();
version parsing from a string. Not feeling like doing that today, no. */ _sharedContext = true;
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;
}
} else } else
#endif #endif
/* Otherwise initialize the classic way. WebGL doesn't have any of the /* Otherwise find the display and initialize EGL */
above, so no need to compile that at all. */
{ {
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
if(configuration.device() != 0) { /* If relevant extensions are supported, try to find some display using
Error{} << "Platform::WindowlessEglContext: requested EGL device" << configuration.device() << "but EGL_EXT_platform_device is not supported and there's just the default one"; those APIs, as that works reliably also when running headless. This
return; 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 #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))) { if(!(_display = eglGetDisplay(EGL_DEFAULT_DISPLAY))) {
Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError()); Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError());
return; return;
}
} }
}
if(!eglInitialize(_display, nullptr, nullptr)) { if(!eglInitialize(_display, nullptr, nullptr)) {
Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError());
return; return;
}
} }
const EGLenum api = const EGLenum api =
@ -299,7 +312,15 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G
#endif #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._display = {};
other._context = {}; other._context = {};
} }
@ -309,11 +330,23 @@ WindowlessEglContext::~WindowlessEglContext() {
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)
if(_surface) eglDestroySurface(_display, _surface); if(_surface) eglDestroySurface(_display, _surface);
#endif #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; using std::swap;
#ifndef MAGNUM_TARGET_WEBGL
swap(other._sharedContext, _sharedContext);
#endif
swap(other._display, _display); swap(other._display, _display);
swap(other._context, _context); swap(other._context, _context);
return *this; return *this;
@ -390,6 +423,16 @@ bool WindowlessEglApplication::tryCreateContext(const Configuration& configurati
return true; 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; WindowlessEglApplication::~WindowlessEglApplication() = default;
}} }}

46
src/Magnum/Platform/WindowlessEglApplication.h

@ -132,6 +132,9 @@ class WindowlessEglContext {
EGLContext glContext() { return _context; } EGLContext glContext() { return _context; }
private: private:
#ifndef MAGNUM_TARGET_WEBGL
bool _sharedContext = false;
#endif
EGLDisplay _display{}; EGLDisplay _display{};
EGLContext _context{}; EGLContext _context{};
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) #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 * The device ID is expected to be smaller than the count of devices
* reported by EGL. When using @ref WindowlessEglApplication, this is * reported by EGL. When using @ref WindowlessEglApplication, this is
* also exposed as a `--magnum-device` command-line option and a * 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. * @requires_gles Device selection is not available in WebGL.
*/ */
Configuration& setDevice(UnsignedInt id) { Configuration& setDevice(UnsignedInt id) {
@ -273,18 +278,25 @@ class WindowlessEglContext::Configuration {
* @m_since_latest * @m_since_latest
* *
* When set, the created context will share a subset of OpenGL objects * When set, the created context will share a subset of OpenGL objects
* with @p context, instead of being independent. Many caveats and * with @p context and its associated @p display, instead of being
* limitations apply to shared OpenGL contexts, please consult the * independent. Many caveats and limitations apply to shared OpenGL
* OpenGL specification for details. Default is `EGL_NO_CONTEXT`, i.e. * contexts, please consult the OpenGL specification for details.
* no sharing. * Default is `EGL_NO_CONTEXT`, i.e. no sharing. See
* @ref Platform-WindowlessEglApplication-shared-contexts for more
* information.
* @see @ref WindowlessEglContext::glContext(), * @see @ref WindowlessEglContext::glContext(),
* @ref WindowlessEglApplication::glContext() * @ref WindowlessEglApplication::glContext()
* @requires_gles Context sharing is not available in WebGL. * @requires_gles Context sharing is not available in WebGL.
*/ */
Configuration& setSharedContext(EGLContext context) { Configuration& setSharedContext(EGLDisplay display, EGLContext context);
_sharedContext = context;
return *this; /**
} * @brief Shared display
* @m_since_latest
*
* @requires_gles Context sharing is not available in WebGL.
*/
EGLContext sharedDisplay() const { return _sharedDisplay; }
/** /**
* @brief Shared context * @brief Shared context
@ -299,6 +311,7 @@ class WindowlessEglContext::Configuration {
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
Flags _flags; Flags _flags;
UnsignedInt _device; UnsignedInt _device;
EGLDisplay _sharedDisplay = EGL_NO_DISPLAY;
EGLContext _sharedContext = EGL_NO_CONTEXT; EGLContext _sharedContext = EGL_NO_CONTEXT;
#endif #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 __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json ./my-application
@endcode @endcode
@endparblock @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 { class WindowlessEglApplication {
public: public:

Loading…
Cancel
Save