mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
ifcviewer: event-driven rendering, idle scenes cost zero CPU
Replaced the 16ms QTimer with QEvent::UpdateRequest delivered via requestUpdate(), posted from every state mutator (mouse/wheel, model lifecycle, selection, visibility, resize). A static BIM scene — the common case for a viewer — now does no work at all between user actions. FPS is now measured as time spent inside render() rather than wall-clock gap between frames, so idle gaps don't pollute the 1-second window and the headline number reflects real render throughput. Headline fps still caps at vsync; sub-vsync profiling lives in the cull[...] phase timers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -317,10 +317,12 @@ ViewportWindow::ViewportWindow(QWindow* parent)
|
|||||||
fmt.setSamples(4);
|
fmt.setSamples(4);
|
||||||
setFormat(fmt);
|
setFormat(fmt);
|
||||||
|
|
||||||
connect(&render_timer_, &QTimer::timeout, this, [this]() {
|
// Redraw is driven by QEvent::UpdateRequest. We post one via
|
||||||
if (isExposed()) render();
|
// requestUpdate() from every function that mutates visible state
|
||||||
});
|
// (mouse/wheel, model lifecycle, selection, resize). When nothing
|
||||||
render_timer_.setInterval(16);
|
// changes — the common case for a static BIM model — we don't burn
|
||||||
|
// CPU/GPU redrawing the same frame. Qt coalesces multiple
|
||||||
|
// requestUpdate() calls inside a single vblank.
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewportWindow::~ViewportWindow() {
|
ViewportWindow::~ViewportWindow() {
|
||||||
@@ -381,11 +383,11 @@ void ViewportWindow::initGL() {
|
|||||||
context_->makeCurrent(this);
|
context_->makeCurrent(this);
|
||||||
if (on) gl_->glEnable(GL_CULL_FACE);
|
if (on) gl_->glEnable(GL_CULL_FACE);
|
||||||
else gl_->glDisable(GL_CULL_FACE);
|
else gl_->glDisable(GL_CULL_FACE);
|
||||||
|
requestUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
gl_initialized_ = true;
|
gl_initialized_ = true;
|
||||||
frame_clock_.start();
|
requestUpdate();
|
||||||
render_timer_.start();
|
|
||||||
|
|
||||||
emit initialized();
|
emit initialized();
|
||||||
}
|
}
|
||||||
@@ -613,6 +615,7 @@ void ViewportWindow::uploadInstanceChunk(const InstanceChunk& chunk) {
|
|||||||
m.total_triangles += m.meshes[chunk.local_mesh_id].index_count / 3;
|
m.total_triangles += m.meshes[chunk.local_mesh_id].index_count / 3;
|
||||||
}
|
}
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::finalizeModel(uint32_t model_id) {
|
void ViewportWindow::finalizeModel(uint32_t model_id) {
|
||||||
@@ -637,6 +640,7 @@ void ViewportWindow::finalizeModel(uint32_t model_id) {
|
|||||||
|
|
||||||
m.finalized = true;
|
m.finalized = true;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
|
|
||||||
const size_t ssbo_bytes = m.ssbo_instance_count * sizeof(InstanceGpu);
|
const size_t ssbo_bytes = m.ssbo_instance_count * sizeof(InstanceGpu);
|
||||||
qDebug("Model %u finalized: %zu verts, %zu meshes, %zu instances, %.1f MB vram "
|
qDebug("Model %u finalized: %zu verts, %zu meshes, %zu instances, %.1f MB vram "
|
||||||
@@ -746,6 +750,7 @@ void ViewportWindow::applyCachedModel(uint32_t model_id, SidecarData data) {
|
|||||||
m.finalized = true;
|
m.finalized = true;
|
||||||
models_gpu_.emplace(model_id, std::move(m));
|
models_gpu_.emplace(model_id, std::move(m));
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
|
|
||||||
qDebug("Sidecar apply: model %u %zu verts, %zu meshes, %zu instances "
|
qDebug("Sidecar apply: model %u %zu verts, %zu meshes, %zu instances "
|
||||||
"%.1f MB vram (vbo %.1f + ebo %.1f + ssbo %.1f)",
|
"%.1f MB vram (vbo %.1f + ebo %.1f + ssbo %.1f)",
|
||||||
@@ -770,6 +775,7 @@ void ViewportWindow::applyLodExtension(uint32_t model_id, const SidecarData& sd)
|
|||||||
// case lod1_* fields were touched.
|
// case lod1_* fields were touched.
|
||||||
m.meshes = sd.meshes;
|
m.meshes = sd.meshes;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,6 +792,7 @@ void ViewportWindow::applyLodExtension(uint32_t model_id, const SidecarData& sd)
|
|||||||
// Replace mesh metadata so cullAndUploadVisible sees the new lod1_ fields.
|
// Replace mesh metadata so cullAndUploadVisible sees the new lod1_ fields.
|
||||||
m.meshes = sd.meshes;
|
m.meshes = sd.meshes;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::resetScene() {
|
void ViewportWindow::resetScene() {
|
||||||
@@ -802,6 +809,7 @@ void ViewportWindow::resetScene() {
|
|||||||
models_gpu_.clear();
|
models_gpu_.clear();
|
||||||
selected_object_id_ = 0;
|
selected_object_id_ = 0;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::hideModel(uint32_t model_id) {
|
void ViewportWindow::hideModel(uint32_t model_id) {
|
||||||
@@ -809,6 +817,7 @@ void ViewportWindow::hideModel(uint32_t model_id) {
|
|||||||
if (it != models_gpu_.end()) {
|
if (it != models_gpu_.end()) {
|
||||||
it->second.hidden = true;
|
it->second.hidden = true;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,6 +826,7 @@ void ViewportWindow::showModel(uint32_t model_id) {
|
|||||||
if (it != models_gpu_.end()) {
|
if (it != models_gpu_.end()) {
|
||||||
it->second.hidden = false;
|
it->second.hidden = false;
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,10 +843,14 @@ void ViewportWindow::removeModel(uint32_t model_id) {
|
|||||||
if (it->second.indirect_buffer) gl_->glDeleteBuffers(1, &it->second.indirect_buffer);
|
if (it->second.indirect_buffer) gl_->glDeleteBuffers(1, &it->second.indirect_buffer);
|
||||||
models_gpu_.erase(it);
|
models_gpu_.erase(it);
|
||||||
have_cached_cull_ = false;
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::setSelectedObjectId(uint32_t id) { selected_object_id_ = id; }
|
void ViewportWindow::setSelectedObjectId(uint32_t id) {
|
||||||
|
selected_object_id_ = id;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
// --- HiZ occlusion culling (Phase 3C) -----------------------------------
|
// --- HiZ occlusion culling (Phase 3C) -----------------------------------
|
||||||
|
|
||||||
@@ -1367,6 +1381,9 @@ void ViewportWindow::updateCamera() {
|
|||||||
void ViewportWindow::render() {
|
void ViewportWindow::render() {
|
||||||
if (!gl_initialized_ || !isExposed()) return;
|
if (!gl_initialized_ || !isExposed()) return;
|
||||||
|
|
||||||
|
QElapsedTimer frame_cost_clock;
|
||||||
|
frame_cost_clock.start();
|
||||||
|
|
||||||
context_->makeCurrent(this);
|
context_->makeCurrent(this);
|
||||||
updateCamera();
|
updateCamera();
|
||||||
|
|
||||||
@@ -1508,8 +1525,13 @@ void ViewportWindow::render() {
|
|||||||
|
|
||||||
context_->swapBuffers(this);
|
context_->swapBuffers(this);
|
||||||
|
|
||||||
float dt = frame_clock_.restart() / 1000.0f;
|
// Measure frame *cost* (time spent inside render()) rather than the
|
||||||
accumulated_time_ += dt;
|
// wall-clock gap between frames. With event-driven rendering, idle gaps
|
||||||
|
// between requestUpdate() calls would otherwise pollute the FPS window.
|
||||||
|
// Reported fps = "if I rendered continuously, this is the rate I'd hit",
|
||||||
|
// which is what profiling actually wants.
|
||||||
|
const float frame_cost_s = frame_cost_clock.nsecsElapsed() * 1e-9f;
|
||||||
|
accumulated_time_ += frame_cost_s;
|
||||||
frame_count_++;
|
frame_count_++;
|
||||||
if (accumulated_time_ >= 1.0f) {
|
if (accumulated_time_ >= 1.0f) {
|
||||||
last_fps_ = static_cast<float>(frame_count_) / accumulated_time_;
|
last_fps_ = static_cast<float>(frame_count_) / accumulated_time_;
|
||||||
@@ -1649,13 +1671,19 @@ void ViewportWindow::renderAxisGizmo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::exposeEvent(QExposeEvent*) {
|
void ViewportWindow::exposeEvent(QExposeEvent*) {
|
||||||
if (isExposed() && !gl_initialized_) initGL();
|
if (isExposed()) {
|
||||||
|
if (!gl_initialized_) initGL();
|
||||||
|
else requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void ViewportWindow::resizeEvent(QResizeEvent*) {
|
void ViewportWindow::resizeEvent(QResizeEvent*) {
|
||||||
if (gl_initialized_) render();
|
if (gl_initialized_) requestUpdate();
|
||||||
}
|
}
|
||||||
bool ViewportWindow::event(QEvent* e) {
|
bool ViewportWindow::event(QEvent* e) {
|
||||||
switch (e->type()) {
|
switch (e->type()) {
|
||||||
|
case QEvent::UpdateRequest:
|
||||||
|
if (isExposed() && gl_initialized_) render();
|
||||||
|
return true;
|
||||||
case QEvent::MouseButtonPress: handleMousePress(static_cast<QMouseEvent*>(e)); return true;
|
case QEvent::MouseButtonPress: handleMousePress(static_cast<QMouseEvent*>(e)); return true;
|
||||||
case QEvent::MouseButtonRelease: handleMouseRelease(static_cast<QMouseEvent*>(e)); return true;
|
case QEvent::MouseButtonRelease: handleMouseRelease(static_cast<QMouseEvent*>(e)); return true;
|
||||||
case QEvent::MouseMove: handleMouseMove(static_cast<QMouseEvent*>(e)); return true;
|
case QEvent::MouseMove: handleMouseMove(static_cast<QMouseEvent*>(e)); return true;
|
||||||
@@ -1673,6 +1701,7 @@ void ViewportWindow::handleMouseRelease(QMouseEvent* e) {
|
|||||||
uint32_t id = pickObjectAt(e->pos().x(), e->pos().y());
|
uint32_t id = pickObjectAt(e->pos().x(), e->pos().y());
|
||||||
selected_object_id_ = id;
|
selected_object_id_ = id;
|
||||||
emit objectPicked(id);
|
emit objectPicked(id);
|
||||||
|
requestUpdate(); // selection highlight changed
|
||||||
}
|
}
|
||||||
active_button_ = Qt::NoButton;
|
active_button_ = Qt::NoButton;
|
||||||
}
|
}
|
||||||
@@ -1695,10 +1724,12 @@ void ViewportWindow::handleMouseMove(QMouseEvent* e) {
|
|||||||
camera_pitch_ += delta.y() * 0.3f;
|
camera_pitch_ += delta.y() * 0.3f;
|
||||||
camera_pitch_ = qBound(-89.0f, camera_pitch_, 89.0f);
|
camera_pitch_ = qBound(-89.0f, camera_pitch_, 89.0f);
|
||||||
}
|
}
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ViewportWindow::handleWheel(QWheelEvent* e) {
|
void ViewportWindow::handleWheel(QWheelEvent* e) {
|
||||||
float factor = e->angleDelta().y() > 0 ? 0.9f : 1.1f;
|
float factor = e->angleDelta().y() > 0 ? 0.9f : 1.1f;
|
||||||
camera_distance_ *= factor;
|
camera_distance_ *= factor;
|
||||||
camera_distance_ = qMax(0.1f, camera_distance_);
|
camera_distance_ = qMax(0.1f, camera_distance_);
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
#include <QtOpenGL/QOpenGLFunctions_4_5_Core>
|
#include <QtOpenGL/QOpenGLFunctions_4_5_Core>
|
||||||
#include <QTimer>
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
@@ -107,6 +106,11 @@ struct ModelGpuData {
|
|||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Rendering is event-driven: render() runs only when QEvent::UpdateRequest
|
||||||
|
// is delivered, posted via requestUpdate(). An idle scene costs zero CPU.
|
||||||
|
// INVARIANT: every public mutator that changes what should be on screen
|
||||||
|
// (camera, selection, model lifecycle, visibility) MUST call requestUpdate()
|
||||||
|
// before returning, or the viewport will go silently stale.
|
||||||
class ViewportWindow : public QWindow {
|
class ViewportWindow : public QWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -217,8 +221,6 @@ private:
|
|||||||
|
|
||||||
QOpenGLContext* context_ = nullptr;
|
QOpenGLContext* context_ = nullptr;
|
||||||
QOpenGLFunctions_4_5_Core* gl_ = nullptr;
|
QOpenGLFunctions_4_5_Core* gl_ = nullptr;
|
||||||
QTimer render_timer_;
|
|
||||||
QElapsedTimer frame_clock_;
|
|
||||||
bool gl_initialized_ = false;
|
bool gl_initialized_ = false;
|
||||||
|
|
||||||
// Shaders
|
// Shaders
|
||||||
|
|||||||
Reference in New Issue
Block a user