From 212ba7c03e1becd76c52bbc2b25843bb4998d323 Mon Sep 17 00:00:00 2001 From: TheGag96 Date: Sat, 7 Mar 2026 21:14:49 -0600 Subject: [PATCH 1/4] Create a cache around `fontCalcGlyphPos` for ASCII characters ...since this function is pretty slow. This applies both to the system font and user-loaded fonts. This is cache is now used downstream of `C2D_TextFontParseLine`, which should improve its performance. --- include/c2d/font.h | 10 ++++++++++ source/base.c | 2 +- source/font.c | 27 +++++++++++++++++++++++++++ source/internal.h | 5 +++++ source/text.c | 9 ++++++++- 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/include/c2d/font.h b/include/c2d/font.h index 130aa8e..b12dd35 100644 --- a/include/c2d/font.h +++ b/include/c2d/font.h @@ -86,6 +86,16 @@ charWidthInfo_s* C2D_FontGetCharWidthInfo(C2D_Font font, int glyphIndex); */ void C2D_FontCalcGlyphPos(C2D_Font font, fontGlyphPos_s* out, int glyphIndex, u32 flags, float scaleX, float scaleY); +/** @brief Calculate glyph position of a given Unicode codepoint. Uses a cached value when the codepoint is ASCII, flags are 0, and scales are 1. + * @param[in] font Font to read from, or NULL for system font + * @param[out] out Glyph position + * @param[in] codepoint The Unicode codepoint of the glyph + * @param[in] flags Misc flags + * @param[in] scaleX Size to scale in X + * @param[in] scaleY Size to scale in Y + */ +void C2D_FontCalcGlyphPosFromCodePoint(C2D_Font font, fontGlyphPos_s* out, u32 codepoint, u32 flags, float scaleX, float scaleY); + /** @brief Get the font info structure associated with the font * @param[in] font Font to read from, or NULL for the system font * @returns FINF associated with the font diff --git a/source/base.c b/source/base.c index 9e3554c..795a88a 100755 --- a/source/base.c +++ b/source/base.c @@ -575,7 +575,7 @@ void C2Di_FlushVtxBuf(void) ctx->idxBufLastPos = ctx->idxBufPos; } -void C2Di_Update(void) +void C2Di_Update() { C2Di_Context* ctx = C2Di_GetContext(); u32 flags = ctx->flags & C2DiF_DirtyAny; diff --git a/source/font.c b/source/font.c index 5d1b61d..1839a9c 100644 --- a/source/font.c +++ b/source/font.c @@ -4,6 +4,8 @@ #include "internal.h" #include +fontGlyphPos_s g_systemFontASCIICache[128]; + C2D_Font C2D_FontLoad(const char* filename) { FILE* f = fopen(filename, "rb"); @@ -51,6 +53,11 @@ static C2D_Font C2Di_PostLoadFont(C2D_Font font) tex->border = 0; tex->lodParam = 0; } + + for (i = 0; i < NUM_ASCII_CHARACTERS; i++) + { + fontCalcGlyphPos(&font->asciiCache[i], font->cfnt, fontGlyphIndexFromCodePoint(font->cfnt, i), 0, 1.0, 1.0); + } } return font; } @@ -243,6 +250,26 @@ void C2D_FontCalcGlyphPos(C2D_Font font, fontGlyphPos_s* out, int glyphIndex, u3 fontCalcGlyphPos(out, font->cfnt, glyphIndex, flags, scaleX, scaleY); } +void C2D_FontCalcGlyphPosFromCodePoint(C2D_Font font, fontGlyphPos_s* out, u32 codepoint, u32 flags, float scaleX, float scaleY) +{ + // Building glyph positions is pretty expensive, but we could just store the results for plain ASCII. + if (codepoint < NUM_ASCII_CHARACTERS && flags == 0 && scaleX == 1 && scaleY == 1) + { + if (font) + { + *out = font->asciiCache[codepoint]; + } + else + { + *out = g_systemFontASCIICache[codepoint]; + } + } + else + { + C2D_FontCalcGlyphPos(font, out, C2D_FontGlyphIndexFromCodePoint(font, codepoint), 0, 1.0f, 1.0f); + } +} + FINF_s* C2D_FontGetInfo(C2D_Font font) { if (!font) diff --git a/source/internal.h b/source/internal.h index 42bd09d..86f2f11 100644 --- a/source/internal.h +++ b/source/internal.h @@ -1,5 +1,6 @@ #pragma once #include +#include <3ds/font.h> typedef struct { @@ -77,6 +78,7 @@ struct C2D_Font_s CFNT_s* cfnt; C3D_Tex* glyphSheets; float textScale; + fontGlyphPos_s asciiCache[128]; }; static inline C2Di_Context* C2Di_GetContext(void) @@ -117,3 +119,6 @@ void C2Di_AppendQuad(void); void C2Di_AppendVtx(float x, float y, float z, float u, float v, float ptx, float pty, u32 color); void C2Di_FlushVtxBuf(void); void C2Di_Update(void); + +#define NUM_ASCII_CHARACTERS 128 +extern fontGlyphPos_s g_systemFontASCIICache[NUM_ASCII_CHARACTERS]; diff --git a/source/text.c b/source/text.c index ab419ff..1cf78c0 100644 --- a/source/text.c +++ b/source/text.c @@ -89,6 +89,13 @@ static void C2Di_TextEnsureLoad(void) tex->border = 0; tex->lodParam = 0; } + + // Initialize system font ASCII cache for C2D_FontCalcGlyphPosFromCodePoint + for (int i = 0; i < NUM_ASCII_CHARACTERS; i++) + { + // This will readjust glyph UVs to account for being a part of the combined texture. + C2D_FontCalcGlyphPos(NULL, &g_systemFontASCIICache[i], fontGlyphIndexFromCodePoint(font, i), 0, 1.0, 1.0); + } } C2D_TextBuf C2D_TextBufNew(size_t maxGlyphs) @@ -161,7 +168,7 @@ const char* C2D_TextFontParseLine(C2D_Text* text, C2D_Font font, C2D_TextBuf buf p += units; fontGlyphPos_s glyphData; - C2D_FontCalcGlyphPos(font, &glyphData, C2D_FontGlyphIndexFromCodePoint(font, code), 0, 1.0f, 1.0f); + C2D_FontCalcGlyphPosFromCodePoint(font, &glyphData, code, 0, 1.0f, 1.0f); if (glyphData.width > 0.0f) { C2Di_Glyph* glyph = &buf->glyphs[buf->glyphCount++]; From 116f09e355198c626a90f4688bd41f1828dc7571 Mon Sep 17 00:00:00 2001 From: TheGag96 Date: Sat, 7 Mar 2026 21:08:28 -0600 Subject: [PATCH 2/4] Improve text rendering performance again by treating font sheets as combined The way TGLP_s is set up, all of a font's texture sheets are adjacent in memory and have the same size. We can reinterpet the memory to describe a smaller set of much taller textures if we'd like. If we choose the right size, we can get all of the ASCII glyphs under a single texture, which will massively improve performance by reducing texture swaps within a piece of all-English text down to 0! We don't need any extra linear allocating to do this! The coalescing will be applied to all characters / glyph sheets up until the last `glyphInfo->nSheets % 32` sheets. This means that there are more operations per glyph being done in `C2D_FontCalcGlyphPos`, but this is probably offset by the savings from not switching textures as often. And, this won't matter for English text, which has these results cached. --- source/font.c | 19 ++++++++++++++++++ source/internal.h | 3 +++ source/text.c | 49 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/source/font.c b/source/font.c index 1839a9c..c0dba60 100644 --- a/source/font.c +++ b/source/font.c @@ -5,6 +5,7 @@ #include fontGlyphPos_s g_systemFontASCIICache[128]; +u32 g_numFontSheetsCombined; C2D_Font C2D_FontLoad(const char* filename) { @@ -245,9 +246,27 @@ charWidthInfo_s* C2D_FontGetCharWidthInfo(C2D_Font font, int glyphIndex) void C2D_FontCalcGlyphPos(C2D_Font font, fontGlyphPos_s* out, int glyphIndex, u32 flags, float scaleX, float scaleY) { if (!font) + { fontCalcGlyphPos(out, fontGetSystemFont(), glyphIndex, flags, scaleX, scaleY); + + if (out->sheetIndex < g_numFontSheetsCombined) + { + u32 indexWithinBigSheet = out->sheetIndex % SHEETS_PER_BIG_SHEET; + out->sheetIndex /= SHEETS_PER_BIG_SHEET; + + // Readjust glyph UVs to account for being a part of the combined texture. + out->texcoord.top = (out->texcoord.top + (SHEETS_PER_BIG_SHEET - indexWithinBigSheet - 1)) / (float) SHEETS_PER_BIG_SHEET; + out->texcoord.bottom = (out->texcoord.bottom + (SHEETS_PER_BIG_SHEET - indexWithinBigSheet - 1)) / (float) SHEETS_PER_BIG_SHEET; + } + else + { + out->sheetIndex = out->sheetIndex - g_numFontSheetsCombined + g_numFontSheetsCombined / SHEETS_PER_BIG_SHEET; + } + } else + { fontCalcGlyphPos(out, font->cfnt, glyphIndex, flags, scaleX, scaleY); + } } void C2D_FontCalcGlyphPosFromCodePoint(C2D_Font font, fontGlyphPos_s* out, u32 codepoint, u32 flags, float scaleX, float scaleY) diff --git a/source/internal.h b/source/internal.h index 86f2f11..f530ba6 100644 --- a/source/internal.h +++ b/source/internal.h @@ -122,3 +122,6 @@ void C2Di_Update(void); #define NUM_ASCII_CHARACTERS 128 extern fontGlyphPos_s g_systemFontASCIICache[NUM_ASCII_CHARACTERS]; + +#define SHEETS_PER_BIG_SHEET 32 +extern u32 g_numFontSheetsCombined; \ No newline at end of file diff --git a/source/text.c b/source/text.c index 1cf78c0..6e88f22 100644 --- a/source/text.c +++ b/source/text.c @@ -57,6 +57,19 @@ static int C2Di_GlyphComp(const void* _g1, const void* _g2) return ret; } +static void fillSheet(C3D_Tex *tex, void *data, TGLP_s *glyphInfo) +{ + tex->data = data; + tex->fmt = glyphInfo->sheetFmt; + tex->size = glyphInfo->sheetSize; + tex->width = glyphInfo->sheetWidth; + tex->height = glyphInfo->sheetHeight; + tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) + | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); + tex->border = 0; + tex->lodParam = 0; +} + static void C2Di_TextEnsureLoad(void) { // Skip if already loaded @@ -70,24 +83,32 @@ static void C2Di_TextEnsureLoad(void) // Load the glyph texture sheets CFNT_s* font = fontGetSystemFont(); TGLP_s* glyphInfo = fontGetGlyphInfo(font); - s_glyphSheets = malloc(sizeof(C3D_Tex)*glyphInfo->nSheets); - s_textScale = 30.0f / glyphInfo->cellHeight; + + // The way TGLP_s is set up, all of a font's texture sheets are adjacent in memory and have the same size. We can + // reinterpet the memory to describe a smaller set of much taller textures if we'd like. If we choose the right size, + // we can get all of the ASCII glyphs under a single texture, which will massively improve performance by reducing + // texture swaps within a piece of all-English text down to 0! We don't need any extra linear allocating to do this! + u32 numSheetsBig = glyphInfo->nSheets / SHEETS_PER_BIG_SHEET; + u32 numSheetsSmall = glyphInfo->nSheets % SHEETS_PER_BIG_SHEET; + u32 numSheetsTotal = numSheetsBig + numSheetsSmall; + g_numFontSheetsCombined = glyphInfo->nSheets - numSheetsSmall; + + s_glyphSheets = malloc(sizeof(C3D_Tex)*numSheetsTotal); if (!s_glyphSheets) svcBreak(USERBREAK_PANIC); - - int i; - for (i = 0; i < glyphInfo->nSheets; i ++) + memset(s_glyphSheets, 0, sizeof(sizeof(C3D_Tex)*numSheetsTotal)); + s_textScale = 30.0f / glyphInfo->cellHeight; + for (u32 i = 0; i < numSheetsBig; i++) { C3D_Tex* tex = &s_glyphSheets[i]; - tex->data = fontGetGlyphSheetTex(font, i); - tex->fmt = glyphInfo->sheetFmt; - tex->size = glyphInfo->sheetSize; - tex->width = glyphInfo->sheetWidth; - tex->height = glyphInfo->sheetHeight; - tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) - | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER); - tex->border = 0; - tex->lodParam = 0; + fillSheet(tex, fontGetGlyphSheetTex(font, i * SHEETS_PER_BIG_SHEET), glyphInfo); + tex->height = (uint16_t) (tex->height * SHEETS_PER_BIG_SHEET); + tex->size = tex->size * SHEETS_PER_BIG_SHEET; + } + + for (u32 i = 0; i < numSheetsSmall; i++) + { + fillSheet(&s_glyphSheets[numSheetsBig + i], fontGetGlyphSheetTex(font, numSheetsBig * SHEETS_PER_BIG_SHEET + i), glyphInfo); } // Initialize system font ASCII cache for C2D_FontCalcGlyphPosFromCodePoint From 52f0cbcbd34fce7660c6729374c73e1a6cd0fcbb Mon Sep 17 00:00:00 2001 From: TheGag96 Date: Sun, 8 Mar 2026 21:48:54 -0500 Subject: [PATCH 3/4] fixup! Create a cache around `fontCalcGlyphPos` for ASCII characters --- source/base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/base.c b/source/base.c index 795a88a..9e3554c 100755 --- a/source/base.c +++ b/source/base.c @@ -575,7 +575,7 @@ void C2Di_FlushVtxBuf(void) ctx->idxBufLastPos = ctx->idxBufPos; } -void C2Di_Update() +void C2Di_Update(void) { C2Di_Context* ctx = C2Di_GetContext(); u32 flags = ctx->flags & C2DiF_DirtyAny; From 716c2057275e9e9bc73dfb0f50b2b6d1526c2db8 Mon Sep 17 00:00:00 2001 From: TheGag96 Date: Mon, 9 Mar 2026 21:52:18 -0500 Subject: [PATCH 4/4] Funnel system font handling into the same codepaths as user fonts, apply big sheets hack to all ...calculating the number of small sheets per big sheet dynamically such that the height ends up as 1024 pixels. There are still checks to load the pointer to the system font's `C2D_Font_s` when passed-in fonts are `NULL`, but at least there aren't two diverging codepaths and separate globals matching that struct's fields anymore. --- source/font.c | 132 ++++++++++++++++++++++++++-------------------- source/internal.h | 17 +++--- source/text.c | 96 ++++++--------------------------- 3 files changed, 100 insertions(+), 145 deletions(-) diff --git a/source/font.c b/source/font.c index c0dba60..d74a58b 100644 --- a/source/font.c +++ b/source/font.c @@ -4,8 +4,7 @@ #include "internal.h" #include -fontGlyphPos_s g_systemFontASCIICache[128]; -u32 g_numFontSheetsCombined; +C2D_Font_s g_systemFont; C2D_Font C2D_FontLoad(const char* filename) { @@ -21,43 +20,72 @@ static inline C2D_Font C2Di_FontAlloc(void) return (C2D_Font)malloc(sizeof(struct C2D_Font_s)); } +static void fillSheet(C3D_Tex *tex, void *data, TGLP_s *glyphInfo) +{ + tex->data = data; + tex->fmt = glyphInfo->sheetFmt; + tex->size = glyphInfo->sheetSize; + tex->width = glyphInfo->sheetWidth; + tex->height = glyphInfo->sheetHeight; + tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) + | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); + tex->border = 0; + tex->lodParam = 0; +} + static C2D_Font C2Di_PostLoadFont(C2D_Font font) { if (!font->cfnt) { free(font); font = NULL; - } else + } + else { - fontFixPointers(font->cfnt); + if (font->cfnt != fontGetSystemFont()) + { + fontFixPointers(font->cfnt); + } TGLP_s* glyphInfo = font->cfnt->finf.tglp; - font->glyphSheets = malloc(sizeof(C3D_Tex)*glyphInfo->nSheets); font->textScale = 30.0f / glyphInfo->cellHeight; + + // The way TGLP_s is set up, all of a font's texture sheets are adjacent in memory and have the same size. We can + // reinterpet the memory to describe a smaller set of much taller textures if we'd like. If we choose the right size, + // we can get all of the ASCII glyphs under a single texture, which will massively improve performance by reducing + // texture swaps within a piece of all-English text down to 0! We don't need any extra linear allocating to do this! + // Let's combine as many sheets as it takes to get to a big sheet height of 1024, which is the maximum height a + // texture can have. + font->sheetsPerBigSheet = 1024U / glyphInfo->sheetHeight; + u32 numSheetsBig = glyphInfo->nSheets / font->sheetsPerBigSheet; + u32 numSheetsSmall = glyphInfo->nSheets % font->sheetsPerBigSheet; + u32 numSheetsTotal = numSheetsBig + numSheetsSmall; + font->numSheetsCombined = glyphInfo->nSheets - numSheetsSmall; + + font->glyphSheets = malloc(sizeof(C3D_Tex)*numSheetsTotal); if (!font->glyphSheets) { C2D_FontFree(font); return NULL; } - - int i; - for (i = 0; i < glyphInfo->nSheets; i++) + memset(font->glyphSheets, 0, sizeof(sizeof(C3D_Tex)*numSheetsTotal)); + for (u32 i = 0; i < numSheetsBig; i++) { C3D_Tex* tex = &font->glyphSheets[i]; - tex->data = &glyphInfo->sheetData[glyphInfo->sheetSize*i]; - tex->fmt = glyphInfo->sheetFmt; - tex->size = glyphInfo->sheetSize; - tex->width = glyphInfo->sheetWidth; - tex->height = glyphInfo->sheetHeight; - tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) - | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER); - tex->border = 0; - tex->lodParam = 0; + fillSheet(tex, fontGetGlyphSheetTex(font->cfnt, i * font->sheetsPerBigSheet), glyphInfo); + tex->height = (uint16_t) (tex->height * font->sheetsPerBigSheet); + tex->size = tex->size * font->sheetsPerBigSheet; } - for (i = 0; i < NUM_ASCII_CHARACTERS; i++) + for (u32 i = 0; i < numSheetsSmall; i++) { - fontCalcGlyphPos(&font->asciiCache[i], font->cfnt, fontGlyphIndexFromCodePoint(font->cfnt, i), 0, 1.0, 1.0); + fillSheet(&font->glyphSheets[numSheetsBig + i], fontGetGlyphSheetTex(font->cfnt, numSheetsBig * font->sheetsPerBigSheet + i), glyphInfo); + } + + for (u32 i = 0; i < NUM_ASCII_CHARACTERS; i++) + { + // This will readjust glyph UVs to account for being a part of the combined texture. + C2D_FontCalcGlyphPos(NULL, &font->asciiCache[i], fontGlyphIndexFromCodePoint(font->cfnt, i), 0, 1.0, 1.0); } } return font; @@ -159,6 +187,13 @@ static C2D_Font C2Di_FontLoadFromArchive(u64 tid, const char* path) return C2Di_PostLoadFont(font); } +C2D_Font C2Di_LoadSystemFont(void) +{ + g_systemFont.cfnt = fontGetSystemFont(); + C2Di_PostLoadFont(&g_systemFont); + return &g_systemFont; +} + static unsigned C2Di_RegionToFontIndex(CFG_Region region) { switch (region) @@ -204,7 +239,7 @@ C2D_Font C2D_FontLoadSystem(CFG_Region region) void C2D_FontFree(C2D_Font font) { - if (font) + if (font && font != &g_systemFont) { if (font->cfnt) linearFree(font->cfnt); @@ -214,7 +249,7 @@ void C2D_FontFree(C2D_Font font) void C2D_FontSetFilter(C2D_Font font, GPU_TEXTURE_FILTER_PARAM magFilter, GPU_TEXTURE_FILTER_PARAM minFilter) { - if (!font) + if (!font || font == &g_systemFont) return; TGLP_s* glyphInfo = font->cfnt->finf.tglp; @@ -229,59 +264,44 @@ void C2D_FontSetFilter(C2D_Font font, GPU_TEXTURE_FILTER_PARAM magFilter, GPU_TE int C2D_FontGlyphIndexFromCodePoint(C2D_Font font, u32 codepoint) { - if (!font) - return fontGlyphIndexFromCodePoint(fontGetSystemFont(), codepoint); - else - return fontGlyphIndexFromCodePoint(font->cfnt, codepoint); + if (!font) font = &g_systemFont; + return fontGlyphIndexFromCodePoint(font->cfnt, codepoint); } charWidthInfo_s* C2D_FontGetCharWidthInfo(C2D_Font font, int glyphIndex) { - if (!font) - return fontGetCharWidthInfo(fontGetSystemFont(), glyphIndex); - else - return fontGetCharWidthInfo(font->cfnt, glyphIndex); + if (!font) font = &g_systemFont; + return fontGetCharWidthInfo(font->cfnt, glyphIndex); } void C2D_FontCalcGlyphPos(C2D_Font font, fontGlyphPos_s* out, int glyphIndex, u32 flags, float scaleX, float scaleY) { - if (!font) - { - fontCalcGlyphPos(out, fontGetSystemFont(), glyphIndex, flags, scaleX, scaleY); + if (!font) font = &g_systemFont; + fontCalcGlyphPos(out, font->cfnt, glyphIndex, flags, scaleX, scaleY); - if (out->sheetIndex < g_numFontSheetsCombined) - { - u32 indexWithinBigSheet = out->sheetIndex % SHEETS_PER_BIG_SHEET; - out->sheetIndex /= SHEETS_PER_BIG_SHEET; + if (out->sheetIndex < font->numSheetsCombined) + { + u32 indexWithinBigSheet = out->sheetIndex % font->sheetsPerBigSheet; + out->sheetIndex /= font->sheetsPerBigSheet; - // Readjust glyph UVs to account for being a part of the combined texture. - out->texcoord.top = (out->texcoord.top + (SHEETS_PER_BIG_SHEET - indexWithinBigSheet - 1)) / (float) SHEETS_PER_BIG_SHEET; - out->texcoord.bottom = (out->texcoord.bottom + (SHEETS_PER_BIG_SHEET - indexWithinBigSheet - 1)) / (float) SHEETS_PER_BIG_SHEET; - } - else - { - out->sheetIndex = out->sheetIndex - g_numFontSheetsCombined + g_numFontSheetsCombined / SHEETS_PER_BIG_SHEET; - } + // Readjust glyph UVs to account for being a part of the combined texture. + out->texcoord.top = (out->texcoord.top + (font->sheetsPerBigSheet - indexWithinBigSheet - 1)) / (float) font->sheetsPerBigSheet; + out->texcoord.bottom = (out->texcoord.bottom + (font->sheetsPerBigSheet - indexWithinBigSheet - 1)) / (float) font->sheetsPerBigSheet; } else { - fontCalcGlyphPos(out, font->cfnt, glyphIndex, flags, scaleX, scaleY); + out->sheetIndex = out->sheetIndex - font->numSheetsCombined + font->numSheetsCombined / font->sheetsPerBigSheet; } } void C2D_FontCalcGlyphPosFromCodePoint(C2D_Font font, fontGlyphPos_s* out, u32 codepoint, u32 flags, float scaleX, float scaleY) { + if (!font) font = &g_systemFont; + // Building glyph positions is pretty expensive, but we could just store the results for plain ASCII. if (codepoint < NUM_ASCII_CHARACTERS && flags == 0 && scaleX == 1 && scaleY == 1) { - if (font) - { - *out = font->asciiCache[codepoint]; - } - else - { - *out = g_systemFontASCIICache[codepoint]; - } + *out = font->asciiCache[codepoint]; } else { @@ -291,8 +311,6 @@ void C2D_FontCalcGlyphPosFromCodePoint(C2D_Font font, fontGlyphPos_s* out, u32 c FINF_s* C2D_FontGetInfo(C2D_Font font) { - if (!font) - return fontGetInfo(NULL); - else - return fontGetInfo(font->cfnt); + if (!font) font = &g_systemFont; + return fontGetInfo(font->cfnt); } diff --git a/source/internal.h b/source/internal.h index f530ba6..e2c7e86 100644 --- a/source/internal.h +++ b/source/internal.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include <3ds/font.h> typedef struct @@ -73,13 +74,16 @@ enum C2DiF_DirtyAny = C2DiF_DirtyProj | C2DiF_DirtyMdlv | C2DiF_DirtyTex | C2DiF_DirtyMode | C2DiF_DirtyFade, }; -struct C2D_Font_s +#define NUM_ASCII_CHARACTERS 128 +typedef struct C2D_Font_s { CFNT_s* cfnt; C3D_Tex* glyphSheets; float textScale; - fontGlyphPos_s asciiCache[128]; -}; + u32 numSheetsCombined; + u32 sheetsPerBigSheet; + fontGlyphPos_s asciiCache[NUM_ASCII_CHARACTERS]; +} C2D_Font_s; static inline C2Di_Context* C2Di_GetContext(void) { @@ -120,8 +124,5 @@ void C2Di_AppendVtx(float x, float y, float z, float u, float v, float ptx, floa void C2Di_FlushVtxBuf(void); void C2Di_Update(void); -#define NUM_ASCII_CHARACTERS 128 -extern fontGlyphPos_s g_systemFontASCIICache[NUM_ASCII_CHARACTERS]; - -#define SHEETS_PER_BIG_SHEET 32 -extern u32 g_numFontSheetsCombined; \ No newline at end of file +extern C2D_Font_s g_systemFont; +C2D_Font C2Di_LoadSystemFont(void); diff --git a/source/text.c b/source/text.c index 6e88f22..19d08ae 100644 --- a/source/text.c +++ b/source/text.c @@ -4,9 +4,6 @@ #include #include -static C3D_Tex* s_glyphSheets; -static float s_textScale; - typedef struct C2Di_Glyph_s { u32 lineNo; @@ -57,66 +54,18 @@ static int C2Di_GlyphComp(const void* _g1, const void* _g2) return ret; } -static void fillSheet(C3D_Tex *tex, void *data, TGLP_s *glyphInfo) -{ - tex->data = data; - tex->fmt = glyphInfo->sheetFmt; - tex->size = glyphInfo->sheetSize; - tex->width = glyphInfo->sheetWidth; - tex->height = glyphInfo->sheetHeight; - tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) - | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); - tex->border = 0; - tex->lodParam = 0; -} - static void C2Di_TextEnsureLoad(void) { // Skip if already loaded - if (s_glyphSheets) + if (g_systemFont.glyphSheets) return; // Ensure the shared system font is mapped if (R_FAILED(fontEnsureMapped())) svcBreak(USERBREAK_PANIC); - // Load the glyph texture sheets - CFNT_s* font = fontGetSystemFont(); - TGLP_s* glyphInfo = fontGetGlyphInfo(font); - - // The way TGLP_s is set up, all of a font's texture sheets are adjacent in memory and have the same size. We can - // reinterpet the memory to describe a smaller set of much taller textures if we'd like. If we choose the right size, - // we can get all of the ASCII glyphs under a single texture, which will massively improve performance by reducing - // texture swaps within a piece of all-English text down to 0! We don't need any extra linear allocating to do this! - u32 numSheetsBig = glyphInfo->nSheets / SHEETS_PER_BIG_SHEET; - u32 numSheetsSmall = glyphInfo->nSheets % SHEETS_PER_BIG_SHEET; - u32 numSheetsTotal = numSheetsBig + numSheetsSmall; - g_numFontSheetsCombined = glyphInfo->nSheets - numSheetsSmall; - - s_glyphSheets = malloc(sizeof(C3D_Tex)*numSheetsTotal); - if (!s_glyphSheets) + if (!C2Di_LoadSystemFont()) svcBreak(USERBREAK_PANIC); - memset(s_glyphSheets, 0, sizeof(sizeof(C3D_Tex)*numSheetsTotal)); - s_textScale = 30.0f / glyphInfo->cellHeight; - for (u32 i = 0; i < numSheetsBig; i++) - { - C3D_Tex* tex = &s_glyphSheets[i]; - fillSheet(tex, fontGetGlyphSheetTex(font, i * SHEETS_PER_BIG_SHEET), glyphInfo); - tex->height = (uint16_t) (tex->height * SHEETS_PER_BIG_SHEET); - tex->size = tex->size * SHEETS_PER_BIG_SHEET; - } - - for (u32 i = 0; i < numSheetsSmall; i++) - { - fillSheet(&s_glyphSheets[numSheetsBig + i], fontGetGlyphSheetTex(font, numSheetsBig * SHEETS_PER_BIG_SHEET + i), glyphInfo); - } - - // Initialize system font ASCII cache for C2D_FontCalcGlyphPosFromCodePoint - for (int i = 0; i < NUM_ASCII_CHARACTERS; i++) - { - // This will readjust glyph UVs to account for being a part of the combined texture. - C2D_FontCalcGlyphPos(NULL, &g_systemFontASCIICache[i], fontGlyphIndexFromCodePoint(font, i), 0, 1.0, 1.0); - } } C2D_TextBuf C2D_TextBufNew(size_t maxGlyphs) @@ -167,6 +116,8 @@ const char* C2D_TextParseLine(C2D_Text* text, C2D_TextBuf buf, const char* str, const char* C2D_TextFontParseLine(C2D_Text* text, C2D_Font font, C2D_TextBuf buf, const char* str, u32 lineNo) { + if (!font) font = &g_systemFont; + const uint8_t* p = (const uint8_t*)str; text->font = font; text->buf = buf; @@ -193,10 +144,7 @@ const char* C2D_TextFontParseLine(C2D_Text* text, C2D_Font font, C2D_TextBuf buf if (glyphData.width > 0.0f) { C2Di_Glyph* glyph = &buf->glyphs[buf->glyphCount++]; - if (font) - glyph->sheet = &font->glyphSheets[glyphData.sheetIndex]; - else - glyph->sheet = &s_glyphSheets[glyphData.sheetIndex]; + glyph->sheet = &font->glyphSheets[glyphData.sheetIndex]; glyph->xPos = text->width + glyphData.xOffset; glyph->lineNo = lineNo; glyph->wordNo = wordNum; @@ -220,7 +168,7 @@ const char* C2D_TextFontParseLine(C2D_Text* text, C2D_Font font, C2D_TextBuf buf wordNum++; text->end = buf->glyphCount; - text->width *= font ? font->textScale : s_textScale; + text->width *= font->textScale; text->lines = 1; text->words = wordNum; return (const char*)p; @@ -233,6 +181,8 @@ const char* C2D_TextParse(C2D_Text* text, C2D_TextBuf buf, const char* str) const char* C2D_TextFontParse(C2D_Text* text, C2D_Font font, C2D_TextBuf buf, const char* str) { + if (!font) font = &g_systemFont; + text->font = font; text->buf = buf; text->begin = buf->glyphCount; @@ -268,10 +218,8 @@ void C2D_TextGetDimensions(const C2D_Text* text, float scaleX, float scaleY, flo *outWidth = scaleX*text->width; if (outHeight) { - if (text->font) - *outHeight = ceilf(scaleY*text->font->textScale*text->font->cfnt->finf.lineFeed)*text->lines; - else - *outHeight = ceilf(scaleY*s_textScale*fontGetInfo(fontGetSystemFont())->lineFeed)*text->lines; + C2D_Font font = text->font ? text->font : &g_systemFont; + *outHeight = ceilf(scaleY*font->textScale*font->cfnt->finf.lineFeed)*text->lines; } } @@ -349,24 +297,15 @@ void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, fl C2Di_Glyph* begin = &text->buf->glyphs[text->begin]; C2Di_Glyph* end = &text->buf->glyphs[text->end]; C2Di_Glyph* cur; - CFNT_s* systemFont = fontGetSystemFont(); float glyphZ = z; float glyphH; float dispY; - if (text->font) - { - scaleX *= text->font->textScale; - scaleY *= text->font->textScale; - glyphH = scaleY*text->font->cfnt->finf.tglp->cellHeight; - dispY = ceilf(scaleY*text->font->cfnt->finf.lineFeed); - } else - { - scaleX *= s_textScale; - scaleY *= s_textScale; - glyphH = scaleY*fontGetGlyphInfo(systemFont)->cellHeight; - dispY = ceilf(scaleY*fontGetInfo(systemFont)->lineFeed); - } + C2D_Font font = text->font ? text->font : &g_systemFont; + scaleX *= font->textScale; + scaleY *= font->textScale; + glyphH = scaleY*font->cfnt->finf.tglp->cellHeight; + dispY = ceilf(scaleY*font->cfnt->finf.lineFeed); u32 color = 0xFF000000; float maxWidth = scaleX*text->width; @@ -375,10 +314,7 @@ void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, fl if (flags & C2D_AtBaseline) { - if (text->font) - y -= scaleY*text->font->cfnt->finf.tglp->baselinePos; - else - y -= scaleY*fontGetGlyphInfo(systemFont)->baselinePos; + y -= scaleY*font->cfnt->finf.tglp->baselinePos; } if (flags & C2D_WithColor) color = va_arg(va, u32);