PtexExtractor/ptex/PtexWriter.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.
*/

/* Ptex writer classes:

   PtexIncrWriter implements "incremental" mode and simply appends
   "edit" blocks to the end of the file.

   PtexMainWriter implements both writing from scratch and updating
   an existing file, either to add data or to "roll up" previous
   incremental edits.

   Because the various headers (faceinfo, levelinfo, etc.) are
   variable-length and precede the data, and because the data size
   is not known until it is compressed and written, all data
   are written to a temp file and then copied at the end to the
   final location.  This happens during the "finish" phase.

   Each time a texture is written to the file, a reduction of the
   texture is also generated and stored.  These reductions are stored
   in a temporary form and recalled later as the resolution levels are
   generated.

   The final reduction for each face is averaged and stored in the
   const data block.
*/

#include "PtexPlatform.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <sstream>

#include "Ptexture.h"
#include "PtexUtils.h"
#include "PtexWriter.h"


namespace {

    FILE* OpenTempFile(std::string& tmppath)
    {
    static Mutex lock;
    AutoMutex locker(lock);

    // choose temp dir
    static std::string tmpdir;
    static int initialized = 0;
    if (!initialized) {
        initialized = 1;
#ifdef WINDOWS
        // use GetTempPath API (first call determines length of result)
        DWORD result = ::GetTempPath(0, _T(""));
        if (result > 0) {
        std::vector<TCHAR> tempPath(result + 1);
        result = ::GetTempPath(static_cast<DWORD>(tempPath.size()), &tempPath[0]);
        if (result > 0 && result <= tempPath.size())
            tmpdir = std::string(tempPath.begin(), 
                     tempPath.begin() + static_cast<std::size_t>(result));
        else
            tmpdir = ".";
        }
#else
        // try $TEMP or $TMP, use /tmp as last resort
        const char* t = getenv("TEMP");
        if (!t) t = getenv("TMP");
        if (!t) t = "/tmp";
        tmpdir = t;
#endif
    }

    // build temp path

#ifdef WINDOWS
    // use process id and counter to make unique filename
    std::stringstream s;
    static int count = 0;
    s << tmpdir << "/" << "PtexTmp" << getpid() << "_" << ++count;
    tmppath = s.str();
    return fopen((char*) tmppath.c_str(), "wb+");
#else
    // use mkstemp to open unique file
    tmppath = tmpdir + "/PtexTmpXXXXXX";
    int fd = mkstemp((char*) tmppath.c_str());
    return fdopen(fd, "w+");
#endif
    }

    std::string fileError(const char* message, const char* path)
    {
    std::stringstream str;
    str << message << path << "\n" << strerror(errno);
    return str.str();
    }

    bool checkFormat(Ptex::MeshType mt, Ptex::DataType dt, int nchannels, int alphachan,
             Ptex::String& error)
    {
    // check to see if given file attributes are valid
    if (!PtexIO::LittleEndian()) {
        error = "PtexWriter doesn't currently support big-endian cpu's";
        return 0;
    }

    if (mt < Ptex::mt_triangle || mt > Ptex::mt_quad) {
        error = "PtexWriter error: Invalid mesh type";
        return 0;
    }

    if (dt < Ptex::dt_uint8 || dt > Ptex::dt_float) {
        error = "PtexWriter error: Invalid data type";
        return 0;
    }

    if (nchannels <= 0) {
        error = "PtexWriter error: Invalid number of channels";
        return 0;
    }

    if (alphachan != -1 && (alphachan < 0 || alphachan >= nchannels)) {
        error = "PtexWriter error: Invalid alpha channel";
        return 0;
    }

    return 1;
    }
}


PtexWriter* PtexWriter::open(const char* path,
                 Ptex::MeshType mt, Ptex::DataType dt,
                 int nchannels, int alphachan, int nfaces,
                 Ptex::String& error, bool genmipmaps)
{
    if (!checkFormat(mt, dt, nchannels, alphachan, error))
    return 0;

    PtexMainWriter* w = new PtexMainWriter(path, 0,
                       mt, dt, nchannels, alphachan, nfaces,
                       genmipmaps);
    std::string errstr;
    if (!w->ok(error)) {
    w->release();
    return 0;
    }
    return w;
}


