Disabling auto-refresh of game list by default, as it is causing bugs sometimes
[open-ps2-loader.git] / src / fntsys.c
blobc963d6bb20c5ca6550b6fa1435079f6e52fd6049
1 /*
2 Copyright 2010, Volca
3 Licenced under Academic Free License version 3.0
4 Review OpenUsbLd README & LICENSE files for further details.
5 */
7 #include "include/integers.h"
8 #include "include/usbld.h"
9 #include "include/fntsys.h"
10 #include "include/renderman.h"
11 #include "include/ioman.h"
12 #include "include/utf8.h"
13 #include "include/util.h"
14 #include "include/atlas.h"
16 #include <ft2build.h>
18 #include FT_FREETYPE_H
20 extern void* freesansfont_raw;
21 extern int size_freesansfont_raw;
23 /// Maximal count of atlases per font
24 #define ATLAS_MAX 4
25 /// Atlas width in pixels
26 #define ATLAS_WIDTH 128
27 /// Atlas height in pixels
28 #define ATLAS_HEIGHT 128
30 // freetype vars
31 static FT_Library font_library;
33 static int gCharHeight = 16;
35 static s32 gFontSemaId;
36 static ee_sema_t gFontSema;
38 static GSCLUT fontClut;
40 /** Single entry in the glyph cache */
41 typedef struct {
42 int isValid;
43 // size in pixels of the glyph
44 int width, height;
45 // offsetting of the glyph
46 int ox, oy;
47 // advancements in pixels after rendering this glyph
48 int shx, shy;
50 // atlas for which the allocation was done
51 atlas_t* atlas;
53 // atlas allocation position
54 struct atlas_allocation_t *allocation;
56 } fnt_glyph_cache_entry_t;
58 /** A whole font definition */
59 typedef struct {
60 /** GLYPH CACHE. Every glyph in the ASCII range is cached when first used
61 * this means no additional memory aside from the one needed to render the
62 * character set is used.
64 fnt_glyph_cache_entry_t **glyphCache;
66 /// Maximal font cache page index
67 int cacheMaxPageID;
69 /// Font face
70 FT_Face face;
72 /// Nonzero if font is used
73 int isValid;
75 /// Texture atlases (default to NULL)
76 atlas_t *atlases[ATLAS_MAX];
78 /// Pointer to data, if allocation takeover was selected (will be freed)
79 void *dataPtr;
80 } font_t;
82 #define FNT_MAX_COUNT (16)
84 /// Array of font definitions
85 static font_t fonts[FNT_MAX_COUNT];
87 static rm_quad_t quad;
88 static uint32_t codepoint, state;
89 static fnt_glyph_cache_entry_t* glyph;
90 static FT_Bool use_kerning;
91 static FT_UInt glyph_index, previous;
92 static FT_Vector delta;
94 #define GLYPH_CACHE_PAGE_SIZE 256
96 #define GLYPH_PAGE_OK(font,page) ((pageid <= font->cacheMaxPageID) && (font->glyphCache[page]))
98 static void fntCacheFlushPage(fnt_glyph_cache_entry_t *page) {
99 int i;
101 for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i, ++page) {
102 page->isValid = 0;
103 // we're not doing any atlasFree or such - atlas has to be rebuild
104 page->allocation = NULL;
105 page->atlas = NULL;
109 static void fntCacheFlush(font_t *font) {
110 // Release all the glyphs from the cache
111 int i;
112 for (i = 0; i <= font->cacheMaxPageID; ++i) {
113 if (font->glyphCache[i]) {
114 fntCacheFlushPage(font->glyphCache[i]);
115 free(font->glyphCache[i]);
116 font->glyphCache[i] = NULL;
120 free(font->glyphCache);
121 font->glyphCache = NULL;
122 font->cacheMaxPageID = -1;
124 // free all atlasses too, they're invalid now anyway
125 int aid;
126 for(aid = 0; aid < ATLAS_MAX; ++aid) {
127 atlasFree(font->atlases[aid]);
128 font->atlases[aid] = NULL;
132 static int fntPrepareGlyphCachePage(font_t *font, int pageid) {
133 if (pageid > font->cacheMaxPageID) {
134 fnt_glyph_cache_entry_t **np = realloc(font->glyphCache, (pageid + 1) * sizeof(fnt_glyph_cache_entry_t *));
136 if (!np)
137 return 0;
139 font->glyphCache = np;
141 unsigned int page;
142 for (page = font->cacheMaxPageID + 1; page <= pageid; ++page)
143 font->glyphCache[page] = NULL;
145 font->cacheMaxPageID = pageid;
148 // if it already was allocated, skip this
149 if (font->glyphCache[pageid])
150 return 1;
152 // allocate the page
153 font->glyphCache[pageid] = malloc(sizeof(fnt_glyph_cache_entry_t) * GLYPH_CACHE_PAGE_SIZE);
155 int i;
156 for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i) {
157 font->glyphCache[pageid][i].isValid = 0;
158 font->glyphCache[pageid][i].atlas = NULL;
159 font->glyphCache[pageid][i].allocation = NULL;
162 return 1;
165 static void fntPrepareCLUT() {
166 fontClut.PSM = GS_PSM_T8;
167 fontClut.ClutPSM = GS_PSM_CT32;
168 fontClut.Clut = memalign(128, 256 * 4);
169 fontClut.VramClut = 0;
171 // generate the clut table
172 size_t i;
173 u32 *clut = fontClut.Clut;
174 for (i = 0; i < 256; ++i) {
175 u8 alpha = i > 0x080 ? 0x080 : i;
177 *clut = alpha << 24 | i << 16 | i << 8 | i;
178 clut++;
182 static void fntDestroyCLUT() {
183 free(fontClut.Clut);
184 fontClut.Clut = NULL;
187 static void fntInitSlot(font_t *font) {
188 font->face = NULL;
189 font->glyphCache = NULL;
190 font->cacheMaxPageID = -1;
191 font->dataPtr = NULL;
192 font->isValid = 0;
194 int aid = 0;
195 for(; aid < ATLAS_MAX; ++aid)
196 font->atlases[aid] = NULL;
199 static void fntDeleteSlot(font_t *font) {
200 // free the glyph cache, atlases, unload the font
201 fntCacheFlush(font);
203 FT_Done_Face(font->face);
204 font->face = NULL;
206 if (font->dataPtr) {
207 free(font->dataPtr);
208 font->dataPtr = NULL;
211 font->isValid = 0;
214 void fntRelease(int id) {
215 if (id > FNT_DEFAULT && id < FNT_MAX_COUNT)
216 fntDeleteSlot(&fonts[id]);
219 static int fntLoadSlot(font_t *font, char* path) {
220 void* buffer = NULL;
221 int bufferSize = -1;
223 fntInitSlot(font);
225 if (path) {
226 buffer = readFile(path, -1, &bufferSize);
227 if (!buffer) {
228 LOG("FNTSYS Font file loading failed: %s\n", path);
229 return FNT_ERROR;
231 font->dataPtr = buffer;
232 } else {
233 buffer = &freesansfont_raw;
234 bufferSize = size_freesansfont_raw;
237 // load the font via memory handle
238 int error = FT_New_Memory_Face(font_library, (FT_Byte*) buffer, bufferSize, 0, &font->face);
239 if (error) {
240 LOG("FNTSYS Freetype font loading failed with %x!\n", error);
241 fntDeleteSlot(font);
242 return FNT_ERROR;
245 error = FT_Set_Char_Size(font->face, 0, gCharHeight * 16, 300, 300);
246 /*error = FT_Set_Pixel_Sizes( face, 0, // pixel_width gCharHeight ); // pixel_height */
247 if (error) {
248 LOG("FNTSYS Freetype error setting font pixel size with %x!\n", error);
249 fntDeleteSlot(font);
250 return FNT_ERROR;
253 font->isValid = 1;
254 return 0;
257 void fntInit() {
258 LOG("FNTSYS Init\n");
259 int error = FT_Init_FreeType(&font_library);
260 if (error) {
261 // just report over the ps2link
262 LOG("FNTSYS Freetype init failed with %x!\n", error);
263 // SleepThread();
266 fntPrepareCLUT();
268 gFontSema.init_count = 1;
269 gFontSema.max_count = 1;
270 gFontSema.option = 0;
271 gFontSemaId = CreateSema(&gFontSema);
273 int i = 0;
274 for (; i < FNT_MAX_COUNT; ++i)
275 fntInitSlot(&fonts[i]);
277 fntLoadDefault(NULL);
280 int fntLoadFile(char* path) {
281 font_t *font;
282 int i = 1;
283 for (; i < FNT_MAX_COUNT; i++) {
284 font = &fonts[i];
285 if (!font->isValid) {
286 if (fntLoadSlot(font, path) != FNT_ERROR)
287 return i;
288 break;
292 return FNT_ERROR;
295 void fntLoadDefault(char* path) {
296 font_t newFont, oldFont;
298 if (fntLoadSlot(&newFont, path) != FNT_ERROR) {
299 // copy over the new font definition
300 // we have to lock this phase, as the old font may still be used
301 // Note: No check for concurrency is done here, which is kinda funky!
302 WaitSema(gFontSemaId);
303 memcpy(&oldFont, &fonts[FNT_DEFAULT], sizeof(font_t));
304 memcpy(&fonts[FNT_DEFAULT], &newFont, sizeof(font_t));
305 SignalSema(gFontSemaId);
307 // delete the old font
308 fntDeleteSlot(&oldFont);
312 void fntEnd() {
313 LOG("FNTSYS End\n");
314 // release all the fonts
315 int id;
316 for (id = 0; id < FNT_MAX_COUNT; ++id)
317 fntDeleteSlot(&fonts[id]);
319 // deinit freetype system
320 FT_Done_FreeType(font_library);
322 DeleteSema(gFontSemaId);
324 fntDestroyCLUT();
327 static atlas_t *fntNewAtlas() {
328 atlas_t *atl = atlasNew(ATLAS_WIDTH, ATLAS_HEIGHT, GS_PSM_T8);
330 atl->surface.ClutPSM = GS_PSM_CT32;
331 atl->surface.Clut = (u32*)(&fontClut);
333 return atl;
336 static int fntGlyphAtlasPlace(font_t *font, fnt_glyph_cache_entry_t* glyph) {
337 FT_GlyphSlot slot = font->face->glyph;
339 //LOG("FNTSYS GlyphAtlasPlace: Placing the glyph... %d x %d\n", slot->bitmap.width, slot->bitmap.rows);
341 if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
342 // no bitmap glyph, just skip
343 return 1;
346 int aid = 0;
347 for (; aid < ATLAS_MAX; aid++) {
348 //LOG("FNTSYS Placing aid %d...\n", aid);
349 atlas_t **atl = &font->atlases[aid];
350 if (!*atl) { // atlas slot not yet used
351 //LOG("FNTSYS aid %d is new...\n", aid);
352 *atl = fntNewAtlas();
355 glyph->allocation = atlasPlace(*atl, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.buffer);
356 if (glyph->allocation) {
357 //LOG("FNTSYS Found placement\n", aid);
358 glyph->atlas = *atl;
360 return 1;
364 LOG("FNTSYS No atlas free\n", aid);
365 return 0;
368 /** Internal method. Makes sure the bitmap data for particular character are pre-rendered to the glyph cache */
369 static fnt_glyph_cache_entry_t* fntCacheGlyph(font_t *font, uint32_t gid) {
370 // calc page id and in-page index from glyph id
371 int pageid = gid / GLYPH_CACHE_PAGE_SIZE;
372 int idx = gid % GLYPH_CACHE_PAGE_SIZE;
374 // do not call on every char of every font rendering call
375 if (!GLYPH_PAGE_OK(font, pageid))
376 if (!fntPrepareGlyphCachePage(font, pageid)) // failed to prepare the page...
377 return NULL;
379 fnt_glyph_cache_entry_t *page = font->glyphCache[pageid];
380 /* Should never happen.
381 if (!page) // safeguard
382 return NULL;
385 fnt_glyph_cache_entry_t* glyph = &page[idx];
386 if (glyph->isValid)
387 return glyph;
389 // not cached but valid. Cache
390 if (!font->face) {
391 LOG("FNTSYS Face is NULL!\n");
394 int error = FT_Load_Char(font->face, gid, FT_LOAD_RENDER);
395 if (error) {
396 LOG("FNTSYS Error loading glyph - %d\n", error);
397 return NULL;
400 // find atlas placement for the glyph
401 if (!fntGlyphAtlasPlace(font, glyph))
402 return NULL;
404 FT_GlyphSlot slot = font->face->glyph;
405 glyph->width = slot->bitmap.width;
406 glyph->height = slot->bitmap.rows;
407 glyph->shx = slot->advance.x;
408 glyph->shy = slot->advance.y;
409 glyph->ox = slot->bitmap_left;
410 glyph->oy = gCharHeight - slot->bitmap_top;
412 glyph->isValid = 1;
414 return glyph;
417 void fntSetAspectRatio(float aw, float ah) {
418 // flush cache - it will be invalid after the setting
419 int i = 0;
420 for (; i < FNT_MAX_COUNT; i++) {
421 if (fonts[i].isValid)
422 fntCacheFlush(&fonts[i]);
425 // TODO: set new aspect ratio (Is this correct, I wonder?)
426 // error = FT_Set_Char_Size(face, 0, gCharHeight*64, ah*300, aw*300);
429 static void fntRenderGlyph(fnt_glyph_cache_entry_t* glyph, int pen_x, int pen_y) {
430 // only if glyph has atlas placement
431 if (glyph->allocation) {
432 /* TODO: Ineffective on many parts:
433 * 1. Usage of floats for UV - fixed point should suffice (and is used internally by GS for UV)
435 * 2. GS_SETREG_TEX0 for every quad - why? gsKit should only set texture if demanded
436 * We should prepare a special fnt render method that would step over most of the
437 * performance problems under - beginning with rmSetupQuad and continuing into gsKit
438 * - this method would handle the preparation of the quads and GS upload itself,
439 * without the use of prim_quad_texture and rmSetupQuad...
441 * 3. We should use clut to keep the memory allocations sane - we're linear in the 32bit buffers
442 * anyway, no reason to waste like we do!
444 quad.ul.x = pen_x + glyph->ox;
445 quad.br.x = quad.ul.x + glyph->width;
446 quad.ul.y = pen_y + glyph->oy;
447 quad.br.y = quad.ul.y + glyph->height;
449 // UV is our own, custom thing here
450 quad.txt = &glyph->atlas->surface;
451 quad.ul.u = glyph->allocation->x;
452 quad.br.u = quad.ul.u + glyph->width;
453 quad.ul.v = glyph->allocation->y;
454 quad.br.v = quad.ul.v + glyph->height;
456 rmDrawQuad(&quad);
460 #ifndef __RTL
461 int fntRenderString(int id, int x, int y, short aligned, size_t width, size_t height, const unsigned char* string, u64 colour) {
462 // wait for font lock to unlock
463 WaitSema(gFontSemaId);
464 font_t *font = &fonts[id];
465 SignalSema(gFontSemaId);
467 if (aligned) {
468 if (width) {
469 x -= min(fntCalcDimensions(id, string), width) >> 1;
470 } else {
471 x -= fntCalcDimensions(id, string) >> 1;
473 y -= MENU_ITEM_HEIGHT >> 1;
476 rmApplyShiftRatio(&y);
477 quad.color = colour;
479 int pen_x = x;
480 int xmax = x + width;
481 int ymax = y + height;
483 use_kerning = FT_HAS_KERNING(font->face);
484 state = UTF8_ACCEPT;
485 previous = 0;
487 // Note: We need to change this so that we'll accumulate whole word before doing a layout with it
488 // for now this method breaks on any character - which is a bit ugly
490 // I don't want to do anything complicated though so I'd say
491 // we should instead have a dynamic layout routine that'll replace spaces with newlines as appropriate
492 // because that'll make the code run only once per N frames, not every frame
494 // cache glyphs and render as we go
495 for (; *string; ++string) {
496 if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
497 continue;
499 glyph = fntCacheGlyph(font, codepoint);
500 if (!glyph)
501 continue;
503 // kerning
504 if (use_kerning && previous) {
505 glyph_index = FT_Get_Char_Index(font->face, codepoint);
506 if (glyph_index) {
507 FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
508 pen_x += delta.x >> 6;
510 previous = glyph_index;
513 if (width) {
514 if (codepoint == '\n') {
515 pen_x = x;
516 y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
517 continue;
520 if (y > ymax) // stepped over the max
521 break;
523 if (pen_x + glyph->width > xmax) {
524 pen_x = xmax + 1; // to be sure no other cahr will be written (even not a smaller one just following)
525 continue;
529 fntRenderGlyph(glyph, pen_x, y);
530 pen_x += glyph->shx >> 6;
533 return pen_x;
536 #else
537 static void fntRenderSubRTL(font_t *font, const unsigned char* startRTL, const unsigned char* string, fnt_glyph_cache_entry_t* glyph, int x, int y) {
538 if (glyph) {
539 x -= glyph->shx >> 6;
540 fntRenderGlyph(glyph, x, y);
543 for (; startRTL != string; ++startRTL) {
544 if (utf8Decode(&state, &codepoint, *startRTL))
545 continue;
547 glyph = fntCacheGlyph(font, codepoint);
548 if (!glyph)
549 continue;
551 if (use_kerning && previous) {
552 glyph_index = FT_Get_Char_Index(font->face, codepoint);
553 if (glyph_index) {
554 FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
555 x -= delta.x >> 6;
557 previous = glyph_index;
560 x -= glyph->shx >> 6;
561 fntRenderGlyph(glyph, x, y);
565 int fntRenderString(int id, int x, int y, short aligned, size_t width, size_t height, const unsigned char* string, u64 colour) {
566 // wait for font lock to unlock
567 WaitSema(gFontSemaId);
568 font_t *font = &fonts[id];
569 SignalSema(gFontSemaId);
571 if (aligned) {
572 if (width) {
573 x -= min(fntCalcDimensions(id, string), width) >> 1;
574 } else {
575 x -= fntCalcDimensions(id, string) >> 1;
577 y -= MENU_ITEM_HEIGHT >> 1;
580 rmApplyShiftRatio(&y);
581 quad.color = colour;
583 int pen_x = x;
584 /*int xmax = x + width;
585 int ymax = y + height;*/
587 use_kerning = FT_HAS_KERNING(font->face);
588 state = UTF8_ACCEPT;
589 previous = 0;
591 short inRTL = 0;
592 int delta_x, pen_xRTL = 0;
593 fnt_glyph_cache_entry_t* glyphRTL = NULL;
594 const unsigned char* startRTL = NULL;
596 // cache glyphs and render as we go
597 for (; *string; ++string) {
598 if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
599 continue;
601 glyph = fntCacheGlyph(font, codepoint);
602 if (!glyph)
603 continue;
605 // kerning
606 delta_x = 0;
607 if (use_kerning && previous) {
608 glyph_index = FT_Get_Char_Index(font->face, codepoint);
609 if (glyph_index) {
610 FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
611 delta_x = delta.x >> 6;
613 previous = glyph_index;
617 /*if (width) {
618 if (codepoint == '\n') {
619 x = xori;
620 y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
621 continue;
624 if ((x + glyph_w > xmax) || (y > ymax)) // stepped over the max
625 break;
628 if (codepoint > 0xFF) {
629 if (!inRTL) {
630 inRTL = 1;
631 pen_xRTL = pen_x;
632 glyphRTL = glyph;
633 startRTL = string + 1;
635 } else if ((codepoint > 96 && codepoint < 123) || (codepoint > 64 && codepoint < 91)) {
636 if (inRTL) { // render RTL
637 inRTL = 0;
638 pen_x = pen_xRTL;
639 fntRenderSubRTL(font, startRTL, string, glyphRTL, pen_xRTL, y);
643 if (inRTL) {
644 pen_xRTL += delta_x + (glyph->shx >> 6);
645 } else {
646 pen_x += delta_x;
647 fntRenderGlyph(glyph, pen_x, y);
648 pen_x += glyph->shx >> 6;
653 if (inRTL) {
654 pen_x = pen_xRTL;
655 fntRenderSubRTL(font, startRTL, string, glyphRTL, pen_xRTL, y);
658 return pen_x;
660 #endif
662 void fntFitString(int id, unsigned char *string, size_t width) {
663 size_t cw = 0;
664 unsigned char *str = string;
665 size_t spacewidth = fntCalcDimensions(id, " ");
666 unsigned char *psp = NULL;
668 while (*str) {
669 // scan forward to the next whitespace
670 unsigned char *sp = str;
671 for (; *sp && *sp != ' ' && *sp != '\n'; ++sp);
673 // store what was there before
674 unsigned char osp = *sp;
676 // newline resets the situation
677 if (osp == '\n') {
678 cw = 0;
679 str = ++sp;
680 psp = NULL;
681 continue;
684 // terminate after the word
685 *sp = '\0';
687 // Calc the font's width...
688 // NOTE: The word was terminated, so we're seeing a single word
689 // on that position
690 size_t ww = fntCalcDimensions(id, str);
692 if (cw + ww > width) {
693 if (psp) {
694 // we have a prev space to utilise (wrap on it)
695 *psp = '\n';
696 *sp = osp;
697 cw = ww;
698 psp = sp;
699 } else {
700 // no prev. space to hijack, must break after the word
701 // this will mean overflowed text...
702 *sp = '\n';
703 cw = 0;
705 } else {
706 cw += ww;
707 *sp = osp;
708 psp = sp;
711 cw += spacewidth;
712 str = ++sp;
716 int fntCalcDimensions(int id, const unsigned char* str) {
717 int w = 0;
719 WaitSema(gFontSemaId);
720 font_t *font = &fonts[id];
721 SignalSema(gFontSemaId);
723 uint32_t codepoint;
724 uint32_t state = UTF8_ACCEPT;
725 FT_Bool use_kerning = FT_HAS_KERNING(font->face);
726 FT_UInt glyph_index, previous = 0;
727 FT_Vector delta;
729 // cache glyphs and render as we go
730 for (; *str; ++str) {
731 if (utf8Decode(&state, &codepoint, *str)) // accumulate the codepoint value
732 continue;
734 // Could just as well only get the glyph dimensions
735 // but it is probable the glyphs will be needed anyway
736 fnt_glyph_cache_entry_t* glyph = fntCacheGlyph(font, codepoint);
737 if (!glyph)
738 continue;
740 // kerning
741 if (use_kerning && previous) {
742 glyph_index = FT_Get_Char_Index(font->face, codepoint);
743 if (glyph_index) {
744 FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
745 w += delta.x >> 6;
747 previous = glyph_index;
750 w += glyph->shx >> 6;
753 return w;