The RtxPlugin API

The RtxPlugin API

November, 2010

Introduction

Release 16 of Pixar's PhotoRealistic RenderMan introduced a new texture plugin feature that allows users to write C++ code that responds to texture requests from the renderer by filling cache tiles on demand. This provides many benefits: the renderer can reuse the tiles once they are cached, the tiles are requested at a resolution appropriate for the given grid being shaded, and antialiasing is automatically performed by the texture filters built into the renderer.

The header file below provides the interface description for the C++ plugins.

#ifndef RTXPLUGIN_H
#define RTXPLUGIN_H

#include "ri.h"
#include <stdarg.h>
#include <assert.h>
#include <string>
#include <map>

// A RixContext is provided to an RtxPlugin at
// creation time, so that the plugin may use
// services provided by the renderer.
class RixContext;

// An RtxPlugin class
class RtxPlugin {
public:
    RtxPlugin() {}
    virtual ~RtxPlugin() {}

   //! A struct to define an X,Y coordinate
    struct Int2D
    {
        int X;
        int Y;
    };

    //! A struct to define a 2D region
    struct Tile2D
    {
        Int2D offset;
        Int2D size;
    };

    /**
       TextureCtx provides initial defining properties for textures generated
       by this plugin. Each plugin should fill this structure during an
       Open() method call. Open() is called for each unique instance of a given
       texture() call in the shader. A unique instance is defined by the string
       passed to the file parameter of the texture() SL call.
       NOTE: The Open() method may be called multiple times for a texture
        as will Close(). On a Close() all resources should be released, and
        on an Open() they are safe to reaquire.
    **/
    struct TextureCtx {
        /// This is the total number of channels present
        int numChannels;

        /// The maximum # of layers this texture can access.
        unsigned int numLayers;

        /// Plugin fills in the names and channel-count of the layers
        /// associated with this texture.  Fill requests are characterized
        /// by the combination of layername, channelOffset and nchans.
        ///
        /// Memory for the array is owned and managed by the plugin
        struct layerSpec
        {
            const char *name;
            unsigned numChannels;
        } **layers;

        /// The min,max resolution requested for this texture.
        Int2D   minRes, maxRes;

        /// The wrap mode applied at the edges of max resolution
        enum WrapMode
        {
            k_Black = 0,
            k_Clamp,
            k_Periodic
        } sWrap, tWrap;

        /// The type of the data provided by the texture
        enum DataType
        {
            k_Byte = 0,
            k_Float
        } dataType;

        /// What type of pyramid should be used?
        enum PyramidType
        {
            k_Single = 0,
            k_MIP,
            k_RIP
        } pyramidType;

        /// Should the tile be locked while filling? (not thread safe?)
        bool isLocked;

        /// Read-only texture args, they come in pairs, memory
        /// is owned by the renderer.
        unsigned int argc;
        const char **argv;

        /// The plugin can use this field to stash its own instance
        /// data. The plugin manages ownership.
        void *userData;
    };

    /// The Open() method is called the first time a texture() encounters
    /// the plugin and provides a unique arg string.
    virtual int Open (TextureCtx& tCtx) = 0;

    struct FillRequest {
        /// This is the resolution of the image at a given MIP level
        Int2D imgRes;

        /// This is the tile index and tile size
        Tile2D tile;

        /// This is a channel selection string. This string can be
        /// NULL if channel selection is not made by string.
        const char *channelRefExpr;

        /// This is the offset from zero for a given channel selection
        /// It is always zero if channelRefExpr is not NULL.
        int channelOffset;

        /// This is the number of channels we want to fill
        int numChannels;

        /// The data (interleaved) that the plugin should write
        /// the tile results into
        RtPointer tileData;
    };

    /// The plugin should use the fillReq inputs to write to the
    /// tileData in the FillRequest.
    virtual int Fill (TextureCtx& tCtx,
                           FillRequest& fillReq) = 0;

    /// The plugin should release all assets at Close()
    virtual int Close (TextureCtx& tCtx) = 0;
};

/// This is the single C-entry point for creation of the C++
/// object that represents the texture plugin
#define RTXPLUGINCREATE\
        extern "C" const PRMANEXPORT int RtxPluginVersion=1;\
        extern "C" PRMANEXPORT RtxPlugin *RtxPluginNew(RixContext *rixCtx, \
                                                       const char *pluginName)
#endif

Here is an example plugin that creates a zonePlate texture at a given frequency, defined by the URI string.

// A simple plugin to create a zone plate texture
#include "RtxPlugin.h"
#include <cstdlib>
#include <cmath>
#include <cstdio>

class ZonePlate : public RtxPlugin
{
public:
    ZonePlate( );
    virtual ~ZonePlate();
    virtual int Open (TextureCtx& tCtx);
    virtual int Fill (TextureCtx& tCtx,
                           FillRequest& fillReq);
    virtual int Close (TextureCtx& tCtx);


    RtFloat evalZonePlate(RtFloat x, RtFloat y, RtFloat k);
};

ZonePlate::ZonePlate() {}
ZonePlate::~ZonePlate() {}

int
ZonePlate::Open (TextureCtx &tCtx)
{
    tCtx.numChannels = 1;
    tCtx.minRes.X = 1;
    tCtx.minRes.Y = 1;
    tCtx.maxRes.X = 32768;
    tCtx.maxRes.Y = 32768;
    tCtx.sWrap = TextureCtx::k_Black;
    tCtx.tWrap = TextureCtx::k_Black;
    tCtx.dataType = TextureCtx::k_Float;
    tCtx.pyramidType = TextureCtx::k_MIP;
    tCtx.isLocked = false;

    RtFloat *myVal = (RtFloat *)malloc(sizeof(RtFloat));
    myVal[0] = 0.0f;
    if (tCtx.argc == 2) {
        myVal[0] = atof(tCtx.argv[1]);
    }

    // Provide a default frequency
    if (myVal[0] == 0.0f) {
        myVal[0] = 820.0f;
    }

    tCtx.userData = (RtPointer) myVal;
    return 0;
}

RtFloat
ZonePlate::evalZonePlate (RtFloat x, RtFloat y, RtFloat k)
{
    RtFloat r = sqrt(x*x + y*y);

    RtFloat result = (1.0f + cos(k * r*r)) / 2.0f;
    return result;
}

int
ZonePlate::Fill (TextureCtx& tCtx,
                 FillRequest& fillReq) {

    // This assumes the plugin creates a 1 chan, RfFloat format texture
    RtFloat *myVal = (RtFloat *) tCtx.userData;
    RtFloat dX = 1.0f / fillReq.imgRes.X;
    RtFloat dY = 1.0f / fillReq.imgRes.Y;

    RtFloat startX = (RtFloat)fillReq.tile.offset.X * fillReq.tile.size.X * dX;
    RtFloat fY =     (RtFloat)fillReq.tile.offset.Y * fillReq.tile.size.Y * dY;

    RtFloat *fdata = (RtFloat *)fillReq.tileData;
    for (int y = 0; y < fillReq.tile.size.Y; y++, fY+=dY) {
        float fX = startX;
        for (int x = 0; x < fillReq.tile.size.X; x++, fX+=dX) {
            RtFloat zoneVal = evalZonePlate (fX-.5, fY-.5, myVal[0]);
            *(fdata++) = zoneVal;
        }
    }
    return 0;
}

int
ZonePlate::Close (TextureCtx& tCtx)
{
    if (tCtx.userData) {
        free (tCtx.userData);
    }

    return 0;
}

RTXPLUGINCREATE
{
    return new ZonePlate();
}