Short Contents

9.2 Writing Display Drivers

3Delight comes with a set of standard display drivers that are suitable for most applications (see section Display Drivers). However, it is possible to write custom display drivers if some specific functionality is needed. Basically, a display driver is a DSO (DLL under Windows) which implements an interface that 3Delight understands. This interface, along with all the data types used, is described in the `$DELIGHT/include/ndspy.h' header file and is further investigated in the following sections.

9.2.1 Required Entry Points

A display driver must implement four mandatory entry points:

PtDspyError DspyImageQuery ( PtDspyImageHandle image, PtDspyQueryType type, size_t size, void *data )

Queries the display driver about format information.

PtDspyError DspyImageOpen ( PtDspyImageHandle * image, const char *drivername, const char *filename, int width, int height, int paramcount, const UserParameter *parameters, int formatcount, PtDspyDevFormat *format, PtFlagStuff *flags)

Opens a display driver.

PtDspyError DspyImageData ( PtDspyImageHandle image, int xmin, int xmax_plus_one, int ymin, int ymax_plus_one, int entrysize, const unsigned char *data )

Sends data to display driver.

PtDspyError DspyImageClose ( PtDspyImageHandle image )

Closes the display driver.

An optional entry point is also defined:

PtDspyError DspyImageDelayClose ( PtDspyImageHandle image )

Closes the display driver in a separate process. This means that renderer will exit leaving the display driver running. This is particularly useful for framebuffer-like display driver(68).

Every function is detailed in the following sections.

DspyImageQuery

This function is called for two reasons:

  1. 3Delight needs to know the default resolution of the display driver. This may happen if the user did not call Format.
  2. 3Delight needs to know whether the display driver overwrites or not the specified file (not used at the moment).

Parameters are:

type
Takes two possible values: PkOverwriteQuery and PkSizeQuery. For each query, a different data structure needs to be filled. The structures are declared in `$DELIGHT/include/ndspy.h'.
size
Maximum size of the structure to fill.
data
A pointer to the data to fill. Copy the appropriate structure here.

See section A Complete Example.

DspyImageOpen

Called before rendering starts. It is time for the display driver to initialize data, open file(s), .... Here is a description of all the parameters passed to this function.

image
This opaque pointer is not used in any way by 3Delight. It should be allocated and used by the display driver to pass information to DspyImageData and DspyImageClose. For instance, a TIFF display driver would put some useful information about the TIFF during DspyImageOpen so that DspyImageData could access the opened file.
drivername
Gives the device driver name as specified by Display. For example:
Display "super_render" "framebuffer" "rgb"
provides `framebuffer' in drivername.
filename
Gives the filename provided in the Display command. For example:
Display "render.tif" "tiff" "rgb"
provides `render.tif' in filename.
width
height
Gives the resolution of the image, in pixels. If the image is cropped, width and height reflect the size of the cropped window.
paramcount
Indicates total number of user parameters provided in this call.
UserParameter
An array of user parameters, of size paramcount. UserParameter is defined as:
typedef struct
{
    const char *name;
    char valueType, valueCount;
    const void *value;
    int nbytes;
} UserParameter;
name is the name of the parameter, valueType is its type, which can be one of the following: `i' for an integer type, `f' for an IEEE floating point type, `s' for a string type and `p' for a pointer type. valueCount is used for parameters that have more than one value, such as matrices and arrays. value is the pointer to the actual data, except for the `p' type where it is the data itself. A set of standard parameters is always provided; those are described in Table 9.1.
formatcount
Number of output channels.
formats
An array of channel descriptions of size formatcount. A channel description contains a name and a type:
typedef struct
{
    char *name;
    unsigned type;
} PtDspyDevFormat;
The type can take one of the following values and is modifiable by the display driver:
PkDspyFloat32
PkDspyUnsigned32
PkDspySigned32
PkDspyUnsigned16
PkDspySigned16
PkDspyUnsigned8
PkDspySigned8
Additionally, the display driver may choose the byte ordering of the data by "or-ing" the type with one of the following two values:
PkDspyByteOrderHiLo
PkDspyByteOrderLoHi
flags
Modifiable flags to control the way data is sent to the display driver. This flag can be set to PkDspyFlagsWantsScanLineOrder to receive the data per scanline instead of per bucket.

Parameters can be passed to a display driver when issuing the Display command:

Display "render" "my_display" "rgb" "string compression" "zip"

