BLF: FreeType Optional Caching

Implementation of the FreeType 2 cache subsystem, which limits the
number of concurrently-opened FT_Face and FT_Size objects, as well as
caching information like character maps to speed up glyph id lookups.
This time with the option of opening FontBLFs that are not cached.

See D15686 for more details.

Differential Revision: https://developer.blender.org/D15686

Reviewed by Brecht Van Lommel
This commit is contained in:
Harley Acheson 2022-08-16 15:02:56 -07:00
parent 09640ab291
commit d39abb74a0
Notes: blender-bot 2023-05-31 04:43:10 +02:00
Referenced by commit dc06bf2cb6, Fix crash loading fonts that fail to be read
6 changed files with 226 additions and 38 deletions

View File

@ -353,6 +353,8 @@ enum {
BLF_LAST_RESORT = 1 << 15,
/** Failure to load this font. Don't try again. */
BLF_BAD_FONT = 1 << 16,
/** This font is managed by the FreeType cache subsystem. */
BLF_CACHED = 1 << 17,
};
#define BLF_DRAW_STR_DUMMY_MAX 1024

View File

@ -22,6 +22,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_threads.h"
#include "BLF_api.h"
@ -885,12 +886,21 @@ void BLF_draw_buffer(int fontid, const char *str, const size_t str_len)
char *BLF_display_name_from_file(const char *filepath)
{
FontBLF *font = blf_font_new("font_name", filepath);
if (!font) {
return NULL;
/* While listing font directories this function can be called simultaneously from a greater
* number of threads than we want the FreeType cache to keep open at a time. Therefore open
* with own FT_Library object and use FreeType calls directly to avoid any contention. */
char *name = NULL;
FT_Library ft_library;
if (FT_Init_FreeType(&ft_library) == FT_Err_Ok) {
FT_Face face;
if (FT_New_Face(ft_library, filepath, 0, &face) == FT_Err_Ok) {
if (face->family_name) {
name = BLI_sprintfN("%s %s", face->family_name, face->style_name);
}
FT_Done_Face(face);
}
FT_Done_FreeType(ft_library);
}
char *name = blf_display_name(font);
blf_font_free(font);
return name;
}

View File

@ -17,6 +17,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_CACHE_H /* FreeType Cache. */
#include FT_GLYPH_H
#include FT_MULTIPLE_MASTERS_H /* Variable font support. */
#include FT_TRUETYPE_IDS_H /* Codepoint coverage constants. */
@ -55,6 +56,8 @@ BatchBLF g_batch;
/* freetype2 handle ONLY for this file! */
static FT_Library ft_lib = NULL;
static FTC_Manager ftc_manager = NULL;
static FTC_CMapCache ftc_charmap_cache = NULL;
/* Lock for FreeType library, used around face creation and deletion. */
static ThreadMutex ft_lib_mutex;
@ -67,19 +70,75 @@ static ft_pix blf_font_width_max_ft_pix(struct FontBLF *font);
/* -------------------------------------------------------------------- */
/* Return glyph id from charcode. */
uint blf_get_char_index(struct FontBLF *font, uint charcode)
/** \name FreeType Caching
* \{ */
/* Called when a face is removed by the cache. FreeType will call FT_Done_Face. */
static void blf_face_finalizer(void *object)
{
return blf_ensure_face(font) ? FT_Get_Char_Index(font->face, charcode) : 0;
FT_Face face = object;
FontBLF *font = (FontBLF *)face->generic.data;
font->face = NULL;
}
/* Called in response to FTC_Manager_LookupFace. Now add a face to our font. */
FT_Error blf_cache_face_requester(FTC_FaceID faceID,
FT_Library lib,
FT_Pointer reqData,
FT_Face *face)
{
FontBLF *font = (FontBLF *)faceID;
int err = FT_Err_Cannot_Open_Resource;
BLI_mutex_lock(&ft_lib_mutex);
if (font->filepath) {
err = FT_New_Face(lib, font->filepath, 0, face);
}
else if (font->mem) {
err = FT_New_Memory_Face(lib, font->mem, (FT_Long)font->mem_size, 0, face);
}
BLI_mutex_unlock(&ft_lib_mutex);
if (err == FT_Err_Ok) {
font->face = *face;
font->face->generic.data = font;
font->face->generic.finalizer = blf_face_finalizer;
}
return err;
}
/* Called when the FreeType cache is removing a font size. */
static void blf_size_finalizer(void *object)
{
FT_Size size = object;
FontBLF *font = (FontBLF *)size->generic.data;
font->ft_size = NULL;
}
/* -------------------------------------------------------------------- */
/** \name FreeType Utilities (Internal)
* \{ */
/* Return glyph id from charcode. */
uint blf_get_char_index(struct FontBLF *font, uint charcode)
{
if (font->flags & BLF_CACHED) {
/* Use charmap cache for much faster lookup. */
return FTC_CMapCache_Lookup(ftc_charmap_cache, font, -1, charcode);
}
else {
/* Fonts that are not cached need to use the regular lookup function. */
return blf_ensure_face(font) ? FT_Get_Char_Index(font->face, charcode) : 0;
}
}
/* Convert a FreeType 26.6 value representing an unscaled design size to fractional pixels. */
static ft_pix blf_unscaled_F26Dot6_to_pixels(FontBLF *font, FT_Pos value)
{
/* Make sure we have a valid font->ft_size. */
blf_ensure_size(font);
/* Scale value by font size using integer-optimized multiplication. */
FT_Long scaled = FT_MulFix(value, font->ft_size->metrics.x_scale);
@ -1115,6 +1174,7 @@ int blf_font_count_missing_chars(FontBLF *font,
static ft_pix blf_font_height_max_ft_pix(FontBLF *font)
{
blf_ensure_size(font);
/* Metrics.height is rounded to pixel. Force minimum of one pixel. */
return MAX2((ft_pix)font->ft_size->metrics.height, ft_pix_from_int(1));
}
@ -1126,6 +1186,7 @@ int blf_font_height_max(FontBLF *font)
static ft_pix blf_font_width_max_ft_pix(FontBLF *font)
{
blf_ensure_size(font);
/* Metrics.max_advance is rounded to pixel. Force minimum of one pixel. */
return MAX2((ft_pix)font->ft_size->metrics.max_advance, ft_pix_from_int(1));
}
@ -1137,11 +1198,13 @@ int blf_font_width_max(FontBLF *font)
int blf_font_descender(FontBLF *font)
{
blf_ensure_size(font);
return ft_pix_to_int((ft_pix)font->ft_size->metrics.descender);
}
int blf_font_ascender(FontBLF *font)
{
blf_ensure_size(font);
return ft_pix_to_int((ft_pix)font->ft_size->metrics.ascender);
}
@ -1164,12 +1227,29 @@ int blf_font_init(void)
memset(&g_batch, 0, sizeof(g_batch));
BLI_mutex_init(&ft_lib_mutex);
int err = FT_Init_FreeType(&ft_lib);
if (err == FT_Err_Ok) {
/* Create a FreeType cache manager. */
err = FTC_Manager_New(ft_lib,
BLF_CACHE_MAX_FACES,
BLF_CACHE_MAX_SIZES,
BLF_CACHE_BYTES,
blf_cache_face_requester,
NULL,
&ftc_manager);
if (err == FT_Err_Ok) {
/* Create a charmap cache to speed up glyph index lookups. */
err = FTC_CMapCache_New(ftc_manager, &ftc_charmap_cache);
}
}
return err;
}
void blf_font_exit(void)
{
BLI_mutex_end(&ft_lib_mutex);
if (ftc_manager) {
FTC_Manager_Done(ftc_manager);
}
if (ft_lib) {
FT_Done_FreeType(ft_lib);
}
@ -1229,8 +1309,6 @@ static void blf_font_fill(FontBLF *font)
font->buf_info.col_init[1] = 0;
font->buf_info.col_init[2] = 0;
font->buf_info.col_init[3] = 0;
font->ft_lib = ft_lib;
}
/**
@ -1248,14 +1326,20 @@ bool blf_ensure_face(FontBLF *font)
FT_Error err;
BLI_mutex_lock(&ft_lib_mutex);
if (font->filepath) {
err = FT_New_Face(ft_lib, font->filepath, 0, &font->face);
if (font->flags & BLF_CACHED) {
err = FTC_Manager_LookupFace(ftc_manager, font, &font->face);
}
if (font->mem) {
err = FT_New_Memory_Face(ft_lib, font->mem, (FT_Long)font->mem_size, 0, &font->face);
else {
BLI_mutex_lock(&ft_lib_mutex);
if (font->filepath) {
err = FT_New_Face(font->ft_lib, font->filepath, 0, &font->face);
}
if (font->mem) {
err = FT_New_Memory_Face(font->ft_lib, font->mem, (FT_Long)font->mem_size, 0, &font->face);
}
font->face->generic.data = font;
BLI_mutex_unlock(&ft_lib_mutex);
}
BLI_mutex_unlock(&ft_lib_mutex);
if (err) {
if (ELEM(err, FT_Err_Unknown_File_Format, FT_Err_Unimplemented_Feature)) {
@ -1295,7 +1379,11 @@ bool blf_ensure_face(FontBLF *font)
}
}
font->ft_size = font->face->size;
if (!(font->flags & BLF_CACHED)) {
/* Not cached so point at the face's size for convenience. */
font->ft_size = font->face->size;
}
font->face_flags = font->face->face_flags;
if (FT_HAS_MULTIPLE_MASTERS(font)) {
@ -1366,11 +1454,16 @@ static const eFaceDetails static_face_details[] = {
{"NotoSansThai-VariableFont_wdth,wght.woff2", TT_UCR_THAI, 0, 0, 0},
};
/* Create a new font from filename OR from passed memory pointer. */
static FontBLF *blf_font_new_ex(const char *name,
const char *filepath,
const unsigned char *mem,
const size_t mem_size)
/**
* Create a new font from filename OR memory pointer.
* For normal operation pass NULL as FT_Library object. Pass a custom FT_Library if you
* want to use the font without its lifetime being managed by the FreeType cache subsystem.
*/
FontBLF *blf_font_new_ex(const char *name,
const char *filepath,
const unsigned char *mem,
const size_t mem_size,
void *ft_library)
{
FontBLF *font = (FontBLF *)MEM_callocN(sizeof(FontBLF), "blf_font_new");
@ -1382,6 +1475,16 @@ static FontBLF *blf_font_new_ex(const char *name,
}
blf_font_fill(font);
if (ft_library && ((FT_Library)ft_library != ft_lib)) {
font->ft_lib = (FT_Library)ft_library;
}
else {
font->ft_lib = ft_lib;
font->flags |= BLF_CACHED;
}
font->ft_lib = ft_library ? (FT_Library)ft_library : ft_lib;
BLI_mutex_init(&font->glyph_cache_mutex);
/* If we have static details about this font file, we don't have to load the Face yet. */
@ -1422,12 +1525,12 @@ static FontBLF *blf_font_new_ex(const char *name,
FontBLF *blf_font_new(const char *name, const char *filename)
{
return blf_font_new_ex(name, filename, NULL, 0);
return blf_font_new_ex(name, filename, NULL, 0, NULL);
}
FontBLF *blf_font_new_from_mem(const char *name, const unsigned char *mem, const size_t mem_size)
{
return blf_font_new_ex(name, NULL, mem, mem_size);
return blf_font_new_ex(name, NULL, mem, mem_size, NULL);
}
void blf_font_attach_from_mem(FontBLF *font, const unsigned char *mem, const size_t mem_size)
@ -1451,12 +1554,17 @@ void blf_font_free(FontBLF *font)
}
if (font->variations) {
FT_Done_MM_Var(ft_lib, font->variations);
FT_Done_MM_Var(font->ft_lib, font->variations);
}
if (font->face) {
BLI_mutex_lock(&ft_lib_mutex);
FT_Done_Face(font->face);
if (font->flags & BLF_CACHED) {
FTC_Manager_RemoveFaceID(ftc_manager, font);
}
else {
FT_Done_Face(font->face);
}
BLI_mutex_unlock(&ft_lib_mutex);
font->face = NULL;
}
@ -1478,6 +1586,28 @@ void blf_font_free(FontBLF *font)
/** \name Font Configure
* \{ */
void blf_ensure_size(FontBLF *font)
{
if (font->ft_size || !(font->flags & BLF_CACHED)) {
return;
}
FTC_ScalerRec scaler = {0};
scaler.face_id = font;
scaler.width = 0;
scaler.height = round_fl_to_uint(font->size * 64.0f);
scaler.pixel = 0;
scaler.x_res = font->dpi;
scaler.y_res = font->dpi;
if (FTC_Manager_LookupSize(ftc_manager, &scaler, &font->ft_size) == FT_Err_Ok) {
font->ft_size->generic.data = (void *)font;
font->ft_size->generic.finalizer = blf_size_finalizer;
return;
}
BLI_assert_unreachable();
}
bool blf_font_size(FontBLF *font, float size, unsigned int dpi)
{
if (!blf_ensure_face(font)) {
@ -1490,16 +1620,30 @@ bool blf_font_size(FontBLF *font, float size, unsigned int dpi)
size = (float)ft_size / 64.0f;
if (font->size != size || font->dpi != dpi) {
if (FT_Set_Char_Size(font->face, 0, ft_size, dpi, dpi) == FT_Err_Ok) {
font->size = size;
font->dpi = dpi;
if (font->flags & BLF_CACHED) {
FTC_ScalerRec scaler = {0};
scaler.face_id = font;
scaler.width = 0;
scaler.height = ft_size;
scaler.pixel = 0;
scaler.x_res = dpi;
scaler.y_res = dpi;
if (FTC_Manager_LookupSize(ftc_manager, &scaler, &font->ft_size) != FT_Err_Ok) {
return false;
}
font->ft_size->generic.data = (void *)font;
font->ft_size->generic.finalizer = blf_size_finalizer;
}
else {
printf("The current font does not support the size, %f and DPI, %u\n", size, dpi);
return false;
if (FT_Set_Char_Size(font->face, 0, ft_size, dpi, dpi) != FT_Err_Ok) {
return false;
}
font->ft_size = font->face->size;
}
}
font->size = size;
font->dpi = dpi;
return true;
}

View File

@ -103,6 +103,7 @@ static GlyphCacheBLF *blf_glyph_cache_new(FontBLF *font)
}
else {
/* Font does not have a face or does not contain "0" so use CSS fallback of 1/2 of em. */
blf_ensure_size(font);
gc->fixed_width = (int)((font->ft_size->metrics.height / 2) >> 6);
}
if (gc->fixed_width < 1) {
@ -570,6 +571,11 @@ static FT_UInt blf_glyph_index_from_charcode(FontBLF **font, const uint charcode
return glyph_index;
}
/* Only fonts managed by the cache can fallback. */
if (!((*font)->flags & BLF_CACHED)) {
return 0;
}
/* Not found in main font, so look in the others. */
FontBLF *last_resort = NULL;
int coverage_bit = blf_charcode_to_coverage_bit(charcode);
@ -787,8 +793,8 @@ static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float factor, bool mo
{
if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
/* Fake bold if the font does not have this variable axis. */
const FT_Pos average_width = FT_MulFix(glyph->face->units_per_EM,
glyph->face->size->metrics.x_scale);
const FontBLF *font = (FontBLF *)glyph->face->generic.data;
const FT_Pos average_width = font->ft_size->metrics.height;
FT_Pos change = (FT_Pos)((float)average_width * factor * 0.1f);
FT_Outline_EmboldenXY(&glyph->outline, change, change / 2);
if (monospaced) {
@ -847,7 +853,8 @@ static bool blf_glyph_transform_width(FT_GlyphSlot glyph, float factor)
static bool blf_glyph_transform_spacing(FT_GlyphSlot glyph, float factor)
{
if (glyph->advance.x > 0) {
const long int size = glyph->face->size->metrics.height;
const FontBLF *font = (FontBLF *)glyph->face->generic.data;
const long int size = font->ft_size->metrics.height;
glyph->advance.x += (FT_Pos)(factor * (float)size / 6.0f);
return true;
}
@ -899,6 +906,8 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font,
blf_font_size(glyph_font, settings_font->size, settings_font->dpi);
}
blf_ensure_size(glyph_font);
/* We need to keep track if changes are still needed. */
bool weight_done = false;
bool slant_done = false;

View File

@ -16,7 +16,14 @@ struct rcti;
/* Max number of FontBLFs in memory. Take care that every font has a glyph cache per size/dpi,
* so we don't need load the same font with different size, just load one and call BLF_size. */
#define BLF_MAX_FONT 32
#define BLF_MAX_FONT 64
/* Maximum number of opened FT_Face objects managed by cache. 0 is default of 2. */
#define BLF_CACHE_MAX_FACES 4
/* Maximum number of opened FT_Size objects managed by cache. 0 is default of 4 */
#define BLF_CACHE_MAX_SIZES 8
/* Maximum number of bytes to use for cached data nodes. 0 is default of 200,000. */
#define BLF_CACHE_BYTES 400000
extern struct FontBLF *global_font[BLF_MAX_FONT];
@ -42,10 +49,17 @@ bool blf_font_id_is_valid(int fontid);
uint blf_get_char_index(struct FontBLF *font, uint charcode);
bool blf_ensure_face(struct FontBLF *font);
void blf_ensure_size(struct FontBLF *font);
void blf_draw_buffer__start(struct FontBLF *font);
void blf_draw_buffer__end(void);
struct FontBLF *blf_font_new_ex(const char *name,
const char *filepath,
const unsigned char *mem,
size_t mem_size,
void *ft_library);
struct FontBLF *blf_font_new(const char *name, const char *filepath);
struct FontBLF *blf_font_new_from_mem(const char *name, const unsigned char *mem, size_t mem_size);
void blf_font_attach_from_mem(struct FontBLF *font, const unsigned char *mem, size_t mem_size);

View File

@ -46,12 +46,20 @@ void BLF_thumb_preview(const char *filepath,
/* shrink 1/th each line */
int font_shrink = 4;
FontBLF *font;
/* While viewing thumbnails in font directories this function can be called simultaneously from a
* greater number of threads than we want the FreeType cache to keep open at a time. Therefore
* pass own FT_Library to font creation so that it is not managed by the FreeType cache system.
*/
/* Create a new blender font obj and fill it with default values */
font = blf_font_new("thumb_font", filepath);
FT_Library ft_library = NULL;
if (FT_Init_FreeType(&ft_library) != FT_Err_Ok) {
return;
}
FontBLF *font = blf_font_new_ex("thumb_font", filepath, NULL, 0, ft_library);
if (!font) {
printf("Info: Can't load font '%s', no preview possible\n", filepath);
FT_Done_FreeType(ft_library);
return;
}
@ -102,4 +110,5 @@ void BLF_thumb_preview(const char *filepath,
blf_draw_buffer__end();
blf_font_free(font);
FT_Done_FreeType(ft_library);
}