diff --git a/.circleci/config.yml b/.circleci/config.yml index 75390c1f2..4f4592e2a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -226,7 +226,9 @@ jobs: # test a meson based dist - run: meson dist -Cbuild && rm -rf build # test experimental APIs - - run: meson build -Dexperimental_api=true && ninja -Cbuild test && rm -rf build + - run: meson build -Dexperimental_api=true -Dbenchmark=true -Dexperimental_api=true -Doptimization=2 && ninja -Cbuild test # or meson test -Cbuild + # run benchmarks + - run: build/perf/perf && meson test -Cbuild --benchmark && rm -rf build # or ninja -Cbuild benchmark # mingw - run: .ci/build-win32.sh - store_artifacts: diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 85068be92..a731233a7 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -17,7 +17,7 @@ jobs: - name: install meson and fonttools run: sudo pip3 install meson fonttools - name: run - run: meson build -Db_coverage=true -Dglib=enabled -Dcairo=enabled -Dicu=enabled -Dgraphite=enabled -Dfreetype=enabled + run: meson build -Db_coverage=true -Dglib=enabled -Dcairo=enabled -Dicu=enabled -Dgraphite=enabled -Dfreetype=enabled -Dbenchmark=true -Doptimization=2 - name: ci run: meson test --print-errorlogs -Cbuild @@ -30,6 +30,23 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} REVISION: ${{ github.sha }} + # waiting for https://github.com/rhysd/github-action-benchmark/issues/36 to happen + # - name: benchmark + # run: build/perf/perf --benchmark_format=json > perf/result.json + # - name: store benchmark result + # uses: rhysd/github-action-benchmark@b2ee598 + # if: github.event_name != 'pull_request' + # with: + # name: C++ Benchmark + # tool: 'googlecpp' + # output-file-path: perf/result.json + # gh-pages-branch: gh-pages + # github-token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} + # auto-push: true + # alert-threshold: '150%' + # comment-on-alert: true + # fail-on-alert: true + - name: cov run: ninja -Cbuild coverage - uses: codecov/codecov-action@v1 diff --git a/meson.build b/meson.build index 662fe03cc..c6d260d80 100644 --- a/meson.build +++ b/meson.build @@ -345,6 +345,10 @@ if not get_option('tests').disabled() subdir('test') endif +if get_option('benchmark') + subdir('perf') +endif + if not get_option('gtk_doc').disabled() subdir('docs') endif diff --git a/meson_options.txt b/meson_options.txt index b9102a80d..a61de2d3c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -28,6 +28,8 @@ option('introspection', type: 'feature', value: 'auto', yield: true, option('gtk_doc', type: 'feature', value: 'auto', yield: true, description: 'Generate documentation with gtk-doc') +option('benchmark', type : 'boolean', value : false, + description: 'Enable benchmark tests') option('icu_builtin', type: 'boolean', value: false, description: 'Don\'t separate ICU support as harfbuzz-icu module') option('experimental_api', type: 'boolean', value: false, diff --git a/perf/meson.build b/perf/meson.build new file mode 100644 index 000000000..da0ec1969 --- /dev/null +++ b/perf/meson.build @@ -0,0 +1,9 @@ +google_benchmark = subproject('google-benchmark') +google_benchmark_dep = google_benchmark.get_variable('google_benchmark_dep') + +benchmark('perf', executable('perf', 'perf.cc', + dependencies : [google_benchmark_dep, freetype_dep], + include_directories: [incconfig, incsrc], + link_with: [libharfbuzz], + install: false, +), workdir: meson.current_source_dir() / '..', timeout: 100) diff --git a/perf/perf-draw.hh b/perf/perf-draw.hh new file mode 100644 index 000000000..0173b719a --- /dev/null +++ b/perf/perf-draw.hh @@ -0,0 +1,128 @@ +#include "benchmark/benchmark.h" + +#include "hb.h" +#include "hb-ot.h" +#include "hb-ft.h" +#include FT_OUTLINE_H + +#define HB_UNUSED __attribute__((unused)) + +static void +_hb_move_to (hb_position_t to_x HB_UNUSED, hb_position_t to_y HB_UNUSED, void *user_data HB_UNUSED) {} + +static void +_hb_line_to (hb_position_t to_x HB_UNUSED, hb_position_t to_y HB_UNUSED, void *user_data HB_UNUSED) {} + +static void +_hb_quadratic_to (hb_position_t control_x HB_UNUSED, hb_position_t control_y HB_UNUSED, + hb_position_t to_x HB_UNUSED, hb_position_t to_y HB_UNUSED, + void *user_data HB_UNUSED) {} + +static void +_hb_cubic_to (hb_position_t control1_x HB_UNUSED, hb_position_t control1_y HB_UNUSED, + hb_position_t control2_x HB_UNUSED, hb_position_t control2_y HB_UNUSED, + hb_position_t to_x HB_UNUSED, hb_position_t to_y HB_UNUSED, + void *user_data HB_UNUSED) {} + +static void +_hb_close_path (void *user_data HB_UNUSED) {} + +static void +_ft_move_to (const FT_Vector* to HB_UNUSED, void* user HB_UNUSED) {} + +static void +_ft_line_to (const FT_Vector* to HB_UNUSED, void* user HB_UNUSED) {} + +static void +_ft_conic_to (const FT_Vector* control HB_UNUSED, const FT_Vector* to HB_UNUSED, + void* user HB_UNUSED) {} + +static void +_ft_cubic_to (const FT_Vector* control1 HB_UNUSED, const FT_Vector* control2 HB_UNUSED, + const FT_Vector* to HB_UNUSED, void* user HB_UNUSED) {} + + +static void draw (benchmark::State &state, const char *font_path, bool is_var, bool is_ft) +{ + hb_font_t *font; + unsigned num_glyphs; + { + hb_blob_t *blob = hb_blob_create_from_file (font_path); + assert (hb_blob_get_length (blob)); + hb_face_t *face = hb_face_create (blob, 0); + hb_blob_destroy (blob); + num_glyphs = hb_face_get_glyph_count (face); + font = hb_font_create (face); + hb_face_destroy (face); + } + + if (is_var) + { + hb_variation_t wght = {HB_TAG ('w','g','h','t'), 500}; + hb_font_set_variations (font, &wght, 1); + } + + if (is_ft) + { + FT_Face ft_face = hb_ft_font_get_face (font); + hb_ft_font_set_load_flags (font, FT_LOAD_NO_HINTING | FT_LOAD_NO_SCALE); + + FT_Outline_Funcs draw_funcs; + draw_funcs.move_to = (FT_Outline_MoveToFunc) _ft_move_to; + draw_funcs.line_to = (FT_Outline_LineToFunc) _ft_line_to; + draw_funcs.conic_to = (FT_Outline_ConicToFunc) _ft_conic_to; + draw_funcs.cubic_to = (FT_Outline_CubicToFunc) _ft_cubic_to; + draw_funcs.shift = 0; + draw_funcs.delta = 0; + + for (auto _ : state) + for (unsigned gid = 0; gid < num_glyphs; ++gid) + { + FT_Load_Glyph (ft_face, gid, FT_LOAD_NO_HINTING | FT_LOAD_NO_SCALE); + FT_Outline_Decompose (&ft_face->glyph->outline, &draw_funcs, nullptr); + } + } + else + { + hb_draw_funcs_t *draw_funcs = hb_draw_funcs_create (); + hb_draw_funcs_set_move_to_func (draw_funcs, _hb_move_to); + hb_draw_funcs_set_line_to_func (draw_funcs, _hb_line_to); + hb_draw_funcs_set_quadratic_to_func (draw_funcs, _hb_quadratic_to); + hb_draw_funcs_set_cubic_to_func (draw_funcs, _hb_cubic_to); + hb_draw_funcs_set_close_path_func (draw_funcs, _hb_close_path); + + for (auto _ : state) + for (unsigned gid = 0; gid < num_glyphs; ++gid) + hb_font_draw_glyph (font, gid, draw_funcs, nullptr); + + hb_draw_funcs_destroy (draw_funcs); + } + + hb_font_destroy (font); +} + +#define FONT_BASE_PATH "test/subset/data/fonts/" + +BENCHMARK_CAPTURE (draw, cff - ot - SourceSansPro, FONT_BASE_PATH "SourceSansPro-Regular.otf", false, false); +BENCHMARK_CAPTURE (draw, cff - ft - SourceSansPro, FONT_BASE_PATH "SourceSansPro-Regular.otf", false, true); + +BENCHMARK_CAPTURE (draw, cff2 - ot - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", false, false); +BENCHMARK_CAPTURE (draw, cff2 - ft - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", false, true); + +BENCHMARK_CAPTURE (draw, cff2/vf - ot - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", true, false); +BENCHMARK_CAPTURE (draw, cff2/vf - ft - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", true, true); + +BENCHMARK_CAPTURE (draw, glyf - ot - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", false, false); +BENCHMARK_CAPTURE (draw, glyf - ft - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", false, true); + +BENCHMARK_CAPTURE (draw, glyf/vf - ot - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", true, false); +BENCHMARK_CAPTURE (draw, glyf/vf - ft - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", true, true); + +BENCHMARK_CAPTURE (draw, glyf - ot - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", false, false); +BENCHMARK_CAPTURE (draw, glyf - ft - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", false, true); + +BENCHMARK_CAPTURE (draw, glyf/vf - ot - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", true, false); +BENCHMARK_CAPTURE (draw, glyf/vf - ft - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", true, true); + +BENCHMARK_CAPTURE (draw, glyf - ot - Roboto, FONT_BASE_PATH "Roboto-Regular.ttf", false, false); +BENCHMARK_CAPTURE (draw, glyf - ft - Roboto, FONT_BASE_PATH "Roboto-Regular.ttf", false, true); diff --git a/perf/perf-extents.hh b/perf/perf-extents.hh new file mode 100644 index 000000000..cb4e17d8d --- /dev/null +++ b/perf/perf-extents.hh @@ -0,0 +1,65 @@ +#include "benchmark/benchmark.h" + +#include "hb.h" +#include "hb-ft.h" +#include "hb-ot.h" + +static void extents (benchmark::State &state, const char *font_path, bool is_var, bool is_ft) +{ + hb_font_t *font; + unsigned num_glyphs; + { + hb_blob_t *blob = hb_blob_create_from_file (font_path); + assert (hb_blob_get_length (blob)); + hb_face_t *face = hb_face_create (blob, 0); + hb_blob_destroy (blob); + num_glyphs = hb_face_get_glyph_count (face); + font = hb_font_create (face); + hb_face_destroy (face); + } + + if (is_var) + { + hb_variation_t wght = {HB_TAG ('w','g','h','t'), 500}; + hb_font_set_variations (font, &wght, 1); + } + + if (is_ft) + { + hb_ft_font_set_funcs (font); + hb_ft_font_set_load_flags (font, FT_LOAD_NO_HINTING | FT_LOAD_NO_SCALE); + } + + hb_glyph_extents_t extents; + for (auto _ : state) + for (unsigned gid = 0; gid < num_glyphs; ++gid) + hb_font_get_glyph_extents (font, gid, &extents); + + hb_font_destroy (font); +} + +#define FONT_BASE_PATH "test/subset/data/fonts/" + +BENCHMARK_CAPTURE (extents, cff - ot - SourceSansPro, FONT_BASE_PATH "SourceSansPro-Regular.otf", false, false); +BENCHMARK_CAPTURE (extents, cff - ft - SourceSansPro, FONT_BASE_PATH "SourceSansPro-Regular.otf", false, true); + +BENCHMARK_CAPTURE (extents, cff2 - ot - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", false, false); +BENCHMARK_CAPTURE (extents, cff2 - ft - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", false, true); + +BENCHMARK_CAPTURE (extents, cff2/vf - ot - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", true, false); +BENCHMARK_CAPTURE (extents, cff2/vf - ft - AdobeVFPrototype, FONT_BASE_PATH "AdobeVFPrototype.otf", true, true); + +BENCHMARK_CAPTURE (extents, glyf - ot - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", false, false); +BENCHMARK_CAPTURE (extents, glyf - ft - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", false, true); + +BENCHMARK_CAPTURE (extents, glyf/vf - ot - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", true, false); +BENCHMARK_CAPTURE (extents, glyf/vf - ft - SourceSerifVariable, FONT_BASE_PATH "SourceSerifVariable-Roman.ttf", true, true); + +BENCHMARK_CAPTURE (extents, glyf - ot - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", false, false); +BENCHMARK_CAPTURE (extents, glyf - ft - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", false, true); + +BENCHMARK_CAPTURE (extents, glyf/vf - ot - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", true, false); +BENCHMARK_CAPTURE (extents, glyf/vf - ft - Comfortaa, FONT_BASE_PATH "Comfortaa-Regular-new.ttf", true, true); + +BENCHMARK_CAPTURE (extents, glyf - ot - Roboto, FONT_BASE_PATH "Roboto-Regular.ttf", false, false); +BENCHMARK_CAPTURE (extents, glyf - ft - Roboto, FONT_BASE_PATH "Roboto-Regular.ttf", false, true); diff --git a/perf/perf-shaping.hh b/perf/perf-shaping.hh new file mode 100644 index 000000000..73efd0a7e --- /dev/null +++ b/perf/perf-shaping.hh @@ -0,0 +1,65 @@ +#include "benchmark/benchmark.h" + +#include "hb.h" + +static void shape (benchmark::State &state, const char *text_path, + hb_direction_t direction, hb_script_t script, + const char *font_path) +{ + hb_font_t *font; + { + hb_blob_t *blob = hb_blob_create_from_file (font_path); + assert (hb_blob_get_length (blob)); + hb_face_t *face = hb_face_create (blob, 0); + hb_blob_destroy (blob); + font = hb_font_create (face); + hb_face_destroy (face); + } + + hb_blob_t *text_blob = hb_blob_create_from_file (text_path); + unsigned text_length; + const char *text = hb_blob_get_data (text_blob, &text_length); + assert (text_length); + + hb_buffer_t *buf = hb_buffer_create (); + for (auto _ : state) + { + hb_buffer_add_utf8 (buf, text, text_length, 0, -1); + hb_buffer_set_direction (buf, direction); + hb_buffer_set_script (buf, script); + hb_shape (font, buf, nullptr, 0); + hb_buffer_clear_contents (buf); + } + hb_buffer_destroy (buf); + + hb_blob_destroy (text_blob); + hb_font_destroy (font); +} + +BENCHMARK_CAPTURE (shape, fa-thelittleprince.txt - Amiri, + "perf/texts/fa-thelittleprince.txt", + HB_DIRECTION_RTL, HB_SCRIPT_ARABIC, + "perf/fonts/Amiri-Regular.ttf"); +BENCHMARK_CAPTURE (shape, fa-thelittleprince.txt - NotoNastaliqUrdu, + "perf/texts/fa-thelittleprince.txt", + HB_DIRECTION_RTL, HB_SCRIPT_ARABIC, + "perf/fonts/NotoNastaliqUrdu-Regular.ttf"); + +BENCHMARK_CAPTURE (shape, fa-monologue.txt - Amiri, + "perf/texts/fa-monologue.txt", + HB_DIRECTION_RTL, HB_SCRIPT_ARABIC, + "perf/fonts/Amiri-Regular.ttf"); +BENCHMARK_CAPTURE (shape, fa-monologue.txt - NotoNastaliqUrdu, + "perf/texts/fa-monologue.txt", + HB_DIRECTION_RTL, HB_SCRIPT_ARABIC, + "perf/fonts/NotoNastaliqUrdu-Regular.ttf"); + +BENCHMARK_CAPTURE (shape, en-thelittleprince.txt - Roboto, + "perf/texts/en-thelittleprince.txt", + HB_DIRECTION_LTR, HB_SCRIPT_LATIN, + "perf/fonts/Roboto-Regular.ttf"); + +BENCHMARK_CAPTURE (shape, en-words.txt - Roboto, + "perf/texts/en-words.txt", + HB_DIRECTION_LTR, HB_SCRIPT_LATIN, + "perf/fonts/Roboto-Regular.ttf"); diff --git a/perf/perf.cc b/perf/perf.cc new file mode 100644 index 000000000..893fedc0f --- /dev/null +++ b/perf/perf.cc @@ -0,0 +1,15 @@ +#include "benchmark/benchmark.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "perf-shaping.hh" +#ifdef HAVE_FREETYPE +#include "perf-extents.hh" +#ifdef HB_EXPERIMENTAL_API +#include "perf-draw.hh" +#endif +#endif + +BENCHMARK_MAIN (); diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 04b04874b..5e2056b2d 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -6,3 +6,4 @@ /proxy-libintl /zlib /packagecache +/google-benchmark diff --git a/subprojects/google-benchmark.wrap b/subprojects/google-benchmark.wrap new file mode 100644 index 000000000..180c6ee77 --- /dev/null +++ b/subprojects/google-benchmark.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = benchmark-1.4.1 +source_url = https://github.com/google/benchmark/archive/v1.4.1.zip +source_filename = benchmark-1.4.1.zip +source_hash = 61ae07eb5d4a0b02753419eb17a82b7d322786bb36ab62bd3df331a4d47c00a7 +patch_url = https://wrapdb.mesonbuild.com/v1/projects/google-benchmark/1.4.1/1/get_zip +patch_filename = google-benchmark-1.4.1-1-wrap.zip +patch_hash = 4cc5fe02ebd4fc82e110919b7977d7463eb2a99e4ecb9feca920eab6fd911d67