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