Deep テクスチャ API
Deep テクスチャ API
August, 2003
Important
* The Deep テクスチャ API has been deprecated in favor of RixDeeptexture.
Introduction
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.
Error Codes
Unless otherwise specified, an int return value from a library return is an error code. These error codes are:
- DTEX_TILECOMPRESSION: Error during tile compression
- DTEX_UNFINISHED: Illegal operation on pixel with unresolved compression state
- DTEX_RANGE: Out of range parameter
- DTEX_NOMEM: Ran out of memory
- DTEX_EMPTY: Illegal operation on an empty structure
- DTEX_NOIMAGE: The specified image was not found
- DTEX_UNSUPPORTED: The specified operation is not supported
- DTEX_BADFILE: File was corrupt
- DTEX_NOFILE: File was not found or was invalid
- DTEX_ERR: Miscellaneous error occurred
- DTEX_NOERR: No error
Cache Management
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);
These routines create, destroy, and synchronize tile caches. accessor specifies a set of I/O functions to use upon faults. If accessor is NULL, standard system calls are used.
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.
File Management
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);
These routines open, create, close, and synchronize deep texture files.
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.
Image Management
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);
Pixel Management
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 DtexMergePixel(DtexPixel *dest, DtexPixel *src);
int DtexMergePixelEx(DtexPixel *dest, DtexPixel *src, int rgbChannel, int alphaChannel);
Like DtexMergePixel, this merges data from src into dest. In addition, it can correctly handle embedding geometry in volumes and overlapping volumes. These volume segments are flagged with a negative alpha and extend to the next sample in the pixel. There must be at least one non-volume sample marking the end of chain (which may be completely transparent).
This assumes a four channel RGBA pixel function when splitting and merging volume regions. The rgbChannel must be the index of the first color channel, with the assumption that all three color channels are contiguous. The alphaChannel must be the index of the alpha channel. Any additional channels will be copied unchanged from the most recent volume sample.
int DtexFinishPixel(DtexPixel *dest);
int DtexAppendPixel(DtexPixel *pix, float z, int n, float *data, float error);
Adds a data point to the end of the specified pixel. n must specify how many floats are stored in data, and must match the number of channels in the pixel.
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:
- The first call to DtexAppendPixel always adds the pixel to the depth function. Call this point A.
- The next call to DtexAppendPixel results in a candidate pixel, which is not immediately added. Call this point B.
- The next call to DtexAppendPixel with a point C checks whether the previous candidate pixel B can be discarded based on whether the target window drawn from A to B intersects C. The target window itself can be imagined by considering B_hi and B_lo, where B_hi is some distance directly above B and B_low is directly below B. The tangent window is the area between A to B_hi and A to B_lo, and the distance between B_hi and B_lo is directly related to the error parameter. If the new pixel (C) is inside the target window then the candidate (B) is discarded. Otherwise, the candidate (B) is added to the list of pixels.
- The new point becomes the new candidate pixel.
int DtexEvalPixel(DtexPixel *pix, float z, int n, float *data);
int DtexCompositePixel(DtexPixel *pix, int rgbChannel, int alphaChannel, float *data);
Composite a four channel RGBA pixel function and store the result in data. rgbChannel must be the index of the first color channel, with the assumption that all three color channels are contiguous. alphaChannel must be the index of the alpha channel. data must be a buffer of at least four floats.
If the pixel is empty, DTEX_EMPTY is returned and data is filled with zeros. Otherwise, data[0], data[1], and data[2] contain the composited color values and data[3] contains the composited alpha value, and DTEX_NOERR is returned.
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). フィルタリング 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:
- Call DtexCreateDeepFilter() to create a filter structure.
- For each input function, we have to compute the following:
- Its filter weight.
- How many deltas are needed to represent it.
- Put this information in two n-sized arrays.
- Pass this information to DtexGetDeepFilterData(). This sets up the function based on your information and returns a delta buffer.
- Run through your input functions again, in the same order, and put the delta data into the delta buffer. You must put exactly the right number of deltas to match the number of deltas you specified earlier.
- Call DtexComputeDeepPointData() to get the filtered result.
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);
Executes the given filter to produce point data. pointData must contain enough space to hold "totalNumDeltas" deltas as specified in DtexGetDeepFilterData.
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);
ユーティリティ Routines
void DtexQueryMemory(long *current,long *peak);
Example
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 %s.\n", outname); 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: continue; } } } 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; }