237 lines
7.0 KiB
C++
237 lines
7.0 KiB
C++
/*
|
|
* 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;
|
|
}
|