Display Driver Plugins

Display Driver Plugins

Custom Display Drivers

Display Driver Overview

In addition to the display drivers supplied with PRMan, users can extend the system by writing their own custom drivers. This allows for new image or geometry types or framebuffers to be directly supported for rendering.

Display drivers are written as dynamic shared objects (DSOs), which are also known as dynamic link libraries. (You should consult the documentation for your compiler on how to compile and link these objects.) You may compile your display DSOs either with a 'C' or 'C++' compiler, but you must use 'C' linkage (i.e. if you use C++, you need to use extern "C" to guarantee C-style linkage). A header file has been provided, ndspy.h, which contains function prototype declarations that should be used when writing display drivers, as these prototypes should guarantee that the correct linking and exporting of symbols occurs across various platforms.

  • Note

    RenderMan Pro Server for OSX display driver DSOs do not support Objective C (ObjC).

See the RenderMan SDK documentation for details on compiling and linking these plug-ins.

Display driver DSOs must export and implement at least four required functions:

DspyImageOpen
Initializes 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.

Optionally, they may also implement:

DspyImageDelayClose
Let the display driver handle user interaction or anything else that might delay the shutdown of the display driver.
DspyImageDeepData
Send variable sized information to the display driver (to support geometry drivers).
DspyImageActiveRegion
Tell the display driver that only a portion of the image is going to be sent (to support interactive crop windows during re-rendering).

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 and PkDspySigned8.

These 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:</p>

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

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 that 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 parameters 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.
  • 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. Display drivers 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.

Geometry drivers (those that have responded to the PkPointCloudQuery) will be supplied information about the format of the extended data they will receive in DspyImageDeepData. The format.name field will contain data types and names formatted with the syntax:

(uniform|varying) float|point|vector|normal|color|matrix name.XXX

The trailing .XXX information exists to ensure a unique channel * identifier, and can be ignored.

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. If any of the dspy drivers in use set this flag, then all dspy drivers will receive data in scan line order.

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" that 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 <string.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 = (MyImageType) 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;
}
DspyImageQuery
PtDspyError DspyImageQuery(PtDspyImageHandle pvImage,
       PtDspyQueryType type,
       size_t size,
       void *p);

DspyImageQuery is used to communicate state information about the features of the display driver and the image the display driver can produce. Each query is made by calling DspyImageQuery with a query-type and corresponding query-structure passed as p. DspyImageQuery fills values in the query-structure and returns a value indicating the sucess or failure of the query.

The following query-types may be passed to DspyImageQuery:

PkSizeQuery

Asks for the size of the image, or a default size of no image is supplied (pvImage may be NULL!). It 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).

PkOverwriteQuery

Asks whether the driver in question actually overwrites the file name given, and is used to keep the user from accidentally overwriting an image. The 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.

PkRedrawQuery

Asks whether the driver can accept multiple calls to DspyImageData for the same image region and redraw the display when new updates are recieved. A driver that supports this behavior must set the .redraw field of the PtDspyRedrawInfo to 1. This query is mainly used for determining whether the display driver is an interactive framebuffer.

PkSupportsCheckpointing

Asks whether the display driver supports image checkpointing. Image checkpointing allows the render to skip a portion of a render if the "-recover 1" option is passed to render. The PkSupportsCheckpointing is first issued by the render to all display drivers that are used in the render. If all respond positively to the PkSupportsCheckpointing query, the display drivers are initialized with DspyImageOpen, and the PkRenderingStartQuery is issued to determine where restart the render. PkSupportsCheckpointing is issued with a NULL pvImage. Any data that needs to be communicated to the display driver before the DspyImageOpen method needs to be initialized upon receiving PkSupportsCheckpointing. If the display driver supports image checkpointing, DspyImageOpen needs to determine where the render should start.

PkPointCloudQuery

Asks whether the display driver supports geometry (can be used by the bake3d() shadeop). A display driver that sets the supportsPointsClouds field of the PtDspyPointCloudQuery to 1 indicates that it has implemented the DspyImageDeepData routine and is able to handle a collection of point data. The driver will be supplied information about the format of this point data in the DspyImageOpen routine.

PkGridQuery

Asks whether the display driver supports geometry with topological information (can be used by the bake3d() shadeop). A driver that supports this query must also support the PkPointCloudQuery and set the supportsPointClouds field therein to 1. A display driver that sets the supportsGrids field of the PtDspyGridQuery to 1 indicates that it has implemented the DspyImageDeepData routine and is able to handle a collection of point data along with topological information. The driver will be supplied information about the format of this point data in the DspyImageOpen routine.

