diff --git a/inc/cvc/gl/SceneGraph.h b/inc/cvc/gl/SceneGraph.h index 4dc6e91..10077d0 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 ed16e91..61e5b91 100644 --- a/src/cvcGL/GeometryNode.cpp +++ b/src/cvcGL/GeometryNode.cpp @@ -6,6 +6,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 34a09fa..b244f9d 100644 --- a/src/cvcGL/GraphicsNode.cpp +++ b/src/cvcGL/GraphicsNode.cpp @@ -5,6 +5,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 7e0915b..f2213ae 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;