diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index abc5ec92..f7a0d6cb 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -880,6 +880,7 @@ int main(int argc, char** argv) { // } std::cout << "!!!! shutdown time" << std::endl; + polyscope::shutdown(); return 0; } diff --git a/include/polyscope/messages.h b/include/polyscope/messages.h index 01aac9ca..4e64b3a5 100644 --- a/include/polyscope/messages.h +++ b/include/polyscope/messages.h @@ -27,4 +27,5 @@ void exception(std::string message); // Process any warnings that have accumulated, showing them to the user and clearing the queue. void showDelayedWarnings(); +void clearMessages(); } // namespace polyscope diff --git a/include/polyscope/polyscope.h b/include/polyscope/polyscope.h index 633db17a..c7205399 100644 --- a/include/polyscope/polyscope.h +++ b/include/polyscope/polyscope.h @@ -60,7 +60,7 @@ void unshow(); void frameTick(); // Do shutdown work and de-initialize Polyscope -void shutdown(); +void shutdown(bool allowMidFrameShutdown=false); // Returns true if the user has tried to exit the window at the OS level, e.g clicking the close button. Useful for // deciding when to exit your control loop when using frameTick() diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index e5eb8349..3d71e8c0 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -442,6 +442,7 @@ class Engine { virtual ~Engine(); // High-level control + virtual void shutdown() {}; virtual void checkError(bool fatal = false) = 0; void buildEngineGui(); diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index 360e06a4..df2651c7 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -325,6 +325,7 @@ class MockGLEngine : public Engine { // High-level control void initialize(); + virtual void shutdown() override; void checkError(bool fatal = false) override; void swapDisplayBuffers() override; diff --git a/include/polyscope/render/opengl/gl_engine_egl.h b/include/polyscope/render/opengl/gl_engine_egl.h index 24d93ef3..a57f8863 100644 --- a/include/polyscope/render/opengl/gl_engine_egl.h +++ b/include/polyscope/render/opengl/gl_engine_egl.h @@ -38,6 +38,7 @@ class GLEngineEGL : public GLEngine { // High-level control void initialize(); + virtual void shutdown() override; void swapDisplayBuffers() override; void checkError(bool fatal = false) override; diff --git a/include/polyscope/render/opengl/gl_engine_glfw.h b/include/polyscope/render/opengl/gl_engine_glfw.h index 88c38f10..67caddf2 100644 --- a/include/polyscope/render/opengl/gl_engine_glfw.h +++ b/include/polyscope/render/opengl/gl_engine_glfw.h @@ -48,6 +48,7 @@ class GLEngineGLFW : public GLEngine { // High-level control void initialize(); + virtual void shutdown() override; void swapDisplayBuffers() override; // === Windowing and framework things diff --git a/include/polyscope/slice_plane.h b/include/polyscope/slice_plane.h index 2316a7f2..f1286011 100644 --- a/include/polyscope/slice_plane.h +++ b/include/polyscope/slice_plane.h @@ -111,6 +111,7 @@ class SlicePlane { SlicePlane* addSceneSlicePlane(bool initiallyVisible = false); void removeLastSceneSlicePlane(); +void removeAllSlicePlanes(); void buildSlicePlaneGUI(); // flag to open the slice plane menu after adding a slice plane diff --git a/src/messages.cpp b/src/messages.cpp index fca039a4..e556f88c 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -235,7 +235,7 @@ void terminatingError(std::string message) { pushContext(func, false); // Quit the program - shutdown(); + shutdown(true); std::exit(-1); } @@ -281,4 +281,6 @@ void showDelayedWarnings() { } } +void clearMessages() { warningMessages.clear(); } + } // namespace polyscope diff --git a/src/polyscope.cpp b/src/polyscope.cpp index b26dec14..575f8e2b 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -934,14 +934,30 @@ bool windowRequestsClose() { return false; } -void shutdown() { +void shutdown(bool allowMidFrameShutdown) { + + if (!allowMidFrameShutdown && contextStack.size() > 1) { + terminatingError("shutdown() was called mid-frame (e.g. in a per-frame callback, or UI element). This is not " + "permitted, shutdown() may only be called when the main loop is not executing."); + } - // TODO should we make an effort to destruct everything here? if (options::usePrefsFile) { writePrefsFile(); } - render::engine->shutdownImGui(); + // Clear out all structures and other scene objects + removeAllStructures(); + removeAllGroups(); + removeAllSlicePlanes(); + clearMessages(); + state::userCallback = nullptr; + + // Shut down the render engine + render::engine->shutdown(); + delete render::engine; + render::engine = nullptr; + state::backend = ""; + state::initialized = false; } bool registerStructure(Structure* s, bool replaceIfPresent) { diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 6b33aef2..0e6ce5c0 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -33,11 +33,8 @@ namespace polyscope { namespace render { namespace backend_openGL_mock { - -MockGLEngine* glEngine = nullptr; // alias for engine pointer - void initializeRenderEngine() { - glEngine = new MockGLEngine(); + MockGLEngine* glEngine = new MockGLEngine(); engine = glEngine; glEngine->initialize(); engine->allocateGlobalBuffersAndPrograms(); @@ -874,7 +871,7 @@ void GLShaderProgram::assignBufferToVAO(GLShaderAttribute& a) { void GLShaderProgram::createBuffer(GLShaderAttribute& a) { // generate the buffer if needed - std::shared_ptr newBuff = glEngine->generateAttributeBuffer(a.type, a.arrayCount); + std::shared_ptr newBuff = engine->generateAttributeBuffer(a.type, a.arrayCount); std::shared_ptr engineNewBuff = std::dynamic_pointer_cast(newBuff); if (!engineNewBuff) throw std::invalid_argument("buffer type cast failed"); a.buff = engineNewBuff; @@ -1581,6 +1578,11 @@ void MockGLEngine::initializeImGui() { configureImGui(); } +void MockGLEngine::shutdown() { + checkError(); + shutdownImGui(); +} + void MockGLEngine::shutdownImGui() { ImGui::DestroyContext(); } void MockGLEngine::swapDisplayBuffers() {} diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 477cdac9..f028686c 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -38,8 +38,6 @@ namespace render { namespace backend_openGL3 { -GLEngine* glEngine = nullptr; // alias for global engine pointer - // == Map enums to native values // clang-format off @@ -1345,7 +1343,7 @@ void GLShaderProgram::createBuffer(GLShaderAttribute& a) { if (a.location == -1) return; // generate the buffer if needed - std::shared_ptr newBuff = glEngine->generateAttributeBuffer(a.type, a.arrayCount); + std::shared_ptr newBuff = engine->generateAttributeBuffer(a.type, a.arrayCount); std::shared_ptr engineNewBuff = std::dynamic_pointer_cast(newBuff); if (!engineNewBuff) throw std::invalid_argument("buffer type cast failed"); a.buff = engineNewBuff; diff --git a/src/render/opengl/gl_engine_egl.cpp b/src/render/opengl/gl_engine_egl.cpp index 76a957a0..81a0c3cf 100644 --- a/src/render/opengl/gl_engine_egl.cpp +++ b/src/render/opengl/gl_engine_egl.cpp @@ -18,9 +18,6 @@ namespace polyscope { namespace render { namespace backend_openGL3 { -GLEngineEGL* glEngineEGL = nullptr; // alias for global engine pointer -extern GLEngine* glEngine; // defined in gl_engine.h - namespace { // anonymous helpers void checkEGLError(bool fatal = true) { @@ -116,10 +113,8 @@ void checkEGLError(bool fatal = true) { void initializeRenderEngine_egl() { - glEngineEGL = new GLEngineEGL(); // create the new global engine object - - engine = glEngineEGL; // we keep a few copies of this pointer with various types - glEngine = glEngineEGL; + GLEngineEGL* glEngineEGL = new GLEngineEGL(); // create the new global engine object + engine = glEngineEGL; // initialize glEngineEGL->initialize(); @@ -128,9 +123,7 @@ void initializeRenderEngine_egl() { } GLEngineEGL::GLEngineEGL() {} -GLEngineEGL::~GLEngineEGL() { - // eglTerminate(eglDisplay) // TODO handle termination -} +GLEngineEGL::~GLEngineEGL() {} void GLEngineEGL::initialize() { @@ -239,6 +232,15 @@ void GLEngineEGL::initializeImGui() { configureImGui(); } +void GLEngineEGL::shutdown() { + checkError(); + shutdownImGui(); + + eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(eglDisplay, eglContext); + eglTerminate(eglDisplay); +} + void GLEngineEGL::shutdownImGui() { ImGui::DestroyContext(); } void GLEngineEGL::ImGuiNewFrame() { diff --git a/src/render/opengl/gl_engine_glfw.cpp b/src/render/opengl/gl_engine_glfw.cpp index 4eede7aa..585309c3 100644 --- a/src/render/opengl/gl_engine_glfw.cpp +++ b/src/render/opengl/gl_engine_glfw.cpp @@ -17,15 +17,11 @@ namespace polyscope { namespace render { namespace backend_openGL3 { -GLEngineGLFW* glEngineGLFW = nullptr; // alias for global engine pointer -extern GLEngine* glEngine; // defined in gl_engine.h - void initializeRenderEngine_glfw() { - glEngineGLFW = new GLEngineGLFW(); // create the new global engine object + GLEngineGLFW* glEngineGLFW = new GLEngineGLFW(); // create the new global engine object engine = glEngineGLFW; // we keep a few copies of this pointer with various types - glEngine = glEngineGLFW; // initialize glEngineGLFW->initialize(); @@ -119,6 +115,15 @@ void GLEngineGLFW::initializeImGui() { configureImGui(); } + +void GLEngineGLFW::shutdown() { + checkError(); + shutdownImGui(); + glfwDestroyWindow(mainWindow); + glfwTerminate(); +} + + void GLEngineGLFW::shutdownImGui() { // ImGui shutdown things ImGui_ImplOpenGL3_Shutdown(); diff --git a/src/slice_plane.cpp b/src/slice_plane.cpp index cca6cf88..d12731fd 100644 --- a/src/slice_plane.cpp +++ b/src/slice_plane.cpp @@ -37,6 +37,12 @@ void removeLastSceneSlicePlane() { } } +void removeAllSlicePlanes() { + while(!state::slicePlanes.empty()) { + removeLastSceneSlicePlane(); + } +} + void buildSlicePlaneGUI() { diff --git a/test/src/basics_test.cpp b/test/src/basics_test.cpp index df87c353..afb2aafc 100644 --- a/test/src/basics_test.cpp +++ b/test/src/basics_test.cpp @@ -84,6 +84,12 @@ TEST_F(PolyscopeTest, Unshow) { polyscope::state::userCallback = nullptr; } +TEST_F(PolyscopeTest, ShutdownAndReinitialize) { + polyscope::shutdown(); + SetUpTestSuite(); + polyscope::show(3); +} + // Make sure that creating an empty buffer does not throw errors TEST_F(PolyscopeTest, EmptyBuffer) {