Пример #1
0
void PtexReader::getPixel(int faceid, int u, int v,
                          float* result, int firstchan, int nchannels,
                          Ptex::Res res)
{
    memset(result, 0, nchannels);

    // clip nchannels against actual number available
    nchannels = PtexUtils::min(nchannels,
                               _header.nchannels-firstchan);
    if (nchannels <= 0) return;

    // get raw pixel data
    PtexPtr<PtexFaceData> data ( getData(faceid, res) );
    if (!data) return;
    void* pixel = alloca(_pixelsize);
    data->getPixel(u, v, pixel);

    // adjust for firstchan offset
    int datasize = DataSize(_header.datatype);
    if (firstchan)
        pixel = (char*) pixel + datasize * firstchan;

    // convert/copy to result as needed
    if (_header.datatype == dt_float)
        memcpy(result, pixel, datasize * nchannels);
    else
        ConvertToFloat(result, pixel, _header.datatype, nchannels);
}
Пример #2
0
int main(int argc, char** argv)
{
    int maxmem = argc >= 2 ? atoi(argv[1]) : 1024*1024;
    PtexCache* c = PtexCache::create(0, maxmem);

    Ptex::String error;
    PtexPtr<PtexTexture> r ( c->get("test.ptx", error) );

    if (!r) {
	std::cerr << error.c_str() << std::endl;
	return 1;
    }

    PtexFilter::Options opts(PtexFilter::f_bicubic, 0, 1.0);
    PtexPtr<PtexFilter> f ( PtexFilter::getFilter(r, opts) );
    float result[4];
    int faceid = 0;
    float u=0, v=0, uw=.125, vw=.125;
    for (v = 0; v <= 1; v += .125) {
	for (u = 0; u <= 1; u += .125) {
	    f->eval(result, 0, 1, faceid, u, v, uw, 0, 0, vw);
	    printf("%8f %8f -> %8f\n", u, v, result[0]);
	}
    }

    return 0;
}
Пример #3
0
void PtexReader::TiledFaceBase::getPixel(int ui, int vi, void* result)
{
    int tileu = ui >> _tileres.ulog2;
    int tilev = vi >> _tileres.vlog2;
    PtexPtr<PtexFaceData> tile ( getTile(tilev * _ntilesu + tileu) );
    tile->getPixel(ui - (tileu<<_tileres.ulog2),
                   vi - (tilev<<_tileres.vlog2), result);
}
Пример #4
0
PTEX_NAMESPACE_BEGIN