PkMultiResolutionQuery

Asks whether the display driver supports multi-resolution images. A driver that supports this query must set the supportsMultiResolution field therein to 1. A multi-resolution display driver will receive multiple DspyImageOpen calls, one for each level of detail. The first is the highest resolution. Subsequent calls will have ":lod:#" appended to the image name, where # is an integer indicating the level of detail. 1 is the first decimation, 2 the second, and so on.

PkMemoryUsageQuery

Asks the display driver for its current memory usage. This query is made with a valid pvImage, indicating the target of the query. The response structure, PtMemoryUsageQuery, has two fields. The .currentkB field should be set to the amount of memory the driver currently has allocated for the given pvImage, in kilobytes (1024 bytes). The .peakkB field should be set to the peak amount of memory the driver has allocated during execution for the given pvImage. The results are aggregated across displays and presented as "Display driver memory" in the "Memory usage summary" section of the statistics.

PkElapsedTimeQuery

Asks the display driver for the elapsed time processing an image. This query is made with a valid pvImage, indicating the target of the query. The response structure, PtElapsedTimeQuery has a single field, .seconds. This field should be set to the elapsed time, in seconds, that the driver has spend processing the given pvImage.The results are aggregated across displays and presented as "Display driver time" in the "Time summary" section of the statistics.

PkRenderingStartQuery

Asks where the display driver can recover to. After issuing a PkSupportsCheckpointing query, the render knows if it is possible to recover. The render needs to know how far along in the image the display driver can recover. The PtDspyRenderingStartQuery return structure indicates, in image space, where the display driver contains its last piece of valid image data. The render will compile all of the rendering start locations from all the display drivers to determine where to start rendering. PkRenderingStartQuery is called with a valid pvImage so any information that needs to be passed to the DspyImageData needs to be initialized here. If the display driver supports image checkpointing, the display driver must ignore all data passed to DspyImageData that is less than the value returned in the PtDspyRenderingStartQuery structure.
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;
          }
          case PkRenderingStartQuery :
          {
              PtDspyRenderingStartQuery startLocation;

              if (datalen > sizeof(startLocation))
                  datalen = sizeof(startLocation);

              if (image) {
                  /*
                   * initialize values in startLocation
                   */
                  memcpy(data, &startLocation, datalen);
              } else {
                  ret = PkDspyErrorUndefined;
              }
              break;
          }

          default :
              ret = PkDspyErrorUnsupported;
              break;
      }
    }
    else
    {
      ret = PkDspyErrorBadParams;
    }
    return ret;
}

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

PtDspyError DspyImageClose(PtDspyImageHandle pvImage)
{
   PtDspyError ret;
   MyImageType image = (MyImageType )pvImage;

   fclose(image->file);
   free(image);
   ret = PkDspyErrorNone;
   return ret;
}
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, 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;
      }
   }
}
DspyImageActiveRegion
PtDspyError DspyImageActiveRegion(PtDspyImageHandle image,
       int xmin,
       int xmax_plusone,
       int ymin,
       int ymax_plusone);

During display of a multi-resolution image the display driver is responsible for scaling the low resolution images before they are displayed in a framebuffer. During re-rendering, the user may specify a crop window other than the one used to originally define the image; we call this an active region. Only buckets within the active region will be sent to the display driver. Active regions are specified in units of pixels in the highest-resolution image; therefore, the boundary of the region may cross through an up-scaled pixel of a lower resolution image. To ensure display of the multi-resolution does not affect pixels outside of the active region the display driver must clip the up-scaled images. DspyImageActiveRegion will be called every time a new region is defined, which may happen multiple times during a re-rendering session. Like DspyImageData, xmax_plusone and ymax_plusone are 1 more than the last pixel to be written, and the width of the region to be written is xmax_plusone-xmin.

DspyImageDeepData
PtDspyError DspyImageDeepData(PtDspyImageHandle image,
       int xmin, int xmax,     /* npoints, 0 */
       int ymin, int ymax,     /* ignored (0) */
       char *data,             /* filename and point data */
       int totalsize,  /* total size of all data (in bytes) */
       int *pixeloffsets,      /* (ignored) */
       int *pixelsizes)        /* (ignored) */

This entry point is used by geometry drivers (those useable by bake3d()). A geometry driver will receive a list of points with associated data. The points are homogeneous, ie. each point * has the same amount of data. If the driver indicates support for the PkPointCloudQuery only, each point in the input (*data) consists of a position (3 floats), a normal (3 floats), a radius (1 float), and shader specified data (any number of floats), with the last being described in the DspyImageOpen routine. An example of such usage is:

