1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5
6 #include <OpenImageIO/argparse.h>
7 #include <OpenImageIO/benchmark.h>
8 #include <OpenImageIO/imagebuf.h>
9 #include <OpenImageIO/imagebufalgo.h>
10 #include <OpenImageIO/imageio.h>
11 #include <OpenImageIO/strutil.h>
12 #include <OpenImageIO/timer.h>
13 #include <OpenImageIO/unittest.h>
14 #include <OpenImageIO/ustring.h>
15
16 #include <functional>
17 #include <iostream>
18 #include <vector>
19
20 using namespace OIIO;
21
22 static bool verbose = false;
23 static int iterations = 1;
24 static int ntrials = 1;
25 static int numthreads = 0;
26 static int autotile_size = 64;
27 static bool iter_only = false;
28 static bool no_iter = false;
29 static std::string conversionname;
30 static TypeDesc conversion = TypeDesc::UNKNOWN; // native by default
31 static std::vector<ustring> input_filename;
32 static std::string output_filename;
33 static std::string output_format;
34 static std::vector<char> buffer;
35 static ImageSpec bufspec, outspec;
36 static ImageCache* imagecache = NULL;
37 static imagesize_t total_image_pixels = 0;
38 static float cache_size = 0;
39
40
41
42 static void
getargs(int argc,char * argv[])43 getargs(int argc, char* argv[])
44 {
45 ArgParse ap;
46 // clang-format off
47 ap.intro("imagespeed_test\n" OIIO_INTRO_STRING)
48 .usage("imagespeed_test [options]");
49
50 ap.arg("filename")
51 .hidden()
52 .action([&](cspan<const char*> argv){ input_filename.emplace_back(argv[0]); });
53 ap.arg("-v", &verbose)
54 .help("Verbose mode");
55 ap.arg("--threads %d", &numthreads)
56 .help(Strutil::sprintf("Number of threads (default: %d)", numthreads));
57 ap.arg("--iters %d", &iterations)
58 .help(Strutil::sprintf("Number of iterations (default: %d)", iterations));
59 ap.arg("--trials %d", &ntrials)
60 .help("Number of trials");
61 ap.arg("--autotile %d", &autotile_size)
62 .help(Strutil::sprintf("Autotile size (when used; default: %d)", autotile_size));
63 ap.arg("--iteronly", &iter_only)
64 .help("Run ImageBuf iteration tests only (not read tests)");
65 ap.arg("--noiter", &no_iter)
66 .help("Don't run ImageBuf iteration tests");
67 ap.arg("--convert %s", &conversionname)
68 .help("Convert to named type upon read (default: native)");
69 ap.arg("--cache %f", &cache_size)
70 .help("Specify ImageCache size, in MB");
71 ap.arg("-o %s", &output_filename)
72 .help("Test output by writing to this file");
73 ap.arg("-od %s", &output_format)
74 .help("Requested output format");
75 // clang-format on
76
77 ap.parse(argc, (const char**)argv);
78 }
79
80
81
82 static void
time_read_image()83 time_read_image()
84 {
85 for (ustring filename : input_filename) {
86 auto in = ImageInput::open(filename.c_str());
87 OIIO_ASSERT(in);
88 in->read_image(conversion, &buffer[0]);
89 in->close();
90 }
91 }
92
93
94
95 static void
time_read_scanline_at_a_time()96 time_read_scanline_at_a_time()
97 {
98 for (ustring filename : input_filename) {
99 auto in = ImageInput::open(filename.c_str());
100 OIIO_ASSERT(in);
101 const ImageSpec& spec(in->spec());
102 size_t pixelsize = spec.nchannels * conversion.size();
103 if (!pixelsize)
104 pixelsize = spec.pixel_bytes(true); // UNKNOWN -> native
105 imagesize_t scanlinesize = spec.width * pixelsize;
106 for (int y = 0; y < spec.height; ++y) {
107 in->read_scanline(y + spec.y, 0, conversion,
108 &buffer[scanlinesize * y]);
109 }
110 in->close();
111 }
112 }
113
114
115
116 static void
time_read_64_scanlines_at_a_time()117 time_read_64_scanlines_at_a_time()
118 {
119 for (ustring filename : input_filename) {
120 auto in = ImageInput::open(filename.c_str());
121 OIIO_ASSERT(in);
122 const ImageSpec& spec(in->spec());
123 size_t pixelsize = spec.nchannels * conversion.size();
124 if (!pixelsize)
125 pixelsize = spec.pixel_bytes(true); // UNKNOWN -> native
126 imagesize_t scanlinesize = spec.width * pixelsize;
127 for (int y = 0; y < spec.height; y += 64) {
128 in->read_scanlines(y + spec.y,
129 std::min(y + spec.y + 64, spec.y + spec.height),
130 0, conversion, &buffer[scanlinesize * y]);
131 }
132 in->close();
133 }
134 }
135
136
137
138 static void
time_read_imagebuf()139 time_read_imagebuf()
140 {
141 imagecache->invalidate_all(true);
142 for (ustring filename : input_filename) {
143 ImageBuf ib(filename.string(), imagecache);
144 ib.read(0, 0, true, conversion);
145 }
146 }
147
148
149
150 static void
time_ic_get_pixels()151 time_ic_get_pixels()
152 {
153 imagecache->invalidate_all(true);
154 for (ustring filename : input_filename) {
155 const ImageSpec spec = (*imagecache->imagespec(filename));
156 imagecache->get_pixels(filename, 0, 0, spec.x, spec.x + spec.width,
157 spec.y, spec.y + spec.height, spec.z,
158 spec.z + spec.depth, conversion, &buffer[0]);
159 }
160 }
161
162
163
164 static void
test_read(const std::string & explanation,void (* func)(),int autotile=64,int autoscanline=1)165 test_read(const std::string& explanation, void (*func)(), int autotile = 64,
166 int autoscanline = 1)
167 {
168 imagecache->invalidate_all(true); // Don't hold anything
169 imagecache->attribute("autotile", autotile);
170 imagecache->attribute("autoscanline", autoscanline);
171 double t = time_trial(func, ntrials);
172 double rate = double(total_image_pixels) / t;
173 std::cout << " " << explanation << ": "
174 << Strutil::timeintervalformat(t, 2) << " = "
175 << Strutil::sprintf("%5.1f", rate / 1.0e6) << " Mpel/s"
176 << std::endl;
177 }
178
179
180
181 static void
time_write_image()182 time_write_image()
183 {
184 auto out = ImageOutput::create(output_filename);
185 OIIO_ASSERT(out);
186 bool ok = out->open(output_filename, outspec);
187 OIIO_ASSERT(ok);
188 out->write_image(bufspec.format, &buffer[0]);
189 }
190
191
192
193 static void
time_write_scanline_at_a_time()194 time_write_scanline_at_a_time()
195 {
196 auto out = ImageOutput::create(output_filename);
197 OIIO_ASSERT(out);
198 bool ok = out->open(output_filename, outspec);
199 OIIO_ASSERT(ok);
200
201 size_t pixelsize = outspec.nchannels * sizeof(float);
202 imagesize_t scanlinesize = outspec.width * pixelsize;
203 for (int y = 0; y < outspec.height; ++y) {
204 out->write_scanline(y + outspec.y, outspec.z, bufspec.format,
205 &buffer[scanlinesize * y]);
206 }
207 }
208
209
210
211 static void
time_write_64_scanlines_at_a_time()212 time_write_64_scanlines_at_a_time()
213 {
214 auto out = ImageOutput::create(output_filename);
215 OIIO_ASSERT(out);
216 bool ok = out->open(output_filename, outspec);
217 OIIO_ASSERT(ok);
218
219 size_t pixelsize = outspec.nchannels * sizeof(float);
220 imagesize_t scanlinesize = outspec.width * pixelsize;
221 for (int y = 0; y < outspec.height; y += 64) {
222 out->write_scanlines(y + outspec.y,
223 std::min(y + outspec.y + 64,
224 outspec.y + outspec.height),
225 outspec.z, bufspec.format,
226 &buffer[scanlinesize * y]);
227 }
228 }
229
230
231
232 static void
time_write_tile_at_a_time()233 time_write_tile_at_a_time()
234 {
235 auto out = ImageOutput::create(output_filename);
236 OIIO_ASSERT(out);
237 bool ok = out->open(output_filename, outspec);
238 OIIO_ASSERT(ok);
239
240 size_t pixelsize = outspec.nchannels * sizeof(float);
241 imagesize_t scanlinesize = outspec.width * pixelsize;
242 imagesize_t planesize = outspec.height * scanlinesize;
243 for (int z = 0; z < outspec.depth; z += outspec.tile_depth) {
244 for (int y = 0; y < outspec.height; y += outspec.tile_height) {
245 for (int x = 0; x < outspec.width; x += outspec.tile_width) {
246 out->write_tile(x + outspec.x, y + outspec.y, z + outspec.z,
247 bufspec.format,
248 &buffer[scanlinesize * y + pixelsize * x],
249 pixelsize, scanlinesize, planesize);
250 }
251 }
252 }
253 }
254
255
256
257 static void
time_write_tiles_row_at_a_time()258 time_write_tiles_row_at_a_time()
259 {
260 auto out = ImageOutput::create(output_filename);
261 OIIO_ASSERT(out);
262 bool ok = out->open(output_filename, outspec);
263 OIIO_ASSERT(ok);
264
265 size_t pixelsize = outspec.nchannels * sizeof(float);
266 imagesize_t scanlinesize = outspec.width * pixelsize;
267 for (int z = 0; z < outspec.depth; z += outspec.tile_depth) {
268 for (int y = 0; y < outspec.height; y += outspec.tile_height) {
269 out->write_tiles(outspec.x, outspec.x + outspec.width,
270 y + outspec.y, y + outspec.y + outspec.tile_height,
271 z + outspec.z, z + outspec.z + outspec.tile_depth,
272 bufspec.format, &buffer[scanlinesize * y],
273 pixelsize /*xstride*/, scanlinesize /*ystride*/);
274 }
275 }
276 }
277
278
279
280 static void
time_write_imagebuf()281 time_write_imagebuf()
282 {
283 ImageBuf ib(output_filename, bufspec, &buffer[0]); // wrap the buffer
284 auto out = ImageOutput::create(output_filename);
285 OIIO_ASSERT(out);
286 bool ok = out->open(output_filename, outspec);
287 OIIO_ASSERT(ok);
288 ib.write(out.get());
289 }
290
291
292 static void
test_write(const std::string & explanation,void (* func)(),int tilesize=0)293 test_write(const std::string& explanation, void (*func)(), int tilesize = 0)
294 {
295 outspec.tile_width = tilesize;
296 outspec.tile_height = tilesize;
297 outspec.tile_depth = 1;
298 double t = time_trial(func, ntrials);
299 double rate = double(total_image_pixels) / t;
300 std::cout << " " << explanation << ": "
301 << Strutil::timeintervalformat(t, 2) << " = "
302 << Strutil::sprintf("%5.1f", rate / 1.0e6) << " Mpel/s"
303 << std::endl;
304 }
305
306
307
308 static float
time_loop_pixels_1D(ImageBuf & ib,int iters)309 time_loop_pixels_1D(ImageBuf& ib, int iters)
310 {
311 OIIO_ASSERT(ib.localpixels() && ib.pixeltype() == TypeFloat);
312 const ImageSpec& spec(ib.spec());
313 imagesize_t npixels = spec.image_pixels();
314 int nchannels = spec.nchannels;
315 double sum = 0.0f;
316 for (int i = 0; i < iters; ++i) {
317 const float* f = (const float*)ib.pixeladdr(spec.x, spec.y, spec.z);
318 OIIO_DASSERT(f);
319 for (imagesize_t p = 0; p < npixels; ++p) {
320 sum += f[0];
321 f += nchannels;
322 }
323 }
324 // std::cout << float(sum/npixels/iters) << "\n";
325 return float(sum / npixels / iters);
326 }
327
328
329
330 static float
time_loop_pixels_3D(ImageBuf & ib,int iters)331 time_loop_pixels_3D(ImageBuf& ib, int iters)
332 {
333 OIIO_ASSERT(ib.localpixels() && ib.pixeltype() == TypeFloat);
334 const ImageSpec& spec(ib.spec());
335 imagesize_t npixels = spec.image_pixels();
336 int nchannels = spec.nchannels;
337 double sum = 0.0f;
338 for (int i = 0; i < iters; ++i) {
339 const float* f = (const float*)ib.pixeladdr(spec.x, spec.y, spec.z);
340 OIIO_DASSERT(f);
341 for (int z = spec.z, ze = spec.z + spec.depth; z < ze; ++z) {
342 for (int y = spec.y, ye = spec.y + spec.height; y < ye; ++y) {
343 for (int x = spec.x, xe = spec.x + spec.width; x < xe; ++x) {
344 sum += f[0];
345 f += nchannels;
346 }
347 }
348 }
349 }
350 // std::cout << float(sum/npixels/iters) << "\n";
351 return float(sum / npixels / iters);
352 }
353
354
355
356 static float
time_loop_pixels_3D_getchannel(ImageBuf & ib,int iters)357 time_loop_pixels_3D_getchannel(ImageBuf& ib, int iters)
358 {
359 OIIO_DASSERT(ib.pixeltype() == TypeFloat);
360 const ImageSpec& spec(ib.spec());
361 imagesize_t npixels = spec.image_pixels();
362 double sum = 0.0f;
363 for (int i = 0; i < iters; ++i) {
364 for (int z = spec.z, ze = spec.z + spec.depth; z < ze; ++z) {
365 for (int y = spec.y, ye = spec.y + spec.height; y < ye; ++y) {
366 for (int x = spec.x, xe = spec.x + spec.width; x < xe; ++x) {
367 sum += ib.getchannel(x, y, 0, 0);
368 }
369 }
370 }
371 }
372 // std::cout << float(sum/npixels/iters) << "\n";
373 return float(sum / npixels / iters);
374 }
375
376
377
378 static float
time_iterate_pixels(ImageBuf & ib,int iters)379 time_iterate_pixels(ImageBuf& ib, int iters)
380 {
381 OIIO_DASSERT(ib.pixeltype() == TypeFloat);
382 const ImageSpec& spec(ib.spec());
383 imagesize_t npixels = spec.image_pixels();
384 double sum = 0.0f;
385 for (int i = 0; i < iters; ++i) {
386 for (ImageBuf::ConstIterator<float, float> p(ib); !p.done(); ++p) {
387 sum += p[0];
388 }
389 }
390 // std::cout << float(sum/npixels/iters) << "\n";
391 return float(sum / npixels / iters);
392 }
393
394
395
396 static float
time_iterate_pixels_slave_pos(ImageBuf & ib,int iters)397 time_iterate_pixels_slave_pos(ImageBuf& ib, int iters)
398 {
399 OIIO_DASSERT(ib.pixeltype() == TypeFloat);
400 const ImageSpec& spec(ib.spec());
401 imagesize_t npixels = spec.image_pixels();
402 double sum = 0.0f;
403 for (int i = 0; i < iters; ++i) {
404 ImageBuf::ConstIterator<float, float> slave(ib);
405 for (ImageBuf::ConstIterator<float, float> p(ib); !p.done(); ++p) {
406 slave.pos(p.x(), p.y());
407 sum += p[0];
408 }
409 }
410 // std::cout << float(sum/npixels/iters) << "\n";
411 return float(sum / npixels / iters);
412 }
413
414
415
416 static float
time_iterate_pixels_slave_incr(ImageBuf & ib,int iters)417 time_iterate_pixels_slave_incr(ImageBuf& ib, int iters)
418 {
419 OIIO_DASSERT(ib.pixeltype() == TypeFloat);
420 const ImageSpec& spec(ib.spec());
421 imagesize_t npixels = spec.image_pixels();
422 double sum = 0.0f;
423 for (int i = 0; i < iters; ++i) {
424 ImageBuf::ConstIterator<float, float> slave(ib);
425 for (ImageBuf::ConstIterator<float, float> p(ib); !p.done(); ++p) {
426 sum += p[0];
427 ++slave;
428 }
429 }
430 // std::cout << float(sum/npixels/iters) << "\n";
431 return float(sum / npixels / iters);
432 }
433
434
435
436 static void
test_pixel_iteration(const std::string & explanation,float (* func)(ImageBuf &,int),bool preload,int iters=100,int autotile=64)437 test_pixel_iteration(const std::string& explanation,
438 float (*func)(ImageBuf&, int), bool preload,
439 int iters = 100, int autotile = 64)
440 {
441 imagecache->invalidate_all(true); // Don't hold anything
442 // Force the whole image to be read at once
443 imagecache->attribute("autotile", autotile);
444 imagecache->attribute("autoscanline", 1);
445 ImageBuf ib(input_filename[0].string(), imagecache);
446 ib.read(0, 0, preload, TypeFloat);
447 double t = time_trial(std::bind(func, std::ref(ib), iters), ntrials);
448 double rate = double(ib.spec().image_pixels()) / (t / iters);
449 std::cout << " " << explanation << ": "
450 << Strutil::timeintervalformat(t / iters, 3) << " = "
451 << Strutil::sprintf("%5.1f", rate / 1.0e6) << " Mpel/s"
452 << std::endl;
453 }
454
455
456
457 static void
set_dataformat(const std::string & output_format,ImageSpec & outspec)458 set_dataformat(const std::string& output_format, ImageSpec& outspec)
459 {
460 if (output_format == "uint8")
461 outspec.format = TypeDesc::UINT8;
462 else if (output_format == "int8")
463 outspec.format = TypeDesc::INT8;
464 else if (output_format == "uint16")
465 outspec.format = TypeDesc::UINT16;
466 else if (output_format == "int16")
467 outspec.format = TypeDesc::INT16;
468 else if (output_format == "half")
469 outspec.format = TypeDesc::HALF;
470 else if (output_format == "float")
471 outspec.format = TypeDesc::FLOAT;
472 else if (output_format == "double")
473 outspec.format = TypeDesc::DOUBLE;
474 // Otherwise leave at the default
475 }
476
477
478
479 int
main(int argc,char ** argv)480 main(int argc, char** argv)
481 {
482 getargs(argc, argv);
483 if (input_filename.size() == 0) {
484 std::cout << "Error: Must supply a filename.\n";
485 return -1;
486 }
487
488 OIIO::attribute("threads", numthreads);
489 OIIO::attribute("exr_threads", numthreads);
490 conversion.fromstring(conversionname);
491
492 imagecache = ImageCache::create();
493 if (cache_size)
494 imagecache->attribute("max_memory_MB", cache_size);
495 imagecache->attribute("forcefloat", 1);
496
497 // Allocate a buffer big enough (for floats)
498 bool all_scanline = true;
499 total_image_pixels = 0;
500 imagesize_t maxpelchans = 0;
501 for (auto&& fn : input_filename) {
502 ImageSpec spec;
503 if (!imagecache->get_imagespec(fn, spec, 0, 0, true)) {
504 std::cout << "File \"" << fn << "\" could not be opened.\n";
505 return -1;
506 }
507 total_image_pixels += spec.image_pixels();
508 maxpelchans = std::max(maxpelchans,
509 spec.image_pixels() * spec.nchannels);
510 all_scanline &= (spec.tile_width == 0);
511 }
512 imagecache->invalidate_all(true); // Don't hold anything
513
514 if (!iter_only) {
515 std::cout << "Timing various ways of reading images:\n";
516 if (conversion == TypeDesc::UNKNOWN)
517 std::cout
518 << " ImageInput reads will keep data in native format.\n";
519 else
520 std::cout << " ImageInput reads will convert data to "
521 << conversion << "\n";
522 buffer.resize(maxpelchans * sizeof(float), 0);
523 test_read("read_image ",
524 time_read_image, 0, 0);
525 if (all_scanline) {
526 test_read("read_scanline (1 at a time) ",
527 time_read_scanline_at_a_time, 0, 0);
528 test_read("read_scanlines (64 at a time) ",
529 time_read_64_scanlines_at_a_time, 0, 0);
530 }
531 test_read("ImageBuf read ",
532 time_read_imagebuf, 0, 0);
533 test_read("ImageCache get_pixels ",
534 time_ic_get_pixels, 0, 0);
535 test_read("ImageBuf read (autotile) ",
536 time_read_imagebuf, autotile_size, 0);
537 test_read("ImageCache get_pixels (autotile) ",
538 time_ic_get_pixels, autotile_size, 0);
539 if (all_scanline) { // don't bother for tiled images
540 test_read("ImageBuf read (autotile+autoscanline) ",
541 time_read_imagebuf, autotile_size, 1);
542 test_read("ImageCache get_pixels (autotile+autoscanline)",
543 time_ic_get_pixels, autotile_size, 1);
544 }
545 if (verbose)
546 std::cout << "\n" << imagecache->getstats(2) << "\n";
547 std::cout << std::endl;
548 }
549
550 if (output_filename.size()) {
551 // Use the first image
552 auto in = ImageInput::open(input_filename[0].c_str());
553 OIIO_ASSERT(in);
554 bufspec = in->spec();
555 in->read_image(conversion, &buffer[0]);
556 in->close();
557 in.reset();
558 std::cout << "Timing ways of writing images:\n";
559 // imagecache->get_imagespec (input_filename[0], bufspec, 0, 0, true);
560 auto out = ImageOutput::create(output_filename);
561 OIIO_ASSERT(out);
562 bool supports_tiles = out->supports("tiles");
563 out.reset();
564 outspec = bufspec;
565 set_dataformat(output_format, outspec);
566 std::cout << " writing as format " << outspec.format << "\n";
567
568 test_write("write_image (scanline) ",
569 time_write_image, 0);
570 if (supports_tiles)
571 test_write("write_image (tiled) ",
572 time_write_image, 64);
573 test_write("write_scanline (one at a time) ",
574 time_write_scanline_at_a_time, 0);
575 test_write("write_scanlines (64 at a time) ",
576 time_write_64_scanlines_at_a_time, 0);
577 if (supports_tiles) {
578 test_write("write_tile (one at a time) ",
579 time_write_tile_at_a_time, 64);
580 test_write("write_tiles (a whole row at a time) ",
581 time_write_tiles_row_at_a_time, 64);
582 }
583 test_write("ImageBuf::write (scanline) ",
584 time_write_imagebuf, 0);
585 if (supports_tiles)
586 test_write("ImageBuf::write (tiled) ",
587 time_write_imagebuf, 64);
588 std::cout << std::endl;
589 }
590
591 if (!no_iter) {
592 const int iters = 64;
593 std::cout << "Timing ways of iterating over an image:\n";
594 test_pixel_iteration("Loop pointers on loaded image (\"1D\") ",
595 time_loop_pixels_1D, true, iters);
596 test_pixel_iteration("Loop pointers on loaded image (\"3D\") ",
597 time_loop_pixels_3D, true, iters);
598 test_pixel_iteration("Loop + getchannel on loaded image (\"3D\")",
599 time_loop_pixels_3D_getchannel, true, iters / 32);
600 test_pixel_iteration("Loop + getchannel on cached image (\"3D\")",
601 time_loop_pixels_3D_getchannel, false, iters / 32);
602 test_pixel_iteration("Iterate over a loaded image ",
603 time_iterate_pixels, true, iters);
604 test_pixel_iteration("Iterate over a cache image ",
605 time_iterate_pixels, false, iters);
606 test_pixel_iteration("Iterate over a loaded image (pos slave) ",
607 time_iterate_pixels_slave_pos, true, iters);
608 test_pixel_iteration("Iterate over a cache image (pos slave) ",
609 time_iterate_pixels_slave_pos, false, iters);
610 test_pixel_iteration("Iterate over a loaded image (incr slave)",
611 time_iterate_pixels_slave_incr, true, iters);
612 test_pixel_iteration("Iterate over a cache image (incr slave) ",
613 time_iterate_pixels_slave_incr, false, iters);
614 }
615 if (verbose)
616 std::cout << "\n" << imagecache->getstats(2) << "\n";
617
618 ImageCache::destroy(imagecache);
619 return unit_test_failures;
620 }
621