void PtexReader::getData(int faceid, void* buffer, int stride, Res res) { if (!_ok) return; // note - all locking is handled in called getData methods int resu = res.u(), resv = res.v(); int rowlen = _pixelsize * resu; if (stride == 0) stride = rowlen; PtexPtr<PtexFaceData> d ( getData(faceid, res) ); if (!d) return; if (d->isConstant()) { // fill dest buffer with pixel value PtexUtils::fill(d->getData(), buffer, stride, resu, resv, _pixelsize); } else if (d->isTiled()) { // loop over tiles Res tileres = d->tileRes(); int ntilesu = res.ntilesu(tileres); int ntilesv = res.ntilesv(tileres); int tileures = tileres.u(); int tilevres = tileres.v(); int tilerowlen = _pixelsize * tileures; int tile = 0; char* dsttilerow = (char*) buffer; for (int i = 0; i < ntilesv; i++) { char* dsttile = dsttilerow; for (int j = 0; j < ntilesu; j++) { PtexPtr<PtexFaceData> t ( d->getTile(tile++) ); if (!t) { i = ntilesv; break; } if (t->isConstant()) PtexUtils::fill(t->getData(), dsttile, stride, tileures, tilevres, _pixelsize); else PtexUtils::copy(t->getData(), tilerowlen, dsttile, stride, tilevres, tilerowlen); dsttile += tilerowlen; } dsttilerow += stride * tilevres; } } else { PtexUtils::copy(d->getData(), rowlen, buffer, stride, resv, rowlen); } }
void PtexWriterBase::writeFaceBlock(FILE* fp, const void* data, int stride, Res res, FaceDataHeader& fdh) { // write a single face data block // copy to temp buffer, and deinterleave int ures = res.u(), vres = res.v(); int blockSize = ures*vres*_pixelSize; bool useMalloc = blockSize > AllocaMax; char* buff = useMalloc ? (char*) malloc(blockSize) : (char*)alloca(blockSize); PtexUtils::deinterleave(data, stride, ures, vres, buff, ures*DataSize(_header.datatype), _header.datatype, _header.nchannels); // difference if needed bool diff = (_header.datatype == dt_uint8 || _header.datatype == dt_uint16); if (diff) PtexUtils::encodeDifference(buff, blockSize, _header.datatype); // compress and stream data to file, and record size in header int zippedsize = writeZipBlock(fp, buff, blockSize); // record compressed size and encoding in data header fdh.set(zippedsize, diff ? enc_diffzipped : enc_zipped); if (useMalloc) free(buff); }
void PtexReader::PackedFace::reduce(FaceData*& face, PtexReader* r, Res newres, PtexUtils::ReduceFn reducefn) { // get reduce lock and make sure we still need to reduce AutoMutex rlocker(r->reducelock); if (face) { // another thread must have generated it while we were waiting AutoLockCache clocker(_cache->cachelock); // make sure it's still there now that we have the lock if (face) { face->ref(); return; } } // allocate a new face and reduce image DataType dt = r->datatype(); int nchan = r->nchannels(); PackedFace* pf = new PackedFace((void**)&face, _cache, newres, _pixelsize, _pixelsize * newres.size()); // reduce and copy into new face reducefn(_data, _pixelsize * _res.u(), _res.u(), _res.v(), pf->_data, _pixelsize * newres.u(), dt, nchan); AutoLockCache clocker(_cache->cachelock); face = pf; // clean up unused data _cache->purgeData(); }
void PtexTriangleFilter::buildKernel(PtexTriangleKernel& k, float u, float v, float uw1, float vw1, float uw2, float vw2, float width, float blur, Res faceRes) { const float sqrt3 = 1.7320508075688772f; // compute ellipse coefficients, A*u^2 + B*u*v + C*v^2 == AC - B^2/4 float scaleAC = 0.25f * width*width; float scaleB = -2.0f * scaleAC; float A = (vw1*vw1 + vw2*vw2) * scaleAC; float B = (uw1*vw1 + uw2*vw2) * scaleB; float C = (uw1*uw1 + uw2*uw2) * scaleAC; // convert to cartesian domain float Ac = 0.75f * A; float Bc = float(sqrt3/2) * (B-A); float Cc = 0.25f * A - 0.5f * B + C; // compute min blur for eccentricity clamping const float maxEcc = 15.0f; // max eccentricity const float eccRatio = (maxEcc*maxEcc + 1.0f) / (maxEcc*maxEcc - 1.0f); float X = sqrtf(squared(Ac - Cc) + squared(Bc)); float b_e = 0.5f * (eccRatio * X - (Ac + Cc)); // compute min blur for texel clamping // (ensure that ellipse is no smaller than a texel) float b_t = squared(0.5f / (float)faceRes.u()); // add blur float b_b = 0.25f * blur * blur; float b = PtexUtils::max(b_b, PtexUtils::max(b_e, b_t)); Ac += b; Cc += b; // compute minor radius float m = sqrtf(2.0f*(Ac*Cc - 0.25f*Bc*Bc) / (Ac + Cc + X)); // choose desired resolution int reslog2 = PtexUtils::max(0, PtexUtils::calcResFromWidth(2.0f*m)); // convert back to triangular domain A = float(4/3.0) * Ac; B = float(2/sqrt3) * Bc + A; C = -0.25f * A + 0.5f * B + Cc; // scale by kernel width float scale = PtexTriangleKernelWidth * PtexTriangleKernelWidth; A *= scale; B *= scale; C *= scale; // find u,v,w extents float uw = PtexUtils::min(sqrtf(C), 1.0f); float vw = PtexUtils::min(sqrtf(A), 1.0f); float ww = PtexUtils::min(sqrtf(A-B+C), 1.0f); // init kernel float w = 1.0f - u - v; k.set(Res((int8_t)reslog2, (int8_t)reslog2), u, v, u-uw, v-vw, w-ww, u+uw, v+vw, w+ww, A, B, C); }
void PtexWriterBase::writeFaceData(FILE* fp, const void* data, int stride, Res res, FaceDataHeader& fdh) { // determine whether to break into tiles Res tileres = calcTileRes(res); int ntilesu = res.ntilesu(tileres); int ntilesv = res.ntilesv(tileres); int ntiles = ntilesu * ntilesv; if (ntiles == 1) { // write single block writeFaceBlock(fp, data, stride, res, fdh); } else { // write tiles to tilefp temp file rewind(_tilefp); // alloc tile header std::vector<FaceDataHeader> tileHeader(ntiles); int tileures = tileres.u(); int tilevres = tileres.v(); int tileustride = tileures*_pixelSize; int tilevstride = tilevres*stride; // output tiles FaceDataHeader* tdh = &tileHeader[0]; int datasize = 0; const char* rowp = (const char*) data; const char* rowpend = rowp + ntilesv * tilevstride; for (; rowp != rowpend; rowp += tilevstride) { const char* p = rowp; const char* pend = p + ntilesu * tileustride; for (; p != pend; tdh++, p += tileustride) { // determine if tile is constant if (PtexUtils::isConstant(p, stride, tileures, tilevres, _pixelSize)) writeConstFaceBlock(_tilefp, p, *tdh); else writeFaceBlock(_tilefp, p, stride, tileres, *tdh); datasize += tdh->blocksize(); } } // output compressed tile header uint32_t tileheadersize = writeZipBlock(_tilefp, &tileHeader[0], int(sizeof(FaceDataHeader)*tileHeader.size())); // output tile data pre-header int totalsize = 0; totalsize += writeBlock(fp, &tileres, sizeof(Res)); totalsize += writeBlock(fp, &tileheadersize, sizeof(tileheadersize)); // copy compressed tile header from temp file totalsize += copyBlock(fp, _tilefp, datasize, tileheadersize); // copy tile data from temp file totalsize += copyBlock(fp, _tilefp, 0, datasize); fdh.set(totalsize, enc_tiled); } }
void PtexMainWriter::storeConstValue(int faceid, const void* data, int stride, Res res) { // compute average value and store in _constdata block uint8_t* constdata = &_constdata[faceid*_pixelSize]; PtexUtils::average(data, stride, res.u(), res.v(), constdata, _header.datatype, _header.nchannels); if (_header.hasAlpha()) PtexUtils::divalpha(constdata, 1, _header.datatype, _header.nchannels, _header.alphachan); }
void PtexReader::readFaceData(FilePos pos, FaceDataHeader fdh, Res res, int levelid, FaceData*& face) { // keep new face local until fully initialized FaceData* volatile newface = 0; seek(pos); switch (fdh.encoding()) { case enc_constant: { ConstantFace* pf = new ConstantFace((void**)&face, _cache, _pixelsize); readBlock(pf->data(), _pixelsize); if (levelid==0 && _premultiply && _header.hasAlpha()) PtexUtils::multalpha(pf->data(), 1, _header.datatype, _header.nchannels, _header.alphachan); newface = pf; } break; case enc_tiled: { Res tileres; readBlock(&tileres, sizeof(tileres)); uint32_t tileheadersize; readBlock(&tileheadersize, sizeof(tileheadersize)); TiledFace* tf = new TiledFace((void**)&face, _cache, res, tileres, levelid, this); readZipBlock(&tf->_fdh[0], tileheadersize, FaceDataHeaderSize * tf->_ntiles); computeOffsets(tell(), tf->_ntiles, &tf->_fdh[0], &tf->_offsets[0]); newface = tf; } break; case enc_zipped: case enc_diffzipped: { int uw = res.u(), vw = res.v(); int npixels = uw * vw; int unpackedSize = _pixelsize * npixels; PackedFace* pf = new PackedFace((void**)&face, _cache, res, _pixelsize, unpackedSize); bool useMalloc = unpackedSize > AllocaMax; void* tmp = useMalloc ? malloc(unpackedSize) : alloca(unpackedSize); readZipBlock(tmp, fdh.blocksize(), unpackedSize); if (fdh.encoding() == enc_diffzipped) PtexUtils::decodeDifference(tmp, unpackedSize, _header.datatype); PtexUtils::interleave(tmp, uw * DataSize(_header.datatype), uw, vw, pf->data(), uw * _pixelsize, _header.datatype, _header.nchannels); if (levelid==0 && _premultiply && _header.hasAlpha()) PtexUtils::multalpha(pf->data(), npixels, _header.datatype, _header.nchannels, _header.alphachan); newface = pf; if (useMalloc) free(tmp); } break; } face = newface; }
void PtexWriterBase::writeReduction(FILE* fp, const void* data, int stride, Res res) { // reduce and write to file Ptex::Res newres(res.ulog2-1, res.vlog2-1); int buffsize = newres.size() * _pixelSize; bool useMalloc = buffsize > AllocaMax; char* buff = useMalloc ? (char*) malloc(buffsize) : (char*)alloca(buffsize); int dstride = newres.u() * _pixelSize; _reduceFn(data, stride, res.u(), res.v(), buff, dstride, _header.datatype, _header.nchannels); writeBlock(fp, buff, buffsize); if (useMalloc) free(buff); }
void PtexMainWriter::generateReductions() { // first generate "rfaceids", reduction faceids, // which are faceids reordered by decreasing smaller dimension int nfaces = _header.nfaces; _rfaceids.resize(nfaces); _faceids_r.resize(nfaces); PtexUtils::genRfaceids(&_faceinfo[0], nfaces, &_rfaceids[0], &_faceids_r[0]); // determine how many faces in each level, and resize _levels // traverse in reverse rfaceid order to find number of faces // larger than cutoff size of each level for (int rfaceid = nfaces-1, cutoffres = MinReductionLog2; rfaceid >= 0; rfaceid--) { int faceid = _faceids_r[rfaceid]; FaceInfo& face = _faceinfo[faceid]; Res res = face.res; int min = face.isConstant() ? 1 : PtexUtils::min(res.ulog2, res.vlog2); while (min > cutoffres) { // i == last face for current level int size = rfaceid+1; _levels.push_back(LevelRec()); LevelRec& level = _levels.back(); level.pos.resize(size); level.fdh.resize(size); cutoffres++; } } // generate and cache reductions (including const data) // first, find largest face and allocate tmp buffer int buffsize = 0; for (int i = 0; i < nfaces; i++) buffsize = PtexUtils::max(buffsize, _faceinfo[i].res.size()); buffsize *= _pixelSize; char* buff = (char*) malloc(buffsize); int nlevels = int(_levels.size()); for (int i = 1; i < nlevels; i++) { LevelRec& level = _levels[i]; int nextsize = (i+1 < nlevels)? int(_levels[i+1].fdh.size()) : 0; for (int rfaceid = 0, size = int(level.fdh.size()); rfaceid < size; rfaceid++) { // output current reduction for face (previously generated) int faceid = _faceids_r[rfaceid]; Res res = _faceinfo[faceid].res; res.ulog2 -= i; res.vlog2 -= i; int stride = res.u() * _pixelSize; int blocksize = res.size() * _pixelSize; fseeko(_tmpfp, _rpos[faceid], SEEK_SET); readBlock(_tmpfp, buff, blocksize); fseeko(_tmpfp, 0, SEEK_END); level.pos[rfaceid] = ftello(_tmpfp); writeFaceData(_tmpfp, buff, stride, res, level.fdh[rfaceid]); if (!_ok) return; // write a new reduction if needed for next level if (rfaceid < nextsize) { fseeko(_tmpfp, _rpos[faceid], SEEK_SET); writeReduction(_tmpfp, buff, stride, res); } else { // the last reduction for each face is its constant value storeConstValue(faceid, buff, stride, res); } } } fseeko(_tmpfp, 0, SEEK_END); free(buff); }
void PtexReader::blendFaces(FaceData*& face, int faceid, Res res, bool blendu) { Res pres; // parent res, 1 higher in blend direction int length; // length of blend edge (1xN or Nx1) int e1, e2; // neighboring edge ids if (blendu) { assert(res.ulog2 < 0); // res >= 0 requires reduction, not blending length = (res.vlog2 <= 0 ? 1 : res.v()); e1 = e_bottom; e2 = e_top; pres = Res(res.ulog2+1, res.vlog2); } else { assert(res.vlog2 < 0); length = (res.ulog2 <= 0 ? 1 : res.u()); e1 = e_right; e2 = e_left; pres = Res(res.ulog2, res.vlog2+1); } // get neighbor face ids FaceInfo& f = _faceinfo[faceid]; int nf1 = f.adjfaces[e1], nf2 = f.adjfaces[e2]; // compute rotation of faces relative to current int r1 = (f.adjedge(e1)-e1+2)&3; int r2 = (f.adjedge(e2)-e2+2)&3; // swap u and v res for faces rotated +/- 90 degrees Res pres1 = pres, pres2 = pres; if (r1 & 1) pres1.swapuv(); if (r2 & 1) pres2.swapuv(); // ignore faces that have insufficient res (unlikely, but possible) if (nf1 >= 0 && !(_faceinfo[nf1].res >= pres)) nf1 = -1; if (nf2 >= 0 && !(_faceinfo[nf2].res >= pres)) nf2 = -1; // get parent face data int nf = 1; // number of faces to blend (1 to 3) bool flip[3]; // true if long dimension needs to be flipped PtexFaceData* psrc[3]; // the face data psrc[0] = getData(faceid, pres); flip[0] = 0; // don't flip main face if (nf1 >= 0) { // face must be flipped if rot is 1 or 2 for blendu, or 2 or 3 for blendv // thus, just add the blendu bool val to align the ranges and check bit 1 // also, no need to flip if length is zero flip[nf] = length ? (r1 + blendu) & 1 : 0; psrc[nf++] = getData(nf1, pres1); } if (nf2 >= 0) { flip[nf] = length ? (r2 + blendu) & 1 : 0; psrc[nf++] = getData(nf2, pres2); } // get reduce lock and make sure we still need to reduce AutoMutex rlocker(reducelock); if (face) { // another thread must have generated it while we were waiting AutoLockCache locker(_cache->cachelock); // make sure it's still there now that we have the lock if (face) { face->ref(); // release parent data for (int i = 0; i < nf; i++) psrc[i]->release(); return; } } // allocate a new face data (1 x N or N x 1) DataType dt = datatype(); int nchan = nchannels(); int size = _pixelsize * length; PackedFace* pf = new PackedFace((void**)&face, _cache, res, _pixelsize, size); void* data = pf->getData(); if (nf == 1) { // no neighbors - just copy face memcpy(data, psrc[0]->getData(), size); } else { float weight = 1.0f / nf; memset(data, 0, size); for (int i = 0; i < nf; i++) PtexUtils::blend(psrc[i]->getData(), weight, data, flip[i], length, dt, nchan); } { AutoLockCache clocker(_cache->cachelock); face = pf; // clean up unused data _cache->purgeData(); } // release parent data for (int i = 0; i < nf; i++) psrc[i]->release(); }
void PtexReader::TiledFaceBase::reduce(FaceData*& face, PtexReader* r, Res newres, PtexUtils::ReduceFn reducefn) { // get reduce lock and make sure we still need to reduce AutoMutex rlocker(r->reducelock); if (face) { // another thread must have generated it while we were waiting AutoLockCache clocker(_cache->cachelock); // make sure it's still there now that we have the lock if (face) { face->ref(); return; } } /* Tiled reductions should generally only be anisotropic (just u or v, not both) since isotropic reductions are precomputed and stored on disk. (This function should still work for isotropic reductions though.) In the anisotropic case, the number of tiles should be kept the same along the direction not being reduced in order to preserve the laziness of the file access. In contrast, if reductions were not tiled, then any reduction would read all the tiles and defeat the purpose of tiling. */ // keep new face local until fully initialized FaceData* volatile newface = 0; // don't tile if either dimension is 1 (rare, would complicate blendFaces too much) // also, don't tile triangle reductions (too complicated) Res newtileres; bool isTriangle = r->_header.meshtype == mt_triangle; if (newres.ulog2 == 1 || newres.vlog2 == 1 || isTriangle) { newtileres = newres; } else { // propagate the tile res to the reduction newtileres = _tileres; // but make sure tile isn't larger than the new face! if (newtileres.ulog2 > newres.ulog2) newtileres.ulog2 = newres.ulog2; if (newtileres.vlog2 > newres.vlog2) newtileres.vlog2 = newres.vlog2; } // determine how many tiles we will have on the reduction int newntiles = newres.ntiles(newtileres); if (newntiles == 1) { // no need to keep tiling, reduce tiles into a single face // first, get all tiles and check if they are constant (with the same value) PtexFaceData** tiles = (PtexFaceData**) alloca(_ntiles * sizeof(PtexFaceData*)); bool allConstant = true; for (int i = 0; i < _ntiles; i++) { PtexFaceData* tile = tiles[i] = getTile(i); allConstant = (allConstant && tile->isConstant() && (i == 0 || (0 == memcmp(tiles[0]->getData(), tile->getData(), _pixelsize)))); } if (allConstant) { // allocate a new constant face newface = new ConstantFace((void**)&face, _cache, _pixelsize); memcpy(newface->getData(), tiles[0]->getData(), _pixelsize); } else if (isTriangle) { // reassemble all tiles into temporary contiguous image // (triangle reduction doesn't work on tiles) int tileures = _tileres.u(); int tilevres = _tileres.v(); int sstride = _pixelsize * tileures; int dstride = sstride * _ntilesu; int dstepv = dstride * tilevres - sstride*(_ntilesu-1); char* tmp = (char*) malloc(_ntiles * _tileres.size() * _pixelsize); char* tmpptr = tmp; for (int i = 0; i < _ntiles;) { PtexFaceData* tile = tiles[i]; if (tile->isConstant()) PtexUtils::fill(tile->getData(), tmpptr, dstride, tileures, tilevres, _pixelsize); else PtexUtils::copy(tile->getData(), sstride, tmpptr, dstride, tilevres, sstride); i++; tmpptr += i%_ntilesu ? sstride : dstepv; } // allocate a new packed face newface = new PackedFace((void**)&face, _cache, newres, _pixelsize, _pixelsize * newres.size()); // reduce and copy into new face reducefn(tmp, _pixelsize * _res.u(), _res.u(), _res.v(), newface->getData(), _pixelsize * newres.u(), _dt, _nchan); free(tmp); } else { // allocate a new packed face newface = new PackedFace((void**)&face, _cache, newres, _pixelsize, _pixelsize*newres.size()); int tileures = _tileres.u(); int tilevres = _tileres.v(); int sstride = _pixelsize * tileures; int dstride = _pixelsize * newres.u(); int dstepu = dstride/_ntilesu; int dstepv = dstride*newres.v()/_ntilesv - dstepu*(_ntilesu-1); char* dst = (char*) newface->getData(); for (int i = 0; i < _ntiles;) { PtexFaceData* tile = tiles[i]; if (tile->isConstant()) PtexUtils::fill(tile->getData(), dst, dstride, newres.u()/_ntilesu, newres.v()/_ntilesv, _pixelsize); else reducefn(tile->getData(), sstride, tileures, tilevres, dst, dstride, _dt, _nchan); i++; dst += i%_ntilesu ? dstepu : dstepv; } } // release the tiles for (int i = 0; i < _ntiles; i++) tiles[i]->release(); } else { // otherwise, tile the reduced face newface = new TiledReducedFace((void**)&face, _cache, newres, newtileres, _dt, _nchan, this, reducefn); } AutoLockCache clocker(_cache->cachelock); face = newface; // clean up unused data _cache->purgeData(); }