Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for loading and configuration of headless EGL rendering #307

Merged
merged 34 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4dc7a81
use extensions to fetch platform devices for EGL
nmwsharp Nov 24, 2024
05881c5
fix typo
nmwsharp Nov 24, 2024
94f8c6b
small fixes, formatter
nmwsharp Nov 29, 2024
30c8ce3
device preference sorting
nmwsharp Nov 29, 2024
d016854
few more fixes
nmwsharp Nov 29, 2024
8ba375e
use vector of device
nmwsharp Nov 29, 2024
c2b08ab
use EGLDeviceEXT
nmwsharp Nov 29, 2024
b6c23a2
use list of ints
nmwsharp Nov 29, 2024
7f2294f
two more small fixes
nmwsharp Nov 29, 2024
058a474
string safety and logging
nmwsharp Nov 29, 2024
ed0af02
nest logging correctly
nmwsharp Nov 29, 2024
eddea5e
check for extension
nmwsharp Nov 29, 2024
e4f8d43
missing semicolon
nmwsharp Nov 29, 2024
50954fd
more logging
nmwsharp Nov 29, 2024
044999e
cast
nmwsharp Nov 29, 2024
09854e4
test
nmwsharp Nov 29, 2024
348d4f6
test
nmwsharp Nov 29, 2024
cb04777
try fake initialize
nmwsharp Nov 29, 2024
119e4bc
compile fixes
nmwsharp Nov 29, 2024
9eb6d7c
revert debugging
nmwsharp Nov 29, 2024
e10b602
clean up logging
nmwsharp Nov 29, 2024
a60e2c5
cleanup
nmwsharp Nov 29, 2024
d47099c
back to front
nmwsharp Nov 29, 2024
0fb4b27
cleanup
nmwsharp Nov 29, 2024
a5b4e99
add user-facing options for headless setup
nmwsharp Dec 27, 2024
2ffb0fe
fix framecount check
nmwsharp Dec 27, 2024
3ce6a6e
try testing EGL on ci
nmwsharp Dec 28, 2024
e3752af
fix ci script
nmwsharp Dec 28, 2024
9598241
fix backend string
nmwsharp Dec 28, 2024
612c367
improve logging string formatting
nmwsharp Dec 28, 2024
736eacb
clean up context stack on shutdown
nmwsharp Dec 28, 2024
2143893
don't check asan leaks in egl test
nmwsharp Dec 28, 2024
c5eafee
use asan settings for both configurations
nmwsharp Dec 28, 2024
59b7119
comment clarity
nmwsharp Dec 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ jobs:
- name: build
run: cd test/build && make

- name: run test
- name: run test mock backend
run: cd test/build && ./bin/polyscope-test --gtest_catch_exceptions=0 backend=openGL_mock

- name: run test egl backend
# We get memory leaks inside of EGL that I can't track down. With ASAN, this means the exit code is always nonzero,
# which is indistinguishable from tests failing. The ASAN_OPTIONS=detect_leaks=0 skips checking leaks for this test
# as a workaround.
run: cd test/build && ASAN_OPTIONS=detect_leaks=0 ./bin/polyscope-test --gtest_catch_exceptions=0 backend=openGL3_egl

build_shared:
strategy:
Expand All @@ -45,10 +51,16 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y xorg-dev libglu1-mesa-dev xpra xserver-xorg-video-dummy freeglut3-dev

- name: configure
run: cd test && mkdir build && cd build && cmake -DBUILD_SHARED_LIBS=TRUE -DCMAKE_BUILD_TYPE=Debug -DPOLYSCOPE_BACKEND_OPENGL3_GLFW=ON -DPOLYSCOPE_BACKEND_OPENGL_MOCK=ON ..
run: cd test && mkdir build && cd build && cmake -DBUILD_SHARED_LIBS=TRUE -DCMAKE_BUILD_TYPE=Debug -DPOLYSCOPE_BACKEND_OPENGL3_GLFW=ON -DPOLYSCOPE_BACKEND_OPENGL_MOCK=ON -DPOLYSCOPE_BACKEND_OPENGL3_EGL=ON ..

- name: build
run: cd test/build && make

- name: run test
- name: run test mock backend
run: cd test/build && ./bin/polyscope-test --gtest_catch_exceptions=0 backend=openGL_mock

