summaryrefslogtreecommitdiff
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
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.
-rw-r--r--src/app.c4
-rw-r--r--src/app.h1
-rw-r--r--src/ui/documentwidget.c13
-rw-r--r--src/ui/inputwidget.c10
-rw-r--r--src/ui/text.c157
-rw-r--r--src/ui/text.h2
6 files changed, 167 insertions, 20 deletions
diff --git a/src/app.c b/src/app.c
index f1ba2432..6261c74b 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1050,6 +1050,10 @@ iBool isRefreshPending_App(void) {
1050 return value_Atomic(&app_.pendingRefresh); 1050 return value_Atomic(&app_.pendingRefresh);
1051} 1051}
1052 1052
1053iBool isFinishedLaunching_App(void) {
1054 return app_.isFinishedLaunching;
1055}
1056
1053uint32_t elapsedSinceLastTicker_App(void) { 1057uint32_t elapsedSinceLastTicker_App(void) {
1054 return app_.elapsedSinceLastTicker; 1058 return app_.elapsedSinceLastTicker;
1055} 1059}
diff --git a/src/app.h b/src/app.h
index 015f5a3e..bdb0e22f 100644
--- a/src/app.h
+++ b/src/app.h
@@ -68,6 +68,7 @@ void processEvents_App (enum iAppEventMode mode);
68iBool handleCommand_App (const char *cmd); 68iBool handleCommand_App (const char *cmd);
69void refresh_App (void); 69void refresh_App (void);
70iBool isRefreshPending_App (void); 70iBool isRefreshPending_App (void);
71iBool isFinishedLaunching_App (void);
71uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ 72uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */
72 73
73iBool isLandscape_App (void); 74iBool isLandscape_App (void);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index a7666865..9111b546 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1004,6 +1004,17 @@ static void parseUser_DocumentWidget_(iDocumentWidget *d) {
1004 setRange_String(d->titleUser, urlUser_String(d->mod.url)); 1004 setRange_String(d->titleUser, urlUser_String(d->mod.url));
1005} 1005}
1006 1006
1007static void cacheRunGlyphs_(void *data, const iGmRun *run) {
1008 iUnused(data);
1009 if (!isEmpty_Range(&run->text)) {
1010 cache_Text(run->font, run->text);
1011 }
1012}
1013
1014static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1015 render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, cacheRunGlyphs_, NULL);
1016}
1017
1007static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1018static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1008 const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); 1019 const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url);
1009 if (recent && recent->cachedResponse) { 1020 if (recent && recent->cachedResponse) {
@@ -1026,6 +1037,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1026 updateSideOpacity_DocumentWidget_(d, iFalse); 1037 updateSideOpacity_DocumentWidget_(d, iFalse);
1027 updateSideIconBuf_DocumentWidget_(d); 1038 updateSideIconBuf_DocumentWidget_(d);
1028 updateVisible_DocumentWidget_(d); 1039 updateVisible_DocumentWidget_(d);
1040 cacheDocumentGlyphs_DocumentWidget_(d);
1029 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1041 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1030 return iTrue; 1042 return iTrue;
1031 } 1043 }
@@ -1762,6 +1774,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1762 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); 1774 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading));
1763 clear_String(&d->pendingGotoHeading); 1775 clear_String(&d->pendingGotoHeading);
1764 } 1776 }
1777 cacheDocumentGlyphs_DocumentWidget_(d);
1765 return iFalse; 1778 return iFalse;
1766 } 1779 }
1767 else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) { 1780 else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 27f0217c..06e6373e 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -256,10 +256,12 @@ static iString *visText_InputWidget_(const iInputWidget *d) {
256} 256}
257 257
258static void updateBuffered_InputWidget_(iInputWidget *d) { 258static void updateBuffered_InputWidget_(iInputWidget *d) {
259 invalidateBuffered_InputWidget_(d); 259 if (isFinishedLaunching_App()) {
260 iString *visText = visText_InputWidget_(d); 260 invalidateBuffered_InputWidget_(d);
261 d->buffered = new_TextBuf(d->font, cstr_String(visText)); 261 iString *visText = visText_InputWidget_(d);
262 delete_String(visText); 262 d->buffered = new_TextBuf(d->font, cstr_String(visText));
263 delete_String(visText);
264 }
263} 265}
264 266
265void setText_InputWidget(iInputWidget *d, const iString *text) { 267void setText_InputWidget(iInputWidget *d, const iString *text) {
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),
diff --git a/src/ui/text.h b/src/ui/text.h
index 897dfed0..606096b6 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -192,6 +192,8 @@ enum iAlignment {
192 192
193void setOpacity_Text (float opacity); 193void setOpacity_Text (float opacity);
194 194
195void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */
196
195void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); 197void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...);
196void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); 198void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...);
197void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); 199void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...);