From f18c5e9a259c88f7bc3bbc9cf8afcf805ba00a60 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 13 Mar 2021 19:59:41 +0200 Subject: 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. --- src/app.c | 4 ++ src/app.h | 1 + src/ui/documentwidget.c | 13 ++++ src/ui/inputwidget.c | 10 +-- src/ui/text.c | 157 +++++++++++++++++++++++++++++++++++++++++++----- src/ui/text.h | 2 + 6 files changed, 167 insertions(+), 20 deletions(-) (limited to 'src') 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) { return value_Atomic(&app_.pendingRefresh); } +iBool isFinishedLaunching_App(void) { + return app_.isFinishedLaunching; +} + uint32_t elapsedSinceLastTicker_App(void) { return app_.elapsedSinceLastTicker; } 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); iBool handleCommand_App (const char *cmd); void refresh_App (void); iBool isRefreshPending_App (void); +iBool isFinishedLaunching_App (void); uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ iBool 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) { setRange_String(d->titleUser, urlUser_String(d->mod.url)); } +static void cacheRunGlyphs_(void *data, const iGmRun *run) { + iUnused(data); + if (!isEmpty_Range(&run->text)) { + cache_Text(run->font, run->text); + } +} + +static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { + render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, cacheRunGlyphs_, NULL); +} + static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); if (recent && recent->cachedResponse) { @@ -1026,6 +1037,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { updateSideOpacity_DocumentWidget_(d, iFalse); updateSideIconBuf_DocumentWidget_(d); updateVisible_DocumentWidget_(d); + cacheDocumentGlyphs_DocumentWidget_(d); postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); return iTrue; } @@ -1762,6 +1774,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); clear_String(&d->pendingGotoHeading); } + cacheDocumentGlyphs_DocumentWidget_(d); return iFalse; } 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) { } static void updateBuffered_InputWidget_(iInputWidget *d) { - invalidateBuffered_InputWidget_(d); - iString *visText = visText_InputWidget_(d); - d->buffered = new_TextBuf(d->font, cstr_String(visText)); - delete_String(visText); + if (isFinishedLaunching_App()) { + invalidateBuffered_InputWidget_(d); + iString *visText = visText_InputWidget_(d); + d->buffered = new_TextBuf(d->font, cstr_String(visText)); + delete_String(visText); + } } void 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 */ int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ -static iBool enableRaster_Text_ = iTrue; - enum iGlyphFlag { rasterized0_GlyphFlag = iBit(1), /* zero offset */ rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ @@ -521,12 +519,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl SDL_Surface *surface8 = SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); SDL_SetSurfacePalette(surface8, text_.grayscale); - SDL_PixelFormat *fmt = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); - SDL_Surface *surface = SDL_ConvertSurface(surface8, fmt, 0); - SDL_FreeFormat(fmt); - SDL_FreeSurface(surface8); - stbtt_FreeBitmap(bmp, NULL); - return surface; + return surface8; } iLocalDef SDL_Rect sdlRect_(const iRect rect) { @@ -578,6 +571,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { } } +#if 0 static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { iText * txt = &text_; SDL_Renderer *render = txt->render; @@ -601,6 +595,7 @@ static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { } return isRasterized_Glyph_(glyph, hoff); } +#endif iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { @@ -644,6 +639,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { return font; } +#if 0 static void doRaster_Font_(const iFont *font, iGlyph *glyph) { SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); SDL_SetRenderTarget(text_.render, text_.cache); @@ -655,8 +651,9 @@ static void doRaster_Font_(const iFont *font, iGlyph *glyph) { } SDL_SetRenderTarget(text_.render, oldTarget); } +#endif -static const iGlyph *glyph_Font_(iFont *d, iChar ch) { +static iGlyph *glyph_Font_(iFont *d, iChar ch) { iGlyph * glyph; uint32_t glyphIndex = 0; /* 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) { allocate_Font_(font, glyph, 1); insert_Hash(&font->glyphs, &glyph->node); } - if (enableRaster_Text_ && !isFullyRasterized_Glyph_(glyph)) { - doRaster_Font_(font, glyph); - } return glyph; } @@ -702,6 +696,129 @@ static iChar nextChar_(const char **chPos, const char *end) { return ch; } +iDeclareType(RasterGlyph) + +struct Impl_RasterGlyph { + iGlyph *glyph; + int hoff; + iRect rect; +}; + +void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { + const char * chPos = text.start; + SDL_Surface *buf = NULL; + const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Range(&text), 20)), + d->height * 4 / 3); + int bufX = 0; + iArray * rasters = NULL; + SDL_Texture *oldTarget = NULL; + iBool isTargetChanged = iFalse; + /* We'll flush the buffered rasters periodically until everything is cached. */ + while (chPos < text.end) { + while (chPos < text.end) { + const char *lastPos = chPos; + const iChar ch = nextChar_(&chPos, text.end); + if (ch == 0 || isSpace_Char(ch) || isDefaultIgnorable_Char(ch) || + isFitzpatrickType_Char(ch)) { + continue; + } + const int lastCacheBottom = text_.cacheBottom; + iGlyph *glyph = glyph_Font_(d, ch); + if (text_.cacheBottom < lastCacheBottom) { + /* The cache was reset due to running out of space. We need to restart from + the beginning! */ + chPos = text.start; + bufX = 0; + if (rasters) { + clear_Array(rasters); + } + } + if (!isFullyRasterized_Glyph_(glyph)) { + /* Need to cache this. */ + if (buf == NULL) { + rasters = new_Array(sizeof(iRasterGlyph)); + buf = SDL_CreateRGBSurfaceWithFormat( + 0, bufSize.x, bufSize.y, 8, SDL_PIXELFORMAT_INDEX8); + SDL_SetSurfacePalette(buf, text_.grayscale); + } + SDL_Surface *surfaces[2] = { + !isRasterized_Glyph_(glyph, 0) ? + rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0) : NULL, + !isRasterized_Glyph_(glyph, 1) ? + rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0.5f) : NULL + }; + iBool outOfSpace = iFalse; + iForIndices(i, surfaces) { + if (surfaces[i]) { + const int w = surfaces[i]->w; + const int h = surfaces[i]->h; + if (bufX + w <= bufSize.x) { + SDL_BlitSurface(surfaces[i], + NULL, + buf, + &(SDL_Rect){ bufX, 0, w, h }); + pushBack_Array(rasters, + &(iRasterGlyph){ glyph, i, init_Rect(bufX, 0, w, h) }); + bufX += w; + } + else { + outOfSpace = iTrue; + break; + } + } + } + iForIndices(i, surfaces) { + if (surfaces[i]) { + free(surfaces[i]->pixels); + SDL_FreeSurface(surfaces[i]); + } + } + if (outOfSpace) { + chPos = lastPos; + break; + } + } + } + /* Finished or the buffer is full, copy the glyphs to the cache texture. */ + if (!isEmpty_Array(rasters)) { + SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); + SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); + if (!isTargetChanged) { + isTargetChanged = iTrue; + oldTarget = SDL_GetRenderTarget(text_.render); + SDL_SetRenderTarget(text_.render, text_.cache); + } + //printf("copying %d rasters\n", size_Array(rasters)); fflush(stdout); + iConstForEach(Array, i, rasters) { + const iRasterGlyph *rg = i.value; +// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); + const iRect *glRect = &rg->glyph->rect[rg->hoff]; + SDL_RenderCopy(text_.render, + bufTex, + (const SDL_Rect *) &rg->rect, + (const SDL_Rect *) glRect); + setRasterized_Glyph_(rg->glyph, rg->hoff); + } + SDL_DestroyTexture(bufTex); + /* Resume with an empty buffer. */ + clear_Array(rasters); + bufX = 0; + } + else { + iAssert(chPos >= text.end); + } + } + if (rasters) { + delete_Array(rasters); + } + if (buf) { + SDL_FreeSurface(buf); + } + if (isTargetChanged) { + SDL_SetRenderTarget(text_.render, oldTarget); + } +} + enum iRunMode { measure_RunMode = 0, draw_RunMode = 1, @@ -777,8 +894,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (isMonospaced) { monoAdvance = glyph_Font_(d, 'M')->advance; } - /* Global flag that allows glyph rasterization. */ - enableRaster_Text_ = !isMeasuring_(mode); + /* Text rendering is not very straightforward! Let's dive in... */ for (const char *chPos = args->text.start; chPos != args->text.end; ) { iAssert(chPos < args->text.end); const char *currentPos = chPos; @@ -880,6 +996,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { const iGlyph *glyph = glyph_Font_(d, ch); int x1 = iMax(xpos, xposExtend); const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; + if (!isRasterized_Glyph_(glyph, hoff)) { + /* Need to pause here and make sure all glyphs have been cached in the text. */ + cacheTextGlyphs_Font_(d, args->text); + } int x2 = x1 + glyph->rect[hoff].size.x; /* Out of the allotted space? */ if (args->xposLimit > 0 && x2 > args->xposLimit) { @@ -999,6 +1119,10 @@ iInt2 measure_Text(int fontId, const char *text) { return measureRange_Text(fontId, range_CStr(text)); } +void cache_Text(int fontId, iRangecc text) { + cacheTextGlyphs_Font_(font_Text_(fontId), text); +} + static int runFlagsFromId_(enum iFontId fontId) { int runFlags = 0; if (fontId & alwaysVariableFlag_FontId) { @@ -1060,10 +1184,11 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) { } static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { - iText *d = &text_; + iText *d = &text_; + iFont *font = font_Text_(fontId); const iColor clr = get_Color(color & mask_ColorId); SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); - run_Font_(font_Text_(fontId), + run_Font_(font, &(iRunArgs){ .mode = draw_RunMode | (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | 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 { void setOpacity_Text (float opacity); +void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ + void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); -- cgit v1.2.3