| #include "hb-benchmark.hh" |
| |
| #include <glib.h> |
| |
| #define SUBSET_FONT_BASE_PATH "test/subset/data/fonts/" |
| |
| static hb_variation_t default_variations[] = { |
| hb_variation_t {HB_TAG ('w','g','h','t'), 500}, |
| hb_variation_t {HB_TAG_NONE, 0} |
| }; |
| |
| static unsigned max_num_glyphs = 0; |
| static const char *text = " "; |
| |
| static struct test_input_t |
| { |
| hb_variation_t *variations; |
| const char *font_path; |
| } default_tests[] = |
| { |
| {nullptr, SUBSET_FONT_BASE_PATH "Roboto-Regular.ttf"}, |
| {default_variations, SUBSET_FONT_BASE_PATH "RobotoFlex-Variable.ttf"}, |
| {default_variations, SUBSET_FONT_BASE_PATH "SourceSansPro-Regular.otf"}, |
| {default_variations, SUBSET_FONT_BASE_PATH "AdobeVFPrototype.otf"}, |
| {default_variations, SUBSET_FONT_BASE_PATH "SourceSerifVariable-Roman.ttf"}, |
| {nullptr, SUBSET_FONT_BASE_PATH "Comfortaa-Regular-new.ttf"}, |
| {nullptr, SUBSET_FONT_BASE_PATH "NotoNastaliqUrdu-Regular.ttf"}, |
| {nullptr, SUBSET_FONT_BASE_PATH "NotoSerifMyanmar-Regular.otf"}, |
| }; |
| |
| static test_input_t *tests = default_tests; |
| static unsigned num_tests = sizeof (default_tests) / sizeof (default_tests[0]); |
| |
| enum operation_t |
| { |
| nominal_glyphs, |
| glyph_h_advances, |
| glyph_v_advances, |
| glyph_v_origins, |
| glyph_extents, |
| draw_glyph, |
| paint_glyph, |
| load_face_and_shape, |
| }; |
| |
| static void |
| _hb_move_to (hb_draw_funcs_t *, void *draw_data, hb_draw_state_t *, float x, float y, void *) |
| { |
| float &i = * (float *) draw_data; |
| i += x + y; |
| } |
| |
| static void |
| _hb_line_to (hb_draw_funcs_t *, void *draw_data, hb_draw_state_t *, float x, float y, void *) |
| { |
| float &i = * (float *) draw_data; |
| i += x + y; |
| } |
| |
| static void |
| _hb_quadratic_to (hb_draw_funcs_t *, void *draw_data, hb_draw_state_t *, float cx, float cy, float x, float y, void *) |
| { |
| float &i = * (float *) draw_data; |
| i += cx + cy + x + y; |
| } |
| |
| static void |
| _hb_cubic_to (hb_draw_funcs_t *, void *draw_data, hb_draw_state_t *, float cx1, float cy1, float cx2, float cy2, float x, float y, void *) |
| { |
| float &i = * (float *) draw_data; |
| i += cx1 + cy1 + cx2 + cy2 + x + y; |
| } |
| |
| static void |
| _hb_close_path (hb_draw_funcs_t *, void *draw_data, hb_draw_state_t *, void *) |
| { |
| float &i = * (float *) draw_data; |
| i += 1.0; |
| } |
| |
| static hb_draw_funcs_t * |
| _draw_funcs_create (void) |
| { |
| hb_draw_funcs_t *draw_funcs = hb_draw_funcs_create (); |
| hb_draw_funcs_set_move_to_func (draw_funcs, _hb_move_to, nullptr, nullptr); |
| hb_draw_funcs_set_line_to_func (draw_funcs, _hb_line_to, nullptr, nullptr); |
| hb_draw_funcs_set_quadratic_to_func (draw_funcs, _hb_quadratic_to, nullptr, nullptr); |
| hb_draw_funcs_set_cubic_to_func (draw_funcs, _hb_cubic_to, nullptr, nullptr); |
| hb_draw_funcs_set_close_path_func (draw_funcs, _hb_close_path, nullptr, nullptr); |
| return draw_funcs; |
| } |
| |
| static void BM_Font (benchmark::State &state, |
| const hb_variation_t *variations, const char * backend, |
| operation_t operation, |
| const test_input_t &test_input) |
| { |
| hb_font_t *font; |
| unsigned num_glyphs; |
| { |
| hb_face_t *face = hb_benchmark_face_create_from_file_or_fail (test_input.font_path, 0); |
| assert (face); |
| num_glyphs = hb_face_get_glyph_count (face); |
| if (max_num_glyphs && num_glyphs > max_num_glyphs) |
| num_glyphs = max_num_glyphs; |
| font = hb_font_create (face); |
| hb_face_destroy (face); |
| } |
| |
| if (variations) |
| { |
| unsigned count = 0; |
| for (const hb_variation_t *v = variations; v->tag != HB_TAG_NONE; v++) |
| count++; |
| hb_font_set_variations (font, variations, count); |
| } |
| |
| bool ret = hb_font_set_funcs_using (font, backend); |
| if (!ret) |
| { |
| state.SkipWithError("Backend failed to initialize for font."); |
| hb_font_destroy (font); |
| return; |
| } |
| |
| switch (operation) |
| { |
| case nominal_glyphs: |
| { |
| hb_set_t *set = hb_set_create (); |
| hb_face_collect_unicodes (hb_font_get_face (font), set); |
| unsigned pop = hb_set_get_population (set); |
| hb_codepoint_t *unicodes = (hb_codepoint_t *) calloc (pop, sizeof (hb_codepoint_t)); |
| hb_codepoint_t *glyphs = (hb_codepoint_t *) calloc (pop, sizeof (hb_codepoint_t)); |
| |
| hb_codepoint_t *p = unicodes; |
| for (hb_codepoint_t u = HB_SET_VALUE_INVALID; |
| hb_set_next (set, &u);) |
| *p++ = u; |
| assert (p == unicodes + pop); |
| |
| for (auto _ : state) |
| hb_font_get_nominal_glyphs (font, |
| pop, |
| unicodes, sizeof (*unicodes), |
| glyphs, sizeof (*glyphs)); |
| |
| free (glyphs); |
| free (unicodes); |
| hb_set_destroy (set); |
| break; |
| } |
| case glyph_h_advances: |
| { |
| hb_codepoint_t *glyphs = (hb_codepoint_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| hb_position_t *advances = (hb_position_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| |
| for (unsigned g = 0; g < num_glyphs; g++) |
| glyphs[g] = g; |
| |
| for (auto _ : state) |
| hb_font_get_glyph_h_advances (font, |
| num_glyphs, |
| glyphs, sizeof (*glyphs), |
| advances, sizeof (*advances)); |
| |
| free (advances); |
| free (glyphs); |
| break; |
| } |
| case glyph_v_advances: |
| { |
| hb_codepoint_t *glyphs = (hb_codepoint_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| hb_position_t *advances = (hb_position_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| |
| for (unsigned g = 0; g < num_glyphs; g++) |
| glyphs[g] = g; |
| |
| for (auto _ : state) |
| hb_font_get_glyph_v_advances (font, |
| num_glyphs, |
| glyphs, sizeof (*glyphs), |
| advances, sizeof (*advances)); |
| |
| free (advances); |
| free (glyphs); |
| break; |
| } |
| case glyph_v_origins: |
| { |
| hb_codepoint_t *glyphs = (hb_codepoint_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| hb_position_t *origins_x = (hb_position_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| hb_position_t *origins_y = (hb_position_t *) calloc (num_glyphs, sizeof (hb_codepoint_t)); |
| |
| for (unsigned g = 0; g < num_glyphs; g++) |
| glyphs[g] = g; |
| |
| for (auto _ : state) |
| hb_font_get_glyph_v_origins (font, |
| num_glyphs, |
| glyphs, sizeof (*glyphs), |
| origins_x, sizeof (*origins_x), |
| origins_y, sizeof (*origins_y)); |
| |
| free (origins_y); |
| free (origins_x); |
| free (glyphs); |
| break; |
| } |
| case glyph_extents: |
| { |
| hb_glyph_extents_t extents; |
| for (auto _ : state) |
| for (unsigned gid = 0; gid < num_glyphs; ++gid) |
| hb_font_get_glyph_extents (font, gid, &extents); |
| break; |
| } |
| case draw_glyph: |
| { |
| hb_draw_funcs_t *draw_funcs = _draw_funcs_create (); |
| for (auto _ : state) |
| { |
| float i = 0; |
| for (unsigned gid = 0; gid < num_glyphs; ++gid) |
| hb_font_draw_glyph (font, gid, draw_funcs, &i); |
| } |
| hb_draw_funcs_destroy (draw_funcs); |
| break; |
| } |
| case paint_glyph: |
| { |
| hb_paint_funcs_t *paint_funcs = hb_paint_funcs_create (); |
| for (auto _ : state) |
| { |
| for (unsigned gid = 0; gid < num_glyphs; ++gid) |
| hb_font_paint_glyph (font, gid, paint_funcs, nullptr, 0, 0); |
| } |
| hb_paint_funcs_destroy (paint_funcs); |
| break; |
| } |
| case load_face_and_shape: |
| { |
| for (auto _ : state) |
| { |
| hb_face_t *face = hb_benchmark_face_create_from_file_or_fail (test_input.font_path, 0); |
| assert (face); |
| hb_font_t *font = hb_font_create (face); |
| hb_face_destroy (face); |
| |
| bool ret = hb_font_set_funcs_using (font, backend); |
| if (!ret) |
| { |
| state.SkipWithError("Backend failed to initialize for font."); |
| hb_font_destroy (font); |
| return; |
| } |
| |
| hb_buffer_t *buffer = hb_buffer_create (); |
| hb_buffer_add_utf8 (buffer, text, -1, 0, -1); |
| hb_buffer_guess_segment_properties (buffer); |
| |
| hb_shape (font, buffer, nullptr, 0); |
| |
| hb_buffer_destroy (buffer); |
| hb_font_destroy (font); |
| } |
| break; |
| } |
| } |
| |
| |
| hb_font_destroy (font); |
| } |
| |
| static void test_backend (const char *backend, |
| const hb_variation_t *variations, |
| operation_t op, |
| const char *op_name, |
| benchmark::TimeUnit time_unit, |
| const test_input_t &test_input) |
| { |
| char name[1024] = "BM_Font/"; |
| strcat (name, op_name); |
| strcat (name, "/"); |
| const char *p = strrchr (test_input.font_path, '/'); |
| strcat (name, p ? p + 1 : test_input.font_path); |
| strcat (name, variations ? "/var" : ""); |
| strcat (name, "/"); |
| strcat (name, backend); |
| |
| benchmark::RegisterBenchmark (name, BM_Font, variations, backend, op, test_input) |
| ->Unit(time_unit); |
| } |
| |
| static void test_operation (operation_t op, |
| const char *op_name, |
| benchmark::TimeUnit time_unit) |
| { |
| const char **supported_backends = hb_font_list_funcs (); |
| for (unsigned i = 0; i < num_tests; i++) |
| { |
| auto& test_input = tests[i]; |
| for (int variable = 0; variable < int (test_input.variations != nullptr) + 1; variable++) |
| { |
| const hb_variation_t *variations = variable ? test_input.variations : nullptr; |
| for (const char **backend = supported_backends; *backend; backend++) |
| test_backend (*backend, variations, op, op_name, time_unit, test_input); |
| } |
| } |
| } |
| |
| static const char *font_file = nullptr; |
| static const char *variations_str = nullptr; |
| |
| static GOptionEntry entries[] = |
| { |
| {"font-file", 0, 0, G_OPTION_ARG_STRING, &font_file, "Font file-path to benchmark", "FONTFILE"}, |
| {"variations", 0, 0, G_OPTION_ARG_STRING, &variations_str, "Variations to apply", "VAR"}, |
| {"max-glyphs", 0, 0, G_OPTION_ARG_INT, &max_num_glyphs, "Maximum number of glyphs to process", "NUM"}, |
| {"text", 0, 0, G_OPTION_ARG_STRING, &text, "Text to use for load_face_and_shape test", "TEXT"}, |
| {nullptr} |
| }; |
| |
| static void print_usage (const char *prgname) |
| { |
| g_print ("Usage: %s [OPTIONS] [FONTFILE]\n", prgname); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| const char *prgname = g_path_get_basename (argv[0]); |
| |
| GOptionContext *context = g_option_context_new (""); |
| g_option_context_set_summary (context, "Benchmark font operations"); |
| g_option_context_set_description (context, "Benchmark Options:"); |
| g_option_context_add_main_entries (context, entries, nullptr); |
| |
| bool show_help = false; |
| for (int i = 1; i < argc; i++) |
| { |
| if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) |
| { |
| argv[i] = (char *) "--help"; // Ensure it is recognized by both google-benchmark |
| show_help = true; |
| break; |
| } |
| } |
| if (show_help) |
| { |
| print_usage (prgname); |
| |
| gchar *help_text = g_option_context_get_help (context, false, nullptr); |
| help_text = strstr (help_text, "\n\n"); |
| g_print ("%s", help_text); |
| |
| benchmark::Initialize(&argc, argv); // This shows the help for google-benchmark |
| } |
| |
| benchmark::Initialize(&argc, argv); |
| g_option_context_parse (context, &argc, &argv, nullptr); |
| g_option_context_free (context); |
| |
| argc--; |
| argv++; |
| if (!font_file && argc) |
| { |
| font_file = *argv; |
| argc--; |
| argv++; |
| } |
| |
| if (argc) |
| { |
| g_printerr ("Unexpected arguments: "); |
| for (int i = 0; i < argc; i++) |
| g_printerr ("%s ", argv[i]); |
| g_printerr ("\n\n"); |
| print_usage (prgname); |
| return 1; |
| } |
| |
| if (font_file) |
| { |
| unsigned num_variations = 0; |
| if (variations_str) |
| { |
| const char *p = variations_str; |
| while (p && *p) |
| { |
| num_variations++; |
| p = strchr (p, ','); |
| if (p) p++; |
| } |
| } |
| hb_variation_t *variations = variations_str ? (hb_variation_t *) calloc (num_variations, sizeof (hb_variation_t)) : nullptr; |
| if (variations_str) |
| { |
| const char *p = variations_str; |
| for (unsigned i = 0; i < num_variations; i++) |
| { |
| const char *end = strchr (p, ','); |
| if (end) |
| { |
| hb_variation_from_string (p, end - p, &variations[i]); |
| p = end + 1; |
| } |
| else |
| { |
| hb_variation_from_string (p, -1, &variations[i]); |
| p = nullptr; |
| } |
| } |
| } |
| |
| num_tests = 1; |
| tests = (test_input_t *) calloc (num_tests, sizeof (test_input_t)); |
| tests[0].variations = variations_str ? variations : default_variations; |
| tests[0].font_path = font_file; |
| } |
| |
| #define TEST_OPERATION(op, time_unit) test_operation (op, #op, time_unit) |
| |
| TEST_OPERATION (nominal_glyphs, benchmark::kMicrosecond); |
| TEST_OPERATION (glyph_h_advances, benchmark::kMicrosecond); |
| TEST_OPERATION (glyph_v_advances, benchmark::kMicrosecond); |
| TEST_OPERATION (glyph_v_origins, benchmark::kMicrosecond); |
| TEST_OPERATION (glyph_extents, benchmark::kMicrosecond); |
| TEST_OPERATION (draw_glyph, benchmark::kMillisecond); |
| TEST_OPERATION (paint_glyph, benchmark::kMillisecond); |
| TEST_OPERATION (load_face_and_shape, benchmark::kMicrosecond); |
| |
| #undef TEST_OPERATION |
| |
| benchmark::RunSpecifiedBenchmarks(); |
| benchmark::Shutdown(); |
| |
| if (tests != default_tests) |
| { |
| if (tests[0].variations != default_variations) |
| free (tests[0].variations); |
| free (tests); |
| } |
| } |
| |