Short Contents

10.1 3Delight Plug-ins

10.1.1 Display Driver Plug-ins

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 dynamic shared library (DSO or DLL in short) 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.

10.1.1.1 Required Entry Points

A display driver must implement four mandatory entry points:

PtDspyError DspyImageQuery ( PtDspyImageHandle image, PtDspyQueryType type, int 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
Can take one the following values:
PkSizeQuery
Queries a default size for the image.
PkCookedQuery
Queries whatever the display driver wants "cooked" (pixels) or "raw" (fragment list) data. Refer to Accessing 3Delight's Deep Buffer for more information about fragment lists. If this query is not handled, the rendered assumes "cooked" data.
PkOverwriteQuery
Unsupported.
For each query, a different data structure needs to be filled. The structures are declared in `$DELIGHT/include/ndspy.h'. A brief description follows:
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 10.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.
3dl_quantize_info `f' 5*fc Quantize information for each of the formatcount channels. Tuples of 5 floats for zero, one, min, max and dither. Comes from either RiQuantize or the "quantize" and "dither" display parameters.
Table 10.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.

10.1.1.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 10.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 );
PtDspyError DspyRegisterDriverTable ( const char *driver_name, const PtDspyDriverFunctionTable *pTable );

This function allows a new display driver to be registered by an application by directly supplying function pointers to the required entry points. This is useful when 3Delight is used as a library and the output is to be displayed in the host application. For example, in 3Delight for Maya, we have:

PtDspyDriverFunctionTable table;
memset( &table, 0, sizeof(table) );

table.Version = k_PtDriverCurrentVersion;
table.pOpen = &MayaDspyImageOpen;
table.pQuery = &MayaDspyImageQuery;
table.pWrite = &MayaDspyImageData;
table.pClose = &MayaDspyImageClose;

DspyRegisterDriverTable( "maya_render_view", &table );

Where MayaDspyImageOpen, etc are the same functions which would be used for a standalone display driver.

10.1.1.3 Accessing 3Delight's Deep Buffer

Contrary to a standard framebuffer -which stores the final color at each pixel- a deep buffer stores a list of surfaces touching a specific pixel. 3Delight provides access to such a buffer with a notable difference: surface lists are provided per sub-sample and not per-pixel. This means that the user has access to the raw, unfiltered, surface lists directly from the render engine. The surface lists, which are called fragment lists in 3Delight, can be accessed using a display driver. By default, all display drivers receive pixels and not fragment lists. To enable fragment lists one has to reply consequently to the PkCookedQuery query, as described in DspyImageQuery. If this is done, the display driver will receive the requested lists of fragments instead of a buffer of pixels. The fragments are received through a PtDspyRawData structure. The structure is shown in Listing 10.1.

typedef struct
{
    char ID[4];

    /* Samples per pixel in x & y */
    unsigned sppx, sppy;

    /* Data width & height in pixels */
    int width, height;

    /* Array of fragment lists. NumLists = width*height*sppx*sppy */
    PtDspyFragment **fragments;

} PtDspyRawData;
Listing 10.1: PtDspyRawData structure description.

Listing 10.2 illustrates how to get a pointer to the structure and then traverse the fragments.

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 )
{
    if( !i_data )
        return PkDspyErrorBadParams;

    const PtDspyRawData *rawData = (const PtDspyRawData *)i_data;

    if (strcmp(rawData->ID, "3DL"))
        return PkDspyErrorBadParams;

    unsigned numFrags = 0;

    /* fpbx = fragments per bucket in x.
       fpby = fragments per bucket in y. */
    unsigned fpbx = rawData->sppx * rawData->width;
   	unsigned fpby = rawData->sppy * rawData->height;

    unsigned numFrags = 0;

    for( unsigned i = 0; i < fpby*fpbx; i++ )
    {
        PtDspyFragment *f = rawData->fragments[ i ];

        /* we'll skip fragments with a depth of FLT_MAX */
        while( f && f->depth==FLT_MAX )
            f = f->next;

        while( f )
        {
            numFrags++;
            f = f->next;
        }
    }

    printf( "total number of fragments = %d\n", numFrags );
}
Listing 10.2: Accessing lists of fragments (deep buffer) in display drivers

A fragment has the following C declaration:

typedef struct PtDspyFragment_s
{
    /* Fragment color & opacity.  */
    float *color;

    /* "Thickness" of this sample. */
    union
    {
        float thickness;
        float *filler;
    };

    /* depth of this fragment */
    float depth;

    /* u/v */
    float u, v;
    
    /* fragment opacity */
    float opacity[3];

    /* Next fragment on the list (in the same subsample) */
    struct PtDspyFragment_s *next;

} PtDspyFragment;
Listing 10.3: PtDspyFragment structure.

