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