perf(console): wrap only new console lines via a per-line cache
Each batch of new output re-wrapped all N visible lines glyph by glyph, allocating a fresh segment vector per line; a line evicted at the 10,000-line cap shifted every index, so nothing could be kept. And on every frame, idle or not, seeding the scanline shading summed the wrap rows of every line above the view. - The model numbers its lines and the view keeps one wrap per line, aligned by that number, so an eviction drops a prefix instead of forcing a rebuild. New output wraps only the new lines; the layout is then a prefix sum over the N visible lines, with no measuring and no per-line allocation. - The wraps are kept while the wrap width, line height, font size, font (by ImFont::FontId) and rasterizer density are exactly unchanged. A line-spacing change only re-sums heights; filters and fold toggles reuse the cached wraps. A resize drag still re-wraps every visible line per frame, as before, but reuses their storage. - The scanline seed is read from the prefix sum: O(1) per frame. Heights, offsets and segments are bit-identical to the old rebuild. A font reload at an unchanged size (the colour-emoji toggle) now re-wraps the console at once; before, it waited for the next line of output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -829,11 +829,25 @@ void ConsoleTab::renderOutput()
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(line_height * 16.0f);
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(interLineGap * 16.0f);
|
||||
layoutKey = layoutKey * 131ull + static_cast<std::uint64_t>(s_console_zoom * 1000.0f);
|
||||
if (layoutKey != layout_key_) {
|
||||
ImFontConsoleMeasure measure(ImGui::GetFont(), ImGui::GetFontSize());
|
||||
layout_ = BuildConsoleLayout(
|
||||
static_cast<int>(visible_indices_.size()),
|
||||
[this](int vi) -> const std::string& { return model_[visible_indices_[vi]].text; },
|
||||
// Exact inputs of the per-line wrap (the gap is excluded: it is added at assembly, so a line-spacing change
|
||||
// only re-sums heights). FontId, not the ImFont* address (an atlas reload can reuse it), catches a font reload
|
||||
// at an unchanged size, e.g. the stb_truetype <-> FreeType swap of the chat colour-emoji toggle.
|
||||
ImFont* outFont = ImGui::GetFont();
|
||||
ConsoleWrapKey wrapKey;
|
||||
wrapKey.wrapWidth = wrap_width;
|
||||
wrapKey.lineHeight = line_height;
|
||||
wrapKey.fontSize = ImGui::GetFontSize();
|
||||
wrapKey.rasterDensity = outFont->CurrentRasterizerDensity;
|
||||
wrapKey.font = outFont;
|
||||
wrapKey.fontId = outFont->FontId;
|
||||
if (layoutKey != layout_key_ || !wrap_cache_.matches(wrapKey)) {
|
||||
ImFontConsoleMeasure measure(outFont, ImGui::GetFontSize());
|
||||
// Align the per-line cache with the model (evicted prefix dropped by sequence number, new lines appended,
|
||||
// everything invalidated on a geometry/font change), then re-point layout_ at it: only lines not yet
|
||||
// wrapped at this geometry are measured; the rest is an O(visible) prefix sum with no allocation.
|
||||
wrap_cache_.sync(model_.firstSeq(), model_.size(), wrapKey);
|
||||
AssembleConsoleLayout(layout_, wrap_cache_, visible_indices_,
|
||||
[this](int raw) -> const std::string& { return model_[static_cast<std::size_t>(raw)].text; },
|
||||
wrap_width, line_height, interLineGap, measure);
|
||||
layout_key_ = layoutKey;
|
||||
}
|
||||
@@ -1042,8 +1056,9 @@ void ConsoleTab::drawVisibleLines(float padX, float lineHeight, bool hasTextFilt
|
||||
// gap) and skipped rows, breaking the alternation. Seed it with the sub-row ordinal of
|
||||
// the first on-screen line so the shading stays stable across scrolling.
|
||||
int scanRowOrdinal = 0;
|
||||
for (int k = 0; k < first_visible && k < static_cast<int>(layout_.segments.size()); k++)
|
||||
scanRowOrdinal += static_cast<int>(layout_.segments[k].size());
|
||||
if (!layout_.rowStart.empty()) // prefix sub-row counts from AssembleConsoleLayout: same value as the old sum
|
||||
scanRowOrdinal = layout_.rowStart[std::min<std::size_t>(static_cast<std::size_t>(first_visible),
|
||||
layout_.rowStart.size() - 1)];
|
||||
|
||||
int last_rendered_vi = first_visible - 1;
|
||||
for (int vi = first_visible; vi < visible_count; vi++) {
|
||||
@@ -2112,6 +2127,8 @@ void ConsoleTab::clear()
|
||||
vis_key_ = ~0ull;
|
||||
layout_key_ = ~0ull;
|
||||
++vis_generation_;
|
||||
layout_ = ConsoleLayout{}; // drop the refs into wrap_cache_ BEFORE resetting it (no dangling segments)
|
||||
wrap_cache_.reset();
|
||||
stop_confirm_pending_ = false; // a pending 'stop' confirmation is cancelled by clearing
|
||||
addLine(TR("console_cleared"), ConsoleChannel::Info);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user