- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files
- Directories
- Examples

# PtexImporter/ptex/PtexReader.cpp

    /*
    PTEX SOFTWARE
    Copyright 2009 Disney Enterprises, Inc.  All rights reserved

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.

      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in
        the documentation and/or other materials provided with the
        distribution.

      * The names "Disney", "Walt Disney Pictures", "Walt Disney Animation
        Studios" or the names of its contributors may NOT be used to
        endorse or promote products derived from this software without
        specific prior written permission from Walt Disney Pictures.

    Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND
    CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
    BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
    FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED.
    IN NO EVENT SHALL WALT DISNEY PICTURES, THE COPYRIGHT HOLDER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND BASED ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
    */

    #include "PtexPlatform.h"
    #include <iostream>
    #include <sstream>
    #include <errno.h>
    #include <stdio.h>

    #include "Ptexture.h"
    #include "PtexUtils.h"
    #include "PtexReader.h"

    namespace {
        class DefaultInputHandler : public PtexInputHandler
        {
         public:
        virtual Handle open(const char* path) { return (Handle) fopen(path, "rb"); }
        virtual void seek(Handle handle, int64_t pos) { fseeko((FILE*)handle, pos, SEEK_SET); }
        virtual size_t read(void* buffer, size_t size, Handle handle) {
            return fread(buffer, size, 1, (FILE*)handle) == 1 ? size : 0;
        }
        virtual bool close(Handle handle) { return fclose((FILE*)handle); }
        virtual const char* lastError() { return strerror(errno); }
        } defaultInputHandler;
    }

    PtexTexture* PtexTexture::open(const char* path, Ptex::String& error, bool premultiply)
    {
        // create a private cache and use it to open the file
        PtexCache* cache = PtexCache::create(1, 1024*1024, premultiply);
        PtexTexture* file = cache->get(path, error);

        // make reader own the cache (so it will delete it later)
        PtexReader* reader = dynamic_cast<PtexReader*> (file);
        if (reader) reader->setOwnsCache();

        // and purge cache so cache doesn't try to hold reader open
        cache->purgeAll();
        return file;
    }

    bool PtexReader::open(const char* path, Ptex::String& error)
    {
        if (!LittleEndian()) {
        error = "Ptex library doesn't currently support big-endian cpu's";
        return 0;
        }
        _path = path;
        _fp = _io->open(path);
        if (!_fp) {
        std::string errstr = "Can't open ptex file: ";
        errstr += path; errstr += "\n"; errstr += _io->lastError();
        error = errstr.c_str();
        return 0;
        }
        readBlock(&_header, HeaderSize);
        if (_header.magic != Magic) {
        std::string errstr = "Not a ptex file: "; errstr += path;
        error = errstr.c_str();
        return 0;
        }
        if (_header.version != 1) {
        char ver[21]; snprintf(ver, 20, "%d", _header.version);
        std::string errstr = "Unsupported ptex file version (";
        errstr += ver; errstr += "): "; errstr += path;
        error = errstr.c_str();
        return 0;
        }
        _pixelsize = _header.pixelSize();

        // read extended header
        memset(&_extheader, 0, sizeof(_extheader));
        readBlock(&_extheader, PtexUtils::min(uint32_t(ExtHeaderSize), _header.extheadersize));

        // compute offsets of various blocks
        FilePos pos = tell();
        _faceinfopos = pos;   pos += _header.faceinfosize;
        _constdatapos = pos;  pos += _header.constdatasize;
        _levelinfopos = pos;  pos += _header.levelinfosize;
        _leveldatapos = pos;  pos += _header.leveldatasize;
        _metadatapos = pos;   pos += _header.metadatazipsize;
                              pos += sizeof(uint64_t); // compatibility barrier
        _lmdheaderpos = pos;  pos += _extheader.lmdheaderzipsize;
        _lmddatapos = pos;    pos += _extheader.lmddatasize;

        // edit data may not start immediately if additional sections have been added
        // use value from extheader if present (and > pos)
        _editdatapos = PtexUtils::max(FilePos(_extheader.editdatapos), pos);

        // read basic file info
        readFaceInfo();
        readConstData();
        readLevelInfo();
        readEditData();

        // an error occurred while reading the file
        if (!_ok) {
        error = _error.c_str();
        return 0;
        }

        return 1;
    }

    PtexReader::PtexReader(void** parent, PtexCacheImpl* cache, bool premultiply,
                   PtexInputHandler* io)
        : PtexCachedFile(parent, cache),
          _io(io ? io : &defaultInputHandler),
          _premultiply(premultiply),
          _ownsCache(false),
          _ok(true),
          _fp(0),
          _pos(0),
          _pixelsize(0),
          _constdata(0),
          _metadata(0),
          _hasEdits(false)
    {
        memset(&_header, 0, sizeof(_header));
        memset(&_zstream, 0, sizeof(_zstream));
        inflateInit(&_zstream);
    }

    PtexReader::~PtexReader()
    {
        if (_fp) _io->close(_fp);

        // we can free the const data directly since we own it (it doesn't go through the cache)
        if (_constdata) free(_constdata);

        // the rest must be orphaned since there may still be references outstanding
        orphanList(_levels);
        for (ReductionMap::iterator i = _reductions.begin(); i != _reductions.end(); i++) {
        FaceData* f = i->second;
        if (f) f->orphan();
        }
        if (_metadata) {
        _metadata->orphan();
        _metadata = 0;
        }

        inflateEnd(&_zstream);

        if (_ownsCache) _cache->setPendingDelete();
    }

    void PtexReader::release()
    {
        PtexCacheImpl* cache = _cache;
        {
        // create local scope for cache lock
        AutoLockCache lock(cache->cachelock);
        unref();
        }
        // If this reader owns the cache, then releasing it may cause deletion of the
        // reader and thus flag the cache for pending deletion.  Call the cache
        // to handle the pending deletion.
        cache->handlePendingDelete();
    }

    const Ptex::FaceInfo& PtexReader::getFaceInfo(int faceid)
    {
        if (faceid >= 0 && uint32_t(faceid) < _faceinfo.size())
        return _faceinfo[faceid];

        static Ptex::FaceInfo dummy;
        return dummy;
    }

    void PtexReader::readFaceInfo()
    {
        if (_faceinfo.empty()) {
        // read compressed face info block
        seek(_faceinfopos);
        int nfaces = _header.nfaces;
        _faceinfo.resize(nfaces);
        readZipBlock(&_faceinfo[0], _header.faceinfosize,
                 sizeof(FaceInfo)*nfaces);

        // generate rfaceids
        _rfaceids.resize(nfaces);
        std::vector<uint32_t> faceids_r(nfaces);
        PtexUtils::genRfaceids(&_faceinfo[0], nfaces,
                       &_rfaceids[0], &faceids_r[0]);

        // store face res values indexed by rfaceid
        _res_r.resize(nfaces);
        for (int i = 0; i < nfaces; i++)
            _res_r[i] = _faceinfo[faceids_r[i]].res;
        }
    }

    void PtexReader::readLevelInfo()
    {
        if (_levelinfo.empty()) {
        // read level info block
        seek(_levelinfopos);
        _levelinfo.resize(_header.nlevels);
        readBlock(&_levelinfo[0], LevelInfoSize*_header.nlevels);

        // initialize related data
        _levels.resize(_header.nlevels);
        _levelpos.resize(_header.nlevels);
        FilePos pos = _leveldatapos;
        for (int i = 0; i < _header.nlevels; i++) {
            _levelpos[i] = pos;
            pos += _levelinfo[i].leveldatasize;
        }
        }
    }

    void PtexReader::readConstData()
    {
        if (!_constdata) {
        // read compressed constant data block
        seek(_constdatapos);
        int size = _pixelsize * _header.nfaces;
        _constdata = (uint8_t*) malloc(size);
        readZipBlock(_constdata, _header.constdatasize, size);
        if (_premultiply && _header.hasAlpha())
            PtexUtils::multalpha(_constdata, _header.nfaces, _header.datatype,
                     _header.nchannels, _header.alphachan);
        }
    }

    PtexMetaData* PtexReader::getMetaData()
    {
        AutoLockCache locker(_cache->cachelock);
        if (_metadata) _metadata->ref();
        else readMetaData();
        return _metadata;
    }

    PtexReader::MetaData::Entry*
    PtexReader::MetaData::getEntry(const char* key)
    {
        MetaMap::iterator iter = _map.find(key);
        if (iter == _map.end()) {
        // not found
        return 0;
        }

        Entry* e = &iter->second;
        if (!e->isLmd) {
        // normal (small) meta data - just return directly
        return e;
        }

        // large meta data - may not be read in yet
        AutoLockCache lock(_cache->cachelock);
        if (e->lmdData) {
        // already in memory, add a ref
        e->lmdData->ref();
        _lmdRefs.push_back(e->lmdData);
        return e;
        }
        else {
        // not present, must read from file
        // temporarily release cache lock so other threads can proceed
        _cache->cachelock.unlock();

        // get read lock and make sure we still need to read
        AutoMutex locker(_reader->readlock);
        if (e->lmdData) {
            // another thread must have read it while we were waiting
            _cache->cachelock.lock();

            // make sure it's still there now that we have the lock
            if (e->lmdData) {
            e->data = e->lmdData->data();
            _lmdRefs.push_back(e->lmdData);
            e->lmdData->ref();
            return e;
            }
        }
        // go ahead and read, keep local until finished
        LargeMetaData*& parent = e->lmdData;
        LargeMetaData* volatile lmdData = new LargeMetaData((void**)&parent, _cache, e->datasize);
        e->data = lmdData->data();
        _reader->seek(e->lmdPos);
        _reader->readZipBlock(e->data, e->lmdZipSize, e->datasize);
        // reacquire cache lock and update entry
        _cache->cachelock.lock();
        e->lmdData = lmdData;
        return e;
        }
    }

    void PtexReader::readMetaData()
    {
        // temporarily release cache lock so other threads can proceed
        _cache->cachelock.unlock();

        // get read lock and make sure we still need to read
        AutoMutex locker(readlock);
        if (_metadata) {
        // another thread must have read it while we were waiting
        _cache->cachelock.lock();
        // make sure it's still there now that we have the lock
        if (_metadata) {
            _metadata->ref();
            return;
        }
        _cache->cachelock.unlock();
        }

        // compute total size (including edit blocks) for cache tracking
        int totalsize = _header.metadatamemsize;
        for (size_t i = 0, size = _metaedits.size(); i < size; i++)
        totalsize += _metaedits[i].memsize;

        // allocate new meta data (keep local until fully initialized)
        MetaData* volatile newmeta = new MetaData(&_metadata, _cache, totalsize, this);

        // read primary meta data blocks
        if (_header.metadatamemsize)
        readMetaDataBlock(newmeta, _metadatapos,
                  _header.metadatazipsize, _header.metadatamemsize);

        // read large meta data headers
        if (_extheader.lmdheadermemsize)
        readLargeMetaDataHeaders(newmeta, _lmdheaderpos,
                     _extheader.lmdheaderzipsize, _extheader.lmdheadermemsize);

        // read meta data edits
        for (size_t i = 0, size = _metaedits.size(); i < size; i++)
        readMetaDataBlock(newmeta, _metaedits[i].pos,
                  _metaedits[i].zipsize, _metaedits[i].memsize);

        // store meta data
        _cache->cachelock.lock();
        _metadata = newmeta;

        // clean up unused data
        _cache->purgeData();
    }

    void PtexReader::readMetaDataBlock(MetaData* metadata, FilePos pos, int zipsize, int memsize)
    {
        seek(pos);
        // read from file
        bool useMalloc = memsize > AllocaMax;
        char* buff = useMalloc ? (char*) malloc(memsize) : (char*)alloca(memsize);

        if (readZipBlock(buff, zipsize, memsize)) {
        // unpack data entries
        char* ptr = buff;
        char* end = ptr + memsize;
        while (ptr < end) {
            uint8_t keysize = *ptr++;
            char* key = (char*)ptr; ptr += keysize;
            key[keysize-1] = '\0';
            uint8_t datatype = *ptr++;
            uint32_t datasize; memcpy(&datasize, ptr, sizeof(datasize));
            ptr += sizeof(datasize);
            char* data = ptr; ptr += datasize;
            metadata->addEntry(keysize-1, key, datatype, datasize, data);
        }
        }
        if (useMalloc) free(buff);
    }

    void PtexReader::readLargeMetaDataHeaders(MetaData* metadata, FilePos pos, int zipsize, int memsize)
    {
        seek(pos);
        // read from file
        bool useMalloc = memsize > AllocaMax;
        char* buff = useMalloc ? (char*) malloc(memsize) : (char*)alloca(memsize);

        if (readZipBlock(buff, zipsize, memsize)) {
        pos += zipsize;

        // unpack data entries
        char* ptr = buff;
        char* end = ptr + memsize;
        while (ptr < end) {
            uint8_t keysize = *ptr++;
            char* key = (char*)ptr; ptr += keysize;
            uint8_t datatype = *ptr++;
            uint32_t datasize; memcpy(&datasize, ptr, sizeof(datasize));
            ptr += sizeof(datasize);
            uint32_t zipsize; memcpy(&zipsize, ptr, sizeof(zipsize));
            ptr += sizeof(zipsize);
            metadata->addLmdEntry(keysize-1, key, datatype, datasize, pos, zipsize);
            pos += zipsize;
        }
        }
        if (useMalloc) free(buff);
    }

    void PtexReader::readEditData()
    {
        // determine file range to scan for edits
        FilePos pos = FilePos(_editdatapos), endpos;
        if (_extheader.editdatapos > 0) {
        // newer files record the edit data position and size in the extheader
        // note: position will be non-zero even if size is zero
        endpos = FilePos(pos + _extheader.editdatasize);
        }
        else {
        // must have an older file, just read until EOF
        endpos = FilePos((uint64_t)-1);
        }

        while (pos < endpos) {
        seek(pos);
        // read the edit data header
        uint8_t edittype = et_editmetadata;
        uint32_t editsize;
        if (!readBlock(&edittype, sizeof(edittype), /*reporterror*/ false)) break;
        if (!readBlock(&editsize, sizeof(editsize), /*reporterror*/ false)) break;
        if (!editsize) break;
        _hasEdits = true;
        pos = tell() + editsize;
        switch (edittype) {
        case et_editfacedata:   readEditFaceData(); break;
        case et_editmetadata:   readEditMetaData(); break;
        }
        }
    }

    void PtexReader::readEditFaceData()
    {
        // read header
        EditFaceDataHeader efdh;
        if (!readBlock(&efdh, EditFaceDataHeaderSize)) return;

        // update face info
        int faceid = efdh.faceid;
        if (faceid < 0 || size_t(faceid) >= _header.nfaces) return;
        FaceInfo& f = _faceinfo[faceid];
        f = efdh.faceinfo;
        f.flags |= FaceInfo::flag_hasedits;

        // read const value now
        uint8_t* constdata = _constdata + _pixelsize * faceid;
        if (!readBlock(constdata, _pixelsize)) return;
        if (_premultiply && _header.hasAlpha())
        PtexUtils::multalpha(constdata, 1, _header.datatype,
                     _header.nchannels, _header.alphachan);

        // update header info for remaining data
        if (!f.isConstant()) {
        _faceedits.push_back(FaceEdit());
        FaceEdit& e = _faceedits.back();
        e.pos = tell();
        e.faceid = faceid;
        e.fdh = efdh.fdh;
        }
    }

    void PtexReader::readEditMetaData()
    {
        // read header
        EditMetaDataHeader emdh;
        if (!readBlock(&emdh, EditMetaDataHeaderSize)) return;

        // record header info for later
        _metaedits.push_back(MetaEdit());
        MetaEdit& e = _metaedits.back();
        e.pos = tell();
        e.zipsize = emdh.metadatazipsize;
        e.memsize = emdh.metadatamemsize;
    }

    bool PtexReader::readBlock(void* data, int size, bool reporterror)
    {
        int result = _io->read(data, size, _fp);
        if (result == size) {
        _pos += size;
        STATS_INC(nblocksRead);
        STATS_ADD(nbytesRead, size);
        return 1;
        }
        if (reporterror)
        setError("PtexReader error: read failed (EOF)");
        return 0;
    }

    bool PtexReader::readZipBlock(void* data, int zipsize, int unzipsize)
    {
        void* buff = alloca(BlockSize);
        _zstream.next_out = (Bytef*) data;
        _zstream.avail_out = unzipsize;

        while (1) {
        int size = (zipsize < BlockSize) ? zipsize : BlockSize;
        zipsize -= size;
        if (!readBlock(buff, size)) break;
        _zstream.next_in = (Bytef*) buff;
        _zstream.avail_in = size;
        int zresult = inflate(&_zstream, zipsize ? Z_NO_FLUSH : Z_FINISH);
        if (zresult == Z_STREAM_END) break;
        if (zresult != Z_OK) {
            setError("PtexReader error: unzip failed, file corrupt");
            inflateReset(&_zstream);
            return 0;
        }
        }

        int total = _zstream.total_out;
        inflateReset(&_zstream);
        return total == unzipsize;
    }

    void PtexReader::readLevel(int levelid, Level*& level)
    {
        // temporarily release cache lock so other threads can proceed
        _cache->cachelock.unlock();

        // get read lock and make sure we still need to read
        AutoMutex locker(readlock);
        if (level) {
        // another thread must have read it while we were waiting
        _cache->cachelock.lock();
        // make sure it's still there now that we have the lock
        if (level) {
            level->ref();
            return;
        }
        _cache->cachelock.unlock();
        }

        // go ahead and read the level
        LevelInfo& l = _levelinfo[levelid];

        // keep new level local until finished
        Level* volatile newlevel = new Level((void**)&level, _cache, l.nfaces);
        seek(_levelpos[levelid]);
        readZipBlock(&newlevel->fdh[0], l.levelheadersize, FaceDataHeaderSize * l.nfaces);
        computeOffsets(tell(), l.nfaces, &newlevel->fdh[0], &newlevel->offsets[0]);

        // apply edits (if any) to level 0
        if (levelid == 0) {
        for (size_t i = 0, size = _faceedits.size(); i < size; i++) {
            FaceEdit& e = _faceedits[i];
            newlevel->fdh[e.faceid] = e.fdh;
            newlevel->offsets[e.faceid] = e.pos;
        }
        }

        // don't assign to result until level data is fully initialized
        _cache->cachelock.lock();
        level = newlevel;

        // clean up unused data
        _cache->purgeData();
    }

    void PtexReader::readFace(int levelid, Level* level, int faceid)
    {
        // temporarily release cache lock so other threads can proceed
        _cache->cachelock.unlock();

        // get read lock and make sure we still need to read
        FaceData*& face = level->faces[faceid];
        AutoMutex locker(readlock);

        if (face) {
        // another thread must have read it while we were waiting
        _cache->cachelock.lock();
        // make sure it's still there now that we have the lock
        if (face) {
            face->ref();
            return;
        }
        _cache->cachelock.unlock();
        }

        // Go ahead and read the face, and read nearby faces if
        // possible. The goal is to coalesce small faces into single
        // runs of consecutive reads to minimize seeking and take
        // advantage of read-ahead buffering.

        // Try to read as many faces as will fit in BlockSize.  Use the
        // in-memory size rather than the on-disk size to prevent flooding
        // the memory cache.  And don't coalesce w/ tiled faces as these
        // are meant to be read individually.

        // scan both backwards and forwards looking for unread faces
        int first = faceid, last = faceid;
        int totalsize = 0;

        FaceDataHeader fdh = level->fdh[faceid];
        if (fdh.encoding() != enc_tiled) {
        totalsize += unpackedSize(fdh, levelid, faceid);

        int nfaces = int(level->fdh.size());
        while (1) {
            int f = first-1;
            if (f < 0 || level->faces[f]) break;
            fdh = level->fdh[f];
            if (fdh.encoding() == enc_tiled) break;
            int size = totalsize + unpackedSize(fdh, levelid, f);
            if (size > BlockSize) break;
            first = f;
            totalsize = size;
        }
        while (1) {
            int f = last+1;
            if (f >= nfaces || level->faces[f]) break;
            fdh = level->fdh[f];
            if (fdh.encoding() == enc_tiled) break;
            int size = totalsize + unpackedSize(fdh, levelid, f);
            if (size > BlockSize) break;
            last = f;
            totalsize = size;
        }
        }

        // read all faces in range
        // keep track of extra faces we read so we can add them to the cache later
        std::vector<FaceData*> extraFaces;
        extraFaces.reserve(last-first);

        for (int i = first; i <= last; i++) {
        fdh = level->fdh[i];
        // skip faces with zero size (which is true for level-0 constant faces)
        if (fdh.blocksize()) {
            FaceData*& face = level->faces[i];
            readFaceData(level->offsets[i], fdh, getRes(levelid, i), levelid, face);
            if (i != faceid) extraFaces.push_back(face);
        }
        }

        // reacquire cache lock, then unref extra faces to add them to the cache
        _cache->cachelock.lock();
        for (size_t i = 0, size = extraFaces.size(); i < size; i++)
        extraFaces[i]->unref();
    }

    void PtexReader::TiledFace::readTile(int tile, FaceData*& data)
    {
        // temporarily release cache lock so other threads can proceed
        _cache->cachelock.unlock();

        // get read lock and make sure we still need to read
        AutoMutex locker(_reader->readlock);
        if (data) {
        // another thread must have read it while we were waiting
        _cache->cachelock.lock();
        // make sure it's still there now that we have the lock
        if (data) {
            data->ref();
            return;
        }
        _cache->cachelock.unlock();
        }

        // go ahead and read the face data
        _reader->readFaceData(_offsets[tile], _fdh[tile], _tileres, _levelid, data);
        _cache->cachelock.lock();

        // clean up unused data
        _cache->purgeData();
    }

    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);
            void* tmp = 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;
        }
        break;
        }

        face = newface;
    }

    void PtexReader::getData(int faceid, void* buffer, int stride)
    {
        if (!_ok) return;
        FaceInfo& f = _faceinfo[faceid];
        getData(faceid, buffer, stride, f.res);
    }

    void PtexReader::getData(int faceid, void* buffer, int stride, Res res)
    {
        if (!_ok) return;

        // note - all locking is handled in called getData methods
        FaceInfo& f = _faceinfo[faceid];
        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 = f.res.ntilesu(tileres);
        int ntilesv = f.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);
        }
    }

    PtexFaceData* PtexReader::getData(int faceid)
    {
        if (faceid < 0 || size_t(faceid) >= _header.nfaces) return 0;
        if (!_ok) return 0;

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

        // get level zero (full) res face
        AutoLockCache locker(_cache->cachelock);
        Level* level = getLevel(0);
        FaceData* face = getFace(0, level, faceid);
        level->unref();
        return face;
    }

    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;
    }

    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::getPixel(int faceid, int u, int v,
                  float* result, int firstchan, int nchannels)
    {
        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) );
        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);
    }

    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);
    }

    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 PtexReader::ConstantFace::reduce(FaceData*& face, PtexReader*,
                          Res, PtexUtils::ReduceFn)
    {
        // get cache lock (just to protect the ref count)
        AutoLockCache locker(_cache->cachelock);

        // must make a new constant face (even though it's identical to this one)
        // because it will be owned by a different reduction level
        // and will therefore have a different parent
        ConstantFace* pf = new ConstantFace((void**)&face, _cache, _pixelsize);
        memcpy(pf->_data, _data, _pixelsize);
        face = pf;
    }

    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)
        Res newtileres;
        if (newres.ulog2 == 1 || newres.vlog2 == 1) {
        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 {
            // 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();
    }

    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);
    }

    PtexFaceData* PtexReader::TiledReducedFace::getTile(int tile)
    {
        _cache->cachelock.lock();
        FaceData*& face = _tiles[tile];
        if (face) {
        face->ref();
        _cache->cachelock.unlock();
        return face;
        }

        // first, get all parent tiles for this tile
        // and check if they are constant (with the same value)
        int pntilesu = _parentface->ntilesu();
        int pntilesv = _parentface->ntilesv();
        int nu = pntilesu / _ntilesu; // num parent tiles for this tile in u dir
        int nv = pntilesv / _ntilesv; // num parent tiles for this tile in v dir

        int ntiles = nu*nv; // num parent tiles for this tile
        PtexFaceData** tiles = (PtexFaceData**) alloca(ntiles * sizeof(PtexFaceData*));
        bool allConstant = true;
        int ptile = (tile/_ntilesu) * nv * pntilesu + (tile%_ntilesu) * nu;
        for (int i = 0; i < ntiles;) {
        // temporarily release cache lock while we get parent tile
        _cache->cachelock.unlock();
        PtexFaceData* tile = tiles[i] = _parentface->getTile(ptile);
        _cache->cachelock.lock();
        allConstant = (allConstant && tile->isConstant() &&
                   (i==0 || (0 == memcmp(tiles[0]->getData(), tile->getData(),
                             _pixelsize))));
        i++;
        ptile += i%nu? 1 : pntilesu - nu + 1;
        }

        // make sure another thread didn't make the tile while we were checking
        if (face) {
        face->ref();
        _cache->cachelock.unlock();
        // release the tiles
        for (int i = 0; i < ntiles; i++) tiles[i]->release();
        return face;
        }

        if (allConstant) {
        // allocate a new constant face
        face = new ConstantFace((void**)&face, _cache, _pixelsize);
        memcpy(face->getData(), tiles[0]->getData(), _pixelsize);
        }
        else {
        // allocate a new packed face for the tile
        face = new PackedFace((void**)&face, _cache, _tileres,
                      _pixelsize, _pixelsize*_tileres.size());

        // generate reduction from parent tiles
        int ptileures = _parentface->tileres().u();
        int ptilevres = _parentface->tileres().v();
        int sstride = ptileures * _pixelsize;
        int dstride = _tileres.u() * _pixelsize;
        int dstepu = dstride/nu;
        int dstepv = dstride*_tileres.v()/nv - dstepu*(nu-1);

        char* dst = (char*) face->getData();
        for (int i = 0; i < ntiles;) {
            PtexFaceData* tile = tiles[i];
            if (tile->isConstant())
            PtexUtils::fill(tile->getData(), dst, dstride,
                    _tileres.u()/nu, _tileres.v()/nv,
                    _pixelsize);
            else
            _reducefn(tile->getData(), sstride, ptileures, ptilevres,
                  dst, dstride, _dt, _nchan);
            i++;
            dst += i%nu ? dstepu : dstepv;
        }
        }

        _cache->cachelock.unlock();

        // release the tiles
        for (int i = 0; i < ntiles; i++) tiles[i]->release();
        return face;
    }