Skip to content

cvcGL: never render synchronously from a state-change handler - #161

Merged
transfix merged 2 commits into
masterfrom
fix/no-sync-render-per-state-change
Jul 27, 2026
Merged

cvcGL: never render synchronously from a state-change handler#161
transfix merged 2 commits into
masterfrom
fix/no-sync-render-per-state-change

Conversation

@transfix

Copy link
Copy Markdown
Owner

GraphicsNode::handleStateChanged ended with a synchronous 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 serializing its matrix into the state tree — turned every displayed frame into dozens of full scene renders.

Measurements (VolRover3, Austin platoon demo: 32 actors + 27 link segments animated per frame over a ~1M-triangle city)

Operation Cost
setTransform, unchanged matrix 0.07 ms (state short-circuits → no signal → no render)
setTransform, changing matrix 5.6 ms (handler fires → full Render())
bare state write, no handler attached 0.01 ms

63 changing transforms × ~5.5 ms of synchronous rendering ≈ 350 ms per frame — the app ran at 2.7 FPS with the GPU active (libGLX_nvidia confirmed loaded). 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.

Also reproducible in isolation: an in-app microbenchmark writing the node's matrix state key directly via pycvc.state_set costs 4.98 ms/call — the C++ setTransform wrapper contributes nothing; it's all handler → render.

Fix

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.

With the demo additionally driving per-frame motion through actor UserMatrix (workaround until this ships), the same scene runs at the render cap. After this lands and the family republishes, setTransform in a loop becomes ~0.1 ms/call and the workaround can be retired.

Notes for review

  • requestRender() takes the same m_eventQueueMutex the flag already uses; no new locking.
  • No behavior change for the flag's consumers: postEvent already set it; this just adds a public way to set it without queueing a no-op event.
  • Found while building the GRL-SNAM platoon demo; the write-up with the full measurement ladder is in the demo PR (grl-snam-dbg#16).

Joe Rivera added 2 commits July 27, 2026 07:22
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.
Sort the added #include <cvc/gl/SceneGraph.h> into alphabetical position in
GraphicsNode.cpp and GeometryNode.cpp (clang-format changed-lines check).
@transfix
transfix merged commit bbe3c8a into master Jul 27, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant