Deep Texture APIAugust, 2003 |
|
This application note describes the dtex library. This library provides the structures and functions necessary to create, load, and modify Pixar deep texture map files. It maintains a tile cache under the covers, making it possible to work on files without loading them entirely into memory.
The API supports an arbitrary number of images in a single file, each with its own resolution, tile size, and view matrices. It also supports filtering of multiple depth functions and a lossy compression technique.
For more information on deep shadows, see Tom Lokovic, Eric Veach. "Deep Shadow Maps". Computer Graphics (SIGGRAPH 2000 Conference Proceedings), pages 385-392. ACM, July 2000.
Unless otherwise specified, an int return value from a library return is an error code. These error codes are:
Before working with deep shadow map files a tile cache must be created. The tile cache makes it possible to work on deep shadow files without loading them entirely into memory.
Once the tile cache has been created, pixel access routines will transparently use the tile cache. Note that, when outputting deep shadow files, it is important to access pixels in a coherent fashion, as thrashing the tile cache will result in nonoptimal deep shadow files. In other words, ideally, once a tile leaves the cache (and is flushed to disk) there should be no need to revisit this tile in the future.
For example, if you plan to write deep shadow map pixels in scanline order, make sure that the tile cache is large enough to contain one scanline's worth of tiles. If you are rendering an image that is 1024 pixels wide and are using a tilesize of 16 pixels, you will need to construct a tile cache containing at least 64 tiles. As each scanline is completed tiles will leave the cache and be flushed to disk, and will not need to be reloaded into the cache.
DtexCache * DtexCreateCache(int numTiles, DtexAccessor *accessor); int DtexDestroyCache(DtexCache *c); int DtexSyncCache(DtexCache *dsc);
The DtexSyncCache routine writes out all modified tiles of all files in the cache, causing the files on disk to reflect any changes that have been made.
DtexCreateCache returns NULL if no cache was created.
The following functions are used to create new deep texture files, or to open existing deep texture files. A cache must be created before using these routines.
int DtexOpenFile(const char *name, const char *mode, DtexCache *cache, DtexFile **result); int DtexCreateFile(const char *name, DtexCache *cache, DtexFile **result); int DtexClose(DtexFile *ds); int DtexSync(DtexFile *ds);
Calling either DtexClose or DtexSync on a texture file will write out all modified tiles, causing the file on disk to reflect any changes that have been made.
These routines manage the deep texture images within a given file. Currently, Pixar's RenderMan only recognizes the first deep texture image as a deep shadow map, but a future release may support arbitrary numbers of images.
int DtexAddImage(DtexFile *f, const char *name, int numChan, int width, int height, int tilewidth, int tileheight, float *NP, float *Nl, enum DtexCompression compression, enum DtexDataType datatype, DtexImage **result); int DtexCountImages(DtexFile *f); int DtexGetImageByName(DtexFile *f, const char *name, DtexImage **result); int DtexGetImageByIndex(DtexFile *f, int index, DtexImage **result);
DtexAddImage constructs a new image with the given dimensions and adds it to the given file, returning a pointer to a new DtexImage.
DtexCountImages returns the number of images in the given file.
DtexGetImageByName and DtexGetImageByIndex return a pointer to the an image with the given name or index. They will return DTEX_NOIMAGE if the given image doesn't exist. The resulting pointer is guaranteed to be valid until DtexClose() is called on the containing file.
int DtexWidth(DtexImage *i); char *DtexImageName(DtexImage *i); int DtexNumChan(DtexImage *i); int DtexHeight(DtexImage *i); int DtexTileWidth(DtexImage *i); int DtexTileHeight(DtexImage *i); int DtexNP(DtexImage *i, float *NP); int DtexNl(DtexImage *i, float *Nl); DtexCompression DtexGetCompression(DtexImage *i); DtexDataType DtexGetDataType(DtexImage *i);
int DtexSetPixelData(DtexImage *img, int x, int y, int numChan, int numPoints, float *data);
int DtexSetPixel(DtexImage *img, int x, int y, DtexPixel *pix);
int DtexGetPixel(DtexImage *img, int x, int y, DtexPixel *pix);
int DtexEval(DtexImage *img, int x, int y, float z, int n, float *data);
int DtexGetZRange(DtexImage *img, int x, int y, float *min, float *max);
int DtexGetMeanDepth(DtexImage *img, int x, int y, float *mean, float *alpha);
Because deep texture pixels vary in size, a pixel's storage must be dynamically allocated. This library provides a type, DtexPixel, which allows users to build and evaluate pixels. The structure is fairly heavyweight because it stores auxiliary information related to compression. We don't recommend allocating an entire image of these structures yourself; keep a small number of DtexPixel's around, and use DtexSetPixel to modify a DtexImage.
The following functions let the user create, modify, and destroy DtexPixel's. Pixels may be cleared with DtexClearPixel(). New datapoints may be added (in increasing Z order) with DtexAppendPixel(). If compression is used, a pixel must be DtexFinish()'ed before lookups can be performed in the pixel.
DtexPixel * DtexMakePixel(int numChan); void DtexDestroyPixel(DtexPixel *pix);
int DtexClearPixel(DtexPixel *pix, int numChan); int DtexEmptyPixel(DtexPixel *pix);
int DtexSpecifyPixel(DtexPixel *pix,int numChan,int numPoints, float *data);
int DtexIsPixelMonochrome(DtexPixel *p); int DtexPixelGetNumChan(DtexPixel *pix); int DtexPixelGetNumPoints(DtexPixel *pix);
int DtexPixelGetPoint(DtexPixel *pix, int i, float *z, float *data);
int DtexPixelSetPoint(DtexPixel *pix, int i, float z, float *data);
int DtexCopyPixel(DtexPixel *dest, DtexPixel *src);
int DtexFinishPixel(DtexPixel *dest);
int DtexAppendPixel(DtexPixel *pix, float z, int n, float *data, float error);
If error is non-zero, a lossy compression technique is applied. This error parameter corresponds to the error tolerance ε described in section 3.3 of "Deep Shadow Maps" by Lokovic and Veach. To clarify how this compression works using this API:
int DtexEvalPixel(DtexPixel *pix, float z, int n, float *data);
int DtexGetPixelZRange(DtexPixel *pix, float *min, float *max);
int DtexPrintPixel(DtexPixel *p);
int DtexAveragePixels(int n, DtexPixel **pixels, float *weights, float error, DtexPixel *result);
These routines provide functionality for the filtering of depth functions (i.e. the filtering of subpixel depth functions into a single depth function for the pixel). Filtering works by creating a DtexDeepFilter structure, filling in function information, then telling it to compute the result.
For efficiency, the DtexDeepFilter structure requires function information as a list of deltas rather than points. Specifically, we assume the function starts out at z=0 with a value of 1, followed by a list of deltas to the slope or position of the function.
Here's how a user would filter n deep functions:
DtexDeepFilter *DtexCreateDeepFilter(void); void DtexDestroyDeepFilter(DtexDeepFilter *filter);
float *DtexGetDeepFilterData(DtexDeepFilter *filter, int numChan, int numSamples, int *numDeltas, float *filterWeights, int totalNumDeltas);
int DtexComputeDeepPointData(DtexDeepFilter *filter, float *pointData, float error, int assumeSmooth);
If assumeSmooth is non-zero, the computation will assume that the underlying function is smooth, and that any discontinuities encountered are part of the sampling error. When we filter several functions to produce a new function, the input functions usually have discontinuities. By definition, the output function will thus have discontinuities. But in some cases, the true filtered function would have no discontinuties. "assumeSmooth" says we assume that the function should be smooth, so we ignore the discontinuties by dropping the top point of each one. This dropping happens after the functions have been filtered, but before the compression.
The interpretation of error is the same as in DtexAppendPixel, i.e. it controls lossy compression. When filtering across each point the same lossy compression technique is considered in turn.
int DtexCompressPointData(float *pointData, int numChan, int numPoints, float *result, float error);
int DtexCompressPixel(DtexPixel *src, DtexPixel *dest, float error);
void DtexQueryMemory(long *current,long *peak);
The following code demonstrates how to use the dtex library to read deep shadow files, read and manipulate pixels, and create new deep shadow files. When built, the dsmerge program allows the user to merge deep shadow map files of the same dimension into new deep shadow map files. Depth functions at each pixel are combined together in sorted order. It may be invoked with:
dsmerge infile1.dshd infile2.dshd ... outfile.dshd
#include <stdlib.h> #include <stdio.h> #include <math.h> #include <dtex.h> #include <assert.h> int main(int argc, char *argv[]) { float NP[16], Nl[16]; int i, j, x, y; DtexCache *inCache, *outCache; DtexImage **inImages, *outImage; float *inData, *outData; float iz, oz; int width, height, nChannels; int nInFiles; int nInPoints, nOutPoints; int inIndex, outIndex; char *inname, *outname; DtexFile **inFiles, *outFile; DtexPixel *inPixel, *outPixel, *mergedPixel; if (argc < 3) { fprintf(stderr, "Must supply at least one input file and an output.\n"); exit(1); } /* * Create the tile cache for the output image. In this * application, this needs to be large enough to hold one * scanline's worth of tiles, due to our scanline, perpixel * pattern of access. */ outCache = DtexCreateCache(128, NULL); /* * Construct a separate tile cache for input images. Again, it * should be large enough to hold one scanline's worth of tiles, * but from each input image. The choice of cache size here will * affect only speed, since if the cache thrashes we just end up * rereading the tile from the input image. */ inCache = DtexCreateCache(256, NULL); if (!outCache) { fprintf(stderr, "Unable to create output tile cache.\n"); exit(1); } /* Create output file */ outname = argv[argc - 1]; if (DtexCreateFile(outname, outCache, &outFile) != DTEX_NOERR) { fprintf(stderr, "Unable to open output file %s.\n", outname); exit(1); } nInFiles = 0; inImages = (DtexImage**) malloc((argc - 1) * sizeof(DtexImage*)); inFiles = (DtexFile**) malloc((argc - 1) * sizeof(DtexFile*)); if (!inImages || !inFiles) { fprintf(stderr, "Unable to open allocate memory for input files.\n"); exit(1); } /* * Loop over the input files, checking for validity and opening * the first input image */ for (i = 1; i < argc - 1; ++i) { inname = argv[i]; if (DtexOpenFile(inname, "rb", inCache, &inFiles[nInFiles]) != DTEX_NOERR) { fprintf(stderr, "Unable to open input file %s, skipping it.\n", inname); continue; } if (DtexCountImages(inFiles[nInFiles]) != 1) { fprintf(stderr, "Input file %s has number of images != 1, skipping.\n", inname); DtexClose(inFiles[nInFiles]); continue; } if (DtexGetImageByIndex(inFiles[nInFiles], 0, &inImages[nInFiles]) != DTEX_NOERR) { fprintf(stderr, "Unable to access image zero in input file %s, skipping.\n", inname); DtexClose(inFiles[nInFiles]); continue; } if (nInFiles == 0) { /* Create the appropriate output image now */ DtexNP(inImages[0], NP); DtexNl(inImages[0], Nl); if (DtexAddImage(outFile, DtexImageName(inImages[0]), DtexNumChan(inImages[0]), DtexWidth(inImages[0]), DtexHeight(inImages[0]), DtexTileWidth(inImages[0]), DtexTileHeight(inImages[0]), NP, Nl, DtexGetCompression(inImages[0]), DtexGetDataType(inImages[0]), &outImage) != DTEX_NOERR) { fprintf(stderr, "Unable to create output image %d.\n", j); exit(1); } } /* * Check that the output and input images have the * same dimensionality. At the moment this is limited * to number of channels, width, height, and data * type. */ else if (DtexNumChan(inImages[nInFiles]) != DtexNumChan(outImage) || DtexWidth(inImages[nInFiles]) != DtexWidth(outImage) || DtexHeight(inImages[nInFiles]) != DtexHeight(outImage) || DtexGetDataType(inImages[nInFiles]) != DtexGetDataType(outImage)) { fprintf(stderr, "Input file %s does not have matching dimensions as output image, skipping.\n", inname); DtexClose(inFiles[nInFiles]); continue; } nInFiles++; } /* Set up invariants over the set of input images */ width = DtexWidth(inImages[0]); height = DtexHeight(inImages[0]); nChannels = DtexNumChan(inImages[0]); inData = (float*) malloc(nChannels * 2 * sizeof(float)); outData = inData + nChannels; inPixel = DtexMakePixel(nChannels); outPixel = DtexMakePixel(nChannels); mergedPixel = DtexMakePixel(nChannels); if (!inPixel || !outPixel || !mergedPixel) { fprintf(stderr, "Unable to create pixels\n"); exit(1); } /* Loop over the scanlines in the image and merge pixels */ for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { for (i = 0; i < nInFiles; ++i) { DtexClearPixel(inPixel, nChannels); DtexClearPixel(outPixel, nChannels); DtexClearPixel(mergedPixel, nChannels); if (DtexGetPixel(inImages[i], x, y, inPixel) != DTEX_NOERR) { fprintf(stderr, "Unable to get input pixel at %d %d from %s.\n", x, y, inname); continue; } if (DtexGetPixel(outImage, x, y, outPixel) != DTEX_NOERR) { fprintf(stderr, "Unable to get output pixel at %d %d.\n", x, y); continue; } /* Merge the depth functions */ nInPoints = DtexPixelGetNumPoints(inPixel); nOutPoints = DtexPixelGetNumPoints(outPixel); inIndex = outIndex = 0; while (1) { /* * If we've finished with the in points, * finish off the merged pixels with the rest * of the out points */ if (inIndex == nInPoints) { while (outIndex < nOutPoints) { if (DtexPixelGetPoint(outPixel, outIndex, &oz, outData) != DTEX_NOERR) { fprintf(stderr, "Unable to get output pixel at %d %d\n", x, y); goto nextpixel; } if (DtexAppendPixel(mergedPixel, oz, nChannels, outData, 0) != DTEX_NOERR) { fprintf(stderr, "Unable to append to merged pixel at %d %d\n", x, y); goto nextpixel; } outIndex++; } break; } /* * Likewise, if we've finished the out points, * finish off with the rest of the in points. */ if (outIndex == nOutPoints) { while (inIndex < nInPoints) { if (DtexPixelGetPoint(inPixel, inIndex, &iz, inData) != DTEX_NOERR) { fprintf(stderr, "Unable to get input pixel at %d %d\n", x, y); goto nextpixel; } if (DtexAppendPixel(mergedPixel, iz, nChannels, inData, 0) != DTEX_NOERR) { fprintf(stderr, "Unable to append to merged pixel at %d %d\n", x, y); goto nextpixel; } inIndex++; } break; } /* Otherwise just insert in sorted order */ if (DtexPixelGetPoint(inPixel, inIndex, &iz, inData) != DTEX_NOERR) { fprintf(stderr, "Unable to get input pixel at %d %d\n", x, y); goto nextpixel; } if (DtexPixelGetPoint(outPixel, outIndex, &oz, outData) != DTEX_NOERR) { fprintf(stderr, "Unable to get output pixel at %d %d\n", x, y); goto nextpixel; } if (iz < oz) { inIndex++; if (DtexAppendPixel(mergedPixel, iz, nChannels, inData, 0) != DTEX_NOERR) { fprintf(stderr, "Unable to append to merged pixel at %d %d\n", x, y); goto nextpixel; } } else { outIndex++; if (DtexAppendPixel(mergedPixel, oz, nChannels, outData, 0) != DTEX_NOERR) { fprintf(stderr, "Unable to append to merged pixel at %d %d\n", x, y); goto nextpixel; } } } if (DtexFinishPixel(mergedPixel) != DTEX_NOERR) { fprintf(stderr, "Unable to finish output pixel at %d %d\n", x, y); continue; } if (DtexSetPixel(outImage, x, y, mergedPixel) != DTEX_NOERR) { fprintf(stderr, "Unable to set output pixel at %d %d\n", x, y); continue; } nextpixel: } } } DtexDestroyPixel(inPixel); DtexDestroyPixel(outPixel); DtexDestroyPixel(mergedPixel); free(inData); for (i = 0; i < nInFiles; ++i) { if (DtexClose(inFiles[i]) != DTEX_NOERR) { fprintf(stderr, "Unable to close input file %s\n", inname); } } free(inImages); free(inFiles); if (DtexClose(outFile) != DTEX_NOERR) { fprintf(stderr, "Unable to close output file %s\n", outname); } DtexDestroyCache(inCache); DtexDestroyCache(outCache); return 0; }
Pixar Animation Studios
|