summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-01-10 16:23:03 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-01-10 16:23:03 +0200
commit2f78fa7f85c4384d5b013a136f02d107c70f33c0 (patch)
tree8dbdc67ec44a725ee47187a4f66286e2d241eaba /src/ui/text.c
parent0a4715c02f7923b1085ca171988bd17cd841e1c3 (diff)
Text: Lazy glyph rasterization
Glyphs are now rasterized only when they are needed for drawing. Otherwise, only the metrics and the cache position are set. This is more robust as we can retry rasterizing glyphs that previously failed, and faster because measuring (e.g., document layout) doesn't rasterize anything.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c127
1 files changed, 82 insertions, 45 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index cf974366..218e7565 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -52,8 +52,16 @@ int gap_Text; /* cf. gap_UI in metrics.h */
52int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ 52int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */
53int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ 53int enableKerning_Text = iTrue; /* looking up kern pairs is slow */
54 54
55static iBool enableRaster_Text_ = iTrue;
56
57enum iGlyphFlag {
58 rasterized0_GlyphFlag = iBit(1), /* zero offset */
59 rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */
60};
61
55struct Impl_Glyph { 62struct Impl_Glyph {
56 iHashNode node; 63 iHashNode node;
64 int flags;
57 uint32_t glyphIndex; 65 uint32_t glyphIndex;
58 const iFont *font; /* may come from symbols/emoji */ 66 const iFont *font; /* may come from symbols/emoji */
59 iRect rect[2]; /* zero and half pixel offset */ 67 iRect rect[2]; /* zero and half pixel offset */
@@ -63,6 +71,7 @@ struct Impl_Glyph {
63 71
64void init_Glyph(iGlyph *d, iChar ch) { 72void init_Glyph(iGlyph *d, iChar ch) {
65 d->node.key = ch; 73 d->node.key = ch;
74 d->flags = 0;
66 d->glyphIndex = 0; 75 d->glyphIndex = 0;
67 d->font = NULL; 76 d->font = NULL;
68 d->rect[0] = zero_Rect(); 77 d->rect[0] = zero_Rect();
@@ -74,10 +83,23 @@ void deinit_Glyph(iGlyph *d) {
74 iUnused(d); 83 iUnused(d);
75} 84}
76 85
77iChar codepoint_Glyph(const iGlyph *d) { 86static iChar codepoint_Glyph_(const iGlyph *d) {
78 return d->node.key; 87 return d->node.key;
79} 88}
80 89
90iLocalDef iBool isRasterized_Glyph_(const iGlyph *d, int hoff) {
91 return (d->flags & (rasterized0_GlyphFlag << hoff)) != 0;
92}
93
94iLocalDef iBool isFullyRasterized_Glyph_(const iGlyph *d) {
95 return (d->flags & (rasterized0_GlyphFlag | rasterized1_GlyphFlag)) ==
96 (rasterized0_GlyphFlag | rasterized1_GlyphFlag);
97}
98
99iLocalDef void setRasterized_Glyph_(iGlyph *d, int hoff) {
100 d->flags |= rasterized0_GlyphFlag << hoff;
101}
102
81iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) 103iDefineTypeConstructionArgs(Glyph, (iChar ch), ch)
82 104
83/*-----------------------------------------------------------------------------------------------*/ 105/*-----------------------------------------------------------------------------------------------*/
@@ -496,41 +518,41 @@ static iInt2 assignCachePos_Text_(iText *d, iInt2 size) {
496 return assigned; 518 return assigned;
497} 519}
498 520
499static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { 521static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
500 iText *txt = &text_;
501 SDL_Renderer *render = txt->render;
502 SDL_Texture *tex = NULL;
503 SDL_Surface *surface = NULL;
504 iRect *glRect = &glyph->rect[hoff]; 522 iRect *glRect = &glyph->rect[hoff];
505 /* Rasterize the glyph using stbtt. */ { 523 int x0, y0, x1, y1;
506 surface = rasterizeGlyph_Font_(d, glyph->glyphIndex, hoff * 0.5f); 524 stbtt_GetGlyphBitmapBoxSubpixel(
507 if (hoff == 0) { /* hoff==1 uses same `glyph` */ 525 &d->font, glyph->glyphIndex, d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);
508 int adv; 526 glRect->size = init_I2(x1 - x0, y1 - y0);
509 const uint32_t gIndex = glyph->glyphIndex; 527 /* Determine placement in the glyph cache texture, advancing in rows. */
510 stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL); 528 glRect->pos = assignCachePos_Text_(&text_, glRect->size);
511 glyph->advance = d->xScale * adv; 529 glyph->d[hoff] = init_I2(x0, y0);
512 } 530 glyph->d[hoff].y += d->vertOffset;
513 stbtt_GetGlyphBitmapBoxSubpixel(&d->font, 531 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
514 glyph->glyphIndex, 532 int adv;
515 d->xScale, 533 const uint32_t gIndex = glyph->glyphIndex;
516 d->yScale, 534 stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL);
517 hoff * 0.5f, 535 glyph->advance = d->xScale * adv;
518 0.0f,
519 &glyph->d[hoff].x,
520 &glyph->d[hoff].y,
521 NULL,
522 NULL);
523 glyph->d[hoff].y += d->vertOffset;
524 tex = SDL_CreateTextureFromSurface(render, surface);
525 SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
526 glRect->size = init_I2(surface->w, surface->h);
527 } 536 }
537}
538
539static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) {
540 iText * txt = &text_;
541 SDL_Renderer *render = txt->render;
542 SDL_Texture * tex = NULL;
543 SDL_Surface * surface = NULL;
544 iRect * glRect = &glyph->rect[hoff];
545 /* Rasterize the glyph using stbtt. */
546 iAssert(!isRasterized_Glyph_(glyph, hoff));
547 surface = rasterizeGlyph_Font_(d, glyph->glyphIndex, hoff * 0.5f);
548 tex = SDL_CreateTextureFromSurface(render, surface);
549 iAssert(isEqual_I2(glRect->size, init_I2(surface->w, surface->h)));
528 if (tex) { 550 if (tex) {
529 /* Determine placement in the glyph cache texture, advancing in rows. */ 551 SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
530 glRect->pos = assignCachePos_Text_(txt, glRect->size);
531 const SDL_Rect dstRect = sdlRect_(*glRect); 552 const SDL_Rect dstRect = sdlRect_(*glRect);
532 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); 553 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect);
533 SDL_DestroyTexture(tex); 554 SDL_DestroyTexture(tex);
555 setRasterized_Glyph_(glyph, hoff);
534 } 556 }
535 if (surface) { 557 if (surface) {
536 SDL_FreeSurface(surface); 558 SDL_FreeSurface(surface);
@@ -580,29 +602,42 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
580} 602}
581 603
582static const iGlyph *glyph_Font_(iFont *d, iChar ch) { 604static const iGlyph *glyph_Font_(iFont *d, iChar ch) {
605 iGlyph * glyph;
583 uint32_t glyphIndex = 0; 606 uint32_t glyphIndex = 0;
584 /* The glyph may actually come from a different font; look up the right font. */ 607 /* The glyph may actually come from a different font; look up the right font. */
585 iFont *font = characterFont_Font_(d, ch, &glyphIndex); 608 iFont *font = characterFont_Font_(d, ch, &glyphIndex);
586 const void *node = value_Hash(&font->glyphs, ch); 609 void * node = value_Hash(&font->glyphs, ch);
587 if (node) { 610 if (node) {
588 return node; 611 glyph = node;
589 } 612 }
590 iGlyph *glyph = new_Glyph(ch); 613 else {
591 glyph->glyphIndex = glyphIndex; 614 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */
592 glyph->font = font; 615 if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) {
593 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */
594 if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) {
595#if !defined (NDEBUG) 616#if !defined (NDEBUG)
596 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); 617 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout);
597#endif 618#endif
598 resetCache_Text_(&text_); 619 resetCache_Text_(&text_);
620 }
621 glyph = new_Glyph(ch);
622 glyph->glyphIndex = glyphIndex;
623 glyph->font = font;
624 /* New glyphs are always allocated at least. This reserves a position in the cache
625 and updates the glyph metrics. */
626 allocate_Font_(font, glyph, 0);
627 allocate_Font_(font, glyph, 1);
628 insert_Hash(&font->glyphs, &glyph->node);
629 }
630 if (enableRaster_Text_ && !isFullyRasterized_Glyph_(glyph)) {
631 SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render);
632 SDL_SetRenderTarget(text_.render, text_.cache);
633 if (!isRasterized_Glyph_(glyph, 0)) {
634 cache_Font_(font, glyph, 0);
635 }
636 if (!isRasterized_Glyph_(glyph, 1)) {
637 cache_Font_(font, glyph, 1); /* half-pixel offset */
638 }
639 SDL_SetRenderTarget(text_.render, oldTarget);
599 } 640 }
600 SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render);
601 SDL_SetRenderTarget(text_.render, text_.cache);
602 cache_Font_(font, glyph, 0);
603 cache_Font_(font, glyph, 1); /* half-pixel offset */
604 SDL_SetRenderTarget(text_.render, oldTarget);
605 insert_Hash(&font->glyphs, &glyph->node);
606 return glyph; 641 return glyph;
607} 642}
608 643
@@ -693,6 +728,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
693 if (isMonospaced) { 728 if (isMonospaced) {
694 monoAdvance = glyph_Font_(d, 'M')->advance; 729 monoAdvance = glyph_Font_(d, 'M')->advance;
695 } 730 }
731 /* Global flag that allows glyph rasterization. */
732 enableRaster_Text_ = !isMeasuring_(mode);
696 for (const char *chPos = args->text.start; chPos != args->text.end; ) { 733 for (const char *chPos = args->text.start; chPos != args->text.end; ) {
697 iAssert(chPos < args->text.end); 734 iAssert(chPos < args->text.end);
698 const char *currentPos = chPos; 735 const char *currentPos = chPos;