diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-13 19:59:41 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-13 19:59:41 +0200 |
commit | f18c5e9a259c88f7bc3bbc9cf8afcf805ba00a60 (patch) | |
tree | e7838b803cad78eb1b0c5c0d15b4f6d639b0d400 /src/ui/text.c | |
parent | 379f8cea2044854be02622a561dd3c9cd9fb06c2 (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.c | 157 |
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 */ | |||
53 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ | 53 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ |
54 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ | 54 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ |
55 | 55 | ||
56 | static iBool enableRaster_Text_ = iTrue; | ||
57 | |||
58 | enum iGlyphFlag { | 56 | enum 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 | ||
532 | iLocalDef SDL_Rect sdlRect_(const iRect rect) { | 525 | iLocalDef 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 | ||
581 | static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { | 575 | static 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 | ||
605 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | 600 | iLocalDef 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 | ||
647 | static void doRaster_Font_(const iFont *font, iGlyph *glyph) { | 643 | static 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 | ||
659 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | 656 | static 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 | ||
699 | iDeclareType(RasterGlyph) | ||
700 | |||
701 | struct Impl_RasterGlyph { | ||
702 | iGlyph *glyph; | ||
703 | int hoff; | ||
704 | iRect rect; | ||
705 | }; | ||
706 | |||
707 | void 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 | |||
705 | enum iRunMode { | 822 | enum 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 | ||
1122 | void cache_Text(int fontId, iRangecc text) { | ||
1123 | cacheTextGlyphs_Font_(font_Text_(fontId), text); | ||
1124 | } | ||
1125 | |||
1002 | static int runFlagsFromId_(enum iFontId fontId) { | 1126 | static 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 | ||
1062 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | 1186 | static 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), |