68df7b96
Clarify dirty-row rendering edge cases
a73x 2026-04-08 18:20
Commit message
docs/superpowers/specs/2026-04-08-dirty-row-rendering-design.md
| Old | New | ||
|---|---|---|---|
| @@ -82,6 +82,19 @@ Behavior: | |||
| 82 | - If `layout_dirty == true`, repack all rows and upload the full packed buffer. | 82 | - If `layout_dirty == true`, repack all rows and upload the full packed buffer. |
| 83 | - Otherwise, overwrite only the changed row byte ranges and cursor byte range in the GPU instance buffer. | 83 | - Otherwise, overwrite only the changed row byte ranges and cursor byte range in the GPU instance buffer. |
| 84 | 84 | ||
| 85 | ### Dirty-flag lifecycle | ||
| 86 | |||
| 87 | Dirty flags are consumed by the renderer cache and must be cleared only after cache state has been brought in sync with the current render state. | ||
| 88 | |||
| 89 | Rules: | ||
| 90 | |||
| 91 | - Read `term.render_state.dirty` and `row_data.items(.dirty)` immediately after `term.snapshot()`. | ||
| 92 | - Complete all required row-cache rebuilds, cursor-cache rebuilds, and any repack decisions before clearing flags. | ||
| 93 | - Clear `term.render_state.dirty` and any consumed row dirty flags only after CPU cache state is updated successfully. | ||
| 94 | - If a rebuild, repack, atlas insertion, or upload-preparation step fails, do not clear dirty flags for that frame. | ||
| 95 | |||
| 96 | This prevents both permanent “always dirty” behavior and lost updates caused by clearing too early. | ||
| 97 | |||
| 85 | ### No redraw | 98 | ### No redraw |
| 86 | 99 | ||
| 87 | If terminal state is not dirty and no window/swapchain event forces a frame, skip both cache work and draw submission. | 100 | If terminal state is not dirty and no window/swapchain event forces a frame, skip both cache work and draw submission. |
| @@ -99,6 +112,12 @@ Behavior: | |||
| 99 | - Copy the provided slice into the instance buffer at `offset_instances * @sizeOf(Instance)`. | 112 | - Copy the provided slice into the instance buffer at `offset_instances * @sizeOf(Instance)`. |
| 100 | - Unmap memory. | 113 | - Unmap memory. |
| 101 | 114 | ||
| 115 | Important fallback rule: | ||
| 116 | |||
| 117 | - If capacity growth is required, `uploadInstanceRange` must not attempt to preserve prior GPU contents implicitly. | ||
| 118 | - Buffer growth forces `layout_dirty = true`, followed by full repack and a full-buffer upload from `packed_instances`. | ||
| 119 | - Partial range uploads are only valid when the underlying GPU buffer remains allocated and existing offsets stay valid. | ||
| 120 | |||
| 102 | Keep `uploadInstances` for full-buffer writes. | 121 | Keep `uploadInstances` for full-buffer writes. |
| 103 | 122 | ||
| 104 | No pipeline or draw-call structure changes are required. | 123 | No pipeline or draw-call structure changes are required. |
| @@ -150,6 +169,9 @@ Add tests before implementation for: | |||
| 150 | - Layout remains stable when a rebuilt row keeps the same instance count. | 169 | - Layout remains stable when a rebuilt row keeps the same instance count. |
| 151 | - Layout becomes dirty when a rebuilt row changes instance count. | 170 | - Layout becomes dirty when a rebuilt row changes instance count. |
| 152 | - Packing offsets remain contiguous after full repack. | 171 | - Packing offsets remain contiguous after full repack. |
| 172 | - Cursor-only updates rebuild cursor cache without requiring unrelated row rebuilds. | ||
| 173 | - Instance-buffer growth during a partial update forces full repack/full upload fallback. | ||
| 174 | - Dirty flags are cleared only after cache refresh planning succeeds. | ||
| 153 | 175 | ||
| 154 | Verification after implementation: | 176 | Verification after implementation: |
| 155 | 177 | ||