PhotoRealistic RenderMan
Display Driver Implementation Guide

Pixar
August, 1998

1. Introduction

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.

2. Theory of Operation

Display drivers are written as shared objects with 4 required functions:

DspyImageOpen
initialize the display driver, allocate necessary space, check image size, and specify the format in which incoming data will arrive.
DspyImageQuery
query the display driver for image size (if not specified in the open call) and aspect ratio.
DspyImageData
send data to the display driver.
DspyImageClose
let the display driver free resources.
And, optionally:
DspyImageDelayClose
let the display driver handle user interaction or anything else that might delay the shutdown of the display driver.

Data 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:

PkDspyFloat32
PkDspyUnsigned32
PkDspySigned32
PkDspyUnsigned16
PkDspySigned16
PkDspyUnsigned8
PkDspySigned8

A few more types are defined for optional parameters:

PkDspyString
PkDspyMatrix

If byte orders aren't specified, the machine default is assumed. Byte order can be specified by "or"ing in one of two values:

PkDspyByteOrderHiLo
big endian, most significant bytes come first
PkDspyByteOrderLoHi
little endian, least significant bytes come first
PkDspyByteOrderNative
just an alias for whichever is the native order on this architecture.

All functions return one of the following enumerated error codes:

PkDspyErrorNone
successful completion
PkDspyErrorNoMemory
unable to allocate memory
PkDspyErrorUnsupported
unsupported operation requested
PkDspyErrorBadParams
bad parameters
PkDspyErrorNoResource
no resource available, file not found, etc.
PkDspyErrorUndefined
no other error messages appropriate

3. Display Driver Implementation

3.1. DspyImageOpen

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

PkDspyFlagsWantsScanlineOrder
Specifies that that data be sent only in scanline order. If this flag is not set, then calls to DspyImageData 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.
PkDspyFlagsWantsEmptyBuckets
If PkDspyFlagsWantsScanlineOrder 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.
PkDspyFlagsWantsNullEmptyBuckets
Much like PkDspyFlagsWantsEmptyBuckets, 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;
}

3.2. DspyImageQuery

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

3.3. DspyImageClose

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

3.4. DspyImageData

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

3.5. DspyImageDelayClose

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:

  1. The renderer uses stdin and stdout to communicate with the separate process. Any printing you've left in for debugging purposes may confuse the communication between the renderer and the display driver manager.
  2. If you're debugging a display driver it might be handy to keep the functionality you'd normally put in DspyImageDelayClose in DspyImageClose so that output goes where you expect it to.

4. Display Driver Installation

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:

  1. The rendermn.ini file is checked for a /displaytype/type entry; if it's found then type is replaced with the associated name.
  2. The type is then checked against the /display/dso/type entry; the associated name is the name of the shared object driver, with path information.
  3. If the /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.
  4. If it's not found, then it's searched for in ./.

Common usage is to put the driver into ${RMANTREE}/etc/ named d_type.so.

5. Helper functions

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.

5.1 DspyFindStringInParamList

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)
{
   ...

5.2 DspyMemReverseCopy

void
DspyMemReverseCopy(unsigned char *target, const unsigned char *source, int len);

Fairly self explanatory. Reverse len bytes from source to target.

5.3 DspyFindMatrixInParamList

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

5.4 DspyFindFloatInParamList

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

5.5 DspyFindFloatsInParamList

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.

5.6 DspyFindIntInParamList

PtDspyError
DspyFindIntInParamList(const char *string,
	int *result,
	int paramCount,
	const UserParameter *parameters)

Get an integer value from the parameter list. Used similarly to DspyFindFloatInParamList().

5.7 DspyFindIntsInParamList

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().

5.8 DspyReorderFormatting

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.

6. Common Parameters

Obviously users can provide any set of parameters they want. The renderer will also provide a standard set of parameters:

NameTypeDescription
NPmatrixworld to NDC space
Nlmatrixworld to camera space
nearfloatnear clipping plane (useful for scaled Z buffer formats)
farfloatfar clipping plane
originint[2]with crop windows provides the origin of this image within the larger image, in pixels.
OriginalSizeint[2]with crop windows provides the size of the larger image into which this image fits.
PixelAspectRatiofloatpixel aspect ratio
Softwarestringrenderer version
HostComputerstringhost name

RenderMan Artist Tools | RenderMan Toolkit
Looks | Textures
Pixar Home Page

Copyright © 1998 Pixar. All rights reserved. RenderMan® is a registered trademark of Pixar.
Pixar Animation Studios, 1001 West Cutting Blvd., Richmond, CA 94804
510-236-4000 (voice) 510-236-0388 (fax)