Skip to content

Commit

Permalink
optionally load EGL at runtime via dlopen()
Browse files Browse the repository at this point in the history
  • Loading branch information
nmwsharp committed Jan 28, 2025
1 parent f9e70ec commit b7c1caa
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 39 deletions.
34 changes: 32 additions & 2 deletions include/polyscope/render/opengl/gl_engine_egl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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<int32_t>& deviceInds, EGLDeviceEXT rawDevices[]);
};

Expand Down
5 changes: 4 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
134 changes: 98 additions & 36 deletions src/render/opengl/gl_engine_egl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <algorithm>
#include <cctype>
#include <dlfcn.h>
#include <set>
#include <string>

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -285,21 +274,94 @@ void GLEngineEGL::initialize() {
checkError();
}

void GLEngineEGL::sortAvailableDevicesByPreference(std::vector<int32_t>& 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<int32_t>& 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<std::tuple<int32_t, int32_t>> scoreDevices;
for (int32_t iDevice : deviceInds) {
Expand Down

0 comments on commit b7c1caa

Please sign in to comment.