typedef struct {
    FILE *file;
    int nvars;
    char **varnames;
    int *varsizes;
} *PtDspyImage;

/*ARGSUSED*/
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 = PkDspyErrorNone;
    PtDspyImage image;
    int i, v;
    int words;
    char mode[80], type[80], name[256];
    image = (PtDspyImage) calloc(1, sizeof(*image));
    if (!image) {
      ret = PkDspyErrorNoMemory;
      goto error;
    }
    image->nvars = formatCount;
    image->varsizes = (int *) malloc(formatCount * sizeof(int));
    image->varnames = (char**) calloc(formatCount, sizeof(char*));

    if (!image->varsizes || !image->varnames) {
      ret = PkDspyErrorNoMemory;
      goto error;
    }
    /*
     * We require data in this machine's native byte order, so modify
     * to inform the renderer of this fact.
     */
    for (i = 0; i < formatCount; i++) {
      format[i].type = (format[i].type & ~PkDspyMaskOrder) |
          PkDspyByteOrderNative;
    }
    /* Look at filename, make sure it's not null. If it is, override */
    if (filename == NULL || *filename == '\0') {
      filename = "ri.ptc"; /* default must be display specific */
    }
    image->file = fopen(filename, "w+b");
    if (!image->file) {
      ret = PkDspyErrorNoResource;
      goto error;
    }
    /*
     * Extract data types and names from the format information.  This
     * will come in on the format[v].name parameter with the syntax:
     *
     * (uniform|varying) float|point|vector|normal|color|matrix name.XXX
     *
     * The trailing .XXX information exists to ensure a unique channel
     * identifier, and can be ignored.
     */
    for (v = 0; v < formatCount; v++) {
      /* Skip file name */
      char* typename = strchr(format[v].name, '|');
      if (!typename) {
          ret = PkDspyErrorUndefined;
          goto error;
      }
      typename++;
      words = sscanf(typename, "%s %s %s", mode, type, name);
      if (words == 2)
          words = sscanf(typename, "%s %s", type, name);
      for (i = 0; i < strlen(name); i++) /* find first '.' in name */
          if (name[i] == '.') break;
      image->varnames[v] = (char*) malloc(strlen(name)+1);
      if (!image->varnames[v]) {
          goto error;
      }
      if (strlen(type) == 0) {
          DspyError(drivername, "Bad type declaration - missing DisplayChannel?\n");
          ret = PkDspyErrorBadParams;
          goto error;
      }
      /* Deduce data size */
      if (strcmp(type, "float") == 0) {
          image->varsizes[v] = 1;
      } else if (strcmp(type, "point") == 0 ||
                 strcmp(type, "vector") == 0 ||
                 strcmp(type, "normal") == 0 ||
                 strcmp(type, "color") == 0) {
          image->varsizes[v] = 3;
      } else if (strcmp(type, "matrix") == 0) {
          image->varsizes[v] = 16;
      } else {
          printf("error: unknown variable type '%s' in pointcloud display driver\n",
                 type);
          /*
           * Let's just hope that a varsize of 1 will work, better
           * than leaving this uninitialized.
           */
          image->varsizes[v] = 1;
      }
      strncpy(image->varnames[v], name, i); /* clip off ".x" */
      image->varnames[v][i] = '\0';
    }
    *pvImage = image;
    return ret;
    error:
    if (image) {
      if (image->file) fclose(image->file);
      if (image->varsizes) free(image->varsizes);
      if (image->varnames) {
          for (i = 0; i < image->nvars; ++i) {
              if (image->varnames[i]) {
                  free(image->varnames[i]);
              }
          }
          free(image->varnames);
      }
      free(image);
    }
    *pvImage = NULL;
    return ret;
}

