From 2feb01191e3b752ea8bc20b0b3f09625a37e9ac4 Mon Sep 17 00:00:00 2001 From: Joe Rivera Date: Mon, 27 Jul 2026 07:22:01 -0500 Subject: [PATCH 1/2] cvcGL: never render synchronously from a state-change handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GraphicsNode::handleStateChanged ended with vtkRenderWindow::Render(), and GeometryNode's polydata-update path did the same. The handler runs once per changed state key, so per-frame animation — setTransform on many nodes, each of which serializes its matrix into the state tree — turned every displayed frame into dozens of full scene renders. Measured in VolRover3 on the Austin platoon demo (28 soldiers + 4 vehicles + 27 radio-link segments animated per frame over a ~1M-triangle city): setTransform, unchanged matrix 0.07 ms (state short-circuits; no signal) setTransform, changing matrix 5.6 ms (handler fires -> full Render()) bare state write, no handler 0.01 ms 63 changing transforms x ~5.5 ms of synchronous rendering = ~350 ms per frame; the app ran at 2.7 FPS with the GPU active. The per-call cost is the whole scene render, not the state tree: setColor alone writes three keys and so rendered the scene three times. Both sites now set SceneGraph's render-needed flag via a new requestRender() and let the host's frame loop render once per frame through checkAndResetRenderNeeded() — the contract volrover3's continuous mode already implements. The synchronous Render() remains only as a fallback for a node used without a SceneGraph, where nothing drains the flag. --- inc/cvc/gl/SceneGraph.h | 9 +++++++++ src/cvcGL/GeometryNode.cpp | 9 +++++++-- src/cvcGL/GraphicsNode.cpp | 15 +++++++++++++-- src/cvcGL/SceneGraph.cpp | 5 +++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/inc/cvc/gl/SceneGraph.h b/inc/cvc/gl/SceneGraph.h index 4dc6e91e..10077d0a 100644 --- a/inc/cvc/gl/SceneGraph.h +++ b/inc/cvc/gl/SceneGraph.h @@ -73,6 +73,15 @@ class SceneGraph { // Check if a render is needed and reset the flag bool checkAndResetRenderNeeded(); + // Mark the scene as needing a render, WITHOUT rendering. The host's frame + // loop picks it up via checkAndResetRenderNeeded(). Nodes reacting to state + // changes must use this rather than calling vtkRenderWindow::Render() + // themselves: a synchronous render per state change turns N per-frame + // property updates into N full scene renders per displayed frame (measured + // in VolRover3: ~5 ms per update on a city-scale scene — 63 animated nodes + // dragged the app from 60 FPS to under 3). + void requestRender(); + // Multi-object graphics management (unified for both geometry and volumes) std::shared_ptr addGraphics(const std::string &name, const cvc::geometry &geom); std::shared_ptr addGraphics(const std::string &name, const cvc::volume &vol); diff --git a/src/cvcGL/GeometryNode.cpp b/src/cvcGL/GeometryNode.cpp index ed16e91a..89186762 100644 --- a/src/cvcGL/GeometryNode.cpp +++ b/src/cvcGL/GeometryNode.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -254,8 +255,12 @@ void GeometryNode::updateRenderModeVTK() { if (m_actor) m_actor->Modified(); - // Request a render update (if we have a renderer with a render window) - if (m_renderer && m_renderer->GetRenderWindow()) { + // Request a render update — flag-only; see GraphicsNode::handleStateChanged + // for why a synchronous Render() here is catastrophic under per-frame + // property animation (setColor alone writes three state keys). + if (m_sceneGraph) { + m_sceneGraph->requestRender(); + } else if (m_renderer && m_renderer->GetRenderWindow()) { m_renderer->GetRenderWindow()->Render(); } } diff --git a/src/cvcGL/GraphicsNode.cpp b/src/cvcGL/GraphicsNode.cpp index 34a09fa5..0061d2bc 100644 --- a/src/cvcGL/GraphicsNode.cpp +++ b/src/cvcGL/GraphicsNode.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -451,8 +452,18 @@ void GraphicsNode::handleStateChanged(const std::string &childState) { SceneNode::handleStateChanged(childState); } - // Request render after any state change - if (m_renderer && m_renderer->GetRenderWindow()) { + // Request render after any state change. NEVER render synchronously here: + // this handler runs once per changed state key, and per-frame animation + // (setTransform/setColor on many nodes) writes many keys per frame — a + // synchronous Render() per key turns each displayed frame into dozens of + // full scene renders (measured: 63 animated nodes at ~5 ms per render + // dragged VolRover3 from 60 FPS to under 3). The host's frame loop renders + // once per frame via checkAndResetRenderNeeded(). The synchronous fallback + // remains only for a node used without a SceneGraph, where nothing drains + // the flag. + if (m_sceneGraph) { + m_sceneGraph->requestRender(); + } else if (m_renderer && m_renderer->GetRenderWindow()) { m_renderer->GetRenderWindow()->Render(); } }); diff --git a/src/cvcGL/SceneGraph.cpp b/src/cvcGL/SceneGraph.cpp index 7e0915b0..f2213ae0 100644 --- a/src/cvcGL/SceneGraph.cpp +++ b/src/cvcGL/SceneGraph.cpp @@ -92,6 +92,11 @@ void SceneGraph::processEvents() { } } +void SceneGraph::requestRender() { + std::lock_guard lock(m_eventQueueMutex); + m_renderNeeded = true; +} + bool SceneGraph::checkAndResetRenderNeeded() { std::lock_guard lock(m_eventQueueMutex); bool needed = m_renderNeeded; From 373cd3836b1f43a32910490b4e4726d444554529 Mon Sep 17 00:00:00 2001 From: Joe Rivera Date: Mon, 27 Jul 2026 16:09:12 -0500 Subject: [PATCH 2/2] cvcGL: clang-format the new SceneGraph.h includes Sort the added #include into alphabetical position in GraphicsNode.cpp and GeometryNode.cpp (clang-format changed-lines check). --- src/cvcGL/GeometryNode.cpp | 2 +- src/cvcGL/GraphicsNode.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cvcGL/GeometryNode.cpp b/src/cvcGL/GeometryNode.cpp index 89186762..61e5b91d 100644 --- a/src/cvcGL/GeometryNode.cpp +++ b/src/cvcGL/GeometryNode.cpp @@ -2,11 +2,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/src/cvcGL/GraphicsNode.cpp b/src/cvcGL/GraphicsNode.cpp index 0061d2bc..b244f9dd 100644 --- a/src/cvcGL/GraphicsNode.cpp +++ b/src/cvcGL/GraphicsNode.cpp @@ -1,11 +1,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include