/****************************************************************************/ /* */ /* The FreeType project -- a free and portable quality TrueType renderer. */ /* */ /* Copyright (C) 2002-2023 by */ /* D. Turner, R.Wilhelm, and W. Lemberg */ /* */ /* ftbench: bench some common FreeType call paths */ /* */ /****************************************************************************/ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* we want to use extensions to `time.h' if available */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_MM_AXES 16 #ifdef UNIX #include #else #include "mlgetopt.h" #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include /* Specify the timer: QPC for accurate wall time, GPT for user-mode time. */ /* Otherwise, QPCT cycles are measured accurately but with huge overhead. */ #define QPC #ifdef QPC double interval; #endif #endif typedef struct btimer_t_ { double t0; double total; } btimer_t; typedef int (*bcall_t)( FT_Face face, void* user_data ); typedef struct btest_t_ { const char* title; bcall_t bench; int cache_first; void* user_data; } btest_t; typedef struct bcharset_t_ { FT_Int size; FT_ULong* code; } bcharset_t; static FT_Error get_face( FT_Face* face ); /* * Globals */ #define CACHE_SIZE 1024 #define BENCH_TIME 2.0 #define FACE_SIZE 10 #define WARMUP_ITER 10 static FT_Library lib; static FTC_Manager cache_man; static FTC_CMapCache cmap_cache; static FTC_ImageCache image_cache; static FTC_SBitCache sbit_cache; static FTC_ImageTypeRec font_type; static FT_MM_Var* multimaster = NULL; static FT_Fixed design_pos [MAX_MM_AXES]; static FT_Fixed requested_pos[MAX_MM_AXES]; static unsigned int requested_cnt = 0; static unsigned int used_num_axes = 0; enum { FT_BENCH_LOAD_GLYPH, FT_BENCH_LOAD_ADVANCES, FT_BENCH_RENDER, FT_BENCH_GET_GLYPH, FT_BENCH_CMAP, FT_BENCH_CMAP_ITER, FT_BENCH_NEW_FACE, FT_BENCH_EMBOLDEN, FT_BENCH_STROKE, FT_BENCH_GET_BBOX, FT_BENCH_GET_CBOX, FT_BENCH_NEW_FACE_AND_LOAD_GLYPH, N_FT_BENCH }; static const char* bench_desc[] = { "load a glyph (FT_Load_Glyph)", "load advance widths (FT_Get_Advances)", "render a glyph (FT_Render_Glyph)", "load a glyph (FT_Get_Glyph)", "get glyph indices (FT_Get_Char_Index)", "iterate CMap (FT_Get_{First,Next}_Char)", "open a new face (FT_New_Face)", "embolden (FT_GlyphSlot_Embolden)", "stroke (FT_Glyph_Stroke)", "get glyph bbox (FT_Outline_Get_BBox)", "get glyph cbox (FT_Glyph_Get_CBox)", "open face and load glyphs", NULL }; static int preload; static char* filename; static int first_index = 0; static int last_index = INT_MAX; static int incr_index = 1; static int cmap_index = -1; #define FOREACH( i ) for ( i = first_index ; \ ( first_index <= i && i <= last_index ) || \ ( first_index >= i && i >= last_index ) ; \ i += incr_index ) static FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL; static FT_Int32 load_flags = FT_LOAD_DEFAULT; static unsigned int tt_interpreter_versions[2]; static int num_tt_interpreter_versions; static unsigned int dflt_tt_interpreter_version; static unsigned int ps_hinting_engines[2]; static int num_ps_hinting_engines; static unsigned int dflt_ps_hinting_engine; static char ps_hinting_engine_names[2][10] = { "freetype", "adobe" }; /* * Face requester for cache testing */ static FT_Error face_requester( FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface ) { FT_UNUSED( face_id ); FT_UNUSED( library ); FT_UNUSED( request_data ); return get_face( aface ); } /* * timer in milliseconds */ static double get_time( void ) { /* NOTE: When building with the Mingw64 toolchain, `_POSIX_TIMERS` is * defined, but function `clock_gettime` is not. Ensure that the * `_WIN32` specific timer code appears first here. */ #if defined _WIN32 #ifdef QPC LARGE_INTEGER ticks; QueryPerformanceCounter( &ticks ); return interval * ticks.QuadPart; #elif defined GPT FILETIME start, end, kern, user; GetProcessTimes( GetCurrentProcess(), &start, &end, &kern, &user ); return 0.1 * user.dwLowDateTime + 429496729.6 * user.dwHighDateTime; #else ULONG64 cycles; QueryProcessCycleTime( GetCurrentProcess(), &cycles ); return 1e-3 * cycles; /* at 1GHz */ #endif #elif defined _POSIX_TIMERS && _POSIX_TIMERS > 0 struct timespec tv; #ifdef _POSIX_CPUTIME clock_gettime( CLOCK_PROCESS_CPUTIME_ID, &tv ); #else clock_gettime( CLOCK_REALTIME, &tv ); #endif /* _POSIX_CPUTIME */ return 1E6 * (double)tv.tv_sec + 1E-3 * (double)tv.tv_nsec; #else /* clock() accuracy has improved since glibc 2.18 */ return 1E6 * (double)clock() / (double)CLOCKS_PER_SEC; #endif /* _POSIX_TIMERS */ } #define TIMER_START( timer ) ( timer )->t0 = get_time() #define TIMER_STOP( timer ) ( timer )->total += get_time() - ( timer )->t0 #define TIMER_GET( timer ) ( timer )->total #define TIMER_RESET( timer ) ( timer )->total = 0 /* * Bench code */ static void benchmark( FT_Face face, btest_t* test, int max_iter, double max_time, double warmup ) { int n, w, done; btimer_t timer; if ( test->cache_first ) { TIMER_RESET( &timer ); test->bench( face, test->user_data ); } TIMER_START(&timer); for( w = 0; w < warmup; w++ ) test->bench(face, test->user_data); TIMER_STOP(&timer); printf( " %-25s ", test->title ); fflush( stdout ); TIMER_RESET( &timer ); for ( n = 0, done = 0; !max_iter || n < max_iter; n++ ) { TIMER_START( &timer ); done += test->bench( face, test->user_data ); TIMER_STOP( &timer ); if ( TIMER_GET( &timer ) > 1E6 * max_time ) break; } if ( done ) printf( "%10.3f microseconds %10d done\n", TIMER_GET( &timer ) / (double)done, done ); else printf( "no error-free calls\n" ); } /* * Various tests */ static int test_load( FT_Face face, void* user_data ) { int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( !FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) done++; } return done; } static int test_load_advances( FT_Face face, void* user_data ) { int done = 0; FT_Fixed* advances; FT_ULong flags = *((FT_ULong*)user_data); FT_Int start, count; if ( incr_index > 0 ) { start = first_index; count = last_index - first_index + 1; } else { start = last_index; count = first_index - last_index + 1; } advances = (FT_Fixed *)calloc( sizeof ( FT_Fixed ), (size_t)count ); FT_Get_Advances( face, (FT_UInt)start, (FT_UInt)count, (FT_Int32)flags, advances ); done += (int)count; free( advances ); return done; } static int test_render( FT_Face face, void* user_data ) { int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) continue; if ( !FT_Render_Glyph( face->glyph, render_mode ) ) done++; } return done; } static int test_embolden( FT_Face face, void* user_data ) { int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) continue; FT_GlyphSlot_Embolden( face->glyph ); done++; } return done; } static int test_stroke( FT_Face face, void* user_data ) { FT_Glyph glyph; FT_Stroker stroker; int i, done = 0; FT_UNUSED( user_data ); FT_Stroker_New( lib, &stroker ); FT_Stroker_Set( stroker, face->size->metrics.y_ppem, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) || face->glyph->format != FT_GLYPH_FORMAT_OUTLINE || FT_Get_Glyph( face->glyph, &glyph ) ) continue; FT_Glyph_Stroke( &glyph, stroker, 1 ); FT_Done_Glyph( glyph ); done++; } FT_Stroker_Done( stroker ); return done; } static int test_get_glyph( FT_Face face, void* user_data ) { FT_Glyph glyph; int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) continue; if ( !FT_Get_Glyph( face->glyph, &glyph ) ) { FT_Done_Glyph( glyph ); done++; } } return done; } static int test_get_cbox( FT_Face face, void* user_data ) { FT_Glyph glyph; FT_BBox bbox; int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) continue; if ( FT_Get_Glyph( face->glyph, &glyph ) ) continue; FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_PIXELS, &bbox ); FT_Done_Glyph( glyph ); done++; } return done; } static int test_get_bbox( FT_Face face, void* user_data ) { FT_BBox bbox; int i, done = 0; FT_UNUSED( user_data ); FOREACH( i ) { if ( FT_Load_Glyph( face, (FT_UInt)i, load_flags ) ) continue; FT_Outline_Get_BBox( &face->glyph->outline, &bbox ); done++; } return done; } static int test_get_char_index( FT_Face face, void* user_data ) { bcharset_t* charset = (bcharset_t*)user_data; int i, done = 0; for ( i = 0; i < charset->size; i++ ) { if ( FT_Get_Char_Index(face, charset->code[i]) ) done++; } return done; } static int test_cmap_cache( FT_Face face, void* user_data ) { bcharset_t* charset = (bcharset_t*)user_data; int i, done = 0; FT_UNUSED( face ); for ( i = 0; i < charset->size; i++ ) { if ( FTC_CMapCache_Lookup( cmap_cache, font_type.face_id, cmap_index, charset->code[i] ) ) done++; } return done; } static int test_image_cache( FT_Face face, void* user_data ) { FT_Glyph glyph; int i, done = 0; FT_UNUSED( face ); FT_UNUSED( user_data ); FOREACH( i ) { if ( !FTC_ImageCache_Lookup( image_cache, &font_type, (FT_UInt)i, &glyph, NULL ) ) done++; } return done; } static int test_sbit_cache( FT_Face face, void* user_data ) { FTC_SBit glyph; int i, done = 0; FT_UNUSED( face ); FT_UNUSED( user_data ); FOREACH( i ) { if ( !FTC_SBitCache_Lookup( sbit_cache, &font_type, (FT_UInt)i, &glyph, NULL ) ) done++; } return done; } static int test_cmap_iter( FT_Face face, void* user_data ) { FT_UInt idx; FT_ULong charcode; int done; FT_UNUSED( user_data ); charcode = FT_Get_First_Char( face, &idx ); done = ( idx != 0 ); while ( idx != 0 ) charcode = FT_Get_Next_Char( face, charcode, &idx ); return done; } static int test_new_face( FT_Face face, void* user_data ) { FT_Face bench_face; FT_UNUSED( face ); FT_UNUSED( user_data ); if ( !get_face( &bench_face ) ) FT_Done_Face( bench_face ); return 1; } static int test_new_face_and_load_glyph( FT_Face face, void* user_data ) { FT_Face bench_face; int i, done = 0; FT_UNUSED( face ); FT_UNUSED( user_data ); if ( !get_face( &bench_face ) ) { FOREACH( i ) { if ( !FT_Load_Glyph( bench_face, (FT_UInt)i, load_flags ) ) done++; } FT_Done_Face( bench_face ); } return done; } /* * main */ static void get_charset( FT_Face face, bcharset_t* charset ) { FT_ULong charcode; int i = 0; charset->code = (FT_ULong*)calloc( (size_t)face->num_glyphs, sizeof ( FT_ULong ) ); if ( !charset->code ) return; if ( face->charmap ) { FT_UInt idx; charcode = FT_Get_First_Char( face, &idx ); /* certain fonts contain a broken charmap that will map character */ /* codes to out-of-bounds glyph indices. Take care of that here. */ /* */ while ( idx && i < face->num_glyphs ) { FT_Int gindex = (FT_Int)idx; if ( ( first_index <= gindex && gindex <= last_index ) || ( first_index >= gindex && gindex >= last_index ) ) charset->code[i++] = charcode; charcode = FT_Get_Next_Char( face, charcode, &idx ); } } else { int j; /* no charmap, do an identity mapping */ FOREACH( j ) charset->code[i++] = (FT_ULong)j; } charset->size = i; } static void header( FT_Face face ) { const FT_String* target = render_mode == FT_RENDER_MODE_NORMAL ? "normal" : render_mode == FT_RENDER_MODE_LIGHT ? "light" : render_mode == FT_RENDER_MODE_MONO ? "mono" : render_mode == FT_RENDER_MODE_LCD ? "lcd" : render_mode == FT_RENDER_MODE_LCD_V ? "lcd-v" : render_mode == FT_RENDER_MODE_SDF ? "sdf" : ""; const FT_String* module_name = FT_FACE_DRIVER_NAME( face ); const FT_String* hinting_engine = ""; FT_UInt prop; if ( !FT_IS_SCALABLE( face ) ) hinting_engine = "bitmap"; else if ( load_flags & FT_LOAD_NO_SCALE ) hinting_engine = "unscaled"; else if ( load_flags & FT_LOAD_NO_HINTING ) hinting_engine = "unhinted"; else if ( render_mode == FT_RENDER_MODE_LIGHT ) hinting_engine = "auto"; else if ( load_flags == FT_LOAD_FORCE_AUTOHINT ) hinting_engine = "auto"; else if ( !FT_Property_Get( lib, module_name, "interpreter-version", &prop ) ) { switch ( prop ) { case TT_INTERPRETER_VERSION_35: hinting_engine = "v35"; break; case TT_INTERPRETER_VERSION_40: hinting_engine = "v40"; break; } } else if ( !FT_Property_Get( lib, module_name, "hinting-engine", &prop ) ) { switch ( prop ) { case FT_HINTING_FREETYPE: hinting_engine = "FT"; break; case FT_HINTING_ADOBE: hinting_engine = "Adobe"; break; } } printf( "\n" "family: %s\n" " style: %s\n" "driver: %s %s\n" "target: %s\n" " flags: 0x%X\n" " cmap: %d\n" "glyphs: %ld\n", face->family_name, face->style_name, module_name, hinting_engine, target, load_flags, FT_Get_Charmap_Index( face->charmap ), face->num_glyphs ); } static FT_Error get_face( FT_Face* face ) { static unsigned char* memory_file = NULL; static size_t memory_size; int face_index = 0; FT_Error error; if ( preload ) { if ( !memory_file ) { FILE* file = fopen( filename, "rb" ); if ( file == NULL ) { fprintf( stderr, "couldn't find or open `%s'\n", filename ); return 1; } fseek( file, 0, SEEK_END ); memory_size = (size_t)ftell( file ); fseek( file, 0, SEEK_SET ); memory_file = (FT_Byte*)malloc( memory_size ); if ( memory_file == NULL ) { fprintf( stderr, "couldn't allocate memory to pre-load font file\n" ); return 1; } if ( !fread( memory_file, memory_size, 1, file ) ) { fprintf( stderr, "read error\n" ); free( memory_file ); memory_file = NULL; return 1; } } error = FT_New_Memory_Face( lib, memory_file, (FT_Long)memory_size, face_index, face ); } else error = FT_New_Face( lib, filename, face_index, face ); if ( error ) fprintf( stderr, "couldn't load font resource\n"); /* Set up MM_Var. */ if ( requested_cnt != 0 ) { unsigned int n; error = FT_Get_MM_Var( *face, &multimaster ); if ( error ) { fprintf( stderr, "couldn't load MultiMaster info\n" ); return error; } used_num_axes = multimaster->num_axis; for ( n = 0; n < used_num_axes; n++ ) { FT_Var_Axis* a = multimaster->axis + n; design_pos[n] = n < requested_cnt ? requested_pos[n] : a->def; if ( design_pos[n] < a->minimum ) design_pos[n] = a->minimum; else if ( design_pos[n] > a->maximum ) design_pos[n] = a->maximum; if ( !FT_IS_SFNT( *face ) ) design_pos[n] = FT_RoundFix( design_pos[n] ); } error = FT_Set_Var_Design_Coordinates( *face, used_num_axes, design_pos ); if ( error ) { fprintf( stderr, "couldn't set design coordinates\n" ); return error; } FT_Done_MM_Var( lib, multimaster ); } return error; } static void usage( void ) { int i; char interpreter_versions[32]; char hinting_engines[32]; /* we expect that at least one interpreter version is available */ if ( num_tt_interpreter_versions == 1 ) snprintf( interpreter_versions, sizeof ( interpreter_versions ), "%u", tt_interpreter_versions[0]); else snprintf( interpreter_versions, sizeof ( interpreter_versions ), "%u and %u", tt_interpreter_versions[0], tt_interpreter_versions[1] ); /* we expect that at least one hinting engine is available */ if ( num_ps_hinting_engines == 1 ) snprintf( hinting_engines, sizeof ( hinting_engines ), "`%s'", ps_hinting_engine_names[ps_hinting_engines[0]] ); else snprintf( hinting_engines, sizeof ( hinting_engines ), "`%s' and `%s'", ps_hinting_engine_names[ps_hinting_engines[0]], ps_hinting_engine_names[ps_hinting_engines[1]] ); fprintf( stderr, "\n" "ftbench: run FreeType benchmarks\n" "--------------------------------\n" "\n" "Usage: ftbench [options] fontname\n" "\n" " -a \"axis1 axis2 ...\"\n" " Specify the design coordinates for each, if any,\n" " Multiple Master axis at start-up.\n" " -C Compare with cached version (if available).\n" " -c N Use at most N iterations for each test\n" " (0 means time limited).\n" " -e E Set specific charmap index E.\n" " -f L Use hex number L as load flags (see `FT_LOAD_XXX').\n" " -H NAME Use PS hinting engine NAME.\n" " Available versions are %s; default is `%s'.\n" " -I VER Use TT interpreter version VER.\n" " Available versions are %s; default is version %u.\n" " -i I-J Forward or reverse range of glyph indices to use\n" " (default is from 0 to the number of glyphs minus one).\n" " -l N Set LCD filter to N\n" " 0: none, 1: default, 2: light, 16: legacy\n" " -m M Set maximum cache size to M KiByte (default is %d).\n", hinting_engines, ps_hinting_engine_names[dflt_ps_hinting_engine], interpreter_versions, dflt_tt_interpreter_version, CACHE_SIZE ); fprintf( stderr, " -p Preload font file in memory.\n" " -r N Set render mode to N\n" " 0: normal, 1: light, 2: mono, 3: LCD, 4: LCD vertical\n" " (default is 0).\n" " -s S Use S ppem as face size (default is %dppem).\n" " If set to zero, don't call FT_Set_Pixel_Sizes.\n" " Use value 0 with option `-f 1' or something similar to\n" " load the glyphs unscaled, otherwise errors will show up.\n", FACE_SIZE ); fprintf( stderr, " -t T Use at most T seconds per bench (default is %.0f).\n" " -w N Use N iterations for warming up before each test\n" "\n" " -b tests Perform chosen tests (default is all):\n", BENCH_TIME ); for ( i = 0; i < N_FT_BENCH; i++ ) { if ( !bench_desc[i] ) break; fprintf( stderr, " %c %s\n", 'a' + i, bench_desc[i] ); } fprintf( stderr, "\n" " -v Show version.\n" "\n" ); exit( 1 ); } #define TEST( x ) ( !test_string || strchr( test_string, (x) ) ) static void parse_design_coords( char *s ) { for ( requested_cnt = 0; requested_cnt < MAX_MM_AXES && *s; requested_cnt++ ) { requested_pos[requested_cnt] = (FT_Fixed)( strtod( s, &s ) * 65536.0 ); while ( *s==' ' ) ++s; } } int main( int argc, char** argv ) { FT_Face face; FT_Error error; btimer_t total; TIMER_RESET(&total); TIMER_START(&total); unsigned long max_bytes = CACHE_SIZE * 1024; char* test_string = NULL; unsigned int size = FACE_SIZE; int max_iter = 0; double max_time = BENCH_TIME; int warmup_iter = WARMUP_ITER; int j; unsigned int versions[2] = { TT_INTERPRETER_VERSION_35, TT_INTERPRETER_VERSION_40 }; unsigned int engines[2] = { FT_HINTING_FREETYPE, FT_HINTING_ADOBE }; int version; char *engine; #if defined _WIN32 && defined QPC LARGE_INTEGER freq; QueryPerformanceFrequency( &freq ); interval = 1e6 / freq.QuadPart; #endif if ( FT_Init_FreeType( &lib ) ) { fprintf( stderr, "could not initialize font library\n" ); return 1; } /* collect all available versions, then set again the default */ FT_Property_Get( lib, "truetype", "interpreter-version", &dflt_tt_interpreter_version ); for ( j = 0; j < 2; j++ ) { error = FT_Property_Set( lib, "truetype", "interpreter-version", &versions[j] ); if ( !error ) tt_interpreter_versions[num_tt_interpreter_versions++] = versions[j]; } FT_Property_Set( lib, "truetype", "interpreter-version", &dflt_tt_interpreter_version ); FT_Property_Get( lib, "cff", "hinting-engine", &dflt_ps_hinting_engine ); for ( j = 0; j < 2; j++ ) { error = FT_Property_Set( lib, "cff", "hinting-engine", &engines[j] ); if ( !error ) ps_hinting_engines[num_ps_hinting_engines++] = engines[j]; } FT_Property_Set( lib, "cff", "hinting-engine", &dflt_ps_hinting_engine ); FT_Property_Set( lib, "type1", "hinting-engine", &dflt_ps_hinting_engine ); FT_Property_Set( lib, "t1cid", "hinting-engine", &dflt_ps_hinting_engine ); version = (int)dflt_tt_interpreter_version; engine = ps_hinting_engine_names[dflt_ps_hinting_engine]; while ( 1 ) { int opt; opt = getopt( argc, argv, "a:b:Cc:e:f:H:I:i:l:m:pr:s:t:w:v" ); if ( opt == -1 ) break; switch ( opt ) { case 'a': parse_design_coords( optarg ); break; case 'b': test_string = optarg; break; case 'C': FTC_Manager_New( lib, 0, 0, max_bytes, face_requester, NULL, &cache_man ); break; case 'c': max_iter = atoi( optarg ); if ( max_iter < 0 ) max_iter = -max_iter; break; case 'e': cmap_index = atoi( optarg ); break; case 'f': load_flags = strtol( optarg, NULL, 16 ); break; case 'H': engine = optarg; for ( j = 0; j < num_ps_hinting_engines; j++ ) { if ( !strcmp( engine, ps_hinting_engine_names[j] ) ) { FT_Property_Set( lib, "cff", "hinting-engine", &j ); FT_Property_Set( lib, "type1", "hinting-engine", &j ); FT_Property_Set( lib, "t1cid", "hinting-engine", &j ); break; } } if ( j == num_ps_hinting_engines ) fprintf( stderr, "warning: couldn't set hinting engine\n" ); break; case 'I': version = atoi( optarg ); for ( j = 0; j < num_tt_interpreter_versions; j++ ) { if ( version == (int)tt_interpreter_versions[j] ) { FT_Property_Set( lib, "truetype", "interpreter-version", &version ); break; } } if ( j == num_tt_interpreter_versions ) fprintf( stderr, "warning: couldn't set TT interpreter version\n" ); break; case 'i': { int fi, li; if ( sscanf( optarg, "%i%*[,:-]%i", &fi, &li ) == 2 ) { first_index = fi < 0 ? 0 : fi; last_index = li < 0 ? 0 : li; } } break; case 'l': { int filter = atoi( optarg ); switch ( filter ) { case FT_LCD_FILTER_NONE: case FT_LCD_FILTER_DEFAULT: case FT_LCD_FILTER_LIGHT: case FT_LCD_FILTER_LEGACY1: case FT_LCD_FILTER_LEGACY: FT_Library_SetLcdFilter( lib, (FT_LcdFilter)filter ); } } break; case 'm': { int mb = atoi( optarg ); if ( mb > 0 ) max_bytes = (unsigned int)mb * 1024; } break; case 'p': preload = 1; break; case 'r': { int rm = atoi( optarg ); if ( rm < 0 || rm >= FT_RENDER_MODE_MAX ) render_mode = FT_RENDER_MODE_NORMAL; else render_mode = (FT_Render_Mode)rm; } break; case 's': { int sz = atoi( optarg ); /* value 0 is special */ if ( sz < 0 ) size = 1; else size = (unsigned int)sz; } break; case 't': max_time = atof( optarg ); if ( max_time < 0 ) max_time = -max_time; break; case 'v': { FT_Int major, minor, patch; FT_Library_Version( lib, &major, &minor, &patch ); printf( "ftbench (FreeType) %d.%d", major, minor ); if ( patch ) printf( ".%d", patch ); printf( "\n" ); exit( 0 ); } break; case 'w': warmup_iter = atoi( optarg ); if ( warmup_iter < 0 ) warmup_iter = -warmup_iter; break; /* break; */ default: usage(); break; } } argc -= optind; argv += optind; if ( argc != 1 ) usage(); filename = *argv; if ( get_face( &face ) ) goto Exit; j = printf( "\n" "ftbench results for font `%s'\n", filename ) - 2; while ( j-- ) putchar( '-' ); putchar( '\n' ); if ( cmap_index >= face->num_charmaps ) cmap_index = -1; if ( cmap_index >= 0 ) face->charmap = face->charmaps[cmap_index]; /* sync target and mode */ load_flags |= FT_LOAD_TARGET_( render_mode ); render_mode = (FT_Render_Mode)( ( load_flags & 0xF0000 ) >> 16 ); header( face ); if ( !face->num_glyphs ) goto Exit; if ( first_index >= face->num_glyphs ) first_index = face->num_glyphs - 1; if ( last_index >= face->num_glyphs ) last_index = face->num_glyphs - 1; incr_index = last_index > first_index ? 1 : -1; if ( size ) { if ( FT_IS_SCALABLE( face ) ) { if ( FT_Set_Pixel_Sizes( face, size, size ) ) { fprintf( stderr, "failed to set pixel size to %u\n", size ); return 1; } } else { size = (unsigned int)face->available_sizes[0].size >> 6; fprintf( stderr, "using size of first bitmap strike (%upx)\n", size ); FT_Select_Size( face, 0 ); } } if ( cache_man ) { font_type.face_id = (FTC_FaceID)1; font_type.width = size; font_type.height = size; font_type.flags = load_flags; } printf( "\n" "font preloading into memory: %s\n" "maximum cache size: %lu KiByte\n", preload ? "yes" : face->stream->base ? "mapped" : "no", max_bytes / 1024 ); printf( "\n" "testing glyph indices from %d to %d at %u ppem\n" "number of seconds for each test: %s%g\n", first_index, last_index, size, max_iter ? "at most " : "", max_time ); if ( max_iter ) printf( "number of iterations for each test: at most %d\n", max_iter ); printf( "\n" "number of iterations as warmup in all tests: %d\n", warmup_iter ); printf( "\n" "executing tests:\n" ); for ( j = 0; j < N_FT_BENCH; j++ ) { btest_t test; FT_ULong flags; if ( !TEST( 'a' + j ) ) continue; test.title = NULL; test.bench = NULL; test.cache_first = 0; test.user_data = NULL; switch ( j ) { case FT_BENCH_LOAD_GLYPH: test.title = "Load"; test.bench = test_load; benchmark( face, &test, max_iter, max_time, warmup_iter ); if ( cache_man ) { test.cache_first = 1; if ( !FTC_ImageCache_New( cache_man, &image_cache ) ) { test.title = "Load (image cached)"; test.bench = test_image_cache; benchmark( face, &test, max_iter, max_time, warmup_iter ); } if ( !FTC_SBitCache_New( cache_man, &sbit_cache ) ) { test.title = "Load (sbit cached)"; test.bench = test_sbit_cache; if ( size ) benchmark( face, &test, max_iter, max_time, warmup_iter ); else printf( " %-25s disabled (size = 0)\n", test.title ); } } break; case FT_BENCH_LOAD_ADVANCES: test.user_data = &flags; test.title = "Load_Advances (Normal)"; test.bench = test_load_advances; flags = FT_LOAD_DEFAULT; benchmark( face, &test, max_iter, max_time, warmup_iter ); test.title = "Load_Advances (Fast)"; test.bench = test_load_advances; flags = FT_LOAD_TARGET_LIGHT; benchmark( face, &test, max_iter, max_time, warmup_iter ); test.title = "Load_Advances (Unscaled)"; test.bench = test_load_advances; flags = FT_LOAD_NO_SCALE; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; case FT_BENCH_RENDER: test.title = "Render"; test.bench = test_render; if ( size ) benchmark( face, &test, max_iter, max_time, warmup_iter ); else printf( " %-25s disabled (size = 0)\n", test.title ); break; case FT_BENCH_GET_GLYPH: test.title = "Get_Glyph"; test.bench = test_get_glyph; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; case FT_BENCH_GET_CBOX: test.title = "Get_CBox"; test.bench = test_get_cbox; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; case FT_BENCH_GET_BBOX: test.title = "Get_BBox"; test.bench = test_get_bbox; { FT_Matrix rot30 = { 0xDDB4, -0x8000, 0x8000, 0xDDB4 }; /* rotate outlines by 30 degrees so that CBox and BBox differ */ FT_Set_Transform( face, &rot30, NULL ); benchmark( face, &test, max_iter, max_time, warmup_iter ); FT_Set_Transform( face, NULL, NULL ); } break; case FT_BENCH_CMAP: { bcharset_t charset; get_charset( face, &charset ); if ( charset.code ) { test.user_data = (void*)&charset; test.title = "Get_Char_Index"; test.bench = test_get_char_index; benchmark( face, &test, max_iter, max_time, warmup_iter ); if ( cache_man && !FTC_CMapCache_New( cache_man, &cmap_cache ) ) { test.cache_first = 1; test.title = "Get_Char_Index (cached)"; test.bench = test_cmap_cache; benchmark( face, &test, max_iter, max_time, warmup_iter ); } free( charset.code ); } } break; case FT_BENCH_CMAP_ITER: test.title = "Iterate CMap"; test.bench = test_cmap_iter; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; case FT_BENCH_NEW_FACE: test.title = "New_Face"; test.bench = test_new_face; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; case FT_BENCH_EMBOLDEN: test.title = "Embolden"; test.bench = test_embolden; if ( size ) benchmark( face, &test, max_iter, max_time, warmup_iter ); else printf( " %-25s disabled (size = 0)\n", test.title ); break; case FT_BENCH_STROKE: test.title = "Stroke"; test.bench = test_stroke; if ( size ) benchmark( face, &test, max_iter, max_time, warmup_iter ); else printf( " %-25s disabled (size = 0)\n", test.title ); break; case FT_BENCH_NEW_FACE_AND_LOAD_GLYPH: test.title = "New_Face & load glyph(s)"; test.bench = test_new_face_and_load_glyph; benchmark( face, &test, max_iter, max_time, warmup_iter ); break; } } TIMER_STOP(&total); double total_time = TIMER_GET(&total); printf("\nTotal time: %.0fs\n", total_time/1000000); if ( cache_man ) FTC_Manager_Done( cache_man ); Exit: /* releases any remaining FT_Face object too */ FT_Done_FreeType( lib ); return 0; } /* End */