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