From 0b18ede2fbe3c81fbc6593b55b07ac1eaa7f6133 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 3 Jul 2026 01:28:18 -0700 Subject: [PATCH] Fixing parameters reloaded when testing camera. Also fixed app freezing when closing Preferences dialog after using test camera dialog --- app/src/main.cpp | 6 ++++ corelib/src/SensorCaptureThread.cpp | 10 ++++-- guilib/src/CloudViewer.cpp | 10 ++++++ guilib/src/PreferencesDialog.cpp | 48 +++++++++++++++++++++++------ tools/DatabaseViewer/main.cpp | 6 ++++ 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/app/src/main.cpp b/app/src/main.cpp index 914a3496..ce529a7b 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -60,6 +60,12 @@ int main(int argc, char* argv[]) QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat()); #endif + // Recommended by VTK when using QVTKOpenGLNativeWidget (a QOpenGLWidget): let all VTK + // render widgets share a single OpenGL context, which is needed for correct rendering + // when render widgets live in / move across multiple top-level windows. Must be set + // before QApplication is constructed. + QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + /* Create tasks */ QApplication * app = new QApplication(argc, argv); app->setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }"); // selectable message box diff --git a/corelib/src/SensorCaptureThread.cpp b/corelib/src/SensorCaptureThread.cpp index 6414fb9f..69975c8c 100644 --- a/corelib/src/SensorCaptureThread.cpp +++ b/corelib/src/SensorCaptureThread.cpp @@ -530,15 +530,21 @@ void SensorCaptureThread::mainLoop() info.odomPose.setNull(); } - if(!data.imageCompressed().empty() || !data.imageRaw().empty() || !data.laserScanRaw().empty() || (dynamic_cast(_camera) != 0 && data.id()>0)) // intermediate nodes could not have image set + if(this->isKilled()) + { + // A kill was requested (e.g. while we were blocked capturing this frame): don't + // publish anything so we never deliver events to handlers that are being torn down. + } + else if(!data.imageCompressed().empty() || !data.imageRaw().empty() || !data.laserScanRaw().empty() || (dynamic_cast(_camera) != 0 && data.id()>0)) // intermediate nodes could not have image set { postUpdate(&data, &info); info.cameraName = _lidar?_lidar->getSerial():_camera->getSerial(); info.timeTotal = totalTime.ticks(); this->post(new SensorEvent(data, info)); } - else if(!this->isKilled()) + else { + // Not killed but no data: end of stream. Signal consumers once, then stop. UWARN("no more data..."); this->kill(); this->post(new SensorEvent()); diff --git a/guilib/src/CloudViewer.cpp b/guilib/src/CloudViewer.cpp index 6b6f04d5..2e54d1ca 100644 --- a/guilib/src/CloudViewer.cpp +++ b/guilib/src/CloudViewer.cpp @@ -265,6 +265,16 @@ CloudViewer::~CloudViewer() { UDEBUG(""); this->clear(); +#if VTK_MAJOR_VERSION > 8 + // Explicitly release the OpenGL context/resources bound to this widget's native window + // before the render window is torn down. + if(this->renderWindow()) + { + this->makeCurrent(); + this->renderWindow()->Finalize(); + this->doneCurrent(); + } +#endif delete _visualizer; UDEBUG(""); } diff --git a/guilib/src/PreferencesDialog.cpp b/guilib/src/PreferencesDialog.cpp index da9c16c3..ebc22d04 100644 --- a/guilib/src/PreferencesDialog.cpp +++ b/guilib/src/PreferencesDialog.cpp @@ -7793,12 +7793,13 @@ void PreferencesDialog::testOdometry() _ui->odom_dataBufferSize->value()); odomThread.registerToEventsManager(); + // parent = 0 (not 'this'): see testCamera() - avoids the nested-modality crash. OdometryViewer * odomViewer = new OdometryViewer(10, _ui->spinBox_decimation_odom->value(), 0.0f, _ui->doubleSpinBox_maxDepth_odom->value(), this->getOdomQualityWarnThr(), - this, + 0, this->getAllParameters()); odomViewer->setWindowTitle(tr("Odometry viewer")); odomViewer->resize(1280, 480+QPushButton().minimumHeight()); @@ -7860,20 +7861,41 @@ void PreferencesDialog::testOdometry() } odomViewer->exec(); - delete odomViewer; + + // Tear down the pipes first so no more events are routed to the threads/viewer being + // destroyed, then stop the threads, then delete the viewer. This avoids delivering + // events to a handler that is being torn down. + UEventsManager::removePipe(&cameraThread, &odomThread, "SensorEvent"); + if(imuThread) + { + UEventsManager::removePipe(imuThread, &odomThread, "IMUEvent"); + } + UEventsManager::removePipe(&odomThread, odomViewer, "OdometryEvent"); + UEventsManager::removePipe(odomViewer, &odomThread, "OdometryResetEvent"); if(imuThread) { imuThread->join(true); - delete imuThread; } cameraThread.join(true); odomThread.join(true); + + // deleteLater() (not delete): see testCamera() - avoids a dangling OpenGL platform + // window that crashes in QWindowsWindow::alertWindow when Preferences later closes. + odomViewer->deleteLater(); + if(imuThread) + { + delete imuThread; + } } void PreferencesDialog::testCamera() { - CameraViewer * window = new CameraViewer(this, this->getAllParameters()); + // Not parented to 'this': the Preferences dialog is itself application-modal, and making + // the viewer a modal *child* of it (nested modality) with an OpenGL/VTK native window + // crashes Qt (QWindowsWindow::alertWindow, this==nullptr) when Preferences later closes. + // exec() below still makes the viewer application-modal, so interaction stays blocked. + CameraViewer * window = new CameraViewer(nullptr, this->getAllParameters()); window->setWindowTitle(tr("Camera viewer")); window->resize(1280, 480+QPushButton().minimumHeight()); window->registerToEventsManager(); @@ -7923,12 +7945,16 @@ void PreferencesDialog::testCamera() cameraThread.start(); window->exec(); - delete window; + UEventsManager::removePipe(&cameraThread, window, "SensorEvent"); cameraThread.join(true); + // deleteLater() (not delete): defer destruction to the event loop so Qt finishes + // tearing down the OpenGL widget's context and window-proc subclass and drains + // pending activation messages first. + window->deleteLater(); } else { - delete window; + window->deleteLater(); } } @@ -8394,7 +8420,8 @@ void PreferencesDialog::calibrateOdomSensorExtrinsics() void PreferencesDialog::testLidar() { - CameraViewer * window = new CameraViewer(this, this->getAllParameters()); + // Not parented to 'this': see testCamera() - avoids the nested-modality crash. + CameraViewer * window = new CameraViewer(nullptr, this->getAllParameters()); window->setWindowTitle(tr("Lidar viewer")); window->setWindowFlags(Qt::Window); window->resize(1280, 480+QPushButton().minimumHeight()); @@ -8420,12 +8447,15 @@ void PreferencesDialog::testLidar() lidarThread.start(); window->exec(); - delete window; + UEventsManager::removePipe(&lidarThread, window, "SensorEvent"); lidarThread.join(true); + // deleteLater() (not delete): see testCamera() - avoids a dangling OpenGL platform + // window that crashes in QWindowsWindow::alertWindow when Preferences later closes. + window->deleteLater(); } else { - delete window; + window->deleteLater(); } } diff --git a/tools/DatabaseViewer/main.cpp b/tools/DatabaseViewer/main.cpp index 597655da..eed6bc80 100644 --- a/tools/DatabaseViewer/main.cpp +++ b/tools/DatabaseViewer/main.cpp @@ -53,6 +53,12 @@ int main(int argc, char * argv[]) QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat()); #endif + // Recommended by VTK when using QVTKOpenGLNativeWidget (a QOpenGLWidget): let all VTK + // render widgets share a single OpenGL context, which is needed for correct rendering + // when render widgets live in / move across multiple top-level windows. Must be set + // before QApplication is constructed. + QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef RTABMAP_PYTHON rtabmap::PythonInterface pythonInterface; #endif