diff --git a/src/display/mapcanvas.cpp b/src/display/mapcanvas.cpp index dc42d80e7..3d3ac36ad 100644 --- a/src/display/mapcanvas.cpp +++ b/src/display/mapcanvas.cpp @@ -94,8 +94,6 @@ MapCanvas::~MapCanvas() if (pmc == this) { pmc = nullptr; } - - cleanupOpenGL(); } MapCanvas *MapCanvas::getPrimary() diff --git a/src/display/mapcanvas_gl.cpp b/src/display/mapcanvas_gl.cpp index a215236e1..e43303076 100644 --- a/src/display/mapcanvas_gl.cpp +++ b/src/display/mapcanvas_gl.cpp @@ -125,6 +125,7 @@ void MapCanvas::cleanupOpenGL() // note: m_batchedMeshes co-owns textures created by MapCanvasData, // and it also owns the lifetime of some OpenGL objects (e.g. VBOs). m_batches.resetExistingMeshesAndIgnorePendingRemesh(); + m_weather.cleanup(); m_textures.destroyAll(); getGLFont().cleanup(); getOpenGL().cleanup(); @@ -306,6 +307,14 @@ void MapCanvas::initializeGL() this->updateTextures(); m_frameManager.requestUpdate(); }); + + // Clean up GL resources while the context is still current. + // The destructor is too late — Qt destroys the context before ~MapCanvas() runs. + connect(context(), + &QOpenGLContext::aboutToBeDestroyed, + this, + &MapCanvas::cleanupOpenGL, + Qt::DirectConnection); } /* Direct means it is always called from the emitter's thread */ diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 110d7b009..26c8457fa 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -102,7 +102,6 @@ static void addApplicationFont() MainWindow::~MainWindow() { - forceNewFile(); mmqt::rdisconnect(this); async_tasks::cleanup(); delete m_listener; diff --git a/src/opengl/OpenGLTypes.h b/src/opengl/OpenGLTypes.h index f7a101ac5..1fff18550 100644 --- a/src/opengl/OpenGLTypes.h +++ b/src/opengl/OpenGLTypes.h @@ -401,6 +401,7 @@ class NODISCARD UniqueMesh final ~UniqueMesh() = default; DEFAULT_MOVES_DELETE_COPIES(UniqueMesh); + void reset() { m_mesh.reset(); } void render(const GLRenderState &rs) const { deref(m_mesh).render(rs); } NODISCARD explicit operator bool() const { return m_mesh != nullptr; } }; diff --git a/src/opengl/Weather.cpp b/src/opengl/Weather.cpp index 0509c1a8f..e64ff19b3 100644 --- a/src/opengl/Weather.cpp +++ b/src/opengl/Weather.cpp @@ -202,6 +202,14 @@ GLWeather::~GLWeather() m_gl.getUboManager().unregisterRebuildFunction(Legacy::SharedVboEnum::WeatherBlock); } +void GLWeather::cleanup() +{ + m_simulation.reset(); + m_particles.reset(); + m_atmosphere.reset(); + m_timeOfDay.reset(); +} + void GLWeather::updateFromGame() { switch (m_observer.getWeather()) { diff --git a/src/opengl/Weather.h b/src/opengl/Weather.h index 4c9a28b3a..45eae1e5b 100644 --- a/src/opengl/Weather.h +++ b/src/opengl/Weather.h @@ -86,7 +86,7 @@ class NODISCARD GLWeather final GameObserver &observer, FrameManager &frameManager); ~GLWeather(); - + void cleanup(); DELETE_CTORS_AND_ASSIGN_OPS(GLWeather); public: diff --git a/src/opengl/legacy/Legacy.cpp b/src/opengl/legacy/Legacy.cpp index 0aa784ccb..c8cb265d1 100644 --- a/src/opengl/legacy/Legacy.cpp +++ b/src/opengl/legacy/Legacy.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -354,6 +355,12 @@ UboManager &Functions::getUboManager() /// This only exists so we can detect errors in contexts that don't support \c glDebugMessageCallback(). void Functions::checkError() { + // glGetError() returns GL_INVALID_OPERATION indefinitely when called without a current context + // (e.g. on NVIDIA drivers), causing an infinite loop. Skip the check if no context is current. + if (QOpenGLContext::currentContext() == nullptr) { + return; + } + #define CASE(x) \ case (x): \ qCritical() << "OpenGL error" << #x; \ diff --git a/src/opengl/legacy/VBO.cpp b/src/opengl/legacy/VBO.cpp index 9ba278a16..ece143480 100644 --- a/src/opengl/legacy/VBO.cpp +++ b/src/opengl/legacy/VBO.cpp @@ -3,6 +3,8 @@ #include "VBO.h" +#include + namespace Legacy { bool LOG_VBO_ALLOCATIONS = false; bool LOG_VBO_STATIC_UPLOADS = false; @@ -25,8 +27,11 @@ void VBO::reset() if (LOG_VBO_ALLOCATIONS) { qInfo() << this << "Freeing VBO" << vbo; } - auto sharedFunctions = std::exchange(m_weakFunctions, {}).lock(); - deref(sharedFunctions).glDeleteBuffers(1, &vbo); + if (auto sharedFunctions = std::exchange(m_weakFunctions, {}).lock()) { + sharedFunctions->glDeleteBuffers(1, &vbo); + } else { + qCritical() << "Legacy::Functions is no longer valid, leaking VBO" << vbo; + } } assert(m_weakFunctions.lock() == nullptr); } @@ -77,8 +82,12 @@ void Program::reset() if (LOG_VBO_ALLOCATIONS) { qInfo() << this << "Freeing Shader Program" << program; } - auto sharedFunctions = std::exchange(m_weakFunctions, {}).lock(); - deref(sharedFunctions).glDeleteProgram(program); + if (auto sharedFunctions = std::exchange(m_weakFunctions, {}).lock()) { + sharedFunctions->glDeleteProgram(program); + } else { + qCritical() << "Legacy::Functions is no longer valid, leaking shader program" + << program; + } } assert(m_weakFunctions.lock() == nullptr); }