// Copyright 2008-present Contributors to the OpenImageIO project. // SPDX-License-Identifier: BSD-3-Clause // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../field3d.imageio/field3d_backdoor.h" #include "imagecache_pvt.h" #include "texture_pvt.h" OIIO_NAMESPACE_BEGIN using namespace pvt; using namespace f3dpvt; namespace { // anonymous static EightBitConverter uchar2float; static ustring s_field3d("field3d"); // OIIO_FORCEINLINE float uchar2float (unsigned char val) { // return float(val) * (1.0f/255.0f); // } OIIO_FORCEINLINE float ushort2float(unsigned short val) { return float(val) * (1.0f / 65535.0f); } OIIO_FORCEINLINE float half2float(half val) { return float(val); } } // end anonymous namespace namespace pvt { // namespace pvt bool TextureSystemImpl::texture3d(ustring filename, TextureOpt& options, const Imath::V3f& P, const Imath::V3f& dPdx, const Imath::V3f& dPdy, const Imath::V3f& dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { PerThreadInfo* thread_info = m_imagecache->get_perthread_info(); TextureFile* texturefile = find_texturefile(filename, thread_info); return texture3d((TextureHandle*)texturefile, (Perthread*)thread_info, options, P, dPdx, dPdy, dPdz, nchannels, result, dresultds, dresultdt, dresultdr); } bool TextureSystemImpl::texture3d(TextureHandle* texture_handle_, Perthread* thread_info_, TextureOpt& options, const Imath::V3f& P, const Imath::V3f& dPdx, const Imath::V3f& dPdy, const Imath::V3f& dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { // Handle >4 channel lookups by recursion. if (nchannels > 4) { int save_firstchannel = options.firstchannel; while (nchannels) { int n = std::min(nchannels, 4); bool ok = texture3d(texture_handle_, thread_info_, options, P, dPdx, dPdy, dPdz, n, result, dresultds, dresultdt, dresultdr); if (!ok) return false; result += n; if (dresultds) dresultds += n; if (dresultdt) dresultdt += n; if (dresultdr) dresultdr += n; options.firstchannel += n; nchannels -= n; } options.firstchannel = save_firstchannel; // restore what we changed return true; } #if 0 // FIXME: currently, no support of actual MIPmapping. No rush, // since the only volume format we currently support, Field3D, // doesn't support MIPmapping. static const texture3d_lookup_prototype lookup_functions[] = { // Must be in the same order as Mipmode enum &TextureSystemImpl::texture3d_lookup, &TextureSystemImpl::texture3d_lookup_nomip, &TextureSystemImpl::texture3d_lookup_trilinear_mipmap, &TextureSystemImpl::texture3d_lookup_trilinear_mipmap, &TextureSystemImpl::texture3d_lookup }; texture3d_lookup_prototype lookup = lookup_functions[(int)options.mipmode]; #else texture3d_lookup_prototype lookup = &TextureSystemImpl::texture3d_lookup_nomip; #endif PerThreadInfo* thread_info = m_imagecache->get_perthread_info( (PerThreadInfo*)thread_info_); TextureFile* texturefile = verify_texturefile((TextureFile*)texture_handle_, thread_info); ImageCacheStatistics& stats(thread_info->m_stats); ++stats.texture3d_batches; ++stats.texture3d_queries; if (!texturefile || texturefile->broken()) return missing_texture(options, nchannels, result, dresultds, dresultdt, dresultdr); if (!options.subimagename.empty()) { // If subimage was specified by name, figure out its index. int s = m_imagecache->subimage_from_name(texturefile, options.subimagename); if (s < 0) { error("Unknown subimage \"{}\" in texture \"{}\"", options.subimagename, texturefile->filename()); return missing_texture(options, nchannels, result, dresultds, dresultdt, dresultdr); } options.subimage = s; options.subimagename.clear(); } if (options.subimage < 0 || options.subimage >= texturefile->subimages()) { error("Unknown subimage \"{}\" in texture \"{}\"", options.subimagename, texturefile->filename()); return missing_texture(options, nchannels, result, dresultds, dresultdt, dresultdr); } const ImageSpec& spec(texturefile->spec(options.subimage, 0)); // Figure out the wrap functions if (options.swrap == TextureOpt::WrapDefault) options.swrap = (TextureOpt::Wrap)texturefile->swrap(); if (options.swrap == TextureOpt::WrapPeriodic && ispow2(spec.width)) options.swrap = TextureOpt::WrapPeriodicPow2; if (options.twrap == TextureOpt::WrapDefault) options.twrap = (TextureOpt::Wrap)texturefile->twrap(); if (options.twrap == TextureOpt::WrapPeriodic && ispow2(spec.height)) options.twrap = TextureOpt::WrapPeriodicPow2; if (options.rwrap == TextureOpt::WrapDefault) options.rwrap = (TextureOpt::Wrap)texturefile->rwrap(); if (options.rwrap == TextureOpt::WrapPeriodic && ispow2(spec.depth)) options.rwrap = TextureOpt::WrapPeriodicPow2; int actualchannels = Imath::clamp(spec.nchannels - options.firstchannel, 0, nchannels); // Do the volume lookup in local space. Imath::V3f Plocal; const auto& si(texturefile->subimageinfo(options.subimage)); if (si.Mlocal) { // See if there is a world-to-local transform stored in the cache // entry. If so, use it to transform the input point. si.Mlocal->multVecMatrix(P, Plocal); } else if (texturefile->fileformat() == s_field3d) { // Field3d is special -- it allows nonlinear or time-varying // transforms procedurally, but we have to use a back door. auto input = texturefile->open(thread_info); Field3DInput_Interface* f3di = (Field3DInput_Interface*)input.get(); if (!f3di) { errorf("Unable to open texture \"%s\"", texturefile->filename()); return false; } f3di->worldToLocal(P, Plocal, options.time); } else { // If no world-to-local matrix could be discerned, just use the // input point directly. Plocal = P; } // FIXME: we don't bother with this for dPdx, dPdy, and dPdz only // because we know that we don't currently filter volume lookups and // therefore don't actually use the derivs. If/when we do, we'll // need to transform them into local space as well. bool ok = (this->*lookup)(*texturefile, thread_info, options, nchannels, actualchannels, Plocal, dPdx, dPdy, dPdz, result, dresultds, dresultdt, dresultdr); if (actualchannels < nchannels && options.firstchannel == 0 && m_gray_to_rgb) fill_gray_channels(spec, nchannels, result, dresultds, dresultdt, dresultdr); return ok; } bool TextureSystemImpl::texture3d(ustring filename, TextureOptions& options, Runflag* runflags, int beginactive, int endactive, VaryingRef P, VaryingRef dPdx, VaryingRef dPdy, VaryingRef dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { Perthread* thread_info = get_perthread_info(); TextureHandle* texture_handle = get_texture_handle(filename, thread_info); return texture3d(texture_handle, thread_info, options, runflags, beginactive, endactive, P, dPdx, dPdy, dPdz, nchannels, result, dresultds, dresultdt, dresultdr); } bool TextureSystemImpl::texture3d( TextureHandle* texture_handle, Perthread* thread_info, TextureOptions& options, Runflag* runflags, int beginactive, int endactive, VaryingRef P, VaryingRef dPdx, VaryingRef dPdy, VaryingRef dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { bool ok = true; result += beginactive * nchannels; if (dresultds) { dresultds += beginactive * nchannels; dresultdt += beginactive * nchannels; } for (int i = beginactive; i < endactive; ++i) { if (runflags[i]) { TextureOpt opt(options, i); ok &= texture3d(texture_handle, thread_info, opt, P[i], dPdx[i], dPdy[i], dPdz[i], 4, result, dresultds, dresultdt, dresultdr); } result += nchannels; if (dresultds) { dresultds += nchannels; dresultdt += nchannels; dresultdr += nchannels; } } return ok; } bool TextureSystemImpl::texture3d_lookup_nomip( TextureFile& texturefile, PerThreadInfo* thread_info, TextureOpt& options, int nchannels_result, int actualchannels, const Imath::V3f& P, const Imath::V3f& /*dPdx*/, const Imath::V3f& /*dPdy*/, const Imath::V3f& /*dPdz*/, float* result, float* dresultds, float* dresultdt, float* dresultdr) { // Initialize results to 0. We'll add from here on as we sample. for (int c = 0; c < nchannels_result; ++c) result[c] = 0; if (dresultds) { OIIO_DASSERT(dresultdt && dresultdr); for (int c = 0; c < nchannels_result; ++c) dresultds[c] = 0; for (int c = 0; c < nchannels_result; ++c) dresultdt[c] = 0; for (int c = 0; c < nchannels_result; ++c) dresultdr[c] = 0; } // If the user only provided us with one pointer, clear all to simplify // the rest of the code, but only after we zero out the data for them so // they know something went wrong. if (!(dresultds && dresultdt && dresultdr)) dresultds = dresultdt = dresultdr = NULL; static const accum3d_prototype accum_functions[] = { // Must be in the same order as InterpMode enum &TextureSystemImpl::accum3d_sample_closest, &TextureSystemImpl::accum3d_sample_bilinear, &TextureSystemImpl::accum3d_sample_bilinear, // FIXME: bicubic, &TextureSystemImpl::accum3d_sample_bilinear, }; accum3d_prototype accumer = accum_functions[(int)options.interpmode]; bool ok = (this->*accumer)(P, 0, texturefile, thread_info, options, nchannels_result, actualchannels, 1.0f, result, dresultds, dresultdt, dresultdr); // Update stats ImageCacheStatistics& stats(thread_info->m_stats); ++stats.aniso_queries; ++stats.aniso_probes; switch (options.interpmode) { case TextureOpt::InterpClosest: ++stats.closest_interps; break; case TextureOpt::InterpBilinear: ++stats.bilinear_interps; break; case TextureOpt::InterpBicubic: ++stats.cubic_interps; break; case TextureOpt::InterpSmartBicubic: ++stats.bilinear_interps; break; } return ok; } bool TextureSystemImpl::accum3d_sample_closest( const Imath::V3f& P, int miplevel, TextureFile& texturefile, PerThreadInfo* thread_info, TextureOpt& options, int nchannels_result, int actualchannels, float weight, float* accum, float* daccumds, float* daccumdt, float* daccumdr) { const ImageSpec& spec(texturefile.spec(options.subimage, miplevel)); const ImageCacheFile::LevelInfo& levelinfo( texturefile.levelinfo(options.subimage, miplevel)); TypeDesc::BASETYPE pixeltype = texturefile.pixeltype(options.subimage); // As passed in, (s,t) map the texture to (0,1). Remap to texel coords. float s = P[0] * spec.full_width + spec.full_x; float t = P[1] * spec.full_height + spec.full_y; float r = P[2] * spec.full_depth + spec.full_z; int stex, ttex, rtex; // Texel coordinates (void)floorfrac(s, &stex); // don't need fractional result (void)floorfrac(t, &ttex); (void)floorfrac(r, &rtex); wrap_impl swrap_func = wrap_functions[(int)options.swrap]; wrap_impl twrap_func = wrap_functions[(int)options.twrap]; wrap_impl rwrap_func = wrap_functions[(int)options.rwrap]; bool svalid, tvalid, rvalid; // Valid texels? false means black border svalid = swrap_func(stex, spec.x, spec.width); tvalid = twrap_func(ttex, spec.y, spec.height); rvalid = rwrap_func(rtex, spec.z, spec.depth); if (!levelinfo.full_pixel_range) { svalid &= (stex >= spec.x && stex < (spec.x + spec.width)); // data window tvalid &= (ttex >= spec.y && ttex < (spec.y + spec.height)); rvalid &= (rtex >= spec.z && rtex < (spec.z + spec.depth)); } if (!(svalid & tvalid & rvalid)) { // All texels we need were out of range and using 'black' wrap. return true; } int tile_chbegin = 0, tile_chend = spec.nchannels; if (spec.nchannels > m_max_tile_channels) { // For files with many channels, narrow the range we cache tile_chbegin = options.firstchannel; tile_chend = options.firstchannel + actualchannels; } int tile_s = (stex - spec.x) % spec.tile_width; int tile_t = (ttex - spec.y) % spec.tile_height; int tile_r = (rtex - spec.z) % spec.tile_depth; TileID id(texturefile, options.subimage, miplevel, stex - tile_s, ttex - tile_t, rtex - tile_r, tile_chbegin, tile_chend); bool ok = find_tile(id, thread_info, true); if (!ok) error("{}", m_imagecache->geterror()); TileRef& tile(thread_info->tile); if (!tile || !ok) return false; int tilepel = (tile_r * spec.tile_height + tile_t) * spec.tile_width + tile_s; int startchan_in_tile = options.firstchannel - id.chbegin(); int offset = spec.nchannels * tilepel + startchan_in_tile; OIIO_DASSERT((size_t)offset < spec.nchannels * spec.tile_pixels()); if (pixeltype == TypeDesc::UINT8) { const unsigned char* texel = tile->bytedata() + offset; for (int c = 0; c < actualchannels; ++c) accum[c] += weight * uchar2float(texel[c]); } else if (pixeltype == TypeDesc::UINT16) { const unsigned short* texel = tile->ushortdata() + offset; for (int c = 0; c < actualchannels; ++c) accum[c] += weight * ushort2float(texel[c]); } else if (pixeltype == TypeDesc::HALF) { const half* texel = tile->halfdata() + offset; for (int c = 0; c < actualchannels; ++c) accum[c] += weight * half2float(texel[c]); } else { OIIO_DASSERT(pixeltype == TypeDesc::FLOAT); const float* texel = tile->floatdata() + offset; for (int c = 0; c < actualchannels; ++c) accum[c] += weight * texel[c]; } // Add appropriate amount of "fill" color to extra channels in // non-"black"-wrapped regions. if (nchannels_result > actualchannels && options.fill) { float f = weight * options.fill; for (int c = actualchannels; c < nchannels_result; ++c) accum[c] += f; if (OIIO_UNLIKELY(daccumds)) { OIIO_DASSERT(daccumdt && daccumdr); for (int c = actualchannels; c < nchannels_result; ++c) { daccumds[c] = 0.0f; daccumdt[c] = 0.0f; daccumdr[c] = 0.0f; } } } return true; } bool TextureSystemImpl::accum3d_sample_bilinear( const Imath::V3f& P, int miplevel, TextureFile& texturefile, PerThreadInfo* thread_info, TextureOpt& options, int nchannels_result, int actualchannels, float weight, float* accum, float* daccumds, float* daccumdt, float* daccumdr) { const ImageSpec& spec(texturefile.spec(options.subimage, miplevel)); const ImageCacheFile::LevelInfo& levelinfo( texturefile.levelinfo(options.subimage, miplevel)); TypeDesc::BASETYPE pixeltype = texturefile.pixeltype(options.subimage); // As passed in, (s,t) map the texture to (0,1). Remap to texel coords // and subtract 0.5 because samples are at texel centers. float s = P[0] * spec.full_width + spec.full_x - 0.5f; float t = P[1] * spec.full_height + spec.full_y - 0.5f; float r = P[2] * spec.full_depth + spec.full_z - 0.5f; int sint, tint, rint; float sfrac = floorfrac(s, &sint); float tfrac = floorfrac(t, &tint); float rfrac = floorfrac(r, &rint); // Now (sint,tint,rint) are the integer coordinates of the texel to the // immediate "upper left" of the lookup point, and (sfrac,tfrac,rfrac) are // the amount that the lookup point is actually offset from the // texel center (with (1,1) being all the way to the next texel down // and to the right). // Wrap wrap_impl swrap_func = wrap_functions[(int)options.swrap]; wrap_impl twrap_func = wrap_functions[(int)options.twrap]; wrap_impl rwrap_func = wrap_functions[(int)options.rwrap]; int stex[2], ttex[2], rtex[2]; // Texel coords stex[0] = sint; stex[1] = sint + 1; ttex[0] = tint; ttex[1] = tint + 1; rtex[0] = rint; rtex[1] = rint + 1; // bool svalid[2], tvalid[2], rvalid[2]; // Valid texels? false means black border union { bool bvalid[6]; unsigned long long ivalid; } valid_storage; valid_storage.ivalid = 0; OIIO_DASSERT(sizeof(valid_storage) == 8); const unsigned long long none_valid = 0; const unsigned long long all_valid = littleendian() ? 0x010101010101LL : 0x01010101010100LL; bool* svalid = valid_storage.bvalid; bool* tvalid = valid_storage.bvalid + 2; bool* rvalid = valid_storage.bvalid + 4; svalid[0] = swrap_func(stex[0], spec.x, spec.width); svalid[1] = swrap_func(stex[1], spec.x, spec.width); tvalid[0] = twrap_func(ttex[0], spec.y, spec.height); tvalid[1] = twrap_func(ttex[1], spec.y, spec.height); rvalid[0] = rwrap_func(rtex[0], spec.z, spec.depth); rvalid[1] = rwrap_func(rtex[1], spec.z, spec.depth); // Account for crop windows if (!levelinfo.full_pixel_range) { svalid[0] &= (stex[0] >= spec.x && stex[0] < spec.x + spec.width); svalid[1] &= (stex[1] >= spec.x && stex[1] < spec.x + spec.width); tvalid[0] &= (ttex[0] >= spec.y && ttex[0] < spec.y + spec.height); tvalid[1] &= (ttex[1] >= spec.y && ttex[1] < spec.y + spec.height); rvalid[0] &= (rtex[0] >= spec.z && rtex[0] < spec.z + spec.depth); rvalid[1] &= (rtex[1] >= spec.z && rtex[1] < spec.z + spec.depth); } // if (! (svalid[0] | svalid[1] | tvalid[0] | tvalid[1] | rvalid[0] | rvalid[1])) if (valid_storage.ivalid == none_valid) return true; // All texels we need were out of range and using 'black' wrap int tilewidthmask = spec.tile_width - 1; // e.g. 63 int tileheightmask = spec.tile_height - 1; int tiledepthmask = spec.tile_depth - 1; const unsigned char* texel[2][2][2]; TileRef savetile[2][2][2]; static float black[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; int tile_s = (stex[0] - spec.x) % spec.tile_width; int tile_t = (ttex[0] - spec.y) % spec.tile_height; int tile_r = (rtex[0] - spec.z) % spec.tile_depth; bool s_onetile = (tile_s != tilewidthmask) & (stex[0] + 1 == stex[1]); bool t_onetile = (tile_t != tileheightmask) & (ttex[0] + 1 == ttex[1]); bool r_onetile = (tile_r != tiledepthmask) & (rtex[0] + 1 == rtex[1]); bool onetile = (s_onetile & t_onetile & r_onetile); size_t channelsize = texturefile.channelsize(options.subimage); size_t pixelsize = texturefile.pixelsize(options.subimage); int tile_chbegin = 0, tile_chend = spec.nchannels; if (spec.nchannels > m_max_tile_channels) { // For files with many channels, narrow the range we cache tile_chbegin = options.firstchannel; tile_chend = options.firstchannel + actualchannels; } TileID id(texturefile, options.subimage, miplevel, 0, 0, 0, tile_chbegin, tile_chend); int startchan_in_tile = options.firstchannel - id.chbegin(); if (onetile && valid_storage.ivalid == all_valid) { // Shortcut if all the texels we need are on the same tile id.xyz(stex[0] - tile_s, ttex[0] - tile_t, rtex[0] - tile_r); bool ok = find_tile(id, thread_info, true); if (!ok) error("{}", m_imagecache->geterror()); TileRef& tile(thread_info->tile); if (!tile->valid()) return false; size_t tilepel = (tile_r * spec.tile_height + tile_t) * spec.tile_width + tile_s; size_t offset = (spec.nchannels * tilepel + startchan_in_tile) * channelsize; OIIO_DASSERT((size_t)offset < spec.tile_width * spec.tile_height * spec.tile_depth * pixelsize); const unsigned char* b = tile->bytedata() + offset; texel[0][0][0] = b; texel[0][0][1] = b + pixelsize; texel[0][1][0] = b + pixelsize * spec.tile_width; texel[0][1][1] = b + pixelsize * spec.tile_width + pixelsize; b += pixelsize * spec.tile_width * spec.tile_height; texel[1][0][0] = b; texel[1][0][1] = b + pixelsize; texel[1][1][0] = b + pixelsize * spec.tile_width; texel[1][1][1] = b + pixelsize * spec.tile_width + pixelsize; } else { bool firstsample = true; for (int k = 0; k < 2; ++k) { for (int j = 0; j < 2; ++j) { for (int i = 0; i < 2; ++i) { if (!(svalid[i] && tvalid[j] && rvalid[k])) { texel[k][j][i] = (unsigned char*)black; continue; } tile_s = (stex[i] - spec.x) % spec.tile_width; tile_t = (ttex[j] - spec.y) % spec.tile_height; tile_r = (rtex[k] - spec.z) % spec.tile_depth; id.xyz(stex[i] - tile_s, ttex[j] - tile_t, rtex[k] - tile_r); bool ok = find_tile(id, thread_info, firstsample); if (!ok) error("{}", m_imagecache->geterror()); firstsample = false; TileRef& tile(thread_info->tile); if (!tile->valid()) return false; savetile[k][j][i] = tile; size_t tilepel = (tile_r * spec.tile_height + tile_t) * spec.tile_width + tile_s; size_t offset = (spec.nchannels * tilepel + startchan_in_tile) * channelsize; #ifndef NDEBUG if ((size_t)offset >= spec.tile_width * spec.tile_height * spec.tile_depth * pixelsize) std::cerr << "offset=" << offset << ", whd " << spec.tile_width << ' ' << spec.tile_height << ' ' << spec.tile_depth << " pixsize " << pixelsize << "\n"; #endif OIIO_DASSERT((size_t)offset < spec.tile_width * spec.tile_height * spec.tile_depth * pixelsize); texel[k][j][i] = tile->bytedata() + offset; OIIO_DASSERT(tile->id() == id); } } } } // FIXME -- optimize the above loop by unrolling // clang-format off if (pixeltype == TypeDesc::UINT8) { for (int c = 0; c < actualchannels; ++c) accum[c] += weight * trilerp(uchar2float(texel[0][0][0][c]), uchar2float(texel[0][0][1][c]), uchar2float(texel[0][1][0][c]), uchar2float(texel[0][1][1][c]), uchar2float(texel[1][0][0][c]), uchar2float(texel[1][0][1][c]), uchar2float(texel[1][1][0][c]), uchar2float(texel[1][1][1][c]), sfrac, tfrac, rfrac); if (daccumds) { float scalex = weight * spec.full_width; float scaley = weight * spec.full_height; float scalez = weight * spec.full_depth; for (int c = 0; c < actualchannels; ++c) { daccumds[c] += scalex * bilerp(uchar2float(texel[0][0][1][c]) - uchar2float(texel[0][0][0][c]), uchar2float(texel[0][1][1][c]) - uchar2float(texel[0][1][0][c]), uchar2float(texel[1][0][1][c]) - uchar2float(texel[1][0][0][c]), uchar2float(texel[1][1][1][c]) - uchar2float(texel[1][1][0][c]), tfrac, rfrac); daccumdt[c] += scaley * bilerp(uchar2float(texel[0][1][0][c]) - uchar2float(texel[0][0][0][c]), uchar2float(texel[0][1][1][c]) - uchar2float(texel[0][0][1][c]), uchar2float(texel[1][1][0][c]) - uchar2float(texel[1][0][0][c]), uchar2float(texel[1][1][1][c]) - uchar2float(texel[1][0][1][c]), sfrac, rfrac); daccumdr[c] += scalez * bilerp(uchar2float(texel[0][1][0][c]) - uchar2float(texel[1][1][0][c]), uchar2float(texel[0][1][1][c]) - uchar2float(texel[1][1][1][c]), uchar2float(texel[0][0][1][c]) - uchar2float(texel[1][0][0][c]), uchar2float(texel[0][1][1][c]) - uchar2float(texel[1][1][1][c]), sfrac, tfrac); } } } else if (pixeltype == TypeDesc::UINT16) { for (int c = 0; c < actualchannels; ++c) accum[c] += weight * trilerp(ushort2float(((const uint16_t*)texel[0][0][0])[c]), ushort2float(((const uint16_t*)texel[0][0][1])[c]), ushort2float(((const uint16_t*)texel[0][1][0])[c]), ushort2float(((const uint16_t*)texel[0][1][1])[c]), ushort2float(((const uint16_t*)texel[1][0][0])[c]), ushort2float(((const uint16_t*)texel[1][0][1])[c]), ushort2float(((const uint16_t*)texel[1][1][0])[c]), ushort2float(((const uint16_t*)texel[1][1][1])[c]), sfrac, tfrac, rfrac); if (daccumds) { float scalex = weight * spec.full_width; float scaley = weight * spec.full_height; float scalez = weight * spec.full_depth; for (int c = 0; c < actualchannels; ++c) { daccumds[c] += scalex * bilerp( ushort2float(((const uint16_t*)texel[0][0][1])[c]) - ushort2float( ((const uint16_t*)texel[0][0][0])[c]), ushort2float(((const uint16_t*)texel[0][1][1])[c]) - ushort2float( ((const uint16_t*)texel[0][1][0])[c]), ushort2float(((const uint16_t*)texel[1][0][1])[c]) - ushort2float( ((const uint16_t*)texel[1][0][0])[c]), ushort2float(((const uint16_t*)texel[1][1][1])[c]) - ushort2float( ((const uint16_t*)texel[1][1][0])[c]), tfrac, rfrac); daccumdt[c] += scaley * bilerp( ushort2float(((const uint16_t*)texel[0][1][0])[c]) - ushort2float( ((const uint16_t*)texel[0][0][0])[c]), ushort2float(((const uint16_t*)texel[0][1][1])[c]) - ushort2float( ((const uint16_t*)texel[0][0][1])[c]), ushort2float(((const uint16_t*)texel[1][1][0])[c]) - ushort2float( ((const uint16_t*)texel[1][0][0])[c]), ushort2float(((const uint16_t*)texel[1][1][1])[c]) - ushort2float( ((const uint16_t*)texel[1][0][1])[c]), sfrac, rfrac); daccumdr[c] += scalez * bilerp( ushort2float(((const uint16_t*)texel[0][1][0])[c]) - ushort2float( ((const uint16_t*)texel[1][1][0])[c]), ushort2float(((const uint16_t*)texel[0][1][1])[c]) - ushort2float( ((const uint16_t*)texel[1][1][1])[c]), ushort2float(((const uint16_t*)texel[0][0][1])[c]) - ushort2float( ((const uint16_t*)texel[1][0][0])[c]), ushort2float(((const uint16_t*)texel[0][1][1])[c]) - ushort2float( ((const uint16_t*)texel[1][1][1])[c]), sfrac, tfrac); } } } else if (pixeltype == TypeDesc::HALF) { for (int c = 0; c < actualchannels; ++c) accum[c] += weight * trilerp(half2float(((const half*)texel[0][0][0])[c]), half2float(((const half*)texel[0][0][1])[c]), half2float(((const half*)texel[0][1][0])[c]), half2float(((const half*)texel[0][1][1])[c]), half2float(((const half*)texel[1][0][0])[c]), half2float(((const half*)texel[1][0][1])[c]), half2float(((const half*)texel[1][1][0])[c]), half2float(((const half*)texel[1][1][1])[c]), sfrac, tfrac, rfrac); if (daccumds) { float scalex = weight * spec.full_width; float scaley = weight * spec.full_height; float scalez = weight * spec.full_depth; for (int c = 0; c < actualchannels; ++c) { daccumds[c] += scalex * bilerp( half2float(((const half*)texel[0][0][1])[c]) - half2float(((const half*)texel[0][0][0])[c]), half2float(((const half*)texel[0][1][1])[c]) - half2float(((const half*)texel[0][1][0])[c]), half2float(((const half*)texel[1][0][1])[c]) - half2float(((const half*)texel[1][0][0])[c]), half2float(((const half*)texel[1][1][1])[c]) - half2float(((const half*)texel[1][1][0])[c]), tfrac, rfrac); daccumdt[c] += scaley * bilerp( half2float(((const half*)texel[0][1][0])[c]) - half2float(((const half*)texel[0][0][0])[c]), half2float(((const half*)texel[0][1][1])[c]) - half2float(((const half*)texel[0][0][1])[c]), half2float(((const half*)texel[1][1][0])[c]) - half2float(((const half*)texel[1][0][0])[c]), half2float(((const half*)texel[1][1][1])[c]) - half2float(((const half*)texel[1][0][1])[c]), sfrac, rfrac); daccumdr[c] += scalez * bilerp( half2float(((const half*)texel[0][1][0])[c]) - half2float(((const half*)texel[1][1][0])[c]), half2float(((const half*)texel[0][1][1])[c]) - half2float(((const half*)texel[1][1][1])[c]), half2float(((const half*)texel[0][0][1])[c]) - half2float(((const half*)texel[1][0][0])[c]), half2float(((const half*)texel[0][1][1])[c]) - half2float(((const half*)texel[1][1][1])[c]), sfrac, tfrac); } } } else { // General case for float tiles trilerp_mad((const float*)texel[0][0][0], (const float*)texel[0][0][1], (const float*)texel[0][1][0], (const float*)texel[0][1][1], (const float*)texel[1][0][0], (const float*)texel[1][0][1], (const float*)texel[1][1][0], (const float*)texel[1][1][1], sfrac, tfrac, rfrac, weight, actualchannels, accum); if (daccumds) { float scalex = weight * spec.full_width; float scaley = weight * spec.full_height; float scalez = weight * spec.full_depth; for (int c = 0; c < actualchannels; ++c) { daccumds[c] += scalex * bilerp(((const float*)texel[0][0][1])[c] - ((const float*)texel[0][0][0])[c], ((const float*)texel[0][1][1])[c] - ((const float*)texel[0][1][0])[c], ((const float*)texel[1][0][1])[c] - ((const float*)texel[1][0][0])[c], ((const float*)texel[1][1][1])[c] - ((const float*)texel[1][1][0])[c], tfrac, rfrac); daccumdt[c] += scaley * bilerp(((const float*)texel[0][1][0])[c] - ((const float*)texel[0][0][0])[c], ((const float*)texel[0][1][1])[c] - ((const float*)texel[0][0][1])[c], ((const float*)texel[1][1][0])[c] - ((const float*)texel[1][0][0])[c], ((const float*)texel[1][1][1])[c] - ((const float*)texel[1][0][1])[c], sfrac, rfrac); daccumdr[c] += scalez * bilerp(((const float*)texel[0][1][0])[c] - ((const float*)texel[1][1][0])[c], ((const float*)texel[0][1][1])[c] - ((const float*)texel[1][1][1])[c], ((const float*)texel[0][0][1])[c] - ((const float*)texel[1][0][0])[c], ((const float*)texel[0][1][1])[c] - ((const float*)texel[1][1][1])[c], sfrac, tfrac); } } } // clang-format on // Add appropriate amount of "fill" color to extra channels in // non-"black"-wrapped regions. if (nchannels_result > actualchannels && options.fill) { float f = trilerp(1.0f * (rvalid[0] * tvalid[0] * svalid[0]), 1.0f * (rvalid[0] * tvalid[0] * svalid[1]), 1.0f * (rvalid[0] * tvalid[1] * svalid[0]), 1.0f * (rvalid[0] * tvalid[1] * svalid[1]), 1.0f * (rvalid[1] * tvalid[0] * svalid[0]), 1.0f * (rvalid[1] * tvalid[0] * svalid[1]), 1.0f * (rvalid[1] * tvalid[1] * svalid[0]), 1.0f * (rvalid[1] * tvalid[1] * svalid[1]), sfrac, tfrac, rfrac); f *= weight * options.fill; for (int c = actualchannels; c < nchannels_result; ++c) accum[c] += f; } return true; } bool TextureSystemImpl::texture3d(TextureHandle* texture_handle, Perthread* thread_info, TextureOptBatch& options, Tex::RunMask mask, const float* P, const float* dPdx, const float* dPdy, const float* dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { // (FIXME) CHEAT! Texture points individually TextureOpt opt; opt.firstchannel = options.firstchannel; opt.subimage = options.subimage; opt.subimagename = options.subimagename; opt.swrap = (TextureOpt::Wrap)options.swrap; opt.twrap = (TextureOpt::Wrap)options.twrap; opt.mipmode = (TextureOpt::MipMode)options.mipmode; opt.interpmode = (TextureOpt::InterpMode)options.interpmode; opt.anisotropic = options.anisotropic; opt.conservative_filter = options.conservative_filter; opt.fill = options.fill; opt.missingcolor = options.missingcolor; opt.rwrap = (TextureOpt::Wrap)options.rwrap; bool ok = true; Tex::RunMask bit = 1; for (int i = 0; i < Tex::BatchWidth; ++i, bit <<= 1) { float r[4], drds[4], drdt[4], drdr[4]; // temp result if (mask & bit) { opt.sblur = options.sblur[i]; opt.tblur = options.tblur[i]; opt.rblur = options.rblur[i]; opt.swidth = options.swidth[i]; opt.twidth = options.twidth[i]; opt.rwidth = options.rwidth[i]; Imath::V3f P_(P[i], P[i + Tex::BatchWidth], P[i + 2 * Tex::BatchWidth]); Imath::V3f dPdx_(dPdx[i], dPdx[i + Tex::BatchWidth], dPdx[i + 2 * Tex::BatchWidth]); Imath::V3f dPdy_(dPdy[i], dPdy[i + Tex::BatchWidth], dPdy[i + 2 * Tex::BatchWidth]); Imath::V3f dPdz_(dPdz[i], dPdz[i + Tex::BatchWidth], dPdz[i + 2 * Tex::BatchWidth]); if (dresultds) { ok &= texture3d(texture_handle, thread_info, opt, P_, dPdx_, dPdy_, dPdz_, nchannels, r, drds, drdt, drdr); for (int c = 0; c < nchannels; ++c) { result[c * Tex::BatchWidth + i] = r[c]; dresultds[c * Tex::BatchWidth + i] = drds[c]; dresultdt[c * Tex::BatchWidth + i] = drdt[c]; dresultdr[c * Tex::BatchWidth + i] = drdr[c]; } } else { ok &= texture3d(texture_handle, thread_info, opt, P_, dPdx_, dPdy_, dPdz_, nchannels, r); for (int c = 0; c < nchannels; ++c) { result[c * Tex::BatchWidth + i] = r[c]; } } } } return ok; } bool TextureSystemImpl::texture3d(ustring filename, TextureOptBatch& options, Tex::RunMask mask, const float* P, const float* dPdx, const float* dPdy, const float* dPdz, int nchannels, float* result, float* dresultds, float* dresultdt, float* dresultdr) { Perthread* thread_info = get_perthread_info(); TextureHandle* texture_handle = get_texture_handle(filename, thread_info); return texture3d(texture_handle, thread_info, options, mask, P, dPdx, dPdy, dPdz, nchannels, result, dresultds, dresultdt, dresultdr); } } // end namespace pvt OIIO_NAMESPACE_END