Skip to content

Commit

Permalink
add user-facing options for headless setup
Browse files Browse the repository at this point in the history
  • Loading branch information
nmwsharp committed Dec 27, 2024
1 parent 4f6216a commit c1b17d8
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 25 deletions.
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
7 changes: 6 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,9 @@ extern std::string printPrefix;
// Should errors throw exceptions, or just display? (default false)
extern bool errorsThrowExceptions;

// Allow initialization to create headless backends (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
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();
// 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
9 changes: 6 additions & 3 deletions include/polyscope/render/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class RenderBuffer {
public:
// abstract class: use the factory methods from the Engine class
RenderBuffer(RenderBufferType type_, unsigned int sizeX_, unsigned int sizeY_);
virtual ~RenderBuffer(){};
virtual ~RenderBuffer() {};

virtual void resize(unsigned int newX, unsigned int newY);

Expand All @@ -228,7 +228,7 @@ class FrameBuffer {
public:
// abstract class: use the factory methods from the Engine class
FrameBuffer();
virtual ~FrameBuffer(){};
virtual ~FrameBuffer() {};

virtual void bind() = 0;
// Bind to this framebuffer so subsequent draw calls will go to it
Expand Down Expand Up @@ -347,7 +347,7 @@ class ShaderProgram {

public:
ShaderProgram(DrawMode dm);
virtual ~ShaderProgram(){};
virtual ~ShaderProgram() {};


// === Store data
Expand Down Expand Up @@ -445,6 +445,9 @@ class Engine {
// High-level control
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
3 changes: 3 additions & 0 deletions include/polyscope/render/opengl/gl_engine_egl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class GLEngineEGL : public GLEngine {
void initialize();
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
1 change: 1 addition & 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
18 changes: 18 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() {

// TODO should we make an effort to destruct everything here?
Expand Down
46 changes: 28 additions & 18 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,38 +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].");
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].");

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 save images of visualizations. 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

0 comments on commit c1b17d8

Please sign in to comment.