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/font.c b/source/font.c index 5d1b61d..d74a58b 100644 --- a/source/font.c +++ b/source/font.c @@ -4,6 +4,8 @@ #include "internal.h" #include +C2D_Font_s g_systemFont; + C2D_Font C2D_FontLoad(const char* filename) { FILE* f = fopen(filename, "rb"); @@ -18,38 +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 (u32 i = 0; i < numSheetsSmall; i++) + { + 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; @@ -151,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) @@ -196,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); @@ -206,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; @@ -221,32 +264,53 @@ 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 < 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 + (font->sheetsPerBigSheet - indexWithinBigSheet - 1)) / (float) font->sheetsPerBigSheet; + out->texcoord.bottom = (out->texcoord.bottom + (font->sheetsPerBigSheet - indexWithinBigSheet - 1)) / (float) font->sheetsPerBigSheet; + } + else + { + 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) + { + *out = font->asciiCache[codepoint]; + } else - fontCalcGlyphPos(out, font->cfnt, glyphIndex, flags, scaleX, scaleY); + { + C2D_FontCalcGlyphPos(font, out, C2D_FontGlyphIndexFromCodePoint(font, codepoint), 0, 1.0f, 1.0f); + } } 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 42bd09d..e2c7e86 100644 --- a/source/internal.h +++ b/source/internal.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include <3ds/font.h> typedef struct { @@ -72,12 +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; -}; + u32 numSheetsCombined; + u32 sheetsPerBigSheet; + fontGlyphPos_s asciiCache[NUM_ASCII_CHARACTERS]; +} C2D_Font_s; static inline C2Di_Context* C2Di_GetContext(void) { @@ -117,3 +123,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); + +extern C2D_Font_s g_systemFont; +C2D_Font C2Di_LoadSystemFont(void); diff --git a/source/text.c b/source/text.c index ab419ff..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; @@ -60,35 +57,15 @@ static int C2Di_GlyphComp(const void* _g1, const void* _g2) 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); - s_glyphSheets = malloc(sizeof(C3D_Tex)*glyphInfo->nSheets); - s_textScale = 30.0f / glyphInfo->cellHeight; - if (!s_glyphSheets) + if (!C2Di_LoadSystemFont()) svcBreak(USERBREAK_PANIC); - - int i; - for (i = 0; i < glyphInfo->nSheets; 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; - } } C2D_TextBuf C2D_TextBufNew(size_t maxGlyphs) @@ -139,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; @@ -161,14 +140,11 @@ 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++]; - 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; @@ -192,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; @@ -205,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; @@ -240,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; } } @@ -321,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; @@ -347,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);