void PtexTriangleFilter::eval(float* result, int firstChan, int nChannels,
                              int faceid, float u, float v,
                              float uw1, float vw1, float uw2, float vw2,
                              float width, float blur)
{
    // init
    if (!_tx || nChannels <= 0) return;
    if (faceid < 0 || faceid >= _tx->numFaces()) return;
    _ntxchan = _tx->numChannels();
    _dt = _tx->dataType();
    _firstChanOffset = firstChan*DataSize(_dt);
    _nchan = PtexUtils::min(nChannels, _ntxchan-firstChan);

    // get face info
    const FaceInfo& f = _tx->getFaceInfo(faceid);

    // if neighborhood is constant, just return constant value of face
    if (f.isNeighborhoodConstant()) {
        PtexPtr<PtexFaceData> data ( _tx->getData(faceid, 0) );
        if (data) {
            char* d = (char*) data->getData() + _firstChanOffset;
            Ptex::ConvertToFloat(result, d, _dt, _nchan);
        }
        return;
    }

    // clamp u and v
    u = PtexUtils::clamp(u, 0.0f, 1.0f);
    v = PtexUtils::clamp(v, 0.0f, 1.0f);

    // build kernel
    PtexTriangleKernel k;
    buildKernel(k, u, v, uw1, vw1, uw2, vw2, width, blur, f.res);

    // accumulate the weight as we apply
    _weight = 0;

    // allocate temporary result
    _result = (float*) alloca(sizeof(float)*_nchan);
    memset(_result, 0, sizeof(float)*_nchan);

    // apply to faces
    splitAndApply(k, faceid, f);

    // normalize (both for data type and cumulative kernel weight applied)
    // and output result
    float scale = 1.0f / (_weight * OneValue(_dt));
    for (int i = 0; i < _nchan; i++) result[i] = float(_result[i] * scale);

    // clear temp result
    _result = 0;
}
Пример #5
0
void PtexTriangleFilter::applyIter(PtexTriangleKernelIter& k, PtexFaceData* dh)
{
    if (dh->isConstant()) {
        k.applyConst(_result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan);
        _weight += k.weight;
    }
    else if (dh->isTiled()) {
        Ptex::Res tileres = dh->tileRes();
        PtexTriangleKernelIter kt = k;
        int tileresu = tileres.u();
        int tileresv = tileres.v();
        kt.rowlen = tileresu;
        int ntilesu = k.rowlen / kt.rowlen;
        int wOffsetBase = k.rowlen - tileresu;
        for (int tilev = k.v1 / tileresv, tilevEnd = (k.v2-1) / tileresv; tilev <= tilevEnd; tilev++) {
            int vOffset = tilev * tileresv;
            kt.v = k.v - (float)vOffset;
            kt.v1 = PtexUtils::max(0, k.v1 - vOffset);
            kt.v2 = PtexUtils::min(k.v2 - vOffset, tileresv);
            for (int tileu = k.u1 / tileresu, tileuEnd = (k.u2-1) / tileresu; tileu <= tileuEnd; tileu++) {
                int uOffset = tileu * tileresu;
                int wOffset = wOffsetBase - uOffset - vOffset;
                kt.u = k.u - (float)uOffset;
                kt.u1 = PtexUtils::max(0, k.u1 - uOffset);
                kt.u2 = PtexUtils::min(k.u2 - uOffset, tileresu);
                kt.w1 = k.w1 - wOffset;
                kt.w2 = k.w2 - wOffset;
                PtexPtr<PtexFaceData> th ( dh->getTile(tilev * ntilesu + tileu) );
                if (th) {
                    kt.weight = 0;
                    if (th->isConstant())
                        kt.applyConst(_result, (char*)th->getData()+_firstChanOffset, _dt, _nchan);
                    else
                        kt.apply(_result, (char*)th->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
                    _weight += kt.weight;
                }
            }
        }
    }
    else {
        k.apply(_result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
        _weight += k.weight;
    }
}
Пример #6
0
void PtexMainWriter::finish()
{
    // do nothing if there's no new data to write
    if (!_hasNewData) return;

    // copy missing faces from _reader
    if (_reader) {
	for (int i = 0, nfaces = _header.nfaces; i < nfaces; i++) {
	    if (_faceinfo[i].flags == uint8_t(-1)) {
		// copy face data
                const Ptex::FaceInfo& info = _reader->getFaceInfo(i);
                int size = _pixelSize * info.res.size();
                if (info.isConstant()) {
                    PtexPtr<PtexFaceData> data ( _reader->getData(i) );
                    if (data) {
                        writeConstantFace(i, info, data->getData());
                    }
                } else {
                    void* data = malloc(size);
                    _reader->getData(i, data, 0);
                    writeFace(i, info, data, 0);
                    free(data);
                }
	    }
	}
    }
    else {
	// just flag missing faces as constant (black)
	for (int i = 0, nfaces = _header.nfaces; i < nfaces; i++) {
	    if (_faceinfo[i].flags == uint8_t(-1))
		_faceinfo[i].flags = FaceInfo::flag_constant;
	}
    }

    // write reductions to tmp file
    if (_genmipmaps)
	generateReductions();

    // flag faces w/ constant neighborhoods
    flagConstantNeighorhoods();

    // update header
    _header.nlevels = uint16_t(_levels.size());
    _header.nfaces = uint32_t(_faceinfo.size());

    // create new file
    FILE* newfp = fopen(_newpath.c_str(), "wb+");
    if (!newfp) {
	setError(fileError("Can't write to ptex file: ", _newpath.c_str()));
	return;
    }

    // write blank header (to fill in later)
    writeBlank(newfp, HeaderSize);
    writeBlank(newfp, ExtHeaderSize);

    // write compressed face info block
    _header.faceinfosize = writeZipBlock(newfp, &_faceinfo[0],
					 sizeof(FaceInfo)*_header.nfaces);

    // write compressed const data block
    _header.constdatasize = writeZipBlock(newfp, &_constdata[0], int(_constdata.size()));

    // write blank level info block (to fill in later)
    FilePos levelInfoPos = ftello(newfp);
    writeBlank(newfp, LevelInfoSize * _header.nlevels);

    // write level data blocks (and record level info)
    std::vector<LevelInfo> levelinfo(_header.nlevels);
    for (int li = 0; li < _header.nlevels; li++)
    {
	LevelInfo& info = levelinfo[li];
	LevelRec& level = _levels[li];
	int nfaces = int(level.fdh.size());
	info.nfaces = nfaces;
	// output compressed level data header
	info.levelheadersize = writeZipBlock(newfp, &level.fdh[0],
					     sizeof(FaceDataHeader)*nfaces);
	info.leveldatasize = info.levelheadersize;
	// copy level data from tmp file
	for (int fi = 0; fi < nfaces; fi++)
	    info.leveldatasize += copyBlock(newfp, _tmpfp, level.pos[fi],
					    level.fdh[fi].blocksize());
	_header.leveldatasize += info.leveldatasize;
    }
    rewind(_tmpfp);

    // write meta data (if any)
    if (!_metadata.empty())
	writeMetaData(newfp);

    // update extheader for edit data position
    _extheader.editdatapos = ftello(newfp);

    // rewrite level info block
    fseeko(newfp, levelInfoPos, SEEK_SET);
    _header.levelinfosize = writeBlock(newfp, &levelinfo[0], LevelInfoSize*_header.nlevels);

    // rewrite header
    fseeko(newfp, 0, SEEK_SET);
    writeBlock(newfp, &_header, HeaderSize);
    writeBlock(newfp, &_extheader, ExtHeaderSize);
    fclose(newfp);
}
Пример #7
0
PtexFaceData* PtexReader::getData(int faceid, Res res)
{
    if (!_ok) return 0;
    if (faceid < 0 || size_t(faceid) >= _header.nfaces) return 0;

    FaceInfo& fi = _faceinfo[faceid];
    if ((fi.isConstant() && res >= 0) || res == 0) {
        return new ConstDataPtr(getConstData() + faceid * _pixelsize, _pixelsize);
    }

    // lock cache (can't autolock since we might need to unlock early)
    _cache->cachelock.lock();


    // determine how many reduction levels are needed
    int redu = fi.res.ulog2 - res.ulog2, redv = fi.res.vlog2 - res.vlog2;

    if (redu == 0 && redv == 0) {
        // no reduction - get level zero (full) res face
        Level* level = getLevel(0);
        FaceData* face = getFace(0, level, faceid);
        level->unref();
        _cache->cachelock.unlock();
        return face;
    }

    if (redu == redv && !fi.hasEdits() && res >= 0) {
        // reduction is symmetric and non-negative
        // and face has no edits => access data from reduction level (if present)
        int levelid = redu;
        if (size_t(levelid) < _levels.size()) {
            Level* level = getLevel(levelid);

            // get reduction face id
            int rfaceid = _rfaceids[faceid];

            // get the face data (if present)
            FaceData* face = 0;
            if (size_t(rfaceid) < level->faces.size())
                face = getFace(levelid, level, rfaceid);
            level->unref();
            if (face) {
                _cache->cachelock.unlock();
                return face;
            }
        }
    }

    // dynamic reduction required - look in dynamic reduction cache
    FaceData*& face = _reductions[ReductionKey(faceid, res)];
    if (face) {
        face->ref();
        _cache->cachelock.unlock();
        return face;
    }

    // not found,  generate new reduction
    // unlock cache - getData and reduce will handle their own locking
    _cache->cachelock.unlock();

    if (res.ulog2 < 0 || res.vlog2 < 0) {
        std::cerr << "PtexReader::getData - reductions below 1 pixel not supported" << std::endl;
        return 0;
    }
    if (redu < 0 || redv < 0) {
        std::cerr << "PtexReader::getData - enlargements not supported" << std::endl;
        return 0;
    }
    if (_header.meshtype == mt_triangle)
    {
        if (redu != redv) {
            std::cerr << "PtexReader::getData - anisotropic reductions not supported for triangle mesh" << std::endl;
            return 0;
        }
        PtexPtr<PtexFaceData> psrc ( getData(faceid, Res(res.ulog2+1, res.vlog2+1)) );
        FaceData* src = dynamic_cast<FaceData*>(psrc.get());
        assert(src);
        if (src) src->reduce(face, this, res, PtexUtils::reduceTri);
        return face;
    }

    // determine which direction to blend
    bool blendu;
    if (redu == redv) {
        // for symmetric face blends, alternate u and v blending
        blendu = (res.ulog2 & 1);
    }
    else blendu = redu > redv;

    if (blendu) {
        // get next-higher u-res and reduce in u
        PtexPtr<PtexFaceData> psrc ( getData(faceid, Res(res.ulog2+1, res.vlog2)) );
        FaceData* src = dynamic_cast<FaceData*>(psrc.get());
        assert(src);
        if (src) src->reduce(face, this, res, PtexUtils::reduceu);
    }
    else {
        // get next-higher v-res and reduce in v
        PtexPtr<PtexFaceData> psrc ( getData(faceid, Res(res.ulog2, res.vlog2+1)) );
        FaceData* src = dynamic_cast<FaceData*>(psrc.get());
        assert(src);
        if (src) src->reduce(face, this, res, PtexUtils::reducev);
    }

    return face;
}
Пример #8
0
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);
    }
}
Пример #9
0
int main(int argc, char** argv)
{
    bool showver = 0;
    bool dumpmeta = 0;
    bool dumpfaceinfo = 0;
    bool dumpdata = 0;
    bool dumpalldata = 0;
    bool dumpinternal = 0;
    bool dumptiling = 0;
    bool checkadjacency = 0;
    const char* fname = 0;

    while (--argc) {
        if (**++argv == '-') {
            char* cp = *argv + 1;
            if (!*cp) usage(); // handle bare '-'
            while (*cp) {
                switch (*cp++) {
                case 'v': showver = 1; break;
                case 'm': dumpmeta = 1; break;
                case 'd': dumpdata = 1; break;
                case 'D': dumpdata = 1; dumpalldata = 1; break;
                case 'f': dumpfaceinfo = 1; break;
                case 't': dumptiling = 1; break;
                case 'i': dumpinternal = 1; break;
                case 'c': checkadjacency = 1; break;
                default: usage();
                }
            }
        }
        else if (fname) usage();
        else fname = *argv;
    }

    if (showver) {
#ifdef PTEX_VENDOR
#  define str(s) #s
#  define xstr(s) str(s)
        std::cout << "Ptex v" << PtexLibraryMajorVersion << "." << PtexLibraryMinorVersion << "-" << xstr(PTEX_VENDOR) << std::endl;
#else
        std::cout << "Ptex v" << PtexLibraryMajorVersion << "." << PtexLibraryMinorVersion << std::endl;
#endif
    }

    if (!fname) {
        if (!showver) usage();
        return 0;
    }

    Ptex::String error;
    PtexPtr<PtexTexture> r ( PtexTexture::open(fname, error) );
    if (!r) {
        std::cerr << error.c_str() << std::endl;
        return 1;
    }

    if (checkadjacency) {
        return CheckAdjacency(r);
    }

    std::cout << "meshType: " << Ptex::MeshTypeName(r->meshType()) << std::endl;
    std::cout << "dataType: " << Ptex::DataTypeName(r->dataType()) << std::endl;
    std::cout << "numChannels: " << r->numChannels() << std::endl;
    std::cout << "alphaChannel: ";
    if (r->alphaChannel() == -1) std::cout << "(none)" << std::endl;
    else std::cout << r->alphaChannel() << std::endl;
    std::cout << "uBorderMode: " << Ptex::BorderModeName(r->uBorderMode()) << std::endl;
    std::cout << "vBorderMode: " << Ptex::BorderModeName(r->vBorderMode()) << std::endl;
    std::cout << "edgeFilterMode: " << Ptex::EdgeFilterModeName(r->edgeFilterMode()) << std::endl;
    std::cout << "numFaces: " << r->numFaces() << std::endl;
    std::cout << "hasEdits: " << (r->hasEdits() ? "yes" : "no") << std::endl;
    std::cout << "hasMipMaps: " << (r->hasMipMaps() ? "yes" : "no") << std::endl;

    PtexPtr<PtexMetaData> meta ( r->getMetaData() );
    if (meta) {
        std::cout << "numMetaKeys: " << meta->numKeys() << std::endl;
        if (dumpmeta && meta->numKeys()) DumpMetaData(meta);
    }

    if (dumpfaceinfo || dumpdata || dumptiling) {
        uint64_t texels = 0;
        for (int i = 0; i < r->numFaces(); i++) {
            std::cout << "face " << i << ":";
            const Ptex::FaceInfo& f = r->getFaceInfo(i);
            DumpFaceInfo(f);
            texels += f.res.size();

            if (dumptiling) {
                PtexPtr<PtexFaceData> dh ( r->getData(i, f.res) );
                DumpTiling(dh);
            }
            if (dumpdata) DumpData(r, i, dumpalldata);
        }
        std::cout << "texels: " << texels << std::endl;
    }

    if (dumpinternal) DumpInternal(r);
    return 0;
}
void PtexSeparableFilter::eval(float* result, int firstChan, int nChannels,
			       int faceid, float u, float v, 
			       float uw1, float vw1, float uw2, float vw2,
			       float width, float blur)
{
    // init
    if (!_tx || nChannels <= 0) return;
    if (faceid < 0 || faceid >= _tx->numFaces()) return;
    _ntxchan = _tx->numChannels();
    _dt = _tx->dataType();
    _firstChanOffset = firstChan*DataSize(_dt);
    _nchan = PtexUtils::min(nChannels, _ntxchan-firstChan);

    // get face info
    const FaceInfo& f = _tx->getFaceInfo(faceid);

    // if neighborhood is constant, just return constant value of face
    if (f.isNeighborhoodConstant()) {
	PtexPtr<PtexFaceData> data ( _tx->getData(faceid, 0) );
	if (data) {
	    char* d = (char*) data->getData() + _firstChanOffset;
	    Ptex::ConvertToFloat(result, d, _dt, _nchan);
	}
	return;
    }

    // find filter width as bounding box of vectors w1 and w2
    float uw = fabs(uw1) + fabs(uw2), vw = fabs(vw1) + fabs(vw2);

    // handle border modes
    switch (_uMode) {
    case m_clamp: u = PtexUtils::clamp(u, 0.0f, 1.0f); break;
    case m_periodic: u = u-floor(u); break;
    case m_black: break; // do nothing
    }

    switch (_vMode) {
    case m_clamp: v = PtexUtils::clamp(v, 0.0f, 1.0f); break;
    case m_periodic: v = v-floor(v);
    case m_black: break; // do nothing
    }

    // build kernel
    PtexSeparableKernel k;
    if (f.isSubface()) {
	// for a subface, build the kernel as if it were on a main face and then downres
	uw = uw * width + blur * 2;
	vw = vw * width + blur * 2;
	buildKernel(k, u*.5, v*.5, uw*.5, vw*.5, f.res);
	if (k.res.ulog2 == 0) k.upresU();
	if (k.res.vlog2 == 0) k.upresV();
	k.res.ulog2--; k.res.vlog2--;
    }
    else {
	uw = uw * width + blur;
	vw = vw * width + blur;
	buildKernel(k, u, v, uw, vw, f.res);
    }
    k.stripZeros();

    // check kernel (debug only)
    assert(k.uw > 0 && k.vw > 0);
    assert(k.uw <= PtexSeparableKernel::kmax && k.vw <= PtexSeparableKernel::kmax);
    _weight = k.weight();

    // allocate temporary double-precision result
    _result = (double*) alloca(sizeof(double)*_nchan);
    memset(_result, 0, sizeof(double)*_nchan);

    // apply to faces
    splitAndApply(k, faceid, f);

    // normalize (both for data type and cumulative kernel weight applied)
    // and output result
    double scale = 1.0 / (_weight * OneValue(_dt));
    for (int i = 0; i < _nchan; i++) result[i] = float(_result[i] * scale);

    // clear temp result
    _result = 0;
}
void PtexSeparableFilter::apply(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f)
{
    assert(k.u >= 0 && k.u + k.uw <= k.res.u());
    assert(k.v >= 0 && k.v + k.vw <= k.res.v());

    if (k.uw <= 0 || k.vw <= 0) return;

    // downres kernel if needed
    while (k.res.u() > f.res.u()) k.downresU();
    while (k.res.v() > f.res.v()) k.downresV();

    // get face data, and apply
    PtexPtr<PtexFaceData> dh ( _tx->getData(faceid, k.res) );
    if (!dh) return;

    if (dh->isConstant()) {
	k.applyConst(_result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan);
    }
    else if (dh->isTiled()) {
	Ptex::Res tileres = dh->tileRes();
	PtexSeparableKernel kt;
	kt.res = tileres;
	int tileresu = tileres.u();
	int tileresv = tileres.v();
	int ntilesu = k.res.u() / tileresu;
	for (int v = k.v, vw = k.vw; vw > 0; vw -= kt.vw, v += kt.vw) {
	    int tilev = v / tileresv;
	    kt.v = v % tileresv;
	    kt.vw = PtexUtils::min(vw, tileresv - kt.v);
	    kt.kv = k.kv + v - k.v;
	    for (int u = k.u, uw = k.uw; uw > 0; uw -= kt.uw, u += kt.uw) {
		int tileu = u / tileresu;
		kt.u = u % tileresu;
		kt.uw = PtexUtils::min(uw, tileresu - kt.u);
		kt.ku = k.ku + u - k.u;
		PtexPtr<PtexFaceData> th ( dh->getTile(tilev * ntilesu + tileu) );
		if (th) {
		    if (th->isConstant())
			kt.applyConst(_result, (char*)th->getData()+_firstChanOffset, _dt, _nchan);
		    else
			kt.apply(_result, (char*)th->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
		}
	    }
	}
    }
    else {
	k.apply(_result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
    }
}
Пример #12
0
void PtexSeparableFilter::apply(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f)
{
    assert(k.u >= 0 && k.u + k.uw <= k.res.u());
    assert(k.v >= 0 && k.v + k.vw <= k.res.v());

    if (k.uw <= 0 || k.vw <= 0) return;

    // downres kernel if needed
    while (k.res.u() > f.res.u()) k.downresU();
    while (k.res.v() > f.res.v()) k.downresV();

    // get face data, and apply
    PtexPtr<PtexFaceData> dh ( _tx->getData(faceid, k.res) );
    if (!dh) return;

    if (dh->isConstant()) {
        k.applyConst(_result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan);
        return;
    }

    // allocate temporary result for tanvec mode (if needed)
    bool tanvecMode = (_efm == efm_tanvec) && (_nchan >= 2) && (k.rot > 0);
    float* result = tanvecMode ? (float*) alloca(sizeof(float)*_nchan) : _result;
    if (tanvecMode) memset(result, 0, sizeof(float)*_nchan);

    if (dh->isTiled()) {
        Ptex::Res tileres = dh->tileRes();
        PtexSeparableKernel kt;
        kt.res = tileres;
        int tileresu = tileres.u();
        int tileresv = tileres.v();
        int ntilesu = k.res.u() / tileresu;
        for (int v = k.v, vw = k.vw; vw > 0; vw -= kt.vw, v += kt.vw) {
            int tilev = v / tileresv;
            kt.v = v % tileresv;
            kt.vw = PtexUtils::min(vw, tileresv - kt.v);
            kt.kv = k.kv + v - k.v;
            for (int u = k.u, uw = k.uw; uw > 0; uw -= kt.uw, u += kt.uw) {
                int tileu = u / tileresu;
                kt.u = u % tileresu;
                kt.uw = PtexUtils::min(uw, tileresu - kt.u);
                kt.ku = k.ku + u - k.u;
                PtexPtr<PtexFaceData> th ( dh->getTile(tilev * ntilesu + tileu) );
                if (th) {
                    if (th->isConstant())
                        kt.applyConst(result, (char*)th->getData()+_firstChanOffset, _dt, _nchan);
                    else
                        kt.apply(result, (char*)th->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
                }
            }
        }
    }
    else {
        k.apply(result, (char*)dh->getData()+_firstChanOffset, _dt, _nchan, _ntxchan);
    }

    if (tanvecMode) {
        // rotate tangent-space vector data and update main result
        switch (k.rot) {
            case 0: // rot==0 included for completeness, but tanvecMode should be false in this case
                _result[0] += result[0];
                _result[1] += result[1];
                break;
            case 1:
                _result[0] -= result[1];
                _result[1] += result[0];
                break;
            case 2:
                _result[0] -= result[0];
                _result[1] -= result[1];
                break;
            case 3:
                _result[0] += result[1];
                _result[1] -= result[0];
                break;
        }
        for (int i = 2; i < _nchan; i++) _result[i] += result[i];
    }
}