summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-03-13 19:59:41 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-03-13 19:59:41 +0200
commitf18c5e9a259c88f7bc3bbc9cf8afcf805ba00a60 (patch)
treee7838b803cad78eb1b0c5c0d15b4f6d639b0d400 /src/ui/text.c
parent379f8cea2044854be02622a561dd3c9cd9fb06c2 (diff)
Text: Separate glyph caching procedure
Previously glyph caching was done during text rendering, but that would mean lots of swapping between rendering stuff to the cache and then immediately afterward rendering to the display. There would be a swap per each new glyph. Now the entire document's glyphs are precached at once when a request is finished. Glyphs are also cached in larger batches when new text needs to be drawn.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c157
1 files changed, 141 insertions, 16 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 36e98e11..4a39bf72 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -53,8 +53,6 @@ int gap_Text; /* cf. gap_UI in metrics.h */
53int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ 53int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */
54int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ 54int enableKerning_Text = iTrue; /* looking up kern pairs is slow */
55 55
56static iBool enableRaster_Text_ = iTrue;
57
58enum iGlyphFlag { 56enum iGlyphFlag {
59 rasterized0_GlyphFlag = iBit(1), /* zero offset */ 57 rasterized0_GlyphFlag = iBit(1), /* zero offset */
60 rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ 58 rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */
@@ -521,12 +519,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl
521 SDL_Surface *surface8 = 519 SDL_Surface *surface8 =
522 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); 520 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
523 SDL_SetSurfacePalette(surface8, text_.grayscale); 521 SDL_SetSurfacePalette(surface8, text_.grayscale);
524 SDL_PixelFormat *fmt = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); 522 return surface8;
525 SDL_Surface *surface = SDL_ConvertSurface(surface8, fmt, 0);
526 SDL_FreeFormat(fmt);
527 SDL_FreeSurface(surface8);
528 stbtt_FreeBitmap(bmp, NULL);
529 return surface;
530} 523}
531 524
532iLocalDef SDL_Rect sdlRect_(const iRect rect) { 525iLocalDef SDL_Rect sdlRect_(const iRect rect) {
@@ -578,6 +571,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
578 } 571 }
579} 572}
580 573
574#if 0
581static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { 575static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) {
582 iText * txt = &text_; 576 iText * txt = &text_;
583 SDL_Renderer *render = txt->render; 577 SDL_Renderer *render = txt->render;
@@ -601,6 +595,7 @@ static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) {
601 } 595 }
602 return isRasterized_Glyph_(glyph, hoff); 596 return isRasterized_Glyph_(glyph, hoff);
603} 597}
598#endif
604 599
605iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { 600iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
606 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 601 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
@@ -644,6 +639,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
644 return font; 639 return font;
645} 640}
646 641
642#if 0
647static void doRaster_Font_(const iFont *font, iGlyph *glyph) { 643static void doRaster_Font_(const iFont *font, iGlyph *glyph) {
648 SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); 644 SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render);
649 SDL_SetRenderTarget(text_.render, text_.cache); 645 SDL_SetRenderTarget(text_.render, text_.cache);
@@ -655,8 +651,9 @@ static void doRaster_Font_(const iFont *font, iGlyph *glyph) {
655 } 651 }
656 SDL_SetRenderTarget(text_.render, oldTarget); 652 SDL_SetRenderTarget(text_.render, oldTarget);
657} 653}
654#endif
658 655
659static const iGlyph *glyph_Font_(iFont *d, iChar ch) { 656static iGlyph *glyph_Font_(iFont *d, iChar ch) {
660 iGlyph * glyph; 657 iGlyph * glyph;
661 uint32_t glyphIndex = 0; 658 uint32_t glyphIndex = 0;
662 /* The glyph may actually come from a different font; look up the right font. */ 659 /* The glyph may actually come from a different font; look up the right font. */
@@ -682,9 +679,6 @@ static const iGlyph *glyph_Font_(iFont *d, iChar ch) {
682 allocate_Font_(font, glyph, 1); 679 allocate_Font_(font, glyph, 1);
683 insert_Hash(&font->glyphs, &glyph->node); 680 insert_Hash(&font->glyphs, &glyph->node);
684 } 681 }
685 if (enableRaster_Text_ && !isFullyRasterized_Glyph_(glyph)) {
686 doRaster_Font_(font, glyph);
687 }
688 return glyph; 682 return glyph;
689} 683}
690 684
@@ -702,6 +696,129 @@ static iChar nextChar_(const char **chPos, const char *end) {
702 return ch; 696 return ch;
703} 697}
704 698
699iDeclareType(RasterGlyph)
700
701struct Impl_RasterGlyph {
702 iGlyph *glyph;
703 int hoff;
704 iRect rect;
705};
706
707void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
708 const char * chPos = text.start;
709 SDL_Surface *buf = NULL;
710 const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Range(&text), 20)),
711 d->height * 4 / 3);
712 int bufX = 0;
713 iArray * rasters = NULL;
714 SDL_Texture *oldTarget = NULL;
715 iBool isTargetChanged = iFalse;
716 /* We'll flush the buffered rasters periodically until everything is cached. */
717 while (chPos < text.end) {
718 while (chPos < text.end) {
719 const char *lastPos = chPos;
720 const iChar ch = nextChar_(&chPos, text.end);
721 if (ch == 0 || isSpace_Char(ch) || isDefaultIgnorable_Char(ch) ||
722 isFitzpatrickType_Char(ch)) {
723 continue;
724 }
725 const int lastCacheBottom = text_.cacheBottom;
726 iGlyph *glyph = glyph_Font_(d, ch);
727 if (text_.cacheBottom < lastCacheBottom) {
728 /* The cache was reset due to running out of space. We need to restart from
729 the beginning! */
730 chPos = text.start;
731 bufX = 0;
732 if (rasters) {
733 clear_Array(rasters);
734 }
735 }
736 if (!isFullyRasterized_Glyph_(glyph)) {
737 /* Need to cache this. */
738 if (buf == NULL) {
739 rasters = new_Array(sizeof(iRasterGlyph));
740 buf = SDL_CreateRGBSurfaceWithFormat(
741 0, bufSize.x, bufSize.y, 8, SDL_PIXELFORMAT_INDEX8);
742 SDL_SetSurfacePalette(buf, text_.grayscale);
743 }
744 SDL_Surface *surfaces[2] = {
745 !isRasterized_Glyph_(glyph, 0) ?
746 rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0) : NULL,
747 !isRasterized_Glyph_(glyph, 1) ?
748 rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0.5f) : NULL
749 };
750 iBool outOfSpace = iFalse;
751 iForIndices(i, surfaces) {
752 if (surfaces[i]) {
753 const int w = surfaces[i]->w;
754 const int h = surfaces[i]->h;
755 if (bufX + w <= bufSize.x) {
756 SDL_BlitSurface(surfaces[i],
757 NULL,
758 buf,
759 &(SDL_Rect){ bufX, 0, w, h });
760 pushBack_Array(rasters,
761 &(iRasterGlyph){ glyph, i, init_Rect(bufX, 0, w, h) });
762 bufX += w;
763 }
764 else {
765 outOfSpace = iTrue;
766 break;
767 }
768 }
769 }
770 iForIndices(i, surfaces) {
771 if (surfaces[i]) {
772 free(surfaces[i]->pixels);
773 SDL_FreeSurface(surfaces[i]);
774 }
775 }
776 if (outOfSpace) {
777 chPos = lastPos;
778 break;
779 }
780 }
781 }
782 /* Finished or the buffer is full, copy the glyphs to the cache texture. */
783 if (!isEmpty_Array(rasters)) {
784 SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf);
785 SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE);
786 if (!isTargetChanged) {
787 isTargetChanged = iTrue;
788 oldTarget = SDL_GetRenderTarget(text_.render);
789 SDL_SetRenderTarget(text_.render, text_.cache);
790 }
791 //printf("copying %d rasters\n", size_Array(rasters)); fflush(stdout);
792 iConstForEach(Array, i, rasters) {
793 const iRasterGlyph *rg = i.value;
794// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size));
795 const iRect *glRect = &rg->glyph->rect[rg->hoff];
796 SDL_RenderCopy(text_.render,
797 bufTex,
798 (const SDL_Rect *) &rg->rect,
799 (const SDL_Rect *) glRect);
800 setRasterized_Glyph_(rg->glyph, rg->hoff);
801 }
802 SDL_DestroyTexture(bufTex);
803 /* Resume with an empty buffer. */
804 clear_Array(rasters);
805 bufX = 0;
806 }
807 else {
808 iAssert(chPos >= text.end);
809 }
810 }
811 if (rasters) {
812 delete_Array(rasters);
813 }
814 if (buf) {
815 SDL_FreeSurface(buf);
816 }
817 if (isTargetChanged) {
818 SDL_SetRenderTarget(text_.render, oldTarget);
819 }
820}
821
705enum iRunMode { 822enum iRunMode {
706 measure_RunMode = 0, 823 measure_RunMode = 0,
707 draw_RunMode = 1, 824 draw_RunMode = 1,
@@ -777,8 +894,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
777 if (isMonospaced) { 894 if (isMonospaced) {
778 monoAdvance = glyph_Font_(d, 'M')->advance; 895 monoAdvance = glyph_Font_(d, 'M')->advance;
779 } 896 }
780 /* Global flag that allows glyph rasterization. */ 897 /* Text rendering is not very straightforward! Let's dive in... */
781 enableRaster_Text_ = !isMeasuring_(mode);
782 for (const char *chPos = args->text.start; chPos != args->text.end; ) { 898 for (const char *chPos = args->text.start; chPos != args->text.end; ) {
783 iAssert(chPos < args->text.end); 899 iAssert(chPos < args->text.end);
784 const char *currentPos = chPos; 900 const char *currentPos = chPos;
@@ -880,6 +996,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
880 const iGlyph *glyph = glyph_Font_(d, ch); 996 const iGlyph *glyph = glyph_Font_(d, ch);
881 int x1 = iMax(xpos, xposExtend); 997 int x1 = iMax(xpos, xposExtend);
882 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; 998 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0;
999 if (!isRasterized_Glyph_(glyph, hoff)) {
1000 /* Need to pause here and make sure all glyphs have been cached in the text. */
1001 cacheTextGlyphs_Font_(d, args->text);
1002 }
883 int x2 = x1 + glyph->rect[hoff].size.x; 1003 int x2 = x1 + glyph->rect[hoff].size.x;
884 /* Out of the allotted space? */ 1004 /* Out of the allotted space? */
885 if (args->xposLimit > 0 && x2 > args->xposLimit) { 1005 if (args->xposLimit > 0 && x2 > args->xposLimit) {
@@ -999,6 +1119,10 @@ iInt2 measure_Text(int fontId, const char *text) {
999 return measureRange_Text(fontId, range_CStr(text)); 1119 return measureRange_Text(fontId, range_CStr(text));
1000} 1120}
1001 1121
1122void cache_Text(int fontId, iRangecc text) {
1123 cacheTextGlyphs_Font_(font_Text_(fontId), text);
1124}
1125
1002static int runFlagsFromId_(enum iFontId fontId) { 1126static int runFlagsFromId_(enum iFontId fontId) {
1003 int runFlags = 0; 1127 int runFlags = 0;
1004 if (fontId & alwaysVariableFlag_FontId) { 1128 if (fontId & alwaysVariableFlag_FontId) {
@@ -1060,10 +1184,11 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) {
1060} 1184}
1061 1185
1062static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { 1186static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) {
1063 iText *d = &text_; 1187 iText *d = &text_;
1188 iFont *font = font_Text_(fontId);
1064 const iColor clr = get_Color(color & mask_ColorId); 1189 const iColor clr = get_Color(color & mask_ColorId);
1065 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); 1190 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);
1066 run_Font_(font_Text_(fontId), 1191 run_Font_(font,
1067 &(iRunArgs){ .mode = draw_RunMode | 1192 &(iRunArgs){ .mode = draw_RunMode |
1068 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | 1193 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
1069 runFlagsFromId_(fontId), 1194 runFlagsFromId_(fontId),