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 <cmath>
7 #include <cstdio>
8 #include <cstdlib>
9 #include <cstring>
10 #include <ctime>
11 #include <iostream>
12 #include <iterator>
13 
14 #include <OpenImageIO/argparse.h>
15 #include <OpenImageIO/benchmark.h>
16 #include <OpenImageIO/filesystem.h>
17 #include <OpenImageIO/imagebuf.h>
18 #include <OpenImageIO/imagebufalgo.h>
19 #include <OpenImageIO/imagebufalgo_util.h>
20 #include <OpenImageIO/imageio.h>
21 #include <OpenImageIO/strutil.h>
22 #include <OpenImageIO/sysutil.h>
23 #include <OpenImageIO/texture.h>
24 #include <OpenImageIO/timer.h>
25 #include <OpenImageIO/ustring.h>
26 
27 #include "../libtexture/imagecache_pvt.h"
28 
29 using namespace OIIO;
30 
31 using OIIO::_1;
32 
33 static std::vector<ustring> filenames;
34 static std::string output_filename = "out.exr";
35 static bool verbose                = false;
36 static int nthreads                = 0;
37 static int threadtimes             = 0;
38 static int output_xres = 512, output_yres = 512;
39 static int nchannels_override     = 0;
40 static std::string dataformatname = "half";
41 static float sscale = 1, tscale = 1;
42 static float sblur = 0, tblur = -1;
43 static float width       = 1;
44 static float anisoaspect = 1.0;  // anisotropic aspect ratio
45 static std::string wrapmodes("periodic");
46 static int anisomax           = TextureOpt().anisotropic;
47 static int iters              = 1;
48 static int autotile           = 0;
49 static bool automip           = false;
50 static bool dedup             = true;
51 static bool test_construction = false;
52 static bool test_gettexels    = false;
53 static bool test_getimagespec = false;
54 static bool filtertest        = false;
55 static TextureSystem* texsys  = NULL;
56 static std::string searchpath;
57 static bool batch        = false;
58 static bool nowarp       = false;
59 static bool tube         = false;
60 static bool use_handle   = false;
61 static float cachesize   = -1;
62 static int maxfiles      = -1;
63 static int mipmode       = TextureOpt::MipModeDefault;
64 static int interpmode    = TextureOpt::InterpSmartBicubic;
65 static float missing[4]  = { -1, 0, 0, 1 };
66 static float fill        = -1;  // -1 signifies unset
67 static float scalefactor = 1.0f;
68 static Imath::V3f texoffset(0, 0, 0);
69 static bool nountiled              = false;
70 static bool nounmipped             = false;
71 static bool gray_to_rgb            = false;
72 static bool flip_t                 = false;
73 static bool resetstats             = false;
74 static bool testhash               = false;
75 static bool wedge                  = false;
76 static int ntrials                 = 1;
77 static int testicwrite             = 0;
78 static bool test_derivs            = false;
79 static bool test_statquery         = false;
80 static bool invalidate_before_iter = true;
81 static bool close_before_iter      = false;
82 static Imath::M33f xform;
83 static std::string texoptions;
84 void* dummyptr;
85 
86 typedef void (*Mapping2D)(const int&, const int&, float&, float&, float&,
87                           float&, float&, float&);
88 typedef void (*Mapping3D)(const int&, const int&, Imath::V3f&, Imath::V3f&,
89                           Imath::V3f&, Imath::V3f&);
90 
91 typedef void (*Mapping2DWide)(const Tex::IntWide&, const Tex::IntWide&,
92                               Tex::FloatWide&, Tex::FloatWide&, Tex::FloatWide&,
93                               Tex::FloatWide&, Tex::FloatWide&,
94                               Tex::FloatWide&);
95 typedef void (*Mapping3DWide)(const Tex::IntWide&, const Tex::IntWide&,
96                               Imath::Vec3<Tex::FloatWide>&,
97                               Imath::Vec3<Tex::FloatWide>&,
98                               Imath::Vec3<Tex::FloatWide>&,
99                               Imath::Vec3<Tex::FloatWide>&);
100 
101 
102 
103 static void
getargs(int argc,const char * argv[])104 getargs(int argc, const char* argv[])
105 {
106     // clang-format off
107     ArgParse ap;
108     ap.usage ("testtex [options] inputfile");
109     ap.arg("filename")
110       .hidden()
111       .action([&](cspan<const char*> argv){ filenames.emplace_back(argv[0]); });
112     ap.arg("-v", &verbose)
113       .help("Verbose status messages");
114 
115     ap.arg("-o %s:FILENAME", &output_filename)
116       .help("Output test image");
117     ap.arg("-d %s:TYPE", &dataformatname)
118       .help("Set the output data format to one of: "
119             "uint8, sint8, uint10, uint12, uint16, sint16, half, float, double");
120     ap.arg("--res %d:WIDTH %d:HEIGHT", &output_xres, &output_yres)
121       .help("Resolution of output test image");
122     ap.arg("--nchannels %d:N", &nchannels_override)
123       .help("Force number of channels to look up");
124     ap.arg("--iters %d:N", &iters)
125       .help("Iterations for time trials");
126     ap.arg("--threads %d:N", &nthreads)
127       .help("Number of threads (default 0 = #cores)");
128     ap.arg("-t %d", &nthreads)
129        .hidden();  // synonym
130     ap.arg("--texoptions %s:OPTLIST", &texoptions)
131       .help("Set extra TextureSystem options (name=val,name=val)");
132     ap.arg("--blur %f:BLURSIZE", &sblur)
133       .help("Add blur to texture lookup");
134     ap.arg("--stblur %f:SBLUR %f:TBLUR", &sblur, &tblur)
135       .help("Add blur (s, t) to texture lookup");
136     ap.arg("--width %f:WIDTH", &width)
137       .help("Multiply filter width of texture lookup");
138     ap.arg("--fill %f:FILLVAL", &fill)
139       .help("Set fill value for missing channels");
140     ap.arg("--wrap %s:MODE", &wrapmodes)
141       .help("Set wrap mode (default, black, clamp, periodic, mirror, overscan)");
142     ap.arg("--anisoaspect %f:ASPECT", &anisoaspect)
143       .help("Set anisotropic ellipse aspect ratio for threadtimes tests (default: 2.0)");
144     ap.arg("--anisomax %d:MAX", &anisomax)
145       .help(Strutil::sprintf("Set max anisotropy (default: %d)", anisomax));
146     ap.arg("--mipmode %d:MODE", &mipmode)
147       .help("Set mip mode (default: 0 = aniso)");
148     ap.arg("--interpmode %d:MODE", &interpmode)
149       .help("Set interp mode (default: 3 = smart bicubic)");
150     ap.arg("--missing %f:R %f:G %f:B", &missing[0], &missing[1], &missing[2])
151       .help("Specify missing texture color");
152     ap.arg("--autotile %d:TILESIZE", &autotile)
153       .help("Set auto-tile size for the image cache");
154     ap.arg("--automip", &automip)
155       .help("Set auto-MIPmap for the image cache");
156     ap.arg("--batch", &batch)
157       .help(Strutil::sprintf("Use batched shading, batch size = %d", Tex::BatchWidth));
158     ap.arg("--handle", &use_handle)
159       .help("Use texture handle rather than name lookup");
160     ap.arg("--searchpath %s:PATHLIST", &searchpath)
161       .help("Search path for files (colon-separated directory list)");
162     ap.arg("--filtertest", &filtertest)
163       .help("Test the filter sizes");
164     ap.arg("--nowarp", &nowarp)
165       .help("Do not warp the image->texture mapping");
166     ap.arg("--tube", &tube)
167       .help("Make a tube projection");
168     ap.arg("--ctr", &test_construction)
169       .help("Test TextureOpt construction time");
170     ap.arg("--gettexels", &test_gettexels)
171       .help("Test TextureSystem::get_texels");
172     ap.arg("--getimagespec", &test_getimagespec)
173       .help("Test TextureSystem::get_imagespec");
174     ap.arg("--offset %f:SOFF %f:TOFF %f:ROFF", &texoffset[0], &texoffset[1], &texoffset[2])
175       .help("Offset texture coordinates");
176     ap.arg("--scalest %f:SSCALE %f:TSCALE", &sscale, &tscale)
177       .help("Scale texture lookups (s, t)");
178     ap.arg("--cachesize %f:MB", &cachesize)
179       .help("Set cache size, in MB");
180     ap.arg("--nodedup %!", &dedup)
181       .help("Turn off de-duplication");
182     ap.arg("--scale %f:SCALEFACTOR", &scalefactor)
183       .help("Scale intensities");
184     ap.arg("--maxfiles %d:MAXFILES", &maxfiles)
185       .help("Set maximum open files");
186     ap.arg("--nountiled", &nountiled)
187       .help("Reject untiled images");
188     ap.arg("--nounmipped", &nounmipped)
189       .help("Reject unmipped images");
190     ap.arg("--graytorgb", &gray_to_rgb)
191       .help("Convert gratscale textures to RGB");
192     ap.arg("--flipt", &flip_t)
193       .help("Flip direction of t coordinate");
194     ap.arg("--derivs", &test_derivs)
195       .help("Test returning derivatives of texture lookups");
196     ap.arg("--resetstats", &resetstats)
197       .help("Print and reset statistics on each iteration");
198     ap.arg("--testhash", &testhash)
199       .help("Test the tile hashing function");
200     ap.arg("--threadtimes %d:MODE", &threadtimes)
201       .help("Do thread timings (arg = workload profile)");
202     ap.arg("--trials %d:N", &ntrials)
203       .help("Number of trials for timings");
204     ap.arg("--wedge", &wedge)
205       .help("Wedge test");
206     ap.arg("--noinvalidate %!", &invalidate_before_iter)
207       .help("Don't invalidate the cache before each --threadtimes trial");
208     ap.arg("--closebeforeiter", &close_before_iter)
209       .help("Close all handles before each --iter");
210     ap.arg("--testicwrite %d:MODE", &testicwrite)
211       .help("Test ImageCache write ability (1=seeded, 2=generated)");
212     ap.arg("--teststatquery", &test_statquery)
213       .help("Test queries of statistics");
214 
215     // clang-format on
216     ap.parse(argc, argv);
217 
218     if (filenames.size() < 1 && !test_construction && !test_getimagespec
219         && !testhash) {
220         std::cerr << "testtex: Must have at least one input file\n";
221         ap.usage();
222         exit(EXIT_FAILURE);
223     }
224 }
225 
226 
227 
228 static void
initialize_opt(TextureOpt & opt)229 initialize_opt(TextureOpt& opt)
230 {
231     opt.sblur  = sblur;
232     opt.tblur  = tblur >= 0.0f ? tblur : sblur;
233     opt.rblur  = sblur;
234     opt.swidth = width;
235     opt.twidth = width;
236     opt.rwidth = width;
237     opt.fill   = (fill >= 0.0f) ? fill : 1.0f;
238     if (missing[0] >= 0)
239         opt.missingcolor = (float*)&missing;
240     TextureOpt::parse_wrapmodes(wrapmodes.c_str(), opt.swrap, opt.twrap);
241     opt.rwrap       = opt.swrap;
242     opt.anisotropic = anisomax;
243     opt.mipmode     = (TextureOpt::MipMode)mipmode;
244     opt.interpmode  = (TextureOpt::InterpMode)interpmode;
245 }
246 
247 
248 
249 static void
initialize_opt(TextureOptBatch & opt)250 initialize_opt(TextureOptBatch& opt)
251 {
252     using namespace Tex;
253     FloatWide sb(sblur);
254     sb.store(opt.sblur);
255     FloatWide tb(tblur >= 0.0f ? tblur : sblur);
256     tb.store(opt.tblur);
257     sb.store(opt.rblur);
258     FloatWide w(width);
259     w.store(opt.swidth);
260     w.store(opt.twidth);
261     w.store(opt.rwidth);
262     opt.fill = (fill >= 0.0f) ? fill : 1.0f;
263     if (missing[0] >= 0)
264         opt.missingcolor = (float*)&missing;
265     Tex::parse_wrapmodes(wrapmodes.c_str(), opt.swrap, opt.twrap);
266     opt.rwrap       = opt.swrap;
267     opt.anisotropic = anisomax;
268     opt.mipmode     = MipMode(mipmode);
269     opt.interpmode  = InterpMode(interpmode);
270 }
271 
272 
273 
274 static void
test_gettextureinfo(ustring filename)275 test_gettextureinfo(ustring filename)
276 {
277     bool ok;
278 
279     int res[2] = { 0 };
280     ok         = texsys->get_texture_info(filename, 0, ustring("resolution"),
281                                   TypeDesc(TypeDesc::INT, 2), res);
282     std::cout << "Result of get_texture_info resolution = " << ok << ' '
283               << res[0] << 'x' << res[1] << "\n";
284 
285     int chan = 0;
286     ok       = texsys->get_texture_info(filename, 0, ustring("channels"),
287                                   TypeDesc::INT, &chan);
288     std::cout << "Result of get_texture_info channels = " << ok << ' ' << chan
289               << "\n";
290 
291     float fchan = 0;
292     ok          = texsys->get_texture_info(filename, 0, ustring("channels"),
293                                   TypeDesc::FLOAT, &fchan);
294     std::cout << "Result of get_texture_info channels = " << ok << ' ' << fchan
295               << "\n";
296 
297     int dataformat = 0;
298     ok = texsys->get_texture_info(filename, 0, ustring("format"), TypeDesc::INT,
299                                   &dataformat);
300     std::cout << "Result of get_texture_info data format = " << ok << ' '
301               << TypeDesc((TypeDesc::BASETYPE)dataformat).c_str() << "\n";
302 
303     const char* datetime = NULL;
304     ok = texsys->get_texture_info(filename, 0, ustring("DateTime"),
305                                   TypeDesc::STRING, &datetime);
306     std::cout << "Result of get_texture_info datetime = " << ok << ' '
307               << (datetime ? datetime : "") << "\n";
308 
309     float avg[4];
310     ok = texsys->get_texture_info(filename, 0, ustring("averagecolor"),
311                                   TypeDesc(TypeDesc::FLOAT, 4), avg);
312     std::cout << "Result of get_texture_info averagecolor = "
313               << (ok ? "yes" : "no\n");
314     if (ok)
315         std::cout << " " << avg[0] << ' ' << avg[1] << ' ' << avg[2] << ' '
316                   << avg[3] << "\n";
317     ok = texsys->get_texture_info(filename, 0, ustring("averagealpha"),
318                                   TypeFloat, avg);
319     std::cout << "Result of get_texture_info averagealpha = "
320               << (ok ? "yes" : "no\n");
321     if (ok)
322         std::cout << " " << avg[0] << "\n";
323     ok = texsys->get_texture_info(filename, 0, ustring("constantcolor"),
324                                   TypeDesc(TypeDesc::FLOAT, 4), avg);
325     std::cout << "Result of get_texture_info constantcolor = "
326               << (ok ? "yes" : "no\n");
327     if (ok)
328         std::cout << " " << avg[0] << ' ' << avg[1] << ' ' << avg[2] << ' '
329                   << avg[3] << "\n";
330 
331     const char* texturetype = NULL;
332     ok = texsys->get_texture_info(filename, 0, ustring("textureformat"),
333                                   TypeDesc::STRING, &texturetype);
334     std::cout << "Texture type is " << ok << ' '
335               << (texturetype ? texturetype : "") << "\n";
336     std::cout << "\n";
337 }
338 
339 
340 
341 template<typename Float = float>
342 inline Imath::Vec2<Float>
warp(const Float & x,const Float & y,const Imath::M33f & xform)343 warp(const Float& x, const Float& y, const Imath::M33f& xform)
344 {
345     Imath::Vec2<Float> coord(x, y);
346     xform.multVecMatrix(coord, coord);
347     return coord;
348 }
349 
350 
351 template<typename Float = float>
352 inline Imath::Vec3<Float>
warp(const Float & x,const Float & y,const Float & z,const Imath::M33f & xform)353 warp(const Float& x, const Float& y, const Float& z, const Imath::M33f& xform)
354 {
355     Imath::Vec3<Float> coord(x, y, z);
356     coord *= xform;
357     return coord;
358 }
359 
360 
361 template<typename Float = float>
362 inline Imath::Vec2<Float>
warp_coord(const Float & x,const Float & y)363 warp_coord(const Float& x, const Float& y)
364 {
365     Imath::Vec2<Float> coord = warp(x / output_xres, y / output_yres, xform);
366     coord.x *= sscale;
367     coord.y *= tscale;
368     coord += Imath::Vec2<Float>(texoffset.x, texoffset.y);
369     return coord;
370 }
371 
372 
373 
374 // Just map pixels to [0,1] st space
375 template<typename Float = float, typename Int = int>
376 void
map_default(const Int & x,const Int & y,Float & s,Float & t,Float & dsdx,Float & dtdx,Float & dsdy,Float & dtdy)377 map_default(const Int& x, const Int& y, Float& s, Float& t, Float& dsdx,
378             Float& dtdx, Float& dsdy, Float& dtdy)
379 {
380     s    = (Float(x) + 0.5f) / output_xres * sscale + texoffset[0];
381     t    = (Float(y) + 0.5f) / output_yres * tscale + texoffset[1];
382     dsdx = 1.0f / output_xres * sscale;
383     dtdx = 0.0f;
384     dsdy = 0.0f;
385     dtdy = 1.0f / output_yres * tscale;
386 }
387 
388 
389 
390 template<typename Float = float, typename Int = int>
391 static void
map_warp(const Int & x,const Int & y,Float & s,Float & t,Float & dsdx,Float & dtdx,Float & dsdy,Float & dtdy)392 map_warp(const Int& x, const Int& y, Float& s, Float& t, Float& dsdx,
393          Float& dtdx, Float& dsdy, Float& dtdy)
394 {
395     const Imath::Vec2<Float> coord  = warp_coord(Float(x) + 0.5f,
396                                                 Float(y) + 0.5f);
397     const Imath::Vec2<Float> coordx = warp_coord(Float(x) + 1.5f,
398                                                  Float(y) + 0.5f);
399     const Imath::Vec2<Float> coordy = warp_coord(Float(x) + 0.5f,
400                                                  Float(y) + 1.5f);
401 
402     s    = coord[0];
403     t    = coord[1];
404     dsdx = coordx[0] - coord[0];
405     dtdx = coordx[1] - coord[1];
406     dsdy = coordy[0] - coord[0];
407     dtdy = coordy[1] - coord[1];
408 }
409 
410 
411 
412 static void
map_tube(const int & x,const int & y,float & s,float & t,float & dsdx,float & dtdx,float & dsdy,float & dtdy)413 map_tube(const int& x, const int& y, float& s, float& t, float& dsdx,
414          float& dtdx, float& dsdy, float& dtdy)
415 {
416     float xt     = (float(x) + 0.5f) / output_xres - 0.5f;
417     float dxt_dx = 1.0f / output_xres;
418     float yt     = (float(y) + 0.5f) / output_yres - 0.5f;
419     float dyt_dy = 1.0f / output_yres;
420     float theta  = atan2f(yt, xt);
421     // See OSL's Dual2 for partial derivs of
422     // atan2, hypot, and 1/x
423     double denom     = 1.0 / (xt * xt + yt * yt);
424     double dtheta_dx = yt * dxt_dx * denom;
425     double dtheta_dy = -xt * dyt_dy * denom;
426     s                = float(4.0 * theta / (2.0 * M_PI));
427     dsdx             = float(4.0 * dtheta_dx / (2.0 * M_PI));
428     dsdy             = float(4.0 * dtheta_dy / (2.0 * M_PI));
429     double h         = hypot(xt, yt);
430     double dh_dx     = xt * dxt_dx / h;
431     double dh_dy     = yt * dyt_dy / h;
432     h *= M_SQRT2;
433     dh_dx *= M_SQRT2;
434     dh_dy *= M_SQRT2;
435     double hinv = 1.0 / h;
436     t           = float(hinv);
437     dtdx        = float(hinv * (-hinv * dh_dx));
438     dtdy        = float(hinv * (-hinv * dh_dy));
439 }
440 
441 
442 
443 // FIXME -- templatize map_tube. For now, just loop over scalar version.
444 static void
map_tube(const Tex::IntWide & x,const Tex::IntWide & y,Tex::FloatWide & s,Tex::FloatWide & t,Tex::FloatWide & dsdx,Tex::FloatWide & dtdx,Tex::FloatWide & dsdy,Tex::FloatWide & dtdy)445 map_tube(const Tex::IntWide& x, const Tex::IntWide& y, Tex::FloatWide& s,
446          Tex::FloatWide& t, Tex::FloatWide& dsdx, Tex::FloatWide& dtdx,
447          Tex::FloatWide& dsdy, Tex::FloatWide& dtdy)
448 {
449     for (int i = 0; i < Tex::BatchWidth; ++i)
450         map_tube(x[i], y[i], s[i], t[i], dsdx[i], dtdx[i], dsdy[i], dtdy[i]);
451 }
452 
453 
454 
455 // To test filters, we always sample at the center of the image, and
456 // keep the minor axis of the filter at 1/256, but we vary the
457 // eccentricity (i.e. major axis length) as we go left (1) to right
458 // (32), and vary the angle as we go top (0) to bottom (2pi).
459 //
460 // If filtering is correct, all pixels should sample from the same MIP
461 // level because they have the same minor axis (1/256), regardless of
462 // eccentricity or angle.  If we specify a texture that has a
463 // distinctive color at the 256-res level, and something totally
464 // different at the 512 and 128 levels, it should be easy to verify that
465 // we aren't over-filtering or under-filtering by selecting the wrong
466 // MIP level.  (Though of course, there are other kinds of mistakes we
467 // could be making, such as computing the wrong eccentricity or angle.)
468 static void
map_filtertest(const int & x,const int & y,float & s,float & t,float & dsdx,float & dtdx,float & dsdy,float & dtdy)469 map_filtertest(const int& x, const int& y, float& s, float& t, float& dsdx,
470                float& dtdx, float& dsdy, float& dtdy)
471 {
472     float minoraxis = 1.0f / 256;
473     float majoraxis = minoraxis
474                       * lerp(1.0f, 32.0f, (float)x / (output_xres - 1));
475     float angle = (float)(2.0 * M_PI * (double)y / (output_yres - 1));
476     float sinangle, cosangle;
477     sincos(angle, &sinangle, &cosangle);
478     s = 0.5f;
479     t = 0.5f;
480 
481     dsdx = minoraxis * cosangle;
482     dtdx = minoraxis * sinangle;
483     dsdy = -majoraxis * sinangle;
484     dtdy = majoraxis * cosangle;
485 }
486 
487 
488 
489 // FIXME -- templatize map_filtertest. For now, just loop over scalar version.
490 static void
map_filtertest(const Tex::IntWide & x,const Tex::IntWide & y,Tex::FloatWide & s,Tex::FloatWide & t,Tex::FloatWide & dsdx,Tex::FloatWide & dtdx,Tex::FloatWide & dsdy,Tex::FloatWide & dtdy)491 map_filtertest(const Tex::IntWide& x, const Tex::IntWide& y, Tex::FloatWide& s,
492                Tex::FloatWide& t, Tex::FloatWide& dsdx, Tex::FloatWide& dtdx,
493                Tex::FloatWide& dsdy, Tex::FloatWide& dtdy)
494 {
495     for (int i = 0; i < Tex::BatchWidth; ++i)
496         map_filtertest(x[i], y[i], s[i], t[i], dsdx[i], dtdx[i], dsdy[i],
497                        dtdy[i]);
498 }
499 
500 
501 
502 template<typename Float = float, typename Int = int>
503 void
map_default_3D(const Int & x,const Int & y,Imath::Vec3<Float> & P,Imath::Vec3<Float> & dPdx,Imath::Vec3<Float> & dPdy,Imath::Vec3<Float> & dPdz)504 map_default_3D(const Int& x, const Int& y, Imath::Vec3<Float>& P,
505                Imath::Vec3<Float>& dPdx, Imath::Vec3<Float>& dPdy,
506                Imath::Vec3<Float>& dPdz)
507 {
508     P[0] = (Float(x) + 0.5f) / output_xres * sscale;
509     P[1] = (Float(y) + 0.5f) / output_yres * tscale;
510     P[2] = 0.5f * sscale;
511     P += texoffset;
512     dPdx[0] = 1.0f / output_xres * sscale;
513     dPdx[1] = 0;
514     dPdx[2] = 0;
515     dPdy[0] = 0;
516     dPdy[1] = 1.0f / output_yres * tscale;
517     dPdy[2] = 0;
518     dPdz.setValue(0, 0, 0);
519 }
520 
521 
522 
523 template<typename Float = float, typename Int = int>
524 void
map_warp_3D(const Int & x,const Int & y,Imath::Vec3<Float> & P,Imath::Vec3<Float> & dPdx,Imath::Vec3<Float> & dPdy,Imath::Vec3<Float> & dPdz)525 map_warp_3D(const Int& x, const Int& y, Imath::Vec3<Float>& P,
526             Imath::Vec3<Float>& dPdx, Imath::Vec3<Float>& dPdy,
527             Imath::Vec3<Float>& dPdz)
528 {
529     Imath::Vec3<Float> coord = warp((Float(x) / output_xres),
530                                     (Float(y) / output_yres), Float(0.5),
531                                     xform);
532     coord.x *= sscale;
533     coord.y *= tscale;
534     coord += texoffset;
535     Imath::Vec3<Float> coordx = warp((Float(x + 1) / output_xres),
536                                      (Float(y) / output_yres), Float(0.5),
537                                      xform);
538     coordx.x *= sscale;
539     coordx.y *= tscale;
540     coordx += texoffset;
541     Imath::Vec3<Float> coordy = warp((Float(x) / output_xres),
542                                      (Float(y + 1) / output_yres), Float(0.5),
543                                      xform);
544     coordy.x *= sscale;
545     coordy.y *= tscale;
546     coordy += texoffset;
547     P    = coord;
548     dPdx = coordx - coord;
549     dPdy = coordy - coord;
550     dPdz.setValue(0, 0, 0);
551 }
552 
553 
554 
555 void
plain_tex_region(ImageBuf & image,ustring filename,Mapping2D mapping,ImageBuf * image_ds,ImageBuf * image_dt,ROI roi)556 plain_tex_region(ImageBuf& image, ustring filename, Mapping2D mapping,
557                  ImageBuf* image_ds, ImageBuf* image_dt, ROI roi)
558 {
559     TextureSystem::Perthread* perthread_info     = texsys->get_perthread_info();
560     TextureSystem::TextureHandle* texture_handle = texsys->get_texture_handle(
561         filename);
562     int nchannels = nchannels_override ? nchannels_override : image.nchannels();
563 
564     TextureOpt opt;
565     initialize_opt(opt);
566 
567     float* result    = OIIO_ALLOCA(float, std::max(3, nchannels));
568     float* dresultds = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
569     float* dresultdt = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
570     for (ImageBuf::Iterator<float> p(image, roi); !p.done(); ++p) {
571         float s, t, dsdx, dtdx, dsdy, dtdy;
572         mapping(p.x(), p.y(), s, t, dsdx, dtdx, dsdy, dtdy);
573 
574         // Call the texture system to do the filtering.
575         bool ok;
576         if (use_handle)
577             ok = texsys->texture(texture_handle, perthread_info, opt, s, t,
578                                  dsdx, dtdx, dsdy, dtdy, nchannels, result,
579                                  dresultds, dresultdt);
580         else
581             ok = texsys->texture(filename, opt, s, t, dsdx, dtdx, dsdy, dtdy,
582                                  nchannels, result, dresultds, dresultdt);
583         if (!ok) {
584             std::string e = texsys->geterror();
585             if (!e.empty())
586                 Strutil::fprintf(std::cerr, "ERROR: %s\n", e);
587         }
588 
589         // Save filtered pixels back to the image.
590         for (int i = 0; i < nchannels; ++i)
591             result[i] *= scalefactor;
592         image.setpixel(p.x(), p.y(), result);
593         if (test_derivs) {
594             image_ds->setpixel(p.x(), p.y(), dresultds);
595             image_dt->setpixel(p.x(), p.y(), dresultdt);
596         }
597     }
598 }
599 
600 
601 
602 void
test_plain_texture(Mapping2D mapping)603 test_plain_texture(Mapping2D mapping)
604 {
605     std::cout << "Testing 2d texture " << filenames[0]
606               << ", output = " << output_filename << "\n";
607     const int nchannels = 4;
608     ImageSpec outspec(output_xres, output_yres, nchannels, TypeDesc::FLOAT);
609     ImageBuf image(outspec);
610     TypeDesc fmt(dataformatname);
611     image.set_write_format(fmt);
612     OIIO::ImageBufAlgo::zero(image);
613     ImageBuf image_ds, image_dt;
614     if (test_derivs) {
615         image_ds.reset(outspec);
616         image_ds.set_write_format(fmt);
617         OIIO::ImageBufAlgo::zero(image_ds);
618         image_dt.reset(outspec);
619         OIIO::ImageBufAlgo::zero(image_dt);
620         image_dt.set_write_format(fmt);
621     }
622 
623     ustring filename = filenames[0];
624 
625     for (int iter = 0; iter < iters; ++iter) {
626         if (iters > 1 && filenames.size() > 1) {
627             // Use a different filename for each iteration
628             int texid = iter % (int)filenames.size();
629             filename  = (filenames[texid]);
630             std::cout << "iter " << iter << " file " << filename << "\n";
631         }
632         if (close_before_iter)
633             texsys->close_all();
634 
635         ImageBufAlgo::parallel_image(
636             get_roi(image.spec()), nthreads,
637             std::bind(plain_tex_region, std::ref(image), filename, mapping,
638                       test_derivs ? &image_ds : NULL,
639                       test_derivs ? &image_dt : NULL, _1));
640         if (resetstats) {
641             std::cout << texsys->getstats(2) << "\n";
642             texsys->reset_stats();
643         }
644     }
645 
646     if (!image.write(output_filename))
647         Strutil::fprintf(std::cerr, "Error writing %s : %s\n", output_filename,
648                          image.geterror());
649     if (test_derivs) {
650         if (!image_ds.write(output_filename + "-ds.exr"))
651             Strutil::fprintf(std::cerr, "Error writing %s : %s\n",
652                              (output_filename + "-ds.exr"),
653                              image_ds.geterror());
654         if (!image_dt.write(output_filename + "-dt.exr"))
655             Strutil::fprintf(std::cerr, "Error writing %s : %s\n",
656                              (output_filename + "-dt.exr"),
657                              image_dt.geterror());
658     }
659 }
660 
661 
662 
663 void
plain_tex_region_batch(ImageBuf & image,ustring filename,Mapping2DWide mapping,ImageBuf * image_ds,ImageBuf * image_dt,const ROI roi)664 plain_tex_region_batch(ImageBuf& image, ustring filename, Mapping2DWide mapping,
665                        ImageBuf* image_ds, ImageBuf* image_dt, const ROI roi)
666 {
667     using namespace Tex;
668     TextureSystem::Perthread* perthread_info     = texsys->get_perthread_info();
669     TextureSystem::TextureHandle* texture_handle = texsys->get_texture_handle(
670         filename);
671     int nchannels_img = image.nchannels();
672     int nchannels = nchannels_override ? nchannels_override : image.nchannels();
673     OIIO_DASSERT(image.spec().format == TypeDesc::FLOAT);
674     OIIO_DASSERT(image_ds == nullptr
675                  || image_ds->spec().format == TypeDesc::FLOAT);
676     OIIO_DASSERT(image_dt == nullptr
677                  || image_dt->spec().format == TypeDesc::FLOAT);
678 
679     TextureOptBatch opt;
680     initialize_opt(opt);
681 
682     int nc               = std::max(3, nchannels);
683     FloatWide* result    = OIIO_ALLOCA(FloatWide, nc);
684     FloatWide* dresultds = test_derivs ? OIIO_ALLOCA(FloatWide, nc) : nullptr;
685     FloatWide* dresultdt = test_derivs ? OIIO_ALLOCA(FloatWide, nc) : nullptr;
686     for (int y = roi.ybegin; y < roi.yend; ++y) {
687         for (int x = roi.xbegin; x < roi.xend; x += BatchWidth) {
688             FloatWide s, t, dsdx, dtdx, dsdy, dtdy;
689             mapping(IntWide::Iota(x), y, s, t, dsdx, dtdx, dsdy, dtdy);
690             int npoints  = std::min(BatchWidth, roi.xend - x);
691             RunMask mask = RunMaskOn >> (BatchWidth - npoints);
692             // Call the texture system to do the filtering.
693             bool ok;
694             if (use_handle)
695                 ok = texsys->texture(texture_handle, perthread_info, opt, mask,
696                                      s.data(), t.data(), dsdx.data(),
697                                      dtdx.data(), dsdy.data(), dtdy.data(),
698                                      nchannels, (float*)result,
699                                      (float*)dresultds, (float*)dresultdt);
700             else
701                 ok = texsys->texture(filename, opt, mask, s.data(), t.data(),
702                                      dsdx.data(), dtdx.data(), dsdy.data(),
703                                      dtdy.data(), nchannels, (float*)result,
704                                      (float*)dresultds, (float*)dresultdt);
705             if (!ok) {
706                 std::string e = texsys->geterror();
707                 if (!e.empty())
708                     Strutil::fprintf(std::cerr, "ERROR: %s\n", e);
709             }
710             // Save filtered pixels back to the image.
711             for (int c = 0; c < nchannels; ++c)
712                 result[c] *= scalefactor;
713             float* resultptr = (float*)image.pixeladdr(x, y);
714             // FIXME: simplify by using SIMD scatter
715             for (int c = 0; c < nchannels; ++c)
716                 for (int i = 0; i < npoints; ++i)
717                     resultptr[c + i * nchannels_img] = result[c][i];
718             if (test_derivs) {
719                 float* resultdsptr = (float*)image_ds->pixeladdr(x, y);
720                 float* resultdtptr = (float*)image_dt->pixeladdr(x, y);
721                 for (int c = 0; c < nchannels; ++c) {
722                     for (int i = 0; i < npoints; ++i) {
723                         resultdsptr[c + i * nchannels_img] = dresultds[c][i];
724                         resultdtptr[c + i * nchannels_img] = dresultdt[c][i];
725                     }
726                 }
727             }
728         }
729     }
730 }
731 
732 
733 
734 void
test_plain_texture_batch(Mapping2DWide mapping)735 test_plain_texture_batch(Mapping2DWide mapping)
736 {
737     std::cout << "Testing BATCHED 2d texture " << filenames[0]
738               << ", output = " << output_filename << "\n";
739     const int nchannels = 4;
740     ImageSpec outspec(output_xres, output_yres, nchannels, TypeDesc::FLOAT);
741     TypeDesc fmt(dataformatname);
742     ImageBuf image(outspec);
743     image.set_write_format(fmt);
744     OIIO::ImageBufAlgo::zero(image);
745     std::unique_ptr<ImageBuf> image_ds, image_dt;
746     if (test_derivs) {
747         image_ds.reset(new ImageBuf(outspec));
748         image_ds->set_write_format(fmt);
749         OIIO::ImageBufAlgo::zero(*image_ds);
750         image_dt.reset(new ImageBuf(outspec));
751         image_dt->set_write_format(fmt);
752         OIIO::ImageBufAlgo::zero(*image_dt);
753     }
754 
755     ustring filename = filenames[0];
756 
757     for (int iter = 0; iter < iters; ++iter) {
758         if (iters > 1 && filenames.size() > 1) {
759             // Use a different filename for each iteration
760             int texid = iter % (int)filenames.size();
761             filename  = (filenames[texid]);
762             std::cout << "iter " << iter << " file " << filename << "\n";
763         }
764         if (close_before_iter)
765             texsys->close_all();
766 
767         ImageBufAlgo::parallel_image(
768             get_roi(image.spec()), nthreads, [&](ROI roi) {
769                 plain_tex_region_batch(image, filename, mapping, image_ds.get(),
770                                        image_dt.get(), roi);
771             });
772         if (resetstats) {
773             std::cout << texsys->getstats(2) << "\n";
774             texsys->reset_stats();
775         }
776     }
777 
778     if (!image.write(output_filename))
779         Strutil::fprintf(std::cerr, "Error writing %s : %s\n", output_filename,
780                          image.geterror());
781     if (test_derivs) {
782         if (!image_ds->write(output_filename + "-ds.exr"))
783             Strutil::fprintf(std::cerr, "Error writing %s : %s\n",
784                              (output_filename + "-ds.exr"),
785                              image_ds->geterror());
786         if (!image_dt->write(output_filename + "-dt.exr"))
787             Strutil::fprintf(std::cerr, "Error writing %s : %s\n",
788                              (output_filename + "-dt.exr"),
789                              image_dt->geterror());
790     }
791 }
792 
793 
794 
795 void
tex3d_region(ImageBuf & image,ustring filename,Mapping3D mapping,ROI roi)796 tex3d_region(ImageBuf& image, ustring filename, Mapping3D mapping, ROI roi)
797 {
798     TextureSystem::Perthread* perthread_info     = texsys->get_perthread_info();
799     TextureSystem::TextureHandle* texture_handle = texsys->get_texture_handle(
800         filename);
801     int nchannels = nchannels_override ? nchannels_override : image.nchannels();
802 
803     TextureOpt opt;
804     initialize_opt(opt);
805     opt.fill = (fill >= 0.0f) ? fill : 0.0f;
806     //    opt.swrap = opt.twrap = opt.rwrap = TextureOpt::WrapPeriodic;
807 
808     float* result    = OIIO_ALLOCA(float, nchannels);
809     float* dresultds = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
810     float* dresultdt = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
811     float* dresultdr = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
812     for (ImageBuf::Iterator<float> p(image, roi); !p.done(); ++p) {
813         Imath::V3f P, dPdx, dPdy, dPdz;
814         mapping(p.x(), p.y(), P, dPdx, dPdy, dPdz);
815 
816         // Call the texture system to do the filtering.
817         bool ok = texsys->texture3d(texture_handle, perthread_info, opt, P,
818                                     dPdx, dPdy, dPdz, nchannels, result,
819                                     dresultds, dresultdt, dresultdr);
820         if (!ok) {
821             std::string e = texsys->geterror();
822             if (!e.empty())
823                 Strutil::fprintf(std::cerr, "ERROR: %s\n", e);
824         }
825 
826         // Save filtered pixels back to the image.
827         for (int i = 0; i < nchannels; ++i)
828             result[i] *= scalefactor;
829         image.setpixel(p.x(), p.y(), result);
830     }
831 }
832 
833 
834 
835 void
tex3d_region_batch(ImageBuf & image,ustring filename,Mapping3DWide mapping,ROI roi)836 tex3d_region_batch(ImageBuf& image, ustring filename, Mapping3DWide mapping,
837                    ROI roi)
838 {
839     using namespace Tex;
840     TextureSystem::Perthread* perthread_info     = texsys->get_perthread_info();
841     TextureSystem::TextureHandle* texture_handle = texsys->get_texture_handle(
842         filename);
843     int nchannels_img = image.nchannels();
844     int nchannels = nchannels_override ? nchannels_override : image.nchannels();
845 
846     TextureOptBatch opt;
847     initialize_opt(opt);
848     opt.fill = (fill >= 0.0f) ? fill : 0.0f;
849     //    opt.swrap = opt.twrap = opt.rwrap = TextureOpt::WrapPeriodic;
850 
851     FloatWide* result    = OIIO_ALLOCA(FloatWide, nchannels);
852     FloatWide* dresultds = test_derivs ? OIIO_ALLOCA(FloatWide, nchannels)
853                                        : NULL;
854     FloatWide* dresultdt = test_derivs ? OIIO_ALLOCA(FloatWide, nchannels)
855                                        : NULL;
856     FloatWide* dresultdr = test_derivs ? OIIO_ALLOCA(FloatWide, nchannels)
857                                        : NULL;
858     for (int y = roi.ybegin; y < roi.yend; ++y) {
859         for (int x = roi.xbegin; x < roi.xend; x += BatchWidth) {
860             Imath::Vec3<FloatWide> P, dPdx, dPdy, dPdz;
861             mapping(IntWide::Iota(x), y, P, dPdx, dPdy, dPdz);
862             int npoints  = std::min(BatchWidth, roi.xend - x);
863             RunMask mask = RunMaskOn >> (BatchWidth - npoints);
864 
865             // Call the texture system to do the filtering.
866             if (y == 0 && x == 0)
867                 std::cout << "P = " << P << "\n";
868             bool ok = texsys->texture3d(texture_handle, perthread_info, opt,
869                                         mask, (float*)&P, (float*)&dPdx,
870                                         (float*)&dPdy, (float*)&dPdz, nchannels,
871                                         (float*)result, (float*)dresultds,
872                                         (float*)dresultdt, (float*)dresultdr);
873             if (!ok) {
874                 std::string e = texsys->geterror();
875                 if (!e.empty())
876                     Strutil::fprintf(std::cerr, "ERROR: %s\n", e);
877             }
878 
879             // Save filtered pixels back to the image.
880             for (int c = 0; c < nchannels; ++c)
881                 result[c] *= scalefactor;
882             float* resultptr = (float*)image.pixeladdr(x, y);
883             // FIXME: simplify by using SIMD scatter
884             for (int c = 0; c < nchannels; ++c)
885                 for (int i = 0; i < npoints; ++i)
886                     resultptr[c + i * nchannels_img] = result[c][i];
887         }
888     }
889 }
890 
891 
892 
893 void
test_texture3d(ustring filename,Mapping3D mapping)894 test_texture3d(ustring filename, Mapping3D mapping)
895 {
896     std::cout << "Testing 3d texture " << filename
897               << ", output = " << output_filename << "\n";
898     int nchannels = nchannels_override ? nchannels_override : 4;
899     ImageSpec outspec(output_xres, output_yres, nchannels, TypeDesc::FLOAT);
900     ImageBuf image(outspec);
901     TypeDesc fmt(dataformatname);
902     image.set_write_format(fmt);
903     OIIO::ImageBufAlgo::zero(image);
904 
905     for (int iter = 0; iter < iters; ++iter) {
906         // Trick: switch to second texture, if given, for second iteration
907         if (iter && filenames.size() > 1)
908             filename = filenames[1];
909         if (close_before_iter)
910             texsys->close_all();
911         ImageBufAlgo::parallel_image(get_roi(image.spec()), nthreads,
912                                      std::bind(tex3d_region, std::ref(image),
913                                                filename, mapping, _1));
914     }
915 
916     if (!image.write(output_filename))
917         Strutil::fprintf(std::cerr, "Error writing %s : %s\n", output_filename,
918                          image.geterror());
919 }
920 
921 
922 
923 void
test_texture3d_batch(ustring filename,Mapping3DWide mapping)924 test_texture3d_batch(ustring filename, Mapping3DWide mapping)
925 {
926     std::cout << "Testing 3d texture " << filename
927               << ", output = " << output_filename << "\n";
928     int nchannels = nchannels_override ? nchannels_override : 4;
929     ImageSpec outspec(output_xres, output_yres, nchannels, TypeDesc::FLOAT);
930     ImageBuf image(outspec);
931     TypeDesc fmt(dataformatname);
932     image.set_write_format(fmt);
933     OIIO::ImageBufAlgo::zero(image);
934 
935     for (int iter = 0; iter < iters; ++iter) {
936         // Trick: switch to second texture, if given, for second iteration
937         if (iter && filenames.size() > 1)
938             filename = filenames[1];
939         if (close_before_iter)
940             texsys->close_all();
941         ImageBufAlgo::parallel_image(get_roi(image.spec()), nthreads,
942                                      [&](ROI roi) {
943                                          tex3d_region_batch(image, filename,
944                                                             mapping, roi);
945                                      });
946     }
947 
948     if (!image.write(output_filename))
949         Strutil::fprintf(std::cerr, "Error writing %s : %s\n", output_filename,
950                          image.geterror());
951 }
952 
953 
954 
test_shadow(ustring)955 static void test_shadow(ustring /*filename*/) {}
956 
957 
958 
test_environment(ustring)959 static void test_environment(ustring /*filename*/) {}
960 
961 
962 
963 static void
test_getimagespec_gettexels(ustring filename)964 test_getimagespec_gettexels(ustring filename)
965 {
966     ImageSpec spec;
967     int miplevel = 0;
968     if (!texsys->get_imagespec(filename, 0, spec)) {
969         Strutil::fprintf(std::cerr, "Could not get spec for %s\n", filename);
970         std::string e = texsys->geterror();
971         if (!e.empty())
972             Strutil::fprintf(std::cerr, "ERROR: %s\n", e);
973         return;
974     }
975 
976     if (!test_gettexels)
977         return;
978 
979     int w         = std::min(spec.width, output_xres);
980     int h         = std::min(spec.height, output_yres);
981     int nchannels = nchannels_override ? nchannels_override : spec.nchannels;
982     ImageSpec postagespec(w, h, nchannels, TypeDesc::FLOAT);
983     ImageBuf buf(postagespec);
984     TextureOpt opt;
985     initialize_opt(opt);
986     std::vector<float> tmp(w * h * nchannels);
987     int x = spec.x + spec.width / 2 - w / 2;
988     int y = spec.y + spec.height / 2 - h / 2;
989     for (int i = 0; i < iters; ++i) {
990         bool ok = texsys->get_texels(filename, opt, miplevel, x, x + w, y,
991                                      y + h, 0, 1, 0, nchannels,
992                                      postagespec.format, &tmp[0]);
993         if (!ok)
994             Strutil::fprintf(std::cerr, "ERROR: %s\n", texsys->geterror());
995     }
996     for (int y = 0; y < h; ++y)
997         for (int x = 0; x < w; ++x) {
998             imagesize_t texoffset = (y * w + x) * spec.nchannels;
999             buf.setpixel(x, y, &tmp[texoffset]);
1000         }
1001     TypeDesc fmt(dataformatname);
1002     if (fmt != TypeDesc::UNKNOWN)
1003         buf.set_write_format(fmt);
1004     buf.write(output_filename);
1005 }
1006 
1007 
1008 
1009 static void
test_hash()1010 test_hash()
1011 {
1012     std::vector<size_t> fourbits(1 << 4, 0);
1013     std::vector<size_t> eightbits(1 << 8, 0);
1014     std::vector<size_t> sixteenbits(1 << 16, 0);
1015     std::vector<size_t> highereightbits(1 << 8, 0);
1016 
1017     const size_t iters = 1000000;
1018     const int res      = 4 * 1024;  // Simulate tiles from a 4k image
1019     const int tilesize = 64;
1020     const int nfiles   = iters / ((res / tilesize) * (res / tilesize));
1021     std::cout << "Testing hashing with " << nfiles << " files of " << res << 'x'
1022               << res << " with " << tilesize << 'x' << tilesize << " tiles:\n";
1023 
1024     ImageCache* imagecache = ImageCache::create();
1025 
1026     // Set up the ImageCacheFiles outside of the timing loop
1027     using OIIO::pvt::ImageCacheFile;
1028     using OIIO::pvt::ImageCacheFileRef;
1029     using OIIO::pvt::ImageCacheImpl;
1030     std::vector<ImageCacheFileRef> icf;
1031     for (int f = 0; f < nfiles; ++f) {
1032         ustring filename = ustring::sprintf("%06d.tif", f);
1033         icf.push_back(
1034             new ImageCacheFile(*(ImageCacheImpl*)imagecache, NULL, filename));
1035     }
1036 
1037     // First, just try to do raw timings of the hash
1038     Timer timer;
1039     size_t i = 0, hh = 0;
1040     for (int f = 0; f < nfiles; ++f) {
1041         for (int y = 0; y < res; y += tilesize) {
1042             for (int x = 0; x < res; x += tilesize, ++i) {
1043                 OIIO::pvt::TileID id(*icf[f], 0, 0, x, y, 0);
1044                 size_t h = id.hash();
1045                 hh += h;
1046             }
1047         }
1048     }
1049     std::cout << "hh = " << hh << "\n";
1050     double time = timer();
1051     double rate = (i / 1.0e6) / time;
1052     std::cout << "Hashing rate: " << Strutil::sprintf("%3.2f", rate)
1053               << " Mhashes/sec\n";
1054 
1055     // Now, check the quality of the hash by looking at the low 4, 8, and
1056     // 16 bits and making sure that they divide into hash buckets fairly
1057     // evenly.
1058     i = 0;
1059     for (int f = 0; f < nfiles; ++f) {
1060         for (int y = 0; y < res; y += tilesize) {
1061             for (int x = 0; x < res; x += tilesize, ++i) {
1062                 OIIO::pvt::TileID id(*icf[f], 0, 0, x, y, 0);
1063                 size_t h = id.hash();
1064                 ++fourbits[h & 0xf];
1065                 ++eightbits[h & 0xff];
1066                 ++highereightbits[(h >> 24) & 0xff];
1067                 ++sixteenbits[h & 0xffff];
1068                 // if (i < 16) std::cout << Strutil::sprintf("%llx\n", h);
1069             }
1070         }
1071     }
1072 
1073     size_t min, max;
1074     min = std::numeric_limits<size_t>::max();
1075     max = 0;
1076     for (int i = 0; i < 16; ++i) {
1077         if (fourbits[i] < min)
1078             min = fourbits[i];
1079         if (fourbits[i] > max)
1080             max = fourbits[i];
1081     }
1082     std::cout << "4-bit hash buckets range from " << min << " to " << max
1083               << "\n";
1084 
1085     min = std::numeric_limits<size_t>::max();
1086     max = 0;
1087     for (int i = 0; i < 256; ++i) {
1088         if (eightbits[i] < min)
1089             min = eightbits[i];
1090         if (eightbits[i] > max)
1091             max = eightbits[i];
1092     }
1093     std::cout << "8-bit hash buckets range from " << min << " to " << max
1094               << "\n";
1095 
1096     min = std::numeric_limits<size_t>::max();
1097     max = 0;
1098     for (int i = 0; i < 256; ++i) {
1099         if (highereightbits[i] < min)
1100             min = highereightbits[i];
1101         if (highereightbits[i] > max)
1102             max = highereightbits[i];
1103     }
1104     std::cout << "higher 8-bit hash buckets range from " << min << " to " << max
1105               << "\n";
1106 
1107     min = std::numeric_limits<size_t>::max();
1108     max = 0;
1109     for (int i = 0; i < (1 << 16); ++i) {
1110         if (sixteenbits[i] < min)
1111             min = sixteenbits[i];
1112         if (sixteenbits[i] > max)
1113             max = sixteenbits[i];
1114     }
1115     std::cout << "16-bit hash buckets range from " << min << " to " << max
1116               << "\n";
1117 
1118     std::cout << "\n";
1119 
1120     ImageCache::destroy(imagecache);
1121 }
1122 
1123 
1124 
1125 static const char* workload_names[] = {
1126     /*0*/ "None",
1127     /*1*/ "Everybody accesses the same spot in one file (handles)",
1128     /*2*/ "Everybody accesses the same spot in one file",
1129     /*3*/ "Coherent access, one file, each thread in similar spots",
1130     /*4*/ "Coherent access, one file, each thread in different spots",
1131     /*5*/ "Coherent access, many files, each thread in similar spots",
1132     /*6*/ "Coherent access, many files, each thread in different spots",
1133     /*7*/ "Coherent access, many files, partially overlapping texture sets",
1134     /*8*/ "Coherent access, many files, partially overlapping texture sets, no extra busy work",
1135     NULL
1136 };
1137 
1138 
1139 
1140 void
do_tex_thread_workout(int iterations,int mythread)1141 do_tex_thread_workout(int iterations, int mythread)
1142 {
1143     int nfiles = (int)filenames.size();
1144     float s = 0.1f, t = 0.1f;
1145     int nchannels = nchannels_override ? nchannels_override : 3;
1146     float* result = OIIO_ALLOCA(float, nchannels);
1147     TextureOpt opt;
1148     initialize_opt(opt);
1149     float* dresultds = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
1150     float* dresultdt = test_derivs ? OIIO_ALLOCA(float, nchannels) : NULL;
1151     TextureSystem::Perthread* perthread_info = texsys->get_perthread_info();
1152     int pixel, whichfile = 0;
1153 
1154     std::vector<TextureSystem::TextureHandle*> texture_handles;
1155     for (auto f : filenames)
1156         texture_handles.emplace_back(texsys->get_texture_handle(f));
1157 
1158     ImageSpec spec0;
1159     bool ok = texsys->get_imagespec(filenames[0], 0, spec0);
1160     if (!ok) {
1161         Strutil::fprintf(std::cerr, "Unexpected error: %s\n",
1162                          texsys->geterror());
1163         return;
1164     }
1165     // Compute a filter size that's between the second and third MIP levels.
1166     float fw   = (1.0f / spec0.width) * 1.5f * 2.0;
1167     float fh   = (1.0f / spec0.height) * 1.5f * 2.0;
1168     float dsdx = fw, dtdx = 0.0f, dsdy = 0.0f, dtdy = fh;
1169     if (anisoaspect > 1.001f) {
1170         // Make an ellipse 30 degrees off horizontal, with given aspect
1171         float xs = sqrtf(3.0) / 2.0, ys = 0.5f;
1172         dsdx = fw * xs * anisoaspect;
1173         dtdx = fh * ys * anisoaspect;
1174         dsdy = fw * ys;
1175         dtdy = -fh * xs;
1176     }
1177 
1178     for (int i = 0; i < iterations; ++i) {
1179         pixel   = i;
1180         bool ok = false;
1181         // Several different texture access patterns
1182         switch (threadtimes) {
1183         case 1:
1184             // Workload 1: Speed of light: Static texture access (same
1185             // texture coordinates all the time, one file), with handles
1186             // and per-thread data already queried only once rather than
1187             // per-call.
1188             ok = texsys->texture(texture_handles[0], perthread_info, opt, s, t,
1189                                  dsdx, dtdx, dsdy, dtdy, nchannels, result,
1190                                  dresultds, dresultdt);
1191             break;
1192         case 2:
1193             // Workload 2: Static texture access, with filenames.
1194             ok = texsys->texture(filenames[0], opt, s, t, dsdx, dtdx, dsdy,
1195                                  dtdy, nchannels, result, dresultds, dresultdt);
1196             break;
1197         case 3:
1198         case 4:
1199             // Workload 3: One file, coherent texture coordinates.
1200             //
1201             // Workload 4: Each thread starts with a different texture
1202             // coordinate offset, so likely are not simultaneously
1203             // accessing the very same tile as the other threads.
1204             if (threadtimes == 4)
1205                 pixel += 57557 * mythread;
1206             break;
1207         case 5:
1208         case 6:
1209             // Workload 5: Coherent texture coordinates, but access
1210             // a series of textures at each coordinate.
1211             //
1212             // Workload 6: Each thread starts with a different texture
1213             // coordinate offset, so likely are not simultaneously
1214             // accessing the very same tile as the other threads.
1215             whichfile = i % nfiles;
1216             pixel     = i / nfiles;
1217             if (threadtimes == 6)
1218                 pixel += 57557 * mythread;
1219             break;
1220         case 7:
1221         case 8:
1222             // Workload 7: Coherent texture coordinates, but access
1223             // a series of textures at each coordinate, which partially
1224             // overlap with other threads.
1225             {
1226                 int file = i % 8;
1227                 if (file < 2)  // everybody accesses the first 2 files
1228                     whichfile = std::min(file, nfiles - 1);
1229                 else  // and a slowly changing set of 6 others
1230                     whichfile = (file + 11 * mythread + i / 1000) % nfiles;
1231                 pixel = i / nfiles;
1232                 pixel += 57557 * mythread;
1233             }
1234             break;
1235         default:
1236             OIIO_ASSERT_MSG(0, "Unkonwn thread work pattern %d", threadtimes);
1237         }
1238         if (!ok && spec0.width && spec0.height) {
1239             s = (((2 * pixel) % spec0.width) + 0.5f) / spec0.width;
1240             t = (((2 * ((2 * pixel) / spec0.width)) % spec0.height) + 0.5f)
1241                 / spec0.height;
1242             if (use_handle)
1243                 ok = texsys->texture(texture_handles[whichfile], perthread_info,
1244                                      opt, s, t, dsdx, dtdx, dsdy, dtdy,
1245                                      nchannels, result, dresultds, dresultdt);
1246             else
1247                 ok = texsys->texture(filenames[whichfile], opt, s, t, dsdx,
1248                                      dtdx, dsdy, dtdy, nchannels, result,
1249                                      dresultds, dresultdt);
1250         }
1251         if (!ok) {
1252             Strutil::fprintf(std::cerr, "Unexpected error: %s\n",
1253                              texsys->geterror());
1254             return;
1255         }
1256         // Do some pointless work, to simulate that in a real app, there
1257         // would be operations interspersed with texture accesses.
1258         if (threadtimes != 8 /* skip on this test */) {
1259             for (int j = 0; j < 30; ++j)
1260                 for (int c = 0; c < nchannels; ++c)
1261                     result[c] = cosf(result[c]);
1262         }
1263     }
1264     // Force the compiler to not optimize away the "other work"
1265     for (int c = 0; c < nchannels; ++c)
1266         OIIO_ASSERT(!isnan(result[c]));
1267 }
1268 
1269 
1270 
1271 // Launch numthreads threads each of which performs a workout of texture
1272 // accesses.
1273 void
launch_tex_threads(int numthreads,int iterations)1274 launch_tex_threads(int numthreads, int iterations)
1275 {
1276     if (invalidate_before_iter)
1277         texsys->invalidate_all(true);
1278     OIIO::thread_group threads;
1279     for (int i = 0; i < numthreads; ++i) {
1280         threads.create_thread(std::bind(do_tex_thread_workout, iterations, i));
1281     }
1282     OIIO_ASSERT((int)threads.size() == numthreads);
1283     threads.join_all();
1284 }
1285 
1286 
1287 
1288 class GridImageInput final : public ImageInput {
1289 public:
GridImageInput()1290     GridImageInput()
1291         : m_miplevel(-1)
1292     {
1293     }
~GridImageInput()1294     virtual ~GridImageInput() { close(); }
format_name(void) const1295     virtual const char* format_name(void) const final { return "grid"; }
valid_file(const std::string &) const1296     virtual bool valid_file(const std::string& /*filename*/) const final
1297     {
1298         return true;
1299     }
open(const std::string &,ImageSpec & newspec)1300     virtual bool open(const std::string& /*name*/, ImageSpec& newspec) final
1301     {
1302         bool ok = seek_subimage(0, 0);
1303         newspec = spec();
1304         return ok;
1305     }
close()1306     virtual bool close() { return true; }
current_miplevel(void) const1307     virtual int current_miplevel(void) const final { return m_miplevel; }
seek_subimage(int subimage,int miplevel)1308     virtual bool seek_subimage(int subimage, int miplevel) final
1309     {
1310         if (subimage > 0)
1311             return false;
1312         if (miplevel > 0 && automip /* if automip is on, don't generate MIP */)
1313             return false;
1314         if (miplevel == m_miplevel)
1315             return true;
1316         int res = 512;
1317         res >>= miplevel;
1318         if (res == 0)
1319             return false;
1320         m_spec             = ImageSpec(res, res, 3, TypeDesc::FLOAT);
1321         m_spec.tile_width  = std::min(64, res);
1322         m_spec.tile_height = std::min(64, res);
1323         m_spec.tile_depth  = 1;
1324         m_miplevel         = miplevel;
1325         return true;
1326     }
read_native_scanline(int,int,int,int,void *)1327     virtual bool read_native_scanline(int /*subimage*/, int /*miplevel*/,
1328                                       int /*y*/, int /*z*/,
1329                                       void* /*data*/) final
1330     {
1331         return false;
1332     }
read_native_tile(int subimage,int miplevel,int xbegin,int ybegin,int zbegin,void * data)1333     virtual bool read_native_tile(int subimage, int miplevel, int xbegin,
1334                                   int ybegin, int zbegin, void* data) final
1335     {
1336         lock_guard lock(m_mutex);
1337         if (!seek_subimage(subimage, miplevel))
1338             return false;
1339         float* tile = (float*)data;
1340         for (int z = zbegin, zend = z + m_spec.tile_depth; z < zend; ++z)
1341             for (int y = ybegin, yend = y + m_spec.tile_height; y < yend; ++y)
1342                 for (int x = xbegin, xend = x + m_spec.tile_width; x < xend;
1343                      ++x) {
1344                     tile[0] = float(x) / m_spec.width;
1345                     tile[2] = float(y) / m_spec.height;
1346                     tile[1] = (((x / 16) & 1) == ((y / 16) & 1))
1347                                   ? 1.0f / (m_miplevel + 1)
1348                                   : 0.05f;
1349                     tile += m_spec.nchannels;
1350                 }
1351         return true;
1352     }
1353 
1354 private:
1355     int m_miplevel;
1356 };
1357 
1358 
1359 
1360 ImageInput*
make_grid_input()1361 make_grid_input()
1362 {
1363     return new GridImageInput;
1364 }
1365 
1366 
1367 
1368 void
test_icwrite(int testicwrite)1369 test_icwrite(int testicwrite)
1370 {
1371     std::cout << "Testing IC write, mode " << testicwrite << "\n";
1372 
1373     // The global "shared" ImageCache will be the same one the
1374     // TextureSystem uses.
1375     ImageCache* ic = ImageCache::create();
1376 
1377     // Set up the fake file ane add it
1378     int tw = 64, th = 64;  // tile width and height
1379     int nc = nchannels_override ? nchannels_override : 3;  // channels
1380     ImageSpec spec(512, 512, nc, TypeDesc::FLOAT);
1381     spec.depth       = 1;
1382     spec.tile_width  = tw;
1383     spec.tile_height = th;
1384     spec.tile_depth  = 1;
1385     ustring filename(filenames[0]);
1386     bool ok = ic->add_file(filename, make_grid_input);
1387     if (!ok) {
1388         std::cout << "ic->add_file error: " << ic->geterror() << "\n";
1389         OIIO_ASSERT(ok);
1390     }
1391 
1392     // Now add all the tiles if it's a seeded map
1393     // testicwrite == 1 means to seed the first MIP level using add_tile.
1394     // testicwrite == 2 does not use add_tile, but instead will rely on
1395     // the make_grid_input custom ImageInput that constructs a pattern
1396     // procedurally.
1397     if (testicwrite == 1) {
1398         std::vector<float> tile(spec.tile_pixels() * spec.nchannels);
1399         for (int ty = 0; ty < spec.height; ty += th) {
1400             for (int tx = 0; tx < spec.width; tx += tw) {
1401                 // Construct a tile
1402                 for (int y = 0; y < th; ++y)
1403                     for (int x = 0; x < tw; ++x) {
1404                         int index = (y * tw + x) * nc;
1405                         int xx = x + tx, yy = y + ty;
1406                         tile[index + 0] = float(xx) / spec.width;
1407                         tile[index + 1] = float(yy) / spec.height;
1408                         tile[index + 2] = (!(xx % 10) || !(yy % 10)) ? 1.0f
1409                                                                      : 0.0f;
1410                     }
1411                 bool ok = ic->add_tile(filename, 0, 0, tx, ty, 0, 0, -1,
1412                                        TypeDesc::FLOAT, &tile[0]);
1413                 if (!ok) {
1414                     Strutil::fprintf(std::cerr, "ic->add_tile error: %s\n",
1415                                      ic->geterror());
1416                     return;
1417                 }
1418             }
1419         }
1420     }
1421 }
1422 
1423 
1424 
1425 int
main(int argc,const char * argv[])1426 main(int argc, const char* argv[])
1427 {
1428     Filesystem::convert_native_arguments(argc, argv);
1429     getargs(argc, argv);
1430 
1431     // environment variable TESTTEX_BATCH can force batch mode
1432     string_view testtex_batch = Sysutil::getenv("TESTTEX_BATCH");
1433     if (testtex_batch.size())
1434         batch = Strutil::from_string<int>(testtex_batch);
1435 
1436     OIIO::attribute("threads", nthreads);
1437 
1438     texsys = TextureSystem::create();
1439     std::cout << "Created texture system\n";
1440     if (texoptions.size())
1441         texsys->attribute("options", texoptions);
1442     texsys->attribute("autotile", autotile);
1443     texsys->attribute("automip", (int)automip);
1444     texsys->attribute("deduplicate", (int)dedup);
1445     if (cachesize >= 0)
1446         texsys->attribute("max_memory_MB", cachesize);
1447     else
1448         texsys->getattribute("max_memory_MB", TypeFloat, &cachesize);
1449     if (maxfiles >= 0)
1450         texsys->attribute("max_open_files", maxfiles);
1451     if (searchpath.length())
1452         texsys->attribute("searchpath", searchpath);
1453     if (nountiled)
1454         texsys->attribute("accept_untiled", 0);
1455     if (nounmipped)
1456         texsys->attribute("accept_unmipped", 0);
1457     texsys->attribute("gray_to_rgb", gray_to_rgb);
1458     texsys->attribute("flip_t", flip_t);
1459 
1460     if (test_construction) {
1461         Timer t;
1462         for (int i = 0; i < 1000000000; ++i) {
1463             TextureOpt opt;
1464             dummyptr = &opt;  // This forces the optimizer to keep the loop
1465         }
1466         std::cout << "TextureOpt construction: " << t() << " ns\n";
1467         TextureOpt canonical, copy;
1468         t.reset();
1469         t.start();
1470         for (int i = 0; i < 1000000000; ++i) {
1471             copy     = canonical;
1472             dummyptr = &copy;  // This forces the optimizer to keep the loop
1473         }
1474         std::cout << "TextureOpt copy: " << t() << " ns\n";
1475     }
1476 
1477     if (testicwrite && filenames.size()) {
1478         test_icwrite(testicwrite);
1479     }
1480 
1481     if (test_getimagespec) {
1482         ImageSpec spec;
1483         for (int i = 0; i < iters; ++i) {
1484             texsys->get_imagespec(filenames[0], 0, spec);
1485         }
1486         iters = 0;
1487     }
1488 
1489     if (test_gettexels) {
1490         test_getimagespec_gettexels(filenames[0]);
1491         iters = 0;
1492     }
1493 
1494     if (testhash) {
1495         test_hash();
1496     }
1497 
1498     Imath::M33f scale;
1499     scale.scale(Imath::V2f(0.3, 0.3));
1500     Imath::M33f rot;
1501     rot.rotate(radians(25.0f));
1502     Imath::M33f trans;
1503     trans.translate(Imath::V2f(0.75f, 0.25f));
1504     Imath::M33f persp(2, 0, 0, 0, 0.8, -0.55, 0, 0, 1);
1505     xform = persp * rot * trans * scale;
1506     xform.invert();
1507 
1508     if (threadtimes) {
1509         // If the --iters flag was used, do that number of iterations total
1510         // (divided among the threads). If not supplied (iters will be 1),
1511         // then use a large constant *per thread*.
1512         const int iterations = iters > 1 ? iters : 2000000;
1513         std::cout << "Workload: " << workload_names[threadtimes] << "\n";
1514         std::cout << "texture cache size = " << cachesize << " MB\n";
1515         std::cout << "hw threads = " << Sysutil::hardware_concurrency() << "\n";
1516         std::cout << "times are best of " << ntrials << " trials\n\n";
1517         std::cout << "threads  time (s)   speedup efficiency\n";
1518         std::cout << "-------- -------- --------- ----------\n";
1519 
1520         if (nthreads == 0)
1521             nthreads = Sysutil::hardware_concurrency();
1522         static int threadcounts[] = { 1,  2,  4,  8,   12,   16,
1523                                       24, 32, 64, 128, 1024, 1 << 30 };
1524         float single_thread_time  = 0.0f;
1525         for (int i = 0; threadcounts[i] <= nthreads; ++i) {
1526             int nt  = wedge ? threadcounts[i] : nthreads;
1527             int its = iters > 1 ? (std::max(1, iters / nt))
1528                                 : iterations;  // / nt;
1529             double range;
1530             double t = time_trial(std::bind(launch_tex_threads, nt, its),
1531                                   ntrials, &range);
1532             if (nt == 1)
1533                 single_thread_time = (float)t;
1534             float speedup    = (single_thread_time /*/nt*/) / (float)t;
1535             float efficiency = (single_thread_time / nt) / float(t);
1536             std::cout << Strutil::sprintf(
1537                 "%3d     %8.2f   %6.1fx  %6.1f%%    range %.2f\t(%d iters/thread)\n",
1538                 nt, t, speedup, efficiency * 100.0f, range, its);
1539             std::cout.flush();
1540             if (!wedge)
1541                 break;  // don't loop if we're not wedging
1542         }
1543         std::cout << "\n";
1544 
1545     } else if (iters > 0 && filenames.size()) {
1546         ustring filename(filenames[0]);
1547         test_gettextureinfo(filenames[0]);
1548         const char* texturetype = "Plain Texture";
1549         texsys->get_texture_info(filename, 0, ustring("texturetype"),
1550                                  TypeDesc::STRING, &texturetype);
1551         Timer timer;
1552         if (!strcmp(texturetype, "Plain Texture")) {
1553             if (batch) {
1554                 if (nowarp)
1555                     test_plain_texture_batch(map_default);
1556                 else if (tube)
1557                     test_plain_texture_batch(map_tube);
1558                 else if (filtertest)
1559                     test_plain_texture_batch(map_filtertest);
1560                 else
1561                     test_plain_texture_batch(map_warp);
1562 
1563             } else {
1564                 if (nowarp)
1565                     test_plain_texture(map_default);
1566                 else if (tube)
1567                     test_plain_texture(map_tube);
1568                 else if (filtertest)
1569                     test_plain_texture(map_filtertest);
1570                 else
1571                     test_plain_texture(map_warp);
1572             }
1573         }
1574         if (!strcmp(texturetype, "Volume Texture")) {
1575             if (batch) {
1576                 if (nowarp)
1577                     test_texture3d_batch(filename, map_default_3D);
1578                 else
1579                     test_texture3d_batch(filename, map_warp_3D);
1580             } else {
1581                 if (nowarp)
1582                     test_texture3d(filename, map_default_3D);
1583                 else
1584                     test_texture3d(filename, map_warp_3D);
1585             }
1586         }
1587         if (!strcmp(texturetype, "Shadow")) {
1588             test_shadow(filename);
1589         }
1590         if (!strcmp(texturetype, "Environment")) {
1591             test_environment(filename);
1592         }
1593         test_getimagespec_gettexels(filename);
1594         std::cout << "Time: " << Strutil::timeintervalformat(timer()) << "\n";
1595     }
1596 
1597     if (test_statquery) {
1598         std::cout << "Testing statistics queries:\n";
1599         int total_files = 0;
1600         texsys->getattribute("total_files", total_files);
1601         std::cout << "  Total files: " << total_files << "\n";
1602         std::vector<ustring> all_filenames(total_files);
1603         std::cout << TypeDesc(TypeDesc::STRING, total_files) << "\n";
1604         texsys->getattribute("all_filenames",
1605                              TypeDesc(TypeDesc::STRING, total_files),
1606                              &all_filenames[0]);
1607         for (int i = 0; i < total_files; ++i) {
1608             int timesopened   = 0;
1609             int64_t bytesread = 0;
1610             float iotime      = 0.0f;
1611             int64_t data_size = 0, file_size = 0;
1612             texsys->get_texture_info(all_filenames[i], 0,
1613                                      ustring("stat:timesopened"), TypeDesc::INT,
1614                                      &timesopened);
1615             texsys->get_texture_info(all_filenames[i], 0,
1616                                      ustring("stat:bytesread"), TypeDesc::INT64,
1617                                      &bytesread);
1618             texsys->get_texture_info(all_filenames[i], 0,
1619                                      ustring("stat:iotime"), TypeDesc::FLOAT,
1620                                      &iotime);
1621             texsys->get_texture_info(all_filenames[i], 0,
1622                                      ustring("stat:image_size"),
1623                                      TypeDesc::INT64, &data_size);
1624             texsys->get_texture_info(all_filenames[i], 0,
1625                                      ustring("stat:file_size"), TypeDesc::INT64,
1626                                      &file_size);
1627             std::cout << Strutil::sprintf(
1628                 "  %d: %s  opens=%d, read=%s, time=%s, data=%s, file=%s\n", i,
1629                 all_filenames[i], timesopened, Strutil::memformat(bytesread),
1630                 Strutil::timeintervalformat(iotime, 2),
1631                 Strutil::memformat(data_size), Strutil::memformat(file_size));
1632         }
1633     }
1634 
1635     std::cout << "Memory use: "
1636               << Strutil::memformat(Sysutil::memory_used(true)) << "\n";
1637     std::cout << texsys->getstats(verbose ? 2 : 0) << "\n";
1638     TextureSystem::destroy(texsys);
1639 
1640     // Force all files to close, ugh, it's the only way I can find to solve
1641     // an occasional problem with static destructor order fiasco with
1642     // field3d when building with EMBEDPLUGINS=0 on MacOS.
1643     ImageCache::create()->close_all();
1644 
1645     if (verbose)
1646         std::cout << "\nustrings: " << ustring::getstats(false) << "\n\n";
1647     return 0;
1648 }
1649