From 6f6bebf387aa34b33af4aaf14cc5040159fa90ab Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 11 Apr 2026 20:05:50 +1000 Subject: [PATCH] Add performance stats overlay in status bar Show FPS, frame time, visible/total objects, and visible/total triangles in the status bar. Toggled via Settings > Show Performance Stats, persisted in app settings. Co-Authored-By: Claude Opus 4.6 --- src/ifcviewer/AppSettings.cpp | 14 ++++++++++++++ src/ifcviewer/AppSettings.h | 5 +++++ src/ifcviewer/MainWindow.cpp | 21 +++++++++++++++++++++ src/ifcviewer/MainWindow.h | 1 + src/ifcviewer/SettingsWindow.cpp | 6 ++++++ src/ifcviewer/SettingsWindow.h | 2 ++ src/ifcviewer/ViewportWindow.cpp | 21 +++++++++++++++++++++ src/ifcviewer/ViewportWindow.h | 14 ++++++++++++++ 8 files changed, 84 insertions(+) diff --git a/src/ifcviewer/AppSettings.cpp b/src/ifcviewer/AppSettings.cpp index 07c5f8c3bc..af1edfa36f 100644 --- a/src/ifcviewer/AppSettings.cpp +++ b/src/ifcviewer/AppSettings.cpp @@ -24,6 +24,7 @@ namespace { constexpr const char* kGeometryLibraryKey = "geometry/library"; constexpr const char* kGeometryLibraryDefault = "hybrid-cgal-simple-opencascade"; +constexpr const char* kShowStatsKey = "viewport/show_stats"; } AppSettings& AppSettings::instance() { @@ -46,12 +47,25 @@ void AppSettings::setGeometryLibrary(const QString& value) { emit geometryLibraryChanged(value); } +bool AppSettings::showStats() const { + return show_stats_; +} + +void AppSettings::setShowStats(bool value) { + if (show_stats_ == value) return; + show_stats_ = value; + persist(); + emit showStatsChanged(value); +} + void AppSettings::load() { QSettings settings; geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString(); + show_stats_ = settings.value(kShowStatsKey, false).toBool(); } void AppSettings::persist() { QSettings settings; settings.setValue(kGeometryLibraryKey, geometry_library_); + settings.setValue(kShowStatsKey, show_stats_); } diff --git a/src/ifcviewer/AppSettings.h b/src/ifcviewer/AppSettings.h index 9658c10b95..f70062475c 100644 --- a/src/ifcviewer/AppSettings.h +++ b/src/ifcviewer/AppSettings.h @@ -34,8 +34,12 @@ public: QString geometryLibrary() const; void setGeometryLibrary(const QString& value); + bool showStats() const; + void setShowStats(bool value); + signals: void geometryLibraryChanged(const QString& value); + void showStatsChanged(bool value); private: AppSettings(); @@ -43,6 +47,7 @@ private: void persist(); QString geometry_library_; + bool show_stats_ = false; }; #endif // APPSETTINGS_H diff --git a/src/ifcviewer/MainWindow.cpp b/src/ifcviewer/MainWindow.cpp index 6eede35353..4abd929b0b 100644 --- a/src/ifcviewer/MainWindow.cpp +++ b/src/ifcviewer/MainWindow.cpp @@ -18,6 +18,7 @@ ********************************************************************************/ #include "MainWindow.h" +#include "AppSettings.h" #include "SettingsWindow.h" #include @@ -43,6 +44,23 @@ MainWindow::MainWindow(QWidget* parent) QMessageBox::warning(this, "Error", msg); }, Qt::QueuedConnection); + connect(viewport_, &ViewportWindow::frameStatsUpdated, this, [this](const ViewportWindow::FrameStats& s) { + if (!stats_label_->isVisible()) return; + stats_label_->setText( + QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri") + .arg(s.fps, 0, 'f', 1) + .arg(s.frame_time_ms, 0, 'f', 1) + .arg(s.visible_objects) + .arg(s.total_objects) + .arg(s.visible_triangles) + .arg(s.total_triangles)); + }); + + connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { + stats_label_->setVisible(show); + if (!show) stats_label_->clear(); + }); + connect(&element_poll_timer_, &QTimer::timeout, this, &MainWindow::pollNewElements); element_poll_timer_.setInterval(100); @@ -91,7 +109,10 @@ void MainWindow::setupUi() { progress_bar_->setMaximumWidth(200); progress_bar_->setVisible(false); status_label_ = new QLabel("Ready"); + stats_label_ = new QLabel(); + stats_label_->setVisible(AppSettings::instance().showStats()); statusBar()->addWidget(status_label_, 1); + statusBar()->addPermanentWidget(stats_label_); statusBar()->addPermanentWidget(progress_bar_); } diff --git a/src/ifcviewer/MainWindow.h b/src/ifcviewer/MainWindow.h index d5f4c18a39..bbec6ce83d 100644 --- a/src/ifcviewer/MainWindow.h +++ b/src/ifcviewer/MainWindow.h @@ -66,6 +66,7 @@ private: QTableWidget* property_table_ = nullptr; QProgressBar* progress_bar_ = nullptr; QLabel* status_label_ = nullptr; + QLabel* stats_label_ = nullptr; QTimer element_poll_timer_; QElapsedTimer load_timer_; diff --git a/src/ifcviewer/SettingsWindow.cpp b/src/ifcviewer/SettingsWindow.cpp index a24f9bc976..c4ebddc650 100644 --- a/src/ifcviewer/SettingsWindow.cpp +++ b/src/ifcviewer/SettingsWindow.cpp @@ -20,6 +20,7 @@ #include "SettingsWindow.h" #include "AppSettings.h" +#include #include #include #include @@ -40,6 +41,9 @@ void SettingsWindow::setupUi() { geometry_library_edit_->setMinimumWidth(280); form->addRow("Geometry Library", geometry_library_edit_); + show_stats_check_ = new QCheckBox(this); + form->addRow("Show Performance Stats", show_stats_check_); + auto* button_box = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); @@ -60,9 +64,11 @@ void SettingsWindow::showEvent(QShowEvent* event) { void SettingsWindow::syncFromSettings() { geometry_library_edit_->setText(AppSettings::instance().geometryLibrary()); + show_stats_check_->setChecked(AppSettings::instance().showStats()); } void SettingsWindow::onAccepted() { AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text()); + AppSettings::instance().setShowStats(show_stats_check_->isChecked()); accept(); } diff --git a/src/ifcviewer/SettingsWindow.h b/src/ifcviewer/SettingsWindow.h index 77affe7757..ea55252682 100644 --- a/src/ifcviewer/SettingsWindow.h +++ b/src/ifcviewer/SettingsWindow.h @@ -22,6 +22,7 @@ #include +class QCheckBox; class QLineEdit; class QShowEvent; @@ -41,6 +42,7 @@ private: void syncFromSettings(); QLineEdit* geometry_library_edit_ = nullptr; + QCheckBox* show_stats_check_ = nullptr; }; #endif diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 414b9889fa..1ebe988554 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -532,6 +532,7 @@ void ViewportWindow::updateCamera() { void ViewportWindow::buildVisibleList(const QMatrix4x4& vp) { visible_counts_.clear(); visible_offsets_.clear(); + visible_triangles_ = 0; std::lock_guard lock(upload_mutex_); if (object_draw_info_.empty()) return; @@ -582,6 +583,7 @@ void ViewportWindow::buildVisibleList(const QMatrix4x4& vp) { visible_counts_.push_back(static_cast(obj.index_count)); visible_offsets_.push_back(reinterpret_cast( static_cast(obj.index_offset))); + visible_triangles_ += obj.index_count / 3; } } } @@ -617,6 +619,25 @@ void ViewportWindow::render() { renderAxisGizmo(); context_->swapBuffers(this); + + // Compute FPS (updated once per second to avoid flicker). + float dt = frame_clock_.restart() / 1000.0f; + accumulated_time_ += dt; + frame_count_++; + if (accumulated_time_ >= 1.0f) { + last_fps_ = static_cast(frame_count_) / accumulated_time_; + frame_count_ = 0; + accumulated_time_ = 0.0f; + + FrameStats stats; + stats.fps = last_fps_; + stats.frame_time_ms = 1000.0f / last_fps_; + stats.total_objects = static_cast(object_draw_info_.size()); + stats.visible_objects = static_cast(visible_counts_.size()); + stats.total_triangles = total_triangles_; + stats.visible_triangles = visible_triangles_; + emit frameStatsUpdated(stats); + } } void ViewportWindow::renderAxisGizmo() { diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index 363158b16f..58a6334321 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -65,9 +65,19 @@ public: void setSelectedObjectId(uint32_t id); uint32_t pickObjectAt(int x, int y); + struct FrameStats { + float fps; + float frame_time_ms; + uint32_t total_objects; + uint32_t visible_objects; + uint32_t total_triangles; + uint32_t visible_triangles; + }; + signals: void objectPicked(uint32_t object_id); void initialized(); + void frameStatsUpdated(const ViewportWindow::FrameStats& stats); protected: void exposeEvent(QExposeEvent* event) override; @@ -152,6 +162,10 @@ private: // Stats uint32_t total_triangles_ = 0; + uint32_t visible_triangles_ = 0; + int frame_count_ = 0; + float accumulated_time_ = 0.0f; + float last_fps_ = 0.0f; }; #endif // VIEWPORTWINDOW_H