diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-30 13:37:59 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-30 13:37:59 +0300 |
commit | 2a7c5ff7b9f887566a0e0d33ae6474822e23f571 (patch) | |
tree | 0e324bcfb185636460c961333b3a4a925e56a062 /src/ui/text.c | |
parent | a733f5c5a934b8c793f1ca398dc0e9b62a4dccf9 (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.c | 68 |
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 | ||
91 | iDeclareType(Text) | 91 | iDeclareType(Text) |
92 | iDeclareType(CacheRow) | ||
93 | |||
94 | struct Impl_CacheRow { | ||
95 | int height; | ||
96 | iInt2 pos; | ||
97 | }; | ||
92 | 98 | ||
93 | struct Impl_Text { | 99 | struct 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 | ||
106 | void init_Text(SDL_Renderer *render) { | 113 | void 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 | ||
214 | iLocalDef iCacheRow *cacheRow_Text_(iText *d, int height) { | ||
215 | return at_Array(&d->cacheRows, (height - 1) / d->cacheRowAllocStep); | ||
216 | } | ||
217 | |||
218 | static 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 | |||
200 | static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | 241 | static 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 | ||
255 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch) { | 283 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch) { |