Pixar
August, 1998
The PhotoRealistic RenderMan rendering system produces raster graphics images from 3D shape and shading models. The images are output to image files or to hardware display devices (frame buffers) via the PhotoRealistic RenderMan display service. The service may be provided in a variety of ways and may produce output in a variety of forms. The display service may be customized by installing display drivers for specific frame buffer and image file types.
Display drivers are written as shared objects with 4 required functions:
DspyImageOpenDspyImageQueryDspyImageDataDspyImageCloseDspyImageDelayCloseData to the display drivers will be provided in a variety of formats and byte orders. The display driver can then choose which formats and byte orders it would prefer that data in. Constants for those formats are defined in ndspy.h. The names should be self explanatory:
PkDspyFloat32PkDspyUnsigned32PkDspySigned32PkDspyUnsigned16PkDspySigned16PkDspyUnsigned8PkDspySigned8A few more types are defined for optional parameters:
PkDspyStringPkDspyMatrixIf byte orders aren't specified, the machine default is assumed. Byte order can be specified by "or"ing in one of two values:
PkDspyByteOrderHiLoPkDspyByteOrderLoHiPkDspyByteOrderNativeAll functions return one of the following enumerated error codes:
PkDspyErrorNonePkDspyErrorNoMemoryPkDspyErrorUnsupportedPkDspyErrorBadParamsPkDspyErrorNoResourcePkDspyErrorUndefined
PtDspyError DspyImageOpen(PtDspyImageHandle * image,
const char *drivername,
const char *filename,
int width,
int height,
int paramCount,
const UserParameter *parameters,
int formatCount,
PtDspyDevFormat *format,
PtFlagStuff *flagstuff);
This opens an image called filename and puts a
handle to any local data into *image. If
width or height is 0, it chooses an
appropriate default value for them.
drivername is the name before the
/display/dso or /display/dsomapping
translation. This allows a single driver to change its behavior based
on the name used in the RIB file.
Image types which can include various other tag information might
investigate the parameters, which are passed through from
the RIB Display line. A list of the commonly
interpreted ones appears later in this document. Two important points:
The data pointed to by both the parameters structures and
the format structures is valid only for the duration of
this function call, copy any information that you plan to reuse to
your own structures; and the values pointed to by the
UserParameter structure are not necessarily aligned,
which is why all of the helper functions copy this information to your
own variables rather than trying to use them in place.
formatCount and format describe the data as it
will be sent to the driver. The driver should reorder the
format array into the order it expects to deal with data (it
is important that the format.name fields point to the
original strings!), and set format[x].type to types and byte
orders that it can deal with.
Finally, flagstuff allows the driver to request that
data be sent in different ways. Three flags can be "or"ed into
flagstuff->flags
PkDspyFlagsWantsScanlineOrderDspyImageData may
provide data for random portions of the image.
While this is fine for image buffers and other drivers which
already have data for the entire image allocated, it makes it
difficult to build drivers for formats like TIFF, which want
the data a scan line at a time. Setting this bit ensures that all
data will arrive in scan line order. Note that this does not mean
that your DspyImageData function will get a full
scanline at a time, it will get portions of scanlines. If you have
to process full scanlines (for channel separated RLE, for instance)
you'll need to wait for the complete scanline to arrive.PkDspyFlagsWantsEmptyBucketsPkDspyFlagsWantsScanlineOrder isn't set, then not
all of the image will necessarily be filled in. Areas of the image
in which there is no geometry may be left unfilled. Setting this
flag makes the renderer send the highest possible value for the
data type in any channel labeled "z", 0 in all other channels,
for those regions which otherwise wouldn't get data.PkDspyFlagsWantsNullEmptyBucketsPkDspyFlagsWantsEmptyBuckets, but this flag
calls DspyImageData with NULL in the
data argument for those regions.If you don't use PkDspyFlagsWantsScanLineOrder the
renderer won't call DspyImageData for regions of the
image where it doesn't draw anything. Don't assume that the entire
image (or even any of the image, if none of the geometry is visible)
will be filled in.
This example will implement a trivial image format. It makes no allowances for differences between platforms. The format is just an "int" which is the number of channels being sent to this image, that many strings, which name them all, and that many floating point values per pixel for the rest of the image.
#include <ndspy.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
typedef struct
{
FILE *file;
int channels;
int width, height;
} *MyImageType;
PtDspyError
DspyImageOpen(PtDspyImageHandle *pvImage,
const char *drivername,
const char *filename,
int width,
int height,
int paramCount,
const UserParameter *parameters,
int formatCount,
PtDspyDevFormat *format,
PtFlagStuff *flagstuff)
{
PtDspyError ret;
MyImageType image;
/* We want to receive the pixels one after the other */
flagstuff->flags |= PkDspyFlagsWantsScanLineOrder;
/* Do stupidity checking */
if (0 == width) width = 640;
if (0 == height) height = 480;
image = NULL;
ret = PkDspyErrorNone;
image = malloc(sizeof(*image));
if (NULL != image)
{
int i;
image->channels = formatCount;
image->width = width;
image->height = height;
image->file = fopen(filename, "wb");
if (image->file)
{
fwrite(&formatCount, sizeof(formatCount), 1, image->file);
for (i = 0; i < formatCount; i++)
{
format[i].type = PkDspyFloat32;
fwrite(format[i].name, strlen(format[i].name) + 1, 1, image->file);
}
}
else
{
free(image);
image = NULL;
ret = PkDspyErrorNoResource;
}
}
else
ret = PkDspyErrorNoMemory;
*pvImage = image;
return ret;
}
PtDspyError DspyImageQuery(PtDspyImageHandle pvImage,
PtDspyQueryType type,
size_t size,
void *p);
This can pretty much be copied from boilerplate. There are two query types implemented so far. The first asks for the size of the image, or a default size of no image is supplied (pvImage may be NULL!). The second asks whether the driver in question actually overwrites the file name given, and is used to keep the user from accidentally overwriting an image.
PkSizeQuery will be called with an image handle when
the DspyImageOpen was called with a 0 width and
height. It will also be called without an image handle for applications
which want to query for a fixed image size (as might happen with a
framebuffer).
The PkOverwriteQuery response structure,
PtDspyOverwiteInfo, has two fields. The
.overwrite field tries to keep the user from doing damage
when using tools like sho for image conversion, or when
writing to the "framebuffer" display device which may be mapped to
something destructive. The .interactive field isn't yet
used in any tools, the hope was that it would make batch tools a
little bit smarter.
PtDspyError
DspyImageQuery(PtDspyImageHandle pvImage,
PtDspyQueryType querytype,
int datalen,
void *data)
{
PtDspyError ret;
MyImageType image = (MyImageType )pvImage;
ret = PkDspyErrorNone;
if (datalen > 0 && NULL != data)
{
switch (querytype)
{
case PkOverwriteQuery:
{
PtDspyOverwriteInfo overwriteInfo;
if (datalen > sizeof(overwriteInfo))
datalen = sizeof(overwriteInfo);
overwriteInfo.overwrite = 1;
overwriteInfo.interactive = 0;
memcpy(data, &overwriteInfo, datalen);
break;
}
case PkSizeQuery :
{
PtDspySizeInfo sizeInfo;
if (datalen > sizeof(sizeInfo))
datalen = sizeof(sizeInfo);
if (image)
{
if (0 == image->width ||
0 == image->height)
{
image->width = 640;
image->height = 480;
}
sizeInfo.width = image->width;
sizeInfo.height = image->height;
sizeInfo.aspectRatio = 1.0f;
}
else
{
sizeInfo.width = 640;
sizeInfo.height = 480;
sizeInfo.aspectRatio = 1.0f;
}
memcpy(data, &sizeInfo, datalen);
break;
}
default :
ret = PkDspyErrorUnsupported;
break;
}
}
else
{
ret = PkDspyErrorBadParams;
}
return ret;
}
PtDspyError DspyImageClose(PtDspyImageHandle);
Simply free any resources associated with the image handle.
PtDspyError DspyImageClose(PtDspyImageHandle pvImage)
{
PtDspyError ret;
MyImageType image = (MyImageType )pvImage;
fclose(image->file);
free(image);
ret = PkDspyErrorNone;
}
PtDspyError DspyImageData(PtDspyImageHandle image,
int xmin,
int xmax_plusone,
int ymin,
int ymax_plusone,
int entrysize,
const unsigned char *data);
Write data for the image area described. entrysize is the
skip in bytes to the next pixel in data. Unlike the old dspy system,
xmax_plusone and ymax_plusone are 1 more than
the last pixel to be written, that is that the width of the region to be
written is xmax_plusone-xmin and not
xmax-xmin+1.
PtDspyError DspyImageData(PtDspyImageHandle pvImage,
int xmin,
int xmax_plusone,
int ymin,
int ymax_plusone,
int entrysize,
const unsigned char *data)
{
PtDspyError ret;
MyImageType image = (MyImageType )pvImage;
int oldx;
oldx = xmin;
for (;ymin < ymax_plusone; ymin++)
{
for (xmin = oldx; xmin < xmax_plusone; xmin++)
{
fwrite(data, sizeof(float), image->channels, image->file);
data += entrysize;
}
}
}
PtDspyError DspyImageDelayClose(PtDspyImageHandle);
There are situations where a display manager might want to keep running after the renderer has finished sending data. For instance, a driver which displays images in a window may want to keep that window available for user manipulation. This function is the perfect place to put a display loop; simply don't return until the window is closed.
If this function exists in the display driver, then the display driver will run in a separate process from the main renderer and the renderer itself will exit as soon as the image is complete. This is important for two reasons:
DspyImageDelayClose
in DspyImageClose so that output goes where you expect it
to.For the renderer to use the display driver, it has to be a loadable shared object, and the renderer has to be able to find it. Compiling a shared object on a typical system looks like:
cc -elf \
-shared \
-no_unresolved \
-rdata_shared \
-delay_load \
d_myformat.c -o d_myformat.so
In order to use the shared object, the renderer must be able to find it. When you pass a format name in via the type (second) parameter to RiDisplay:
/displaytype/type entry; if it's found then type
is replaced with the associated name./display/dso/type entry; the associated name is the
name of the shared object driver, with path information./display/dso/type isn't found,
/display/dsomapping is used as an sprintf
format string to turn the driver name into a file name, then
the filename is searched for in /display/standarddsopath
and /display/dsopath../.Common usage is to put the driver into ${RMANTREE}/etc/ named
d_type.so.
There are a set of basic functions that nearly every image
implementation needs to do. Many of these have to do with accessing
parameters, which are either predefined or passed
through from the RIB Display line. These have been
encapsulated in dspyhlpr.c.
PtDspyError DspyFindStringInParamList(const char *string, char **result, int paramCount, const UserParameter *parameters)If
string is found in parameters, put
a pointer to it in *result and return
PkDspyErrorNone, else return
PkDspyErrorNoResource.
char *result = NULL;
DspyFindStringInParamList("secondchoice", &result, paramCount, parameters);
DspyFindStringInParamList("firstchoice", &result, paramCount, parameters);
if (result)
{
...
void DspyMemReverseCopy(unsigned char *target, const unsigned char *source, int len);
Fairly self explanatory. Reverse len bytes from
source to target.
PtDspyError DspyFindMatrixInParamList(const char *string, float *result, int paramCount, UserParameter *parameters)
Example:
float matrix[16];
if (DspyFindMatrixInParamList("NP", matrix, paramCount, parameters))
{
/* Didn't find it, use the identity */
matrix[0 ] = 1.0;
matrix[1 ] = 0.0;
matrix[2 ] = 0.0;
matrix[3 ] = 0.0;
matrix[4 ] = 0.0;
matrix[5 ] = 1.0;
matrix[6 ] = 0.0;
matrix[7 ] = 0.0;
matrix[8 ] = 0.0;
matrix[9 ] = 0.0;
matrix[10] = 1.0;
matrix[11] = 0.0;
matrix[12] = 0.0;
matrix[13] = 0.0;
matrix[14] = 0.0;
matrix[15] = 1.0;
}
PtDspyError DspyFindFloatInParamList(const char *string, float *result, int paramCount, const UserParameter *parameters)
Get a floating point value from the parameter list. Here's an example of getting the near clipping plane from the parameter list:
float nearClip;
DspyFindFloatInParamList("near", &nearClip, paramCount, parameters);
PtDspyError
DspyFindFloatsInParamList(const char *string,
int *resultCount,
float *result,
int paramCount,
const UserParameter *parameters)
Get a floating point array from the parameter list. Similar to
DspyFindFloatInParamList except that
*resultCount is the maximum number of floats available
at result, and is set to the number actually copied
into result.
PtDspyError DspyFindIntInParamList(const char *string, int *result, int paramCount, const UserParameter *parameters)
Get an integer value from the parameter list. Used similarly
to DspyFindFloatInParamList().
PtDspyError
DspyFindIntsInParamList(const char *string,
int *resultCount,
int *result,
int paramCount,
const UserParameter *parameters)
Get an integer value from the parameter list. Used similarly
to DspyFindFloatsInParamList().
PtDspyError DspyReorderFormatting(int formatCount, PtDspyDevFormat *format, int outFormatCount, const PtDspyDevFormat *outFormat)
Attempt to reorder format to look like
outFormat. We can't save users from themselves, but
if we expect to get "abgr", we can use this to try to order the
incoming parameters to look more like "abgr" than "rgba" or whatever
other random format our caller may request.
Obviously users can provide any set of parameters they want. The renderer will also provide a standard set of parameters:
| Name | Type | Description |
|---|---|---|
| NP | matrix | world to NDC space |
| Nl | matrix | world to camera space |
| near | float | near clipping plane (useful for scaled Z buffer formats) |
| far | float | far clipping plane |
| origin | int[2] | with crop windows provides the origin of this image within the larger image, in pixels. |
| OriginalSize | int[2] | with crop windows provides the size of the larger image into which this image fits. |
| PixelAspectRatio | float | pixel aspect ratio |
| Software | string | renderer version |
| HostComputer | string | host name |