xref: /aosp_15_r20/external/harfbuzz_ng/perf/benchmark-subset.cc (revision 2d1272b857b1f7575e6e246373e1cb218663db8a)
1 #include "hb-benchmark.hh"
2 
3 enum operation_t
4 {
5   subset_glyphs,
6   subset_unicodes,
7   instance,
8 };
9 
10 struct axis_location_t
11 {
12   hb_tag_t axis_tag;
13   float axis_value;
14 };
15 
16 static const axis_location_t
17 _roboto_flex_instance_opts[] =
18 {
19   {HB_TAG ('w', 'g', 'h', 't'), 600.f},
20   {HB_TAG ('w', 'd', 't', 'h'), 75.f},
21   {HB_TAG ('o', 'p', 's', 'z'), 90.f},
22   {HB_TAG ('G', 'R', 'A', 'D'), -100.f},
23   {HB_TAG ('s', 'l', 'n', 't'), -3.f},
24   {HB_TAG ('X', 'T', 'R', 'A'), 500.f},
25   {HB_TAG ('X', 'O', 'P', 'Q'), 150.f},
26   {HB_TAG ('Y', 'O', 'P', 'Q'), 100.f},
27   {HB_TAG ('Y', 'T', 'L', 'C'), 480.f},
28   {HB_TAG ('Y', 'T', 'U', 'C'), 600.f},
29   {HB_TAG ('Y', 'T', 'A', 'S'), 800.f},
30   {HB_TAG ('Y', 'T', 'D', 'E'), -50.f},
31   {HB_TAG ('Y', 'T', 'F', 'I'), 600.f},
32 };
33 
34 static const axis_location_t
35 _mplus_instance_opts[] =
36 {
37   {HB_TAG ('w', 'g', 'h', 't'), 800.f},
38 };
39 
40 static const axis_location_t
41 _fraunces_partial_instance_opts[] =
42 {
43   {HB_TAG ('S', 'O', 'F', 'T'), 75.0f},
44   {HB_TAG ('W', 'O', 'N', 'K'), 0.75f},
45 };
46 
47 template <typename Type, unsigned int n>
ARRAY_LEN(const Type (&)[n])48 static inline unsigned int ARRAY_LEN (const Type (&)[n]) { return n; }
49 
50 #define SUBSET_FONT_BASE_PATH "test/subset/data/fonts/"
51 
52 struct test_input_t
53 {
54   const char *font_path;
55   unsigned max_subset_size;
56   const axis_location_t *instance_opts;
57   unsigned num_instance_opts;
58 } default_tests[] =
59 {
60   {SUBSET_FONT_BASE_PATH "Roboto-Regular.ttf", 1000, nullptr, 0},
61   {SUBSET_FONT_BASE_PATH "Amiri-Regular.ttf", 4096, nullptr, 0},
62   {SUBSET_FONT_BASE_PATH "NotoNastaliqUrdu-Regular.ttf", 1400, nullptr, 0},
63   {SUBSET_FONT_BASE_PATH "NotoSansDevanagari-Regular.ttf", 1000, nullptr, 0},
64   {SUBSET_FONT_BASE_PATH "Mplus1p-Regular.ttf", 10000, nullptr, 0},
65   {SUBSET_FONT_BASE_PATH "SourceHanSans-Regular_subset.otf", 10000, nullptr, 0},
66   {SUBSET_FONT_BASE_PATH "SourceSansPro-Regular.otf", 2000, nullptr, 0},
67   {SUBSET_FONT_BASE_PATH "AdobeVFPrototype.otf", 300, nullptr, 0},
68   {SUBSET_FONT_BASE_PATH "MPLUS1-Variable.ttf", 6000, _mplus_instance_opts, ARRAY_LEN (_mplus_instance_opts)},
69   {SUBSET_FONT_BASE_PATH "RobotoFlex-Variable.ttf", 900, _roboto_flex_instance_opts, ARRAY_LEN (_roboto_flex_instance_opts)},
70   {SUBSET_FONT_BASE_PATH "Fraunces.ttf", 900, _fraunces_partial_instance_opts, ARRAY_LEN (_fraunces_partial_instance_opts)},
71 #if 0
72   {"perf/fonts/NotoSansCJKsc-VF.ttf", 100000},
73 #endif
74 };
75 
76 static test_input_t *tests = default_tests;
77 static unsigned num_tests = sizeof (default_tests) / sizeof (default_tests[0]);
78 
79 
AddCodepoints(const hb_set_t * codepoints_in_font,unsigned subset_size,hb_subset_input_t * input)80 void AddCodepoints(const hb_set_t* codepoints_in_font,
81                    unsigned subset_size,
82                    hb_subset_input_t* input)
83 {
84   auto *unicodes = hb_subset_input_unicode_set (input);
85   hb_codepoint_t cp = HB_SET_VALUE_INVALID;
86   for (unsigned i = 0; i < subset_size; i++) {
87     // TODO(garretrieger): pick randomly.
88     if (!hb_set_next (codepoints_in_font, &cp)) return;
89     hb_set_add (unicodes, cp);
90   }
91 }
92 
AddGlyphs(unsigned num_glyphs_in_font,unsigned subset_size,hb_subset_input_t * input)93 void AddGlyphs(unsigned num_glyphs_in_font,
94                unsigned subset_size,
95                hb_subset_input_t* input)
96 {
97   auto *glyphs = hb_subset_input_glyph_set (input);
98   for (unsigned i = 0; i < subset_size && i < num_glyphs_in_font; i++) {
99     if (i + 1 == subset_size &&
100         hb_subset_input_get_flags (input) & HB_SUBSET_FLAGS_RETAIN_GIDS)
101     {
102       hb_set_add (glyphs, num_glyphs_in_font - 1);
103       continue;
104     }
105     hb_set_add (glyphs, i);
106   }
107 }
108 
109 // Preprocess face and populate the subset accelerator on it to speed up
110 // the subsetting operations.
preprocess_face(hb_face_t * face)111 static hb_face_t* preprocess_face(hb_face_t* face)
112 {
113   hb_face_t* new_face = hb_subset_preprocess(face);
114   hb_face_destroy(face);
115   return new_face;
116 }
117 
118 static hb_face_t *cached_face;
119 
120 static void
free_cached_face(void)121 free_cached_face (void)
122 {
123   hb_face_destroy (cached_face);
124   cached_face = nullptr;
125 }
126 
127 
128 /* benchmark for subsetting a font */
BM_subset(benchmark::State & state,operation_t operation,const test_input_t & test_input,bool retain_gids)129 static void BM_subset (benchmark::State &state,
130                        operation_t operation,
131                        const test_input_t &test_input,
132                        bool retain_gids)
133 {
134   unsigned subset_size = state.range(0);
135 
136   hb_face_t *face = nullptr;
137 
138   static const char *cached_font_path;
139 
140   if (!cached_font_path || strcmp (cached_font_path, test_input.font_path))
141   {
142     face = hb_benchmark_face_create_from_file_or_fail (test_input.font_path, 0);
143     assert (face);
144 
145     face = preprocess_face (face);
146 
147     if (cached_face)
148       hb_face_destroy (cached_face);
149 
150     cached_face = hb_face_reference (face);
151     cached_font_path = test_input.font_path;
152   }
153   else
154     face = hb_face_reference (cached_face);
155 
156   hb_subset_input_t* input = hb_subset_input_create_or_fail ();
157   assert (input);
158 
159   if (retain_gids)
160     hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS);
161 
162   switch (operation)
163   {
164     case subset_unicodes:
165     {
166       hb_set_t* all_codepoints = hb_set_create ();
167       hb_face_collect_unicodes (face, all_codepoints);
168       AddCodepoints(all_codepoints, subset_size, input);
169       hb_set_destroy (all_codepoints);
170     }
171     break;
172 
173     case subset_glyphs:
174     {
175       unsigned num_glyphs = hb_face_get_glyph_count (face);
176       AddGlyphs(num_glyphs, subset_size, input);
177     }
178     break;
179 
180     case instance:
181     {
182       hb_set_t* all_codepoints = hb_set_create ();
183       hb_face_collect_unicodes (face, all_codepoints);
184       AddCodepoints(all_codepoints, subset_size, input);
185       hb_set_destroy (all_codepoints);
186 
187       hb_subset_input_set_flags(input, hb_subset_input_get_flags(input) | HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS);
188 
189       for (unsigned i = 0; i < test_input.num_instance_opts; i++)
190         hb_subset_input_pin_axis_location (input, face,
191                                            test_input.instance_opts[i].axis_tag,
192                                            test_input.instance_opts[i].axis_value);
193     }
194     break;
195   }
196 
197   for (auto _ : state)
198   {
199     hb_face_t* subset = hb_subset_or_fail (face, input);
200     assert (subset);
201     hb_face_destroy (subset);
202   }
203 
204   hb_subset_input_destroy (input);
205   hb_face_destroy (face);
206 }
207 
test_subset(operation_t op,const char * op_name,bool retain_gids,benchmark::TimeUnit time_unit,const test_input_t & test_input)208 static void test_subset (operation_t op,
209                          const char *op_name,
210                          bool retain_gids,
211                          benchmark::TimeUnit time_unit,
212                          const test_input_t &test_input)
213 {
214   if (op == instance && test_input.instance_opts == nullptr)
215     return;
216 
217   char name[1024] = "BM_subset/";
218   strcat (name, op_name);
219   strcat (name, "/");
220   const char *p = strrchr (test_input.font_path, '/');
221   strcat (name, p ? p + 1 : test_input.font_path);
222   if (retain_gids)
223     strcat (name, "/retaingids");
224 
225   benchmark::RegisterBenchmark (name, BM_subset, op, test_input, retain_gids)
226       ->Range(10, test_input.max_subset_size)
227       ->Unit(time_unit);
228 }
229 
test_operation(operation_t op,const char * op_name,const test_input_t * tests,unsigned num_tests,benchmark::TimeUnit time_unit)230 static void test_operation (operation_t op,
231                             const char *op_name,
232                             const test_input_t *tests,
233                             unsigned num_tests,
234                             benchmark::TimeUnit time_unit)
235 {
236   for (unsigned i = 0; i < num_tests; i++)
237   {
238     auto& test_input = tests[i];
239     test_subset (op, op_name, true, time_unit, test_input);
240     test_subset (op, op_name, false, time_unit, test_input);
241   }
242 }
243 
main(int argc,char ** argv)244 int main(int argc, char** argv)
245 {
246   benchmark::Initialize(&argc, argv);
247 
248 #ifndef HB_NO_ATEXIT
249   atexit (free_cached_face);
250 #endif
251 
252   if (argc > 1)
253   {
254     num_tests = (argc - 1) / 2;
255     tests = (test_input_t *) calloc (num_tests, sizeof (test_input_t));
256     for (unsigned i = 0; i < num_tests; i++)
257     {
258       tests[i].font_path = argv[1 + i * 2];
259       tests[i].max_subset_size = atoi (argv[2 + i * 2]);
260     }
261   }
262 
263 #define TEST_OPERATION(op, time_unit) test_operation (op, #op, tests, num_tests, time_unit)
264 
265   TEST_OPERATION (subset_glyphs, benchmark::kMicrosecond);
266   TEST_OPERATION (subset_unicodes, benchmark::kMicrosecond);
267   TEST_OPERATION (instance, benchmark::kMicrosecond);
268 
269 #undef TEST_OPERATION
270 
271   benchmark::RunSpecifiedBenchmarks();
272   benchmark::Shutdown();
273 
274   if (tests != default_tests)
275     free (tests);
276 }
277