| /* | |
| * Copyright (C) 2026 Behdad Esfahbod | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| * | |
| * Author(s): Behdad Esfahbod | |
| */ | |
| #include <hb.h> | |
| #include <hb-gpu.h> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <cinttypes> | |
| #include <chrono> | |
| int | |
| main (int argc, char **argv) | |
| { | |
| const char *font_path = nullptr; | |
| unsigned num_iters = 1; | |
| for (int i = 1; i < argc; i++) | |
| { | |
| if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "--help")) | |
| { | |
| fprintf (stderr, "Usage: %s [-n iterations] FONTFILE\n", argv[0]); | |
| return 0; | |
| } | |
| if (!strcmp (argv[i], "-n") && i + 1 < argc) | |
| { | |
| num_iters = atoi (argv[++i]); | |
| if (!num_iters) num_iters = 1; | |
| continue; | |
| } | |
| font_path = argv[i]; | |
| } | |
| if (!font_path) | |
| { | |
| fprintf (stderr, "Usage: %s [-n iterations] FONTFILE\n", argv[0]); | |
| return 1; | |
| } | |
| hb_blob_t *blob = hb_blob_create_from_file_or_fail (font_path); | |
| if (!blob) | |
| { | |
| fprintf (stderr, "Failed to open font file: %s\n", font_path); | |
| return 1; | |
| } | |
| hb_face_t *face = hb_face_create (blob, 0); | |
| hb_font_t *font = hb_font_create (face); | |
| unsigned glyph_count = hb_face_get_glyph_count (face); | |
| if (!glyph_count) | |
| { | |
| fprintf (stderr, "Font has no glyphs.\n"); | |
| hb_font_destroy (font); | |
| hb_face_destroy (face); | |
| hb_blob_destroy (blob); | |
| return 1; | |
| } | |
| hb_gpu_paint_t *paint = hb_gpu_paint_create_or_fail (); | |
| hb_gpu_draw_t *draw = hb_gpu_draw_create_or_fail (); | |
| if (!paint || !draw) | |
| { | |
| fprintf (stderr, "Failed to create GPU encoder.\n"); | |
| hb_gpu_paint_destroy (paint); | |
| hb_gpu_draw_destroy (draw); | |
| hb_font_destroy (font); | |
| hb_face_destroy (face); | |
| hb_blob_destroy (blob); | |
| return 1; | |
| } | |
| unsigned num_paint_encoded = 0; | |
| unsigned num_draw_encoded = 0; | |
| unsigned num_empty = 0; | |
| unsigned num_failed = 0; | |
| uint64_t paint_bytes = 0; | |
| uint64_t draw_bytes = 0; | |
| unsigned paint_max_bytes = 0; | |
| unsigned draw_max_bytes = 0; | |
| unsigned paint_max_gid = 0; | |
| unsigned draw_max_gid = 0; | |
| uint64_t paint_walk_ns = 0; | |
| uint64_t paint_encode_ns = 0; | |
| uint64_t draw_walk_ns = 0; | |
| uint64_t draw_encode_ns = 0; | |
| typedef std::chrono::steady_clock clock; | |
| clock::time_point wall_start = clock::now (); | |
| for (unsigned iter = 0; iter < num_iters; iter++) | |
| for (unsigned gid = 0; gid < glyph_count; gid++) | |
| { | |
| hb_gpu_paint_clear (paint); | |
| clock::time_point t0 = clock::now (); | |
| hb_gpu_paint_glyph (paint, font, gid); | |
| clock::time_point t1 = clock::now (); | |
| hb_blob_t *encoded = hb_gpu_paint_encode (paint, nullptr); | |
| clock::time_point t2 = clock::now (); | |
| paint_walk_ns += std::chrono::duration_cast<std::chrono::nanoseconds> (t1 - t0).count (); | |
| paint_encode_ns += std::chrono::duration_cast<std::chrono::nanoseconds> (t2 - t1).count (); | |
| bool is_fallback = false; | |
| if (!encoded) | |
| { | |
| /* Paint did not produce a blob (v1 feature, empty glyph). | |
| * Fall back to the draw encoder. */ | |
| hb_gpu_draw_clear (draw); | |
| clock::time_point f0 = clock::now (); | |
| hb_gpu_draw_glyph (draw, font, gid); | |
| clock::time_point f1 = clock::now (); | |
| encoded = hb_gpu_draw_encode (draw, nullptr); | |
| clock::time_point f2 = clock::now (); | |
| draw_walk_ns += std::chrono::duration_cast<std::chrono::nanoseconds> (f1 - f0).count (); | |
| draw_encode_ns += std::chrono::duration_cast<std::chrono::nanoseconds> (f2 - f1).count (); | |
| is_fallback = true; | |
| } | |
| if (!encoded) | |
| { | |
| if (iter == 0) | |
| { | |
| fprintf (stderr, "gid %u: encode failed\n", gid); | |
| num_failed++; | |
| } | |
| continue; | |
| } | |
| if (iter == 0) | |
| { | |
| unsigned len = hb_blob_get_length (encoded); | |
| if (len == 0) | |
| num_empty++; | |
| else if (is_fallback) | |
| { | |
| num_draw_encoded++; | |
| draw_bytes += len; | |
| if (len > draw_max_bytes) { draw_max_bytes = len; draw_max_gid = gid; } | |
| } | |
| else | |
| { | |
| num_paint_encoded++; | |
| paint_bytes += len; | |
| if (len > paint_max_bytes) { paint_max_bytes = len; paint_max_gid = gid; } | |
| } | |
| } | |
| if (is_fallback) | |
| hb_gpu_draw_recycle_blob (draw, encoded); | |
| else | |
| hb_gpu_paint_recycle_blob (paint, encoded); | |
| } | |
| uint64_t wall_ns = std::chrono::duration_cast<std::chrono::nanoseconds> (clock::now () - wall_start).count (); | |
| printf ("font: %s\n", font_path); | |
| printf ("glyphs: %u\n", glyph_count); | |
| printf ("empty: %u\n", num_empty); | |
| if (num_failed) | |
| printf ("FAILED: %u\n", num_failed); | |
| uint64_t total_glyphs = (uint64_t) glyph_count * num_iters; | |
| if (num_paint_encoded) | |
| { | |
| printf ("\n"); | |
| printf ("paint encoded: %u\n", num_paint_encoded); | |
| printf (" total: %.2f KiB\n", paint_bytes / 1024.); | |
| printf (" avg: %.2f KiB/glyph\n", | |
| paint_bytes / 1024. / num_paint_encoded); | |
| printf (" max: %.2f KiB (gid %u)\n", | |
| paint_max_bytes / 1024., paint_max_gid); | |
| printf (" walk: %.3fms (%.1fus/glyph)\n", | |
| paint_walk_ns / 1e6 / num_iters, | |
| total_glyphs ? paint_walk_ns / 1e3 / total_glyphs : 0.); | |
| printf (" encode: %.3fms (%.1fus/glyph)\n", | |
| paint_encode_ns / 1e6 / num_iters, | |
| total_glyphs ? paint_encode_ns / 1e3 / total_glyphs : 0.); | |
| } | |
| if (num_draw_encoded) | |
| { | |
| printf ("\n"); | |
| printf ("draw fallback: %u\n", num_draw_encoded); | |
| printf (" total: %.2f KiB\n", draw_bytes / 1024.); | |
| printf (" avg: %.2f KiB/glyph\n", | |
| draw_bytes / 1024. / num_draw_encoded); | |
| printf (" max: %.2f KiB (gid %u)\n", | |
| draw_max_bytes / 1024., draw_max_gid); | |
| printf (" walk: %.3fms (%.1fus/glyph)\n", | |
| draw_walk_ns / 1e6 / num_iters, | |
| num_draw_encoded ? draw_walk_ns / 1e3 / num_draw_encoded : 0.); | |
| printf (" encode: %.3fms (%.1fus/glyph)\n", | |
| draw_encode_ns / 1e6 / num_iters, | |
| num_draw_encoded ? draw_encode_ns / 1e3 / num_draw_encoded : 0.); | |
| } | |
| printf ("\n"); | |
| printf ("wall: %.3fms (%.0f glyphs/s)\n", | |
| wall_ns / 1e6 / num_iters, | |
| wall_ns ? total_glyphs * 1e9 / wall_ns : 0.); | |
| hb_gpu_paint_destroy (paint); | |
| hb_gpu_draw_destroy (draw); | |
| hb_font_destroy (font); | |
| hb_face_destroy (face); | |
| hb_blob_destroy (blob); | |
| return num_failed ? 1 : 0; | |
| } |