PtDspyError
DspyImageDeepData(PtDspyImageHandle pvImage,
                int xmin, int xmax,   /* npoints, 0 */
                int ymin, int ymax,   /* ignored (0) */
                char *data,           /* filename and point data */
                int totalsize,        /* total size of all data (in bytes) */
                int *pixeloffsets,    /* (ignored) */
                int *pixelsizes)      /* (ignored) */
{
    PtDspyImage image = (PtDspyImage )pvImage;
    float *fdata = (float *)data;
    float *ptdata;
    int npoints = xmax - xmin + 1;
    int i, j, k;
    int fsize = totalsize / (npoints * sizeof(float));
    fprintf(image->file, "Number of points: %d\n", npoints);
    /*
     * Pry point positions, normals, radii and data out of the
     * "unorganized" data list.
     */
    for (i = 0; i < npoints; i++) {
      ptdata = fdata;
      /* Position */
      fprintf(image->file, "point: %f %f %f\n", ptdata[0], ptdata[1], ptdata[2]);
      ptdata += 3;
      /* Normal */
      fprintf(image->file, "normal: %f %f %f\n", ptdata[0], ptdata[1], ptdata[2]);
      ptdata += 3;
      /* Radius */
      fprintf(image->file, "radius: %f\n", ptdata[0]);
      ptdata++;
      /* Rest of the data */
      for (j = 0; j < image->nvars; ++j) {
          fprintf(image->file, "%s: ", image->varnames[j]);
          for (k = 0; k < image->varsizes[j]; ++k) {
              if (k == image->varsizes[j] - 1) {
                  fprintf(image->file, "%f", *ptdata++);
              } else {
                  fprintf(image->file, "%f ", *ptdata++);
              }
          }
          fprintf(image->file, "\n");
      }
      fdata += fsize;
    }
    fprintf(image->file, "\n");
    return PkDspyErrorNone;
}

If the driver indicates support for the PkGridQuery as well, the data passed to the driver has the following format:

1 float - style: enum PtDspyGridStyle
                     {PkDspyGridConnected, PkDspyGridLines, PkDspyGridPoints}
1 float - npoints: number of vertices
1 float - npolys: number of polys
1 float - fsize: size of per-vertex data
npoints * fsize floats - per vertex data
    // this is the data as what we would have gotten in the
    // case where only PkPointCloudQuery was supported.
npolys floats: per poly vertex count
sum(per poly vertex count) floats - per poly vertex indices

In cases where the style is Lines or Points, the last two fields are not sent and npolys == 0. Otherwise, those fields contain topological information in the same style as style of RiPointsPolygons (i.e. polygon vertex counts followed by polygon vertex indices). Example usage (from the RIB driver) is:

