bool TextureSystemImpl::accum3d_sample_bilinear (const Imath::V3f &P, int miplevel, TextureFile &texturefile, PerThreadInfo *thread_info, TextureOpt &options, 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)); // 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 DASSERT (options.swrap_func != NULL && options.twrap_func != NULL && options.rwrap_func != NULL); 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; DASSERT (sizeof(valid_storage) >= 6*sizeof(bool)); const unsigned long long none_valid = 0; const unsigned long long all_valid = 0x010101010101LL; DASSERT (__LITTLE_ENDIAN__ && "this trick won't work with big endian"); bool *svalid = valid_storage.bvalid; bool *tvalid = valid_storage.bvalid + 2; bool *rvalid = valid_storage.bvalid + 4; svalid[0] = options.swrap_func (stex[0], spec.x, spec.width); svalid[1] = options.swrap_func (stex[1], spec.x, spec.width); tvalid[0] = options.twrap_func (ttex[0], spec.y, spec.height); tvalid[1] = options.twrap_func (ttex[1], spec.y, spec.height); rvalid[0] = options.rwrap_func (rtex[0], spec.z, spec.depth); rvalid[1] = options.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(); size_t pixelsize = texturefile.pixelsize(); if (onetile && valid_storage.ivalid == all_valid) { // Shortcut if all the texels we need are on the same tile TileID id (texturefile, options.subimage, miplevel, stex[0] - tile_s, ttex[0] - tile_t, rtex[0] - tile_r); bool ok = find_tile (id, thread_info); if (! ok) error ("%s", m_imagecache->geterror().c_str()); 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 + options.firstchannel) * channelsize; 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 { 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; TileID id (texturefile, options.subimage, miplevel, stex[i] - tile_s, ttex[j] - tile_t, rtex[k] - tile_r); bool ok = find_tile (id, thread_info); if (! ok) error ("%s", m_imagecache->geterror().c_str()); 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 + options.firstchannel) * channelsize; #if DEBUG 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 DASSERT ((size_t)offset < spec.tile_width*spec.tile_height*spec.tile_depth*pixelsize); texel[k][j][i] = tile->bytedata() + offset; DASSERT (tile->id() == id); } } } } // FIXME -- optimize the above loop by unrolling if (channelsize == 1) { // special case for 8-bit tiles int c; for (c = 0; c < options.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 (c = 0; c < options.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 { // 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, options.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 < options.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 ); } } } return true; }
bool TextureSystemImpl::environment (TextureHandle *texture_handle_, Perthread *thread_info_, TextureOpt &options, const Imath::V3f &_R, const Imath::V3f &_dRdx, const Imath::V3f &_dRdy, float *result) { PerThreadInfo *thread_info = (PerThreadInfo *)thread_info_; TextureFile *texturefile = (TextureFile *)texture_handle_; ImageCacheStatistics &stats (thread_info->m_stats); ++stats.environment_batches; ++stats.environment_queries; if (! texturefile || texturefile->broken()) return missing_texture (options, result); const ImageSpec &spec (texturefile->spec(options.subimage, 0)); options.swrap_func = texturefile->m_sample_border ? wrap_periodic_sharedborder : wrap_periodic; options.twrap_func = wrap_clamp; options.envlayout = LayoutLatLong; int actualchannels = Imath::clamp (spec.nchannels - options.firstchannel, 0, options.nchannels); options.actualchannels = actualchannels; // Initialize results to 0. We'll add from here on as we sample. float* dresultds = options.dresultds; float* dresultdt = options.dresultdt; for (int c = 0; c < options.actualchannels; ++c) { result[c] = 0; if (dresultds) dresultds[c] = 0; if (dresultdt) dresultdt[c] = 0; } // If the user only provided us with one pointer, clear both 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)) dresultds = dresultdt = NULL; // Calculate unit-length vectors in the direction of R, R+dRdx, R+dRdy. // These define the ellipse we're filtering over. Imath::V3f R = _R; R.normalize(); // center Imath::V3f Rx = _R + _dRdx; Rx.normalize(); // x axis of the ellipse Imath::V3f Ry = _R + _dRdy; Ry.normalize(); // y axis of the ellipse // angles formed by the ellipse axes. float xfilt_noblur = std::max (safe_acosf(R.dot(Rx)), 1e-8f); float yfilt_noblur = std::max (safe_acosf(R.dot(Ry)), 1e-8f); int naturalres = int((float)M_PI / std::min (xfilt_noblur, yfilt_noblur)); // FIXME -- figure naturalres sepearately for s and t // FIXME -- ick, why is it x and y at all, shouldn't it be s and t? // N.B. naturalres formulated for latlong // Account for width and blur float xfilt = xfilt_noblur * options.swidth + options.sblur; float yfilt = yfilt_noblur * options.twidth + options.tblur; // Figure out major versus minor, and aspect ratio Imath::V3f Rmajor; // major axis float majorlength, minorlength; bool x_is_majoraxis = (xfilt >= yfilt); if (x_is_majoraxis) { Rmajor = Rx; majorlength = xfilt; minorlength = yfilt; } else { Rmajor = Ry; majorlength = yfilt; minorlength = xfilt; } accum_prototype accumer; long long *probecount; switch (options.interpmode) { case TextureOpt::InterpClosest : accumer = &TextureSystemImpl::accum_sample_closest; probecount = &stats.closest_interps; break; case TextureOpt::InterpBilinear : accumer = &TextureSystemImpl::accum_sample_bilinear; probecount = &stats.bilinear_interps; break; case TextureOpt::InterpBicubic : accumer = &TextureSystemImpl::accum_sample_bicubic; probecount = &stats.cubic_interps; break; default: accumer = NULL; probecount = NULL; break; } TextureOpt::MipMode mipmode = options.mipmode; bool aniso = (mipmode == TextureOpt::MipModeDefault || mipmode == TextureOpt::MipModeAniso); float aspect, trueaspect, filtwidth; int nsamples; float invsamples; if (aniso) { aspect = anisotropic_aspect (majorlength, minorlength, options, trueaspect); filtwidth = minorlength; if (trueaspect > stats.max_aniso) stats.max_aniso = trueaspect; nsamples = std::max (1, (int) ceilf (aspect - 0.25f)); invsamples = 1.0f / nsamples; } else { filtwidth = options.conservative_filter ? majorlength : minorlength; nsamples = 1; invsamples = 1.0f; } ImageCacheFile::SubimageInfo &subinfo (texturefile->subimageinfo(options.subimage)); // FIXME -- assuming latlong bool ok = true; float pos = -0.5f + 0.5f * invsamples; for (int sample = 0; sample < nsamples; ++sample, pos += invsamples) { Imath::V3f Rsamp = R + pos*Rmajor; float s, t; vector_to_latlong (Rsamp, texturefile->m_y_up, s, t); // Determine the MIP-map level(s) we need: we will blend // data(miplevel[0]) * (1-levelblend) + data(miplevel[1]) * levelblend int miplevel[2] = { -1, -1 }; float levelblend = 0; int nmiplevels = (int)subinfo.levels.size(); for (int m = 0; m < nmiplevels; ++m) { // Compute the filter size in raster space at this MIP level. // Filters are in radians, and the vertical resolution of a // latlong map is PI radians. So to compute the raster size of // our filter width... float filtwidth_ras = subinfo.spec(m).full_height * filtwidth * M_1_PI; // Once the filter width is smaller than one texel at this level, // we've gone too far, so we know that we want to interpolate the // previous level and the current level. Note that filtwidth_ras // is expected to be >= 0.5, or would have stopped one level ago. if (filtwidth_ras <= 1) { miplevel[0] = m-1; miplevel[1] = m; levelblend = Imath::clamp (2.0f - 1.0f/filtwidth_ras, 0.0f, 1.0f); break; } } if (miplevel[1] < 0) { // We'd like to blur even more, but make due with the coarsest // MIP level. miplevel[0] = nmiplevels - 1; miplevel[1] = miplevel[0]; levelblend = 0; } else if (miplevel[0] < 0) { // We wish we had even more resolution than the finest MIP level, // but tough for us. miplevel[0] = 0; miplevel[1] = 0; levelblend = 0; } if (options.mipmode == TextureOpt::MipModeOneLevel) { // Force use of just one mipmap level miplevel[1] = miplevel[0]; levelblend = 0; } else if (mipmode == TextureOpt::MipModeNoMIP) { // Just sample from lowest level miplevel[0] = 0; miplevel[1] = 0; levelblend = 0; } float levelweight[2] = { 1.0f - levelblend, levelblend }; int npointson = 0; for (int level = 0; level < 2; ++level) { if (! levelweight[level]) continue; ++npointson; int lev = miplevel[level]; if (options.interpmode == TextureOpt::InterpSmartBicubic) { if (lev == 0 || (texturefile->spec(options.subimage,lev).full_height < naturalres/2)) { accumer = &TextureSystemImpl::accum_sample_bicubic; ++stats.cubic_interps; } else { accumer = &TextureSystemImpl::accum_sample_bilinear; ++stats.bilinear_interps; } } else { *probecount += 1; } ok &= (this->*accumer) (s, t, miplevel[level], *texturefile, thread_info, options, levelweight[level]*invsamples, result, dresultds, dresultdt); } } stats.aniso_probes += nsamples; ++stats.aniso_queries; if (actualchannels < options.nchannels) fill_channels (spec, options, result); return ok; }
bool TextureSystemImpl::accum3d_sample_closest (const Imath::V3f &P, int miplevel, TextureFile &texturefile, PerThreadInfo *thread_info, TextureOpt &options, 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)); // 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 coordintes (void) floorfrac (s, &stex); // don't need fractional result (void) floorfrac (t, &ttex); (void) floorfrac (r, &rtex); // Wrap DASSERT (options.swrap_func != NULL && options.twrap_func != NULL && options.rwrap_func != NULL); bool svalid, tvalid, rvalid; // Valid texels? false means black border svalid = options.swrap_func (stex, spec.x, spec.width); tvalid = options.twrap_func (ttex, spec.y, spec.height); rvalid = options.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_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); bool ok = find_tile (id, thread_info); if (! ok) error ("%s", m_imagecache->geterror().c_str()); TileRef &tile (thread_info->tile); if (! tile || ! ok) return false; size_t channelsize = texturefile.channelsize(); int tilepel = (tile_r * spec.tile_height + tile_t) * spec.tile_width + tile_s; int offset = spec.nchannels * tilepel + options.firstchannel; DASSERT ((size_t)offset < spec.nchannels*spec.tile_pixels()); if (channelsize == 1) { // special case for 8-bit tiles const unsigned char *texel = tile->bytedata() + offset; for (int c = 0; c < options.actualchannels; ++c) accum[c] += weight * uchar2float(texel[c]); } else { // General case for float tiles const float *texel = tile->data() + offset; for (int c = 0; c < options.actualchannels; ++c) accum[c] += weight * texel[c]; } return true; }