In this case, `my_display' driver receives the parameter "compression".

Name Type Count Comments
NP `f' 16 World to Normalized Device Coordinates (NDC) transform
Nl `f' 16 World => Camera transform
near `f' 1 Near clipping plane, as declared by Clipping
far `f' 1 Far clipping plane, as declared by Clipping
nthreads `i' 1 Number of rendering threads
origin `i' 2 Crop window origin in the image, in pixels
OriginalSize `i' 2 Since width and height provided to DspyImageOpen only reflect the size of the cropped window, this variable gives the original, non cropped window size
PixelAspectRatio `f' 1 Pixel aspect ratio as given by Format
Software `s' 1 Name of the rendering software: "3Delight"
errorhandler `p' - A pointer to the standard error handler, of type RtErrorHandler. The display driver can use this function to print messages through 3DELIGHT's error handler(69), as declared by RiErrorHandler. Refer to A Complete Example.
Table 9.1: Standard parameters passed to DspyImageOpen().

DspyImageData

3Delight calls this function when enough pixels are available for output. Most of the time, this happens after each rendered bucket. However, if DspyImageOpen asks for scanline data ordering, a call to this function is issued when a row of buckets is rendered.

image
Opaque data allocated in DspyImageOpen
xmin
xmax_plus_one
ymin
ymax_plus_one
Screen coordinates containing provided pixels data.
entrysize
Size, in bytes, of one pixel. For example, if eight bit RGBA data was asked, entrysize is set to four.
data
Pointer to the actual data, organized in row major order.

DspyImageClose

Called at the end of each rendered frame. It is time to free all resources that were used and free image (which was allocated by DspyImageOpen).

DspyImageDelayClose

If this entry point is defined in the display driver, it is called instead of DspyImageClose() with the difference being that the call occurs in a separate process so that 3Delight can exit without waiting for the display driver. The `framebuffer' display driver uses this functionality (see section The framebuffer display driver). This is not supported on Windows so it should be avoided whenever possible.

9.2.2 Utility Functions


IMPORTANT