/*ARGSUSED*/
PtDspyError
DspyImageDeepData(PtDspyImageHandle pvImage,
                int xmin, int xmax,   /* npoints, 0 */
                int ymin, int ymax,   /* ignored (0) */
                char *data,           /* filename and point data */
                int totalsize,        /* total size of all data (in bytes) */
                int *pixeloffsets,    /* (ignored) */
                int *pixelsizes)      /* (ignored) */
{
    PtDspyImage image = (PtDspyImage )pvImage;
    // Save renderer context
    RtContextHandle renderContext = RiGetContext();
    // Switch to RIB context
    RiContext(image->context);
    RiAttributeBegin();
    float *fdata = (float *)data;
    int style         = (int) *fdata++;
    int npoints = (int) *fdata++;
    int npolys        = (int) *fdata++;
    int fsize         = (int) *fdata++;
    fsize /= sizeof(float);
    int i, j, k, offset;
    // We'll need a buffer to store data to present to the RI interface
    // in uninterleaved form
    RtToken *tokens = (RtToken*) malloc((4 + image->nvars) * sizeof(RtToken));
    RtPointer *values = (RtPointer*) malloc((4 + image->nvars) * sizeof(RtPointer));
    RtFloat *ridata = (RtFloat*) malloc(fsize * npoints * sizeof(RtFloat));
    RtFloat *ridataptr = ridata;
    if (!tokens || !values || !ridata) {
      RiContext(renderContext);
      return PkDspyErrorNoMemory;
    }
    offset = 0;
    ridataptr = ridata;
    // Positions
    tokens[0] = RI_P;
    values[0] = ridataptr;
    float *ptdata = fdata;
    for (i = 0; i < npoints; i++) {
      *ridataptr++ = ptdata[0];
      *ridataptr++ = ptdata[1];
      *ridataptr++ = ptdata[2];
      ptdata += fsize;
    }
    offset += 3;
    // Normals
    tokens[1] = RI_N;
    values[1] = ridataptr;
    ptdata = fdata + offset;
    for (i = 0; i < npoints; i++) {
      *ridataptr++ = ptdata[0];
      *ridataptr++ = ptdata[1];
      *ridataptr++ = ptdata[2];
      ptdata += fsize;
    }
    offset += 3;
    // Radii - multiplied by two to become width
    tokens[2] = "varying float width";
    values[2] = ridataptr;
    ptdata = fdata + offset;
    for (i = 0; i < npoints; i++) {
      *ridataptr++ = 2.0f * ptdata[0];
      ptdata += fsize;
    }
    offset += 1;
    // Rest of the data
    for (j = 0; j < image->nvars; ++j) {
      tokens[j + 3] = (RtToken) malloc(strlen("varying") + strlen(image->typenames[j]) + strlen(image->varnames[j]) + 3);
      if (!tokens[j+3]) {
          RiContext(renderContext);
          return PkDspyErrorNoMemory;
      }
      sprintf(tokens[j + 3], "varying %s %s", image->typenames[j], image->varnames[j]);
      values[j + 3] = ridataptr;
      ptdata = fdata + offset;
      for (i = 0; i < npoints; i++) {
          for (k = 0; k < image->varsizes[j]; ++k) {
              *ridataptr++ = ptdata[k];
          }
          ptdata += fsize;
      }
      offset += image->varsizes[j];
    }
    if (style == PkDspyGridConnected) {
      RtFloat* trimdata = (RtFloat*) malloc(npoints * sizeof(RtFloat));
      if (!trimdata) {
          for (j = 0; j < image->nvars; ++j) {
              free(tokens[j+3]);
          }
          free(tokens);
          free(values);
          free(ridata);
          return PkDspyErrorNoMemory;
      }
      RtInt *nvertices = (RtInt*) malloc(npolys * sizeof(RtInt));
      if (!nvertices) {
          RiContext(renderContext);
          return PkDspyErrorNoMemory;
      }
      // Skip ahead to the face information
      fdata = ((float*) data) + 4 + npoints * fsize;
      int vsize = 0;
      for (i = 0; i < npolys; ++i) {
          nvertices[i] = (RtInt) (*fdata++);
          vsize += nvertices[i];
      }
      RtInt *vertices = (RtInt*) malloc(vsize * sizeof(int));
      if (!vertices) {
          RiContext(renderContext);
          return PkDspyErrorNoMemory;
      }
      RtInt *verticeptr = vertices;
      for (i = 0; i < npolys; ++i) {
          for (j = 0; j < nvertices[i]; ++j) {
              *verticeptr++ = (RtInt) (*fdata++);
          }
      }
      // Output trim information
      tokens[image->nvars + 3] = "uniform float __needstrim";
      values[image->nvars + 3] = trimdata;
      for (i = 0; i < npolys; ++i) {
          trimdata[i] = (RtFloat) (*fdata++);
      }
      RiPointsPolygonsV(npolys, nvertices, vertices, image->nvars + 4, tokens, values);
      free(nvertices);
      free(vertices);
      free(trimdata);
    } else if (style == PkDspyGridPoints) {
      RiPointsV(npoints, image->nvars + 3, tokens, values);
    } else if (style == PkDspyGridLines) {
      RiCurvesV(RI_LINEAR, 1, &npoints, RI_NONPERIODIC, image->nvars + 3, tokens, values);
    }
    RiAttributeEnd();
    // Switch back to renderer context
    RiContext(renderContext);
    for (j = 0; j < image->nvars; ++j) {
      free(tokens[j+3]);
    }
    free(tokens);
    free(values);
    free(ridata);
    return PkDspyErrorNone;
}
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;
}
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.

Display Driver Installation

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 or d_*type*.dll.


Direct-linked Display Driver Registration

An application linked to prman may register a display driver directly rather than through a shared library. This permits the driver to run in the same process as the application. The DspyRegisterDriverTable function takes a versioned struct filled with pointers to display driver functions.

PtDspyError
DspyRegisterDriverTable(const char *name,
                        const PtDspyDriverFunctionTable *pTable);

For example:

PtDspyDriverFunctionTable dt;
dt.Version = k_PtDriverCurrentVersion;
dt.pOpen  = DirectDspyImageOpen;
dt.pWrite = DirectDspyImageData;
dt.pClose = DirectDspyImageClose;
dt.pQuery = DirectDspyImageQuery;
dt.pActiveRegion = DirectDspyImageActiveRegion;
int err = DspyRegisterDriverTable("direct", &dt);

This will register a driver that can be loaded by using "direct" as format name in the type(second) parameter to RiDisplay. DspyRegisterDriverTable replaces the deprecated DspyRegisterDriver.


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 are automatically made available by the renderer, and do not require linking with any further files.

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)
{
   ...
DspyMemReverseCopy
void
DspyMemReverseCopy(unsigned char *target, const unsigned char *source, int len);

Reverse len bytes from source to target.

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

Example:

float matrix[16];
if (DspyFindMatrixInParamList("NP", matrix, paramCount, parameters))
{
     ...
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);
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.

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

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

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.


"Built-in" Parameters

Obviously users can provide any set of parameters they want (provided they are supported by their display driver). Note that, in addition to any parameters provided by the user, the renderer will also provide a standard set of parameters (listed below), that the user does not need to (and should not) provide.

Name Type Description
NP matrix world to screen 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