PtexWriter* PtexWriter::edit(const char* path, bool incremental,
                 Ptex::MeshType mt, Ptex::DataType dt,
                 int nchannels, int alphachan, int nfaces,
                 Ptex::String& error, bool genmipmaps)
{
    if (!checkFormat(mt, dt, nchannels, alphachan, error))
    return 0;

    // try to open existing file (it might not exist)
    FILE* fp = fopen(path, "rb+");
    if (!fp && errno != ENOENT) {
    error = fileError("Can't open ptex file for update: ", path).c_str();
    }

    PtexWriterBase* w = 0;
    // use incremental writer iff incremental mode requested and file exists
    if (incremental && fp) {
    w = new PtexIncrWriter(path, fp, mt, dt, nchannels, alphachan, nfaces);
    }
    // otherwise use main writer
    else {
    PtexTexture* tex = 0;
    if (fp) {
        // got an existing file, close and reopen with PtexReader
        fclose(fp);

        // open reader for existing file
        tex = PtexTexture::open(path, error);
        if (!tex) return 0;

        // make sure header matches
        bool headerMatch = (mt == tex->meshType() &&
                dt == tex->dataType() &&
                nchannels == tex->numChannels() &&
                alphachan == tex->alphaChannel() &&
                nfaces == tex->numFaces());
        if (!headerMatch) {
        std::stringstream str;
        str << "PtexWriter::edit error: header doesn't match existing file, "
            << "conversions not currently supported";
        error = str.str().c_str();
        return 0;
        }
    }
    w = new PtexMainWriter(path, tex, mt, dt, nchannels, alphachan,
                   nfaces, genmipmaps);
    }

    if (!w->ok(error)) {
    w->release();
    return 0;
    }
    return w;
}


bool PtexWriter::applyEdits(const char* path, Ptex::String& error)
{
    // open reader for existing file
    PtexTexture* tex = PtexTexture::open(path, error);
    if (!tex) return 0;

    // see if we have any edits to apply
    if (tex->hasEdits()) {
    // create non-incremental writer
    PtexWriter* w = new PtexMainWriter(path, tex, tex->meshType(), tex->dataType(),
                       tex->numChannels(), tex->alphaChannel(), tex->numFaces(),
                       tex->hasMipMaps());
    // close to rebuild file
    if (!w->close(error)) return 0;
    w->release();
    }
    return 1;
}


PtexWriterBase::PtexWriterBase(const char* path,
                   Ptex::MeshType mt, Ptex::DataType dt,
                   int nchannels, int alphachan, int nfaces,
                   bool compress)
    : _ok(true),
      _path(path),
      _tilefp(0)
{
    memset(&_header, 0, sizeof(_header));
    _header.magic = Magic;
    _header.version = PtexFileMajorVersion;
    _header.minorversion = PtexFileMinorVersion;
    _header.meshtype = mt;
    _header.datatype = dt;
    _header.alphachan = alphachan;
    _header.nchannels = nchannels;
    _header.nfaces = nfaces;
    _header.nlevels = 0;
    _header.extheadersize = sizeof(_extheader);
    _pixelSize = _header.pixelSize();

    memset(&_extheader, 0, sizeof(_extheader));

    if (mt == mt_triangle)
    _reduceFn = &PtexUtils::reduceTri;
    else
    _reduceFn = &PtexUtils::reduce;

    memset(&_zstream, 0, sizeof(_zstream));
    deflateInit(&_zstream, compress ? Z_DEFAULT_COMPRESSION : 0);

    // create temp file for writing tiles
    // (must compress each tile before assembling a tiled face)
    std::string error;
    _tilefp = OpenTempFile(_tilepath);
    if (!_tilefp) {
    setError(fileError("Error creating temp file: ", _tilepath.c_str()));
    }
}


void PtexWriterBase::release()
{
    Ptex::String error;
    // close writer if app didn't, and report error if any
    if (_tilefp && !close(error))
    std::cerr << error.c_str() << std::endl;
    delete this;
}

PtexWriterBase::~PtexWriterBase()
{
    deflateEnd(&_zstream);
}


bool PtexWriterBase::close(Ptex::String& error)
{
    if (_ok) finish();
    if (!_ok) getError(error);
    if (_tilefp) {
    fclose(_tilefp);
    unlink(_tilepath.c_str());
    _tilefp = 0;
    }
    return _ok;
}