The following functions are useful to search through the parameters list and to perform some other low-level tasks. Note that there is a slightly annoying side-effect: it becomes mandatory to link with `3delight.dll' on the Windows operating system see section Linking with 3Delight. This means that display drivers that use these functions are not directly usable with other RenderMan-compliant renderers and will have to be re-compiled for them (although with no other modfications).

PtDspyError DspyFindStringInParamList ( const char *name, char **result, int parameters_count, const UserParameter *parameters )
PtDspyError DspyFindFloatInParamList ( const char *name, float *result, int parameters_count, const UserParameter *parameters )
PtDspyError DspyFindFloatsInParamList ( const char *name, int *result_count, float *result, int parameters_count, const UserParameter *parameters )
PtDspyError DspyFindIntInParamList ( const char *name, float *result, int parameters_count, const UserParameter *parameters )
PtDspyError DspyFindIntsInParamList ( const char *name, int *result_count, float *result, int parameters_count, const UserParameter *parameters )
PtDspyError DspyFindMatrixInParamList ( const char *name, float *result, int parameters_count, const UserParameter *pthe arameters )

All these functions search a parameter of a specific type and size in the provided parameters list (as specified by DspyImageOpen, refer to DspyImageOpen). If the desired parameter is found and the type matches, these functions will fill result with the found values and return PkDspyErrorNone. So for example, to find a float gamma[3] parameter, one could issue:

float display_gamma[3];
float array_size;
PtDspyError result = DspyFindFloatInParamList(
    "gamma",
    &array_size, &display_gamme, /* the ouputs */
    paramCount, parameters);
if( result == PkDspyErrorNone )
{
    /* can read 'array_size' floats in 'display_gamme'. */
    ...
}

Notes:

  • Colors, points, normals and vectors are considered as arrays of floats.
  • Integers are converted to floating points.
  • Both user provided parameters and standard parameters (see Table 9.1) are accesible with these functions.
PtDspyError DspyReorderFormatting ( int format_count, PtDspyDevFormat *format, int out_format_count, const PtDspyDevFormat *out_format )

Reorders the format array in a specific order. Parameters:

format_count
format
This is the format channels description as provided by DspyImageOpen. It contains all the channels provided to this display driver.
out_format_count
The size of the out_format character array.
out_format
The desired ordering. For example: "bgra".

If a display driver wants the channels in an "bgra" ordering it can call this function as follows:

/* the formats will be re-ordered in-place. */
DspyReorderFormatting( format_count, formats, 4, "bgra" );
void DspyMemReverseCopy ( unsigned char *target, const unsigned char *source, int len )

Reverse len bytes, from source to target.

void DspyError ( const char *module, const char *fmt )

Output an error message using the specified formatting. For example:

DspyError( "dspy_tiff", "cannot open file '%s'\n", file_name );

9.2.3 A Complete Example

/*
    Copyright (c)The 3Delight Team.
    All Rights Reserved.
*/

//
// = LIBRARY
//     3delight
// = AUTHOR(S)
//     Aghiles Kheffache
// = VERSION
//     $Revision$
// = DATE RELEASED
//     $Date: 2009-04-06 21:43:34 -0400 (Mon, 06 Apr 2009) $
// = RCSID
//     $Id: zfile.cpp 12168 2009-04-07 01:43:34Z aghiles $
//

#include <ndspy.h>
#include <uparam.h>
#include <ri.h>

#include <assert.h>
#include <stdio.h>
#include <float.h>
#include <string.h>
#include <limits.h>

/* ZFile Display Driver Implementation */

const unsigned kDefaultZFileSize = 512;

static void stderr_error( int type, int severity, char *msg )
{
	/* just ignore type and severity. */
	fprintf( stderr, "%s", msg );
}

/*
zfile format:
    zFile format is (matrices and image are row-major):
    magic # (0x2f0867ab)                                    (4 bytes)
    width (short)                                           (2 bytes)
    height (short)                                          (2 bytes)
    shadow matrices (32 floats, 16 for NP and 16 for Nl)  (128 bytes)
    image data (floats)                        (width*height*4 bytes)
    
    NOTE
    Matrices are stored in row major format.
*/
class zFile
{
public:
    zFile(
        const char* fileName,
        const float* np, const float* nl,
        unsigned short width, unsigned short height )

    : m_file(0x0), m_width(width), m_height(height),
    m_currentLine(0), m_pixelsLeftOnLine(width)
    {
        m_file = fopen( fileName, "wb" );

        if( m_file )
        {
            unsigned long magic = 0x2f0867ab;

            assert( sizeof(long) == 4 );

            fwrite( &magic, 4, 1, m_file );
            fwrite( &m_width, sizeof(m_width), 1, m_file );
            fwrite( &m_height, sizeof(m_height), 1, m_file );
            fwrite( np, sizeof(float), 16, m_file );
            fwrite( nl, sizeof(float), 16, m_file );
        }
    }

    ~zFile()
    {
        if( m_file )
        {
            fclose(m_file);
        }
    }
    
    bool Valid() const { return m_file != 0x0; }

    unsigned GetWidth() const {return m_width;}
    unsigned GetHeight() const {return m_height;}

    bool WriteScanline(
        unsigned short y, unsigned short size, const float* data )
    {
        if( y != m_currentLine || size > m_pixelsLeftOnLine )
        {
            return false;
        }
        
        m_pixelsLeftOnLine -= size;

        if( m_pixelsLeftOnLine == 0 )
        {
            ++m_currentLine;
            m_pixelsLeftOnLine = m_width;
        }
        
        return fwrite( data, sizeof(float), size, m_file ) == size;
    }

private:
    FILE* m_file;
    unsigned short m_width;
    unsigned short m_height;

    unsigned short m_currentLine;
    unsigned short m_pixelsLeftOnLine;
};

/*
    A utility function to get user parameters ...
*/
const void* GetParameter(
    const char *name,
    unsigned n,
    const UserParameter parms[] )
{
    for( unsigned i=0; i<n; i++ )
    {
        if(0 == strcmp(name, parms[i].name))
        {
            return parms[i].value;
        }
    }
    return 0x0;
}

/*
    Open
*/
PtDspyError DspyImageOpen(
    PtDspyImageHandle *i_phImage,
    const char *i_drivername,
    const char *i_filename,
    int i_width,  int i_height,
    int i_parametercount,
    const UserParameter i_parameters[],
    int i_numFormat,
    PtDspyDevFormat i_format[],
    PtFlagStuff *flagstuff ) 
{
    int i;
    bool zfound = false;

    const float* nl =
        (float*)GetParameter( "Nl", i_parametercount, i_parameters );

    const float* np =
        (float*)GetParameter( "NP", i_parametercount, i_parameters );

	RtErrorHandler error_handler = 
        (RtErrorHandler)GetParameter(
			"errorhandler", i_parametercount, i_parameters );

	if( !error_handler )
	{
		/* could happen if display driver is not run from 3Delight. */
		error_handler = &stderr_error;
	}

    /* Loop through all provided data channels and only ask for the 'z'
       channel. */

    for( i=0; i<i_numFormat; i++ )
    {
        if( strcmp(i_format[i].name, "z") != 0 )
        {
            i_format[i].type = PkDspyNone;
        }
        else
        {
            i_format[i].type = PkDspyFloat32;
            zfound = true;
        }
    }

    if( !zfound )
    {
		/* An example on how to call the error message handler. */
		(*error_handler)(
			RIE_CONSISTENCY, RIE_ERROR, "dspy_z : need 'z' in order to proceed.\n" );
        return PkDspyErrorUnsupported;
    }

    if( !nl || !np )
    {
        (*error_handler)( RIE_CONSISTENCY, RIE_ERROR,
            "dspy_z : need Nl & Np matrices in order to proceed. bug.\n" );
        return PkDspyErrorBadParams;
    }

    if (i_width > USHRT_MAX || i_height > USHRT_MAX)
    {
        (*error_handler)(
            RIE_LIMIT, RIE_ERROR,
            "dspy_z : image too large for zfile format" \
            " (use shadowmap display driver).\n" );
        return PkDspyErrorUndefined;
    } 

    zFile* aZFile = new zFile( i_filename, np, nl, i_width, i_height );

    if( !aZFile || !aZFile->Valid() )
    {
		(*error_handler) 
			( RIE_SYSTEM, RIE_ERROR, "dspy_z : cannot create file.\n" );

        delete aZFile;    
        return PkDspyErrorNoResource;
    }
    
    *i_phImage = (void*) aZFile;

    /* Ask display manager to provide data scanline by scanline
    */
    flagstuff->flags |= PkDspyFlagsWantsScanLineOrder;

    return PkDspyErrorNone;
}

/*
    DspyImageQuery
*/
PtDspyError DspyImageQuery(
    PtDspyImageHandle i_hImage,
    PtDspyQueryType i_type,
    size_t i_datalen,         
    void *i_data )
{
    zFile *aZFile = (zFile*) i_hImage;

    if( !i_data )
    {
        return PkDspyErrorBadParams;
    }
    
    switch( i_type )
    {
        case PkSizeQuery:
        {
            PtDspySizeInfo sizeQ;

            if( aZFile )
            {
                sizeQ.width = aZFile->GetWidth();
                sizeQ.height = aZFile->GetHeight();
                sizeQ.aspectRatio = 1;
            }
            else
            {
                sizeQ.width = kDefaultZFileSize;
                sizeQ.height = kDefaultZFileSize;
                sizeQ.aspectRatio = 1;
            }

            memcpy(
                i_data, &sizeQ,
                i_datalen>sizeof(sizeQ) ? sizeof(sizeQ) : i_datalen );

            break;
        }

        case PkOverwriteQuery:
        {
            PtDspyOverwriteInfo overwQ;

            overwQ.overwrite = 1;

            memcpy(
                i_data, &overwQ,
                i_datalen>sizeof(overwQ) ? sizeof(overwQ) : i_datalen );

            break;
        }

        default:
            return PkDspyErrorUnsupported;
    }

    return PkDspyErrorNone;        
}


/*
    DspyImageData
    
    Data is expected in scanline order (as asked in DspyImageOpen()).
*/
PtDspyError DspyImageData(
    PtDspyImageHandle i_hImage,
    int i_xmin, int i_xmax_plusone,
    int i_ymin, int i_ymax_plusone,
    int i_entrySize,        
    const unsigned char* i_data )
{
    zFile* aZFile = (zFile*) i_hImage;
    const float* fdata = (const float*) i_data;

    if( !aZFile || !fdata )
    {
        return PkDspyErrorBadParams;
    }

    /* Perform some sanity checks but everything should be fine really ...
       :> */

    if( i_ymax_plusone - i_ymin > 1 ||
        i_xmin != 0 ||
        i_xmax_plusone != aZFile->GetWidth() ||
        i_entrySize != sizeof(float) )
    {
        return PkDspyErrorBadParams;
    }

    if( !aZFile->WriteScanline(i_ymin, i_xmax_plusone - i_xmin, fdata) )
    {
        return PkDspyErrorNoResource;
    }

    return PkDspyErrorNone;
}

/*
    DspyImageClose
    
    delete our object.
*/
PtDspyError DspyImageClose( PtDspyImageHandle i_hImage )
{
    zFile* aZFile = (zFile*) i_hImage;
    
    if( !aZFile )
    {
        return PkDspyErrorUndefined;
    }

    delete aZFile;

    return PkDspyErrorNone;
}

9.2.4 Compilation Directives

Here is the compilation command line for the given example (`zfile.cpp'):

Linux
g++ -shared -o zfile.so -I$DELIGHT/include zfile.cpp
IRIX
CC -shared -o zfile.so -I$DELIGHT/include zfile.cpp
MacOS X
g++ -dynamiclib -o zfile.so -I$DELIGHT/include -arch "ppc" zfile.cpp
Windows
cl -I"%DELIGHT%/include" -LD zfile.cpp

3Delight 8.5. Copyright 2000-2009 The 3Delight Team. All Rights Reserved.