[Bugfix] UIGridView::render() has no clean-state early-out — idle grid clears+redraws its RenderTexture every frame #351

Closed
opened 2026-07-11 04:06:08 +00:00 by john · 0 comments
Owner

Rescoped from #255 (which tracked general grid-render perf and is now closed). This is the concrete, actionable defect that verification surfaced.

Problem

The clean-state early-out and markCompositeDirty() machinery added under #255 live in UIGrid::render(), but that is no longer the live render path. Since the #252 GridView shim, on-screen rendering goes through UIGridView::render() (src/UIGridView.cpp:131), which has no dirty check and no early-out — it unconditionally renderTexture.clear(fill_color) and redraws every frame (src/UIGridView.cpp:165), even when nothing changed. UIGridView.cpp references render_dirty/composite_dirty only in center_camera, never in render().

So the "idle grid re-renders to its RenderTexture every frame" cost that #255 set out to eliminate is still present on the real path — the optimization exists but is dead code on UIGrid::render().

Compounding factors found in the same pass:

  • Entity move / sprite_index setters (src/UIEntity.cpp) update only the spatial hash — they call neither markCompositeDirty() nor markContentDirty(). So even if UIGridView gained a dirty gate, entity motion wouldn't invalidate it correctly without adding the marks.
  • UIGrid::render()'s early-out shader branch falls through instead of returning after drawing output (src/UIGrid.cpp:108+) — latent, but don't re-inherit it.
  • UIGrid::render() never resets its own dirty flags (clearDirty() is never called there), so its early-out could never re-arm anyway.

Proposed fix

  1. Port the clean-state early-out from UIGrid::render() into UIGridView::render(): when !render_dirty && !composite_dirty, redraw the cached output sprite and return (handle the shader branch with a real return, not a fallthrough).
  2. Ensure UIGridView::render() actually resets its dirty flags (clearDirty()) after a full re-render so the early-out re-arms.
  3. Make entity position / sprite_index setters mark the owning grid composite-dirty (not content-dirty — chunk caches are unaffected) so the early-out stays correct while entities move.

Acceptance

  • Idle grid (no animation, no entity motion) does not clear()+redraw its RenderTexture every frame — verify via Callgrind on a static-scene loop and via the Gauntlet idle baseline.
  • Moving entities / animating camera still update correctly (regression test: screenshot diff across a move).
  • No stale-frame artifacts when toggling visibility/fill_color/camera.

References

  • Parent tracker: #255 (closed, rescoped here)
  • Introduced by the #252 GridView shim
  • src/UIGridView.cpp:131,165, src/UIGrid.cpp:108+ (legacy early-out), src/UIDrawable.cpp:1068,1082 (markContentDirty/markCompositeDirty), src/UIEntity.cpp (entity setters)
Rescoped from #255 (which tracked general grid-render perf and is now closed). This is the concrete, actionable defect that verification surfaced. ## Problem The clean-state early-out and `markCompositeDirty()` machinery added under #255 live in **`UIGrid::render()`**, but that is no longer the live render path. Since the #252 GridView shim, on-screen rendering goes through **`UIGridView::render()` (`src/UIGridView.cpp:131`), which has no dirty check and no early-out** — it unconditionally `renderTexture.clear(fill_color)` and redraws every frame (`src/UIGridView.cpp:165`), even when nothing changed. `UIGridView.cpp` references `render_dirty`/`composite_dirty` only in `center_camera`, never in `render()`. So the "idle grid re-renders to its RenderTexture every frame" cost that #255 set out to eliminate is **still present on the real path** — the optimization exists but is dead code on `UIGrid::render()`. Compounding factors found in the same pass: - Entity move / `sprite_index` setters (`src/UIEntity.cpp`) update only the spatial hash — they call **neither** `markCompositeDirty()` nor `markContentDirty()`. So even if UIGridView gained a dirty gate, entity motion wouldn't invalidate it correctly without adding the marks. - `UIGrid::render()`'s early-out **shader branch falls through** instead of `return`ing after drawing `output` (`src/UIGrid.cpp:108+`) — latent, but don't re-inherit it. - `UIGrid::render()` never resets its own dirty flags (`clearDirty()` is never called there), so its early-out could never re-arm anyway. ## Proposed fix 1. Port the clean-state early-out from `UIGrid::render()` into `UIGridView::render()`: when `!render_dirty && !composite_dirty`, redraw the cached `output` sprite and return (handle the shader branch with a real `return`, not a fallthrough). 2. Ensure `UIGridView::render()` actually resets its dirty flags (`clearDirty()`) after a full re-render so the early-out re-arms. 3. Make entity position / `sprite_index` setters mark the owning grid **composite-dirty** (not content-dirty — chunk caches are unaffected) so the early-out stays correct while entities move. ## Acceptance - Idle grid (no animation, no entity motion) does not `clear()`+redraw its RenderTexture every frame — verify via Callgrind on a static-scene loop and via the Gauntlet idle baseline. - Moving entities / animating camera still update correctly (regression test: screenshot diff across a move). - No stale-frame artifacts when toggling visibility/fill_color/camera. ## References - Parent tracker: #255 (closed, rescoped here) - Introduced by the #252 GridView shim - `src/UIGridView.cpp:131,165`, `src/UIGrid.cpp:108+` (legacy early-out), `src/UIDrawable.cpp:1068,1082` (`markContentDirty`/`markCompositeDirty`), `src/UIEntity.cpp` (entity setters)
john closed this issue 2026-07-11 14:42:07 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
john/McRogueFace#351
No description provided.