From b7c1caa762bbe5c70e1a83d4439d6c089e7d4a56 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 27 Jan 2025 17:38:10 -0800 Subject: [PATCH 1/2] optionally load EGL at runtime via dlopen() --- .../polyscope/render/opengl/gl_engine_egl.h | 34 ++++- src/CMakeLists.txt | 5 +- src/render/opengl/gl_engine_egl.cpp | 134 +++++++++++++----- 3 files changed, 134 insertions(+), 39 deletions(-) diff --git a/include/polyscope/render/opengl/gl_engine_egl.h b/include/polyscope/render/opengl/gl_engine_egl.h index 2a53d5c3..637e3479 100644 --- a/include/polyscope/render/opengl/gl_engine_egl.h +++ b/include/polyscope/render/opengl/gl_engine_egl.h @@ -42,9 +42,9 @@ class GLEngineEGL : public GLEngine { virtual void shutdown() override; void swapDisplayBuffers() override; void checkError(bool fatal = false) override; - + // EGL backend is always headless - virtual bool isHeadless() override { return true; } + virtual bool isHeadless() override { return true; } // === Windowing and framework things @@ -74,11 +74,41 @@ class GLEngineEGL : public GLEngine { void ImGuiRender() override; protected: + // Function pointers for dynamic loading of EGL and extensions + // (see exaplanation in resolveEGL) + void resolveEGL(); + typedef EGLint (*eglGetErrorT)(void); + eglGetErrorT eglGetError = nullptr; + typedef EGLDisplay (*eglGetPlatformDisplayT)(EGLenum, void*, const EGLAttrib*); + eglGetPlatformDisplayT eglGetPlatformDisplay = nullptr; + typedef EGLBoolean (*eglInitializeT)(EGLDisplay, EGLint*, EGLint*); + eglInitializeT eglInitialize = nullptr; + typedef EGLBoolean (*eglChooseConfigT)(EGLDisplay, EGLint const*, EGLConfig*, EGLint, EGLint*); + eglChooseConfigT eglChooseConfig = nullptr; + typedef EGLBoolean (*eglBindAPIT)(EGLenum); + eglBindAPIT eglBindAPI = nullptr; + typedef EGLContext (*eglCreateContextT)(EGLDisplay, EGLConfig, EGLContext, EGLint const*); + eglCreateContextT eglCreateContext = nullptr; + typedef EGLBoolean (*eglMakeCurrentT)(EGLDisplay, EGLSurface, EGLSurface, EGLContext); + eglMakeCurrentT eglMakeCurrent = nullptr; + typedef EGLBoolean (*eglDestroyContextT)(EGLDisplay, EGLContext); + eglDestroyContextT eglDestroyContext = nullptr; + typedef EGLBoolean (*eglTerminateT)(EGLDisplay); + eglTerminateT eglTerminate = nullptr; + typedef void (*eglProcT)(void); // our helper type + typedef eglProcT (*eglGetProcAddressT)(const char*); + eglGetProcAddressT eglGetProcAddress = nullptr; + typedef const char* (*eglQueryStringT)(EGLDisplay, EGLint); + eglQueryStringT eglQueryString = nullptr; + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; + // Internal windowing and engine details EGLDisplay eglDisplay; EGLContext eglContext; // helpers + void checkEGLError(bool fatal = true); void sortAvailableDevicesByPreference(std::vector& deviceInds, EGLDeviceEXT rawDevices[]); }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f074ec4c..5e1cd2c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,7 +129,10 @@ if("${POLYSCOPE_BACKEND_OPENGL3_EGL}") message(FATAL_ERROR "Compiling EGL backed on Windows is not supported. Set POLYSCOPE_BACKEND_OPENGL3_EGL=False") else() # linux # only linux is actually supported for EGL - list(APPEND BACKEND_LIBS EGL) + + # We intentionally *do not* list this as a shared library dependency, it gets loaded optionally at runtime + # via dlopen(). See explanation in gl_engine_egl.cpp. + # list(APPEND BACKEND_LIBS EGL) endif() endif() diff --git a/src/render/opengl/gl_engine_egl.cpp b/src/render/opengl/gl_engine_egl.cpp index 0f95bbcb..0eaaa875 100644 --- a/src/render/opengl/gl_engine_egl.cpp +++ b/src/render/opengl/gl_engine_egl.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -23,19 +24,21 @@ namespace polyscope { namespace render { namespace backend_openGL3 { -namespace { // anonymous helpers +void initializeRenderEngine_egl() { -// Helper function to get an EGL (extension?) function and error-check that -// we got it successfully -void* getEGLProcAddressAndCheck(std::string name) { - void* procAddr = (void*)(eglGetProcAddress(name.c_str())); - if (!procAddr) { - error("EGL failed to get function pointer for " + name); - } - return procAddr; + GLEngineEGL* glEngineEGL = new GLEngineEGL(); // create the new global engine object + engine = glEngineEGL; + + // initialize + glEngineEGL->initialize(); + engine->allocateGlobalBuffersAndPrograms(); + glEngineEGL->applyWindowSize(); } -void checkEGLError(bool fatal = true) { +GLEngineEGL::GLEngineEGL() {} +GLEngineEGL::~GLEngineEGL() {} + +void GLEngineEGL::checkEGLError(bool fatal) { if (!options::enableRenderErrorChecks) { return; @@ -124,29 +127,15 @@ void checkEGLError(bool fatal = true) { exception("EGL error occurred. Text: " + errText); } } -} // namespace - -void initializeRenderEngine_egl() { - - GLEngineEGL* glEngineEGL = new GLEngineEGL(); // create the new global engine object - engine = glEngineEGL; - - // initialize - glEngineEGL->initialize(); - engine->allocateGlobalBuffersAndPrograms(); - glEngineEGL->applyWindowSize(); -} - -GLEngineEGL::GLEngineEGL() {} -GLEngineEGL::~GLEngineEGL() {} void GLEngineEGL::initialize() { + // === Initialize EGL - // Pre-load required extension functions - PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = - (PFNEGLQUERYDEVICESEXTPROC)getEGLProcAddressAndCheck("eglQueryDevicesEXT"); + // Runtime-load shared library functions for EGL + // (see note inside for details) + resolveEGL(); // Query the available EGL devices const int N_MAX_DEVICE = 256; @@ -285,21 +274,94 @@ void GLEngineEGL::initialize() { checkError(); } -void GLEngineEGL::sortAvailableDevicesByPreference(std::vector& deviceInds, EGLDeviceEXT rawDevices[]) { +void GLEngineEGL::resolveEGL() { - // check that we actually have the query extension + // This function does a bunch of gymnastics to avoid taking on libEGL.so as a dependency. This is a + // machine/driver-specific library, so we would have to dynamically load it on the end user's machine. However, + // simply specifying it as a shared library at build time would make it required for Polyscope, even for users + // who would not use EGL, and we don't want that. Instead, we manually dynamically load the functions below. + // + // Note that this is on top of the dynamic loading that always happens when you load exention functions from libEGL. + // We are furthermore loading `libEGL.so` dynamically in the middle of runtime, rather than at load time as via ldd + // etc. At the end of this function we also load EGL extensions in the usual way. + + + if (options::verbosity > 5) { + std::cout << polyscope::options::printPrefix << "Attempting to dlopen libEGL.so" << std::endl; + } + void* handle = dlopen("libEGL.so", RTLD_LAZY); + if (!handle) { + error("EGL: Could not open libEGL.so."); + } + if (options::verbosity > 5) { + std::cout << polyscope::options::printPrefix << " ...loaded libEGL.so" << std::endl; + } + + // Get EGL functions + if (options::verbosity > 5) { + std::cout << polyscope::options::printPrefix << "Attempting to dlsym resolve EGL functions" << std::endl; + } + + eglGetError = (eglGetErrorT)dlsym(handle, "eglGetError"); + eglGetPlatformDisplay = (eglGetPlatformDisplayT)dlsym(handle, "eglGetPlatformDisplay"); + eglChooseConfig = (eglChooseConfigT)dlsym(handle, "eglChooseConfig"); + eglInitialize = (eglInitializeT)dlsym(handle, "eglInitialize"); + eglChooseConfig = (eglChooseConfigT)dlsym(handle, "eglChooseConfig"); + eglBindAPI = (eglBindAPIT)dlsym(handle, "eglBindAPI"); + eglCreateContext = (eglCreateContextT)dlsym(handle, "eglCreateContext"); + eglMakeCurrent = (eglMakeCurrentT)dlsym(handle, "eglMakeCurrent"); + eglDestroyContext = (eglDestroyContextT)dlsym(handle, "eglDestroyContext"); + eglTerminate = (eglTerminateT)dlsym(handle, "eglTerminate"); + eglGetProcAddress = (eglGetProcAddressT)dlsym(handle, "eglGetProcAddress"); + eglQueryString = (eglQueryStringT)dlsym(handle, "eglQueryString"); + + if (!eglGetError || !eglGetPlatformDisplay || !eglChooseConfig || !eglInitialize || !eglChooseConfig || !eglBindAPI || + !eglCreateContext || !eglMakeCurrent || !eglDestroyContext || !eglTerminate || !eglGetProcAddress || + !eglQueryString) { + dlclose(handle); + const char* errTextPtr = dlerror(); + std::string errText = ""; + if (errTextPtr) { + errText = errTextPtr; + } + error("EGL: Error loading symbol " + errText); + } + if (options::verbosity > 5) { + std::cout << polyscope::options::printPrefix << " ...resolved EGL functions" << std::endl; + } + // ... at this point, we have essentially loaded libEGL.so + + // === Resolve EGL extension functions + + // Helper function to get an EGL extension function and error-check that + // we got it successfully + auto getEGLExtensionProcAndCheck = [&](std::string name) { + void* procAddr = (void*)(eglGetProcAddress(name.c_str())); + if (!procAddr) { + error("EGL failed to get function pointer for " + name); + } + return procAddr; + }; + + // Pre-load required extension functions + eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)getEGLExtensionProcAndCheck("eglQueryDevicesEXT"); const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (extensions && std::string(extensions).find("EGL_EXT_device_query") != std::string::npos) { - // good case, supported - } else { + eglQueryDeviceStringEXT = (PFNEGLQUERYDEVICESTRINGEXTPROC)getEGLExtensionProcAndCheck("eglQueryDeviceStringEXT"); + } +} + +void GLEngineEGL::sortAvailableDevicesByPreference( + + std::vector& deviceInds, EGLDeviceEXT rawDevices[]) { + + // check that we actually have the query extension + const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!eglQueryDeviceStringEXT) { info("EGL: cannot sort devices by preference, EGL_EXT_device_query is not supported"); return; } - // Pre-load required extension functions - PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = - (PFNEGLQUERYDEVICESTRINGEXTPROC)getEGLProcAddressAndCheck("eglQueryDeviceStringEXT"); - // Build a list of devices and assign a score to each std::vector> scoreDevices; for (int32_t iDevice : deviceInds) { From 3ae3708dd751715cb61fd35df5caef801008fca8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 27 Jan 2025 17:39:42 -0800 Subject: [PATCH 2/2] spelling --- include/polyscope/render/opengl/gl_engine_egl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/polyscope/render/opengl/gl_engine_egl.h b/include/polyscope/render/opengl/gl_engine_egl.h index 637e3479..7948436a 100644 --- a/include/polyscope/render/opengl/gl_engine_egl.h +++ b/include/polyscope/render/opengl/gl_engine_egl.h @@ -75,7 +75,7 @@ class GLEngineEGL : public GLEngine { protected: // Function pointers for dynamic loading of EGL and extensions - // (see exaplanation in resolveEGL) + // (see explanation in resolveEGL) void resolveEGL(); typedef EGLint (*eglGetErrorT)(void); eglGetErrorT eglGetError = nullptr;