Skip to content

Commit 5010632

Browse files
committed
fix: cache parsed rich text strings for layouts
To save recomputation in text drawing when the underlying rich text string is needed, we can cache a copy of the parsed string with the text layout. This trades a small amount of memory and lays out off- screen text ahead of time, reducing any hitching when scrolling notes.
1 parent c2d165a commit 5010632

2 files changed

Lines changed: 26 additions & 26 deletions

File tree

engine/common/common.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ IndexedUTF32String IndexUTF8ToUTF32(std::string_view input)
438438
auto& offsets = ret.sourceCodeUnitOffsets;
439439
offsets.reserve(byteCount); // conservative reservation
440440
std::vector<char32_t> codepoints;
441+
codepoints.reserve(input.size());
441442

442443
auto bytes = (uint8_t const*)input.data();
443444
for (size_t byteIdx = 0; byteIdx < byteCount;) {

engine/render/r_font.cpp

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,6 @@ struct f_glyphLayout_s {
103103
struct f_textLayout_s {
104104
using Chunk = std::vector<f_glyphLayout_s>;
105105

106-
static std::shared_ptr<f_textLayout_s> LayoutRichTextLine(const f_fontStack_c* stack, const f_richTextString_c& richStr);
107-
static std::shared_ptr<f_textLayout_s> LayoutRichTextLineCached(const f_fontStack_c* stack, std::u32string_view view);
108-
static std::shared_ptr<f_textLayout_s> LayoutRichTextLineCached(const f_fontStack_c* stack, const f_richTextString_c& str);
109-
static void RunStatisticsUI(bool& show);
110-
double TotalAdvanceX() const;
111-
112-
std::deque<Chunk> chunks;
113-
double totalAdvanceX{};
114-
115106
struct CacheKey {
116107
const f_fontStack_c* stack;
117108
std::u32string fullText;
@@ -124,6 +115,15 @@ struct f_textLayout_s {
124115

125116
struct CacheValue;
126117

118+
static std::shared_ptr<f_textLayout_s> LayoutRichTextLine(const f_fontStack_c* stack, const f_richTextString_c& richStr);
119+
static const CacheValue* LayoutRichTextLineCached(const f_fontStack_c* stack, std::u32string_view view);
120+
static const CacheValue* LayoutRichTextLineCached(const f_fontStack_c* stack, const f_richTextString_c& str);
121+
static void RunStatisticsUI(bool& show);
122+
double TotalAdvanceX() const;
123+
124+
std::deque<Chunk> chunks;
125+
double totalAdvanceX{};
126+
127127
private:
128128
void AddChunk(Chunk&& chunk);
129129

@@ -133,6 +133,7 @@ struct f_textLayout_s {
133133

134134
struct f_textLayout_s::CacheValue
135135
{
136+
f_richTextString_c text;
136137
std::shared_ptr<f_textLayout_s> layout;
137138
};
138139

@@ -652,7 +653,7 @@ static std::string StripColorEscapes(const char* str) {
652653
int r_font_c::StringWidthInternal(f_fontStack_c* stack, std::u32string_view str)
653654
{
654655
col3_t col{};
655-
auto layout = f_textLayout_s::LayoutRichTextLineCached(stack, str);
656+
auto layout = f_textLayout_s::LayoutRichTextLineCached(stack, str)->layout;
656657
return (int)std::ceil(layout->TotalAdvanceX());
657658
}
658659

@@ -684,8 +685,7 @@ std::u32string_view r_font_c::StringCursorInternal(f_fontStack_c* stack, std::u3
684685
{
685686
// TODO(LV): Implement this via f_textLayout_s queries for the horizontal section corresponding to the cluster. Also probably group characters as graphemes too.
686687
col3_t col{};
687-
f_richTextString_c richStr(str);
688-
auto layout = f_textLayout_s::LayoutRichTextLineCached(stack, richStr);
688+
auto& [richStr, layout] = *f_textLayout_s::LayoutRichTextLineCached(stack, str);
689689

690690
float x{};
691691
for (auto& chunk : layout->chunks) {
@@ -867,8 +867,9 @@ void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u
867867
renderer->curLayer->Color(col);
868868
};
869869

870-
std::u32string_view tail(codepoints);
871-
f_richTextString_c richStr(tail);
870+
auto stack = FetchFontStack(height);
871+
const auto& [richStr, layout] = *f_textLayout_s::LayoutRichTextLineCached(stack, codepoints);
872+
872873
// Check if the line is visible
873874
if (pos[Y] >= renderer->sys->video->vid.size[1] || pos[Y] <= -height) {
874875
// Just apply the final colour code
@@ -879,9 +880,6 @@ void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u
879880
return;
880881
}
881882

882-
auto stack = FetchFontStack(height);
883-
auto layout = f_textLayout_s::LayoutRichTextLineCached(stack, richStr);
884-
885883
// Calculate the string position
886884
float x = pos[X];
887885
float y = pos[Y];
@@ -904,7 +902,6 @@ void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u
904902
}
905903
}
906904

907-
std::u32string_view startText = tail;
908905
float startX = x;
909906
float startY = y;
910907

@@ -925,7 +922,7 @@ void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u
925922
LatchCurrentColor(change);
926923
++colorI;
927924
}
928-
925+
929926
bool const isTab = chunk.size() == 1 && richStr.onlyText[chunk[0].cluster] == U'\t';
930927
if (!isTab) {
931928
f_glyphMapping_s gm{ info.fontId, info.glyphId };
@@ -1030,7 +1027,7 @@ std::shared_ptr<f_textLayout_s> f_textLayout_s::LayoutRichTextLine(const f_fontS
10301027
* respectively. We want cluster level 1 - HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS, allowing us to colour marks
10311028
differently for example (https://harfbuzz.github.io/working-with-harfbuzz-clusters.html)
10321029
*/
1033-
1030+
10341031
auto ret = std::make_shared<f_textLayout_s>();
10351032
f_textLayout_s& layout = *ret;
10361033
// Segment and shape text
@@ -1213,29 +1210,31 @@ std::shared_ptr<f_textLayout_s> f_textLayout_s::LayoutRichTextLine(const f_fontS
12131210
return ret;
12141211
}
12151212

1216-
std::shared_ptr<f_textLayout_s> f_textLayout_s::LayoutRichTextLineCached(const f_fontStack_c* stack, std::u32string_view view)
1213+
const f_textLayout_s::CacheValue* f_textLayout_s::LayoutRichTextLineCached(const f_fontStack_c* stack, std::u32string_view view)
12171214
{
12181215
CacheKeyProxy proxy{ stack, view };
12191216
auto I = cache.find(proxy);
12201217
if (I == cache.end()) {
12211218
f_richTextString_c str(view);
1222-
CacheValue value{ LayoutRichTextLine(stack, str) };
1219+
auto layout = LayoutRichTextLine(stack, str);
1220+
CacheValue value{ str, layout };
12231221
CacheKey key{ stack, str.fullText };
12241222
I = cache.insert({ key, value }).first;
12251223
}
1226-
return I->second.layout;
1224+
return &I->second;
12271225
}
12281226

1229-
std::shared_ptr<f_textLayout_s> f_textLayout_s::LayoutRichTextLineCached(const f_fontStack_c* stack, const f_richTextString_c& str)
1227+
const f_textLayout_s::CacheValue* f_textLayout_s::LayoutRichTextLineCached(const f_fontStack_c* stack, const f_richTextString_c& str)
12301228
{
12311229
CacheKeyProxy proxy{ stack, str.fullText };
12321230
auto I = cache.find(proxy);
12331231
if (I == cache.end()) {
1234-
CacheValue value{ LayoutRichTextLine(stack, str) };
1232+
auto layout = LayoutRichTextLine(stack, str);
1233+
CacheValue value{ str, layout };
12351234
CacheKey key{ stack, str.fullText };
12361235
I = cache.insert({ key, value }).first;
12371236
}
1238-
return I->second.layout;
1237+
return &I->second;
12391238
}
12401239

12411240
void r_font_c::RunStatisticsUI(bool* show) {

0 commit comments

Comments
 (0)