Skip to content

Commit ad9fd1a

Browse files
authored
optionally load EGL at runtime via dlopen() (#324)
* optionally load EGL at runtime via dlopen() * spelling
1 parent f9e70ec commit ad9fd1a

File tree

3 files changed

+134
-39
lines changed

3 files changed

+134
-39
lines changed

include/polyscope/render/opengl/gl_engine_egl.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ class GLEngineEGL : public GLEngine {
4242
virtual void shutdown() override;
4343
void swapDisplayBuffers() override;
4444
void checkError(bool fatal = false) override;
45-
45+
4646
// EGL backend is always headless
47-
virtual bool isHeadless() override { return true; }
47+
virtual bool isHeadless() override { return true; }
4848

4949
// === Windowing and framework things
5050

@@ -74,11 +74,41 @@ class GLEngineEGL : public GLEngine {
7474
void ImGuiRender() override;
7575

7676
protected:
77+
// Function pointers for dynamic loading of EGL and extensions
78+
// (see explanation in resolveEGL)
79+
void resolveEGL();
80+
typedef EGLint (*eglGetErrorT)(void);
81+
eglGetErrorT eglGetError = nullptr;
82+
typedef EGLDisplay (*eglGetPlatformDisplayT)(EGLenum, void*, const EGLAttrib*);
83+
eglGetPlatformDisplayT eglGetPlatformDisplay = nullptr;
84+
typedef EGLBoolean (*eglInitializeT)(EGLDisplay, EGLint*, EGLint*);
85+
eglInitializeT eglInitialize = nullptr;
86+
typedef EGLBoolean (*eglChooseConfigT)(EGLDisplay, EGLint const*, EGLConfig*, EGLint, EGLint*);
87+
eglChooseConfigT eglChooseConfig = nullptr;
88+
typedef EGLBoolean (*eglBindAPIT)(EGLenum);
89+
eglBindAPIT eglBindAPI = nullptr;
90+
typedef EGLContext (*eglCreateContextT)(EGLDisplay, EGLConfig, EGLContext, EGLint const*);
91+
eglCreateContextT eglCreateContext = nullptr;
92+
typedef EGLBoolean (*eglMakeCurrentT)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
93+
eglMakeCurrentT eglMakeCurrent = nullptr;
94+
typedef EGLBoolean (*eglDestroyContextT)(EGLDisplay, EGLContext);
95+
eglDestroyContextT eglDestroyContext = nullptr;
96+
typedef EGLBoolean (*eglTerminateT)(EGLDisplay);
97+
eglTerminateT eglTerminate = nullptr;
98+
typedef void (*eglProcT)(void); // our helper type
99+
typedef eglProcT (*eglGetProcAddressT)(const char*);
100+
eglGetProcAddressT eglGetProcAddress = nullptr;
101+
typedef const char* (*eglQueryStringT)(EGLDisplay, EGLint);
102+
eglQueryStringT eglQueryString = nullptr;
103+
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr;
104+
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr;
105+
77106
// Internal windowing and engine details
78107
EGLDisplay eglDisplay;
79108
EGLContext eglContext;
80109

81110
// helpers
111+
void checkEGLError(bool fatal = true);
82112
void sortAvailableDevicesByPreference(std::vector<int32_t>& deviceInds, EGLDeviceEXT rawDevices[]);
83113
};
84114

src/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ if("${POLYSCOPE_BACKEND_OPENGL3_EGL}")
129129
message(FATAL_ERROR "Compiling EGL backed on Windows is not supported. Set POLYSCOPE_BACKEND_OPENGL3_EGL=False")
130130
else() # linux
131131
# only linux is actually supported for EGL
132-
list(APPEND BACKEND_LIBS EGL)
132+
133+
# We intentionally *do not* list this as a shared library dependency, it gets loaded optionally at runtime
134+
# via dlopen(). See explanation in gl_engine_egl.cpp.
135+
# list(APPEND BACKEND_LIBS EGL)
133136
endif()
134137

135138
endif()

src/render/opengl/gl_engine_egl.cpp

Lines changed: 98 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <algorithm>
1515
#include <cctype>
16+
#include <dlfcn.h>
1617
#include <set>
1718
#include <string>
1819

@@ -23,19 +24,21 @@ namespace polyscope {
2324
namespace render {
2425
namespace backend_openGL3 {
2526

26-
namespace { // anonymous helpers
27+
void initializeRenderEngine_egl() {
2728

28-
// Helper function to get an EGL (extension?) function and error-check that
29-
// we got it successfully
30-
void* getEGLProcAddressAndCheck(std::string name) {
31-
void* procAddr = (void*)(eglGetProcAddress(name.c_str()));
32-
if (!procAddr) {
33-
error("EGL failed to get function pointer for " + name);
34-
}
35-
return procAddr;
29+
GLEngineEGL* glEngineEGL = new GLEngineEGL(); // create the new global engine object
30+
engine = glEngineEGL;
31+
32+
// initialize
33+
glEngineEGL->initialize();
34+
engine->allocateGlobalBuffersAndPrograms();
35+
glEngineEGL->applyWindowSize();
3636
}
3737

38-
void checkEGLError(bool fatal = true) {
38+
GLEngineEGL::GLEngineEGL() {}
39+
GLEngineEGL::~GLEngineEGL() {}
40+
41+
void GLEngineEGL::checkEGLError(bool fatal) {
3942

4043
if (!options::enableRenderErrorChecks) {
4144
return;
@@ -124,29 +127,15 @@ void checkEGLError(bool fatal = true) {
124127
exception("EGL error occurred. Text: " + errText);
125128
}
126129
}
127-
} // namespace
128-
129-
void initializeRenderEngine_egl() {
130-
131-
GLEngineEGL* glEngineEGL = new GLEngineEGL(); // create the new global engine object
132-
engine = glEngineEGL;
133-
134-
// initialize
135-
glEngineEGL->initialize();
136-
engine->allocateGlobalBuffersAndPrograms();
137-
glEngineEGL->applyWindowSize();
138-
}
139-
140-
GLEngineEGL::GLEngineEGL() {}
141-
GLEngineEGL::~GLEngineEGL() {}
142130

143131
void GLEngineEGL::initialize() {
144132

133+
145134
// === Initialize EGL
146135

147-
// Pre-load required extension functions
148-
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
149-
(PFNEGLQUERYDEVICESEXTPROC)getEGLProcAddressAndCheck("eglQueryDevicesEXT");
136+
// Runtime-load shared library functions for EGL
137+
// (see note inside for details)
138+
resolveEGL();
150139

151140
// Query the available EGL devices
152141
const int N_MAX_DEVICE = 256;
@@ -285,21 +274,94 @@ void GLEngineEGL::initialize() {
285274
checkError();
286275
}
287276

288-
void GLEngineEGL::sortAvailableDevicesByPreference(std::vector<int32_t>& deviceInds, EGLDeviceEXT rawDevices[]) {
277+
void GLEngineEGL::resolveEGL() {
289278

290-
// check that we actually have the query extension
279+
// This function does a bunch of gymnastics to avoid taking on libEGL.so as a dependency. This is a
280+
// machine/driver-specific library, so we would have to dynamically load it on the end user's machine. However,
281+
// simply specifying it as a shared library at build time would make it required for Polyscope, even for users
282+
// who would not use EGL, and we don't want that. Instead, we manually dynamically load the functions below.
283+
//
284+
// Note that this is on top of the dynamic loading that always happens when you load exention functions from libEGL.
285+
// We are furthermore loading `libEGL.so` dynamically in the middle of runtime, rather than at load time as via ldd
286+
// etc. At the end of this function we also load EGL extensions in the usual way.
287+
288+
289+
if (options::verbosity > 5) {
290+
std::cout << polyscope::options::printPrefix << "Attempting to dlopen libEGL.so" << std::endl;
291+
}
292+
void* handle = dlopen("libEGL.so", RTLD_LAZY);
293+
if (!handle) {
294+
error("EGL: Could not open libEGL.so.");
295+
}
296+
if (options::verbosity > 5) {
297+
std::cout << polyscope::options::printPrefix << " ...loaded libEGL.so" << std::endl;
298+
}
299+
300+
// Get EGL functions
301+
if (options::verbosity > 5) {
302+
std::cout << polyscope::options::printPrefix << "Attempting to dlsym resolve EGL functions" << std::endl;
303+
}
304+
305+
eglGetError = (eglGetErrorT)dlsym(handle, "eglGetError");
306+
eglGetPlatformDisplay = (eglGetPlatformDisplayT)dlsym(handle, "eglGetPlatformDisplay");
307+
eglChooseConfig = (eglChooseConfigT)dlsym(handle, "eglChooseConfig");
308+
eglInitialize = (eglInitializeT)dlsym(handle, "eglInitialize");
309+
eglChooseConfig = (eglChooseConfigT)dlsym(handle, "eglChooseConfig");
310+
eglBindAPI = (eglBindAPIT)dlsym(handle, "eglBindAPI");
311+
eglCreateContext = (eglCreateContextT)dlsym(handle, "eglCreateContext");
312+
eglMakeCurrent = (eglMakeCurrentT)dlsym(handle, "eglMakeCurrent");
313+
eglDestroyContext = (eglDestroyContextT)dlsym(handle, "eglDestroyContext");
314+
eglTerminate = (eglTerminateT)dlsym(handle, "eglTerminate");
315+
eglGetProcAddress = (eglGetProcAddressT)dlsym(handle, "eglGetProcAddress");
316+
eglQueryString = (eglQueryStringT)dlsym(handle, "eglQueryString");
317+
318+
if (!eglGetError || !eglGetPlatformDisplay || !eglChooseConfig || !eglInitialize || !eglChooseConfig || !eglBindAPI ||
319+
!eglCreateContext || !eglMakeCurrent || !eglDestroyContext || !eglTerminate || !eglGetProcAddress ||
320+
!eglQueryString) {
321+
dlclose(handle);
322+
const char* errTextPtr = dlerror();
323+
std::string errText = "";
324+
if (errTextPtr) {
325+
errText = errTextPtr;
326+
}
327+
error("EGL: Error loading symbol " + errText);
328+
}
329+
if (options::verbosity > 5) {
330+
std::cout << polyscope::options::printPrefix << " ...resolved EGL functions" << std::endl;
331+
}
332+
// ... at this point, we have essentially loaded libEGL.so
333+
334+
// === Resolve EGL extension functions
335+
336+
// Helper function to get an EGL extension function and error-check that
337+
// we got it successfully
338+
auto getEGLExtensionProcAndCheck = [&](std::string name) {
339+
void* procAddr = (void*)(eglGetProcAddress(name.c_str()));
340+
if (!procAddr) {
341+
error("EGL failed to get function pointer for " + name);
342+
}
343+
return procAddr;
344+
};
345+
346+
// Pre-load required extension functions
347+
eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)getEGLExtensionProcAndCheck("eglQueryDevicesEXT");
291348
const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
292349
if (extensions && std::string(extensions).find("EGL_EXT_device_query") != std::string::npos) {
293-
// good case, supported
294-
} else {
350+
eglQueryDeviceStringEXT = (PFNEGLQUERYDEVICESTRINGEXTPROC)getEGLExtensionProcAndCheck("eglQueryDeviceStringEXT");
351+
}
352+
}
353+
354+
void GLEngineEGL::sortAvailableDevicesByPreference(
355+
356+
std::vector<int32_t>& deviceInds, EGLDeviceEXT rawDevices[]) {
357+
358+
// check that we actually have the query extension
359+
const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
360+
if (!eglQueryDeviceStringEXT) {
295361
info("EGL: cannot sort devices by preference, EGL_EXT_device_query is not supported");
296362
return;
297363
}
298364

299-
// Pre-load required extension functions
300-
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
301-
(PFNEGLQUERYDEVICESTRINGEXTPROC)getEGLProcAddressAndCheck("eglQueryDeviceStringEXT");
302-
303365
// Build a list of devices and assign a score to each
304366
std::vector<std::tuple<int32_t, int32_t>> scoreDevices;
305367
for (int32_t iDevice : deviceInds) {

0 commit comments

Comments
 (0)