diff options
56 files changed, 11303 insertions, 95 deletions
diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 35271d24a2d..83febd9d5ca 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -45,3 +45,5 @@ ENDIF(WITH_LZO) IF(WITH_LZMA) ADD_SUBDIRECTORY(lzma) ENDIF(WITH_LZMA) + +ADD_SUBDIRECTORY(ptex)
\ No newline at end of file diff --git a/extern/ptex/CMakeLists.txt b/extern/ptex/CMakeLists.txt new file mode 100644 index 00000000000..499046dc41f --- /dev/null +++ b/extern/ptex/CMakeLists.txt @@ -0,0 +1,18 @@ +SET(INC src .) + +SET(SRC + src/ptex/PtexCache.cpp + src/ptex/PtexFilters.cpp + src/ptex/PtexHalf.cpp + src/ptex/PtexReader.cpp + src/ptex/PtexSeparableFilter.cpp + src/ptex/PtexSeparableKernel.cpp + src/ptex/PtexTriangleFilter.cpp + src/ptex/PtexTriangleKernel.cpp + src/ptex/PtexUtils.cpp + src/ptex/PtexWriter.cpp + ptex_C_api.cpp +) + +BLENDERLIB(extern_ptex "${SRC}" "${INC}") + diff --git a/extern/ptex/ptex.h b/extern/ptex/ptex.h new file mode 100644 index 00000000000..28d95c299b4 --- /dev/null +++ b/extern/ptex/ptex.h @@ -0,0 +1,45 @@ +#ifndef PTEX_H +#define PTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef void* PtexTextureHandle; +typedef void* PtexFaceInfoHandle; +typedef void* PtexResHandle; + +typedef enum { + PTEX_DT_UINT8, + PTEX_DT_UINT16, + PTEX_DT_FLOAT, + PTEX_DT_UNSUPPORTED +} PtexDataType; + +/* PtexTexture class */ +extern PtexTextureHandle ptex_open(const char *path, int print_error, int premultiply); +extern void ptex_texture_release(PtexTextureHandle ptex_texture_handle); +extern PtexDataType ptex_texture_data_type(PtexTextureHandle ptex_texture_handle); +extern int ptex_texture_num_channels(PtexTextureHandle ptex_texture_handle); +extern PtexFaceInfoHandle ptex_texture_get_face_info(PtexTextureHandle ptex_texture_handle, int faceid); +extern void ptex_texture_get_data(PtexTextureHandle ptex_texture_handle, int faceid, void *buffer, int stride, PtexResHandle res_handle); +extern void ptex_texture_get_pixel(PtexTextureHandle ptex_texture_handle, int faceid, int u, int v, float *result, int firstchan, int nchannels, PtexResHandle res_handle); + +/* FaceInfo struct */ +extern PtexResHandle ptex_face_get_res(PtexFaceInfoHandle face_info_handle); +extern int ptex_face_info_is_subface(PtexFaceInfoHandle face_info_handle); + +/* Res struct */ +extern int ptex_res_u(PtexResHandle ptex_res_handle); +extern int ptex_res_v(PtexResHandle ptex_res_handle); + +/* Utils */ +int ptex_data_size(PtexDataType type); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extern/ptex/ptex_C_api.cpp b/extern/ptex/ptex_C_api.cpp new file mode 100644 index 00000000000..2a9fd7fed7e --- /dev/null +++ b/extern/ptex/ptex_C_api.cpp @@ -0,0 +1,100 @@ +#include "ptex.h" +#include "src/ptex/Ptexture.h" +#include <iostream> + +/**** PtexTexture class ****/ +PtexTextureHandle ptex_open(const char *path, int print_error, int premultiply) +{ + PtexTextureHandle ptex_texture_handle; + + Ptex::String error_string; + + ptex_texture_handle = (PtexTextureHandle)PtexTexture::open(path, error_string, premultiply); + + if(!ptex_texture_handle && print_error) + std::cout << "Ptex error: " << error_string << std::endl; + + return ptex_texture_handle; +} + +void ptex_texture_release(PtexTextureHandle ptex_texture_handle) +{ + ((PtexTexture*)ptex_texture_handle)->release(); +} + +PtexDataType ptex_texture_data_type(PtexTextureHandle ptex_texture_handle) +{ + Ptex::DataType type = ((PtexTexture*)ptex_texture_handle)->dataType(); + + switch(type) { + case Ptex::dt_uint8: + return PTEX_DT_UINT8; + case Ptex::dt_uint16: + return PTEX_DT_UINT16; + case Ptex::dt_float: + return PTEX_DT_FLOAT; + default: + return PTEX_DT_UNSUPPORTED; + } +} + +int ptex_texture_num_channels(PtexTextureHandle ptex_texture_handle) +{ + return ((PtexTexture*)ptex_texture_handle)->numChannels(); +} + +PtexFaceInfoHandle ptex_texture_get_face_info(PtexTextureHandle ptex_texture_handle, int faceid) +{ + return (PtexFaceInfoHandle)(&((PtexTexture*)ptex_texture_handle)->getFaceInfo(faceid)); +} + +void ptex_texture_get_data(PtexTextureHandle ptex_texture_handle, int faceid, void *buffer, int stride, PtexResHandle res_handle) +{ + ((PtexTexture*)ptex_texture_handle)->getData(faceid, buffer, stride, *((Ptex::Res*)res_handle)); +} + +void ptex_texture_get_pixel(PtexTextureHandle ptex_texture_handle, int faceid, int u, int v, float *result, int firstchan, int nchannels, PtexResHandle res_handle) +{ + ((PtexTexture*)ptex_texture_handle)->getPixel(faceid, u, v, result, firstchan, nchannels, *((Ptex::Res*)res_handle)); +} + + + +/**** FaceInfo struct ****/ +PtexResHandle ptex_face_get_res(PtexFaceInfoHandle face_info_handle) +{ + return (PtexResHandle)(&((Ptex::FaceInfo*)face_info_handle)->res); +} + +int ptex_face_info_is_subface(PtexFaceInfoHandle face_info_handle) +{ + return ((Ptex::FaceInfo*)face_info_handle)->isSubface(); +} + + + +/**** Res struct ****/ +int ptex_res_u(PtexResHandle ptex_res_handle) +{ + return ((Ptex::Res*)ptex_res_handle)->u(); +} + +int ptex_res_v(PtexResHandle ptex_res_handle) +{ + return ((Ptex::Res*)ptex_res_handle)->v(); +} + +/**** Utils ****/ +int ptex_data_size(PtexDataType type) +{ + switch(type) { + case PTEX_DT_UINT8: + return 1; + case PTEX_DT_UINT16: + return 2; + case PTEX_DT_FLOAT: + return 4; + default: + return 0; + } +} diff --git a/extern/ptex/src/Makefile b/extern/ptex/src/Makefile new file mode 100644 index 00000000000..7d6de6565c1 --- /dev/null +++ b/extern/ptex/src/Makefile @@ -0,0 +1,13 @@ +SUBDIRS = ptex utils tests + +.PHONY: subdirs $(SUBDIRS) + +subdirs: $(SUBDIRS) + +clean: + for s in $(SUBDIRS); do \ + $(MAKE) -C $$s clean; \ + done + +$(SUBDIRS): + $(MAKE) -C $@ diff --git a/extern/ptex/src/ptex/Makefile b/extern/ptex/src/ptex/Makefile new file mode 100644 index 00000000000..97a2cde67a6 --- /dev/null +++ b/extern/ptex/src/ptex/Makefile @@ -0,0 +1,89 @@ +# use compiler from CXX env var if present, otherwise default to g++ +ifndef CXX +CXX = g++ +LINK = g++ +else +LINK = $(CXX) +endif + +# DEBUG = -g -DDEBUG +DEBUG = -O2 -DNDEBUG +INCPATH = -I. +DEFINES = + +ifdef PRMAN_15_COMPATIBLE_PTEX +DEFINES += -DPTEX_NO_LARGE_METADATA_BLOCKS +endif + +ifdef PTEX_STATIC +DEFINES += -DPTEX_STATIC +endif + +CXXFLAGS = -Wall -pedantic -W -std=c++98 $(DEBUG) $(INCPATH) $(DEFINES) -fPIC +LFLAGS = +LIBS = -lm -lz -lpthread + +SRCS = \ + PtexCache.cpp \ + PtexFilters.cpp \ + PtexHalf.cpp \ + PtexReader.cpp \ + PtexSeparableFilter.cpp \ + PtexSeparableKernel.cpp \ + PtexTriangleFilter.cpp \ + PtexTriangleKernel.cpp \ + PtexUtils.cpp \ + PtexWriter.cpp + +OBJECTS = $(patsubst %.cpp,%.o,$(SRCS)) + +INSTALLDIR = ../../install + +INSTALL = \ + lib/libPtex.a \ + include/Ptexture.h \ + include/PtexHalf.h \ + include/PtexInt.h \ + include/PtexUtils.h + +ALL = libPtex.a + +ifndef PTEX_STATIC +ALL += libPtex.so +INSTALL += lib/libPtex.so +endif + +$(INSTALLDIR)/lib/% : % + @ mkdir -p $(@D) + cp $^ $@ + +$(INSTALLDIR)/bin/% : % + @ mkdir -p $(@D) + cp $^ $@ + +$(INSTALLDIR)/include/% : % + @ mkdir -p $(@D) + cp $^ $@ + +install: all $(patsubst %,$(INSTALLDIR)/%,$(INSTALL)) + +all: $(ALL) + +clean: + rm -f *.o $(ALL) + +ifdef PTEX_STATIC +libPtex.a : libPtex.a($(OBJECTS)) + ar -r $@ + +else +libPtex.a : libPtex.a($(OBJECTS)) + +libPtex.so: $(OBJECTS) + $(LINK) $(LFLAGS) -shared -o $@ $(OBJECTS) $(LIBS) +endif + +depend: + $(CXX) -MM $(SRCS) > Makefile.deps + +include Makefile.deps diff --git a/extern/ptex/src/ptex/Makefile.deps b/extern/ptex/src/ptex/Makefile.deps new file mode 100644 index 00000000000..2dbae12219c --- /dev/null +++ b/extern/ptex/src/ptex/Makefile.deps @@ -0,0 +1,25 @@ +PtexCache.o: PtexCache.cpp PtexPlatform.h Ptexture.h PtexInt.h \ + PtexReader.h PtexIO.h PtexCache.h PtexMutex.h PtexDict.h PtexUtils.h \ + PtexHashMap.h +PtexFilters.o: PtexFilters.cpp PtexPlatform.h Ptexture.h PtexInt.h \ + PtexSeparableFilter.h PtexSeparableKernel.h PtexUtils.h \ + PtexTriangleFilter.h +PtexHalf.o: PtexHalf.cpp PtexHalf.h PtexInt.h +PtexReader.o: PtexReader.cpp PtexPlatform.h Ptexture.h PtexInt.h \ + PtexUtils.h PtexReader.h PtexIO.h PtexCache.h PtexMutex.h PtexDict.h \ + PtexHashMap.h +PtexSeparableFilter.o: PtexSeparableFilter.cpp PtexPlatform.h \ + PtexSeparableFilter.h Ptexture.h PtexInt.h PtexSeparableKernel.h \ + PtexUtils.h +PtexSeparableKernel.o: PtexSeparableKernel.cpp PtexPlatform.h PtexUtils.h \ + Ptexture.h PtexInt.h PtexHalf.h PtexSeparableKernel.h +PtexTriangleFilter.o: PtexTriangleFilter.cpp PtexPlatform.h \ + PtexTriangleFilter.h Ptexture.h PtexInt.h PtexTriangleKernel.h \ + PtexUtils.h +PtexTriangleKernel.o: PtexTriangleKernel.cpp PtexPlatform.h PtexUtils.h \ + Ptexture.h PtexInt.h PtexHalf.h PtexTriangleKernel.h +PtexUtils.o: PtexUtils.cpp PtexPlatform.h PtexHalf.h PtexInt.h \ + PtexUtils.h Ptexture.h +PtexWriter.o: PtexWriter.cpp PtexPlatform.h Ptexture.h PtexInt.h \ + PtexUtils.h PtexWriter.h PtexIO.h PtexReader.h PtexCache.h PtexMutex.h \ + PtexDict.h PtexHashMap.h diff --git a/extern/ptex/src/ptex/PtexCache.cpp b/extern/ptex/src/ptex/PtexCache.cpp new file mode 100644 index 00000000000..218ce9181a5 --- /dev/null +++ b/extern/ptex/src/ptex/PtexCache.cpp @@ -0,0 +1,419 @@ +/* +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. +*/ + +/** + @file PtexCache.cpp + @brief LRU Cache Implementation + + <b> Ownership.</b> + The cache owns all files and data. If an object is in use, the + cache will not delete it. When it is no longer in use, it may be + kept or may be deleted to keep resource usage under the set limits. + Deletions are done in lru order. + + <b> Resource Tracking.</b> + All objects are created as part of the cache and have a ptr back to + the cache. Each object updates the cache's resource total when it is + created or deleted. Unused objects are kept in an lru list in the + cache. Only objects in the lru list can be deleted. + + <b> Reference Counting.</b> + Every object has a ref count to track whether it is being used. + But objects don't generally ref their parent or children (otherwise + nothing would get freed). + + A data handle must hold onto (and ref) all the objects it is using + or may need in the future. E.g. For a non-tiled face, this is just + the single face data block. For a tiled face, the file, the tiled + face, and the current tile must all be ref'd. + + <b> Parents, Children, and Orphans.</b> + Every object must be reachable by some other object, generally the + object that created it, i.e. it's parent. Even though the parent + doesn't own it's children (the cache does), it must still track + them. Parentless objects (i.e. orphans) are not reachable and are + not retained in the cache. + + When any object is deleted (file, tiled face, etc.), it must orphan + its children. If an orphaned child is not in use, then it is + immediately deleted. Otherwise, the child's parent ptr is set to + null and the child is deleted when it is no longer in use. A + parent may also orphan a child that it no longer needs; the + behavior is the same. + + Each object stores a ptr to its own entry within its parent. When + the object is deleted by the cache, it clears this pointer so that + the parent no longer sees it. + + <b> Cache LifeTime.</b> + When a cache is released from its owner, it will delete itself but + only after all objects it owns are no longer in use. To do this, a + ref count on the cache is used. The owner holds 1 ref (only one + owner allowed), and each object holds a ref (maintained internally). + + <b> Threading.</b> + To fully support multi-threading, the following data structures + must be protected with a mutex: the cache lru lists, ref counts, + and parent/child ptrs. This is done with a single mutex per cache. + To avoid the need for recursive locks and to minimize the number of + lock points, this mutex is locked and unlocked primarily at the + external api boundary for methods that affect the cache state: + (e.g. getMetaData, getData, getTile, release, purge, and purgeAll). + Care must be taken to release the cache lock when calling any external + api from within the library. + + Also, in order to prevent thread starvation, the cache lock is + released during file reads and significant computation such as + generating an image data reduction. Additional mutexes are used to + prevent contention in these cases: + - 1 mutex per cache to prevent concurrent file opens + - 1 mutex per file to prevent concurrent file reads + - 1 mutex per file to prevent concurrent (and possibly redundant) + reductions. + */ + +#include "PtexPlatform.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <iostream> +#include <ctype.h> +#include "Ptexture.h" +#include "PtexReader.h" +#include "PtexCache.h" + +#ifdef GATHER_STATS +namespace PtexInternal { + CacheStats::~CacheStats() { + if (getenv("PTEX_STATS")) + print(); + } + + void CacheStats::print() + { + if (nfilesOpened || ndataAllocated || nblocksRead) { + printf("Ptex Stats:\n"); + printf(" nfilesOpened: %6d\n", nfilesOpened); + printf(" nfilesClosed: %6d\n", nfilesClosed); + printf(" ndataAllocated: %6d\n", ndataAllocated); + printf(" ndataFreed: %6d\n", ndataFreed); + printf(" nblocksRead: %6d\n", nblocksRead); + printf(" nseeks: %6d\n", nseeks); + if (nblocksRead) + printf(" avgReadSize: %6d\n", int(nbytesRead/nblocksRead)); + if (nseeks) + printf(" avgSeqReadSize: %6d\n", int(nbytesRead/nseeks)); + printf(" MbytesRead: %6.2f\n", nbytesRead/(1024.0*1024.0)); + } + } + + CacheStats stats; +} +#endif + +PtexCacheImpl::~PtexCacheImpl() +{ + // explicitly pop all unused items so that they are + // destroyed while cache is still valid + AutoLockCache locker(cachelock); + while (_unusedData.pop()); + while (_unusedFiles.pop()); +} + +void PtexCacheImpl::setFileInUse(PtexLruItem* file) +{ + assert(cachelock.locked()); + _unusedFiles.extract(file); + _unusedFileCount--; +} + +void PtexCacheImpl::setFileUnused(PtexLruItem* file) +{ + assert(cachelock.locked()); + _unusedFiles.push(file); + _unusedFileCount++; +} + +void PtexCacheImpl::removeFile() +{ + // cachelock should be locked, but might not be if cache is being deleted + _unusedFileCount--; + STATS_INC(nfilesClosed); +} + +void PtexCacheImpl::setDataInUse(PtexLruItem* data, int size) +{ + assert(cachelock.locked()); + _unusedData.extract(data); + _unusedDataCount--; + _unusedDataSize -= size; +} + +void PtexCacheImpl::setDataUnused(PtexLruItem* data, int size) +{ + assert(cachelock.locked()); + _unusedData.push(data); + _unusedDataCount++; + _unusedDataSize += size; +} + +void PtexCacheImpl::removeData(int size) { + // cachelock should be locked, but might not be if cache is being deleted + _unusedDataCount--; + _unusedDataSize -= size; + STATS_INC(ndataFreed); +} + + +/** Cache for reading Ptex texture files */ +class PtexReaderCache : public PtexCacheImpl +{ +public: + PtexReaderCache(int maxFiles, int maxMem, bool premultiply, PtexInputHandler* handler) + : PtexCacheImpl(maxFiles, maxMem), + _io(handler), _cleanupCount(0), _premultiply(premultiply) + {} + + ~PtexReaderCache() + { + // orphan all files since we're about to delete the file table + // and we don't want the base dtor to try to access it + purgeAll(); + } + + virtual void setSearchPath(const char* path) + { + // get the open lock since the path is used during open operations + AutoMutex locker(openlock); + + // record path + _searchpath = path ? path : ""; + + // split into dirs + _searchdirs.clear(); + char* buff = strdup(path); + char* pos = 0; + char* token = strtok_r(buff, ":", &pos); + while (token) { + if (token[0]) _searchdirs.push_back(token); + token = strtok_r(0, ":", &pos); + } + free(buff); + } + + virtual const char* getSearchPath() + { + // get the open lock since the path is used during open operations + AutoMutex locker(openlock); + return _searchpath.c_str(); + } + + virtual PtexTexture* get(const char* path, Ptex::String& error); + + virtual void purge(PtexTexture* texture) + { + PtexReader* reader = dynamic_cast<PtexReader*>(texture); + if (!reader) return; + purge(reader->path()); + } + + virtual void purge(const char* filename) + { + AutoLockCache locker(cachelock); + FileMap::iterator iter = _files.find(filename); + if (iter != _files.end()) { + PtexReader* reader = iter->second; + if (reader && intptr_t(reader) != -1) { + reader->orphan(); + iter->second = 0; + } + _files.erase(iter); + } + } + + virtual void purgeAll() + { + AutoLockCache locker(cachelock); + FileMap::iterator iter = _files.begin(); + while (iter != _files.end()) { + PtexReader* reader = iter->second; + if (reader && intptr_t(reader) != -1) { + reader->orphan(); + iter->second = 0; + } + iter = _files.erase(iter); + } + } + + + void removeBlankEntries() + { + // remove blank file entries to keep map size in check + for (FileMap::iterator i = _files.begin(); i != _files.end();) { + if (i->second == 0) i = _files.erase(i); + else i++; + } + } + + +private: + PtexInputHandler* _io; + std::string _searchpath; + std::vector<std::string> _searchdirs; + typedef PtexDict<PtexReader*> FileMap; + FileMap _files; + int _cleanupCount; + bool _premultiply; +}; + + +PtexTexture* PtexReaderCache::get(const char* filename, Ptex::String& error) +{ + AutoLockCache locker(cachelock); + + // lookup reader in map + PtexReader* reader = _files[filename]; + if (reader) { + // -1 means previous open attempt failed + if (intptr_t(reader) == -1) return 0; + reader->ref(); + return reader; + } + else { + bool ok = true; + + // get open lock and make sure we still need to open + // temporarily release cache lock while we open acquire open lock + cachelock.unlock(); + AutoMutex openlocker(openlock); + cachelock.lock(); + + // lookup entry again (it might have changed in another thread) + PtexReader** entry = &_files[filename]; + + if (*entry) { + // another thread opened it while we were waiting + if (intptr_t(*entry) == -1) return 0; + (*entry)->ref(); + return *entry; + } + + // make a new reader + reader = new PtexReader((void**)entry, this, _premultiply, _io); + + // temporarily release cache lock while we open the file + cachelock.unlock(); + std::string tmppath; + const char* pathToOpen = filename; + if (!_io) { + bool isAbsolute = (filename[0] == '/' +#ifdef WINDOWS + || filename[0] == '\\' + || (isalpha(filename[0]) && filename[1] == ':') +#endif + ); + if (!isAbsolute && !_searchdirs.empty()) { + // file is relative, search in searchpath + tmppath.reserve(256); // minimize reallocs (will grow automatically) + bool found = false; + struct stat statbuf; + for (size_t i = 0, size = _searchdirs.size(); i < size; i++) { + tmppath = _searchdirs[i]; + tmppath += "/"; + tmppath += filename; + if (stat(tmppath.c_str(), &statbuf) == 0) { + found = true; + pathToOpen = tmppath.c_str(); + break; + } + } + if (!found) { + std::string errstr = "Can't find ptex file: "; + errstr += filename; + error = errstr.c_str(); + ok = false; + } + } + } + if (ok) ok = reader->open(pathToOpen, error); + + // reacquire cache lock + cachelock.lock(); + + if (!ok) { + // open failed, clear parent ptr and unref to delete + *entry = reader; // to pass parent check in orphan() + reader->orphan(); + reader->unref(); + *entry = (PtexReader*)-1; // flag for future lookups + return 0; + } + + // successful open, record in _files map entry + *entry = reader; + + // clean up unused files + purgeFiles(); + + // Cleanup map every so often so it doesn't get HUGE + // from being filled with blank entries from dead files. + // Note: this must be done while we still have the open lock! + if (++_cleanupCount >= 1000) { + _cleanupCount = 0; + removeBlankEntries(); + } + } + return reader; +} + +PtexCache* PtexCache::create(int maxFiles, int maxMem, bool premultiply, + PtexInputHandler* handler) +{ + // set default files to 100 + if (maxFiles <= 0) maxFiles = 100; + + // set default memory to 100 MB + const int MB = 1024*1024; + if (maxMem <= 0) maxMem = 100 * MB; + + // if memory is < 1 MB, warn + if (maxMem < 1 * MB) { + std::cerr << "Warning, PtexCache created with < 1 MB" << std::endl; + } + + return new PtexReaderCache(maxFiles, maxMem, premultiply, handler); +} + + diff --git a/extern/ptex/src/ptex/PtexCache.h b/extern/ptex/src/ptex/PtexCache.h new file mode 100644 index 00000000000..a6ea316a8d1 --- /dev/null +++ b/extern/ptex/src/ptex/PtexCache.h @@ -0,0 +1,300 @@ +#ifndef PtexCache_h +#define PtexCache_h + +/* +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 <assert.h> + +#include "PtexMutex.h" +#include "Ptexture.h" +#include "PtexDict.h" + +#define USE_SPIN // use spinlocks instead of mutex for main cache lock + +namespace PtexInternal { + +#ifdef USE_SPIN + typedef SpinLock CacheLock; +#else + typedef Mutex CacheLock; +#endif + typedef AutoLock<CacheLock> AutoLockCache; + +#ifndef NDEBUG +#define GATHER_STATS +#endif + +#ifdef GATHER_STATS + struct CacheStats{ + int nfilesOpened; + int nfilesClosed; + int ndataAllocated; + int ndataFreed; + int nblocksRead; + long int nbytesRead; + int nseeks; + + CacheStats() + : nfilesOpened(0), + nfilesClosed(0), + ndataAllocated(0), + ndataFreed(0), + nblocksRead(0), + nbytesRead(0), + nseeks(0) {} + + ~CacheStats(); + void print(); + static void inc(int& val) { + static SpinLock spinlock; + AutoSpin lock(spinlock); + val++; + } + static void add(long int& val, int inc) { + static SpinLock spinlock; + AutoSpin lock(spinlock); + val+=inc; + } + }; + extern CacheStats stats; +#define STATS_INC(x) stats.inc(stats.x); +#define STATS_ADD(x, y) stats.add(stats.x, y); +#else +#define STATS_INC(x) +#define STATS_ADD(x, y) +#endif +} +using namespace PtexInternal; + +/** One item in a cache, typically an open file or a block of memory */ +class PtexLruItem { +public: + bool inuse() { return _prev == 0; } + void orphan() + { + // parent no longer wants me + void** p = _parent; + _parent = 0; + assert(p && *p == this); + if (!inuse()) delete this; + *p = 0; + } + template <typename T> static void orphanList(T& list) + { + for (typename T::iterator i=list.begin(); i != list.end(); i++) { + PtexLruItem* obj = *i; + if (obj) { + assert(obj->_parent == (void**)&*i); + obj->orphan(); + } + } + } + +protected: + PtexLruItem(void** parent=0) + : _parent(parent), _prev(0), _next(0) {} + virtual ~PtexLruItem() + { + // detach from parent (if any) + if (_parent) { assert(*_parent == this); *_parent = 0; } + // unlink from lru list (if in list) + if (_prev) { + _prev->_next = _next; + _next->_prev = _prev; + } + } + +private: + friend class PtexLruList; // maintains prev/next, deletes + void** _parent; // pointer to this item within parent + PtexLruItem* _prev; // prev in lru list (0 if in-use) + PtexLruItem* _next; // next in lru list (0 if in-use) +}; + + + +/** A list of items kept in least-recently-used (LRU) order. + Only items not in use are kept in the list. */ +class PtexLruList { +public: + PtexLruList() { _end._prev = _end._next = &_end; } + ~PtexLruList() { while (pop()); } + + void extract(PtexLruItem* node) + { + // remove from list + node->_prev->_next = node->_next; + node->_next->_prev = node->_prev; + node->_next = node->_prev = 0; + } + + void push(PtexLruItem* node) + { + // delete node if orphaned + if (!node->_parent) delete node; + else { + // add to end of list + node->_next = &_end; + node->_prev = _end._prev; + _end._prev->_next = node; + _end._prev = node; + } + } + + bool pop() + { + if (_end._next == &_end) return 0; + delete _end._next; // item will unlink itself + return 1; + } + +private: + PtexLruItem _end; +}; + + +/** Ptex cache implementation. Maintains a file and memory cache + within set limits */ +class PtexCacheImpl : public PtexCache { +public: + PtexCacheImpl(int maxFiles, int maxMem) + : _pendingDelete(false), + _maxFiles(maxFiles), _unusedFileCount(0), + _maxDataSize(maxMem), + _unusedDataSize(0), _unusedDataCount(0) + { + /* Allow for a minimum number of data blocks so cache doesn't + thrash too much if there are any really big items in the + cache pushing over the limit. It's better to go over the + limit in this case and make sure there's room for at least + a modest number of objects in the cache. + */ + + // try to allow for at least 10 objects per file (up to 100 files) + _minDataCount = 10 * maxFiles; + // but no more than 1000 + if (_minDataCount > 1000) _minDataCount = 1000; + } + + virtual void release() { delete this; } + + Mutex openlock; + CacheLock cachelock; + + // internal use - only call from reader classes for deferred deletion + void setPendingDelete() { _pendingDelete = true; } + void handlePendingDelete() { if (_pendingDelete) delete this; } + + // internal use - only call from PtexCachedFile, PtexCachedData + static void addFile() { STATS_INC(nfilesOpened); } + void setFileInUse(PtexLruItem* file); + void setFileUnused(PtexLruItem* file); + void removeFile(); + static void addData() { STATS_INC(ndataAllocated); } + void setDataInUse(PtexLruItem* data, int size); + void setDataUnused(PtexLruItem* data, int size); + void removeData(int size); + + void purgeFiles() { + while (_unusedFileCount > _maxFiles) + { + if (!_unusedFiles.pop()) break; + // note: pop will destroy item and item destructor will + // call removeFile which will decrement _unusedFileCount + } + } + void purgeData() { + while ((_unusedDataSize > _maxDataSize) && + (_unusedDataCount > _minDataCount)) + { + if (!_unusedData.pop()) break; + // note: pop will destroy item and item destructor will + // call removeData which will decrement _unusedDataSize + // and _unusedDataCount + } + } + +protected: + ~PtexCacheImpl(); + +private: + bool _pendingDelete; // flag set if delete is pending + + int _maxFiles, _unusedFileCount; // file limit, current unused file count + long int _maxDataSize, _unusedDataSize; // data limit (bytes), current size + int _minDataCount, _unusedDataCount; // min, current # of unused data blocks + PtexLruList _unusedFiles, _unusedData; // lists of unused items +}; + + +/** Cache entry for open file handle */ +class PtexCachedFile : public PtexLruItem +{ +public: + PtexCachedFile(void** parent, PtexCacheImpl* cache) + : PtexLruItem(parent), _cache(cache), _refcount(1) + { _cache->addFile(); } + void ref() { assert(_cache->cachelock.locked()); if (!_refcount++) _cache->setFileInUse(this); } + void unref() { assert(_cache->cachelock.locked()); if (!--_refcount) _cache->setFileUnused(this); } +protected: + virtual ~PtexCachedFile() { _cache->removeFile(); } + PtexCacheImpl* _cache; +private: + int _refcount; +}; + + +/** Cache entry for allocated memory block */ +class PtexCachedData : public PtexLruItem +{ +public: + PtexCachedData(void** parent, PtexCacheImpl* cache, int size) + : PtexLruItem(parent), _cache(cache), _refcount(1), _size(size) + { _cache->addData(); } + void ref() { assert(_cache->cachelock.locked()); if (!_refcount++) _cache->setDataInUse(this, _size); } + void unref() { assert(_cache->cachelock.locked()); if (!--_refcount) _cache->setDataUnused(this, _size); } +protected: + void incSize(int size) { _size += size; } + virtual ~PtexCachedData() { _cache->removeData(_size); } + PtexCacheImpl* _cache; +private: + int _refcount; + int _size; +}; + + +#endif diff --git a/extern/ptex/src/ptex/PtexDict.h b/extern/ptex/src/ptex/PtexDict.h new file mode 100644 index 00000000000..a0ac85f85d4 --- /dev/null +++ b/extern/ptex/src/ptex/PtexDict.h @@ -0,0 +1,595 @@ +/* +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. +*/ +/** + @file PtexDict.h + @brief Contains PtexDict, a string-keyed hash table. +*/ + +#ifndef PtexDict_h +#define PtexDict_h + +/** + @class PtexDict + @brief A string-keyed dictionary template, using a hash table. + + <P> + An efficient string dictionary template. A hash table is used + that automatically doubles in size when it is more than 50% + full. It is hard-coded to use string keys for efficiency. + + <P> + The interface is compatible with std::hash_map<>, though not all methods + are provided. Methods provided: + + <UL> + <LI>iterator begin(); + <LI>const_iterator begin() const; + <LI>iterator end(); + <LI>const_iterator end() const; + <LI>T& operator[] (const char*); + <LI>iterator find(const char* key); + <LI>const_iterator find(const char* key) const; + <LI>bool erase(const char* key); + <LI>iterator erase(iterator); + <LI>void clear(); + <LI>int size() const; + </UL> + + <P> + Unlike std::hash_map<>, PtexDict doesn't have to construct a string + object to do a lookup. As a result, it is about 4-10 times faster + depending on the length of the keys. And when compiling + non-optimized, it is 6-10 times faster. + + <P> + We have decided NOT to follow the coding standard's naming comvention for + this class, since it needed to remain generic, and compatible with the + STL std::hash_map<> class. + + @author brentb + @author longson + + @version <B>1.0 brentb 11/01/2000:</B> Initial version + @version <B>1.1 longson 06/26/2001:</B> Added file and class comment headers + @version <B>2.0 longson 01/16/2002:</B> Updated to support most of the coding standards, except for the naming conventions. Added const_iterator to provide const-safe access. Fixed problem with iterators and not allowing modification. + +*/ + +/** + @struct PtexDict::value_type + @brief Internal class used to provide a return value for the value type + + All data members and member functions are declared public by design + Default Copy and Assignment operators are sufficient.. + + <P> + We have decided NOT to follow the coding standard's naming comvention for + this class, since it needed to remain compatible with the STL std::pair<> + class. + + @author brentb + @author longson + + @version <B>1.0 brentb 11/01/2000:</B> Initial version + @version <B>1.1 longson 06/26/2001:</B> Added file and class comment headers + +*/ + +template<class T> +class PtexDict +{ + class Entry; ///< forward declared private class + +public: // Public Types + class iterator; ///< forward declared class + class const_iterator; ///< forward declared class + friend class iterator; + friend class const_iterator; + + typedef const char* key_type; ///< This is the lookup type + typedef T mapped_type; ///< The data type stored + + struct value_type { + public: + /// Default constructor references default_value, with a 0 for first + value_type(): first(0), second() {} + /// Creation constructor + value_type(key_type f, const T& s): first(f), second(s) {} + + const key_type first; ///< Reference to the key type + T second; ///< Reference to the data + }; + +public: // Public Member Interfce + + /// Default contructor initializes the dictionary. + PtexDict() : _numEntries(0), _numBuckets(0), _bucketMask(0), _buckets(0) {} + /// clears the entries in the dictionary + virtual ~PtexDict() { clear(); } + + /// Locates an entry, creating a new one if necessary. + /** operator[] will look up an entry and return the value. A new entry + will be created (using the default ctor for T) if one doesn't exist. + */ + T& operator[](const char* key); + + /// Returns an iterator referencing the beginning of the table + iterator begin() + { + iterator iter; + iter._d = this; + for (iter._b = 0; iter._b < _numBuckets; iter._b++) { + iter._e = &_buckets[iter._b]; + if (*iter._e) return iter; + } + iter._e = 0; + return iter; + } + + /// Returns an iterator referencing the end of the table. + inline iterator end() { return iterator( 0, this, 0 ); } + + /// Const access to the beginning of the list + const_iterator begin() const + { + const_iterator iter; + iter._d = this; + for (iter._b = 0; iter._b < _numBuckets; iter._b++) { + iter._e = &_buckets[iter._b]; + if (*iter._e) return iter; + } + iter._e = 0; + return iter; + } + + /// Const access to the end of the list + inline const_iterator end() const { return const_iterator( 0, this, 0 ); } + + /// Locates an entry, without creating a new one. + /** find will locate an entry, but won't create a new one. The result is + returned as a pair of key and value. The returned key points to the + internal key string and will remain valid until the entry is deleted. + If the key is not found, the returned iterator will be equal to the + value returned by end(), and the iterator will be equal to false. O(1) + */ + iterator find(const char* key); + /// works the same as find above, but returns a constant iterator. + const_iterator find(const char* key) const; + + /// Will remove an entry. It will return TRUE if an entry was found. + bool erase(const char* key); + /// Removes the entry referenced by the iterator, from the dictionary. + /** It will return a iterator to the next element, or will equal the + return value of end() if there is nothing else to erase. O(1) */ + iterator erase(iterator iter); + + /// clear will remove all entries from the dictionary. O(n) + void clear(); + + /// Returns the number of entries in the dictionary. O(1) time to call. + int size() const { return _numEntries; } + +private: // Private Member Interface + + /// @brief This internal structure is used to store the dictionary elements + struct Entry { + public: // Public Member Interface + /// Default constructor initiaizes val with the defaul value + Entry() : _next(0), _hashval(0), _keylen(0), + _val(_key,T()), _pad(0) {} + private: // Private Member Interface + /// Copy constructor prohibited by design. + Entry(const Entry&); + /// Assignment operator prohibited by design. + Entry& operator=(const Entry&); + + public: + Entry* _next; ///< Pointer to the next element in the structure + int _hashval; ///< cached hashval of key + int _keylen; ///< cached length of key + value_type _val; ///< The stored value of the hash table + union { + int _pad; ///< for integer align of _key, for fast compares + char _key[1];///< 1 is dummy length - actual size will be allocated + }; + }; + + /// Copy constructor prohibited by design. + PtexDict(const PtexDict&); + /// Assignment operator prohibited by design. + PtexDict& operator=(const PtexDict&); + + /// Returns the integer hash index for the key and length of the key. + int hash(const char* key, int& keylen) const + { + // this is similar to perl's hash function + int hashval = 0; + const char* cp = key; + char c; + while ((c = *cp++)) hashval = hashval * 33 + c; + keylen = int(cp-key)-1; + return hashval; + } + + /// Returns a pointer to the desired entry, based on the key. + Entry** locate(const char* key, int& keylen, int& hashval) const + { + hashval = hash(key, keylen); + if (!_buckets) return 0; + for (Entry** e = &_buckets[hashval & _bucketMask]; *e; e=&(*e)->_next) + if ((*e)->_hashval == hashval && (*e)->_keylen == keylen && + streq(key, (*e)->_key, keylen)) + return e; + return 0; + } + + /// Used for string compares, much faster then strcmp + /** This is MUCH faster than strcmp and even memcmp, partly because + it is inline and partly because it can do 4 chars at a time + */ + static inline bool streq(const char* s1, const char* s2, int len) + { + // first make sure s1 is quad-aligned (s2 is always aligned) + if (((intptr_t)s1 & 3) == 0) { + int len4 = len >> 2; + while (len4--) { + if (*(int*)s1 != *(int*)s2) return 0; + s1 += 4; s2 += 4; + } + len &= 3; + } + while (len--) if (*s1++ != *s2++) return 0; + return 1; + } + + /// Used to increase the size of the table if necessary + void grow(); + +private: // Private Member data + + int _numEntries; ///< The number of entries in the dictionary + int _numBuckets; ///< The number of buckets in use + int _bucketMask; ///< The mask for the buckets + Entry** _buckets; ///< The pointer to the bucket structure +}; + + +/** + @class PtexDict::iterator + @brief Internal class used to provide iteration through the dictionary + + This works on non-const types, and provides type safe modification access + + @author brentb + @author longson + + @version <B>1.0 brentb 11/01/2000:</B> Initial version + @version <B>1.1 longson 06/26/2001:</B> Added file and class comment headers + @version <B>1.2 longson 01/16/2002:</B> Made const-safe with const_iterator + +*/ +template<class T> +class PtexDict<T>::iterator { +public: + /// Default Constructor + iterator() : _d(0), _e(0), _b(0) {} + + /// Proper copy constructor implementation + iterator(const iterator& iter) : + _d(iter._d), _e(iter._e), _b(iter._b) {} + /// Proper assignment operator + inline iterator& operator=(const iterator& iter) + { _e = iter._e; _d = iter._d; _b = iter._b; return *this; } + + /// Operator for obtaining the value that the iterator references + inline value_type& operator*() const { return getValue(); } + /// Pointer reference operator + inline value_type* operator->() const { return &getValue(); } + + /// For determining whether or not an iterator is valid + inline operator bool() { return _e != 0; } + + /// For comparing equality of iterators + inline bool operator==(const iterator& iter) const + { return iter._e == _e; } + /// For comparing inequality of iterators + inline bool operator!=(const iterator& iter) const + { return iter._e != _e; } + /// For comparing equality of iterators + inline bool operator==(const const_iterator& iter) const + { return iter._e == _e; } + /// For comparing inequality of iterators + inline bool operator!=(const const_iterator& iter) const + { return iter._e != _e; } + /// For advancing the iterator to the next element + iterator& operator++(int); + +private: // Private interface + + /// Constructor Helper for inline creation. + iterator( Entry** e, const PtexDict* d, int b): _d(d), _e(e), _b(b) {} + + /// simple helper function for retrieving the value from the Entry + inline value_type& getValue() const{ + if (_e) return (*_e)->_val; + else return _defaultVal; + } + + friend class PtexDict; + friend class const_iterator; + const PtexDict* _d; ///< dictionary back reference + Entry** _e; ///< pointer to entry in table this iterator refs + int _b; ///< bucket number this references + + static value_type _defaultVal; ///< Default value +}; + +// define the static type for the iterator +template<class T> typename PtexDict<T>::value_type PtexDict<T>::iterator::_defaultVal; + +/** + @class PtexDict::const_iterator + @brief Internal class used to provide iteration through the dictionary + + This works on const data types, and provides const safe access. + This class can also be created from a PtexDict::iterator class instance. + + @author longson + + @version <B>1.2 longson 01/16/2002:</B> Initial version based on iterator + +*/ +template<class T> +class PtexDict<T>::const_iterator { +public: + /// Default Constructor + const_iterator() : _d(0), _e(0), _b(0) {} + + /// Proper copy constructor implementation for const_iterator + const_iterator(const const_iterator& iter) : + _d(iter._d), _e(iter._e), _b(iter._b) {} + /// Conversion constructor for iterator + const_iterator(const iterator& iter) : + _d(iter._d), _e(iter._e), _b(iter._b) {} + /// Proper assignment operator for const_iterator + inline const_iterator& operator=(const const_iterator& iter) + { _e = iter._e; _d = iter._d; _b = iter._b; return *this; } + /// Proper assignment operator for iterator + inline const_iterator& operator=(iterator& iter) + { _e = iter._e; _d = iter._d; _b = iter._b; return *this; } + + /// Operator for obtaining the value that the const_iterator references + inline const value_type& operator*() const { return getValue(); } + /// Pointer reference operator + inline const value_type* operator->() const { return &getValue(); } + + /// For determining whether or not an iterator is valid + inline operator bool() { return _e != 0; } + + /// For comparing equality of iterators + inline bool operator==(const iterator& iter) const + { return iter._e == _e; } + /// For comparing inequality of iterators + inline bool operator!=(const iterator& iter) const + { return iter._e != _e; } + /// For comparing equality of const_iterators + inline bool operator==(const const_iterator& iter) const + { return iter._e == _e; } + /// For comparing inequality of iterators + inline bool operator!=(const const_iterator& iter) const + { return iter._e != _e; } + /// For advancing the iterator to the next element + const_iterator& operator++(int); + +private: // Private interface + + /// Constructor Helper for inline creation. + const_iterator( Entry** e, const PtexDict* d, int b): _d(d),_e(e),_b(b) {} + + /// simple helper function for retrieving the value from the Entry + inline const value_type& getValue() const{ + if (_e) return (*_e)->_val; + else return _defaultVal; + } + + friend class PtexDict; + friend class iterator; + const PtexDict* _d; ///< dictionary back reference + Entry** _e; ///< pointer to entry in table this iterator refs + int _b; ///< bucket number this references + + static value_type _defaultVal; ///< Default value +}; + +// define the static type for the iterator +template<class T> typename PtexDict<T>::value_type PtexDict<T>::const_iterator::_defaultVal; + +template<class T> +typename PtexDict<T>::iterator& PtexDict<T>::iterator::operator++(int) +{ + if (_e) { + // move to next entry + _e = &(*_e)->_next; + if (!*_e) { + // move to next non-empty bucket + for (_b++; _b < _d->_numBuckets; _b++) { + _e = &_d->_buckets[_b]; + if (*_e) return *this; + } + _e = 0; + } + } + return *this; +} + +template<class T> +typename PtexDict<T>::const_iterator& PtexDict<T>::const_iterator::operator++(int) +{ + if (_e) { + // move to next entry + _e = &(*_e)->_next; + if (!*_e) { + // move to next non-empty bucket + for (_b++; _b < _d->_numBuckets; _b++) { + _e = &_d->_buckets[_b]; + if (*_e) return *this; + } + _e = 0; + } + } + return *this; +} + +template<class T> +typename PtexDict<T>::iterator PtexDict<T>::find(const char* key) +{ + int keylen, hashval; + Entry** e = locate(key, keylen, hashval); + + /// return a valid iterator if we found an entry, else return end() + if (e) return iterator( e, this, hashval & _bucketMask ); + else return end(); +} + +template<class T> +typename PtexDict<T>::const_iterator PtexDict<T>::find(const char* key) const +{ + int keylen, hashval; + Entry** e = locate(key, keylen, hashval); + + /// return a valid iterator if we found an entry, else return end() + if (e) return const_iterator( e, this, hashval & _bucketMask ); + else return end(); +} + +template<class T> +T& PtexDict<T>::operator[](const char* key) +{ + int keylen, hashval; + Entry** e = locate(key, keylen, hashval); + if (e) return (*e)->_val.second; + + // create a new entry + _numEntries++; + if (_numEntries*2 >= _numBuckets) grow(); + + // allocate a buffer big enough to hold Entry + (the key length ) + // Note: the NULL character is already accounted for by Entry::_key's size + void* ebuf = malloc( sizeof(Entry) + (keylen) * sizeof(char) ); + Entry* ne = new(ebuf) Entry; // note: placement new + + // Store the values in the Entry structure + Entry** slot = &_buckets[hashval & _bucketMask]; + ne->_next = *slot; *slot = ne; + ne->_hashval = hashval; + ne->_keylen = keylen; + + // copy the string given into the new location + memcpy(ne->_key, key, keylen); + ne->_key[keylen] = '\0'; + return ne->_val.second; +} + + +template<class T> +void PtexDict<T>::grow() +{ + if (!_buckets) { + _numBuckets = 16; + _bucketMask = _numBuckets - 1; + _buckets = (Entry**) calloc(_numBuckets, sizeof(Entry*)); + } else { + int newsize = _numBuckets * 2; + _bucketMask = newsize - 1; + Entry** newbuckets = (Entry**) calloc(newsize, sizeof(Entry*)); + for (int i = 0; i < _numBuckets; i++) { + for (Entry* e = _buckets[i]; e;) { + Entry* _next = e->_next; + Entry** slot = &newbuckets[e->_hashval & _bucketMask]; + e->_next = *slot; *slot = e; + e = _next; + } + } + free(_buckets); + _buckets = newbuckets; + _numBuckets = newsize; + } +} + + +template<class T> +bool PtexDict<T>::erase(const char* key) +{ + iterator iter = find(key); + if (!iter) return false; + + erase(iter); + return true; // valid entry to remove +} + + +template<class T> +typename PtexDict<T>::iterator PtexDict<T>::erase(iterator iter) +{ + Entry** eptr = iter._e; + if (!eptr) return iter; + + // patch around deleted entry + Entry* e = *eptr; + Entry* next = e->_next; + if (!next) iter++; // advance iterator if at end of chain + *eptr = next; + + // destroy entry. This is a strange destroy but is necessary because of + // the way Entry() is allocated by using malloc above. + e->~Entry(); // note: explicit dtor call + free(e); // free memory allocated. + _numEntries--; + + return iter; +} + + +template<class T> +void PtexDict<T>::clear() +{ + for (iterator i=begin(); i != end(); i = erase(i)); + free(_buckets); + _buckets = 0; + _numEntries = 0; + _numBuckets = 0; +} + +#endif //PtexDict_h diff --git a/extern/ptex/src/ptex/PtexFilters.cpp b/extern/ptex/src/ptex/PtexFilters.cpp new file mode 100644 index 00000000000..2ab6526a3cf --- /dev/null +++ b/extern/ptex/src/ptex/PtexFilters.cpp @@ -0,0 +1,455 @@ +/* +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 "Ptexture.h" +#include "PtexSeparableFilter.h" +#include "PtexSeparableKernel.h" +#include "PtexTriangleFilter.h" + +namespace { + +/** Point-sampling filter for rectangular textures */ +class PtexPointFilter : public PtexFilter, public Ptex +{ + public: + PtexPointFilter(PtexTexture* tx) : _tx(tx) {} + virtual void release() { delete this; } + virtual void 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*/) + { + if (!_tx || nchannels <= 0) return; + if (faceid < 0 || faceid >= _tx->numFaces()) return; + const FaceInfo& f = _tx->getFaceInfo(faceid); + int resu = f.res.u(), resv = f.res.v(); + int ui = PtexUtils::clamp(int(u*resu), 0, resu-1); + int vi = PtexUtils::clamp(int(v*resv), 0, resv-1); + _tx->getPixel(faceid, ui, vi, result, firstchan, nchannels); + } + + private: + PtexTexture* _tx; +}; + + +/** Point-sampling filter for triangular textures */ +class PtexPointFilterTri : public PtexFilter, public Ptex +{ + public: + PtexPointFilterTri(PtexTexture* tx) : _tx(tx) {} + virtual void release() { delete this; } + virtual void 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*/) + { + if (!_tx || nchannels <= 0) return; + if (faceid < 0 || faceid >= _tx->numFaces()) return; + const FaceInfo& f = _tx->getFaceInfo(faceid); + int res = f.res.u(); + int resm1 = res - 1; + float ut = u * res, vt = v * res; + int ui = PtexUtils::clamp(int(ut), 0, resm1); + int vi = PtexUtils::clamp(int(vt), 0, resm1); + float uf = ut - ui, vf = vt - vi; + + if (uf + vf <= 1.0) { + // "even" triangles are stored in lower-left half-texture + _tx->getPixel(faceid, ui, vi, result, firstchan, nchannels); + } + else { + // "odd" triangles are stored in upper-right half-texture + _tx->getPixel(faceid, resm1-vi, resm1-ui, result, firstchan, nchannels); + } + } + + private: + PtexTexture* _tx; +}; + + +/** Separable filter with width=4 support. + + The kernel width is calculated as a multiple of 4 times the filter + width and the texture resolution is chosen such that each kernel + axis has between 4 and 8. + + For kernel widths too large to handle (because the kernel would + extend significantly beyond both sides of the face), a special + Hermite smoothstep is used to interpolate the two nearest 2 samples + along the affected axis (or axes). +*/ +class PtexWidth4Filter : public PtexSeparableFilter +{ + public: + typedef double KernelFn(double x, const double* c); + + PtexWidth4Filter(PtexTexture* tx, const PtexFilter::Options& opts, KernelFn k, const double* c = 0) + : PtexSeparableFilter(tx, opts), _k(k), _c(c) {} + + virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw, + Res faceRes) + { + buildKernelAxis(k.res.ulog2, k.u, k.uw, k.ku, u, uw, faceRes.ulog2); + buildKernelAxis(k.res.vlog2, k.v, k.vw, k.kv, v, vw, faceRes.vlog2); + } + + private: + + double blur(double x) + { + // 2-unit (x in -1..1) cubic hermite kernel + // this produces a blur roughly 1.5 times that of the 4-unit b-spline kernel + x = fabs(x); + return x < 1 ? (2*x-3)*x*x+1 : 0; + } + + void buildKernelAxis(int8_t& k_ureslog2, int& k_u, int& k_uw, double* ku, + float u, float uw, int f_ureslog2) + { + // build 1 axis (note: "u" labels may repesent either u or v axis) + + // clamp filter width to no smaller than a texel + uw = PtexUtils::max(uw, 1.0f/(1<<f_ureslog2)); + + // compute desired texture res based on filter width + k_ureslog2 = int(ceil(log2(1.0/uw))); + int resu = 1 << k_ureslog2; + double uwlo = 1.0/resu; // smallest filter width for this res + + // compute lerp weights (amount to blend towards next-lower res) + double lerp2 = _options.lerp ? (uw-uwlo)/uwlo : 0; + double lerp1 = 1-lerp2; + + // adjust for large filter widths + if (uw >= .25) { + if (uw < .5) { + k_ureslog2 = 2; + double upix = u * 4 - 0.5; + int u1 = int(ceil(upix - 2)), u2 = int(ceil(upix + 2)); + u1 = u1 & ~1; // round down to even pair + u2 = (u2 + 1) & ~1; // round up to even pair + k_u = u1; + k_uw = u2-u1; + double x1 = u1-upix; + for (int i = 0; i < k_uw; i+=2) { + double xa = x1 + i, xb = xa + 1, xc = (xa+xb)*0.25; + // spread the filter gradually to approach the next-lower-res width + // at uw = .5, s = 1.0; at uw = 1, s = 0.8 + double s = 1.0/(uw + .75); + double ka = _k(xa, _c), kb = _k(xb, _c), kc = blur(xc*s); + ku[i] = ka * lerp1 + kc * lerp2; + ku[i+1] = kb * lerp1 + kc * lerp2; + } + return; + } + else if (uw < 1) { + k_ureslog2 = 1; + double upix = u * 2 - 0.5; + k_u = int(floor(u - .5))*2; + k_uw = 4; + double x1 = k_u-upix; + for (int i = 0; i < k_uw; i+=2) { + double xa = x1 + i, xb = xa + 1, xc = (xa+xb)*0.5; + // spread the filter gradually to approach the next-lower-res width + // at uw = .5, s = .8; at uw = 1, s = 0.5 + double s = 1.0/(uw*1.5 + .5); + double ka = blur(xa*s), kb = blur(xb*s), kc = blur(xc*s); + ku[i] = ka * lerp1 + kc * lerp2; + ku[i+1] = kb * lerp1 + kc * lerp2; + } + return; + } + else { + // use res 0 (1 texel per face) w/ no lerping + // (future: use face-blended values for filter > 2) + k_ureslog2 = 0; + double upix = u - .5; + k_uw = 2; + double ui = floor(upix); + k_u = int(ui); + ku[0] = blur(upix-ui); + ku[1] = 1-ku[0]; + return; + } + } + + // convert from normalized coords to pixel coords + double upix = u * resu - 0.5; + double uwpix = uw * resu; + + // find integer pixel extent: [u,v] +/- [2*uw,2*vw] + // (kernel width is 4 times filter width) + double dupix = 2*uwpix; + int u1 = int(ceil(upix - dupix)), u2 = int(ceil(upix + dupix)); + + if (lerp2) { + // lerp kernel weights towards next-lower res + // extend kernel width to cover even pairs + u1 = u1 & ~1; + u2 = (u2 + 1) & ~1; + k_u = u1; + k_uw = u2-u1; + + // compute kernel weights + double step = 1.0/uwpix, x1 = (u1-upix)*step; + for (int i = 0; i < k_uw; i+=2) { + double xa = x1 + i*step, xb = xa + step, xc = (xa+xb)*0.5; + double ka = _k(xa, _c), kb = _k(xb, _c), kc = _k(xc, _c); + ku[i] = ka * lerp1 + kc * lerp2; + ku[i+1] = kb * lerp1 + kc * lerp2; + } + } + else { + k_u = u1; + k_uw = u2-u1; + // compute kernel weights + double x1 = (u1-upix)/uwpix, step = 1.0/uwpix; + for (int i = 0; i < k_uw; i++) ku[i] = _k(x1 + i*step, _c); + } + } + + KernelFn* _k; // kernel function + const double* _c; // kernel coefficients (if any) +}; + + +/** Separable bicubic filter */ +class PtexBicubicFilter : public PtexWidth4Filter +{ + public: + PtexBicubicFilter(PtexTexture* tx, const PtexFilter::Options& opts, float sharpness) + : PtexWidth4Filter(tx, opts, kernelFn, _coeffs) + { + // compute Cubic filter coefficients: + // abs(x) < 1: + // 1/6 * ((12 - 9*B - 6*C)*x^3 + (-18 + 12*B + 6*C)*x^2 + (6 - 2*B)) + // == c[0]*x^3 + c[1]*x^2 + c[2] + // abs(x) < 2: + // 1/6 * ((-B - 6*C)*x^3 + (6*B + 30*C)*x^2 + (-12*B - 48*C)*x + (8*B + 24*C)) + // == c[3]*x^3 + c[4]*x^2 + c[5]*x + c[6] + // else: 0 + + float B = 1 - sharpness; // choose C = (1-B)/2 + _coeffs[0] = 1.5 - B; + _coeffs[1] = 1.5 * B - 2.5; + _coeffs[2] = 1 - (1./3) * B; + _coeffs[3] = (1./3) * B - 0.5; + _coeffs[4] = 2.5 - 1.5 * B; + _coeffs[5] = 2 * B - 4; + _coeffs[6] = 2 - (2./3) * B; + } + + private: + static double kernelFn(double x, const double* c) + { + x = fabs(x); + if (x < 1) return (c[0]*x + c[1])*x*x + c[2]; + else if (x < 2) return ((c[3]*x + c[4])*x + c[5])*x + c[6]; + else return 0; + } + + double _coeffs[7]; // filter coefficients for current sharpness +}; + + + +/** Separable gaussian filter */ +class PtexGaussianFilter : public PtexWidth4Filter +{ + public: + PtexGaussianFilter(PtexTexture* tx, const PtexFilter::Options& opts) + : PtexWidth4Filter(tx, opts, kernelFn) {} + + private: + static double kernelFn(double x, const double*) + { + return exp(-2*x*x); + } +}; + + + +/** Rectangular box filter. + The box is convolved with the texels as area samples and thus the kernel function is + actually trapezoidally shaped. + */ +class PtexBoxFilter : public PtexSeparableFilter +{ + public: + PtexBoxFilter(PtexTexture* tx, const PtexFilter::Options& opts) + : PtexSeparableFilter(tx, opts) {} + + protected: + virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw, + Res faceRes) + { + // clamp filter width to no larger than 1.0 + uw = PtexUtils::min(uw, 1.0f); + vw = PtexUtils::min(vw, 1.0f); + + // clamp filter width to no smaller than a texel + uw = PtexUtils::max(uw, 1.0f/(faceRes.u())); + vw = PtexUtils::max(vw, 1.0f/(faceRes.v())); + + // compute desired texture res based on filter width + int ureslog2 = int(ceil(log2(1.0/uw))), + vreslog2 = int(ceil(log2(1.0/vw))); + Res res(ureslog2, vreslog2); + k.res = res; + + // convert from normalized coords to pixel coords + u = u * k.res.u(); + v = v * k.res.v(); + uw *= k.res.u(); + vw *= k.res.v(); + + // find integer pixel extent: [u,v] +/- [uw/2,vw/2] + // (box is 1 unit wide for a 1 unit filter period) + double u1 = u - 0.5*uw, u2 = u + 0.5*uw; + double v1 = v - 0.5*vw, v2 = v + 0.5*vw; + double u1floor = floor(u1), u2ceil = ceil(u2); + double v1floor = floor(v1), v2ceil = ceil(v2); + k.u = int(u1floor); + k.v = int(v1floor); + k.uw = int(u2ceil)-k.u; + k.vw = int(v2ceil)-k.v; + + // compute kernel weights along u and v directions + computeWeights(k.ku, k.uw, 1-(u1-u1floor), 1-(u2ceil-u2)); + computeWeights(k.kv, k.vw, 1-(v1-v1floor), 1-(v2ceil-v2)); + } + + private: + void computeWeights(double* kernel, int size, double f1, double f2) + { + assert(size >= 1 && size <= 3); + + if (size == 1) { + kernel[0] = f1 + f2 - 1; + } + else { + kernel[0] = f1; + for (int i = 1; i < size-1; i++) kernel[i] = 1.0; + kernel[size-1] = f2; + } + } +}; + + +/** Bilinear filter (for rectangular textures) */ +class PtexBilinearFilter : public PtexSeparableFilter +{ + public: + PtexBilinearFilter(PtexTexture* tx, const PtexFilter::Options& opts) + : PtexSeparableFilter(tx, opts) {} + + protected: + virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw, + Res faceRes) + { + // clamp filter width to no larger than 1.0 + uw = PtexUtils::min(uw, 1.0f); + vw = PtexUtils::min(vw, 1.0f); + + // clamp filter width to no smaller than a texel + uw = PtexUtils::max(uw, 1.0f/(faceRes.u())); + vw = PtexUtils::max(vw, 1.0f/(faceRes.v())); + + // choose resolution closest to filter res + // there are three choices of "closest" that come to mind: + // 1) closest in terms of filter width, i.e. period of signal + // 2) closest in terms of texel resolution, (1 / filter width), i.e. freq of signal + // 3) closest in terms of resolution level (log2(1/filter width)) + // Choice (1) probably makes the most sense. In log2 terms, that means you should + // use the next higher level when the fractional part of the log2 res is > log2(1/.75), + // and you should add 1-log2(1/.75) to round up. + const double roundWidth = 0.5849625007211563; // 1-log2(1/.75) + int ureslog2 = int(log2(1.0/uw) + roundWidth); + int vreslog2 = int(log2(1.0/vw) + roundWidth); + Res res(ureslog2, vreslog2); + k.res = res; + + // convert from normalized coords to pixel coords + double upix = u * k.res.u() - 0.5; + double vpix = v * k.res.v() - 0.5; + + float ufloor = floor(upix); + float vfloor = floor(vpix); + k.u = int(ufloor); + k.v = int(vfloor); + k.uw = 2; + k.vw = 2; + + // compute kernel weights + float ufrac = upix-ufloor, vfrac = vpix-vfloor; + k.ku[0] = 1 - ufrac; + k.ku[1] = ufrac; + k.kv[0] = 1 - vfrac; + k.kv[1] = vfrac; + } +}; + +} // end local namespace + + +PtexFilter* PtexFilter::getFilter(PtexTexture* tex, const PtexFilter::Options& opts) +{ + switch (tex->meshType()) { + case Ptex::mt_quad: + switch (opts.filter) { + case f_point: return new PtexPointFilter(tex); + case f_bilinear: return new PtexBilinearFilter(tex, opts); + default: + case f_box: return new PtexBoxFilter(tex, opts); + case f_gaussian: return new PtexGaussianFilter(tex, opts); + case f_bicubic: return new PtexBicubicFilter(tex, opts, opts.sharpness); + case f_bspline: return new PtexBicubicFilter(tex, opts, 0.0); + case f_catmullrom: return new PtexBicubicFilter(tex, opts, 1.0); + case f_mitchell: return new PtexBicubicFilter(tex, opts, 2.0/3.0); + } + break; + + case Ptex::mt_triangle: + switch (opts.filter) { + case f_point: return new PtexPointFilterTri(tex); + default: return new PtexTriangleFilter(tex, opts); + } + break; + } + return 0; +} diff --git a/extern/ptex/src/ptex/PtexHalf.cpp b/extern/ptex/src/ptex/PtexHalf.cpp new file mode 100644 index 00000000000..a8809715f9a --- /dev/null +++ b/extern/ptex/src/ptex/PtexHalf.cpp @@ -0,0 +1,105 @@ +/* +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 <math.h> +#include "PtexHalf.h" + +uint16_t PtexHalf::f2hTable[512]; +uint32_t PtexHalf::h2fTable[65536]; + +/** Table initializations. */ +static bool PtexHalfInit() +{ + union { int i; float f; } u; + + for (int h = 0; h < 65536; h++) { + int s = (h & 0x8000)<<16; + int m = h & 0x03ff; + int e = h&0x7c00; + + if (unsigned(e-1) < ((31<<10)-1)) { + // normal case + u.i = s|(((e+0x1c000)|m)<<13); + } + else if (e == 0) { + // denormalized + if (!(h&0x8000)) u.f = float(5.9604644775390625e-08*m); + else u.f = float(-5.9604644775390625e-08*m); + } + else { + // inf/nan, preserve low bits of m for nan code + u.i = s|0x7f800000|(m<<13); + } + PtexHalf::h2fTable[h] = u.i; + } + + for (int i = 0; i < 512; i++) { + int f = i << 23; + int e = (f & 0x7f800000) - 0x38000000; + // normalized iff (0 < e < 31) + if (unsigned(e-1) < ((31<<23)-1)) { + int s = ((f>>16) & 0x8000); + int m = f & 0x7fe000; + // add bit 12 to round + PtexHalf::f2hTable[i] = (s|((e|m)>>13))+((f>>12)&1); + } + } + + return 1; +} + +static bool PtexHalfInitialized = PtexHalfInit(); + + +/** Handle exceptional cases for half-to-float conversion */ +uint16_t PtexHalf::fromFloat_except(uint32_t i) +{ + uint32_t s = ((i>>16) & 0x8000); + int32_t e = ((i>>13) & 0x3fc00) - 0x1c000; + + if (e <= 0) { + // denormalized + union { uint32_t i; float f; } u; + u.i = i; + return s | int(fabs(u.f)*1.6777216e7 + .5); + } + + if (e == 0x23c00) + // inf/nan, preserve msb bits of m for nan code + return s|0x7c00|((i&0x7fffff)>>13); + else + // overflow - convert to inf + return s|0x7c00; +} diff --git a/extern/ptex/src/ptex/PtexHalf.h b/extern/ptex/src/ptex/PtexHalf.h new file mode 100644 index 00000000000..de59da3eac7 --- /dev/null +++ b/extern/ptex/src/ptex/PtexHalf.h @@ -0,0 +1,118 @@ +#ifndef PtexHalf_h +#define PtexHalf_h + +/* +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. +*/ + +/** + @file PtexHalf.h + @brief Half-precision floating-point type. +*/ + +#if defined(_WIN32) || defined(_WINDOWS) || defined(_MSC_VER) +# ifndef PTEX_STATIC +# ifdef PTEX_EXPORTS +# define PTEXAPI __declspec(dllexport) +# else +# define PTEXAPI __declspec(dllimport) +# endif +# else +# define PTEXAPI +# endif +#else +# define PTEXAPI +#endif + +#include "PtexInt.h" + +/** + @class PtexHalf + @brief Half-precision (16-bit) floating-point type. + + This type should be compatible with opengl, openexr, and IEEE 754r. + The range is [-65504.0, 65504.0] and the precision is about 1 part + in 2000 (3.3 decimal places). + + From OpenGL spec 2.1.2: + + A 16-bit floating-point number has a 1-bit sign (S), a 5-bit + exponent (E), and a 10-bit mantissa (M). The value of a 16-bit + floating-point number is determined by the following: + + \verbatim + (-1)^S * 0.0, if E == 0 and M == 0, + (-1)^S * 2^-14 * (M/2^10), if E == 0 and M != 0, + (-1)^S * 2^(E-15) * (1 + M/2^10), if 0 < E < 31, + (-1)^S * INF, if E == 31 and M == 0, or + NaN, if E == 31 and M != 0 \endverbatim +*/ + +struct PtexHalf { + uint16_t bits; + + /// Default constructor, value is undefined + PtexHalf() {} + PtexHalf(float val) : bits(fromFloat(val)) {} + PtexHalf(double val) : bits(fromFloat(float(val))) {} + operator float() const { return toFloat(bits); } + PtexHalf& operator=(float val) { bits = fromFloat(val); return *this; } + + static float toFloat(uint16_t h) + { + union { uint32_t i; float f; } u; + u.i = h2fTable[h]; + return u.f; + } + + static uint16_t fromFloat(float val) + { + if (val==0) return 0; + union { uint32_t i; float f; } u; + u.f = val; + int e = f2hTable[(u.i>>23)&0x1ff]; + if (e) return e + (((u.i&0x7fffff) + 0x1000) >> 13); + return fromFloat_except(u.i); + } + + private: + PTEXAPI static uint16_t fromFloat_except(uint32_t val); +#ifndef DOXYGEN + /* internal */ public: +#endif + PTEXAPI static uint32_t h2fTable[65536]; + PTEXAPI static uint16_t f2hTable[512]; +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexHashMap.h b/extern/ptex/src/ptex/PtexHashMap.h new file mode 100644 index 00000000000..6bd338a793b --- /dev/null +++ b/extern/ptex/src/ptex/PtexHashMap.h @@ -0,0 +1,352 @@ +/* +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. +*/ +/** + @file PtexHashMap.h + @brief Contains PtexHashMap, a lightweight hash table. +*/ + +#ifndef PtexHashMap_h +#define PtexHashMap_h + +/** + @class PtexHashMap + @brief A lightweight hash table. + + <P> + The hash table automatically doubles in size when it is more than 50% full. + + <P> + The interface is mostely compatible with std::hash_map<>, though not all methods + are provided. Methods provided: + + <UL> + <LI>iterator begin(); + <LI>iterator end(); + <LI>DataType& operator[] (KeyType key); + <LI>iterator find(KeyType key) const; + <LI>bool erase(KeyType key); + <LI>iterator erase(iterator); + <LI>void clear(); + <LI>int size() const; + </UL> + + @author brentb + + @version <B>1.0 brentb 11/01/2000:</B> Initial version + @version <B>1.1 longson 06/26/2001:</B> Added file and class comment headers + @version <B>1.2 brentb 105/2006:</B> Generalized IntDict into general hash map + +*/ + +/** + @class PtexHashMap::iterator + @brief Internal class used to provide iteration through the hash table + + @author brentb + + @version <B>1.0 brentb 11/01/2000:</B> Initial version + @version <B>1.1 longson 06/26/2001:</B> Added file and class comment headers + +*/ + +template<typename KeyType, typename DataType, typename HashFn> +class PtexHashMap +{ + struct Entry; // forward decl + +public: + typedef KeyType key_type; + typedef DataType data_type; + typedef HashFn hash_fn; + + struct value_type { + key_type first; + data_type second; + value_type() : first(), second() {} + value_type(const KeyType& first, const DataType& second) + : first(first), second(second) {} + }; + + class iterator { + public: + /// Default Constructor + iterator() : e(0), h(0), b(0) {} + + /// Proper copy constructor implementation + iterator(const iterator& iter) : e(iter.e), h(iter.h), b(iter.b) {} + + /// Operator for obtaining the value that the iterator references + value_type operator*() const { + if (e) return value_type((*e)->key, (*e)->val); + return value_type(); + } + + /// Pointer reference operator + value_type* operator->() const { + static value_type v; + v = operator*(); + return &v; + } + + /// Proper assignment operator + iterator& operator=(const iterator& iter) { + e = iter.e; h = iter.h; b = iter.b; return *this; + } + + /// For comparing equality of iterators + bool operator==(const iterator& iter) const { return iter.e == e; } + /// For comparing inequality of iterators + bool operator!=(const iterator& iter) const { return iter.e != e; } + /// For advancing the iterator to the next element + iterator& operator++(int); + + private: + friend class PtexHashMap; + Entry** e; // pointer to prev entry or hash entry + const PtexHashMap* h; + int b; // bucket number + }; + + /// Returns an iterator referencing the beginning of the table + iterator begin() const + { + iterator iter; + iter.h = this; + for (iter.b = 0; iter.b < _numBuckets; iter.b++) { + iter.e = &_buckets[iter.b]; + if (*iter.e) return iter; + } + iter.e = 0; + return iter; + } + + /// Returns an iteraot referencing the location just beyond the table. + iterator end() const + { + iterator iter; + iter.h = this; iter.b = 0; iter.e = 0; + return iter; + } + + + /// Default contructor initializes the hash table. + PtexHashMap() : _numEntries(0), _numBuckets(0), _bucketMask(0), _buckets(0) {} + /// Non-Virtual destructor by design, clears the entries in the hash table + ~PtexHashMap() { clear(); } + + /// Locates an entry, creating a new one if necessary. + /** operator[] will look up an entry and return the value. A new entry + will be created (using the default ctor for DataType) if one doesn't exist. + */ + DataType& operator[](const KeyType& key); + + /// Locates an entry, without creating a new one. + /** find will locate an entry, but won't create a new one. The result is + returned as a pair of key and value. The returned key points to the + internal key string and will remain valid until the entry is deleted. + If the key is not found, both the returned key and value pointers will + be NULL. + */ + iterator find(const KeyType& key) const; + + /// Will remove an entry. It will return TRUE if an entry was found. + bool erase(const KeyType& key); + /// Will remove an entry. It will return a iterator to the next element + iterator erase(iterator iter); + + /// clear will remove all entries and free the table + void clear(); + + /// number of entries in the hash + int size() const { return _numEntries; } + +private: + /// Copy constructor prohibited by design. + PtexHashMap(const PtexHashMap&); // disallow + /// Assignment operator prohibited by design. + bool operator=(const PtexHashMap&); // disallow + + friend class iterator; + + struct Entry { + Entry() : val() {} + Entry* next; + KeyType key; + DataType val; + }; + + /// Returns a pointer to the desired entry, based on the key. + Entry** locate(const KeyType& key) const + { + if (!_buckets) return 0; + for (Entry** e = &_buckets[hash(key) & _bucketMask]; *e; e = &(*e)->next) + if ((*e)->key == key) + return e; + return 0; + } + + /// Used to increase the size of the table if necessary + void grow(); + + int _numEntries; + int _numBuckets; + int _bucketMask; + Entry** _buckets; + HashFn hash; +}; + + +template<class KeyType, class DataType, class HashFn> +typename PtexHashMap<KeyType, DataType, HashFn>::iterator& +PtexHashMap<KeyType, DataType, HashFn>::iterator::operator++(int) +{ + if (e) { + // move to next entry + e = &(*e)->next; + if (!*e) { + // move to next non-empty bucket + for (b++; b < h->_numBuckets; b++) { + e = &h->_buckets[b]; + if (*e) return *this; + } + e = 0; + } + } + return *this; +} + + +template<class KeyType, class DataType, class HashFn> +typename PtexHashMap<KeyType, DataType, HashFn>::iterator +PtexHashMap<KeyType, DataType, HashFn>::find(const KeyType& key) const +{ + iterator iter; + Entry** e = locate(key); + if (e) { + iter.h = this; + iter.e = e; + iter.b = hash(key) & _bucketMask; + } else iter = end(); + return iter; +} + +template<class KeyType, class DataType, class HashFn> +DataType& PtexHashMap<KeyType, DataType, HashFn>::operator[](const KeyType& key) +{ + Entry** e = locate(key); + if (e) return (*e)->val; + + // create a new entry + _numEntries++; + if (_numEntries*2 >= _numBuckets) grow(); + void* ebuf = malloc(sizeof(Entry)); + Entry* ne = new(ebuf) Entry; // note: placement new + Entry** slot = &_buckets[hash(key) & _bucketMask]; + ne->next = *slot; *slot = ne; + ne->key = key; + return ne->val; +} + + +template<class KeyType, class DataType, class HashFn> +void PtexHashMap<KeyType, DataType, HashFn>::grow() +{ + if (!_buckets) { + _numBuckets = 16; + _bucketMask = _numBuckets - 1; + _buckets = (Entry**) calloc(_numBuckets, sizeof(Entry*)); + } else { + int newsize = _numBuckets * 2; + _bucketMask = newsize - 1; + Entry** newbuckets = (Entry**) calloc(newsize, sizeof(Entry*)); + for (int i = 0; i < _numBuckets; i++) { + for (Entry* e = _buckets[i]; e;) { + Entry* next = e->next; + Entry** slot = &newbuckets[hash(e->key) & _bucketMask]; + e->next = *slot; *slot = e; + e = next; + } + } + free(_buckets); + _buckets = newbuckets; + _numBuckets = newsize; + } +} + + +template<class KeyType, class DataType, class HashFn> +bool PtexHashMap<KeyType, DataType, HashFn>::erase(const KeyType& key) +{ + iterator iter = find(key); + if (iter == end()) return 0; + erase(iter); + return 1; +} + + +template<class KeyType, class DataType, class HashFn> +typename PtexHashMap<KeyType, DataType, HashFn>::iterator +PtexHashMap<KeyType, DataType, HashFn>::erase(iterator iter) +{ + Entry** eptr = iter.e; + if (!eptr) return iter; + + // patch around deleted entry + Entry* e = *eptr; + Entry* next = e->next; + if (!next) iter++; // advance iterator if at end of chain + *eptr = next; + + + // destroy entry + e->~Entry(); // note: explicit dtor call + free(e); + _numEntries--; + + return iter; +} + + +template<class KeyType, class DataType, class HashFn> +void PtexHashMap<KeyType, DataType, HashFn>::clear() +{ + for (iterator i = begin(); i != end(); i = erase(i)); + free(_buckets); + _buckets = 0; + _numEntries = 0; + _numBuckets = 0; +} + +#endif diff --git a/extern/ptex/src/ptex/PtexIO.h b/extern/ptex/src/ptex/PtexIO.h new file mode 100644 index 00000000000..0748d863737 --- /dev/null +++ b/extern/ptex/src/ptex/PtexIO.h @@ -0,0 +1,119 @@ +#ifndef PtexIO_h +#define PtexIO_h + +/* +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 "Ptexture.h" + +struct PtexIO : public Ptex { + struct Header { + uint32_t magic; + uint32_t version; + MeshType meshtype:32; + DataType datatype:32; + int32_t alphachan; + uint16_t nchannels; + uint16_t nlevels; + uint32_t nfaces; + uint32_t extheadersize; + uint32_t faceinfosize; + uint32_t constdatasize; + uint32_t levelinfosize; + uint32_t minorversion; + uint64_t leveldatasize; + uint32_t metadatazipsize; + uint32_t metadatamemsize; + int pixelSize() const { return DataSize(datatype) * nchannels; } + bool hasAlpha() const { return alphachan >= 0 && alphachan < nchannels; } + }; + struct ExtHeader { + BorderMode ubordermode:32; + BorderMode vbordermode:32; + uint32_t lmdheaderzipsize; + uint32_t lmdheadermemsize; + uint64_t lmddatasize; + uint64_t editdatasize; + uint64_t editdatapos; + }; + struct LevelInfo { + uint64_t leveldatasize; + uint32_t levelheadersize; + uint32_t nfaces; + LevelInfo() : leveldatasize(0), levelheadersize(0), nfaces(0) {} + }; + enum Encoding { enc_constant, enc_zipped, enc_diffzipped, enc_tiled }; + struct FaceDataHeader { + uint32_t data; // bits 0..29 = blocksize, bits 30..31 = encoding + uint32_t blocksize() const { return data & 0x3fffffff; } + Encoding encoding() const { return Encoding((data >> 30) & 0x3); } + uint32_t& val() { return *(uint32_t*) this; } + const uint32_t& val() const { return *(uint32_t*) this; } + void set(uint32_t blocksize, Encoding encoding) + { data = (blocksize & 0x3fffffff) | ((encoding & 0x3) << 30); } + FaceDataHeader() : data(0) {} + }; + enum EditType { et_editfacedata, et_editmetadata }; + struct EditFaceDataHeader { + uint32_t faceid; + FaceInfo faceinfo; + FaceDataHeader fdh; + }; + struct EditMetaDataHeader { + uint32_t metadatazipsize; + uint32_t metadatamemsize; + }; + + static const uint32_t Magic = 'P' | ('t'<<8) | ('e'<<16) | ('x'<<24); + static const int HeaderSize = sizeof(Header); + static const int ExtHeaderSize = sizeof(ExtHeader); + static const int LevelInfoSize = sizeof(LevelInfo); + static const int FaceDataHeaderSize = sizeof(FaceDataHeader); + static const int EditFaceDataHeaderSize = sizeof(EditFaceDataHeader); + static const int EditMetaDataHeaderSize = sizeof(EditMetaDataHeader); + + // these constants can be tuned for performance + static const int BlockSize = 16384; // target block size for file I/O + static const int TileSize = 65536; // target tile size (uncompressed) + static const int AllocaMax = 16384; // max size for using alloca + static const int MetaDataThreshold = 1024; // cutoff for large meta data + + static bool LittleEndian() { + short word = 0x0201; + return *(char*)&word == 1; + } +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexInt.h b/extern/ptex/src/ptex/PtexInt.h new file mode 100644 index 00000000000..c3212dc5f4c --- /dev/null +++ b/extern/ptex/src/ptex/PtexInt.h @@ -0,0 +1,57 @@ +#ifndef PtexInt_h +#define PtexInt_h + +/* +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. +*/ + +/** + @file PtexInt.h + @brief Portable fixed-width integer types +*/ + +#if defined(_WIN32) || defined(_WINDOWS) || defined(_MSC_VER) +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include <stdint.h> +#endif + +#endif diff --git a/extern/ptex/src/ptex/PtexMutex.h b/extern/ptex/src/ptex/PtexMutex.h new file mode 100644 index 00000000000..a2ee343ca83 --- /dev/null +++ b/extern/ptex/src/ptex/PtexMutex.h @@ -0,0 +1,79 @@ +#ifndef PtexMutex_h +#define PtexMutex_h + +/* +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. +*/ + +// #define DEBUG_THREADING + +/** For internal use only */ +namespace PtexInternal { +#ifndef NDEBUG + template <class T> + class DebugLock : public T { + public: + DebugLock() : _locked(0) {} + void lock() { T::lock(); _locked = 1; } + void unlock() { assert(_locked); _locked = 0; T::unlock(); } + bool locked() { return _locked != 0; } + private: + int _locked; + }; +#endif + + /** Automatically acquire and release lock within enclosing scope. */ + template <class T> + class AutoLock { + public: + AutoLock(T& m) : _m(m) { _m.lock(); } + ~AutoLock() { _m.unlock(); } + private: + T& _m; + }; + +#ifndef NDEBUG + // add debug wrappers to mutex and spinlock + typedef DebugLock<_Mutex> Mutex; + typedef DebugLock<_SpinLock> SpinLock; +#else + typedef _Mutex Mutex; + typedef _SpinLock SpinLock; +#endif + + typedef AutoLock<Mutex> AutoMutex; + typedef AutoLock<SpinLock> AutoSpin; +} + +#endif diff --git a/extern/ptex/src/ptex/PtexPlatform.h b/extern/ptex/src/ptex/PtexPlatform.h new file mode 100644 index 00000000000..b2959f511c2 --- /dev/null +++ b/extern/ptex/src/ptex/PtexPlatform.h @@ -0,0 +1,160 @@ +#ifndef PtexPlatform_h +#define PtexPlatform_h +#define PtexPlatform_h +/* +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. +*/ + +/** @file PtexPlatform.h + @brief Platform-specific classes, functions, and includes. +*/ + +// platform-specific includes +#if defined(_WIN32) || defined(_WINDOWS) || defined(_MSC_VER) +#ifndef WINDOWS +#define WINDOWS +#endif +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#define _CRT_SECURE_NO_DEPRECATE 1 +#define NOMINMAX 1 + +// windows - defined for both Win32 and Win64 +#include <Windows.h> +#include <malloc.h> +#include <io.h> +#include <tchar.h> +#include <process.h> + +#else + +// linux/unix/posix +#include <stdlib.h> +#include <alloca.h> +#include <string.h> +#include <pthread.h> +// OS for spinlock +#ifdef __APPLE__ +#include <libkern/OSAtomic.h> +#include <sys/types.h> +#endif +#endif + +// general includes +#include <stdio.h> +#include <math.h> +#include <assert.h> + +// missing functions on Windows +#ifdef WINDOWS +#define snprintf sprintf_s +#define strtok_r strtok_s +typedef __int64 FilePos; +#define fseeko _fseeki64 +#define ftello _ftelli64 + +inline double log2(double x) { + return log(x) * 1.4426950408889634; +} + +#else +typedef off_t FilePos; +#endif + + +namespace PtexInternal { + + /* + * Mutex/SpinLock classes + */ + +#ifdef WINDOWS + + class _Mutex { + public: + _Mutex() { _mutex = CreateMutex(NULL, FALSE, NULL); } + ~_Mutex() { CloseHandle(_mutex); } + void lock() { WaitForSingleObject(_mutex, INFINITE); } + void unlock() { ReleaseMutex(_mutex); } + private: + HANDLE _mutex; + }; + + class _SpinLock { + public: + _SpinLock() { InitializeCriticalSection(&_spinlock); } + ~_SpinLock() { DeleteCriticalSection(&_spinlock); } + void lock() { EnterCriticalSection(&_spinlock); } + void unlock() { LeaveCriticalSection(&_spinlock); } + private: + CRITICAL_SECTION _spinlock; + }; + +#else + // assume linux/unix/posix + + class _Mutex { + public: + _Mutex() { pthread_mutex_init(&_mutex, 0); } + ~_Mutex() { pthread_mutex_destroy(&_mutex); } + void lock() { pthread_mutex_lock(&_mutex); } + void unlock() { pthread_mutex_unlock(&_mutex); } + private: + pthread_mutex_t _mutex; + }; + +#ifdef __APPLE__ + class _SpinLock { + public: + _SpinLock() { _spinlock = 0; } + ~_SpinLock() { } + void lock() { OSSpinLockLock(&_spinlock); } + void unlock() { OSSpinLockUnlock(&_spinlock); } + private: + OSSpinLock _spinlock; + }; +#else + class _SpinLock { + public: + _SpinLock() { pthread_spin_init(&_spinlock, PTHREAD_PROCESS_PRIVATE); } + ~_SpinLock() { pthread_spin_destroy(&_spinlock); } + void lock() { pthread_spin_lock(&_spinlock); } + void unlock() { pthread_spin_unlock(&_spinlock); } + private: + pthread_spinlock_t _spinlock; + }; +#endif // __APPLE__ +#endif +} + +#endif // PtexPlatform_h diff --git a/extern/ptex/src/ptex/PtexReader.cpp b/extern/ptex/src/ptex/PtexReader.cpp new file mode 100644 index 00000000000..f86d349d672 --- /dev/null +++ b/extern/ptex/src/ptex/PtexReader.cpp @@ -0,0 +1,1357 @@ +/* +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 + 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); + } +} + + +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; +} diff --git a/extern/ptex/src/ptex/PtexReader.h b/extern/ptex/src/ptex/PtexReader.h new file mode 100644 index 00000000000..30bd2feded7 --- /dev/null +++ b/extern/ptex/src/ptex/PtexReader.h @@ -0,0 +1,613 @@ +#ifndef PtexReader_h +#define PtexReader_h + +/* +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 <stdio.h> +#include <zlib.h> +#include <vector> +#include <string> +#include <map> +#include "Ptexture.h" +#include "PtexIO.h" +#include "PtexCache.h" +#include "PtexUtils.h" + +#include "PtexHashMap.h" +using namespace PtexInternal; + +#ifndef NDEBUG +#include <assert.h> +template<typename T> class safevector : public std::vector<T> +{ +public: + safevector() : std::vector<T>() {} + safevector(size_t n, const T& val = T()) : std::vector<T>(n, val) {} + const T& operator[] (size_t n) const { + assert(n < std::vector<T>::size()); + return std::vector<T>::operator[](n); + } + T& operator[] (size_t n) { + assert(n < std::vector<T>::size()); + return std::vector<T>::operator[](n); + } +}; +#else +#define safevector std::vector +#endif + +class PtexReader : public PtexCachedFile, public PtexTexture, public PtexIO { +public: + PtexReader(void** parent, PtexCacheImpl* cache, bool premultiply, + PtexInputHandler* handler); + bool open(const char* path, Ptex::String& error); + + void setOwnsCache() { _ownsCache = true; } + virtual void release(); + virtual const char* path() { return _path.c_str(); } + virtual Ptex::MeshType meshType() { return MeshType(_header.meshtype); } + virtual Ptex::DataType dataType() { return DataType(_header.datatype); } + virtual Ptex::BorderMode uBorderMode() { return BorderMode(_extheader.ubordermode); }; + virtual Ptex::BorderMode vBorderMode() { return BorderMode(_extheader.vbordermode); }; + virtual int alphaChannel() { return _header.alphachan; } + virtual int numChannels() { return _header.nchannels; } + virtual int numFaces() { return _header.nfaces; } + virtual bool hasEdits() { return _hasEdits; } + virtual bool hasMipMaps() { return _header.nlevels > 1; } + + virtual PtexMetaData* getMetaData(); + virtual const Ptex::FaceInfo& getFaceInfo(int faceid); + virtual void getData(int faceid, void* buffer, int stride); + virtual void getData(int faceid, void* buffer, int stride, Res res); + virtual PtexFaceData* getData(int faceid); + virtual PtexFaceData* getData(int faceid, Res res); + virtual void getPixel(int faceid, int u, int v, + float* result, int firstchan, int nchannels); + virtual void getPixel(int faceid, int u, int v, + float* result, int firstchan, int nchannels, + Ptex::Res res); + + DataType datatype() const { return _header.datatype; } + int nchannels() const { return _header.nchannels; } + int pixelsize() const { return _pixelsize; } + const Header& header() const { return _header; } + const ExtHeader& extheader() const { return _extheader; } + const LevelInfo& levelinfo(int level) const { return _levelinfo[level]; } + + class MetaData : public PtexCachedData, public PtexMetaData { + public: + MetaData(MetaData** parent, PtexCacheImpl* cache, int size, PtexReader* reader) + : PtexCachedData((void**)parent, cache, sizeof(*this) + size), + _reader(reader) {} + virtual void release() { + AutoLockCache lock(_cache->cachelock); + // first, unref all lmdData refs + for (int i = 0, n = _lmdRefs.size(); i < n; i++) + _lmdRefs[i]->unref(); + _lmdRefs.resize(0); + + // finally, unref self + unref(); + } + + virtual int numKeys() { return int(_entries.size()); } + virtual void getKey(int n, const char*& key, MetaDataType& type) + { + Entry* e = _entries[n]; + key = e->key; + type = e->type; + } + + virtual void getValue(const char* key, const char*& value) + { + Entry* e = getEntry(key); + if (e) value = (const char*) e->data; + else value = 0; + } + + virtual void getValue(const char* key, const int8_t*& value, int& count) + { + Entry* e = getEntry(key); + if (e) { value = (const int8_t*) e->data; count = e->datasize; } + else { value = 0; count = 0; } + } + + virtual void getValue(const char* key, const int16_t*& value, int& count) + { + Entry* e = getEntry(key); + if (e) { + value = (const int16_t*) e->data; + count = int(e->datasize/sizeof(int16_t)); + } + else { value = 0; count = 0; } + } + + virtual void getValue(const char* key, const int32_t*& value, int& count) + { + Entry* e = getEntry(key); + if (e) { + value = (const int32_t*) e->data; + count = int(e->datasize/sizeof(int32_t)); + } + else { value = 0; count = 0; } + } + + virtual void getValue(const char* key, const float*& value, int& count) + { + Entry* e = getEntry(key); + if (e) { + value = (const float*) e->data; + count = int(e->datasize/sizeof(float)); + } + else { value = 0; count = 0; } + } + + virtual void getValue(const char* key, const double*& value, int& count) + { + Entry* e = getEntry(key); + if (e) { + value = (const double*) e->data; + count = int(e->datasize/sizeof(double)); + } + else { value = 0; count = 0; } + } + + void addEntry(uint8_t keysize, const char* key, uint8_t datatype, + uint32_t datasize, void* data) + { + Entry* e = newEntry(keysize, key, datatype, datasize); + e->data = malloc(datasize); + memcpy(e->data, data, datasize); + } + + void addLmdEntry(uint8_t keysize, const char* key, uint8_t datatype, + uint32_t datasize, FilePos filepos, uint32_t zipsize) + { + Entry* e = newEntry(keysize, key, datatype, datasize); + e->isLmd = true; + e->lmdData = 0; + e->lmdPos = filepos; + e->lmdZipSize = zipsize; + } + + protected: + class LargeMetaData : public PtexCachedData + { + public: + LargeMetaData(void** parent, PtexCacheImpl* cache, int size) + : PtexCachedData(parent, cache, size), _data(malloc(size)) {} + void* data() { return _data; } + private: + virtual ~LargeMetaData() { free(_data); } + void* _data; + }; + + struct Entry { + const char* key; // ptr to map key string + MetaDataType type; // meta data type + uint32_t datasize; // size of data in bytes + void* data; // if lmd, data only valid when lmd is loaded and ref'ed + bool isLmd; // true if data is a large meta data block + LargeMetaData* lmdData; // large meta data (lazy-loaded, lru-cached) + FilePos lmdPos; // large meta data file position + uint32_t lmdZipSize; // large meta data size on disk + + Entry() : + key(0), type(MetaDataType(0)), datasize(0), data(0), + isLmd(0), lmdData(0), lmdPos(0), lmdZipSize(0) {} + ~Entry() { clear(); } + void clear() { + if (isLmd) { + isLmd = 0; + if (lmdData) { lmdData->orphan(); lmdData = 0; } + lmdPos = 0; + lmdZipSize = 0; + } + else { + free(data); + } + data = 0; + } + }; + + Entry* newEntry(uint8_t keysize, const char* key, uint8_t datatype, uint32_t datasize) + { + std::pair<MetaMap::iterator,bool> result = + _map.insert(std::make_pair(std::string(key, keysize), Entry())); + Entry* e = &result.first->second; + bool newEntry = result.second; + if (newEntry) _entries.push_back(e); + else e->clear(); + e->key = result.first->first.c_str(); + e->type = MetaDataType(datatype); + e->datasize = datasize; + return e; + } + + Entry* getEntry(const char* key); + + PtexReader* _reader; + typedef std::map<std::string, Entry> MetaMap; + MetaMap _map; + safevector<Entry*> _entries; + std::vector<LargeMetaData*> _lmdRefs; + }; + + + class ConstDataPtr : public PtexFaceData { + public: + ConstDataPtr(void* data, int pixelsize) + : _data(data), _pixelsize(pixelsize) {} + virtual void release() { delete this; } + virtual Ptex::Res res() { return 0; } + virtual bool isConstant() { return true; } + virtual void getPixel(int, int, void* result) + { memcpy(result, _data, _pixelsize); } + virtual void* getData() { return _data; } + virtual bool isTiled() { return false; } + virtual Ptex::Res tileRes() { return 0; } + virtual PtexFaceData* getTile(int) { return 0; } + + protected: + void* _data; + int _pixelsize; + }; + + + class FaceData : public PtexCachedData, public PtexFaceData { + public: + FaceData(void** parent, PtexCacheImpl* cache, Res res, int size) + : PtexCachedData(parent, cache, size), _res(res) {} + virtual void release() { AutoLockCache lock(_cache->cachelock); unref(); } + virtual Ptex::Res res() { return _res; } + virtual void reduce(FaceData*&, PtexReader*, + Res newres, PtexUtils::ReduceFn) = 0; + protected: + Res _res; + }; + + class PackedFace : public FaceData { + public: + PackedFace(void** parent, PtexCacheImpl* cache, Res res, int pixelsize, int size) + : FaceData(parent, cache, res, sizeof(*this)+size), + _pixelsize(pixelsize), _data(malloc(size)) {} + void* data() { return _data; } + virtual bool isConstant() { return false; } + virtual void getPixel(int u, int v, void* result) + { + memcpy(result, (char*)_data + (v*_res.u() + u) * _pixelsize, _pixelsize); + } + virtual void* getData() { return _data; }; + virtual bool isTiled() { return false; } + virtual Ptex::Res tileRes() { return _res; }; + virtual PtexFaceData* getTile(int) { return 0; }; + virtual void reduce(FaceData*&, PtexReader*, + Res newres, PtexUtils::ReduceFn); + + protected: + virtual ~PackedFace() { free(_data); } + + int _pixelsize; + void* _data; + }; + + class ConstantFace : public PackedFace { + public: + ConstantFace(void** parent, PtexCacheImpl* cache, int pixelsize) + : PackedFace(parent, cache, 0, pixelsize, pixelsize) {} + virtual bool isConstant() { return true; } + virtual void getPixel(int, int, void* result) { memcpy(result, _data, _pixelsize); } + virtual void reduce(FaceData*&, PtexReader*, + Res newres, PtexUtils::ReduceFn); + }; + + + class TiledFaceBase : public FaceData { + public: + TiledFaceBase(void** parent, PtexCacheImpl* cache, Res res, + Res tileres, DataType dt, int nchan) + : FaceData(parent, cache, res, sizeof(*this)), + _tileres(tileres), + _dt(dt), + _nchan(nchan), + _pixelsize(DataSize(dt)*nchan) + { + _ntilesu = _res.ntilesu(tileres); + _ntilesv = _res.ntilesv(tileres); + _ntiles = _ntilesu*_ntilesv; + _tiles.resize(_ntiles); + incSize(sizeof(FaceData*)*_ntiles); + } + + virtual void release() { + // Tiled faces ref the reader (directly or indirectly) and + // thus may trigger cache deletion on release. Call cache + // to check for pending delete. + // Note: release() may delete "this", so save _cache in + // local var. + PtexCacheImpl* cache = _cache; + FaceData::release(); + cache->handlePendingDelete(); + } + virtual bool isConstant() { return false; } + virtual void getPixel(int u, int v, void* result); + virtual void* getData() { return 0; } + virtual bool isTiled() { return true; } + virtual Ptex::Res tileRes() { return _tileres; }; + virtual void reduce(FaceData*&, PtexReader*, + Res newres, PtexUtils::ReduceFn); + Res tileres() const { return _tileres; } + int ntilesu() const { return _ntilesu; } + int ntilesv() const { return _ntilesv; } + int ntiles() const { return _ntiles; } + + protected: + virtual ~TiledFaceBase() { orphanList(_tiles); } + + Res _tileres; + DataType _dt; + int _nchan; + int _ntilesu; + int _ntilesv; + int _ntiles; + int _pixelsize; + safevector<FaceData*> _tiles; + }; + + + class TiledFace : public TiledFaceBase { + public: + TiledFace(void** parent, PtexCacheImpl* cache, Res res, Res tileres, + int levelid, PtexReader* reader) + : TiledFaceBase(parent, cache, res, tileres, + reader->datatype(), reader->nchannels()), + _reader(reader), + _levelid(levelid) + { + _fdh.resize(_ntiles), + _offsets.resize(_ntiles); + incSize((sizeof(FaceDataHeader)+sizeof(FilePos))*_ntiles); + } + virtual PtexFaceData* getTile(int tile) + { + AutoLockCache locker(_cache->cachelock); + FaceData*& f = _tiles[tile]; + if (!f) readTile(tile, f); + else f->ref(); + return f; + } + void readTile(int tile, FaceData*& data); + + protected: + friend class PtexReader; + PtexReader* _reader; + int _levelid; + safevector<FaceDataHeader> _fdh; + safevector<FilePos> _offsets; + }; + + + class TiledReducedFace : public TiledFaceBase { + public: + TiledReducedFace(void** parent, PtexCacheImpl* cache, Res res, + Res tileres, DataType dt, int nchan, + TiledFaceBase* parentface, PtexUtils::ReduceFn reducefn) + : TiledFaceBase(parent, cache, res, tileres, dt, nchan), + _parentface(parentface), + _reducefn(reducefn) + { + AutoLockCache locker(_cache->cachelock); + _parentface->ref(); + } + ~TiledReducedFace() + { + _parentface->unref(); + } + virtual PtexFaceData* getTile(int tile); + + protected: + TiledFaceBase* _parentface; + PtexUtils::ReduceFn* _reducefn; + }; + + + class Level : public PtexCachedData { + public: + safevector<FaceDataHeader> fdh; + safevector<FilePos> offsets; + safevector<FaceData*> faces; + + Level(void** parent, PtexCacheImpl* cache, int nfaces) + : PtexCachedData(parent, cache, + sizeof(*this) + nfaces * (sizeof(FaceDataHeader) + + sizeof(FilePos) + + sizeof(FaceData*))), + fdh(nfaces), + offsets(nfaces), + faces(nfaces) {} + protected: + virtual ~Level() { orphanList(faces); } + }; + + Mutex readlock; + Mutex reducelock; + +protected: + virtual ~PtexReader(); + void setError(const char* error) + { + _error = error; _error += " PtexFile: "; _error += _path; + _ok = 0; + } + + FilePos tell() { return _pos; } + void seek(FilePos pos) + { + if (pos != _pos) { + _io->seek(_fp, pos); + _pos = pos; + STATS_INC(nseeks); + } + } + + bool readBlock(void* data, int size, bool reportError=true); + bool readZipBlock(void* data, int zipsize, int unzipsize); + Level* getLevel(int levelid) + { + Level*& level = _levels[levelid]; + if (!level) readLevel(levelid, level); + else level->ref(); + return level; + } + + uint8_t* getConstData() { if (!_constdata) readConstData(); return _constdata; } + FaceData* getFace(int levelid, Level* level, int faceid) + { + FaceData*& face = level->faces[faceid]; + if (!face) readFace(levelid, level, faceid); + else face->ref(); + return face; + } + + Res getRes(int levelid, int faceid) + { + if (levelid == 0) return _faceinfo[faceid].res; + else { + // for reduction level, look up res via rfaceid + Res res = _res_r[faceid]; + // and adjust for number of reductions + return Res(res.ulog2 - levelid, res.vlog2 - levelid); + } + } + + int unpackedSize(FaceDataHeader fdh, int levelid, int faceid) + { + if (fdh.encoding() == enc_constant) + // level 0 constant faces are not stored + return levelid == 0 ? 0 : _pixelsize; + else + return getRes(levelid, faceid).size() * _pixelsize; + } + + void readFaceInfo(); + void readLevelInfo(); + void readConstData(); + void readLevel(int levelid, Level*& level); + void readFace(int levelid, Level* level, int faceid); + void readFaceData(FilePos pos, FaceDataHeader fdh, Res res, int levelid, FaceData*& face); + void readMetaData(); + void readMetaDataBlock(MetaData* metadata, FilePos pos, int zipsize, int memsize); + void readLargeMetaDataHeaders(MetaData* metadata, FilePos pos, int zipsize, int memsize); + void readEditData(); + void readEditFaceData(); + void readEditMetaData(); + + void computeOffsets(FilePos pos, int noffsets, const FaceDataHeader* fdh, FilePos* offsets) + { + FilePos* end = offsets + noffsets; + while (offsets != end) { *offsets++ = pos; pos += fdh->blocksize(); fdh++; } + } + void blendFaces(FaceData*& face, int faceid, Res res, bool blendu); + + PtexInputHandler* _io; // IO handler + bool _premultiply; // true if reader should premultiply the alpha chan + bool _ownsCache; // true if reader owns the cache + bool _ok; // flag set if read error occurred) + std::string _error; // error string (if !_ok) + PtexInputHandler::Handle _fp; // file pointer + FilePos _pos; // current seek position + std::string _path; // current file path + Header _header; // the header + ExtHeader _extheader; // extended header + FilePos _faceinfopos; // file positions of data sections + FilePos _constdatapos; // ... + FilePos _levelinfopos; + FilePos _leveldatapos; + FilePos _metadatapos; + FilePos _lmdheaderpos; + FilePos _lmddatapos; + FilePos _editdatapos; + int _pixelsize; // size of a pixel in bytes + uint8_t* _constdata; // constant pixel value per face + MetaData* _metadata; // meta data (read on demand) + bool _hasEdits; // has edit blocks + + safevector<FaceInfo> _faceinfo; // per-face header info + safevector<uint32_t> _rfaceids; // faceids sorted in reduction order + safevector<Res> _res_r; // face res indexed by rfaceid + safevector<LevelInfo> _levelinfo; // per-level header info + safevector<FilePos> _levelpos; // file position of each level's data + safevector<Level*> _levels; // level data (read on demand) + + struct MetaEdit + { + FilePos pos; + int zipsize; + int memsize; + }; + safevector<MetaEdit> _metaedits; + + struct FaceEdit + { + FilePos pos; + int faceid; + FaceDataHeader fdh; + }; + safevector<FaceEdit> _faceedits; + + struct ReductionKey { + int faceid; + Res res; + ReductionKey() : faceid(0), res(0,0) {} + ReductionKey(uint32_t faceid, Res res) : faceid(faceid), res(res) {} + bool operator==(const ReductionKey& k) const + { return k.faceid == faceid && k.res == res; } + struct Hasher { + uint32_t operator() (const ReductionKey& key) const + { + // constants from Knuth + static uint32_t M = 1664525, C = 1013904223; + uint32_t val = (key.res.ulog2 * M + key.res.vlog2 + C) * M + key.faceid; + return val; + } + }; + }; + typedef PtexHashMap<ReductionKey, FaceData*, ReductionKey::Hasher> ReductionMap; + ReductionMap _reductions; + + z_stream_s _zstream; +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexSeparableFilter.cpp b/extern/ptex/src/ptex/PtexSeparableFilter.cpp new file mode 100644 index 00000000000..20360287b18 --- /dev/null +++ b/extern/ptex/src/ptex/PtexSeparableFilter.cpp @@ -0,0 +1,389 @@ +/* +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 <math.h> +#include <assert.h> + +#include "PtexSeparableFilter.h" +#include "PtexSeparableKernel.h" +#include "PtexUtils.h" + + +//#define NOEDGEBLEND // uncomment to disable filtering across edges (for debugging) + +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::splitAndApply(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f) +{ + // do we need to split? (i.e. does kernel span an edge?) + bool splitR = (k.u+k.uw > k.res.u()), splitL = (k.u < 0); + bool splitT = (k.v+k.vw > k.res.v()), splitB = (k.v < 0); + +#ifdef NOEDGEBLEND + // for debugging only + if (splitR) k.mergeR(_uMode); + if (splitL) k.mergeL(_uMode); + if (splitT) k.mergeT(_vMode); + if (splitB) k.mergeB(_vMode); +#else + if (splitR || splitL || splitT || splitB) { + PtexSeparableKernel ka, kc; + if (splitR) { + if (f.adjface(e_right) >= 0) { + k.splitR(ka); + if (splitT) { + if (f.adjface(e_top) >= 0) { + ka.splitT(kc); + applyToCorner(kc, faceid, f, e_top); + } + else ka.mergeT(_vMode); + } + if (splitB) { + if (f.adjface(e_bottom) >= 0) { + ka.splitB(kc); + applyToCorner(kc, faceid, f, e_right); + } + else ka.mergeB(_vMode); + } + applyAcrossEdge(ka, faceid, f, e_right); + } + else k.mergeR(_uMode); + } + if (splitL) { + if (f.adjface(e_left) >= 0) { + k.splitL(ka); + if (splitT) { + if (f.adjface(e_top) >= 0) { + ka.splitT(kc); + applyToCorner(kc, faceid, f, e_left); + } + else ka.mergeT(_vMode); + } + if (splitB) { + if (f.adjface(e_bottom) >= 0) { + ka.splitB(kc); + applyToCorner(kc, faceid, f, e_bottom); + } + else ka.mergeB(_vMode); + } + applyAcrossEdge(ka, faceid, f, e_left); + } + else k.mergeL(_uMode); + } + if (splitT) { + if (f.adjface(e_top) >= 0) { + k.splitT(ka); + applyAcrossEdge(ka, faceid, f, e_top); + } + else k.mergeT(_vMode); + } + if (splitB) { + if (f.adjface(e_bottom) >= 0) { + k.splitB(ka); + applyAcrossEdge(ka, faceid, f, e_bottom); + } + else k.mergeB(_vMode); + } + } +#endif + + // do local face + apply(k, faceid, f); +} + + +void PtexSeparableFilter::applyAcrossEdge(PtexSeparableKernel& k, + int faceid, const Ptex::FaceInfo& f, int eid) +{ + int afid = f.adjface(eid), aeid = f.adjedge(eid); + const Ptex::FaceInfo* af = &_tx->getFaceInfo(afid); + int rot = eid - aeid + 2; + + // adjust uv coord and res for face/subface boundary + bool fIsSubface = f.isSubface(), afIsSubface = af->isSubface(); + if (fIsSubface != afIsSubface) { + if (afIsSubface) { + // main face to subface transition + // adjust res and offset uv coord for primary subface + bool primary = k.adjustMainToSubface(eid); + if (!primary) { + // advance ajacent face and edge id to secondary subface + int neid = (aeid + 3) % 4; + afid = af->adjface(neid); + aeid = af->adjedge(neid); + af = &_tx->getFaceInfo(afid); + rot += neid - aeid + 2; + } + } + else { + // subface to main face transition + // Note: the transform depends on which subface the kernel is + // coming from. The "primary" subface is the one the main + // face is pointing at. The secondary subface adjustment + // happens to be the same as for the primary subface for the + // next edge, so the cases can be combined. + bool primary = (af->adjface(aeid) == faceid); + k.adjustSubfaceToMain(eid - primary); + } + } + + // rotate and apply (resplit if going to a subface) + k.rotate(rot); + if (afIsSubface) splitAndApply(k, afid, *af); + else apply(k, afid, *af); +} + + +void PtexSeparableFilter::applyToCorner(PtexSeparableKernel& k, int faceid, + const Ptex::FaceInfo& f, int eid) +{ + // traverse clockwise around corner vertex and gather corner faces + int afid = faceid, aeid = eid; + const FaceInfo* af = &f; + bool prevIsSubface = af->isSubface(); + + const int MaxValence = 10; + int cfaceId[MaxValence]; + int cedgeId[MaxValence]; + const FaceInfo* cface[MaxValence]; + + int numCorners = 0; + for (int i = 0; i < MaxValence; i++) { + // advance to next face + int prevFace = afid; + afid = af->adjface(aeid); + aeid = (af->adjedge(aeid) + 1) % 4; + + // we hit a boundary or reached starting face + // note: we need to check edge id too because we might have + // a periodic texture (w/ toroidal topology) where all 4 corners + // are from the same face + if (afid < 0 || (afid == faceid && aeid == eid)) { + numCorners = i - 2; + break; + } + + // record face info + af = &_tx->getFaceInfo(afid); + cfaceId[i] = afid; + cedgeId[i] = aeid; + cface[i] = af; + + // check to see if corner is a subface "tee" + bool isSubface = af->isSubface(); + if (prevIsSubface && !isSubface && af->adjface((aeid+3)%4) == prevFace) + { + // adjust the eid depending on whether we started from + // the primary or secondary subface. + bool primary = (i==1); + k.adjustSubfaceToMain(eid + primary * 2); + k.rotate(eid - aeid + 3 - primary); + splitAndApply(k, afid, *af); + return; + } + prevIsSubface = isSubface; + } + + if (numCorners == 1) { + // regular case (valence 4) + applyToCornerFace(k, f, eid, cfaceId[1], *cface[1], cedgeId[1]); + } + else if (numCorners > 1) { + // valence 5+, make kernel symmetric and apply equally to each face + // first, rotate to standard orientation, u=v=0 + k.rotate(eid + 2); + double initialWeight = k.weight(); + double newWeight = k.makeSymmetric(initialWeight); + for (int i = 1; i <= numCorners; i++) { + PtexSeparableKernel kc = k; + applyToCornerFace(kc, f, 2, cfaceId[i], *cface[i], cedgeId[i]); + } + // adjust weight for symmetrification and for additional corners + _weight += newWeight * numCorners - initialWeight; + } + else { + // valence 2 or 3, ignore corner face (just adjust weight) + _weight -= k.weight(); + } +} + + +void PtexSeparableFilter::applyToCornerFace(PtexSeparableKernel& k, const Ptex::FaceInfo& f, int eid, + int cfid, const Ptex::FaceInfo& cf, int ceid) +{ + // adjust uv coord and res for face/subface boundary + bool fIsSubface = f.isSubface(), cfIsSubface = cf.isSubface(); + if (fIsSubface != cfIsSubface) { + if (cfIsSubface) k.adjustMainToSubface(eid + 3); + else k.adjustSubfaceToMain(eid + 3); + } + + // rotate and apply (resplit if going to a subface) + k.rotate(eid - ceid + 2); + if (cfIsSubface) splitAndApply(k, cfid, cf); + else apply(k, cfid, cf); +} + + +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); + } +} diff --git a/extern/ptex/src/ptex/PtexSeparableFilter.h b/extern/ptex/src/ptex/PtexSeparableFilter.h new file mode 100644 index 00000000000..da23f75566e --- /dev/null +++ b/extern/ptex/src/ptex/PtexSeparableFilter.h @@ -0,0 +1,80 @@ +#ifndef PtexSeparableFilter_h +#define PtexSeparableFilter_h + +/* +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 "Ptexture.h" + +class PtexSeparableKernel; + +class PtexSeparableFilter : public PtexFilter, public Ptex +{ + public: + virtual void release() { delete this; } + virtual void 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); + + protected: + PtexSeparableFilter(PtexTexture* tx, const PtexFilter::Options& opts ) : + _tx(tx), _options(opts), _result(0), _weight(0), + _firstChanOffset(0), _nchan(0), _ntxchan(0), + _dt((DataType)0), _uMode(tx->uBorderMode()), _vMode(tx->vBorderMode()) {} + virtual ~PtexSeparableFilter() {} + + virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw, + Res faceRes) = 0; + + void splitAndApply(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f); + void applyAcrossEdge(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f, int eid); + void applyToCorner(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f, int eid); + void applyToCornerFace(PtexSeparableKernel& k, const Ptex::FaceInfo& f, int eid, + int cfaceid, const Ptex::FaceInfo& cf, int ceid); + void apply(PtexSeparableKernel& k, int faceid, const Ptex::FaceInfo& f); + + PtexTexture* _tx; // texture being evaluated + Options _options; // options + double* _result; // temp result + double _weight; // accumulated weight of data in _result + int _firstChanOffset; // byte offset of first channel to eval + int _nchan; // number of channels to eval + int _ntxchan; // number of channels in texture + DataType _dt; // data type of texture + BorderMode _uMode, _vMode; // border modes (clamp,black,periodic) +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexSeparableKernel.cpp b/extern/ptex/src/ptex/PtexSeparableKernel.cpp new file mode 100644 index 00000000000..eea51109bbb --- /dev/null +++ b/extern/ptex/src/ptex/PtexSeparableKernel.cpp @@ -0,0 +1,149 @@ +/* +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 "PtexUtils.h" +#include "PtexHalf.h" +#include "PtexSeparableKernel.h" + +namespace { + // apply to 1..4 channels (unrolled channel loop) of packed data (nTxChan==nChan) + template<class T, int nChan> + void Apply(PtexSeparableKernel& k, double* result, void* data, int /*nChan*/, int /*nTxChan*/) + { + double* rowResult = (double*) alloca(nChan*sizeof(double)); + int rowlen = k.res.u() * nChan; + int datalen = k.uw * nChan; + int rowskip = rowlen - datalen; + double* kvp = k.kv; + T* p = (T*)data + (k.v * k.res.u() + k.u) * nChan; + T* pEnd = p + k.vw * rowlen; + while (p != pEnd) + { + double* kup = k.ku; + T* pRowEnd = p + datalen; + // just mult and copy first element + PtexUtils::VecMult<T,nChan>()(rowResult, p, *kup++); + p += nChan; + // accumulate remaining elements + while (p != pRowEnd) { + // rowResult[i] = p[i] * ku[u] for i in {0..n-1} + PtexUtils::VecAccum<T,nChan>()(rowResult, p, *kup++); + p += nChan; + } + // result[i] += rowResult[i] * kv[v] for i in {0..n-1} + PtexUtils::VecAccum<double,nChan>()(result, rowResult, *kvp++); + p += rowskip; + } + } + + // apply to 1..4 channels (unrolled channel loop) w/ pixel stride + template<class T, int nChan> + void ApplyS(PtexSeparableKernel& k, double* result, void* data, int /*nChan*/, int nTxChan) + { + double* rowResult = (double*) alloca(nChan*sizeof(double)); + int rowlen = k.res.u() * nTxChan; + int datalen = k.uw * nTxChan; + int rowskip = rowlen - datalen; + double* kvp = k.kv; + T* p = (T*)data + (k.v * k.res.u() + k.u) * nTxChan; + T* pEnd = p + k.vw * rowlen; + while (p != pEnd) + { + double* kup = k.ku; + T* pRowEnd = p + datalen; + // just mult and copy first element + PtexUtils::VecMult<T,nChan>()(rowResult, p, *kup++); + p += nTxChan; + // accumulate remaining elements + while (p != pRowEnd) { + // rowResult[i] = p[i] * ku[u] for i in {0..n-1} + PtexUtils::VecAccum<T,nChan>()(rowResult, p, *kup++); + p += nTxChan; + } + // result[i] += rowResult[i] * kv[v] for i in {0..n-1} + PtexUtils::VecAccum<double,nChan>()(result, rowResult, *kvp++); + p += rowskip; + } + } + + // apply to N channels (general case) + template<class T> + void ApplyN(PtexSeparableKernel& k, double* result, void* data, int nChan, int nTxChan) + { + double* rowResult = (double*) alloca(nChan*sizeof(double)); + int rowlen = k.res.u() * nTxChan; + int datalen = k.uw * nTxChan; + int rowskip = rowlen - datalen; + double* kvp = k.kv; + T* p = (T*)data + (k.v * k.res.u() + k.u) * nTxChan; + T* pEnd = p + k.vw * rowlen; + while (p != pEnd) + { + double* kup = k.ku; + T* pRowEnd = p + datalen; + // just mult and copy first element + PtexUtils::VecMultN<T>()(rowResult, p, nChan, *kup++); + p += nTxChan; + // accumulate remaining elements + while (p != pRowEnd) { + // rowResult[i] = p[i] * ku[u] for i in {0..n-1} + PtexUtils::VecAccumN<T>()(rowResult, p, nChan, *kup++); + p += nTxChan; + } + // result[i] += rowResult[i] * kv[v] for i in {0..n-1} + PtexUtils::VecAccumN<double>()(result, rowResult, nChan, *kvp++); + p += rowskip; + } + } +} + + + +PtexSeparableKernel::ApplyFn +PtexSeparableKernel::applyFunctions[] = { + // nChan == nTxChan + ApplyN<uint8_t>, ApplyN<uint16_t>, ApplyN<PtexHalf>, ApplyN<float>, + Apply<uint8_t,1>, Apply<uint16_t,1>, Apply<PtexHalf,1>, Apply<float,1>, + Apply<uint8_t,2>, Apply<uint16_t,2>, Apply<PtexHalf,2>, Apply<float,2>, + Apply<uint8_t,3>, Apply<uint16_t,3>, Apply<PtexHalf,3>, Apply<float,3>, + Apply<uint8_t,4>, Apply<uint16_t,4>, Apply<PtexHalf,4>, Apply<float,4>, + + // nChan != nTxChan (need pixel stride) + ApplyN<uint8_t>, ApplyN<uint16_t>, ApplyN<PtexHalf>, ApplyN<float>, + ApplyS<uint8_t,1>, ApplyS<uint16_t,1>, ApplyS<PtexHalf,1>, ApplyS<float,1>, + ApplyS<uint8_t,2>, ApplyS<uint16_t,2>, ApplyS<PtexHalf,2>, ApplyS<float,2>, + ApplyS<uint8_t,3>, ApplyS<uint16_t,3>, ApplyS<PtexHalf,3>, ApplyS<float,3>, + ApplyS<uint8_t,4>, ApplyS<uint16_t,4>, ApplyS<PtexHalf,4>, ApplyS<float,4>, +}; diff --git a/extern/ptex/src/ptex/PtexSeparableKernel.h b/extern/ptex/src/ptex/PtexSeparableKernel.h new file mode 100644 index 00000000000..dcae2174ad4 --- /dev/null +++ b/extern/ptex/src/ptex/PtexSeparableKernel.h @@ -0,0 +1,473 @@ +#ifndef PtexSeparableKernel_h +#define PtexSeparableKernel_h + +/* +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 <assert.h> +#include <algorithm> +#include <numeric> +#include "Ptexture.h" +#include "PtexUtils.h" + +// Separable convolution kernel +class PtexSeparableKernel : public Ptex { + public: + Res res; // resolution that kernel was built for + int u, v; // uv offset within face data + int uw, vw; // kernel width + double* ku; // kernel weights in u + double* kv; // kernel weights in v + static const int kmax = 10; // max kernel width + double kubuff[kmax]; + double kvbuff[kmax]; + + PtexSeparableKernel() + : res(0), u(0), v(0), uw(0), vw(0), ku(kubuff), kv(kvbuff) {} + + PtexSeparableKernel(const PtexSeparableKernel& k) + { + set(k.res, k.u, k.v, k.uw, k.vw, k.ku, k.kv); + } + + PtexSeparableKernel& operator= (const PtexSeparableKernel& k) + { + set(k.res, k.u, k.v, k.uw, k.vw, k.ku, k.kv); + return *this; + } + + void set(Res resVal, + int uVal, int vVal, + int uwVal, int vwVal, + const double* kuVal, const double* kvVal) + { + assert(uwVal <= kmax && vwVal <= kmax); + res = resVal; + u = uVal; + v = vVal; + uw = uwVal; + vw = vwVal; + memcpy(kubuff, kuVal, sizeof(*ku)*uw); + memcpy(kvbuff, kvVal, sizeof(*kv)*vw); + ku = kubuff; + kv = kvbuff; + } + + void stripZeros() + { + while (ku[0] == 0) { ku++; u++; uw--; } + while (ku[uw-1] == 0) { uw--; } + while (kv[0] == 0) { kv++; v++; vw--; } + while (kv[vw-1] == 0) { vw--; } + assert(uw > 0 && vw > 0); + } + + double weight() const + { + return accumulate(ku, uw) * accumulate(kv, vw); + } + + void mergeL(BorderMode mode) + { + int w = -u; + if (mode != m_black) + ku[w] += accumulate(ku, w); + ku += w; + uw -= w; + u = 0; + } + + void mergeR(BorderMode mode) + { + int w = uw + u - res.u(); + double* kp = ku + uw - w; + if (mode != m_black) + kp[-1] += accumulate(kp, w); + uw -= w; + } + + void mergeB(BorderMode mode) + { + int w = -v; + if (mode != m_black) + kv[w] += accumulate(kv, w); + kv += w; + vw -= w; + v = 0; + } + + void mergeT(BorderMode mode) + { + int w = vw + v - res.v(); + double* kp = kv + vw - w; + if (mode != m_black) + kp[-1] += accumulate(kp, w); + vw -= w; + } + + void splitL(PtexSeparableKernel& k) + { + // split off left piece of width w into k + int w = -u; + + if (w < uw) { + // normal case - split off a portion + // res u v uw vw ku kv + k.set(res, res.u()-w, v, w, vw, ku, kv); + + // update local + u = 0; + uw -= w; + ku += w; + } + else { + // entire kernel is split off + k = *this; + k.u += res.u(); + u = 0; uw = 0; + } + } + + void splitR(PtexSeparableKernel& k) + { + // split off right piece of width w into k + int w = u + uw - res.u(); + + if (w < uw) { + // normal case - split off a portion + // res u v uw vw ku kv + k.set(res, 0, v, w, vw, ku + uw - w, kv); + + // update local + uw -= w; + } + else { + // entire kernel is split off + k = *this; + k.u -= res.u(); + u = 0; uw = 0; + } + } + + void splitB(PtexSeparableKernel& k) + { + // split off bottom piece of width w into k + int w = -v; + if (w < vw) { + // normal case - split off a portion + // res u v uw vw ku kv + k.set(res, u, res.v()-w, uw, w, ku, kv); + + // update local + v = 0; + vw -= w; + kv += w; + } + else { + // entire kernel is split off + k = *this; + k.v += res.v(); + v = 0; vw = 0; + } + } + + void splitT(PtexSeparableKernel& k) + { + // split off top piece of width w into k + int w = v + vw - res.v(); + if (w < vw) { + // normal case - split off a portion + // res u v uw vw ku kv + k.set(res, u, 0, uw, w, ku, kv + vw - w); + + // update local + vw -= w; + } + else { + // entire kernel is split off + k = *this; + k.v -= res.v(); + v = 0; vw = 0; + } + } + + void flipU() + { + u = res.u() - u - uw; + std::reverse(ku, ku+uw); + } + + void flipV() + { + v = res.v() - v - vw; + std::reverse(kv, kv+vw); + } + + void swapUV() + { + res.swapuv(); + std::swap(u, v); + std::swap(uw, vw); + std::swap(ku, kv); + } + + void rotate(int rot) + { + // rotate kernel 'rot' steps ccw + switch (rot & 3) { + default: return; + case 1: flipU(); swapUV(); break; + case 2: flipU(); flipV(); break; + case 3: flipV(); swapUV(); break; + } + } + + bool adjustMainToSubface(int eid) + { + // to adjust the kernel for the subface, we must adjust the res down and offset the uv coords + // however, if the res is already zero, we must upres the kernel first + if (res.ulog2 == 0) upresU(); + if (res.vlog2 == 0) upresV(); + + if (res.ulog2 > 0) res.ulog2--; + if (res.vlog2 > 0) res.vlog2--; + + // offset uv coords and determine whether target subface is the primary one + bool primary = 0; + int resu = res.u(), resv = res.v(); + switch (eid&3) { + case e_bottom: + primary = (u < resu); + v -= resv; + if (!primary) u -= resu; + break; + case e_right: + primary = (v < resv); + if (!primary) v -= resv; + break; + case e_top: + primary = (u >= resu); + if (primary) u -= resu; + break; + case e_left: + primary = (v >= resv); + u -= resu; + if (primary) v -= resv; + break; + } + return primary; + } + + void adjustSubfaceToMain(int eid) + { + switch (eid&3) { + case e_bottom: v += res.v(); break; + case e_right: break; + case e_top: u += res.u(); break; + case e_left: u += res.u(); v += res.v(); break; + } + res.ulog2++; res.vlog2++; + } + + void downresU() + { + double* src = ku; + double* dst = ku; + + // skip odd leading sample (if any) + if (u & 1) { + dst++; + src++; + uw--; + } + + // combine even pairs + for (int i = uw/2; i > 0; i--) { + *dst++ = src[0] + src[1]; + src += 2; + } + + // copy odd trailing sample (if any) + if (uw & 1) { + *dst++ = *src++; + } + + // update state + u /= 2; + uw = int(dst - ku); + res.ulog2--; + } + + void downresV() + { + double* src = kv; + double* dst = kv; + + // skip odd leading sample (if any) + if (v & 1) { + dst++; + src++; + vw--; + } + + // combine even pairs + for (int i = vw/2; i > 0; i--) { + *dst++ = src[0] + src[1]; + src += 2; + } + + // copy odd trailing sample (if any) + if (vw & 1) { + *dst++ = *src++; + } + + // update state + v /= 2; + vw = int(dst - kv); + res.vlog2--; + } + + void upresU() + { + double* src = ku + uw-1; + double* dst = ku + uw*2-2; + for (int i = uw; i > 0; i--) { + dst[0] = dst[1] = *src-- / 2; + dst -=2; + } + uw *= 2; + u *= 2; + res.ulog2++; + } + + void upresV() + { + double* src = kv + vw-1; + double* dst = kv + vw*2-2; + for (int i = vw; i > 0; i--) { + dst[0] = dst[1] = *src-- / 2; + dst -=2; + } + vw *= 2; + v *= 2; + res.vlog2++; + } + + double makeSymmetric(double initialWeight) + { + assert(u == 0 && v == 0); + + // downres higher-res dimension until equal + if (res.ulog2 > res.vlog2) { + do { downresU(); } while(res.ulog2 > res.vlog2); + } + else if (res.vlog2 > res.ulog2) { + do { downresV(); } while (res.vlog2 > res.ulog2); + } + + // truncate excess samples in longer dimension + uw = vw = PtexUtils::min(uw, vw); + + // combine corresponding u and v samples and compute new kernel weight + double newWeight = 0; + for (int i = 0; i < uw; i++) { + double sum = ku[i] + kv[i]; + ku[i] = kv[i] = sum; + newWeight += sum; + } + newWeight *= newWeight; // equivalent to k.weight() ( = sum(ku)*sum(kv) ) + + // compute scale factor to compensate for weight change + double scale = newWeight == 0 ? 1.0 : initialWeight / newWeight; + + // Note: a sharpening kernel (like Mitchell) can produce + // negative weights which may cancel out when adding the two + // kernel axes together, and this can cause the compensation + // scale factor to spike up. We expect the scale factor to be + // less than one in "normal" cases (i.e. ku*kv <= (ku+kv)^2 if ku + // and kv are both positive), so clamping to -1..1 will have + // no effect on positive kernels. If there are negative + // weights, the clamping will just limit the amount of + // sharpening happening at the corners, and the result will + // still be smooth. + + // clamp scale factor to -1..1 range + if (scale >= 1) { + // scale by 1 (i.e. do nothing) + } + else { + if (scale < -1) { + // a negative scale means the original kernel had an overall negative weight + // after making symmetric, the kernel will always be positive + // scale ku by -1 + // note: choice of u is arbitrary; we could have scaled u or v (but not both) + for (int i = 0; i < uw; i++) ku[i] *= -1; + newWeight = -newWeight; + } + else { + // scale ku to restore initialWeight (again, choice of u instead of v is arbitrary) + for (int i = 0; i < uw; i++) ku[i] *= scale; + newWeight = initialWeight; + } + } + return newWeight; + } + + void apply(double* dst, void* data, DataType dt, int nChan, int nTxChan) + { + // dispatch specialized apply function + ApplyFn fn = applyFunctions[(nChan!=nTxChan)*20 + ((unsigned)nChan<=4)*nChan*4 + dt]; + fn(*this, dst, data, nChan, nTxChan); + } + + void applyConst(double* dst, void* data, DataType dt, int nChan) + { + PtexUtils::applyConst(weight(), dst, data, dt, nChan); + } + + private: + typedef void (*ApplyFn)(PtexSeparableKernel& k, double* dst, void* data, int nChan, int nTxChan); + typedef void (*ApplyConstFn)(double weight, double* dst, void* data, int nChan); + static ApplyFn applyFunctions[40]; + static ApplyConstFn applyConstFunctions[20]; + static inline double accumulate(const double* p, int n) + { + double result = 0; + for (const double* e = p + n; p != e; p++) result += *p; + return result; + } +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexTriangleFilter.cpp b/extern/ptex/src/ptex/PtexTriangleFilter.cpp new file mode 100644 index 00000000000..42f12e0bc94 --- /dev/null +++ b/extern/ptex/src/ptex/PtexTriangleFilter.cpp @@ -0,0 +1,261 @@ +/* +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 <math.h> +#include <assert.h> + +#include "PtexTriangleFilter.h" +#include "PtexTriangleKernel.h" +#include "PtexUtils.h" + +namespace { + inline double squared(double x) { return x*x; } +} + +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 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 PtexTriangleFilter::buildKernel(PtexTriangleKernel& k, float u, float v, + float uw1, float vw1, float uw2, float vw2, + float width, float blur, Res faceRes) +{ + const double sqrt3 = 1.7320508075688772; + + // compute ellipse coefficients, A*u^2 + B*u*v + C*v^2 == AC - B^2/4 + double scaleAC = 0.25 * width*width; + double scaleB = -2 * scaleAC; + double A = (vw1*vw1 + vw2*vw2) * scaleAC; + double B = (uw1*vw1 + uw2*vw2) * scaleB; + double C = (uw1*uw1 + uw2*uw2) * scaleAC; + + // convert to cartesian domain + double Ac = 0.75 * A; + double Bc = (sqrt3/2) * (B-A); + double Cc = 0.25 * A - 0.5 * B + C; + + // compute min blur for eccentricity clamping + const double maxEcc = 15; // max eccentricity + const double eccRatio = (maxEcc*maxEcc + 1) / (maxEcc*maxEcc - 1); + double X = sqrt(squared(Ac - Cc) + squared(Bc)); + double b_e = 0.5 * (eccRatio * X - (Ac + Cc)); + + // compute min blur for texel clamping + // (ensure that ellipse is no smaller than a texel) + double b_t = squared(0.5 / faceRes.u()); + + // add blur + double b_b = 0.25 * blur * blur; + double b = PtexUtils::max(b_b, PtexUtils::max(b_e, b_t)); + Ac += b; + Cc += b; + + // compute minor radius + double m = sqrt(2*(Ac*Cc - 0.25*Bc*Bc) / (Ac + Cc + X)); + + // choose desired resolution + int reslog2 = PtexUtils::max(0, int(ceil(log2(0.5/m)))); + + // convert back to triangular domain + A = (4/3.0) * Ac; + B = (2/sqrt3) * Bc + A; + C = -0.25 * A + 0.5 * B + Cc; + + // scale by kernel width + double scale = PtexTriangleKernelWidth * PtexTriangleKernelWidth; + A *= scale; + B *= scale; + C *= scale; + + // find u,v,w extents + double uw = PtexUtils::min(sqrt(C), 1.0); + double vw = PtexUtils::min(sqrt(A), 1.0); + double ww = PtexUtils::min(sqrt(A-B+C), 1.0); + + // init kernel + double w = 1 - u - v; + k.set(Res(reslog2, reslog2), u, v, u-uw, v-vw, w-ww, u+uw, v+vw, w+ww, A, B, C); +} + + +void PtexTriangleFilter::splitAndApply(PtexTriangleKernel& k, int faceid, const Ptex::FaceInfo& f) +{ + // do we need to split? if so, split kernel and apply across edge(s) + if (k.u1 < 0 && f.adjface(2) >= 0) { + PtexTriangleKernel ka; + k.splitU(ka); + applyAcrossEdge(ka, f, 2); + } + if (k.v1 < 0 && f.adjface(0) >= 0) { + PtexTriangleKernel ka; + k.splitV(ka); + applyAcrossEdge(ka, f, 0); + } + if (k.w1 < 0 && f.adjface(1) >= 0) { + PtexTriangleKernel ka; + k.splitW(ka); + applyAcrossEdge(ka, f, 1); + } + // apply to local face + apply(k, faceid, f); +} + + +void PtexTriangleFilter::applyAcrossEdge(PtexTriangleKernel& k, + const Ptex::FaceInfo& f, int eid) +{ + int afid = f.adjface(eid), aeid = f.adjedge(eid); + const Ptex::FaceInfo& af = _tx->getFaceInfo(afid); + k.reorient(eid, aeid); + splitAndApply(k, afid, af); +} + + +void PtexTriangleFilter::apply(PtexTriangleKernel& k, int faceid, const Ptex::FaceInfo& f) +{ + // clamp kernel face (resolution and extent) + k.clampRes(f.res); + k.clampExtent(); + + // build kernel iterators + PtexTriangleKernelIter keven, kodd; + k.getIterators(keven, kodd); + if (!keven.valid && !kodd.valid) return; + + // get face data, and apply + PtexPtr<PtexFaceData> dh ( _tx->getData(faceid, k.res) ); + if (!dh) return; + + if (keven.valid) applyIter(keven, dh); + if (kodd.valid) applyIter(kodd, dh); +} + + +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 - 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 - 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; + } +} diff --git a/extern/ptex/src/ptex/PtexTriangleFilter.h b/extern/ptex/src/ptex/PtexTriangleFilter.h new file mode 100644 index 00000000000..c02161c1121 --- /dev/null +++ b/extern/ptex/src/ptex/PtexTriangleFilter.h @@ -0,0 +1,78 @@ +#ifndef PtexTriangleFilter_h +#define PtexTriangleFilter_h + +/* +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 "Ptexture.h" +class PtexTriangleKernel; +class PtexTriangleKernelIter; + +class PtexTriangleFilter : public PtexFilter, public Ptex +{ + public: + PtexTriangleFilter(PtexTexture* tx, const PtexFilter::Options& opts ) : + _tx(tx), _options(opts), _result(0), _weight(0), + _firstChanOffset(0), _nchan(0), _ntxchan(0), + _dt((DataType)0) {} + virtual void release() { delete this; } + virtual void 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); + + protected: + void buildKernel(PtexTriangleKernel& k, float u, float v, + float uw1, float vw1, float uw2, float vw2, + float width, float blur, Res faceRes); + + void splitAndApply(PtexTriangleKernel& k, int faceid, const Ptex::FaceInfo& f); + void applyAcrossEdge(PtexTriangleKernel& k, const Ptex::FaceInfo& f, int eid); + void apply(PtexTriangleKernel& k, int faceid, const Ptex::FaceInfo& f); + void applyIter(PtexTriangleKernelIter& k, PtexFaceData* dh); + + virtual ~PtexTriangleFilter() {} + + PtexTexture* _tx; // texture being evaluated + Options _options; // options + double* _result; // temp result + double _weight; // accumulated weight of data in _result + int _firstChanOffset; // byte offset of first channel to eval + int _nchan; // number of channels to eval + int _ntxchan; // number of channels in texture + DataType _dt; // data type of texture +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexTriangleKernel.cpp b/extern/ptex/src/ptex/PtexTriangleKernel.cpp new file mode 100644 index 00000000000..3e7c2ba091d --- /dev/null +++ b/extern/ptex/src/ptex/PtexTriangleKernel.cpp @@ -0,0 +1,179 @@ +/* +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 "PtexUtils.h" +#include "PtexHalf.h" +#include "PtexTriangleKernel.h" + + +namespace { + inline double gaussian(double x_squared) + { + static const double scale = -0.5 * (PtexTriangleKernelWidth * PtexTriangleKernelWidth); + return exp(scale * x_squared); + } +} + + +namespace { + + // apply to 1..4 channels (unrolled channel loop) of packed data (nTxChan==nChan) + // the ellipse equation, Q, is calculated via finite differences (Heckbert '89 pg 57) + template<class T, int nChan> + void Apply(PtexTriangleKernelIter& k, double* result, void* data, int /*nChan*/, int /*nTxChan*/) + { + int nTxChan = nChan; + double DDQ = 2*k.A; + for (int vi = k.v1; vi != k.v2; vi++) { + int xw = k.rowlen - vi; + int x1 = PtexUtils::max(k.u1, xw-k.w2); + int x2 = PtexUtils::min(k.u2, xw-k.w1); + double U = x1 - k.u; + double V = vi - k.v; + double DQ = k.A*(2*U+1)+k.B*V; + double Q = k.A*U*U + (k.B*U + k.C*V)*V; + T* p = (T*)data + (vi * k.rowlen + x1) * nTxChan; + T* pEnd = p + (x2-x1)*nTxChan; + for (; p < pEnd; p += nTxChan) { + if (Q < 1) { + double weight = gaussian(Q)*k.wscale; + k.weight += weight; + PtexUtils::VecAccum<T,nChan>()(result, p, weight); + } + Q += DQ; + DQ += DDQ; + } + } + } + + // apply to 1..4 channels (unrolled channel loop) w/ pixel stride + template<class T, int nChan> + void ApplyS(PtexTriangleKernelIter& k, double* result, void* data, int /*nChan*/, int nTxChan) + { + double DDQ = 2*k.A; + for (int vi = k.v1; vi != k.v2; vi++) { + int xw = k.rowlen - vi; + int x1 = PtexUtils::max(k.u1, xw-k.w2); + int x2 = PtexUtils::min(k.u2, xw-k.w1); + double U = x1 - k.u; + double V = vi - k.v; + double DQ = k.A*(2*U+1)+k.B*V; + double Q = k.A*U*U + (k.B*U + k.C*V)*V; + T* p = (T*)data + (vi * k.rowlen + x1) * nTxChan; + T* pEnd = p + (x2-x1)*nTxChan; + for (; p < pEnd; p += nTxChan) { + if (Q < 1) { + double weight = gaussian(Q)*k.wscale; + k.weight += weight; + PtexUtils::VecAccum<T,nChan>()(result, p, weight); + } + Q += DQ; + DQ += DDQ; + } + } + } + + // apply to N channels (general case) + template<class T> + void ApplyN(PtexTriangleKernelIter& k, double* result, void* data, int nChan, int nTxChan) + { + double DDQ = 2*k.A; + for (int vi = k.v1; vi != k.v2; vi++) { + int xw = k.rowlen - vi; + int x1 = PtexUtils::max(k.u1, xw-k.w2); + int x2 = PtexUtils::min(k.u2, xw-k.w1); + double U = x1 - k.u; + double V = vi - k.v; + double DQ = k.A*(2*U+1)+k.B*V; + double Q = k.A*U*U + (k.B*U + k.C*V)*V; + T* p = (T*)data + (vi * k.rowlen + x1) * nTxChan; + T* pEnd = p + (x2-x1)*nTxChan; + for (; p < pEnd; p += nTxChan) { + if (Q < 1) { + double weight = gaussian(Q)*k.wscale; + k.weight += weight; + PtexUtils::VecAccumN<T>()(result, p, nChan, weight); + } + Q += DQ; + DQ += DDQ; + } + } + } +} + + +PtexTriangleKernelIter::ApplyFn +PtexTriangleKernelIter::applyFunctions[] = { + // nChan == nTxChan + ApplyN<uint8_t>, ApplyN<uint16_t>, ApplyN<PtexHalf>, ApplyN<float>, + Apply<uint8_t,1>, Apply<uint16_t,1>, Apply<PtexHalf,1>, Apply<float,1>, + Apply<uint8_t,2>, Apply<uint16_t,2>, Apply<PtexHalf,2>, Apply<float,2>, + Apply<uint8_t,3>, Apply<uint16_t,3>, Apply<PtexHalf,3>, Apply<float,3>, + Apply<uint8_t,4>, Apply<uint16_t,4>, Apply<PtexHalf,4>, Apply<float,4>, + + // nChan != nTxChan (need pixel stride) + ApplyN<uint8_t>, ApplyN<uint16_t>, ApplyN<PtexHalf>, ApplyN<float>, + ApplyS<uint8_t,1>, ApplyS<uint16_t,1>, ApplyS<PtexHalf,1>, ApplyS<float,1>, + ApplyS<uint8_t,2>, ApplyS<uint16_t,2>, ApplyS<PtexHalf,2>, ApplyS<float,2>, + ApplyS<uint8_t,3>, ApplyS<uint16_t,3>, ApplyS<PtexHalf,3>, ApplyS<float,3>, + ApplyS<uint8_t,4>, ApplyS<uint16_t,4>, ApplyS<PtexHalf,4>, ApplyS<float,4>, +}; + + +void PtexTriangleKernelIter::applyConst(double* dst, void* data, DataType dt, int nChan) +{ + // iterate over texel locations and calculate weight as if texture weren't const + double DDQ = 2*A; + for (int vi = v1; vi != v2; vi++) { + int xw = rowlen - vi; + int x1 = PtexUtils::max(u1, xw-w2); + int x2 = PtexUtils::min(u2, xw-w1); + double U = x1 - u; + double V = vi - v; + double DQ = A*(2*U+1)+B*V; + double Q = A*U*U + (B*U + C*V)*V; + for (int x = x1; x < x2; x++) { + if (Q < 1) { + weight += gaussian(Q)*wscale; + } + Q += DQ; + DQ += DDQ; + } + } + + // apply weight to single texel value + PtexUtils::applyConst(weight, dst, data, dt, nChan); +} diff --git a/extern/ptex/src/ptex/PtexTriangleKernel.h b/extern/ptex/src/ptex/PtexTriangleKernel.h new file mode 100644 index 00000000000..696d993ee14 --- /dev/null +++ b/extern/ptex/src/ptex/PtexTriangleKernel.h @@ -0,0 +1,227 @@ +#ifndef PtexTriangleKernel_h +#define PtexTriangleKernel_h + +/* +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 <assert.h> +#include <algorithm> +#include <numeric> +#include "Ptexture.h" +#include "PtexUtils.h" + +// kernel width as a multiple of filter width (should be between 3 and 4) +// for values below 3, the gaussian is not close to zero and a contour will be formed +// larger values are more expensive (proportional to width-squared) +static const float PtexTriangleKernelWidth = 3.5; + + +/// Triangle filter kernel iterator (in texel coords) +class PtexTriangleKernelIter : public Ptex { + public: + int rowlen; // row length (in u) + double u, v; // uv center in texels + int u1, v1, w1; // uvw lower bounds + int u2, v2, w2; // uvw upper bounds + double A,B,C; // ellipse coefficients (F = 1) + bool valid; // footprint is valid (non-empty) + double wscale; // amount to scale weights by (proportional to texel area) + double weight; // accumulated weight + + void apply(double* dst, void* data, DataType dt, int nChan, int nTxChan) + { + // dispatch specialized apply function + ApplyFn fn = applyFunctions[(nChan!=nTxChan)*20 + ((unsigned)nChan<=4)*nChan*4 + dt]; + fn(*this, dst, data, nChan, nTxChan); + } + + void applyConst(double* dst, void* data, DataType dt, int nChan); + + private: + + typedef void (*ApplyFn)(PtexTriangleKernelIter& k, double* dst, void* data, int nChan, int nTxChan); + static ApplyFn applyFunctions[40]; +}; + + +/// Triangle filter kernel (in normalized triangle coords) +class PtexTriangleKernel : public Ptex { + public: + Res res; // desired resolution + double u, v; // uv filter center + double u1, v1, w1; // uvw lower bounds + double u2, v2, w2; // uvw upper bounds + double A,B,C; // ellipse coefficients (F = A*C-B*B/4) + + void set(Res resVal, double uVal, double vVal, + double u1Val, double v1Val, double w1Val, + double u2Val, double v2Val, double w2Val, + double AVal, double BVal, double CVal) + { + res = resVal; + u = uVal; v = vVal; + u1 = u1Val; v1 = v1Val; w1 = w1Val; + u2 = u2Val; v2 = v2Val; w2 = w2Val; + A = AVal; B = BVal; C = CVal; + } + + void set(double uVal, double vVal, + double u1Val, double v1Val, double w1Val, + double u2Val, double v2Val, double w2Val) + { + u = uVal; v = vVal; + u1 = u1Val; v1 = v1Val; w1 = w1Val; + u2 = u2Val; v2 = v2Val; w2 = w2Val; + } + + void setABC(double AVal, double BVal, double CVal) + { + A = AVal; B = BVal; C = CVal; + } + + void splitU(PtexTriangleKernel& ka) + { + ka = *this; + u1 = 0; + ka.u2 = 0; + } + + void splitV(PtexTriangleKernel& ka) + { + ka = *this; + v1 = 0; + ka.v2 = 0; + } + + void splitW(PtexTriangleKernel& ka) + { + ka = *this; + w1 = 0; + ka.w2 = 0; + } + + void rotate1() + { + // rotate ellipse where u'=w, v'=u, w'=v + // (derived by converting to Barycentric form, rotating, and converting back) + setABC(C, 2*C-B, A+C-B); + } + + void rotate2() + { + // rotate ellipse where u'=v, v'=w, w'=u + // (derived by converting to Barycentric form, rotating, and converting back) + setABC(A+C-B, 2*A-B, A); + } + + void reorient(int eid, int aeid) + { + double w = 1-u-v; + +#define C(eid, aeid) (eid*3 + aeid) + switch (C(eid, aeid)) { + case C(0, 0): set(1-u, -v, 1-u2, -v2, 1-w2, 1-u1, -v1, 1-w1); break; + case C(0, 1): set(1-w, 1-u, 1-w2, 1-u2, -v2, 1-w1, 1-u1, -v1); rotate1(); break; + case C(0, 2): set( -v, 1-w, -v2, 1-w2, 1-u2, -v1, 1-w1, 1-u1); rotate2(); break; + + case C(1, 0): set(1-v, -w, 1-v2, -w2, 1-u2, 1-v1, -w1, 1-u1); rotate2(); break; + case C(1, 1): set(1-u, 1-v, 1-u2, 1-v2, -w2, 1-u1, 1-v1, -w1); break; + case C(1, 2): set( -w, 1-u, -w2, 1-u2, 1-v2, -w1, 1-u1, 1-v1); rotate1(); break; + + case C(2, 0): set(1-w, -u, 1-w2, -u2, 1-v2, 1-w1, -u1, 1-v1); rotate1(); break; + case C(2, 1): set(1-v, 1-w, 1-v2, 1-w2, -u2, 1-v1, 1-w1, -u1); rotate2(); break; + case C(2, 2): set( -u, 1-v, -u2, 1-v2, 1-w2, -u1, 1-v1, 1-w1); break; +#undef C + } + } + + void clampRes(Res fres) + { + res.ulog2 = PtexUtils::min(res.ulog2, fres.ulog2); + res.vlog2 = res.ulog2; + } + + void clampExtent() + { + u1 = PtexUtils::max(u1, 0.0); + v1 = PtexUtils::max(v1, 0.0); + w1 = PtexUtils::max(w1, 0.0); + u2 = PtexUtils::min(u2, 1-(v1+w1)); + v2 = PtexUtils::min(v2, 1-(w1+u1)); + w2 = PtexUtils::min(w2, 1-(u1+v1)); + } + + void getIterators(PtexTriangleKernelIter& ke, PtexTriangleKernelIter& ko) + { + int resu = res.u(); + + // normalize coefficients for texel units + double Finv = 1.0/(resu*resu*(A*C - 0.25 * B * B)); + double Ak = A*Finv, Bk = B*Finv, Ck = C*Finv; + + // build even iterator + ke.rowlen = resu; + ke.wscale = 1.0/(resu*resu); + double scale = ke.rowlen; + ke.u = u * scale - 1/3.0; + ke.v = v * scale - 1/3.0; + ke.u1 = int(ceil(u1 * scale - 1/3.0)); + ke.v1 = int(ceil(v1 * scale - 1/3.0)); + ke.w1 = int(ceil(w1 * scale - 1/3.0)); + ke.u2 = int(ceil(u2 * scale - 1/3.0)); + ke.v2 = int(ceil(v2 * scale - 1/3.0)); + ke.w2 = int(ceil(w2 * scale - 1/3.0)); + ke.A = Ak; ke.B = Bk; ke.C = Ck; + ke.valid = (ke.u2 > ke.u1 && ke.v2 > ke.v1 && ke.w2 > ke.w1); + ke.weight = 0; + + // build odd iterator: flip kernel across diagonal (u = 1-v, v = 1-u, w = -w) + ko.rowlen = ke.rowlen; + ko.wscale = ke.wscale; + ko.u = (1-v) * scale - 1/3.0; + ko.v = (1-u) * scale - 1/3.0; + ko.u1 = int(ceil((1-v2) * scale - 1/3.0)); + ko.v1 = int(ceil((1-u2) * scale - 1/3.0)); + ko.w1 = int(ceil(( -w2) * scale - 1/3.0)); + ko.u2 = int(ceil((1-v1) * scale - 1/3.0)); + ko.v2 = int(ceil((1-u1) * scale - 1/3.0)); + ko.w2 = int(ceil(( -w1) * scale - 1/3.0)); + ko.A = Ck; ko.B = Bk; ko.C = Ak; + ko.valid = (ko.u2 > ko.u1 && ko.v2 > ko.v1 && ko.w2 > ko.w1); + ko.weight = 0; + } +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexUtils.cpp b/extern/ptex/src/ptex/PtexUtils.cpp new file mode 100644 index 00000000000..59cc7b62021 --- /dev/null +++ b/extern/ptex/src/ptex/PtexUtils.cpp @@ -0,0 +1,677 @@ +/* +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 <algorithm> +#include <vector> +#include <stdlib.h> +#include <string.h> + +#include "PtexHalf.h" +#include "PtexUtils.h" + +const char* Ptex::MeshTypeName(MeshType mt) +{ + static const char* names[] = { "triangle", "quad" }; + if (mt < 0 || mt >= int(sizeof(names)/sizeof(const char*))) + return "(invalid mesh type)"; + return names[mt]; +} + + +const char* Ptex::DataTypeName(DataType dt) +{ + static const char* names[] = { "uint8", "uint16", "float16", "float32" }; + if (dt < 0 || dt >= int(sizeof(names)/sizeof(const char*))) + return "(invalid data type)"; + return names[dt]; +} + + +const char* Ptex::BorderModeName(BorderMode m) +{ + static const char* names[] = { "clamp", "black", "periodic" }; + if (m < 0 || m >= int(sizeof(names)/sizeof(const char*))) + return "(invalid border mode)"; + return names[m]; +} + + +const char* Ptex::EdgeIdName(EdgeId eid) +{ + static const char* names[] = { "bottom", "right", "top", "left" }; + if (eid < 0 || eid >= int(sizeof(names)/sizeof(const char*))) + return "(invalid edge id)"; + return names[eid]; +} + + +const char* Ptex::MetaDataTypeName(MetaDataType mdt) +{ + static const char* names[] = { "string", "int8", "int16", "int32", "float", "double" }; + if (mdt < 0 || mdt >= int(sizeof(names)/sizeof(const char*))) + return "(invalid meta data type)"; + return names[mdt]; +} + + + +namespace { + template<typename DST, typename SRC> + void ConvertArrayClamped(DST* dst, SRC* src, int numChannels, double scale, double round=0) + { + for (int i = 0; i < numChannels; i++) + dst[i] = DST(PtexUtils::clamp(src[i], 0.0f, 1.0f) * scale + round); + } + + template<typename DST, typename SRC> + void ConvertArray(DST* dst, SRC* src, int numChannels, double scale, double round=0) + { + for (int i = 0; i < numChannels; i++) dst[i] = DST(src[i] * scale + round); + } +} + +void Ptex::ConvertToFloat(float* dst, const void* src, Ptex::DataType dt, int numChannels) +{ + switch (dt) { + case dt_uint8: ConvertArray(dst, (uint8_t*)src, numChannels, 1/255.0); break; + case dt_uint16: ConvertArray(dst, (uint16_t*)src, numChannels, 1/65535.0); break; + case dt_half: ConvertArray(dst, (PtexHalf*)src, numChannels, 1.0); break; + case dt_float: memcpy(dst, src, sizeof(float)*numChannels); break; + } +} + + +void Ptex::ConvertFromFloat(void* dst, const float* src, Ptex::DataType dt, int numChannels) +{ + switch (dt) { + case dt_uint8: ConvertArrayClamped((uint8_t*)dst, src, numChannels, 255.0, 0.5); break; + case dt_uint16: ConvertArrayClamped((uint16_t*)dst, src, numChannels, 65535.0, 0.5); break; + case dt_half: ConvertArray((PtexHalf*)dst, src, numChannels, 1.0); break; + case dt_float: memcpy(dst, src, sizeof(float)*numChannels); break; + } +} + + +bool PtexUtils::isConstant(const void* data, int stride, int ures, int vres, + int pixelSize) +{ + int rowlen = pixelSize * ures; + const char* p = (const char*) data + stride; + + // compare each row with the first + for (int i = 1; i < vres; i++, p += stride) + if (0 != memcmp(data, p, rowlen)) return 0; + + // make sure first row is constant + p = (const char*) data + pixelSize; + for (int i = 1; i < ures; i++, p += pixelSize) + if (0 != memcmp(data, p, pixelSize)) return 0; + + return 1; +} + + +namespace { + template<typename T> + inline void interleave(const T* src, int sstride, int uw, int vw, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + // for each channel + for (T* dstend = dst + nchan; dst != dstend; dst++) { + // for each row + T* drow = dst; + for (const T* rowend = src + sstride*vw; src != rowend; + src += sstride, drow += dstride) { + // copy each pixel across the row + T* dp = drow; + for (const T* sp = src, * end = sp + uw; sp != end; dp += nchan) + *dp = *sp++; + } + } + } +} + + +void PtexUtils::interleave(const void* src, int sstride, int uw, int vw, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::interleave((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: + case dt_uint16: ::interleave((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::interleave((const float*) src, sstride, uw, vw, + (float*) dst, dstride, nchan); break; + } +} + +namespace { + template<typename T> + inline void deinterleave(const T* src, int sstride, int uw, int vw, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + // for each channel + for (const T* srcend = src + nchan; src != srcend; src++) { + // for each row + const T* srow = src; + for (const T* rowend = srow + sstride*vw; srow != rowend; + srow += sstride, dst += dstride) { + // copy each pixel across the row + const T* sp = srow; + for (T* dp = dst, * end = dp + uw; dp != end; sp += nchan) + *dp++ = *sp; + } + } + } +} + + +void PtexUtils::deinterleave(const void* src, int sstride, int uw, int vw, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::deinterleave((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: + case dt_uint16: ::deinterleave((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::deinterleave((const float*) src, sstride, uw, vw, + (float*) dst, dstride, nchan); break; + } +} + + +namespace { + template<typename T> + void encodeDifference(T* data, int size) + { + size /= sizeof(T); + T* p = (T*) data, * end = p + size, tmp, prev = 0; + while (p != end) { tmp = prev; prev = *p; *p++ -= tmp; } + } +} + +void PtexUtils::encodeDifference(void* data, int size, DataType dt) +{ + switch (dt) { + case dt_uint8: ::encodeDifference((uint8_t*) data, size); break; + case dt_uint16: ::encodeDifference((uint16_t*) data, size); break; + default: break; // skip other types + } +} + + +namespace { + template<typename T> + void decodeDifference(T* data, int size) + { + size /= sizeof(T); + T* p = (T*) data, * end = p + size, prev = 0; + while (p != end) { *p += prev; prev = *p++; } + } +} + +void PtexUtils::decodeDifference(void* data, int size, DataType dt) +{ + switch (dt) { + case dt_uint8: ::decodeDifference((uint8_t*) data, size); break; + case dt_uint16: ::decodeDifference((uint16_t*) data, size); break; + default: break; // skip other types + } +} + + +namespace { + template<typename T> + inline void reduce(const T* src, int sstride, int uw, int vw, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + int rowlen = uw*nchan; + int srowskip = 2*sstride - rowlen; + int drowskip = dstride - rowlen/2; + for (const T* end = src + vw*sstride; src != end; + src += srowskip, dst += drowskip) + for (const T* rowend = src + rowlen; src != rowend; src += nchan) + for (const T* pixend = src+nchan; src != pixend; src++) + *dst++ = T(0.25 * (src[0] + src[nchan] + + src[sstride] + src[sstride+nchan])); + } +} + +void PtexUtils::reduce(const void* src, int sstride, int uw, int vw, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::reduce((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: ::reduce((const PtexHalf*) src, sstride, uw, vw, + (PtexHalf*) dst, dstride, nchan); break; + case dt_uint16: ::reduce((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::reduce((const float*) src, sstride, uw, vw, + (float*) dst, dstride, nchan); break; + } +} + + +namespace { + template<typename T> + inline void reduceu(const T* src, int sstride, int uw, int vw, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + int rowlen = uw*nchan; + int srowskip = sstride - rowlen; + int drowskip = dstride - rowlen/2; + for (const T* end = src + vw*sstride; src != end; + src += srowskip, dst += drowskip) + for (const T* rowend = src + rowlen; src != rowend; src += nchan) + for (const T* pixend = src+nchan; src != pixend; src++) + *dst++ = T(0.5 * (src[0] + src[nchan])); + } +} + +void PtexUtils::reduceu(const void* src, int sstride, int uw, int vw, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::reduceu((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: ::reduceu((const PtexHalf*) src, sstride, uw, vw, + (PtexHalf*) dst, dstride, nchan); break; + case dt_uint16: ::reduceu((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::reduceu((const float*) src, sstride, uw, vw, + (float*) dst, dstride, nchan); break; + } +} + + +namespace { + template<typename T> + inline void reducev(const T* src, int sstride, int uw, int vw, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + int rowlen = uw*nchan; + int srowskip = 2*sstride - rowlen; + int drowskip = dstride - rowlen; + for (const T* end = src + vw*sstride; src != end; + src += srowskip, dst += drowskip) + for (const T* rowend = src + rowlen; src != rowend; src++) + *dst++ = T(0.5 * (src[0] + src[sstride])); + } +} + +void PtexUtils::reducev(const void* src, int sstride, int uw, int vw, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::reducev((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: ::reducev((const PtexHalf*) src, sstride, uw, vw, + (PtexHalf*) dst, dstride, nchan); break; + case dt_uint16: ::reducev((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::reducev((const float*) src, sstride, uw, vw, + (float*) dst, dstride, nchan); break; + } +} + + + +namespace { + // generate a reduction of a packed-triangle texture + // note: this method won't work for tiled textures + template<typename T> + inline void reduceTri(const T* src, int sstride, int w, int /*vw*/, + T* dst, int dstride, int nchan) + { + sstride /= sizeof(T); + dstride /= sizeof(T); + int rowlen = w*nchan; + const T* src2 = src + (w-1) * sstride + rowlen - nchan; + int srowinc2 = -2*sstride - nchan; + int srowskip = 2*sstride - rowlen; + int srowskip2 = w*sstride - 2 * nchan; + int drowskip = dstride - rowlen/2; + for (const T* end = src + w*sstride; src != end; + src += srowskip, src2 += srowskip2, dst += drowskip) + for (const T* rowend = src + rowlen; src != rowend; src += nchan, src2 += srowinc2) + for (const T* pixend = src+nchan; src != pixend; src++, src2++) + *dst++ = T(0.25 * (src[0] + src[nchan] + src[sstride] + src2[0])); + } +} + +void PtexUtils::reduceTri(const void* src, int sstride, int w, int /*vw*/, + void* dst, int dstride, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::reduceTri((const uint8_t*) src, sstride, w, 0, + (uint8_t*) dst, dstride, nchan); break; + case dt_half: ::reduceTri((const PtexHalf*) src, sstride, w, 0, + (PtexHalf*) dst, dstride, nchan); break; + case dt_uint16: ::reduceTri((const uint16_t*) src, sstride, w, 0, + (uint16_t*) dst, dstride, nchan); break; + case dt_float: ::reduceTri((const float*) src, sstride, w, 0, + (float*) dst, dstride, nchan); break; + } +} + + +void PtexUtils::fill(const void* src, void* dst, int dstride, + int ures, int vres, int pixelsize) +{ + // fill first row + int rowlen = ures*pixelsize; + char* ptr = (char*) dst; + char* end = ptr + rowlen; + for (; ptr != end; ptr += pixelsize) memcpy(ptr, src, pixelsize); + + // fill remaining rows from first row + ptr = (char*) dst + dstride; + end = (char*) dst + vres*dstride; + for (; ptr != end; ptr += dstride) memcpy(ptr, dst, rowlen); +} + + +void PtexUtils::copy(const void* src, int sstride, void* dst, int dstride, + int vres, int rowlen) +{ + // regular non-tiled case + if (sstride == rowlen && dstride == rowlen) { + // packed case - copy in single block + memcpy(dst, src, vres*rowlen); + } else { + // copy a row at a time + char* sptr = (char*) src; + char* dptr = (char*) dst; + for (char* end = sptr + vres*sstride; sptr != end;) { + memcpy(dptr, sptr, rowlen); + dptr += dstride; + sptr += sstride; + } + } +} + + +namespace { + template<typename T> + inline void blend(const T* src, float weight, T* dst, int rowlen, int nchan) + { + for (const T* end = src + rowlen * nchan; src != end; dst++) + *dst = *dst + T(weight * *src++); + } + + template<typename T> + inline void blendflip(const T* src, float weight, T* dst, int rowlen, int nchan) + { + dst += (rowlen-1) * nchan; + for (const T* end = src + rowlen * nchan; src != end;) { + for (int i = 0; i < nchan; i++, dst++) + *dst = *dst + T(weight * *src++); + dst -= nchan*2; + } + } +} + + +void PtexUtils::blend(const void* src, float weight, void* dst, bool flip, + int rowlen, DataType dt, int nchan) +{ + switch ((dt<<1) | int(flip)) { + case (dt_uint8<<1): ::blend((const uint8_t*) src, weight, + (uint8_t*) dst, rowlen, nchan); break; + case (dt_uint8<<1 | 1): ::blendflip((const uint8_t*) src, weight, + (uint8_t*) dst, rowlen, nchan); break; + case (dt_half<<1): ::blend((const PtexHalf*) src, weight, + (PtexHalf*) dst, rowlen, nchan); break; + case (dt_half<<1 | 1): ::blendflip((const PtexHalf*) src, weight, + (PtexHalf*) dst, rowlen, nchan); break; + case (dt_uint16<<1): ::blend((const uint16_t*) src, weight, + (uint16_t*) dst, rowlen, nchan); break; + case (dt_uint16<<1 | 1): ::blendflip((const uint16_t*) src, weight, + (uint16_t*) dst, rowlen, nchan); break; + case (dt_float<<1): ::blend((const float*) src, weight, + (float*) dst, rowlen, nchan); break; + case (dt_float<<1 | 1): ::blendflip((const float*) src, weight, + (float*) dst, rowlen, nchan); break; + } +} + + +namespace { + template<typename T> + inline void average(const T* src, int sstride, int uw, int vw, + T* dst, int nchan) + { + float* buff = (float*) alloca(nchan*sizeof(float)); + memset(buff, 0, nchan*sizeof(float)); + sstride /= sizeof(T); + int rowlen = uw*nchan; + int rowskip = sstride - rowlen; + for (const T* end = src + vw*sstride; src != end; src += rowskip) + for (const T* rowend = src + rowlen; src != rowend;) + for (int i = 0; i < nchan; i++) buff[i] += *src++; + double scale = 1.0/(uw*vw); + for (int i = 0; i < nchan; i++) dst[i] = T(buff[i]*scale); + } +} + +void PtexUtils::average(const void* src, int sstride, int uw, int vw, + void* dst, DataType dt, int nchan) +{ + switch (dt) { + case dt_uint8: ::average((const uint8_t*) src, sstride, uw, vw, + (uint8_t*) dst, nchan); break; + case dt_half: ::average((const PtexHalf*) src, sstride, uw, vw, + (PtexHalf*) dst, nchan); break; + case dt_uint16: ::average((const uint16_t*) src, sstride, uw, vw, + (uint16_t*) dst, nchan); break; + case dt_float: ::average((const float*) src, sstride, uw, vw, + (float*) dst, nchan); break; + } +} + + +namespace { + struct CompareRfaceIds { + const Ptex::FaceInfo* faces; + CompareRfaceIds(const Ptex::FaceInfo* faces) : faces(faces) {} + bool operator() (uint32_t faceid1, uint32_t faceid2) + { + const Ptex::FaceInfo& f1 = faces[faceid1]; + const Ptex::FaceInfo& f2 = faces[faceid2]; + int min1 = f1.isConstant() ? 1 : PtexUtils::min(f1.res.ulog2, f1.res.vlog2); + int min2 = f2.isConstant() ? 1 : PtexUtils::min(f2.res.ulog2, f2.res.vlog2); + return min1 > min2; + } + }; +} + + +namespace { + template<typename T> + inline void multalpha(T* data, int npixels, int nchannels, int alphachan, double scale) + { + int alphaoffset; // offset to alpha chan from data ptr + int nchanmult; // number of channels to alpha-multiply + if (alphachan == 0) { + // first channel is alpha chan: mult the rest of the channels + data++; + alphaoffset = -1; + nchanmult = nchannels - 1; + } + else { + // mult all channels up to alpha chan + alphaoffset = alphachan; + nchanmult = alphachan; + } + + for (T* end = data + npixels*nchannels; data != end; data += nchannels) { + double aval = scale * data[alphaoffset]; + for (int i = 0; i < nchanmult; i++) data[i] = T(data[i] * aval); + } + } +} + +void PtexUtils::multalpha(void* data, int npixels, DataType dt, int nchannels, int alphachan) +{ + double scale = OneValueInv(dt); + switch(dt) { + case dt_uint8: ::multalpha((uint8_t*) data, npixels, nchannels, alphachan, scale); break; + case dt_uint16: ::multalpha((uint16_t*) data, npixels, nchannels, alphachan, scale); break; + case dt_half: ::multalpha((PtexHalf*) data, npixels, nchannels, alphachan, scale); break; + case dt_float: ::multalpha((float*) data, npixels, nchannels, alphachan, scale); break; + } +} + + +namespace { + template<typename T> + inline void divalpha(T* data, int npixels, int nchannels, int alphachan, double scale) + { + int alphaoffset; // offset to alpha chan from data ptr + int nchandiv; // number of channels to alpha-divide + if (alphachan == 0) { + // first channel is alpha chan: div the rest of the channels + data++; + alphaoffset = -1; + nchandiv = nchannels - 1; + } + else { + // div all channels up to alpha chan + alphaoffset = alphachan; + nchandiv = alphachan; + } + + for (T* end = data + npixels*nchannels; data != end; data += nchannels) { + T alpha = data[alphaoffset]; + if (!alpha) continue; // don't divide by zero! + double aval = scale / alpha; + for (int i = 0; i < nchandiv; i++) data[i] = T(data[i] * aval); + } + } +} + +void PtexUtils::divalpha(void* data, int npixels, DataType dt, int nchannels, int alphachan) +{ + double scale = OneValue(dt); + switch(dt) { + case dt_uint8: ::divalpha((uint8_t*) data, npixels, nchannels, alphachan, scale); break; + case dt_uint16: ::divalpha((uint16_t*) data, npixels, nchannels, alphachan, scale); break; + case dt_half: ::divalpha((PtexHalf*) data, npixels, nchannels, alphachan, scale); break; + case dt_float: ::divalpha((float*) data, npixels, nchannels, alphachan, scale); break; + } +} + + +void PtexUtils::genRfaceids(const FaceInfo* faces, int nfaces, + uint32_t* rfaceids, uint32_t* faceids) +{ + // stable_sort faceids by smaller dimension (u or v) in descending order + // treat const faces as having res of 1 + + // init faceids + for (int i = 0; i < nfaces; i++) faceids[i] = i; + + // sort faceids by rfaceid + std::stable_sort(faceids, faceids + nfaces, CompareRfaceIds(faces)); + + // generate mapping from faceid to rfaceid + for (int i = 0; i < nfaces; i++) { + // note: i is the rfaceid + rfaceids[faceids[i]] = i; + } +} + +namespace { + // apply to 1..4 channels, unrolled + template<class T, int nChan> + void ApplyConst(double weight, double* dst, void* data, int /*nChan*/) + { + // dst[i] += data[i] * weight for i in {0..n-1} + PtexUtils::VecAccum<T,nChan>()(dst, (T*) data, weight); + } + + // apply to N channels (general case) + template<class T> + void ApplyConstN(double weight, double* dst, void* data, int nChan) + { + // dst[i] += data[i] * weight for i in {0..n-1} + PtexUtils::VecAccumN<T>()(dst, (T*) data, nChan, weight); + } +} + +PtexUtils::ApplyConstFn +PtexUtils::applyConstFunctions[20] = { + ApplyConstN<uint8_t>, ApplyConstN<uint16_t>, ApplyConstN<PtexHalf>, ApplyConstN<float>, + ApplyConst<uint8_t,1>, ApplyConst<uint16_t,1>, ApplyConst<PtexHalf,1>, ApplyConst<float,1>, + ApplyConst<uint8_t,2>, ApplyConst<uint16_t,2>, ApplyConst<PtexHalf,2>, ApplyConst<float,2>, + ApplyConst<uint8_t,3>, ApplyConst<uint16_t,3>, ApplyConst<PtexHalf,3>, ApplyConst<float,3>, + ApplyConst<uint8_t,4>, ApplyConst<uint16_t,4>, ApplyConst<PtexHalf,4>, ApplyConst<float,4>, +}; + +#ifndef PTEX_USE_STDSTRING +Ptex::String::~String() +{ + if (_str) free(_str); +} + + +Ptex::String& Ptex::String::operator=(const char* str) +{ + if (_str) free(_str); + _str = str ? strdup(str) : 0; + return *this; +} + +std::ostream& operator << (std::ostream& stream, const Ptex::String& str) +{ + stream << str.c_str(); + return stream; +} +#endif + diff --git a/extern/ptex/src/ptex/PtexUtils.h b/extern/ptex/src/ptex/PtexUtils.h new file mode 100644 index 00000000000..73293f06415 --- /dev/null +++ b/extern/ptex/src/ptex/PtexUtils.h @@ -0,0 +1,199 @@ +#ifndef PtexUtils_h +#define PtexUtils_h + +/* +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 "Ptexture.h" + +struct PtexUtils : public Ptex { + + static bool isPowerOfTwo(int x) + { + return !(x&(x-1)); + } + + static uint32_t ones(uint32_t x) + { + // count number of ones + x = (x & 0x55555555) + ((x >> 1) & 0x55555555); // add pairs of bits + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); // add bit pairs + x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f); // add nybbles + x += (x >> 8); // add bytes + x += (x >> 16); // add words + return(x & 0xff); + } + + static uint32_t floor_log2(uint32_t x) + { + // floor(log2(n)) + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return ones(x>>1); + } + + static uint32_t ceil_log2(uint32_t x) + { + // ceil(log2(n)) + bool isPow2 = isPowerOfTwo(x); + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return ones(x>>1) + !isPow2; + } + + static double smoothstep(double x, double a, double b) + { + if ( x < a ) return 0; + if ( x >= b ) return 1; + x = (x - a)/(b - a); + return x*x * (3 - 2*x); + } + + static double qsmoothstep(double x, double a, double b) + { + // quintic smoothstep (cubic is only C1) + if ( x < a ) return 0; + if ( x >= b ) return 1; + x = (x - a)/(b - a); + return x*x*x * (10 + x * (-15 + x*6)); + } + + template<typename T> + static T cond(bool c, T a, T b) { return c * a + (!c)*b; } + + template<typename T> + static T min(T a, T b) { return cond(a < b, a, b); } + + template<typename T> + static T max(T a, T b) { return cond(a >= b, a, b); } + + template<typename T> + static T clamp(T x, T lo, T hi) { return cond(x < lo, lo, cond(x > hi, hi, x)); } + + static bool isConstant(const void* data, int stride, int ures, int vres, + int pixelSize); + static void interleave(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void deinterleave(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void encodeDifference(void* data, int size, DataType dt); + static void decodeDifference(void* data, int size, DataType dt); + typedef void ReduceFn(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void reduce(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void reduceu(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void reducev(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void reduceTri(const void* src, int sstride, int ures, int vres, + void* dst, int dstride, DataType dt, int nchannels); + static void average(const void* src, int sstride, int ures, int vres, + void* dst, DataType dt, int nchannels); + static void fill(const void* src, void* dst, int dstride, + int ures, int vres, int pixelsize); + static void copy(const void* src, int sstride, void* dst, int dstride, + int nrows, int rowlen); + static void blend(const void* src, float weight, void* dst, bool flip, + int rowlen, DataType dt, int nchannels); + static void multalpha(void* data, int npixels, DataType dt, int nchannels, int alphachan); + static void divalpha(void* data, int npixels, DataType dt, int nchannels, int alphachan); + + static void genRfaceids(const FaceInfo* faces, int nfaces, + uint32_t* rfaceids, uint32_t* faceids); + + // fixed length vector accumulator: dst[i] += val[i] * weight + template<typename T, int n> + struct VecAccum { + VecAccum() {} + void operator()(double* dst, const T* val, double weight) + { + *dst += *val * weight; + // use template to unroll loop + VecAccum<T,n-1>()(dst+1, val+1, weight); + } + }; + template<typename T> + struct VecAccum<T,0> { void operator()(double*, const T*, double) {} }; + + // variable length vector accumulator: dst[i] += val[i] * weight + template<typename T> + struct VecAccumN { + void operator()(double* dst, const T* val, int nchan, double weight) + { + for (int i = 0; i < nchan; i++) dst[i] += val[i] * weight; + } + }; + + // fixed length vector multiplier: dst[i] += val[i] * weight + template<typename T, int n> + struct VecMult { + VecMult() {} + void operator()(double* dst, const T* val, double weight) + { + *dst = *val * weight; + // use template to unroll loop + VecMult<T,n-1>()(dst+1, val+1, weight); + } + }; + template<typename T> + struct VecMult<T,0> { void operator()(double*, const T*, double) {} }; + + // variable length vector multiplier: dst[i] = val[i] * weight + template<typename T> + struct VecMultN { + void operator()(double* dst, const T* val, int nchan, double weight) + { + for (int i = 0; i < nchan; i++) dst[i] = val[i] * weight; + } + }; + + typedef void (*ApplyConstFn)(double weight, double* dst, void* data, int nChan); + static ApplyConstFn applyConstFunctions[20]; + static void applyConst(double weight, double* dst, void* data, Ptex::DataType dt, int nChan) + { + // dispatch specialized apply function + ApplyConstFn fn = applyConstFunctions[((unsigned)nChan<=4)*nChan*4 + dt]; + fn(weight, dst, data, nChan); + } +}; + +#endif diff --git a/extern/ptex/src/ptex/PtexWriter.cpp b/extern/ptex/src/ptex/PtexWriter.cpp new file mode 100644 index 00000000000..0d5a7a3bfed --- /dev/null +++ b/extern/ptex/src/ptex/PtexWriter.cpp @@ -0,0 +1,1400 @@ +/* +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, (LPSTR) L""); + 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]; +#ifndef PTEX_NO_LARGE_METADATA_BLOCKS + if (int(e.data.size()) > MetaDataThreshold) { + // skip large items, but record for later + lmdEntries.push_back(&e); + } + else +#endif + { + // 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); + } +} diff --git a/extern/ptex/src/ptex/PtexWriter.h b/extern/ptex/src/ptex/PtexWriter.h new file mode 100644 index 00000000000..af2ee03e24a --- /dev/null +++ b/extern/ptex/src/ptex/PtexWriter.h @@ -0,0 +1,193 @@ +#ifndef PtexWriter_h +#define PtexWriter_h + +/* +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 <zlib.h> +#include <map> +#include <vector> +#include <stdio.h> +#include "Ptexture.h" +#include "PtexIO.h" +#include "PtexReader.h" + + +class PtexWriterBase : public PtexWriter, public PtexIO { +public: + virtual void setBorderModes(Ptex::BorderMode uBorderMode, Ptex::BorderMode vBorderMode) + { + _extheader.ubordermode = uBorderMode; + _extheader.vbordermode = vBorderMode; + } + virtual void writeMeta(const char* key, const char* value); + virtual void writeMeta(const char* key, const int8_t* value, int count); + virtual void writeMeta(const char* key, const int16_t* value, int count); + virtual void writeMeta(const char* key, const int32_t* value, int count); + virtual void writeMeta(const char* key, const float* value, int count); + virtual void writeMeta(const char* key, const double* value, int count); + virtual void writeMeta(PtexMetaData* data); + virtual bool close(Ptex::String& error); + virtual void release(); + + bool ok(Ptex::String& error) { + if (!_ok) getError(error); + return _ok; + } + void getError(Ptex::String& error) { + error = (_error + "\nPtex file: " + _path).c_str(); + } + +protected: + struct MetaEntry { + std::string key; + MetaDataType datatype; + std::vector<uint8_t> data; + MetaEntry() : datatype(MetaDataType(0)), data() {} + }; + + virtual void finish() = 0; + PtexWriterBase(const char* path, + Ptex::MeshType mt, Ptex::DataType dt, + int nchannels, int alphachan, int nfaces, + bool compress); + virtual ~PtexWriterBase(); + + int writeBlank(FILE* fp, int size); + int writeBlock(FILE* fp, const void* data, int size); + int writeZipBlock(FILE* fp, const void* data, int size, bool finish=true); + int readBlock(FILE* fp, void* data, int size); + int copyBlock(FILE* dst, FILE* src, FilePos pos, int size); + Res calcTileRes(Res faceres); + virtual void addMetaData(const char* key, MetaDataType t, const void* value, int size); + void writeConstFaceBlock(FILE* fp, const void* data, FaceDataHeader& fdh); + void writeFaceBlock(FILE* fp, const void* data, int stride, Res res, + FaceDataHeader& fdh); + void writeFaceData(FILE* fp, const void* data, int stride, Res res, + FaceDataHeader& fdh); + void writeReduction(FILE* fp, const void* data, int stride, Res res); + int writeMetaDataBlock(FILE* fp, MetaEntry& val); + void setError(const std::string& error) { _error = error; _ok = false; } + bool storeFaceInfo(int faceid, FaceInfo& dest, const FaceInfo& src, int flags=0); + + bool _ok; // true if no error has occurred + std::string _error; // the error text (if any) + std::string _path; // file path + std::string _tilepath; // temp tile file path ("<path>.tiles.tmp") + FILE* _tilefp; // temp tile file handle + Header _header; // the file header + ExtHeader _extheader; // extended header + int _pixelSize; // size of a pixel in bytes + std::vector<MetaEntry> _metadata; // meta data waiting to be written + std::map<std::string,int> _metamap; // for preventing duplicate keys + z_stream_s _zstream; // libzip compression stream + + PtexUtils::ReduceFn* _reduceFn; +}; + + +class PtexMainWriter : public PtexWriterBase { +public: + PtexMainWriter(const char* path, PtexTexture* tex, + Ptex::MeshType mt, Ptex::DataType dt, + int nchannels, int alphachan, int nfaces, bool genmipmaps); + + virtual bool close(Ptex::String& error); + virtual bool writeFace(int faceid, const FaceInfo& f, const void* data, int stride); + virtual bool writeConstantFace(int faceid, const FaceInfo& f, const void* data); + +protected: + virtual ~PtexMainWriter(); + virtual void addMetaData(const char* key, MetaDataType t, const void* value, int size) + { + PtexWriterBase::addMetaData(key, t, value, size); + _hasNewData = true; + } + +private: + virtual void finish(); + void generateReductions(); + void flagConstantNeighorhoods(); + void storeConstValue(int faceid, const void* data, int stride, Res res); + void writeMetaData(FILE* fp); + + std::string _newpath; // path to ".new" file + std::string _tmppath; // temp file path ("<path>.tmp") + FILE* _tmpfp; // temp file handle + bool _hasNewData; // true if data has been written + bool _genmipmaps; // true if mipmaps should be generated + std::vector<FaceInfo> _faceinfo; // info about each face + std::vector<uint8_t> _constdata; // constant data for each face + std::vector<uint32_t> _rfaceids; // faceid reordering for reduction levels + std::vector<uint32_t> _faceids_r; // faceid indexed by rfaceid + + static const int MinReductionLog2 =2; // log2(minimum reduction size) - can tune + struct LevelRec { + // note: level 0 is ordered by faceid + // levels 1+ are reduction levels (half res in both u and v) and + // are ordered by rfaceid[faceid]. Also, faces with a minimum + // dimension (the smaller of u or v) smaller than MinReductionLog2 + // are omitted from subsequent levels. + std::vector<FilePos> pos; // position of data blocks within _tmp file + std::vector<FaceDataHeader> fdh; // face data headers + }; + std::vector<LevelRec> _levels; // info about each level + std::vector<FilePos> _rpos; // reduction file positions + + PtexReader* _reader; // reader for accessing existing data in file +}; + + +class PtexIncrWriter : public PtexWriterBase { + public: + PtexIncrWriter(const char* path, FILE* fp, + Ptex::MeshType mt, Ptex::DataType dt, + int nchannels, int alphachan, int nfaces); + + virtual bool close(Ptex::String& error); + virtual bool writeFace(int faceid, const FaceInfo& f, const void* data, int stride); + virtual bool writeConstantFace(int faceid, const FaceInfo& f, const void* data); + + protected: + void writeMetaDataEdit(); + virtual void finish(); + virtual ~PtexIncrWriter(); + + private: + FILE* _fp; // the file being edited +}; + +#endif diff --git a/extern/ptex/src/ptex/Ptexture.h b/extern/ptex/src/ptex/Ptexture.h new file mode 100644 index 00000000000..2b798681b8f --- /dev/null +++ b/extern/ptex/src/ptex/Ptexture.h @@ -0,0 +1,984 @@ +#ifndef Ptexture_h +#define Ptexture_h + +/* +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. +*/ + +/** + @file Ptexture.h + @brief Public API classes for reading, writing, caching, and filtering Ptex files. +*/ + +#if defined(_WIN32) || defined(_WINDOWS) || defined(_MSC_VER) +# ifndef PTEX_STATIC +# ifdef PTEX_EXPORTS +# define PTEXAPI __declspec(dllexport) +# else +# define PTEXAPI __declspec(dllimport) +# endif +# else +# define PTEXAPI +# endif +#else +# define PTEXAPI +# ifndef DOXYGEN +# define PTEX_USE_STDSTRING +# endif +#endif + +#include "PtexInt.h" +#include <ostream> + +#define PtexAPIVersion 2 +#define PtexFileMajorVersion 1 +#define PtexFileMinorVersion 3 + +/** Common data structures and enums used throughout the API. */ +struct Ptex { + /** Type of base mesh for which the textures are defined. A mesh + can be triangle-based (with triangular textures) or quad-based + (with rectangular textures). */ + enum MeshType { + mt_triangle, ///< Mesh is triangle-based. + mt_quad ///< Mesh is quad-based. + }; + + /** Type of data stored in texture file. */ + enum DataType { + dt_uint8, ///< Unsigned, 8-bit integer. + dt_uint16, ///< Unsigned, 16-bit integer. + dt_half, ///< Half-precision (16-bit) floating point. + dt_float ///< Single-precision (32-bit) floating point. + }; + + /** How to handle mesh border when filtering. */ + enum BorderMode { + m_clamp, ///< texel access is clamped to border + m_black, ///< texel beyond border are assumed to be black + m_periodic ///< texel access wraps to other side of face + }; + + /** Edge IDs used in adjacency data in the Ptex::FaceInfo struct. + Edge ID usage for triangle meshes is TBD. */ + enum EdgeId { + e_bottom, ///< Bottom edge, from UV (0,0) to (1,0) + e_right, ///< Right edge, from UV (1,0) to (1,1) + e_top, ///< Top edge, from UV (1,1) to (0,1) + e_left ///< Left edge, from UV (0,1) to (0,0) + }; + + /** Type of meta data entry. */ + enum MetaDataType { + mdt_string, ///< Null-terminated string. + mdt_int8, ///< Signed 8-bit integer. + mdt_int16, ///< Signed 16-bit integer. + mdt_int32, ///< Signed 32-bit integer. + mdt_float, ///< Single-precision (32-bit) floating point. + mdt_double ///< Double-precision (32-bit) floating point. + }; + + /** Look up name of given mesh type. */ + PTEXAPI static const char* MeshTypeName(MeshType mt); + + /** Look up name of given data type. */ + PTEXAPI static const char* DataTypeName(DataType dt); + + /** Look up name of given border mode. */ + PTEXAPI static const char* BorderModeName(BorderMode m); + + /** Look up name of given edge ID. */ + PTEXAPI static const char* EdgeIdName(EdgeId eid); + + /** Look up name of given meta data type. */ + PTEXAPI static const char* MetaDataTypeName(MetaDataType mdt); + + /** Look up size of given data type (in bytes). */ + static int DataSize(DataType dt) { + static const int sizes[] = { 1,2,2,4 }; + return sizes[dt]; + } + + /** Look up value of given data type that corresponds to the normalized value of 1.0. */ + static double OneValue(DataType dt) { + static const double one[] = { 255.0, 65535.0, 1.0, 1.0 }; + return one[dt]; + } + + /** Lookup up inverse value of given data type that corresponds to the normalized value of 1.0. */ + static double OneValueInv(DataType dt) { + static const double one[] = { 1.0/255.0, 1.0/65535.0, 1.0, 1.0 }; + return one[dt]; + } + + /** Convert a number of data values from the given data type to float. */ + PTEXAPI static void ConvertToFloat(float* dst, const void* src, + Ptex::DataType dt, int numChannels); + + /** Convert a number of data values from float to the given data type. */ + PTEXAPI static void ConvertFromFloat(void* dst, const float* src, + Ptex::DataType dt, int numChannels); + + /** Pixel resolution of a given texture. + The resolution is stored in log form: ulog2 = log2(ures), vlog2 = log2(vres)). + Note: negative ulog2 or vlog2 values are reserved for internal use. + */ + struct Res { + int8_t ulog2; ///< log base 2 of u resolution, in texels + int8_t vlog2; ///< log base 2 of v resolution, in texels + + /// Default constructor, sets res to 0 (1x1 texel). + Res() : ulog2(0), vlog2(0) {} + + /// Constructor. + Res(int8_t ulog2, int8_t vlog2) : ulog2(ulog2), vlog2(vlog2) {} + + /// Constructor from 16-bit integer + Res(uint16_t value) { + ulog2 = *(uint8_t *)&value; + vlog2 = *((uint8_t *)&value + 1); + } + + /// U resolution in texels. + int u() const { return 1<<(unsigned)ulog2; } + + /// V resolution in texels. + int v() const { return 1<<(unsigned)vlog2; } + + /// Resolution as a single 16-bit integer value. + uint16_t& val() { return *(uint16_t*)this; } + + /// Resolution as a single 16-bit integer value. + const uint16_t& val() const { return *(uint16_t*)this; } + + /// Total size of specified texture in texels (u * v). + int size() const { return u() * v(); } + + /// Comparison operator. + bool operator==(const Res& r) const { return val() == r.val(); } + + /// Comparison operator. + bool operator!=(const Res& r) const { return val() != r.val(); } + + /// True if res is >= given res in both u and v directions. + bool operator>=(const Res& r) const { return ulog2 >= r.ulog2 && vlog2 >= r.vlog2; } + + /// Get value of resolution with u and v swapped. + Res swappeduv() const { return Res(vlog2, ulog2); } + + /// Swap the u and v resolution values in place. + void swapuv() { *this = swappeduv(); } + + /// Clamp the resolution value against the given value. + void clamp(const Res& r) { + if (ulog2 > r.ulog2) ulog2 = r.ulog2; + if (vlog2 > r.vlog2) vlog2 = r.vlog2; + } + + /// Determine the number of tiles in the u direction for the given tile res. + int ntilesu(Res tileres) const { return 1<<(ulog2-tileres.ulog2); } + + /// Determine the number of tiles in the v direction for the given tile res. + int ntilesv(Res tileres) const { return 1<<(vlog2-tileres.vlog2); } + + /// Determine the total number of tiles for the given tile res. + int ntiles(Res tileres) const { return ntilesu(tileres) * ntilesv(tileres); } + }; + + /** Information about a face, as stored in the Ptex file header. + The FaceInfo data contains the face resolution and neighboring face + adjacency information as well as a set of flags describing the face. + + The adjfaces data member contains the face ids of the four neighboring faces. + The neighbors are accessed in EdgeId order, CCW, starting with the bottom edge. + The adjedges data member contains the corresponding edge id for each neighboring face. + + If a face has no neighbor for a given edge, the adjface id should be -1, and the + adjedge id doesn't matter (but is typically zero). + + If an adjacent face is a pair of subfaces, the id of the first subface as encountered + in a CCW traversal should be stored as the adjface id. + */ + struct FaceInfo { + Res res; ///< Resolution of face. + uint8_t adjedges; ///< Adjacent edges, 2 bits per edge. + uint8_t flags; ///< Flags. + int32_t adjfaces[4]; ///< Adjacent faces (-1 == no adjacent face). + + /// Default constructor, sets all members to zero. + FaceInfo() : res(), adjedges(0), flags(0) + { + adjfaces[0] = adjfaces[1] = adjfaces[2] = adjfaces[3] = -1; + } + + /// Constructor. + FaceInfo(Res res) : res(res), adjedges(0), flags(0) + { + adjfaces[0] = adjfaces[1] = adjfaces[2] = adjfaces[3] = -1; + } + + /// Constructor. + FaceInfo(Res res, int adjfaces[4], int adjedges[4], bool isSubface=false) + : res(res), flags(isSubface ? flag_subface : 0) + { + setadjfaces(adjfaces[0], adjfaces[1], adjfaces[2], adjfaces[3]); + setadjedges(adjedges[0], adjedges[1], adjedges[2], adjedges[3]); + } + + /// Access an adjacent edge id. The eid value must be 0..3. + EdgeId adjedge(int eid) const { return EdgeId((adjedges >> (2*eid)) & 3); } + + /// Access an adjacent face id. The eid value must be 0..3. + int adjface(int eid) const { return adjfaces[eid]; } + + /// Determine if face is constant (by checking a flag). + bool isConstant() const { return (flags & flag_constant) != 0; } + + /// Determine if neighborhood of face is constant (by checking a flag). + bool isNeighborhoodConstant() const { return (flags & flag_nbconstant) != 0; } + + /// Determine if face has edits in the file (by checking a flag). + bool hasEdits() const { return (flags & flag_hasedits) != 0; } + + /// Determine if face is a subface (by checking a flag). + bool isSubface() const { return (flags & flag_subface) != 0; } + + /// Set the adjfaces data. + void setadjfaces(int f0, int f1, int f2, int f3) + { adjfaces[0] = f0, adjfaces[1] = f1, adjfaces[2] = f2; adjfaces[3] = f3; } + + /// Set the adjedges data. + void setadjedges(int e0, int e1, int e2, int e3) + { adjedges = (e0&3) | ((e1&3)<<2) | ((e2&3)<<4) | ((e3&3)<<6); } + + /// Flag bit values (for internal use). + enum { flag_constant = 1, flag_hasedits = 2, flag_nbconstant = 4, flag_subface = 8 }; + }; + + + /** Memory-managed string. Used for returning error messages from + API functions. On most platforms, this is a typedef to + std::string. For Windows, this is a custom class that + implements a subset of std::string. (Note: std::string cannot + be passed through a Windows DLL interface). + */ +#ifdef PTEX_USE_STDSTRING + typedef std::string String; +#else + class String + { + public: + String() : _str(0) {} + String(const String& str) : _str(0) { *this = str; } + PTEXAPI ~String(); + PTEXAPI String& operator=(const char* str); + String& operator=(const String& str) { *this = str._str; return *this; } + const char* c_str() const { return _str ? _str : ""; } + bool empty() const { return _str == 0; } + + private: + char* _str; + }; +#endif + +} +#ifndef DOXYGEN +; +#endif + +/// std::stream output operator. \relates Ptex::String +#ifndef PTEX_USE_STDSTRING +std::ostream& operator << (std::ostream& stream, const Ptex::String& str); +#endif + + +/** + @class PtexMetaData + @brief Meta data accessor + + Meta data is acquired from PtexTexture and accessed through this interface. + */ +class PtexMetaData { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexMetaData() {} + + public: + /// Release resources held by this pointer (pointer becomes invalid). + virtual void release() = 0; + + /// Query number of meta data entries stored in file. + virtual int numKeys() = 0; + + /// Query the name and type of a meta data entry. + virtual void getKey(int n, const char*& key, Ptex::MetaDataType& type) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const char*& value) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const int8_t*& value, int& count) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const int16_t*& value, int& count) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const int32_t*& value, int& count) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const float*& value, int& count) = 0; + + /** Query the value of a given meta data entry. + If the key doesn't exist or the type doesn't match, value is set to null */ + virtual void getValue(const char* key, const double*& value, int& count) = 0; +}; + + +/** + @class PtexFaceData + @brief Per-face texture data accessor + + Per-face texture data is acquired from PtexTexture and accessed + through this interface. This interface provides low-level access + to the data as stored on disk for maximum efficiency. If this + isn't needed, face data can be more conveniently read directly + from PtexTexture. + */ +class PtexFaceData { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexFaceData() {} + + public: + /// Release resources held by this pointer (pointer becomes invalid). + virtual void release() = 0; + + /** True if this data block is constant. */ + virtual bool isConstant() = 0; + + /** Resolution of the texture held by this data block. Note: the + indicated texture res may be larger than 1x1 even if the + texture data is constant. */ + virtual Ptex::Res res() = 0; + + /** Read a single texel from the data block. The texel coordinates, u and v, have + a range of [0..ures-1, 0..vres-1]. Note: this method will work correctly even if + the face is constant or tiled. */ + virtual void getPixel(int u, int v, void* result) = 0; + + /** Access the data from this data block. + + If the data block is constant, getData will return a pointer to a single texel's data value. + + If the data block is tiled, then getData will return null and + the data must be accessed per-tile via the getTile() function. */ + virtual void* getData() = 0; + + /** True if this data block is tiled. + If tiled, the data must be access per-tile via getTile(). */ + virtual bool isTiled() = 0; + + /** Resolution of each tile in this data block. */ + virtual Ptex::Res tileRes() = 0; + + /** Access a tile from the data block. Tiles are accessed in v-major order. */ + virtual PtexFaceData* getTile(int tile) = 0; +}; + + +/** + @class PtexTexture + @brief Interface for reading data from a ptex file + + PtexTexture instances can be acquired via the static open() method, or via + the PtexCache interface. + + Data access through this interface is returned in v-major order with all data channels interleaved per texel. + */ +class PtexTexture { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexTexture() {} + + public: + /** Open a ptex file for reading. + + If an error occurs, an error message will be stored in the + error string param and the a pointer will be returned. + + If the premultiply param is set to true and the texture file has a specified alpha channel, + then all data stored in the file will be multiplied by alpha when read from disk. If premultiply + is false, then the full-resolution textures will be returned as stored on disk which is assumed + to be unmultiplied. Reductions (both stored mip-maps and dynamically generated reductions) are + always premultiplied with alpha. See PtexWriter for more information about alpha channels. + */ + PTEXAPI static PtexTexture* open(const char* path, Ptex::String& error, bool premultiply=0); + + + /// Release resources held by this pointer (pointer becomes invalid). + virtual void release() = 0; + + /** Path that file was opened with. If the file was opened using a search path (via PtexCache), + the the path will be the path as found in the search path. Otherwise, the path will be + the path as supplied to open. */ + virtual const char* path() = 0; + + /** Type of mesh for which texture data is defined. */ + virtual Ptex::MeshType meshType() = 0; + + /** Type of data stored in file. */ + virtual Ptex::DataType dataType() = 0; + + /** Mode for filtering texture access beyond mesh border. */ + virtual Ptex::BorderMode uBorderMode() = 0; + + /** Mode for filtering texture access beyond mesh border. */ + virtual Ptex::BorderMode vBorderMode() = 0; + + /** Index of alpha channel (if any). One channel in the file can be flagged to be the alpha channel. + If no channel is acting as the alpha channel, -1 is returned. + See PtexWriter for more details. */ + virtual int alphaChannel() = 0; + + /** Number of channels stored in file. */ + virtual int numChannels() = 0; + + /** Number of faces stored in file. */ + virtual int numFaces() = 0; + + /** True if the file has edit blocks. See PtexWriter for more details. */ + virtual bool hasEdits() = 0; + + /** True if the file has mipmaps. See PtexWriter for more details. */ + virtual bool hasMipMaps() = 0; + + /** Access meta data. */ + virtual PtexMetaData* getMetaData() = 0; + + /** Access resolution and adjacency information about a face. */ + virtual const Ptex::FaceInfo& getFaceInfo(int faceid) = 0; + + /** Access texture data for a face at highest-resolution. + + The texture data is copied into the user-supplied buffer. + The buffer must be at least this size (in bytes): + DataSize(dataType()) * numChannels() * getFaceInfo(faceid).res.size(). + + If a stride is given, then (stride-row_length) bytes will be + skipped after each row. If stride is zero, then no bytes will + be skipped. Note: the image can be flipped vertically by using + an appropriate negative stride value. + + @param faceid Face index [0..numFaces-1] + @param buffer User-supplied buffer + @param stride Size of each row in user buffer (in bytes) + */ + virtual void getData(int faceid, void* buffer, int stride) = 0; + + /** Access texture data for a face at a specific resolution. + + The specified resolution may be lower than the full resolution + for the face. If it is lower, then the texture data is + accessed from the stored mip-maps. If the requested + resolution doesn't match a stored resolution, the desired + resolution will be generated from the nearest available + resolution. + + See previous getData() method for interface details. + */ + virtual void getData(int faceid, void* buffer, int stride, Ptex::Res res) = 0; + + /** Access texture data for a face at highest-resolution as stored on disk. */ + virtual PtexFaceData* getData(int faceid) = 0; + + /** Access texture data for a face at a specific resolution as stored on disk. + + The specified resolution may be lower (but not higher) than + the full resolution for the face. If it is lower, then the + texture data is accessed from the stored mip-maps. If the + requested resolution doesn't match a stored resolution, the + desired resolution will be generated from the nearest + available resolution. + */ + virtual PtexFaceData* getData(int faceid, Ptex::Res res) = 0; + + /** Access a single texel from the highest resolution texture . + The texel data is converted to floating point (integer types + are normalized 0.0 to 1.0). A subset of the available + channels may be accessed. + + @param faceid Face index [0..numFaces-1] + @param u U coordinate [0..ures-1] + @param v V coordinate [0..vres-1] + @param result Result data + @param firstchan First channel to access [0..numChannels-1] + @param nchannels Number of channels to access. + */ + virtual void getPixel(int faceid, int u, int v, + float* result, int firstchan, int nchannels) = 0; + + /** Access a single texel for a face at a particular resolution. + + The specified resolution may be lower (but not higher) than + the full resolution for the face. If it is lower, then the + texture data is accessed from the stored mip-maps. If the + requested resolution doesn't match a stored resolution, the + desired resolution will be generated from the nearest + available resolution. + + See previous getPixel() method for details. + */ + virtual void getPixel(int faceid, int u, int v, + float* result, int firstchan, int nchannels, + Ptex::Res res) = 0; +}; + + +/** @class PtexInputHandler + @brief Custom handler interface for intercepting and redirecting Ptex input stream calls + + A custom instance of this class can be defined and supplied to the PtexCache class. + Files accessed through the cache will have their input streams redirected through this + interface. + */ +class PtexInputHandler { + protected: + virtual ~PtexInputHandler() {} + + public: + typedef void* Handle; + + /** Open a file in read mode. + Returns null if there was an error. + If an error occurs, the error string is available via lastError(). + */ + virtual Handle open(const char* path) = 0; + + /** Seek to an absolute byte position in the input stream. */ + virtual void seek(Handle handle, int64_t pos) = 0; + + /** Read a number of bytes from the file. + Returns the number of bytes successfully read. + If less than the requested number of bytes is read, the error string + is available via lastError(). + */ + virtual size_t read(void* buffer, size_t size, Handle handle) = 0; + + /** Close a file. Returns false if an error occurs, and the error + string is available via lastError(). */ + virtual bool close(Handle handle) = 0; + + /** Return the last error message encountered. */ + virtual const char* lastError() = 0; +}; + + +/** + @class PtexCache + @brief File-handle and memory cache for reading ptex files + + The PtexCache class allows cached read access to multiple ptex + files while constraining the open file count and memory usage to + specified limits. File and data objects accessed via the cache + are added back to the cache when their release method is called. + Released objects are maintained in an LRU list and only destroyed + when the specified resource limits are exceeded. + + The cache is fully multi-threaded. Cached data will be shared among + all threads that have access to the cache, and the data are protected + with internal locks. See PtexCache.cpp for details about the caching + and locking implementation. + */ + +class PtexCache { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexCache() {} + + public: + /** Create a cache with the specified limits. + + @param maxFiles Maximum open file handles. If unspecified, + limit is set to 100 open files. + + @param maxMem Maximum allocated memory, in bytes. If unspecified, + limit is set to 100MB. + + @param premultiply If true, textures will be premultiplied by the alpha + channel (if any) when read from disk. See PtexTexture and PtexWriter + for more details. + + @param handler If specified, all input calls made through this cache will + be directed through the handler. + */ + PTEXAPI static PtexCache* create(int maxFiles=0, + int maxMem=0, + bool premultiply=false, + PtexInputHandler* handler=0); + + /// Release resources held by this pointer (pointer becomes invalid). + virtual void release() = 0; + + /** Set a search path for finding textures. + Note: if an input handler is installed the search path will be ignored. + + @param path colon-delimited search path. + */ + virtual void setSearchPath(const char* path) = 0; + + /** Query the search path. Returns string set via setSearchPath. */ + virtual const char* getSearchPath() = 0; + + /** Open a texture. If the specified path was previously opened, and the + open file limit hasn't been exceeded, then a pointer to the already + open file will be returned. + + If the specified path hasn't been opened yet or was closed, + either to maintain the open file limit or because the file was + explicitly purged from the cache, then the file will be newly + opened. If the path is relative (i.e. doesn't begin with a + '/') then the search path will be used to locate the file. + + The texture file will stay open until the PtexTexture::release + method is called, at which point the texture will be returned + to the cache. Once released, the file will be closed when + either the file handle limit is reached, the cached is + released, or when the texture is purged (see purge methods + below). + + If texture could not be opened, null will be returned and + an error string will be set. + + @param path File path. If path is relative, search path will + be used to find the file. + + @param error Error string set if texture could not be + opened. + */ + virtual PtexTexture* get(const char* path, Ptex::String& error) = 0; + + /** Remove a texture file from the cache. The texture file will remain + open and accessible until the file handle is released, but any + future cache accesses for that file will open the file anew. + */ + virtual void purge(PtexTexture* texture) = 0; + + /** Remove a texture file from the cache by pathname. The path must + match the full path as opened. This function will not search + for the file, but if a search path was used, the path must + match the path as found by the search path. + */ + virtual void purge(const char* path) = 0; + + /** Remove all texture files from the cache. Open files with + active PtexTexture* handles remain open until released. + */ + virtual void purgeAll() = 0; +}; + + +/** + @class PtexWriter + @brief Interface for writing data to a ptex file. + + Note: if an alpha channel is specified, then the textures being + written to the file are expected to have unmultiplied-alpha data. + Generated mipmaps will be premultiplied by the Ptex library. On + read, PtexTexture will (if requested) premultiply all textures by + alpha when getData is called; by default only reductions are + premultiplied. If the source textures are already premultiplied, + then alphachan can be set to -1 and the library will just leave all + the data as-is. The only reason to store unmultiplied-alpha + textures in the file is to preserve the original texture data for + later editing. +*/ + +class PtexWriter { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexWriter() {} + + public: + /** Open a new texture file for writing. + @param path Path to file. + @param mt Type of mesh for which the textures are defined. + @param dt Type of data stored within file. + @param nchannels Number of data channels. + @param alphachan Index of alpha channel, [0..nchannels-1] or -1 if no alpha channel is present. + @param nfaces Number of faces in mesh. + @param error String containing error message if open failed. + @param genmipmaps Specify true if mipmaps should be generated. + */ + PTEXAPI + static PtexWriter* open(const char* path, + Ptex::MeshType mt, Ptex::DataType dt, + int nchannels, int alphachan, int nfaces, + Ptex::String& error, bool genmipmaps=true); + + /** Open an existing texture file for writing. + + If the incremental param is specified as true, then data + values written to the file are appended to the file as "edit + blocks". This is the fastest way to write data to the file, but + edit blocks are slower to read back, and they have no mipmaps so + filtering can be inefficient. + + If incremental is false, then the edits are applied to the + file and the entire file is regenerated on close as if it were + written all at once with open(). + + If the file doesn't exist it will be created and written as if + open() were used. If the file exists, the mesh type, data + type, number of channels, alpha channel, and number of faces + must agree with those stored in the file. + */ + PTEXAPI + static PtexWriter* edit(const char* path, bool incremental, + Ptex::MeshType mt, Ptex::DataType dt, + int nchannels, int alphachan, int nfaces, + Ptex::String& error, bool genmipmaps=true); + + /** Apply edits to a file. + + If a file has pending edits, the edits will be applied and the + file will be regenerated with no edits. This is equivalent to + calling edit() with incremental set to false. The advantage + is that the file attributes such as mesh type, data type, + etc., don't need to be known in advance. + */ + PTEXAPI + static bool applyEdits(const char* path, Ptex::String& error); + + /** Release resources held by this pointer (pointer becomes invalid). */ + virtual void release() = 0; + + /** Set border modes */ + virtual void setBorderModes(Ptex::BorderMode uBorderMode, Ptex::BorderMode vBorderMode) = 0; + + /** Write a string as meta data. Both the key and string params must be null-terminated strings. */ + virtual void writeMeta(const char* key, const char* string) = 0; + + /** Write an array of signed 8-bit integers as meta data. The key must be a null-terminated string. */ + virtual void writeMeta(const char* key, const int8_t* value, int count) = 0; + + /** Write an array of signed 16-bit integers as meta data. The key must be a null-terminated string. */ + virtual void writeMeta(const char* key, const int16_t* value, int count) = 0; + + /** Write an array of signed 32-bit integers as meta data. The key must be a null-terminated string. */ + virtual void writeMeta(const char* key, const int32_t* value, int count) = 0; + + /** Write an array of signed 32-bit floats as meta data. The key must be a null-terminated string. */ + virtual void writeMeta(const char* key, const float* value, int count) = 0; + + /** Write an array of signed 32-bit doubles as meta data. The key must be a null-terminated string. */ + virtual void writeMeta(const char* key, const double* value, int count) = 0; + + /** Copy meta data from an existing meta data block. */ + virtual void writeMeta(PtexMetaData* data) = 0; + + /** Write texture data for a face. + The data is assumed to be channel-interleaved per texel and stored in v-major order. + + @param faceid Face index [0..nfaces-1]. + @param info Face resolution and adjacency information. + @param data Texel data. + @param stride Distance between rows, in bytes (if zero, data is assumed packed). + + If an error is encountered while writing, false is returned and an error message can be + when close is called. + */ + virtual bool writeFace(int faceid, const Ptex::FaceInfo& info, const void* data, int stride=0) = 0; + + /** Write constant texture data for a face. + The data is written as a single constant texel value. Note: the resolution specified in the + info param may indicate a resolution greater than 1x1 and the value will be preserved when + reading. This is useful to indicate a texture's logical resolution even when the data is + constant. */ + virtual bool writeConstantFace(int faceid, const Ptex::FaceInfo& info, const void* data) = 0; + + /** Close the file. This operation can take some time if mipmaps are being generated or if there + are many edit blocks. If an error occurs while writing, false is returned and an error string + is written into the error parameter. */ + virtual bool close(Ptex::String& error) = 0; + +#if NEW_API + virtual bool writeFaceReduction(int faceid, const Ptex::Res& res, const void* data, int stride=0) = 0; + virtual bool writeConstantFaceReduction(int faceid, const Ptex::Res& res, const void* data) = 0; +#endif +}; + + +/** + @class PtexFilter + @brief Interface for filtered sampling of ptex data files. + + PtexFilter instances are obtained by calling one of the particular static methods. When finished using + the filter, it must be returned to the library using release(). + + To apply the filter to a ptex data file, use the eval() method. + */ +class PtexFilter { + protected: + /// Destructor not for public use. Use release() instead. + virtual ~PtexFilter() {}; + + public: + /// Filter types + enum FilterType { + f_point, ///< Point-sampled (no filtering) + f_bilinear, ///< Bi-linear interpolation + f_box, ///< Box filter + f_gaussian, ///< Gaussian filter + f_bicubic, ///< General bi-cubic filter (uses sharpness option) + f_bspline, ///< BSpline (equivalent to bi-cubic w/ sharpness=0) + f_catmullrom, ///< Catmull-Rom (equivalent to bi-cubic w/ sharpness=1) + f_mitchell ///< Mitchell (equivalent to bi-cubic w/ sharpness=2/3) + }; + + /// Choose filter options + struct Options { + int __structSize; ///< (for internal use only) + FilterType filter; ///< Filter type. + bool lerp; ///< Interpolate between mipmap levels. + float sharpness; ///< Filter sharpness, 0..1 (for general bi-cubic filter only). + + /// Constructor - sets defaults + Options(FilterType filter=f_box, bool lerp=0, float sharpness=0) : + __structSize(sizeof(Options)), + filter(filter), lerp(lerp), sharpness(sharpness) {} + }; + + /* Construct a filter for the given texture. + */ + PTEXAPI static PtexFilter* getFilter(PtexTexture* tx, const Options& opts); + + /** Release resources held by this pointer (pointer becomes invalid). */ + virtual void release() = 0; + + /** Apply filter to a ptex data file. + + The filter region is a parallelogram centered at the given + (u,v) coordinate with sides defined by two vectors [uw1, vw1] + and [uw2, vw2]. For an axis-aligned rectangle, the vectors + are [uw, 0] and [0, vw]. See \link filterfootprint Filter + Footprint \endlink for details. + + @param result Buffer to hold filter result. Must be large enough to hold nchannels worth of data. + @param firstchan First channel to evaluate [0..tx->numChannels()-1] + @param nchannels Number of channels to evaluate + @param faceid Face index [0..tx->numFaces()-1] + @param u U coordinate, normalized [0..1] + @param v V coordinate, normalized [0..1] + @param uw1 U filter width 1, normalized [0..1] + @param vw1 V filter width 1, normalized [0..1] + @param uw2 U filter width 2, normalized [0..1] + @param vw2 V filter width 2, normalized [0..1] + @param width scale factor for filter width + @param blur amount to add to filter width [0..1] + */ + virtual void eval(float* result, int firstchan, int nchannels, + int faceid, float u, float v, float uw1, float vw1, float uw2, float vw2, + float width=1, float blur=0) = 0; +}; + + +/** + @class PtexPtr + @brief Smart-pointer for acquiring and releasing API objects + + All public API objects must be released back to the Ptex library + via the release() method. This smart-pointer class can wrap any of + the Ptex API objects and will automatically release the object when + the pointer goes out of scope. Usage of PtexPtr is optional, but + recommended. + + Note: for efficiency and safety, PtexPtr is noncopyable. However, + ownership can be transferred between PtexPtr instances via the + PtexPtr::swap member function. + + Example: + \code + { + Ptex::String error; + PtexPtr<PtexTexture> inptx(PtexTexture::open(inptxname, error)); + if (!inptx) { + std::cerr << error << std::endl; + } + else { + // read some data + inptx->getData(faceid, buffer, stride); + } + } + \endcode + */ +template <class T> class PtexPtr { + T* _ptr; + public: + /// Constructor. + PtexPtr(T* ptr=0) : _ptr(ptr) {} + + /// Destructor, calls ptr->release(). + ~PtexPtr() { if (_ptr) _ptr->release(); } + + /// Use as pointer value. + operator T* () { return _ptr; } + + /// Access members of pointer. + T* operator-> () { return _ptr; } + + /// Get pointer value. + T* get() { return _ptr; } + + /// Swap pointer values. + void swap(PtexPtr& p) + { + T* tmp = p._ptr; + p._ptr = _ptr; + _ptr = tmp; + } + + private: + /// Copying prohibited + PtexPtr(const PtexPtr& p); + + /// Assignment prohibited + void operator= (PtexPtr& p); +}; + +#endif diff --git a/release/scripts/ui/properties_data_mesh.py b/release/scripts/ui/properties_data_mesh.py index 2e5fbe7e37d..3947ea4943c 100644 --- a/release/scripts/ui/properties_data_mesh.py +++ b/release/scripts/ui/properties_data_mesh.py @@ -372,6 +372,21 @@ class DATA_PT_vertex_colors(DataButtonsPanel): layout.operator("mesh.vertex_color_multiresolution_toggle", text="Add Multires") +class DATA_PT_ptex(DataButtonsPanel): + bl_label = "PTex" + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw(self, context): + layout = self.layout + + me = context.mesh + + layout.template_list(me, "ptex_layers", me, "active_ptex_index", rows=2) + row = layout.row() + row.operator_menu_enum("ptex.layer_add", "type") + row.operator("ptex.open") + + classes = [ MESH_MT_vertex_group_specials, MESH_MT_shape_key_specials, @@ -381,6 +396,7 @@ classes = [ DATA_PT_settings, DATA_PT_vertex_groups, DATA_PT_shape_keys, + DATA_PT_ptex, DATA_PT_uv_texture, DATA_PT_texface, DATA_PT_vertex_colors, diff --git a/source/blender/blenkernel/intern/cdderivedmesh.c b/source/blender/blenkernel/intern/cdderivedmesh.c index 402609947ec..576b76edf4b 100644 --- a/source/blender/blenkernel/intern/cdderivedmesh.c +++ b/source/blender/blenkernel/intern/cdderivedmesh.c @@ -220,7 +220,13 @@ static struct PBVH *cdDM_getPBVH(Object *ob, DerivedMesh *dm) this derivedmesh is just original mesh. it's the multires subsurf dm that this is actually for, to support a pbvh on a modified mesh */ if(!cddm->pbvh && ob->type == OB_MESH) { - cddm->pbvh = BLI_pbvh_new(); + int leaf_limit = PBVH_DEFAULT_LEAF_LIMIT; + + /* TODO: set leaf limit more intelligently */ + if(ob->mode & OB_MODE_VERTEX_PAINT) + leaf_limit = 1; + + cddm->pbvh = BLI_pbvh_new(leaf_limit); cddm->pbvh_draw = can_pbvh_draw(ob, dm); BLI_pbvh_build_mesh(cddm->pbvh, me->mface, me->mvert, &me->vdata, &me->fdata, me->totface, diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index ce3cb1284ef..5e3802443d9 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -797,6 +797,29 @@ static void layerFree_grid(void *data_v, int count, int size) } } +static void layerCopy_mptex(const void *source_v, void *dest_v, int count) +{ + const MPtex *source = source_v; + MPtex *dest = dest_v; + int i; + + for(i = 0; i < count; ++i) { + dest[i] = source[i]; + dest[i].data = MEM_dupallocN(source[i].data); + } +} + +static void layerFree_mptex(void *data_v, int count, int size) +{ + MPtex *data = data_v; + int i; + + for(i = 0; i < count; ++i) { + if(data[i].data) + MEM_freeN(data[i].data); + } +} + const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { /* 0 */ {sizeof(MVert), "MVert", 1, NULL, NULL, NULL, NULL, NULL, NULL}, @@ -843,6 +866,8 @@ const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { {sizeof(CustomDataMultires), "CustomDataMultires", 1, "Grid", layerCopy_grid, layerFree_grid, NULL, NULL, NULL}, /* CD_PAINTMASK */ {sizeof(float), "", 0, "Mask", NULL, NULL, NULL, NULL, NULL}, + /* CD_MPTEX */ + {sizeof(MPtex), "MPtex", 1, "Ptex", layerCopy_mptex, layerFree_mptex, NULL, NULL, NULL}, }; const char *LAYERTYPENAMES[CD_NUMTYPES] = { @@ -851,7 +876,7 @@ const char *LAYERTYPENAMES[CD_NUMTYPES] = { /* 10-14 */ "CDMFloatProperty", "CDMIntProperty","CDMStringProperty", "CDOrigSpace", "CDOrco", /* 15-19 */ "CDMTexPoly", "CDMLoopUV", "CDMloopCol", "CDTangent", "CDMDisps", /* 20-24 */ "CDWeightMCol", "CDIDMCol", "CDTextureMCol", "CDClothOrco", "CDGrid", - /* 25-26 */ "CDPaintMask" + /* 25-26 */ "CDPaintMask", "CD_PTEX" }; const CustomDataMask CD_MASK_BAREMESH = @@ -860,11 +885,11 @@ const CustomDataMask CD_MASK_MESH = CD_MASK_MVERT | CD_MASK_MEDGE | CD_MASK_MFACE | CD_MASK_MSTICKY | CD_MASK_MDEFORMVERT | CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR | CD_MASK_MDISPS | - CD_MASK_GRIDS | CD_MASK_PAINTMASK; + CD_MASK_GRIDS | CD_MASK_PAINTMASK | CD_MASK_MPTEX; const CustomDataMask CD_MASK_EDITMESH = CD_MASK_MSTICKY | CD_MASK_MDEFORMVERT | CD_MASK_MTFACE | CD_MASK_MCOL|CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR | - CD_MASK_MDISPS | CD_MASK_GRIDS | CD_MASK_PAINTMASK; + CD_MASK_MDISPS | CD_MASK_GRIDS | CD_MASK_PAINTMASK | CD_MASK_MPTEX; const CustomDataMask CD_MASK_DERIVEDMESH = CD_MASK_MSTICKY | CD_MASK_MDEFORMVERT | CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_ORIGINDEX | CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_CLOTH_ORCO | diff --git a/source/blender/blenkernel/intern/subsurf_ccg.c b/source/blender/blenkernel/intern/subsurf_ccg.c index 554f94c8d15..65f4e07d1f1 100644 --- a/source/blender/blenkernel/intern/subsurf_ccg.c +++ b/source/blender/blenkernel/intern/subsurf_ccg.c @@ -2496,7 +2496,7 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) numGrids = ccgDM_getNumGrids(dm); gridkey = ccgDM_getGridKey(dm); - ob->paint->pbvh= ccgdm->pbvh = BLI_pbvh_new(); + ob->paint->pbvh= ccgdm->pbvh = BLI_pbvh_new(PBVH_DEFAULT_LEAF_LIMIT); BLI_pbvh_build_grids(ccgdm->pbvh, ccgdm->gridData, ccgdm->gridAdjacency, numGrids, gridSize, gridkey, (void**)ccgdm->gridFaces, &me->vdata, &me->fdata, @@ -2504,7 +2504,7 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) ccgdm->pbvh_draw = 1; } else if(ob->type == OB_MESH) { - ob->paint->pbvh= ccgdm->pbvh = BLI_pbvh_new(); + ob->paint->pbvh= ccgdm->pbvh = BLI_pbvh_new(PBVH_DEFAULT_LEAF_LIMIT); BLI_pbvh_build_mesh(ccgdm->pbvh, me->mface, me->mvert, &me->vdata, &me->fdata, me->totface, me->totvert, ss ? &ss->hidden_areas : NULL); @@ -2918,8 +2918,11 @@ void subsurf_calculate_limit_positions(Mesh *me, float (*positions_r)[3]) float edge_sum[3], face_sum[3]; CCGVertIterator *vi; DerivedMesh *dm = CDDM_from_mesh(me, NULL); + GridKey gridkey; + + GRIDELEM_KEY_INIT(&gridkey, 1, 0, 0, 1); - ss = _getSubSurf(NULL, NULL, 1, 0, 1, 0); + ss = _getSubSurf(NULL, &gridkey, 1, 0, 1, 0); ss_sync_from_derivedmesh(ss, dm, NULL, 0); vi = ccgSubSurf_getVertIterator(ss); diff --git a/source/blender/blenlib/BLI_pbvh.h b/source/blender/blenlib/BLI_pbvh.h index d2645fd21c0..e7fc42e8210 100644 --- a/source/blender/blenlib/BLI_pbvh.h +++ b/source/blender/blenlib/BLI_pbvh.h @@ -68,8 +68,8 @@ typedef struct { int BLI_pbvh_search_sphere_cb(PBVHNode *node, void *data); /* Building */ - -PBVH *BLI_pbvh_new(void); +#define PBVH_DEFAULT_LEAF_LIMIT 10000 +PBVH *BLI_pbvh_new(int leaf_limit); void BLI_pbvh_build_mesh(PBVH *bvh, struct MFace *faces, struct MVert *verts, struct CustomData *vdata, struct CustomData *fdata, int totface, int totvert, ListBase *hidden_areas); diff --git a/source/blender/blenlib/intern/pbvh.c b/source/blender/blenlib/intern/pbvh.c index b344eb21764..27e51b930c2 100644 --- a/source/blender/blenlib/intern/pbvh.c +++ b/source/blender/blenlib/intern/pbvh.c @@ -21,6 +21,7 @@ */ #include "DNA_meshdata_types.h" +#include "DNA_mesh_types.h" #include "DNA_object_types.h" #include "BLI_math.h" @@ -37,8 +38,6 @@ static void pbvh_free_nodes(PBVH *bvh); -#define LEAF_LIMIT 10000 - //#define PERFCNTRS /* Bitmap */ @@ -669,7 +668,6 @@ void BLI_pbvh_build_mesh(PBVH *bvh, MFace *faces, MVert *verts, bvh->vdata = vdata; bvh->fdata = fdata; bvh->totvert = totvert; - bvh->leaf_limit = LEAF_LIMIT; if(totface) pbvh_begin_build(bvh, totface, hidden_areas); @@ -690,16 +688,18 @@ void BLI_pbvh_build_grids(PBVH *bvh, DMGridData **grids, bvh->gridkey= gridkey; bvh->vdata= vdata; bvh->fdata= fdata; - bvh->leaf_limit = MAX2(LEAF_LIMIT/((gridsize-1)*(gridsize-1)), 1); + bvh->leaf_limit = MAX2(PBVH_DEFAULT_LEAF_LIMIT/((gridsize-1)*(gridsize-1)), 1); if(totgrid) pbvh_begin_build(bvh, totgrid, hidden_areas); } -PBVH *BLI_pbvh_new(void) +PBVH *BLI_pbvh_new(int leaf_limit) { PBVH *bvh = MEM_callocN(sizeof(PBVH), "pbvh"); + bvh->leaf_limit = leaf_limit; + return bvh; } @@ -1178,8 +1178,11 @@ static void pbvh_update_draw_buffers(PBVH *bvh, PBVHNode **nodes, int totnode, G flags); } else { - GPU_update_mesh_color_buffers(node->draw_buffers, - bvh, node, flags); + if(flags & GPU_DRAW_ACTIVE_MCOL) + GPU_update_mesh_ptex(node->draw_buffers, bvh, node); + else + GPU_update_mesh_color_buffers(node->draw_buffers, + bvh, node, flags); } node->flag &= ~PBVH_UpdateColorBuffers; diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt index cae0fccd12a..fd4f697409f 100644 --- a/source/blender/blenloader/CMakeLists.txt +++ b/source/blender/blenloader/CMakeLists.txt @@ -31,6 +31,7 @@ SET(INC ../makesdna ../readblenfile ../include ../makesrna ../python ../../kernel/gen_messaging ../render/extern/include + ../../../extern/ptex ${ZLIB_INC} ) diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index cc9fe1e57fe..0ecb1168b29 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -3278,6 +3278,17 @@ static void direct_link_customdata_multires(FileData *fd, int count, } } +static void direct_link_customdata_mptex(FileData *fd, int count, MPtex *mptex) +{ + if(mptex) { + int i; + + for(i = 0; i < count; ++i, ++mptex) { + mptex->data = newdataadr(fd, mptex->data); + } + } +} + static void direct_link_customdata(FileData *fd, CustomData *data, int count) { int i = 0; @@ -3297,6 +3308,8 @@ static void direct_link_customdata(FileData *fd, CustomData *data, int count) direct_link_mdisps(fd, count, layer->data, layer->flag & CD_FLAG_EXTERNAL); if(layer->type == CD_GRIDS) direct_link_customdata_multires(fd, count, layer->data); + if(layer->type == CD_MPTEX) + direct_link_customdata_mptex(fd, count, layer->data); i++; } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index e49c39117a2..b19838f9869 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -171,6 +171,8 @@ Any case: direct data is ALWAYS after the lib block #include "readfile.h" +#include "ptex.h" + #include <errno.h> /* ********* my write, buffered writing with minimum size chunks ************ */ @@ -1505,6 +1507,25 @@ static void write_customdata_multires(WriteData *wd, int count, } } +static void write_customdata_mptex(WriteData *wd, int count, + MPtex *mptex) +{ + int i; + + writestruct(wd, DATA, "MPtex", count, mptex); + + for(i = 0; i < count; ++i, ++mptex) { + int layersize = mptex->channels * ptex_data_size(mptex->type); + float texels = mptex->ures*mptex->vres; + + if(mptex->subfaces) + texels *= mptex->subfaces; + + writedata(wd, DATA, layersize * texels, + mptex->data); + } +} + static void write_customdata(WriteData *wd, ID *id, int count, CustomData *data, int partial_type, int partial_count) { int i; @@ -1533,6 +1554,9 @@ static void write_customdata(WriteData *wd, ID *id, int count, CustomData *data, else if (layer->type == CD_GRIDS) { write_customdata_multires(wd, count, layer->data); } + else if (layer->type == CD_MPTEX) { + write_customdata_mptex(wd, count, layer->data); + } else { CustomData_file_write_info(layer->type, &structname, &structnum); if (structnum) { diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index f0493d8e2d8..08fab38826f 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -28,6 +28,7 @@ SET(INC ../../blenlib ../include ../../../../intern/guardedalloc + ../../../../extern/ptex ../../makesdna ../../makesrna ../../windowmanager diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 3c92089a258..d57bc346387 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -243,5 +243,9 @@ pbvh_undo_s3 pbvh_undo_node_no(PBVHUndoNode *unode); float *pbvh_undo_node_layer_disp(PBVHUndoNode *unode); void pbvh_undo_node_set_layer_disp(PBVHUndoNode *unode, float *layer_disp); +/* ptex.c */ +void PTEX_OT_layer_add(struct wmOperatorType *ot); +void PTEX_OT_open(struct wmOperatorType *ot); + #endif /* ED_PAINT_INTERN_H */ diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index c60c81dad11..fc88aa15ebe 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -218,6 +218,10 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PAINT_OT_vertex_color_set); WM_operatortype_append(PAINT_OT_vertex_colors_to_texture); + /* ptex */ + WM_operatortype_append(PTEX_OT_layer_add); + WM_operatortype_append(PTEX_OT_open); + /* face-select */ WM_operatortype_append(PAINT_OT_face_select_linked); WM_operatortype_append(PAINT_OT_face_select_linked_pick); diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 05c53831f8f..c7dc76230b3 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -70,6 +70,7 @@ #include "BKE_multires.h" #include "BKE_object.h" #include "BKE_paint.h" +#include "BKE_subsurf.h" #include "BKE_utildefines.h" #include "WM_api.h" @@ -83,6 +84,8 @@ #include "ED_screen.h" #include "ED_view3d.h" +#include "ptex.h" + #include "paint_intern.h" /* polling - retrieve whether cursor should be set or operator should be done */ @@ -1459,7 +1462,6 @@ void PAINT_OT_weight_set(wmOperatorType *ot) /* ************ set / clear vertex paint mode ********** */ - static int set_vpaint(bContext *C, wmOperator *op) /* toggle */ { Object *ob= CTX_data_active_object(C); @@ -1485,13 +1487,13 @@ static int set_vpaint(bContext *C, wmOperator *op) /* toggle */ /* Turn off weight painting */ if (ob->mode & OB_MODE_WEIGHT_PAINT) set_wpaint(C, op); - - if(vp==NULL) - vp= scene->toolsettings->vpaint= new_vpaint(0); if(me->mcol==NULL) make_vertexcol(ob); + if(!CustomData_get_layer(&me->fdata, CD_MPTEX)) + WM_operator_name_call(C, "PTEX_OT_layer_add", WM_OP_INVOKE_REGION_WIN, NULL); + create_paintsession(ob); paint_cursor_start(C, vertex_paint_poll); @@ -1587,43 +1589,200 @@ static void vpaint_blend(Brush *brush, float col[4], float alpha) IMB_blend_color_float(col, col, brush->rgb, alpha, tool); } +/* TODO */ +#if 0 static void vpaint_nodes_grids(Brush *brush, PaintStroke *stroke, + MPtex *ptexes, GridToFace *grid_face_map, DMGridData **grids, CustomData *vdata, GridKey *gridkey, int *grid_indices, int totgrid, - int gridsize, int active) + int gridsize) { PaintStrokeTest test; - int i, x, y; + int i; paint_stroke_test_init(&test, stroke); for(i = 0; i < totgrid; ++i) { DMGridData *grid = grids[grid_indices[i]]; + PTex *ptex = &ptexes[grid_face_map[grid_indices[i]].face]; + int offset = grid_face_map[grid_indices[i]].offset * ptex->ures*ptex->vres; + int u, v; + float x, y, xfac, yfac; - for(y = 0; y < gridsize; ++y) { - for(x = 0; x < gridsize; ++x) { - DMGridData *elem = GRIDELEM_AT(grid, - y*gridsize+x, - gridkey); - float *co = GRIDELEM_CO(elem, gridkey); - float *gridcol = GRIDELEM_COLOR(elem, gridkey)[active]; - float mask = paint_mask_from_gridelem(elem, - gridkey, - vdata); + xfac = (gridsize-1); + if(ptex->ures > 1) xfac /= (float)(ptex->ures - 1); + yfac = (gridsize-1); + if(ptex->vres > 1) yfac /= (float)(ptex->vres - 1); + + for(v = 0; v < ptex->vres; ++v) { + for(u = 0; u < ptex->ures; ++u) { + DMGridData *elem[4]; + float *color, co[3], co_top[3], co_bot[3]; + int xi, yi; + + color = ptex->colors[offset + v*ptex->ures+ u]; + + /* do a naive interpolation to get gridco, + can probably do this much better */ + + x = u*xfac; + y = v*yfac; + xi = (int)x; + yi = (int)y; + + elem[0] = GRIDELEM_AT(grid, yi*gridsize+xi, gridkey); + elem[1] = GRIDELEM_AT(grid, (yi+1)*gridsize+xi, gridkey); + elem[2] = GRIDELEM_AT(grid, (yi+1)*gridsize+xi+1, gridkey); + elem[3] = GRIDELEM_AT(grid, yi*gridsize+xi+1, gridkey); + + interp_v3_v3v3(co_top, GRIDELEM_CO(elem[0], gridkey), + GRIDELEM_CO(elem[3], gridkey), x - xi); + + interp_v3_v3v3(co_bot, GRIDELEM_CO(elem[1], gridkey), + GRIDELEM_CO(elem[2], gridkey), x - xi); + + interp_v3_v3v3(co, co_top, co_bot, y - yi); if(paint_stroke_test(&test, co)) { float strength; strength = brush->alpha * - paint_stroke_combined_strength(stroke, test.dist, co, mask); + paint_stroke_combined_strength(stroke, test.dist, co, 0); - vpaint_blend(brush, gridcol, strength); + vpaint_blend(brush, color, strength); } } } } } +#endif + +static void ptex_elem_to_float4(PtexDataType type, int channels, void *data, float fcol[4]) +{ + int i; + + /* default alpha */ + fcol[3] = 1; + + switch(type) { + case PTEX_DT_UINT8: + for(i = 0; i < channels; ++i) + fcol[i] = ((unsigned char*)data)[i] / 255.0; + break; + case PTEX_DT_UINT16: + for(i = 0; i < channels; ++i) + fcol[i] = ((unsigned char*)data)[i] / 65535.0; + break; + case PTEX_DT_FLOAT: + for(i = 0; i < channels; ++i) + fcol[i] = ((float*)data)[i]; + break; + default: + break; + } + + if(channels == 1) { + for(i = 1; i < 4; ++i) + fcol[i] = fcol[0]; + } +} + +static void ptex_elem_from_float4(PtexDataType type, int channels, void *data, float fcol[4]) +{ + int i; + + if(channels == 1) { + float avg = (fcol[0]+fcol[1]+fcol[2]) / 3.0f; + switch(type) { + case PTEX_DT_UINT8: + ((unsigned char*)data)[0] = avg * 255; + break; + case PTEX_DT_UINT16: + ((unsigned short*)data)[0] = avg * 65535; + break; + case PTEX_DT_FLOAT: + ((float*)data)[0] = avg; + break; + default: + break; + } + } + else { + switch(type) { + case PTEX_DT_UINT8: + for(i = 0; i < channels; ++i) + ((unsigned char*)data)[i] = fcol[i] * 255; + break; + case PTEX_DT_UINT16: + for(i = 0; i < channels; ++i) + ((unsigned short*)data)[i] = fcol[i] * 65535; + break; + case PTEX_DT_FLOAT: + for(i = 0; i < channels; ++i) + ((float*)data)[i] = fcol[i]; + break; + default: + break; + } + } +} + +static void vpaint_ptex_from_quad(Brush *brush, PaintStroke *stroke, PaintStrokeTest *test, + MPtex *pt, char *data, + float v1[3], float v2[3], float v3[3], float v4[3]) + +{ + float dtop[3], dbot[3], yinterp, ustep, vstep; + float co_bot[3], co_top[3]; + int u, v, layersize; + + layersize = pt->channels * ptex_data_size(pt->type); + + sub_v3_v3v3(dtop, v1, v2); + sub_v3_v3v3(dbot, v4, v3);; + ustep = 1; + if(pt->ures != 1) + ustep /= (pt->ures - 1); + mul_v3_fl(dtop, ustep); + mul_v3_fl(dbot, ustep); + + vstep = 1; + if(pt->vres != 1) + vstep /= (pt->vres - 1); + + for(v = 0, yinterp = 0; v < pt->vres; ++v) { + copy_v3_v3(co_top, v2); + copy_v3_v3(co_bot, v3); + + for(u = 0; u < pt->ures; ++u, data += layersize) { + float co[3]; + + interp_v3_v3v3(co, co_bot, co_top, yinterp); + + if(paint_stroke_test(test, co)) { + float strength; + float fcol[4]; + + strength = brush->alpha * + paint_stroke_combined_strength(stroke, test->dist, co, 0); + + ptex_elem_to_float4(pt->type, pt->channels, data, fcol); + vpaint_blend(brush, fcol, strength); + ptex_elem_from_float4(pt->type, pt->channels, data, fcol); + } + + /*(*color)[0] = u / (pt->ures-1.0f); + (*color)[1] = v / (pt->vres-1.0f); + (*color)[2] = 1;*/ + + add_v3_v3(co_bot, dbot); + add_v3_v3(co_top, dtop); + } + + yinterp += vstep; + } +} static void vpaint_nodes_faces(Brush *brush, PaintStroke *stroke, MFace *mface, MVert *mvert, @@ -1631,6 +1790,7 @@ static void vpaint_nodes_faces(Brush *brush, PaintStroke *stroke, int *face_indices, int totface) { PaintStrokeTest test; + MPtex *mptex; MCol *mcol; int pmask_totlayer, pmask_first_layer; int i, j; @@ -1641,39 +1801,46 @@ static void vpaint_nodes_faces(Brush *brush, PaintStroke *stroke, paint_stroke_test_init(&test, stroke); + mptex = CustomData_get_layer(fdata, CD_MPTEX); + for(i = 0; i < totface; ++i) { int face_index = face_indices[i]; MFace *f = mface + face_index; + MPtex *pt = &mptex[face_index]; int S = f->v4 ? 4 : 3; - for(j = 0; j < S; ++j) { - int vndx = (&f->v1)[j]; - int cndx = face_index*4 + j; - float *co = mvert[vndx].co; - float fcol[4]; - float mask; - - fcol[0] = mcol[cndx].b / 255.0f; - fcol[1] = mcol[cndx].g / 255.0f; - fcol[2] = mcol[cndx].r / 255.0f; - fcol[3] = mcol[cndx].a / 255.0f; - - mask = paint_mask_from_vertex(vdata, vndx, - pmask_totlayer, - pmask_first_layer); - if(paint_stroke_test(&test, co)) { - float strength; - - strength = brush->alpha * - paint_stroke_combined_strength(stroke, test.dist, co, mask); - - vpaint_blend(brush, fcol, strength); + if(S == 4) { + /* fast case */ + vpaint_ptex_from_quad(brush, stroke, &test, pt, pt->data, + mvert[f->v3].co, mvert[f->v4].co, + mvert[f->v1].co, mvert[f->v2].co); + } + else { + /* subfaces */ + + /* for now this is just tris, can be updated for ngons too */ + + float half[4][3], center[3]; + int layersize = pt->channels * ptex_data_size(pt->type); + + for(j = 0; j < 3; ++j) { + int next = (j == S-1) ? 0 : j+1; + mid_v3_v3v3(half[j], mvert[(&f->v1)[j]].co, mvert[(&f->v1)[next]].co); } - mcol[cndx].b = fcol[0] * 255.0f; - mcol[cndx].g = fcol[1] * 255.0f; - mcol[cndx].r = fcol[2] * 255.0f; - mcol[cndx].a = fcol[3] * 255.0f; + cent_tri_v3(center, mvert[f->v1].co, mvert[f->v2].co, mvert[f->v3].co); + + for(j = 0; j < 3; ++j) { + int vndx = (&f->v1)[j]; + int prev = j==0 ? S-1 : j-1; + char *data = pt->data; + data += layersize*pt->ures*pt->vres * j; + + vpaint_ptex_from_quad(brush, stroke, &test, pt, + data, + mvert[vndx].co, half[j], + center, half[prev]); + } } } } @@ -1823,50 +1990,34 @@ static void vpaint_nodes(VPaint *vp, PaintStroke *stroke, PBVH *pbvh = ob->paint->pbvh; Brush *brush = paint_brush(&vp->paint); int blur = brush->vertexpaint_tool == VERTEX_PAINT_BLUR; + CustomData *vdata = NULL; + CustomData *fdata = NULL; int n; - for(n = 0; n < totnode; ++n) { - CustomData *vdata = NULL; - CustomData *fdata = NULL; + BLI_pbvh_get_customdata(pbvh, &vdata, &fdata); + for(n = 0; n < totnode; ++n) { pbvh_undo_push_node(nodes[n], PBVH_UNDO_COLOR, ob); - BLI_pbvh_get_customdata(pbvh, &vdata, &fdata); - if(BLI_pbvh_uses_grids(pbvh)) { +#if 0 /* TODO */ DMGridData **grids; GridKey *gridkey; int *grid_indices, totgrid, gridsize; - int active; BLI_pbvh_node_get_grids(pbvh, nodes[n], &grid_indices, &totgrid, NULL, &gridsize, &grids, NULL, &gridkey); - active = gridelem_active_offset(fdata, gridkey, CD_MCOL); - - if(active != -1) { - if(blur) { - vpaint_nodes_grids_smooth(brush, stroke, - grids, vdata, - gridkey, - grid_indices, - totgrid, - gridsize, - active); - BLI_pbvh_node_set_flags(nodes[n], - SET_INT_IN_POINTER(PBVH_NeedsColorStitch)); - } - else { - vpaint_nodes_grids(brush, stroke, - grids, vdata, - gridkey, - grid_indices, - totgrid, - gridsize, - active); - } - } + vpaint_nodes_grids(brush, stroke, + NULL, + grid_face_map, + grids, vdata, + gridkey, + grid_indices, + totgrid, + gridsize); +#endif } else { MVert *mvert; diff --git a/source/blender/editors/sculpt_paint/pbvh_undo.c b/source/blender/editors/sculpt_paint/pbvh_undo.c index bd883ac1507..88033ce9fd8 100644 --- a/source/blender/editors/sculpt_paint/pbvh_undo.c +++ b/source/blender/editors/sculpt_paint/pbvh_undo.c @@ -550,6 +550,9 @@ void pbvh_undo_push_end(void) ListBase *lb= undo_paint_push_get_list(UNDO_PAINT_MESH); PBVHUndoNode *unode; + if(!lb || !lb->first) + return; + /* remove data that's only used during stroke */ for(unode=lb->first; unode; unode=unode->next) { if(unode->no) { diff --git a/source/blender/editors/sculpt_paint/ptex.c b/source/blender/editors/sculpt_paint/ptex.c new file mode 100644 index 00000000000..b51c51b2b3d --- /dev/null +++ b/source/blender/editors/sculpt_paint/ptex.c @@ -0,0 +1,331 @@ +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_space_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_DerivedMesh.h" +#include "BKE_report.h" +#include "BKE_subsurf.h" + +#include "BLI_math.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ptex.h" + +#include <assert.h> +#include <stdlib.h> + +static int next_power_of_two(int n) +{ + n--; + n = (n >> 1) | n; + n = (n >> 2) | n; + n = (n >> 4) | n; + n = (n >> 8) | n; + n = (n >> 16) | n; + n++; + + return n; +} + +static const void *ptex_default_data(PtexDataType type) { + static const unsigned char ptex_def_val_uc[] = {255, 255, 255, 255}; + static const unsigned short ptex_def_val_us[] = {65535, 65535, 65535, 65535}; + static const float ptex_def_val_f[] = {1, 1, 1, 1}; + + switch(type) { + case PTEX_DT_UINT8: + return ptex_def_val_uc; + case PTEX_DT_UINT16: + return ptex_def_val_us; + case PTEX_DT_FLOAT: + return ptex_def_val_f; + default: + return NULL; + }; +} + +/* add a new ptex layer + automatically sets resolution based on face area */ +static int ptex_layer_add_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Mesh *me = ob->data; + MPtex *mptex; + float (*limit_pos)[3], *face_area, totarea; + float density; + float largest_face_area = 0; + const void *def_val; + PtexDataType type; + int totchannel; + int layer_size; + int tottexel = 0; + int i, j; + + type = RNA_enum_get(op->ptr, "type"); + totchannel = RNA_int_get(op->ptr, "channels"); + layer_size = ptex_data_size(type) * totchannel; + def_val = ptex_default_data(type); + + mptex = CustomData_add_layer(&me->fdata, CD_MPTEX, CD_CALLOC, + NULL, me->totface); + + /* TODO: for now i'm allocating texels based on limit surface area; + according to ptex paper it's better to use surface derivatives */ + + limit_pos = MEM_callocN(sizeof(float)*3*me->totvert, "limit_pos"); + face_area = MEM_callocN(sizeof(float)*me->totface, "face_area"); + subsurf_calculate_limit_positions(me, limit_pos); + for(i = 0; i < me->totface; ++i) { + MFace *f = &me->mface[i]; + if(f->v4) { + face_area[i] = area_quad_v3(limit_pos[f->v1], limit_pos[f->v2], + limit_pos[f->v3], limit_pos[f->v4]); + } + else { + face_area[i] = area_tri_v3(limit_pos[f->v1], limit_pos[f->v2], + limit_pos[f->v3]); + } + largest_face_area = MAX2(largest_face_area, face_area[i]); + totarea += face_area[i]; + } + + /* try to make the density factor less dependent on mesh size */ + density = RNA_float_get(op->ptr, "density") * 1000 / largest_face_area; + + for(i = 0; i < me->totface; ++i) { + int S = me->mface[i].v4 ? 4 : 3; + int ures; + int vres; + int gridsize; + int texels; + char *data; + + if(S == 4) { + /* adjust u and v resolution by the ration + between the average edge size in u and v + directions */ + float len1 = (len_v3v3(limit_pos[me->mface[i].v1], + limit_pos[me->mface[i].v2]) + + len_v3v3(limit_pos[me->mface[i].v3], + limit_pos[me->mface[i].v4])) * 0.5f; + float len2 = (len_v3v3(limit_pos[me->mface[i].v2], + limit_pos[me->mface[i].v3]) + + len_v3v3(limit_pos[me->mface[i].v4], + limit_pos[me->mface[i].v1])) * 0.5f; + float r = len2/len1; + + ures = next_power_of_two(sqrtf((face_area[i] * density) * r)); + vres = next_power_of_two(sqrtf((face_area[i] * density) / r)); + } + else { + /* do triangles uniform (subfaces) */ + ures = sqrtf(face_area[i] * (density / 3.0f)); + vres = ures = next_power_of_two(ures); + } + + ures = MAX2(ures, 1); + vres = MAX2(vres, 1); + gridsize = ures * vres; + + mptex[i].ures = ures; + mptex[i].vres = vres; + mptex[i].type = type; + mptex[i].channels = totchannel; + + if(S == 4) { + mptex[i].subfaces = 0; + texels = gridsize; + } + else { + mptex[i].subfaces = S; + texels = S*gridsize; + } + + data = mptex[i].data = MEM_callocN(layer_size * texels, "PTex colors"); + tottexel += texels; + + for(j = 0; j < texels; ++j) { + memcpy(data, def_val, layer_size); + data += layer_size; + } + } + + printf("total texels = %d, sqrt(texels)=%.1f\n", tottexel, sqrtf(tottexel)); + + MEM_freeN(face_area); + MEM_freeN(limit_pos); + + return OPERATOR_FINISHED; +} + +void PTEX_OT_layer_add(wmOperatorType *ot) +{ + static EnumPropertyItem type_items[]= { + {PTEX_DT_UINT8, "PTEX_DT_UINT8", 0, "8-bit channels", ""}, + {PTEX_DT_UINT16, "PTEX_DT_UINT16", 0, "16-bit channels", ""}, + {PTEX_DT_FLOAT, "PTEX_DT_FLOAT", 0, "32-bit floating-point channels", ""}, + + {0, NULL, 0, NULL, NULL}}; + + /* identifiers */ + ot->name= "Add Layer"; + ot->description= "Add a new ptex layer"; + ot->idname= "PTEX_OT_layer_add"; + + /* api callbacks */ + ot->exec= ptex_layer_add_exec; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_float(ot->srna, "density", 10, 0, 6000, "Density", "Density of texels to generate", 0, 6000); + RNA_def_int(ot->srna, "channels", 3, 1, 4, "Channels", "", 1, 4); + RNA_def_enum(ot->srna, "type", type_items, PTEX_DT_FLOAT, "Type", "Layer channels and data type"); +} + +/* loads a .ptx file + makes some fairly strict assumptions that could + be relaxed later as our ptex implementation is refined + + on the other hand, some unsupported ptex features + are not checked for yet +*/ +int ptex_open_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Mesh *me = ob->data; + MPtex *mptex; + + PtexTextureHandle *ptex_texture; + PtexDataType ptex_data_type; + int totchannel; + + char *path; + int i, j; + + path = RNA_string_get_alloc(op->ptr, "filepath", NULL, 0); + + ptex_texture = ptex_open(path, 1, 0); + MEM_freeN(path); + + /* check if loader worked */ + if(!ptex_texture) { + BKE_report(op->reports, RPT_ERROR, "Error loading ptex file (see stdout for now, TODO)"); + return OPERATOR_CANCELLED; + } + + /* data type */ + ptex_data_type = ptex_texture_data_type(ptex_texture); + if(ptex_data_type == PTEX_DT_UNSUPPORTED) { + BKE_report(op->reports, RPT_ERROR, "Ptex format unsupported"); + ptex_texture_release(ptex_texture); + return OPERATOR_CANCELLED; + } + + /* data channels */ + totchannel = ptex_texture_num_channels(ptex_texture); + if(totchannel == 2 || totchannel > 4) { + BKE_report(op->reports, RPT_ERROR, "Ptex channel count unsupported"); + ptex_texture_release(ptex_texture); + return OPERATOR_CANCELLED; + } + + /* check that ptex file matches mesh topology */ + for(i = 0, j = 0; i < me->totface; ++i, j += (me->mface[i].v4 ? 1 : 3)) { + MFace *f = &me->mface[i]; + PtexFaceInfoHandle *ptex_face = ptex_texture_get_face_info(ptex_texture, j); + int subface; + + if(!ptex_face) { + BKE_report(op->reports, RPT_ERROR, "Ptex/mesh topology mismatch"); + ptex_texture_release(ptex_texture); + return OPERATOR_CANCELLED; + } + + subface = ptex_face_info_is_subface(ptex_face); + + if(subface != (f->v4 == 0)) { + BKE_report(op->reports, RPT_ERROR, "Ptex/mesh topology mismatch"); + ptex_texture_release(ptex_texture); + return OPERATOR_CANCELLED; + } + + /* TODO: check that all subfaces of a particular face have the same resolution */ + } + + mptex = CustomData_add_layer(&me->fdata, CD_MPTEX, CD_CALLOC, + NULL, me->totface); + + /* TODO: for now, just convert to customdata ptex */ + for(i = 0, j = 0; i < me->totface; ++i, j += (me->mface[i].v4 ? 1 : 3)) { + int S = me->mface[i].v4 ? 4 : 3; + PtexFaceInfoHandle *ptex_face; + PtexResHandle *ptex_res; + int texels; + + ptex_face = ptex_texture_get_face_info(ptex_texture, j); + ptex_res = ptex_face_get_res(ptex_face); + + mptex[i].ures = ptex_res_u(ptex_res); + mptex[i].vres = ptex_res_v(ptex_res); + mptex[i].type = ptex_data_type; + mptex[i].channels = totchannel; + + if(S == 4) { + mptex[i].subfaces = 0; + texels = mptex[i].ures * mptex[i].vres; + } + else { + mptex[i].subfaces = S; + mptex[i].ures = ptex_res_u(ptex_res); + mptex[i].vres = ptex_res_v(ptex_res); + texels = mptex[i].ures * mptex[i].vres * S; + } + + mptex[i].data = MEM_callocN(ptex_data_size(ptex_data_type) * + totchannel * texels, "Ptex file data"); + + ptex_texture_get_data(ptex_texture, j, mptex[i].data, 0, ptex_res); + + /* TODO: interleave data? */ + } + + /* data is all copied, can release ptex file */ + ptex_texture_release(ptex_texture); + + return OPERATOR_FINISHED; +} + +static int ptex_open_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + WM_event_add_fileselect(C, op); + return OPERATOR_RUNNING_MODAL; +} + +void PTEX_OT_open(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Open"; + ot->idname= "PTEX_OT_open"; + + /* api callbacks */ + ot->exec= ptex_open_exec; + ot->invoke= ptex_open_invoke; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + WM_operator_properties_filesel(ot, 0, FILE_SPECIAL, FILE_OPENFILE, WM_FILESEL_FILEPATH|WM_FILESEL_RELPATH); +} diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 126cddf852f..4401600a317 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -28,7 +28,8 @@ FILE(GLOB SRC intern/*.c) SET(INC . ../blenlib ../blenkernel ../makesdna ../makesrna ../include - ../../../extern/glew/include ../../../intern/guardedalloc ../../../intern/smoke/extern ../imbuf) + ../../../extern/glew/include ../../../intern/guardedalloc ../../../intern/smoke/extern ../imbuf + ../../../extern/ptex) IF(WIN32) INCLUDE_DIRECTORIES(${PTHREADS_INC}) diff --git a/source/blender/gpu/GPU_buffers.h b/source/blender/gpu/GPU_buffers.h index 79feb5cf6eb..62151f1e338 100644 --- a/source/blender/gpu/GPU_buffers.h +++ b/source/blender/gpu/GPU_buffers.h @@ -162,6 +162,12 @@ void GPU_update_grid_color_buffers(GPU_Buffers *buffers, int *grid_indices, int totgrid, int gridsize, struct GridKey *gridkey, struct CustomData *vdata, GPUDrawFlags flags); +typedef struct { + float (*colors)[4]; + int ures, vres; +} PTexHandle; +void GPU_update_mesh_ptex(GPU_Buffers *buffers, struct PBVH *bvh, struct PBVHNode *node); +void GPU_update_grid_ptex(GPU_Buffers *buffers, PTexHandle *grids, int totgrid); void GPU_draw_buffers(GPU_Buffers *buffers, struct PBVH *bvh, struct PBVHNode *node, GPUDrawFlags flags); void GPU_free_buffers(GPU_Buffers *buffers); diff --git a/source/blender/gpu/intern/gpu_buffers.c b/source/blender/gpu/intern/gpu_buffers.c index a2ff9b40b9f..6c12f981897 100644 --- a/source/blender/gpu/intern/gpu_buffers.c +++ b/source/blender/gpu/intern/gpu_buffers.c @@ -35,6 +35,7 @@ #include <string.h> #include "GL/glew.h" +#include "ptex.h" #include "MEM_guardedalloc.h" @@ -423,6 +424,9 @@ struct GPU_Buffers { GLuint vert_buf, index_buf, color_buf; GLenum index_type; + GLuint *ptex; + int totptex; + unsigned int tot_tri, tot_quad; }; @@ -730,6 +734,120 @@ void GPU_update_grid_color_buffers(GPU_Buffers *buffers, DMGridData **grids, int } } +GLenum gl_type_from_ptex(MPtex *pt) +{ + switch(pt->type) { + case PTEX_DT_UINT8: + return GL_UNSIGNED_BYTE; + case PTEX_DT_UINT16: + return GL_UNSIGNED_SHORT; + case PTEX_DT_FLOAT: + return GL_FLOAT; + default: + return 0; + } +} + +GLenum gl_format_from_ptex(MPtex *pt) +{ + switch(pt->channels) { + case 1: + return GL_LUMINANCE; + case 2: + return GL_LUMINANCE_ALPHA; + case 3: + return GL_RGB; + case 4: + return GL_RGBA; + default: + return 0; + } +} + +void GPU_update_mesh_ptex(GPU_Buffers *buffers, PBVH *pbvh, PBVHNode *node) +{ + CustomData *fdata; + MFace *mface; + MPtex *mptex; + int *face_indices, totface; + int i, j; + + BLI_pbvh_get_customdata(pbvh, NULL, &fdata); + BLI_pbvh_node_get_faces(pbvh, node, &mface, &face_indices, NULL, &totface); + + /* TODO: composite multiple layers */ + mptex = CustomData_get_layer(fdata, CD_MPTEX); + + /* pack all ptex for one face into one texture */ + if(!buffers->ptex) { + buffers->totptex = totface; + buffers->ptex = MEM_callocN(sizeof(GLuint) * buffers->totptex, "PTex IDs"); + glGenTextures(buffers->totptex, buffers->ptex); + } + + for(i = 0; i < totface; ++i) { + int face_ndx = face_indices[i]; + MPtex *pt = &mptex[face_ndx]; + int S = mface[i].v4 ? 4 : 3; + + GLenum gltype = gl_type_from_ptex(pt); + GLenum glformat = gl_format_from_ptex(pt); + int layersize = pt->channels * ptex_data_size(pt->type); + + glBindTexture(GL_TEXTURE_2D, buffers->ptex[i]); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + if(S == 4) { + /* load quad ptex whole */ + glTexImage2D(GL_TEXTURE_2D, 0, glformat, pt->ures, pt->vres, + 0, glformat, gltype, pt->data); + } + else { + /* pack non-quads ptex in side by side */ + glTexImage2D(GL_TEXTURE_2D, 0, glformat, S * pt->ures, pt->vres, + 0, glformat, gltype, NULL); + + for(j = 0; j < S; ++j) { + glTexSubImage2D(GL_TEXTURE_2D, 0, pt->ures*j, 0, pt->ures, + pt->vres, glformat, gltype, + ((char*)pt->data) + pt->ures*pt->vres*j*layersize); + } + } + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GPU_update_grid_ptex(GPU_Buffers *buffers, PTexHandle *grids, int totgrid) +{ + int i; + + if(buffers->totptex != totgrid) { + buffers->totptex = totgrid; + buffers->ptex = MEM_callocN(sizeof(GLuint) * totgrid, "PTex IDs"); + glGenTextures(totgrid, buffers->ptex); + } + + + for(i = 0; i < totgrid; ++i) { + glBindTexture(GL_TEXTURE_2D, buffers->ptex[i]); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, grids[i].ures, grids[i].vres, + 0, GL_RGBA, GL_FLOAT, grids[i].colors); + } +} + typedef struct { float co[3]; float no[3]; @@ -878,6 +996,71 @@ GPU_Buffers *GPU_build_grid_buffers(DMGridData **grids, return buffers; } +static void gpu_draw_ptex_node_without_vb(GPU_Buffers *buffers, MFace *mface, MVert *mvert, + int *face_indices, int totface, GPUDrawFlags flags) +{ + int i, j; + + for(i = 0; i < totface; ++i) { + int face_index = face_indices[i]; + MFace *f = mface + face_index; + int S = f->v4 ? 4 : 3; + + glBindTexture(GL_TEXTURE_2D, buffers->ptex[i]); + + glBegin(GL_QUADS); + + if(S == 4) { + /* fast case */ + + float uv[4][2] = {{0,0}, {1,0}, {1,1}, {0,1}}; + + for(j = 0; j < 4; ++j) { + int vndx = (&f->v1)[j]; + + glTexCoord2fv(uv[j]); + glNormal3sv(mvert[vndx].no); + glVertex3fv(mvert[vndx].co); + } + } + else { + /* split ngons into subfaces (still quads) */ + + /* for now this is just tris, can be updated for ngons too */ + float center[3]; + float half[4][3]; + float texoffs = 1.0f / S; + + cent_tri_v3(center, mvert[f->v1].co, mvert[f->v2].co, mvert[f->v3].co); + + for(j = 0; j < 3; ++j) { + int next = (j == S-1) ? 0 : j+1; + mid_v3_v3v3(half[j], mvert[(&f->v1)[j]].co, mvert[(&f->v1)[next]].co); + } + + for(j = 0; j < 3; ++j) { + int prev = j == 0 ? S-1 : j-1; + int vndx = (&f->v1)[j]; + + glTexCoord2f(texoffs * (j+1), 1); + glNormal3sv(mvert[vndx].no); + glVertex3fv(mvert[vndx].co); + + glTexCoord2f(texoffs * j, 1); + glVertex3fv(half[j]); + + glTexCoord2f(texoffs * j, 0); + glVertex3fv(center); + + glTexCoord2f(texoffs * (j+1), 0); + glVertex3fv(half[prev]); + } + } + + glEnd(); + } +} + static void gpu_draw_node_without_vb(GPU_Buffers *buffers, PBVH *pbvh, PBVHNode *node, GPUDrawFlags flags) { DMGridData **grids; @@ -904,14 +1087,17 @@ static void gpu_draw_node_without_vb(GPU_Buffers *buffers, PBVH *pbvh, PBVHNode mcol_first_layer = CustomData_get_layer_index(fdata, CD_MCOL); pmask_first_layer = CustomData_get_layer_index(vdata, CD_PAINTMASK); - use_color = mcol_first_layer != -1 || pmask_first_layer != -1; + use_color = !(flags & GPU_DRAW_ACTIVE_MCOL) && pmask_first_layer != -1; } - + if(use_color) { glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); glEnable(GL_COLOR_MATERIAL); } + if(buffers->ptex && GPU_DRAW_ACTIVE_MCOL) + glEnable(GL_TEXTURE_2D); + if(use_grids) { int x, y; @@ -963,6 +1149,11 @@ static void gpu_draw_node_without_vb(GPU_Buffers *buffers, PBVH *pbvh, PBVHNode BLI_pbvh_node_get_verts(pbvh, node, NULL, &mvert); BLI_pbvh_node_get_faces(pbvh, node, &mface, &face_indices, NULL, &totface); + if(flags & GPU_DRAW_ACTIVE_MCOL && buffers->ptex) { + gpu_draw_ptex_node_without_vb(buffers, mface, mvert, face_indices, totface, flags); + return; + } + if(mcol_first_layer) mcol_totlayer = CustomData_number_of_layers(fdata, CD_MCOL); @@ -1078,6 +1269,10 @@ void GPU_free_buffers(GPU_Buffers *buffers_v) glDeleteBuffersARB(1, &buffers->vert_buf); if(buffers->index_buf) glDeleteBuffersARB(1, &buffers->index_buf); + if(buffers->ptex) { + glDeleteTextures(buffers->totptex, buffers->ptex); + MEM_freeN(buffers->ptex); + } MEM_freeN(buffers); } diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 33c536d8df6..407efc53cdc 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -103,7 +103,8 @@ typedef struct CustomDataMultires { #define CD_CLOTH_ORCO 23 #define CD_GRIDS 24 #define CD_PAINTMASK 25 -#define CD_NUMTYPES 26 +#define CD_MPTEX 26 +#define CD_NUMTYPES 27 /* Bits for CustomDataMask */ #define CD_MASK_MVERT (1 << CD_MVERT) @@ -130,6 +131,7 @@ typedef struct CustomDataMultires { #define CD_MASK_CLOTH_ORCO (1 << CD_CLOTH_ORCO) #define CD_MASK_GRIDS (1 << CD_GRIDS) #define CD_MASK_PAINTMASK (1 << CD_PAINTMASK) +#define CD_MASK_MPTEX (1 << CD_MPTEX) /* CustomData.flag */ diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 450c56b0dec..0eff26fb6b7 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -125,6 +125,26 @@ typedef struct MDisps { float (*disps)[3]; } MDisps; +typedef struct MPtex { + void *data; + + /* for non-quads, ures and vres are the + resolution of the subfaces */ + int ures; + int vres; + + /* for quads, subfaces=0; anything else + is set to the face's number of vertices */ + int subfaces; + + /* enum PtexDataType */ + char type; + + char channels; + + char pad[2]; +} MPtex; + /** Multires structs kept for compatibility with old files **/ typedef struct MultiresCol { float a, r, g, b; diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index b9bc1d626ac..cb1d1ca62cf 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -301,6 +301,7 @@ extern StructRNA RNA_MeshFloatPropertyLayer; extern StructRNA RNA_MeshIntProperty; extern StructRNA RNA_MeshIntPropertyLayer; extern StructRNA RNA_MeshPaintMaskLayer; +extern StructRNA RNA_MeshPtexLayer; extern StructRNA RNA_MeshSticky; extern StructRNA RNA_MeshStringProperty; extern StructRNA RNA_MeshStringPropertyLayer; diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c index d463df7b0d3..c9bff267c24 100644 --- a/source/blender/makesrna/intern/rna_mesh.c +++ b/source/blender/makesrna/intern/rna_mesh.c @@ -786,7 +786,7 @@ static void rna_MeshColorLayer_name_set(PointerRNA *ptr, const char *value) CustomData_set_layer_unique_name(fdata, cdl - fdata->layers); } -/* Paint mask layer */ +/* Paint mask layers */ static void rna_MeshPaintMask_update_data(Main *bmain, Scene *scene, PointerRNA *ptr) { @@ -878,6 +878,62 @@ static void rna_Mesh_active_paint_mask_index_range(PointerRNA *ptr, int *min, in *max= MAX2(0, *max); } +/* Ptex layers */ + +static char *rna_MeshPtexLayer_path(PointerRNA *ptr) +{ + CustomDataLayer *layer= (CustomDataLayer*)ptr->data; + + return BLI_sprintfN("ptex[%d]", (MPtex*)ptr->data - (MPtex*)layer->data); +} + +static int rna_ptex_layer_check(CollectionPropertyIterator *iter, void *data) +{ + CustomDataLayer *layer= (CustomDataLayer*)data; + return (layer->type != CD_MPTEX); +} + +static void rna_Mesh_ptex_layers_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + Mesh *me= (Mesh*)ptr->data; + CustomData *fdata= rna_mesh_fdata(me); + rna_iterator_array_begin(iter, (void*)fdata->layers, + sizeof(CustomDataLayer), + fdata->totlayer, 0, + rna_ptex_layer_check); +} + +static int rna_Mesh_ptex_layers_length(PointerRNA *ptr) +{ + return rna_face_CustomDataLayer_count(ptr, CD_MPTEX); +} + + +static int rna_Mesh_active_ptex_index_get(PointerRNA *ptr) +{ + Mesh *me= (Mesh*)ptr->data; + CustomData *fdata= rna_mesh_fdata(me); + return CustomData_get_active_layer(fdata, CD_MPTEX); +} + +static void rna_Mesh_active_ptex_index_set(PointerRNA *ptr, int value) +{ + Mesh *me= (Mesh*)ptr->data; + CustomData *fdata= rna_mesh_fdata(me); + + CustomData_set_layer_active(fdata, CD_MPTEX, value); +} + +static void rna_Mesh_active_ptex_index_range(PointerRNA *ptr, int *min, int *max) +{ + Mesh *me= (Mesh*)ptr->data; + CustomData *fdata= rna_mesh_fdata(me); + + *min= 0; + *max= CustomData_number_of_layers(fdata, CD_MPTEX)-1; + *max= MAX2(0, *max); +} + /* Custom property layers */ static void rna_MeshFloatPropertyLayer_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) @@ -1619,6 +1675,22 @@ static void rna_def_mcol(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Mesh_update_data"); } +static void rna_def_ptex(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna= RNA_def_struct(brna, "MeshPtexLayer", NULL); + RNA_def_struct_sdna(srna, "CustomDataLayer"); + RNA_def_struct_ui_text(srna, "Mesh Ptex Layer", ""); + RNA_def_struct_path_func(srna, "rna_MeshPtexLayer_path"); + + prop= RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_property_update(prop, 0, "rna_Mesh_update_data"); +} + static void rna_def_paintmask(BlenderRNA *brna) { StructRNA *srna; @@ -1870,6 +1942,20 @@ static void rna_def_mesh(BlenderRNA *brna) "rna_Mesh_active_paint_mask_index_range"); RNA_def_property_ui_text(prop, "Active Paint Mask Index", "Active paint mask layer index"); + /* Ptex */ + prop= RNA_def_property(srna, "ptex_layers", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "fdata.layers", "fdata.totlayer"); + RNA_def_property_collection_funcs(prop, "rna_Mesh_ptex_layers_begin", + 0, 0, 0, "rna_Mesh_ptex_layers_length", 0, 0); + RNA_def_property_struct_type(prop, "MeshPtexLayer"); + RNA_def_property_ui_text(prop, "Ptex Layers", "Variable-resolution mesh colors"); + + prop= RNA_def_property(srna, "active_ptex_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_funcs(prop, "rna_Mesh_active_ptex_index_get", + "rna_Mesh_active_ptex_index_set", + "rna_Mesh_active_ptex_index_range"); + RNA_def_property_ui_text(prop, "Active Ptex Index", "Active ptex layer index"); + prop= RNA_def_property(srna, "float_layers", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "fdata.layers", "fdata.totlayer"); RNA_def_property_collection_funcs(prop, "rna_Mesh_float_layers_begin", 0, 0, 0, "rna_Mesh_float_layers_length", 0, 0); @@ -2067,6 +2153,7 @@ void RNA_def_mesh(BlenderRNA *brna) rna_def_mtface(brna); rna_def_msticky(brna); rna_def_mcol(brna); + rna_def_ptex(brna); rna_def_paintmask(brna); rna_def_mproperties(brna); } diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index a653d45bd14..8b451474f38 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -489,6 +489,7 @@ ENDIF(CMAKE_SYSTEM_NAME MATCHES "Linux") bf_gen_python extern_binreloc extern_glew + extern_ptex extern_libopenjpeg bf_videotex bf_rna |