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 <list>
8 #include <sstream>
9 #include <string>
10 
11 #include <OpenImageIO/dassert.h>
12 #include <OpenImageIO/filter.h>
13 #include <OpenImageIO/fmath.h>
14 #include <OpenImageIO/imagecache.h>
15 #include <OpenImageIO/imageio.h>
16 #include <OpenImageIO/strutil.h>
17 #include <OpenImageIO/texture.h>
18 #include <OpenImageIO/thread.h>
19 #include <OpenImageIO/typedesc.h>
20 #include <OpenImageIO/ustring.h>
21 #include <OpenImageIO/varyingref.h>
22 
23 #include "imagecache_pvt.h"
24 #include "texture_pvt.h"
25 
26 
27 /*
28 Discussion about environment map issues and conventions:
29 
30 Latlong maps (spherical parameterization) come in two varieties
31 that OIIO supports:
32 
33 (a) Our default follows the RenderMan convention of "z is up" and
34 left-handed, with the north pole (t=0) pointing toward +z and the
35 "center" (0.5,0.5) pointing toward +y:
36 
37           --s-->         (0,0,1)
38   (0,0) +---------------------------------------+ (1,0)
39         |                                       |
40      |  |                                       |
41      t  |(-1,0,0)        (1,0,0)                |
42      |  +         +         +         +         |
43      V  |      (0,-1,0)            (0,1,0)      |
44         |                                       |
45         |                                       |
46   (0,1) +---------------------------------------+ (1,1)
47                          (0,0,-1)
48 
49 (b) If the metadata "oiio:updirection" is "y", the map is assumed to use
50 the OpenEXR convention where the +y axis is "up", and the coordinate
51 system is right-handed, and the center pixel points toward the +x axis:
52 
53           --s-->         (0,1,0)
54   (0,0) +---------------------------------------+ (1,0)
55         |                                       |
56      |  |                                       |
57      t  |(0,0,-1)        (0,0,1)                |
58      |  +         +         +         +         |
59      V  |      (1,0,0)            (0,-1,0)      |
60         |                                       |
61         |                                       |
62   (0,1) +---------------------------------------+ (1,1)
63                          (0,-1,0)
64 
65 
66 
67 By default, we assume the conversion between pixel coordinates and
68 texture coordinates is the same as for 2D textures; that is, pixel (i,j)
69 is located at s,t = ( (i+0.5)/xres, (j+0.5)/yres ).  We believe that
70 this is the usual interpretation of latlong maps.
71 
72 However, if the metadata "oiio:sampleborder" is present and nonzero,
73 we assume instead that pixel (i,j) has st coordinates ( i/(xres-1),
74 j/(yres-1) ), in other words, that the edge texels correspond exactly to
75 the pole or median seam, and therefore that pixel (0,j) and (xres-1,j)
76 should be identical for all j, pixel (i,0) should be identical for all
77 i, and pixel (i,yres-1) should be identical for all i.  This latter
78 convention is dictated by OpenEXR.
79 
80 
81 
82 Cubeface environment maps are composed of six orthogonal faces (that
83 we name px, nx, py, ny, pz, nz).
84 
85                major   +s dir   +t dir
86        Face    axis    (right)  (down)
87        ----    -----   -------  ------
88         px      +x       -z       -y
89         nx      -x       +z       -y
90         py      +y       +x       +z
91         ny      -y       +x       -z
92         pz      +z       +x       -y
93         nz      -z       -x       -y
94 
95 The cubeface layout is easily visualized by "unwrapping":
96 
97                     +-------------+
98                     |py           |
99                     |             |
100                     |     +y->+x  |
101                     |      |      |
102                     |      V      |
103                     |     +z      |
104       +-------------|-------------|-------------+-------------+
105       |nx           |pz           |px           |nz           |
106       |             |             |             |             |
107       |     -x->+z  |     +z->+x  |     +x->-z  |     -z->-x  |
108       |      |      |      |      |      |      |      |      |
109       |      V      |      V      |      V      |      V      |
110       |     -y      |     -y      |     -y      |     -y      |
111       +-------------+-------------+-------------+-------------+
112                     |ny           |
113                     |             |
114                     |    -y->+x   |
115                     |     |       |
116                     |     V       |
117                     |    -z       |
118                     +-------------+
119 
120 But that's just for visualization.  The way it's actually stored in a
121 file varies by convention of the file format.  Here are the two
122 conventions that we support natively:
123 
124 (a) "2x3" (a.k.a. the RenderMan/BMRT convention) wherein all six faces
125 are arranged within a single image:
126 
127       +-------------+-------------+-------------+
128       |px           |py           |pz           |
129       |             |             |             |
130       |    +x->-z   |    +y->+x   |    +z->+x   |
131       |     |       |     |       |     |       |
132       |     V       |     V       |     V       |
133       |    -y       |    +z       |    -y       |
134       |-------------|-------------|-------------|
135       |nx           |ny           |nz           |
136       |             |             |             |
137       |    -x->+z   |    -y->+x   |    -z->-x   |
138       |     |       |     |       |     |       |
139       |     V       |     V       |     V       |
140       |    -y       |    -z       |    -y       |
141       +-------------+-------------+-------------+
142 
143 The space for each "face" is an integer multiple of the tile size,
144 padded by black pixels if necessary (i.e. if the face res is not a
145 full multiple of the tile size).  For example,
146 
147       +--------+--------+--------+
148       |px|     |py|     |pz|     |
149       |--+     |--+     |--+     |
150       | (black)|        |        |
151       |--------+--------+--------|
152       |nx|     |ny|     |nz|     |
153       |--+     |--+     |--+     |
154       |        |        |        |
155       +--------+--------+--------+
156 
157 This might happen on low-res MIP levels if the tile size is 64x64 but
158 each face is only 8x8, say.
159 
160 The way we signal these things is for the ImageSpec width,height to be
161 the true data window size (3 x res, 2 x res), but for
162 full_width,full_height to be the size of the valid area of each face.
163 
164 (b) "6x1" (the OpenEXR convention) wherein the six faces are arranged
165 in a vertical stack like this:
166 
167       +--+
168       |px|
169       +--+
170       |nx|
171       +--+
172       |py|
173       +--+
174       |ny|
175       +--+
176       |pz|
177       +--+
178       |nz|
179       +--+
180 
181 Which of these conventions is being followed in a particular cubeface
182 environment map file should be obvious merely by looking at the aspect
183 ratio -- 3:2 or 1:6.
184 
185 As with latlong maps, by default we assume the conversion between pixel
186 coordinates and texture coordinates within a face is the same as for 2D
187 textures; that is, pixel (i,j) is located at s,t = ( (i+0.5)/faceres,
188 (j+0.5)/faceres ).
189 
190 However, if the metadata "oiio:sampleborder" is present and nonzero, we
191 assume instead that pixel (i,j) has st coordinates ( i/(faceres-1),
192 j/(faceres-1) ), in other words, that the edge texels correspond exactly
193 to the cube edge itself, and therefore that each cube face's edge texels
194 are identical to the bordering face, and that any corner pixel values
195 are identical for all three faces that share the corner.  This
196 convention is dictated by OpenEXR.
197 
198 */
199 
200 
201 OIIO_NAMESPACE_BEGIN
202 using namespace pvt;
203 using namespace simd;
204 
205 namespace {  // anonymous
206 
207 static EightBitConverter<float> uchar2float;
208 
209 }  // end anonymous namespace
210 
211 namespace pvt {  // namespace pvt
212 
213 
214 
215 bool
environment(ustring filename,TextureOptions & options,Runflag * runflags,int beginactive,int endactive,VaryingRef<Imath::V3f> R,VaryingRef<Imath::V3f> dRdx,VaryingRef<Imath::V3f> dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)216 TextureSystemImpl::environment(ustring filename, TextureOptions& options,
217                                Runflag* runflags, int beginactive,
218                                int endactive, VaryingRef<Imath::V3f> R,
219                                VaryingRef<Imath::V3f> dRdx,
220                                VaryingRef<Imath::V3f> dRdy, int nchannels,
221                                float* result, float* dresultds,
222                                float* dresultdt)
223 {
224     Perthread* thread_info        = get_perthread_info();
225     TextureHandle* texture_handle = get_texture_handle(filename, thread_info);
226     return environment(texture_handle, thread_info, options, runflags,
227                        beginactive, endactive, R, dRdx, dRdy, nchannels, result,
228                        dresultds, dresultdt);
229 }
230 
231 
232 
233 bool
environment(TextureHandle * texture_handle,Perthread * thread_info,TextureOptions & options,Runflag * runflags,int beginactive,int endactive,VaryingRef<Imath::V3f> R,VaryingRef<Imath::V3f> dRdx,VaryingRef<Imath::V3f> dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)234 TextureSystemImpl::environment(TextureHandle* texture_handle,
235                                Perthread* thread_info, TextureOptions& options,
236                                Runflag* runflags, int beginactive,
237                                int endactive, VaryingRef<Imath::V3f> R,
238                                VaryingRef<Imath::V3f> dRdx,
239                                VaryingRef<Imath::V3f> dRdy, int nchannels,
240                                float* result, float* dresultds,
241                                float* dresultdt)
242 {
243     if (!texture_handle)
244         return false;
245     bool ok = true;
246     result += beginactive * nchannels;
247     if (dresultds) {
248         dresultds += beginactive * nchannels;
249         dresultdt += beginactive * nchannels;
250     }
251     for (int i = beginactive; i < endactive; ++i) {
252         if (runflags[i]) {
253             TextureOpt opt(options, i);
254             ok &= environment(texture_handle, thread_info, opt, R[i], dRdx[i],
255                               dRdy[i], nchannels, result, dresultds, dresultdt);
256         }
257         result += nchannels;
258         if (dresultds) {
259             dresultds += nchannels;
260             dresultdt += nchannels;
261         }
262     }
263     return ok;
264 }
265 
266 
267 
268 /// Convert a direction vector to latlong st coordinates
269 ///
270 inline void
vector_to_latlong(const Imath::V3f & R,bool y_is_up,float & s,float & t)271 vector_to_latlong(const Imath::V3f& R, bool y_is_up, float& s, float& t)
272 {
273     if (y_is_up) {
274         s = atan2f(-R[0], R[2]) / (2.0f * (float)M_PI) + 0.5f;
275         t = 0.5f - atan2f(R[1], hypotf(R[2], -R[0])) / (float)M_PI;
276     } else {
277         s = atan2f(R[1], R[0]) / (2.0f * (float)M_PI) + 0.5f;
278         t = 0.5f - atan2f(R[2], hypotf(R[0], R[1])) / (float)M_PI;
279     }
280     // learned from experience, beware NaNs
281     if (isnan(s))
282         s = 0.0f;
283     if (isnan(t))
284         t = 0.0f;
285 }
286 
287 
288 
289 bool
environment(ustring filename,TextureOpt & options,const Imath::V3f & R,const Imath::V3f & dRdx,const Imath::V3f & dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)290 TextureSystemImpl::environment(ustring filename, TextureOpt& options,
291                                const Imath::V3f& R, const Imath::V3f& dRdx,
292                                const Imath::V3f& dRdy, int nchannels,
293                                float* result, float* dresultds,
294                                float* dresultdt)
295 {
296     PerThreadInfo* thread_info = m_imagecache->get_perthread_info();
297     TextureFile* texturefile   = find_texturefile(filename, thread_info);
298     return environment((TextureHandle*)texturefile, (Perthread*)thread_info,
299                        options, R, dRdx, dRdy, nchannels, result, dresultds,
300                        dresultdt);
301 }
302 
303 
304 
305 bool
environment(TextureHandle * texture_handle_,Perthread * thread_info_,TextureOpt & options,const Imath::V3f & _R,const Imath::V3f & _dRdx,const Imath::V3f & _dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)306 TextureSystemImpl::environment(TextureHandle* texture_handle_,
307                                Perthread* thread_info_, TextureOpt& options,
308                                const Imath::V3f& _R, const Imath::V3f& _dRdx,
309                                const Imath::V3f& _dRdy, int nchannels,
310                                float* result, float* dresultds,
311                                float* dresultdt)
312 {
313     // Handle >4 channel lookups by recursion.
314     if (nchannels > 4) {
315         int save_firstchannel = options.firstchannel;
316         while (nchannels) {
317             int n   = std::min(nchannels, 4);
318             bool ok = environment(texture_handle_, thread_info_, options, _R,
319                                   _dRdx, _dRdy, n, result, dresultds,
320                                   dresultdt);
321             if (!ok)
322                 return false;
323             result += n;
324             if (dresultds)
325                 dresultds += n;
326             if (dresultdt)
327                 dresultdt += n;
328             options.firstchannel += n;
329             nchannels -= n;
330         }
331         options.firstchannel = save_firstchannel;  // restore what we changed
332         return true;
333     }
334 
335     PerThreadInfo* thread_info = m_imagecache->get_perthread_info(
336         (PerThreadInfo*)thread_info_);
337     TextureFile* texturefile = verify_texturefile((TextureFile*)texture_handle_,
338                                                   thread_info);
339     ImageCacheStatistics& stats(thread_info->m_stats);
340     ++stats.environment_batches;
341     ++stats.environment_queries;
342 
343     if (!texturefile || texturefile->broken())
344         return missing_texture(options, nchannels, result, dresultds,
345                                dresultdt);
346 
347     if (!options.subimagename.empty()) {
348         // If subimage was specified by name, figure out its index.
349         int s = m_imagecache->subimage_from_name(texturefile,
350                                                  options.subimagename);
351         if (s < 0) {
352             error("Unknown subimage \"{}\" in texture \"{}\"",
353                   options.subimagename, texturefile->filename());
354             return missing_texture(options, nchannels, result, dresultds,
355                                    dresultdt);
356         }
357         options.subimage = s;
358         options.subimagename.clear();
359     }
360     if (options.subimage < 0 || options.subimage >= texturefile->subimages()) {
361         error("Unknown subimage \"{}\" in texture \"{}\"", options.subimagename,
362               texturefile->filename());
363         return missing_texture(options, nchannels, result, dresultds,
364                                dresultdt);
365     }
366     const ImageSpec& spec(texturefile->spec(options.subimage, 0));
367 
368     // Environment maps dictate particular wrap modes
369     options.swrap = texturefile->m_sample_border
370                         ? TextureOpt::WrapPeriodicSharedBorder
371                         : TextureOpt::WrapPeriodic;
372     options.twrap = TextureOpt::WrapClamp;
373 
374     options.envlayout  = LayoutLatLong;
375     int actualchannels = Imath::clamp(spec.nchannels - options.firstchannel, 0,
376                                       nchannels);
377 
378     // Initialize results to 0.  We'll add from here on as we sample.
379     for (int c = 0; c < nchannels; ++c)
380         result[c] = 0;
381     if (dresultds) {
382         for (int c = 0; c < nchannels; ++c)
383             dresultds[c] = 0;
384         for (int c = 0; c < nchannels; ++c)
385             dresultdt[c] = 0;
386     }
387     // If the user only provided us with one pointer, clear both to simplify
388     // the rest of the code, but only after we zero out the data for them so
389     // they know something went wrong.
390     if (!(dresultds && dresultdt))
391         dresultds = dresultdt = NULL;
392 
393     // Calculate unit-length vectors in the direction of R, R+dRdx, R+dRdy.
394     // These define the ellipse we're filtering over.
395     Imath::V3f R = _R;
396     R.normalize();  // center
397     Imath::V3f Rx = _R + _dRdx;
398     Rx.normalize();  // x axis of the ellipse
399     Imath::V3f Ry = _R + _dRdy;
400     Ry.normalize();  // y axis of the ellipse
401     // angles formed by the ellipse axes.
402     float xfilt_noblur = std::max(safe_acos(R.dot(Rx)), 1e-8f);
403     float yfilt_noblur = std::max(safe_acos(R.dot(Ry)), 1e-8f);
404     int naturalres = int((float)M_PI / std::min(xfilt_noblur, yfilt_noblur));
405     // FIXME -- figure naturalres separately for s and t
406     // FIXME -- ick, why is it x and y at all, shouldn't it be s and t?
407     // N.B. naturalres formulated for latlong
408 
409     // Account for width and blur
410     float xfilt = xfilt_noblur * options.swidth + options.sblur;
411     float yfilt = yfilt_noblur * options.twidth + options.tblur;
412 
413     // Figure out major versus minor, and aspect ratio
414     Imath::V3f Rmajor;  // major axis
415     float majorlength, minorlength;
416     bool x_is_majoraxis = (xfilt >= yfilt);
417     if (x_is_majoraxis) {
418         Rmajor      = Rx;
419         majorlength = xfilt;
420         minorlength = yfilt;
421     } else {
422         Rmajor      = Ry;
423         majorlength = yfilt;
424         minorlength = xfilt;
425     }
426 
427     sampler_prototype sampler;
428     long long* probecount;
429     switch (options.interpmode) {
430     case TextureOpt::InterpClosest:
431         sampler    = &TextureSystemImpl::sample_closest;
432         probecount = &stats.closest_interps;
433         break;
434     case TextureOpt::InterpBilinear:
435         sampler    = &TextureSystemImpl::sample_bilinear;
436         probecount = &stats.bilinear_interps;
437         break;
438     case TextureOpt::InterpBicubic:
439         sampler    = &TextureSystemImpl::sample_bicubic;
440         probecount = &stats.cubic_interps;
441         break;
442     default:
443         sampler    = &TextureSystemImpl::sample_bilinear;
444         probecount = &stats.bilinear_interps;
445         break;
446     }
447 
448     TextureOpt::MipMode mipmode = options.mipmode;
449     bool aniso                  = (mipmode == TextureOpt::MipModeDefault
450                   || mipmode == TextureOpt::MipModeAniso);
451 
452     float aspect, trueaspect, filtwidth;
453     int nsamples;
454     float invsamples;
455     if (aniso) {
456         aspect    = anisotropic_aspect(majorlength, minorlength, options,
457                                     trueaspect);
458         filtwidth = minorlength;
459         if (trueaspect > stats.max_aniso)
460             stats.max_aniso = trueaspect;
461         nsamples   = std::max(1, (int)ceilf(aspect - 0.25f));
462         invsamples = 1.0f / nsamples;
463     } else {
464         filtwidth  = options.conservative_filter ? majorlength : minorlength;
465         nsamples   = 1;
466         invsamples = 1.0f;
467     }
468 
469     ImageCacheFile::SubimageInfo& subinfo(
470         texturefile->subimageinfo(options.subimage));
471     int min_mip_level = subinfo.min_mip_level;
472 
473     // FIXME -- assuming latlong
474     bool ok   = true;
475     float pos = -0.5f + 0.5f * invsamples;
476     for (int sample = 0; sample < nsamples; ++sample, pos += invsamples) {
477         Imath::V3f Rsamp = R + pos * Rmajor;
478         float s, t;
479         vector_to_latlong(Rsamp, texturefile->m_y_up, s, t);
480 
481         // Determine the MIP-map level(s) we need: we will blend
482         //  data(miplevel[0]) * (1-levelblend) + data(miplevel[1]) * levelblend
483         int miplevel[2]  = { -1, -1 };
484         float levelblend = 0;
485 
486         int nmiplevels = (int)subinfo.levels.size();
487         for (int m = min_mip_level; m < nmiplevels; ++m) {
488             // Compute the filter size in raster space at this MIP level.
489             // Filters are in radians, and the vertical resolution of a
490             // latlong map is PI radians.  So to compute the raster size of
491             // our filter width...
492             float filtwidth_ras = subinfo.spec(m).full_height * filtwidth
493                                   * M_1_PI;
494             // Once the filter width is smaller than one texel at this level,
495             // we've gone too far, so we know that we want to interpolate the
496             // previous level and the current level.  Note that filtwidth_ras
497             // is expected to be >= 0.5, or would have stopped one level ago.
498             if (filtwidth_ras <= 1) {
499                 miplevel[0] = m - 1;
500                 miplevel[1] = m;
501                 levelblend  = Imath::clamp(2.0f * filtwidth_ras - 1.0f, 0.0f,
502                                           1.0f);
503                 break;
504             }
505         }
506         if (miplevel[1] < 0) {
507             // We'd like to blur even more, but make due with the coarsest
508             // MIP level.
509             miplevel[0] = nmiplevels - 1;
510             miplevel[1] = miplevel[0];
511             levelblend  = 0;
512         } else if (miplevel[0] < min_mip_level) {
513             // We wish we had even more resolution than the finest MIP level,
514             // but tough for us.
515             miplevel[0] = min_mip_level;
516             miplevel[1] = min_mip_level;
517             levelblend  = 0;
518         }
519         if (options.mipmode == TextureOpt::MipModeOneLevel) {
520             // Force use of just one mipmap level
521             miplevel[1] = miplevel[0];
522             levelblend  = 0;
523         } else if (mipmode == TextureOpt::MipModeNoMIP) {
524             // Just sample from lowest level
525             miplevel[0] = min_mip_level;
526             miplevel[1] = min_mip_level;
527             levelblend  = 0;
528         }
529 
530         float levelweight[2] = { 1.0f - levelblend, levelblend };
531 
532         int npointson = 0;
533         for (int level = 0; level < 2; ++level) {
534             if (!levelweight[level])
535                 continue;
536             ++npointson;
537             int lev = miplevel[level];
538             if (options.interpmode == TextureOpt::InterpSmartBicubic) {
539                 if (lev == 0
540                     || (texturefile->spec(options.subimage, lev).full_height
541                         < naturalres / 2)) {
542                     sampler = &TextureSystemImpl::sample_bicubic;
543                     ++stats.cubic_interps;
544                 } else {
545                     sampler = &TextureSystemImpl::sample_bilinear;
546                     ++stats.bilinear_interps;
547                 }
548             } else {
549                 *probecount += 1;
550             }
551 
552             OIIO_SIMD4_ALIGN float sval[4] = { s, 0.0f, 0.0f, 0.0f };
553             OIIO_SIMD4_ALIGN float tval[4] = { t, 0.0f, 0.0f, 0.0f };
554             OIIO_SIMD4_ALIGN float weight[4]
555                 = { levelweight[level] * invsamples, 0.0f, 0.0f, 0.0f };
556             vfloat4 r, drds, drdt;
557             ok &= (this->*sampler)(1, sval, tval, miplevel[level], *texturefile,
558                                    thread_info, options, nchannels,
559                                    actualchannels, weight, &r,
560                                    dresultds ? &drds : NULL,
561                                    dresultds ? &drdt : NULL);
562             for (int c = 0; c < nchannels; ++c)
563                 result[c] += r[c];
564             if (dresultds) {
565                 for (int c = 0; c < nchannels; ++c) {
566                     dresultds[c] += drds[c];
567                     dresultdt[c] += drdt[c];
568                 }
569             }
570         }
571     }
572     stats.aniso_probes += nsamples;
573     ++stats.aniso_queries;
574 
575     if (actualchannels < nchannels && options.firstchannel == 0
576         && m_gray_to_rgb)
577         fill_gray_channels(spec, nchannels, result, dresultds, dresultdt);
578 
579     return ok;
580 }
581 
582 
583 
584 bool
environment(TextureHandle * texture_handle,Perthread * thread_info,TextureOptBatch & options,Tex::RunMask mask,const float * R,const float * dRdx,const float * dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)585 TextureSystemImpl::environment(TextureHandle* texture_handle,
586                                Perthread* thread_info, TextureOptBatch& options,
587                                Tex::RunMask mask, const float* R,
588                                const float* dRdx, const float* dRdy,
589                                int nchannels, float* result, float* dresultds,
590                                float* dresultdt)
591 {
592     // (FIXME) CHEAT! Texture points individually
593     TextureOpt opt;
594     opt.firstchannel        = options.firstchannel;
595     opt.subimage            = options.subimage;
596     opt.subimagename        = options.subimagename;
597     opt.swrap               = (TextureOpt::Wrap)options.swrap;
598     opt.twrap               = (TextureOpt::Wrap)options.twrap;
599     opt.mipmode             = (TextureOpt::MipMode)options.mipmode;
600     opt.interpmode          = (TextureOpt::InterpMode)options.interpmode;
601     opt.anisotropic         = options.anisotropic;
602     opt.conservative_filter = options.conservative_filter;
603     opt.fill                = options.fill;
604     opt.missingcolor        = options.missingcolor;
605 
606     bool ok          = true;
607     Tex::RunMask bit = 1;
608     for (int i = 0; i < Tex::BatchWidth; ++i, bit <<= 1) {
609         float r[4], drds[4], drdt[4];  // temp result
610         if (mask & bit) {
611             opt.sblur  = options.sblur[i];
612             opt.tblur  = options.tblur[i];
613             opt.swidth = options.swidth[i];
614             opt.twidth = options.twidth[i];
615             Imath::V3f R_(R[i], R[i + Tex::BatchWidth],
616                           R[i + 2 * Tex::BatchWidth]);
617             Imath::V3f dRdx_(dRdx[i], dRdx[i + Tex::BatchWidth],
618                              dRdx[i + 2 * Tex::BatchWidth]);
619             Imath::V3f dRdy_(dRdy[i], dRdy[i + Tex::BatchWidth],
620                              dRdy[i + 2 * Tex::BatchWidth]);
621             if (dresultds) {
622                 ok &= environment(texture_handle, thread_info, opt, R_, dRdx_,
623                                   dRdy_, nchannels, r, drds, drdt);
624                 for (int c = 0; c < nchannels; ++c) {
625                     result[c * Tex::BatchWidth + i]    = r[c];
626                     dresultds[c * Tex::BatchWidth + i] = drds[c];
627                     dresultdt[c * Tex::BatchWidth + i] = drdt[c];
628                 }
629             } else {
630                 ok &= environment(texture_handle, thread_info, opt, R_, dRdx_,
631                                   dRdy_, nchannels, r);
632                 for (int c = 0; c < nchannels; ++c) {
633                     result[c * Tex::BatchWidth + i] = r[c];
634                 }
635             }
636         }
637     }
638     return ok;
639 }
640 
641 
642 
643 bool
environment(ustring filename,TextureOptBatch & options,Tex::RunMask mask,const float * R,const float * dRdx,const float * dRdy,int nchannels,float * result,float * dresultds,float * dresultdt)644 TextureSystemImpl::environment(ustring filename, TextureOptBatch& options,
645                                Tex::RunMask mask, const float* R,
646                                const float* dRdx, const float* dRdy,
647                                int nchannels, float* result, float* dresultds,
648                                float* dresultdt)
649 {
650     Perthread* thread_info        = get_perthread_info();
651     TextureHandle* texture_handle = get_texture_handle(filename, thread_info);
652     return environment(texture_handle, thread_info, options, mask, R, dRdx,
653                        dRdy, nchannels, result, dresultds, dresultdt);
654 }
655 
656 
657 }  // end namespace pvt
658 
659 OIIO_NAMESPACE_END
660