summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-30 13:37:59 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-30 13:37:59 +0300
commit2a7c5ff7b9f887566a0e0d33ae6474822e23f571 (patch)
tree0e324bcfb185636460c961333b3a4a925e56a062 /src/ui/text.c
parenta733f5c5a934b8c793f1ca398dc0e9b62a4dccf9 (diff)
Text: Improved glyph cache allocator
Tighter row packing by collecting glyphs of similar height to the same rows.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c68
1 files changed, 48 insertions, 20 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 1c728642..83b3964d 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -89,14 +89,21 @@ static void deinit_Font(iFont *d) {
89} 89}
90 90
91iDeclareType(Text) 91iDeclareType(Text)
92iDeclareType(CacheRow)
93
94struct Impl_CacheRow {
95 int height;
96 iInt2 pos;
97};
92 98
93struct Impl_Text { 99struct Impl_Text {
94 iFont fonts[max_FontId]; 100 iFont fonts[max_FontId];
95 SDL_Renderer *render; 101 SDL_Renderer *render;
96 SDL_Texture * cache; 102 SDL_Texture * cache;
97 iInt2 cacheSize; 103 iInt2 cacheSize;
98 iInt2 cachePos; 104 int cacheRowAllocStep;
99 int cacheRowHeight; 105 int cacheBottom;
106 iArray cacheRows;
100 SDL_Palette * grayscale; 107 SDL_Palette * grayscale;
101 iRegExp * ansiEscape; 108 iRegExp * ansiEscape;
102}; 109};
@@ -105,6 +112,7 @@ static iText text_;
105 112
106void init_Text(SDL_Renderer *render) { 113void init_Text(SDL_Renderer *render) {
107 iText *d = &text_; 114 iText *d = &text_;
115 init_Array(&d->cacheRows, sizeof(iCacheRow));
108 d->ansiEscape = new_RegExp("\\[([0-9;]+)m", 0); 116 d->ansiEscape = new_RegExp("\\[([0-9;]+)m", 0);
109 d->render = render; 117 d->render = render;
110 /* A grayscale palette for rasterized glyphs. */ { 118 /* A grayscale palette for rasterized glyphs. */ {
@@ -116,15 +124,20 @@ void init_Text(SDL_Renderer *render) {
116 SDL_SetPaletteColors(d->grayscale, colors, 0, 256); 124 SDL_SetPaletteColors(d->grayscale, colors, 0, 256);
117 } 125 }
118 /* Initialize the glyph cache. */ { 126 /* Initialize the glyph cache. */ {
119 d->cacheSize = init1_I2(fontSize_UI * 32); 127 d->cacheSize = init_I2(fontSize_UI * 16, fontSize_UI * 64);
120 d->cachePos = zero_I2(); 128 d->cacheRowAllocStep = fontSize_UI / 6;
121 d->cache = SDL_CreateTexture(render, 129 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache
130 once at least one glyph is stored. */
131 for (int h = d->cacheRowAllocStep; h <= 2 * fontSize_UI; h += d->cacheRowAllocStep) {
132 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 });
133 }
134 d->cacheBottom = 0;
135 d->cache = SDL_CreateTexture(render,
122 SDL_PIXELFORMAT_RGBA8888, 136 SDL_PIXELFORMAT_RGBA8888,
123 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, 137 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
124 d->cacheSize.x, 138 d->cacheSize.x,
125 d->cacheSize.y); 139 d->cacheSize.y);
126 SDL_SetTextureBlendMode(d->cache, SDL_BLENDMODE_BLEND); 140 SDL_SetTextureBlendMode(d->cache, SDL_BLENDMODE_BLEND);
127 d->cacheRowHeight = 0;
128 } 141 }
129 /* Load the fonts. */ { 142 /* Load the fonts. */ {
130 const struct { 143 const struct {
@@ -173,6 +186,7 @@ void deinit_Text(void) {
173 iForIndices(i, d->fonts) { 186 iForIndices(i, d->fonts) {
174 deinit_Font(&d->fonts[i]); 187 deinit_Font(&d->fonts[i]);
175 } 188 }
189 deinit_Array(&d->cacheRows);
176 SDL_DestroyTexture(d->cache); 190 SDL_DestroyTexture(d->cache);
177 d->render = NULL; 191 d->render = NULL;
178 iRelease(d->ansiEscape); 192 iRelease(d->ansiEscape);
@@ -197,13 +211,39 @@ iLocalDef SDL_Rect sdlRect_(const iRect rect) {
197 return (SDL_Rect){ rect.pos.x, rect.pos.y, rect.size.x, rect.size.y }; 211 return (SDL_Rect){ rect.pos.x, rect.pos.y, rect.size.x, rect.size.y };
198} 212}
199 213
214iLocalDef iCacheRow *cacheRow_Text_(iText *d, int height) {
215 return at_Array(&d->cacheRows, (height - 1) / d->cacheRowAllocStep);
216}
217
218static iInt2 assignCachePos_Text_(iText *d, iInt2 size) {
219 iCacheRow *cur = cacheRow_Text_(d, size.y);
220 if (cur->height == 0) {
221 /* Begin a new row height. */
222 cur->height = (1 + (size.y - 1) / d->cacheRowAllocStep) * d->cacheRowAllocStep;
223 cur->pos.y = d->cacheBottom;
224 d->cacheBottom = cur->pos.y + cur->height;
225 }
226 iAssert(cur->height >= size.y);
227 /* TODO: Automatically enlarge the cache if running out of space?
228 Maybe make it paged, but beware of texture swapping too often inside a text string. */
229 if (cur->pos.x + size.x > d->cacheSize.x) {
230 /* Does not fit on this row, advance to a new location in the cache. */
231 cur->pos.y = d->cacheBottom;
232 cur->pos.x = 0;
233 d->cacheBottom += cur->height;
234 iAssert(d->cacheBottom <= d->cacheSize.y);
235 }
236 const iInt2 assigned = cur->pos;
237 cur->pos.x += size.x;
238 return assigned;
239}
240
200static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { 241static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) {
201 iText *txt = &text_; 242 iText *txt = &text_;
202 SDL_Renderer *render = txt->render; 243 SDL_Renderer *render = txt->render;
203 SDL_Texture *tex = NULL; 244 SDL_Texture *tex = NULL;
204 SDL_Surface *surface = NULL; 245 SDL_Surface *surface = NULL;
205 const iChar ch = char_Glyph(glyph); 246 const iChar ch = char_Glyph(glyph);
206 iBool fromStb = iFalse;
207 iRect *glRect = &glyph->rect[hoff]; 247 iRect *glRect = &glyph->rect[hoff];
208 /* Rasterize the glyph using stbtt. */ { 248 /* Rasterize the glyph using stbtt. */ {
209 surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); 249 surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f);
@@ -223,17 +263,11 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) {
223 NULL, 263 NULL,
224 NULL); 264 NULL);
225 glyph->d[hoff].y += d->baselineOffset; 265 glyph->d[hoff].y += d->baselineOffset;
226 fromStb = iTrue;
227 tex = SDL_CreateTextureFromSurface(render, surface); 266 tex = SDL_CreateTextureFromSurface(render, surface);
228 glRect->size = init_I2(surface->w, surface->h); 267 glRect->size = init_I2(surface->w, surface->h);
229 } 268 }
230 /* Determine placement in the glyph cache texture, advancing in rows. */ 269 /* Determine placement in the glyph cache texture, advancing in rows. */
231 if (txt->cachePos.x + glRect->size.x > txt->cacheSize.x) { 270 glRect->pos = assignCachePos_Text_(txt, glRect->size);
232 txt->cachePos.x = 0;
233 txt->cachePos.y += txt->cacheRowHeight;
234 txt->cacheRowHeight = 0;
235 }
236 glRect->pos = txt->cachePos;
237 SDL_SetRenderTarget(render, txt->cache); 271 SDL_SetRenderTarget(render, txt->cache);
238 const SDL_Rect dstRect = sdlRect_(*glRect); 272 const SDL_Rect dstRect = sdlRect_(*glRect);
239 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); 273 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect);
@@ -244,12 +278,6 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) {
244 stbtt_FreeBitmap(surface->pixels, NULL); 278 stbtt_FreeBitmap(surface->pixels, NULL);
245 SDL_FreeSurface(surface); 279 SDL_FreeSurface(surface);
246 } 280 }
247 /* Update cache cursor. */
248 txt->cachePos.x += glRect->size.x;
249 txt->cacheRowHeight = iMax(txt->cacheRowHeight, glRect->size.y);
250 iAssert(txt->cachePos.y + txt->cacheRowHeight <= txt->cacheSize.y);
251 /* TODO: Automatically enlarge the cache if running out of space?
252 Maybe make it paged. */
253} 281}
254 282
255iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch) { 283iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch) {