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 = © // 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 ×opened);
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