Follows some important remarks about fragment lists:

  1. The lists are sorted in Z. Furthest fragments come first in the least.
  2. Lists are truncated at first opaque object unless special culling attributes are set to disable hidden surface removal (see culling attributes). In general, lists longer than one will contain fragments that are not opaque.
  3. The length of each list is not constant per sample, of course.
  4. In order to obtain the final color for a pixel, the user must composite the fragments and then filter them.

10.1.1.4 A Complete Example

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

//
// = LIBRARY
//     3delight
// = AUTHOR(S)
//     Aghiles Kheffache
// = VERSION
//     $Revision$
// = DATE RELEASED
//     $Date$
// = RCSID
//     $Id$
//

#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 magic = 0x2f0867ab;

            assert( sizeof(magic) == 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,
    int i_datalen,         
    void *i_data )
{
    zFile *aZFile = (zFile*) i_hImage;

    if( !i_data )
    {
        return PkDspyErrorBadParams;
    }
    
    size_t datalen = i_datalen;
    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,
                datalen>sizeof(sizeQ) ? sizeof(sizeQ) : datalen );

            break;
        }

        case PkOverwriteQuery:
        {
            PtDspyOverwriteInfo overwQ;

            overwQ.overwrite = 1;

            memcpy(
                i_data, &overwQ,
                datalen>sizeof(overwQ) ? sizeof(overwQ) : 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;
}

10.1.1.5 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
MacOS X
g++ -dynamiclib -o zfile.dylib -I$DELIGHT/include zfile.cpp
Windows
cl -I"%DELIGHT%/include" -LD zfile.cpp

10.1.2 RSL Plug-ins

It is possible to extend the capabilities of the shading language by calling C or C++ functions from inside shaders. When compiling a shader, if the compiler encounters a function it doesn't now, it automatically searches all the directories specified by the `-I' command line option (see section Using the Shader Compiler - shaderdl) looking for a DSO containing a definition of the unknown function.

All this is better explained by an example:

/*
	A sample rsl plugin with two functions: one to square float values and
	another to square point values.
*/
#include "RslPlugin.h"

#include <iostream>

int float_sqr( RslContext* rslContext, int argc, const RslArg* argv[] )
{
	RslFloatIter retArg( argv[0] );
	RslFloatIter arg1( argv[1] );

	unsigned numVals = RslArg::NumValues( argc, argv );
	for( unsigned i = 0; i < numVals; ++i)
	{
		*retArg = *arg1 * *arg1;
		++retArg;
		++arg1;
    }

    return 0;
}

int point_sqr( RslContext* rslContext, int argc, const RslArg* argv[] )
{
	RslPointIter retArg( argv[0] );
	RslPointIter arg1( argv[1] );

	unsigned numVals = RslArg::NumValues( argc, argv );
	for( unsigned i = 0; i < numVals; ++i)
	{
		(*retArg)[0] = (*arg1)[0] * (*arg1)[0];
		(*retArg)[1] = (*arg1)[1] * (*arg1)[1];
		(*retArg)[2] = (*arg1)[2] * (*arg1)[2];
		++retArg;
		++arg1;
    }

    return 0;
}

void plugin_init( RixContext *context )
{
	/*
		This is an initialization function for the entire plugin. shadeop
		specific functions can also be specified in the pluginFunctions array
		below.
	*/
}

void plugin_cleanup( RixContext *context )
{
	/*
		This is a cleanup function for the entire plugin. shadeop specific
		functions can also be specified in the pluginFunctions array below.
	*/
}

extern "C"
{
	static RslFunction pluginFunctions[] =
	{
		{ "float sqr(float)", float_sqr, NULL, NULL },
		{ "point sqr(point)", point_sqr, NULL, NULL },
		NULL
	};

	RSLEXPORT RslFunctionTable RslPublicFunctions(
			pluginFunctions, plugin_init, plugin_cleanup );
}


Here is how to compile a DSO under different environments:

Linux
g++ -shared -o sqr.so -I$DELIGHT/include sqr.cpp
MacOS X
g++ -dynamiclib -o sqr.dylib -I$DELIGHT/include sqr.cpp
Windows
cl -I"%DELIGHT%/include" -LD sqr.cpp

When dealing with string parameters, 3Delight provides the plug-in with pointers which are valid at least until the plug-in is unloaded. For output parameters, the plug-in should assign strings obtained from the RixTokenStorage interface. Here is an example which illustrates how to do this:


/*
	Sample plugin which concatenates two strings.
*/
#include "RslPlugin.h"

#include <stdlib.h>
#include <string.h>

int concat( RslContext* rslContext, int argc, const RslArg* argv[] )
{
	RslStringIter retArg( argv[0] );
	RslStringIter arg1( argv[1] );
	RslStringIter arg2( argv[2] );

	RixTokenStorage *token_storage =
		static_cast<RixTokenStorage*>(
			rslContext->GetRixInterface( k_RixGlobalTokenData ) );

	unsigned numVals = RslArg::NumValues( argc, argv );
	for( unsigned i = 0; i < numVals; ++i)
	{
		size_t l1 = strlen( *arg1 );
		size_t l2 = strlen( *arg2 );
		/* NOTE: dynamic allocation may easily cause horrible performance. */
		char *buffer = (char*) malloc( l1 + l2 + 1 );
		memcpy( buffer, *arg1, l1 );
		memcpy( buffer + l1, *arg2, l2 );
		buffer[l1 + l2] = '\0';

		/* This provides a copy of the string which will live long enough. */
		*retArg = token_storage->GetToken( buffer );

		free( buffer );

		++retArg;
		++arg1;
		++arg2;
    }

    return 0;
}

extern "C"
{
	static RslFunction pluginFunctions[] =
	{
		{ "string external_concat(string,string)", concat, NULL, NULL },
		NULL
	};

	RSLEXPORT RslFunctionTable RslPublicFunctions(
			pluginFunctions, NULL, NULL );
}


10.1.3 Ri Filter Plug-ins

All Rif API function and class definitions are defined in `$DELIGHT/include/rif.h', so this file must be #included. API entry points are described in the following sections.

Plug-in Entry Points

The following functions should be defined in all Ri plug-in filters.

RifPlugin* RifPluginManufacture ( int argc, char **argv )

This function is called after the plug-in is loaded. It should return an instance of the Ri plug-in filter. An example is provided in Listing 7.18. All parameters are passed using i_argc and i_Argv as for the main() in C. The RiPlugin abstract class is defined in `$DELIGHT/include/rif.h' and all plug-in filters must derive from it:

class RifPlugin
{
public:
    virtual ~RifPlugin() {}
    virtual RifFilter &GetFilter() = 0;
};

The RifFilter structure returned by RifPlugin::GetFilter() contains all the filtering information (refer to Using Ri Plug-in Filters for usage examples):

struct RifFilter
{
    enum { k_UnknownVersion = 0, k_CurrentVersion = 1 };
    enum { k_Continue = 1, k_Terminate = 2 } DefaultFiltering;
    int Filtering;
    short Version;
    void *ClientData;
    char Reserved[64];
    RifFilter();
    /* Transforms */
    RtVoid (*Perspective)(RtFloat fov);
    RtVoid (*ConcatTransform)(RtMatrix transform);
    RtVoid (*CoordinateSystem)(RtToken space);
    RtVoid (*ScopedCoordinateSystem)(RtToken space);
    RtVoid (*CoordSysTransform)(RtToken space);
    RtVoid (*Identity)(void);
    ... 
};
RtVoid RifPluginDelete ( RifPlugin* i_plugin )

It is recommended to have this entry point in the DSO, since 3DELIGHT will use it to destroy DSOs loaded using RifLoadPlugin() (see below). This function has been defined for Windows systems, were memory allocation and deallocation cannot span multiple DLLs. The Plug-in will still work without this entry point but will leak memory on Windows systems.

Renderer's Entry Points

The following functions are implemented in 3DELIGHT and are accessible from Ri plug-ins.

RifPlugin* RifLoadPlugin ( const char *i_name, int i_argc, char **argv )

Loads the Ri plug-in specified named i_name and add it at the end of the plug-in chain. It is necessary to call RifUnloadPlugins() to free all allocated resources at the end of render.

RtVoid RifUnloadPlugins ( void )

Free all allocated resources and close all DSOs that have been loaded by RifLoadPlugin().

RtVoid RifAddPlugin ( RifPlugin *i_plugin )

Add a plug-in at the end of the plug-in chain. The user is responsible of the actual loading and resource management tasks. It is suggested to use RifLoadPlugin() instead.

RtVoid RifRemovePlugin ( RifPlugin *i_plugin )

Removes a plug-in from the plug-in chain. The user is responsible for releasing resources used by the plug-in.

RifPlugin* RifGetCurrentPlugin ( )

Returns the currently active plug-in. This function is important since there is no way, for the plug-in, on which particular instance it is running (because all callbacks are static).

RifEmbedding RifGetEmbedding ( )

Says whether this plug-in is run in RIB output mode or in normal rendering mode. For example, when executing renderdl with the `-catrib' option, plug-ins are in RIB output mode. A particular plug-in might choose to act differently depending on the "embedding". RifEmbedding is defined as follows:

typedef enum
{
    k_RifRIB,
    k_RifRenderer
} RifEmbedding;
RtInt RifGetDeclaration ( RtToken i_token, RifTokenType *i_token, RifTokenDetail *i_detail, RtInt *o_array_len )

A helper function to parse Ri in-line declarations.

RtString RifGetTokenName ( RtToken i_token )

Another helper function to parse in-line token declarations. This one returns the name of the token or null if the declaration is invalid. The returned name does not need to be freed.

RtVoid RifGetChainInfo (RtInt *o_current, RtInt *o_total )

This procedure returns the current level in the Ri plug-in filter chain as well as the total number of plug-ins in the chain. Range of the o_current variable is [0 ... o_total-1]

RtVoid RifParseFile (const char *i_filename, RifParseMode i_mode )

This procedure enables a Ri plug-in to easily parse RIB files. The i_mode variables tells 3DELIGHT where, in the Ri plug-in filter chain, the RIB stream should be inserted:

k_RifParseNextLayer
Inject RIB stream into next plug-in filter in the chain.
k_RifParseThisLayer
Inject RIB stream back into this layer in the chain.
k_RifParseFirstLayer
Inject RIB stream into first layer in the chain.

The declaration of RifParseMode is found in `$DELIGHT/include/rif.h':

typedef enum
{
    k_RifParseNextLayer,
    k_RifParseThisLayer, 
    k_RifParseFirstlayer
} RifParseMode;
RtVoid RifParseBuffer (const char *i_buf, unsigned i_size, RifParseMode i_mode )

Same as RifParseFile but parse RIB commands from memory.

10.1.4 Procedural Geometry Plug-ins

This section is meant to provide examples on how to write simple procedural primitives for 3Delight. For more complete documentation about procedural primitives and the interface to them, refer to the RenderMan Interface Specification.

10.1.4.1 The RunProgram Procedural Primitive

This example shows how to write a procedural primitive in the form of an external program which generates RIB. It can be invoked in a RIB stream as such:

Procedural "RunProgram" ["sphere" "0 0.5 1"] [-1 1 -1 1 -1 1]

The three floats it receives as parameters represent a color, as expected by the program. The program itself outputs a sphere of that color. This example also shows how to generate a RIB using `lib3delight'. Note that on Windows, 3Delight searches for `sphere.exe' if it doesn't find `sphere' in its procedural search path.

Note that a poorly written program (especially one which fails to output the \377 delimiter) may easily hang the renderer. Great care was taken in 3Delight to check for the most common errors but there are still some which are not caught. It is also important that your program be written to accept multiple requests.

#include <stdio.h>
#include <stdlib.h>

#include "ri.h"

int main(int argc, char **argv)
{
    char buf[256];

    /*
      You can still use the standard error output to diagnose your program.
      This allows you to see that your program is started only once even if it
      is invoked several times in a frame.
    */
    fprintf(stderr, "diagnostic: sphere program started.\n");
    
    /*
      Requests from the renderer to the program are passed on the standard
      input. Each request is written on a single line. The detail level required
      is written first, followed by the arguments passed by the user in the RIB.
      The two are separated by a single space.
    */
    while (fgets(buf, 256, stdin) != NULL)
    {
        RtFloat detail;
        RtColor color = {1.0f, 1.0f, 1.0f};
        sscanf(buf, "%g %g %g %g", &detail, &color[0], &color[1], &color[2]);

        /*
          Calling RiBegin with a file name as a parameter causes the commands
          to be output as RIB to that file when using lib3delight. Using
          "stdout" or "stderr" will output the RIB to the standard output or
          error respectively.
          
          It is important to call RiBegin()/RiEnd() inside the loop (for each
          request) to ensure that the output is received properly by the
          renderer.
        */
        RiBegin("stdout");
        
        RiColor(&color[0]);
        RiSphere(1.0f, -1.0f, 1.0f, 360.0f, RI_NULL);
        
        /*
          Outputting a single 0xFF character (377 in octal) is the method to
          signal the renderer that the program has finished processing this
          request. This can also be done manually if you choose not to use the
          library to output your RIB.
        */
        RiArchiveRecord(RI_VERBATIM, "\377");
        
        RiEnd();
    }

    fprintf(stderr, "diagnostic: sphere program is about to end.\n");
    return 0;
}


This example can be compiled with the following commands:

Linux
g++ -o sphere -O3 -I$DELIGHT/include/ -L$DELIGHT/lib/ sphere.cpp -l3delight
Mac OS X
g++ -o sphere -O3 -I$DELIGHT/include/ -L$DELIGHT/lib/ sphere.cpp -l3delight
Windows
CL /Ox /I"%DELIGHT%\include" sphere.cpp "%DELIGHT%\lib\3Delight.lib"

10.1.4.2 The DynamicLoad Procedural Primitive

This example shows how to write a procedural primitive in the form of a DSO. It is special in that it calls itself a number of times to create a Menger's Sponge(70). It can be invoked in a RIB stream as such:

Procedural "DynamicLoad" ["sponge" "3"] [-1 1 -1 1 -1 1]

The single parameter it takes is the maximal recursion depth. It also makes use of the `detailsize' parameter of the subdivision routine to avoid outputting too much detail. Note that 3Delight attempts to find `sponge.so' (or `sponge.dll' on Windows) and then `sponge' in the procedural search path. This allows you not to specify the extension.

#include <stdlib.h>
#include <stdio.h>

#include "ri.h"

#if defined(_WIN32)
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* Declarations */
RtPointer DLLEXPORT ConvertParameters(RtString paramstr);
RtVoid DLLEXPORT Subdivide(RtPointer data, float detail);
RtVoid DLLEXPORT Free(RtPointer data);

RtPointer DLLEXPORT ConvertParameters(RtString paramstr)
{
    int* depth = (int*) malloc(sizeof(int));
    *depth = 3;                /* decent default value */
    sscanf(paramstr, "%d", depth);
    return depth;
}

RtVoid DLLEXPORT Subdivide(RtPointer blinddata, RtFloat detailsize)
{
    int depth = *(int*) blinddata;
    
    /* Simple usage of detailsize to avoid drawing too much detail */
    if (depth <= 0 || detailsize <= 5.0f)
    {
        /* Draw a cube */
        RtInt nverts[] = {4, 4, 4, 4, 4, 4};
        RtInt verts[] = {
            3, 7, 6, 2,        /* top face    */
            5, 1, 0, 4,        /* bottom face */
            7, 3, 1, 5,        /* back face   */
            3, 2, 0, 1,        /* left face   */
            6, 7, 5, 4,        /* right face  */
            2, 6, 4, 0};       /* front face  */

        RtFloat points[] = {
            -1, -1, -1,      -1, -1, 1,
            -1, 1, -1,       -1, 1, 1,
            1, -1, -1,       1, -1, 1,
            1, 1, -1,        1, 1, 1};
        RiPointsPolygons(
            (RtInt)6, nverts, verts, RI_P, (RtPointer)points, RI_NULL);
    } else {
        /* Recursive call, reduce depth and scale the object by 1/3 */
        RtBound bound = {-1, 1, -1, 1, -1, 1};
        int* newDepth;
        unsigned x,y,z;
        RiScale(1.0/3.0, 1.0/3.0, 1.0/3.0);

        for (x = 0; x < 3; ++x)
            for (y = 0; y < 3; ++y)
                for (z = 0; z < 3; ++z)
                    if (x % 2 + y % 2 + z % 2 < 2)
                    {
                        RiTransformBegin();
                        RiTranslate(
                            x * 2.0 - 2.0,
                            y * 2.0 - 2.0,
                            z * 2.0 - 2.0);
                        newDepth = (int*) malloc(sizeof(int));
                        *newDepth = depth - 1;
                        /* We could make the recursive call using
                           RiProcDynamicLoad but that would be more complex and
                           slightly less efficient */
                        RiProcedural(newDepth, bound, Subdivide, Free);
                        RiTransformEnd();
                    }
    }
}

RtVoid DLLEXPORT Free(RtPointer blinddata)
{
    free(blinddata);
}

#ifdef __cplusplus
}
#endif

This example can be compiled with the following commands:

Linux
gcc -o sponge.so -O3 -I$DELIGHT/include/ -shared sponge.c
Mac OS X
gcc -dynamiclib -o sponge.dylib -O3 -I$DELIGHT/include/ -L$DELIGHT/lib/ sponge.c -l3delight
Windows
CL /Ox /LD /I"%DELIGHT%\include" sponge.c "%DELIGHT%\lib\3Delight.lib"

3Delight 10.0. Copyright 2000-2011 The 3Delight Team. All Rights Reserved.