diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-10 16:23:03 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-10 16:23:03 +0200 |
commit | 2f78fa7f85c4384d5b013a136f02d107c70f33c0 (patch) | |
tree | 8dbdc67ec44a725ee47187a4f66286e2d241eaba /src/ui | |
parent | 0a4715c02f7923b1085ca171988bd17cd841e1c3 (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')
-rw-r--r-- | src/ui/text.c | 127 |
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 */ | |||
52 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ | 52 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ |
53 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ | 53 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ |
54 | 54 | ||
55 | static iBool enableRaster_Text_ = iTrue; | ||
56 | |||
57 | enum iGlyphFlag { | ||
58 | rasterized0_GlyphFlag = iBit(1), /* zero offset */ | ||
59 | rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ | ||
60 | }; | ||
61 | |||
55 | struct Impl_Glyph { | 62 | struct 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 | ||
64 | void init_Glyph(iGlyph *d, iChar ch) { | 72 | void 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 | ||
77 | iChar codepoint_Glyph(const iGlyph *d) { | 86 | static iChar codepoint_Glyph_(const iGlyph *d) { |
78 | return d->node.key; | 87 | return d->node.key; |
79 | } | 88 | } |
80 | 89 | ||
90 | iLocalDef iBool isRasterized_Glyph_(const iGlyph *d, int hoff) { | ||
91 | return (d->flags & (rasterized0_GlyphFlag << hoff)) != 0; | ||
92 | } | ||
93 | |||
94 | iLocalDef iBool isFullyRasterized_Glyph_(const iGlyph *d) { | ||
95 | return (d->flags & (rasterized0_GlyphFlag | rasterized1_GlyphFlag)) == | ||
96 | (rasterized0_GlyphFlag | rasterized1_GlyphFlag); | ||
97 | } | ||
98 | |||
99 | iLocalDef void setRasterized_Glyph_(iGlyph *d, int hoff) { | ||
100 | d->flags |= rasterized0_GlyphFlag << hoff; | ||
101 | } | ||
102 | |||
81 | iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) | 103 | iDefineTypeConstructionArgs(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 | ||
499 | static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | 521 | static 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 | |||
539 | static 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 | ||
582 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | 604 | static 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; |