- name: run test egl backend
# We get memory leaks inside of EGL that I can't track down. With ASAN, this means the exit code is always nonzero,
# which is indistinguishable from tests failing. The ASAN_OPTIONS=detect_leaks=0 skips checking leaks for this test
# as a workaround.
run: cd test/build && ASAN_OPTIONS=detect_leaks=0 ./bin/polyscope-test --gtest_catch_exceptions=0 backend=openGL3_egl
12 changes: 9 additions & 3 deletions examples/demo-app/demo_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ int main(int argc, char** argv) {
// polyscope::options::maxFPS = -1;
polyscope::options::verbosity = 100;
polyscope::options::enableRenderErrorChecks = true;
polyscope::options::allowHeadlessBackends = true;

// Initialize polyscope
polyscope::init();
Expand All @@ -871,9 +872,14 @@ int main(int argc, char** argv) {
// Add a few gui elements
polyscope::state::userCallback = callback;

// Show the gui
polyscope::show();

if (polyscope::isHeadless()) {
// save a screenshot to prove we initialized
std::cout << "Headless mode detected, saving screenshot" << std::endl;
polyscope::screenshot("headless_screenshot.png");
} else {
// Show the gui
polyscope::show();
}
// main loop using manual frameTick() instead
// while (true) {
// polyscope::frameTick();
Expand Down
13 changes: 12 additions & 1 deletion include/polyscope/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@


namespace polyscope {
namespace options { // A general name to use when referring to the program in window headings.
namespace options {

// A general name to use when referring to the program in window headings.
extern std::string programName;

// How much should polyscope print to std::out?
Expand All @@ -28,6 +30,10 @@ extern std::string printPrefix;
// Should errors throw exceptions, or just display? (default false)
extern bool errorsThrowExceptions;

// Allow initialization to create headless backends when selecting a backend automatically
// (they can still created explicitly by name) (default: false)
extern bool allowHeadlessBackends;

// Don't let the main loop run at more than this speed. (-1 disables) (default: 60)
extern int maxFPS;

Expand Down Expand Up @@ -119,6 +125,11 @@ extern std::function<void()> configureImGuiStyleCallback;
// assign your own function to create custom styles. If this callback is null, default fonts will be used.
extern std::function<std::tuple<ImFontAtlas*, ImFont*, ImFont*>()> prepareImGuiFontsCallback;

// === Backend and low-level options

// When using the EGL backend, which device to try to initialize with
// (default is -1 which means try all of them)
extern int eglDeviceIndex;

// === Debug options

Expand Down
6 changes: 6 additions & 0 deletions include/polyscope/polyscope.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ void shutdown(bool allowMidFrameShutdown=false);
// deciding when to exit your control loop when using frameTick()
bool windowRequestsClose();

// Is Polyscope running in 'headless' mode? Headless means there is no physical display to open windows on,
// e.g. when running on a remote server. It is still possible to run Polyscope in such settings with a supported
// backend (currently, the EGL backend only), and render to save screenshots or for other purposes.
// Can only be called after initialization.
bool isHeadless();

// === Global variables ===
namespace state {

Expand Down
3 changes: 3 additions & 0 deletions include/polyscope/render/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ class Engine {
virtual void shutdown() {};
virtual void checkError(bool fatal = false) = 0;
void buildEngineGui();

// 'headless' means there is no physical display to actually render to, e.g. when running on a remote server
virtual bool isHeadless() { return false; }

virtual void clearDisplay();
virtual void bindDisplay();
Expand Down
7 changes: 7 additions & 0 deletions include/polyscope/render/opengl/gl_engine_egl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "glad/glad.h"
// glad must come first
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif


Expand Down Expand Up @@ -41,6 +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; }

// === Windowing and framework things

Expand Down Expand Up @@ -74,6 +78,9 @@ class GLEngineEGL : public GLEngine {
// Internal windowing and engine details
EGLDisplay eglDisplay;
EGLContext eglContext;

// helpers
void sortAvailableDevicesByPreference(std::vector<int32_t>& deviceInds, EGLDeviceEXT rawDevices[]);
};

} // namespace backend_openGL3
Expand Down
3 changes: 3 additions & 0 deletions src/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace options {
std::string programName = "Polyscope";
int verbosity = 2;
std::string printPrefix = "[polyscope] ";
bool allowHeadlessBackends = false;
bool errorsThrowExceptions = false;
bool debugDrawPickBuffer = false;
int maxFPS = 60;
Expand Down Expand Up @@ -55,6 +56,8 @@ bool openImGuiWindowForUserCallback = true;
std::function<void()> configureImGuiStyleCallback = configureImGuiStyle;
std::function<std::tuple<ImFontAtlas*, ImFont*, ImFont*>()> prepareImGuiFontsCallback = prepareImGuiFonts;

// Backend and low-level options
int eglDeviceIndex = -1; // means "try all of them"

// enabled by default in debug mode
#ifndef NDEBUG
Expand Down
19 changes: 19 additions & 0 deletions src/polyscope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,14 @@ void show(size_t forFrames) {
if (!state::initialized) {
exception("must initialize Polyscope with polyscope::init() before calling polyscope::show().");
}

if (isHeadless() && forFrames == 0) {
info("You called show() while in headless mode. In headless mode there is no display to create windows on. By "
"default, the show() call will block indefinitely. If you did not mean to run in headless mode, check the "
"initialization settings. Otherwise, be sure to set a callback to make something happen while polyscope is "
"showing the UI, or use functions like screenshot() to render directly without calling show().");
}

unshowRequested = false;

// the popContext() doesn't quit until _after_ the last frame, so we need to decrement by 1 to get the count right
Expand Down Expand Up @@ -934,6 +942,16 @@ bool windowRequestsClose() {
return false;
}

bool isHeadless() {
if (!isInitialized()) {
exception("must initialize Polyscope with init() before calling isHeadless().");
}
if (render::engine) {
return render::engine->isHeadless();
}
return false;
}

void shutdown(bool allowMidFrameShutdown) {

if (!allowMidFrameShutdown && contextStack.size() > 1) {
Expand All @@ -955,6 +973,7 @@ void shutdown(bool allowMidFrameShutdown) {
// Shut down the render engine
render::engine->shutdown();
delete render::engine;
contextStack.clear();
render::engine = nullptr;
state::backend = "";
state::initialized = false;
Expand Down
48 changes: 28 additions & 20 deletions src/render/initialize_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void initializeRenderEngine(std::string backend) {
// Attempt to automatically initialize by trynig

bool initSucces = false;
std::string extraMessage = "";

#ifdef POLYSCOPE_BACKEND_OPENGL3_GLFW_ENABLED
// First try GLFW, if available
Expand All @@ -55,40 +56,47 @@ void initializeRenderEngine(std::string backend) {
initSucces = true;
} catch (const std::exception& e) {
if (options::verbosity > 0) {
info("Attempting automatic initialization. Could not initialize backend [openGL3_glfw]. Message: " +
std::string(e.what()));
info("Automatic initialization status: could not initialize backend [openGL3_glfw].");
}
}
if (initSucces) return;
#endif

#ifdef POLYSCOPE_BACKEND_OPENGL3_EGL_ENABLED
// Then, try EGL if available
engineBackendName = "openGL3_egl";
try {
backend_openGL3::initializeRenderEngine_egl();
initSucces = true;
} catch (const std::exception& e) {
if (options::verbosity > 0) {
info("Attempting automatic initialization. Could not initialize backend [openGL3_egl]. Message: " +
std::string(e.what()));

if (options::allowHeadlessBackends) {

// Then, try EGL if available
engineBackendName = "openGL3_egl";
try {
backend_openGL3::initializeRenderEngine_egl();
initSucces = true;
} catch (const std::exception& e) {
if (options::verbosity > 0) {
info("Automatic initialization status: could not initialize backend [openGL3_egl].");
}
}
}
if (initSucces) {
if (options::verbosity > 0) {
info("Automatic initialization could not create an interactive backend, and created a headless backend "
"instead. This likely means no displays are available. With the headless backend, you can still run "
"Polyscope and even render, for instance to record screenshots. However no interactive windows can be "
"created.");
if (initSucces) {
if (options::verbosity > 0) {
info("Automatic initialization could not create an interactive backend, and created a headless backend "
"instead. This likely means no displays are available. With the headless backend, you can still run "
"Polyscope and even render, for instance to save images of visualizations. However no interactive "
"windows can be created.");
}
return;
}
return;

} else {
extraMessage = " The headless EGL backend was available, but allowHeadlessBackends=false. Set it to true for "
"headless initialization.";
}

#endif

// Don't bother trying the 'mock' backend, it is unlikely to be what the user wants from the 'auto' option

// Failure
exception("Automatic initialization: no Polyscope backends could be initialized successfully.");
exception("Automatic initialization: no Polyscope backends could be initialized successfully." + extraMessage);

} else {
exception("unrecognized Polyscope backend " + backend);
Expand Down
Loading
Loading