bool PtexWriterBase::storeFaceInfo(int faceid, FaceInfo& f, const FaceInfo& src, int flags)
{
    if (faceid < 0 || size_t(faceid) >= _header.nfaces) {
    setError("PtexWriter error: faceid out of range");
    return 0;
    }

    if (_header.meshtype == mt_triangle && (f.res.ulog2 != f.res.vlog2)) {
    setError("PtexWriter error: asymmetric face res not supported for triangle textures");
    return 0;
    }

    // copy all values
    f = src;

    // and clear extraneous ones
    if (_header.meshtype == mt_triangle) {
    f.flags = 0; // no user-settable flags on triangles
    f.adjfaces[3] = -1;
    f.adjedges &= 0x3f; // clear all but bottom six bits
    }
    else {
    // clear non-user-settable flags
    f.flags &= FaceInfo::flag_subface;
    }

    // set new flags
    f.flags |= flags;
    return 1;
}


void PtexWriterBase::writeMeta(const char* key, const char* value)
{
    addMetaData(key, mdt_string, value, int(strlen(value)+1));
}


void PtexWriterBase::writeMeta(const char* key, const int8_t* value, int count)
{
    addMetaData(key, mdt_int8, value, count);
}


void PtexWriterBase::writeMeta(const char* key, const int16_t* value, int count)
{
    addMetaData(key, mdt_int16, value, count*sizeof(int16_t));
}


void PtexWriterBase::writeMeta(const char* key, const int32_t* value, int count)
{
    addMetaData(key, mdt_int32, value, count*sizeof(int32_t));
}


void PtexWriterBase::writeMeta(const char* key, const float* value, int count)
{
    addMetaData(key, mdt_float, value, count*sizeof(float));
}


void PtexWriterBase::writeMeta(const char* key, const double* value, int count)
{
    addMetaData(key, mdt_double, value, count*sizeof(double));
}


void PtexWriterBase::writeMeta(PtexMetaData* data)
{
    int nkeys = data->numKeys();
    for (int i = 0; i < nkeys; i++) {
    const char* key = 0;
    MetaDataType type;
    data->getKey(i, key, type);
    int count;
    switch (type) {
    case mdt_string:
        {
        const char* val=0;
        data->getValue(key, val);
        writeMeta(key, val);
        }
        break;
    case mdt_int8:
        {
        const int8_t* val=0;
        data->getValue(key, val, count);
        writeMeta(key, val, count);
        }
        break;
    case mdt_int16:
        {
        const int16_t* val=0;
        data->getValue(key, val, count);
        writeMeta(key, val, count);
        }
        break;
    case mdt_int32:
        {
        const int32_t* val=0;
        data->getValue(key, val, count);
        writeMeta(key, val, count);
        }
        break;
    case mdt_float:
        {
        const float* val=0;
        data->getValue(key, val, count);
        writeMeta(key, val, count);
        }
        break;
    case mdt_double:
        {
        const double* val=0;
        data->getValue(key, val, count);
        writeMeta(key, val, count);
        }
        break;
    }
    }
}


void PtexWriterBase::addMetaData(const char* key, MetaDataType t,
                 const void* value, int size)
{
    if (strlen(key) > 255) {
    std::stringstream str;
    str << "PtexWriter error: meta data key too long (max=255) \"" << key << "\"";
    setError(str.str());
    return;
    }
    if (size <= 0) {
    std::stringstream str;
    str << "PtexWriter error: meta data size <= 0 for \"" << key << "\"";
    setError(str.str());
    }
    std::map<std::string,int>::iterator iter = _metamap.find(key);
    int index;
    if (iter != _metamap.end()) {
    // see if we already have this entry - if so, overwrite it
    index = iter->second;
    }
    else {
    // allocate a new entry
    index = _metadata.size();
    _metadata.resize(index+1);
    _metamap[key] = index;
    }
    MetaEntry& m = _metadata[index];
    m.key = key;
    m.datatype = t;
    m.data.resize(size);
    memcpy(&m.data[0], value, size);
}


int PtexWriterBase::writeBlank(FILE* fp, int size)
{
    if (!_ok) return 0;
    static char zeros[BlockSize] = {0};
    int remain = size;
    while (remain > 0) {
    remain -= writeBlock(fp, zeros, remain < BlockSize ? remain : BlockSize);
    }
    return size;
}


int PtexWriterBase::writeBlock(FILE* fp, const void* data, int size)
{
    if (!_ok) return 0;
    if (!fwrite(data, size, 1, fp)) {
    setError("PtexWriter error: file write failed");
    return 0;
    }
    return size;
}


int PtexWriterBase::writeZipBlock(FILE* fp, const void* data, int size, bool finish)
{
    if (!_ok) return 0;
    void* buff = alloca(BlockSize);
    _zstream.next_in = (Bytef*)data;
    _zstream.avail_in = size;

    while (1) {
    _zstream.next_out = (Bytef*)buff;
    _zstream.avail_out = BlockSize;
    int zresult = deflate(&_zstream, finish ? Z_FINISH : Z_NO_FLUSH);
    int size = BlockSize - _zstream.avail_out;
    if (size > 0) writeBlock(fp, buff, size);
    if (zresult == Z_STREAM_END) break;
    if (zresult != Z_OK) {
        setError("PtexWriter error: data compression internal error");
        break;
    }
    if (!finish && _zstream.avail_out != 0)
        // waiting for more input
        break;
    }

    if (!finish) return 0;

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


int PtexWriterBase::readBlock(FILE* fp, void* data, int size)
{
    if (!fread(data, size, 1, fp)) {
    setError("PtexWriter error: temp file read failed");
    return 0;
    }
    return size;
}


int PtexWriterBase::copyBlock(FILE* dst, FILE* src, FilePos pos, int size)
{
    if (size <= 0) return 0;
    fseeko(src, pos, SEEK_SET);
    int remain = size;
    void* buff = alloca(BlockSize);
    while (remain) {
    int nbytes = remain < BlockSize ? remain : BlockSize;
    if (!fread(buff, nbytes, 1, src)) {
        setError("PtexWriter error: temp file read failed");
        return 0;
    }
    if (!writeBlock(dst, buff, nbytes)) break;
    remain -= nbytes;
    }
    return size;
}


Ptex::Res PtexWriterBase::calcTileRes(Res faceres)
{
    // desired number of tiles = floor(log2(facesize / tilesize))
    int facesize = faceres.size() * _pixelSize;
    int ntileslog2 = PtexUtils::floor_log2(facesize/TileSize);
    if (ntileslog2 == 0) return faceres;

    // number of tiles is defined as:
    //   ntileslog2 = ureslog2 + vreslog2 - (tile_ureslog2 + tile_vreslog2)
    // rearranging to solve for the tile res:
    //   tile_ureslog2 + tile_vreslog2 = ureslog2 + vreslog2 - ntileslog2
    int n = faceres.ulog2 + faceres.vlog2 - ntileslog2;

    // choose u and v sizes for roughly square result (u ~= v ~= n/2)
    // and make sure tile isn't larger than face
    Res tileres;
    tileres.ulog2 = PtexUtils::min((n+1)/2, int(faceres.ulog2));
    tileres.vlog2 = PtexUtils::min(n - tileres.ulog2, int(faceres.vlog2));
    return tileres;
}


void PtexWriterBase::writeConstFaceBlock(FILE* fp, const void* data,
                     FaceDataHeader& fdh)
{
    // write a single const face data block
    // record level data for face and output the one pixel value
    fdh.set(_pixelSize, enc_constant);
    writeBlock(fp, data, _pixelSize);
}


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



int PtexWriterBase::writeMetaDataBlock(FILE* fp, MetaEntry& val)
{
    uint8_t keysize = uint8_t(val.key.size()+1);
    uint8_t datatype = val.datatype;
    uint32_t datasize = uint32_t(val.data.size());
    writeZipBlock(fp, &keysize, sizeof(keysize), false);
    writeZipBlock(fp, val.key.c_str(), keysize, false);
    writeZipBlock(fp, &datatype, sizeof(datatype), false);
    writeZipBlock(fp, &datasize, sizeof(datasize), false);
    writeZipBlock(fp, &val.data[0], datasize, false);
    int memsize = (sizeof(keysize) + keysize + sizeof(datatype)
           + sizeof(datasize) + datasize);
    return memsize;
}


PtexMainWriter::PtexMainWriter(const char* path, PtexTexture* tex,
                   Ptex::MeshType mt, Ptex::DataType dt,
                   int nchannels, int alphachan, int nfaces, bool genmipmaps)
    : PtexWriterBase(path, mt, dt, nchannels, alphachan, nfaces,
             /* compress */ true),
      _hasNewData(false),
      _genmipmaps(genmipmaps),
      _reader(0)
{
    _tmpfp = OpenTempFile(_tmppath);
    if (!_tmpfp) {
    setError(fileError("Error creating temp file: ", _tmppath.c_str()));
    return;
    }

    // data will be written to a ".new" path and then renamed to final location
    _newpath = path; _newpath += ".new";

    _levels.reserve(20);
    _levels.resize(1);

    // init faceinfo and set flags to -1 to mark as uninitialized
    _faceinfo.resize(nfaces);
    for (int i = 0; i < nfaces; i++) _faceinfo[i].flags = uint8_t(-1);

    _levels.front().pos.resize(nfaces);
    _levels.front().fdh.resize(nfaces);
    _rpos.resize(nfaces);
    _constdata.resize(nfaces*_pixelSize);

    if (tex) {
    // access reader implementation
    _reader = dynamic_cast<PtexReader*>(tex);
    if (!_reader) {
        setError("Internal error: dynamic_cast<PtexReader*> failed");
        tex->release();
        return;
    }

    // copy border modes
    setBorderModes(tex->uBorderMode(), tex->vBorderMode());

    // copy meta data from existing file
    PtexPtr<PtexMetaData> meta ( _reader->getMetaData() );
    writeMeta(meta);

    // see if we have any edits
    _hasNewData = _reader->hasEdits();
    }
}


PtexMainWriter::~PtexMainWriter()
{
    if (_reader) _reader->release();
}


bool PtexMainWriter::close(Ptex::String& error)
{
    // closing base writer will write all pending data via finish() method
    // and will close _fp (which in this case is on the temp disk)
    bool result = PtexWriterBase::close(error);
    if (_reader) {
    _reader->release();
    _reader = 0;
    }
    if (_tmpfp) {
    fclose(_tmpfp);
    unlink(_tmppath.c_str());
    _tmpfp = 0;
    }
    if (result && _hasNewData) {
    // rename temppath into final location
    unlink(_path.c_str());
    if (rename(_newpath.c_str(), _path.c_str()) == -1) {
        error = fileError("Can't write to ptex file: ", _path.c_str()).c_str();
        unlink(_newpath.c_str());
        result = false;
    }
    }
    return result;
}

bool PtexMainWriter::writeFace(int faceid, const FaceInfo& f, const void* data, int stride)
{
    if (!_ok) return 0;

    // auto-compute stride
    if (stride == 0) stride = f.res.u()*_pixelSize;

    // handle constant case
    if (PtexUtils::isConstant(data, stride, f.res.u(), f.res.v(), _pixelSize))
    return writeConstantFace(faceid, f, data);

    // non-constant case, ...

    // check and store face info
    if (!storeFaceInfo(faceid, _faceinfo[faceid], f)) return 0;

    // record position of current face
    _levels.front().pos[faceid] = ftello(_tmpfp);

    // write face data
    writeFaceData(_tmpfp, data, stride, f.res, _levels.front().fdh[faceid]);
    if (!_ok) return 0;

    // premultiply (if needed) before making reductions; use temp copy of data
    uint8_t* temp = 0;
    if (_header.hasAlpha()) {
    // first copy to temp buffer
    int rowlen = f.res.u() * _pixelSize, nrows = f.res.v();
    temp = (uint8_t*) malloc(rowlen * nrows);
    PtexUtils::copy(data, stride, temp, rowlen, nrows, rowlen);

    // multiply alpha
    PtexUtils::multalpha(temp, f.res.size(), _header.datatype, _header.nchannels,
                 _header.alphachan);

    // override source buffer
    data = temp;
    stride = rowlen;
    }

    // generate first reduction (if needed)
    if (_genmipmaps &&
    (f.res.ulog2 > MinReductionLog2 && f.res.vlog2 > MinReductionLog2))
    {
    _rpos[faceid] = ftello(_tmpfp);
    writeReduction(_tmpfp, data, stride, f.res);
    }
    else {
    storeConstValue(faceid, data, stride, f.res);
    }

    if (temp) free(temp);
    _hasNewData = true;
    return 1;
}


bool PtexMainWriter::writeConstantFace(int faceid, const FaceInfo& f, const void* data)
{
    if (!_ok) return 0;

    // check and store face info
    if (!storeFaceInfo(faceid, _faceinfo[faceid], f, FaceInfo::flag_constant)) return 0;

    // store face value in constant block
    memcpy(&_constdata[faceid*_pixelSize], data, _pixelSize);
    _hasNewData = true;
    return 1;
}



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


void PtexMainWriter::flagConstantNeighorhoods()
{
    // for each constant face
    for (int faceid = 0, n = int(_faceinfo.size()); faceid < n; faceid++) {
    FaceInfo& f = _faceinfo[faceid];
    if (!f.isConstant()) continue;
    uint8_t* constdata = &_constdata[faceid*_pixelSize];

    // check to see if neighborhood is constant
    bool isConst = true;
    bool isTriangle = _header.meshtype == mt_triangle;
    int nedges = isTriangle ? 3 : 4;
    for (int eid = 0; eid < nedges; eid++) {
        bool prevWasSubface = f.isSubface();
        int prevFid = faceid;
        // traverse across edge
        int afid = f.adjface(eid);
        int aeid = f.adjedge(eid);
        int count = 0;
        const int maxcount = 10; // max valence (as safety valve)
        while (afid != faceid) {
        // if we hit a boundary, assume non-const (not worth
        // the trouble to redo traversal from CCW direction;
        // also, boundary might want to be "black")
        // assume const if we hit max valence too
        if (afid < 0 || ++count == maxcount)
        { isConst = false; break; }

        // check if neighor is constant, and has the same value as face
        FaceInfo& af = _faceinfo[afid];
        if (!af.isConstant() ||
            0 != memcmp(constdata, &_constdata[afid*_pixelSize], _pixelSize))
        { isConst = false; break; }

        // traverse around vertex in CW direction
        // handle T junction between subfaces and main face
        bool isSubface = af.isSubface();
        bool isT = !isTriangle && prevWasSubface && !isSubface && af.adjface(aeid) == prevFid;
        std::swap(prevFid, afid);
        prevWasSubface = isSubface;

        if (isT) {
            // traverse to secondary subface across T junction
            FaceInfo& pf = _faceinfo[afid];
            int peid = af.adjedge(aeid);
            peid = (peid + 3) % 4;
            afid = pf.adjface(peid);
            aeid = pf.adjedge(peid);
            aeid = (aeid + 3) % 4;
        }
        else {
            // traverse around vertex
            aeid = (aeid + 1) % nedges;
            afid = af.adjface(aeid);
            aeid = af.adjedge(aeid);
        }
        }
        if (!isConst) break;
    }
    if (isConst) f.flags |= FaceInfo::flag_nbconstant;
    }
}


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 PtexMainWriter::writeMetaData(FILE* fp)
{
    std::vector<MetaEntry*> lmdEntries; // large meta data items

    // write small meta data items in a single zip block
    for (int i = 0, n = _metadata.size(); i < n; i++) {
    MetaEntry& e = _metadata[i];
    if (int(e.data.size()) > MetaDataThreshold) {
        // skip large items, but record for later
        lmdEntries.push_back(&e);
    }
    else {
        // add small item to zip block
        _header.metadatamemsize += writeMetaDataBlock(fp, e);
    }
    }
    if (_header.metadatamemsize) {
    // finish zip block
    _header.metadatazipsize = writeZipBlock(fp, 0, 0, /*finish*/ true);
    }

    // write compatibility barrier
    writeBlank(fp, sizeof(uint64_t));

    // write large items as separate blocks
    int nLmd = lmdEntries.size();
    if (nLmd > 0) {
    // write data records to tmp file and accumulate zip sizes for lmd header
    std::vector<FilePos> lmdoffset(nLmd);
    std::vector<uint32_t> lmdzipsize(nLmd);
    for (int i = 0; i < nLmd; i++) {
        MetaEntry* e= lmdEntries[i];
        lmdoffset[i] = ftello(_tmpfp);
        lmdzipsize[i] = writeZipBlock(_tmpfp, &e->data[0], e->data.size());
    }

    // write lmd header records as single zip block
    for (int i = 0; i < nLmd; i++) {
        MetaEntry* e = lmdEntries[i];
        uint8_t keysize = uint8_t(e->key.size()+1);
        uint8_t datatype = e->datatype;
        uint32_t datasize = e->data.size();
        uint32_t zipsize = lmdzipsize[i];

        writeZipBlock(fp, &keysize, sizeof(keysize), false);
        writeZipBlock(fp, e->key.c_str(), keysize, false);
        writeZipBlock(fp, &datatype, sizeof(datatype), false);
        writeZipBlock(fp, &datasize, sizeof(datasize), false);
        writeZipBlock(fp, &zipsize, sizeof(zipsize), false);
        _extheader.lmdheadermemsize +=
        sizeof(keysize) + keysize + sizeof(datatype) + sizeof(datasize) + sizeof(zipsize);
    }
    _extheader.lmdheaderzipsize = writeZipBlock(fp, 0, 0, /*finish*/ true);

    // copy data records
    for (int i = 0; i < nLmd; i++) {
        _extheader.lmddatasize +=
        copyBlock(fp, _tmpfp, lmdoffset[i], lmdzipsize[i]);
    }
    }
}


PtexIncrWriter::PtexIncrWriter(const char* path, FILE* fp,
                   Ptex::MeshType mt, Ptex::DataType dt,
                   int nchannels, int alphachan, int nfaces)
    : PtexWriterBase(path, mt, dt, nchannels, alphachan, nfaces,
             /* compress */ false),
      _fp(fp)
{
    // note: incremental saves are not compressed (see compress flag above)
    // to improve save time in the case where in incremental save is followed by
    // a full save (which ultimately it always should be).  With a compressed
    // incremental save, the data would be compressed twice and decompressed once
    // on every save vs. just compressing once.

    // make sure existing header matches
    if (!fread(&_header, PtexIO::HeaderSize, 1, fp) || _header.magic != Magic) {
    std::stringstream str;
    str << "Not a ptex file: " << path;
    setError(str.str());
    return;
    }

    bool headerMatch = (mt == _header.meshtype &&
            dt == _header.datatype &&
            nchannels == _header.nchannels &&
            alphachan == int(_header.alphachan) &&
            nfaces == int(_header.nfaces));
    if (!headerMatch) {
    std::stringstream str;
    str << "PtexWriter::edit error: header doesn't match existing file, "
        << "conversions not currently supported";
    setError(str.str());
    return;
    }

    // read extended header
    memset(&_extheader, 0, sizeof(_extheader));
    if (!fread(&_extheader, PtexUtils::min(uint32_t(ExtHeaderSize), _header.extheadersize), 1, fp)) {
    std::stringstream str;
    str << "Error reading extended header: " << path;
    setError(str.str());
    return;
    }

    // seek to end of file to append
    fseeko(_fp, 0, SEEK_END);
}


PtexIncrWriter::~PtexIncrWriter()
{
}


bool PtexIncrWriter::writeFace(int faceid, const FaceInfo& f, const void* data, int stride)
{
    if (stride == 0) stride = f.res.u()*_pixelSize;

    // handle constant case
    if (PtexUtils::isConstant(data, stride, f.res.u(), f.res.v(), _pixelSize))
    return writeConstantFace(faceid, f, data);

    // init headers
    uint8_t edittype = et_editfacedata;
    uint32_t editsize;
    EditFaceDataHeader efdh;
    efdh.faceid = faceid;

    // check and store face info
    if (!storeFaceInfo(faceid, efdh.faceinfo, f))
    return 0;

    // record position and skip headers
    FilePos pos = ftello(_fp);
    writeBlank(_fp, sizeof(edittype) + sizeof(editsize) + sizeof(efdh));

    // must compute constant (average) val first
    uint8_t* constval = (uint8_t*) malloc(_pixelSize);

    if (_header.hasAlpha()) {
    // must premult alpha before averaging
    // first copy to temp buffer
    int rowlen = f.res.u() * _pixelSize, nrows = f.res.v();
    uint8_t* temp = (uint8_t*) malloc(rowlen * nrows);
    PtexUtils::copy(data, stride, temp, rowlen, nrows, rowlen);

    // multiply alpha
    PtexUtils::multalpha(temp, f.res.size(), _header.datatype, _header.nchannels,
                 _header.alphachan);
    // average
    PtexUtils::average(temp, rowlen, f.res.u(), f.res.v(), constval,
               _header.datatype, _header.nchannels);
    // unmult alpha
    PtexUtils::divalpha(constval, 1, _header.datatype, _header.nchannels,
                _header.alphachan);
    free(temp);
    }
    else {
    // average
    PtexUtils::average(data, stride, f.res.u(), f.res.v(), constval,
               _header.datatype, _header.nchannels);
    }
    // write const val
    writeBlock(_fp, constval, _pixelSize);
    free(constval);

    // write face data
    writeFaceData(_fp, data, stride, f.res, efdh.fdh);

    // update editsize in header
    editsize = sizeof(efdh) + _pixelSize + efdh.fdh.blocksize();

    // rewind and write headers
    fseeko(_fp, pos, SEEK_SET);
    writeBlock(_fp, &edittype, sizeof(edittype));
    writeBlock(_fp, &editsize, sizeof(editsize));
    writeBlock(_fp, &efdh, sizeof(efdh));
    fseeko(_fp, 0, SEEK_END);
    return 1;
}


bool PtexIncrWriter::writeConstantFace(int faceid, const FaceInfo& f, const void* data)
{
    // init headers
    uint8_t edittype = et_editfacedata;
    uint32_t editsize;
    EditFaceDataHeader efdh;
    efdh.faceid = faceid;
    efdh.fdh.set(0, enc_constant);
    editsize = sizeof(efdh) + _pixelSize;

    // check and store face info
    if (!storeFaceInfo(faceid, efdh.faceinfo, f, FaceInfo::flag_constant))
    return 0;

    // write headers
    writeBlock(_fp, &edittype, sizeof(edittype));
    writeBlock(_fp, &editsize, sizeof(editsize));
    writeBlock(_fp, &efdh, sizeof(efdh));
    // write data
    writeBlock(_fp, data, _pixelSize);
    return 1;
}


void PtexIncrWriter::writeMetaDataEdit()
{
    // init headers
    uint8_t edittype = et_editmetadata;
    uint32_t editsize;
    EditMetaDataHeader emdh;
    emdh.metadatazipsize = 0;
    emdh.metadatamemsize = 0;

    // record position and skip headers
    FilePos pos = ftello(_fp);
    writeBlank(_fp, sizeof(edittype) + sizeof(editsize) + sizeof(emdh));

    // write meta data
    for (int i = 0, n = _metadata.size(); i < n; i++) {
    MetaEntry& e = _metadata[i];
    emdh.metadatamemsize += writeMetaDataBlock(_fp, e);
    }
    // finish zip block
    emdh.metadatazipsize = writeZipBlock(_fp, 0, 0, /*finish*/ true);

    // update headers
    editsize = sizeof(emdh) + emdh.metadatazipsize;

    // rewind and write headers
    fseeko(_fp, pos, SEEK_SET);
    writeBlock(_fp, &edittype, sizeof(edittype));
    writeBlock(_fp, &editsize, sizeof(editsize));
    writeBlock(_fp, &emdh, sizeof(emdh));
    fseeko(_fp, 0, SEEK_END);
}


bool PtexIncrWriter::close(Ptex::String& error)
{
    // closing base writer will write all pending data via finish() method
    bool result = PtexWriterBase::close(error);
    if (_fp) {
    fclose(_fp);
    _fp = 0;
    }
    return result;
}


void PtexIncrWriter::finish()
{
    // write meta data edit block (if any)
    if (!_metadata.empty()) writeMetaDataEdit();

    // rewrite extheader for updated editdatasize
    if (_extheader.editdatapos) {
    _extheader.editdatasize = uint64_t(ftello(_fp)) - _extheader.editdatapos;
    fseeko(_fp, HeaderSize, SEEK_SET);
    fwrite(&_extheader, PtexUtils::min(uint32_t(ExtHeaderSize), _header.extheadersize), 1, _fp);
    }
}