Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Using Existing Procedural Primitives

Delayed RIB File Reading

The simplest of the procprims is Delayed Read Archive. The existing interface for "including" one RIB file into another isRiReadArchive. Delayed Read Archive operates exactly like RiReadArchive, except that the reading is delayed until the procedural primitive bounding box is reached, unlike RiReadArchive which reads RIB files immediately during parsing. The advantage of the new interface is that since the reading is delayed, memory for the read primitives is not used until the bounding box is actually reached. In addition, if the bounding box proves to be off-screen, the parsing time of the entire RIB file is saved. The disadvantage is that an accurate bounding box for the contents of the RIB file is required, which was never needed before.

In RIB, the syntax for the Delayed Read Archive primitive is:

Procedural "DelayedReadArchive" [ "yourfilename" ] [ bound ]

-or-

Procedural2 "DelayedReadArchive" "SimpleBound"
    "string filename" "yourfilename"
    "float[6] __bound" [xmin xmax ymin ymax zmin zmax]

In place of "yourfilename" you must provide a pathname for the desired RIB file. If your RIB archive is referred to with a relative path we search the paths given by the archivepath setting. As with all RIB parameters that are bounding boxes, the bound is an array of six floating point numbers measured in the current object space.

In C, the syntax for calling the Delayed Read Archive primitive is:

RtString *data;
RtBound bound;
RiProcedural(data, bound, RiProcDelayedReadArchive, freedata);

data is a pointer to a single RtString which contains the filename (ie. data is a char **). bound is a pointer to six floats which contain the bounding box. freedata is the user free method which frees data. data could be a global variable, but almost certainly cannot be an automatic (local) variable, as it must persist until the renderer calls the free routine. In practice, data would probably be malloced, and freedata would merely free() data.

And here's the Procedural2 variant:

RtBound bbox;
RtString filename[1];
filename[0] = "yourfilename.rib";
RiProcedural2(RiProc2DelayedReadArchive, RiSimpleBound,
              "string filename", filename,
              "float[6] __bound", bbox, RI_NULL);

RiSimpleBound is the name of a built-in bound subroutine that searches the parameterlist for conventionally named parameters. This paves the way for more procedural bounding callbacks, but for most purposes RiSimpleBound should suffice.

Procedural Primitive DSOs

A more efficient method for accessing subdivision routines is to write them as dynamic shared objects (DSOs), and dynamically load them into the renderer executable at runtime. In this case, developers write a subset of the subdivision, bound and free routines exactly as required in the direct-linked setting. They are compiled with special compiler options to make them runtime loadable, and you specify the name of the shared .so, .dll file in the RIB file. The renderer loads the DSO the first time it is needed to subdivide a primitive, and from then on it is called as if (and executes as fast as if) it were statically linked. When referring to a DSO keep the following in mind:

  • Relative references are resolved via the procedural searchpath setting.
  • File extensions are optional and, when omitted, may result in more portable Ri stream.

In RIB, the syntax for specifying a dynamically loadable procedural primitive is:

Procedural "DynamicLoad" [ "dsoname" "initial" ] [ bound ]

dsoname is the name of the .so file that contains the required entry-points, and has been compiled and prelinked as described below. The current implementation intentionally subverts the LD_LIBRARY_PATH mechanism for searching for DSOs to load, so full pathnames for the DSO archive should be used.

initial is the ASCII printable string that represents the initial data to be sent to the ConvertParameters routine. The bound is an array of six floating point numbers which is xmin, xmax, ymin, ymax, zmin, zmax in the current object space.

And here's the Procedural2 variant:

Procedural2 "DynamicLoad" "SimpleBound"
    "string __dsoname" "yourdsoname"
    "float[6] __bound" [xmin xmax ymin ymax zmin zmax]
    //  additional meta-arguments to renderer ...
    //  arguments to plugin ...

Here, we employ the RiSimpleBound (and the associated bound parameter) to deliver our bounding box to the renderer. An alternate built-in bounding procedure, RiDSOBound, is provided to vector the bound request to a subroutine exported from the given DSO. This eliminates the requirement that the bound be emitted into the RIB file. RiDSOBound will only work if target DSO has implemented and exported the Bound subroutine.


Note

Please note that the Procedural2 implementation of DynamicLoad supports meta-parameters to control when and in which context the Subdivide2 function will execute. These meta-parameters may be interspersed with the arguments intended for the plugin. Since all meta-parameters are prefixed with '__', plugins can easily discriminate meta parameters from standard plugin parameters. For backwards compatibility, the meta-parameters "dsoname" and "bound" are supported but deprecated in favor of "__dsoname" and "__bound".


Here are two variations of the RIB represention for a trivial DynamicLoad procedural primitive:

Procedural "DynamicLoad" ["a_sphere" "3.0" ] [-3 3 -3 3 -3 3]
Procedural2 "DynamicLoad" "SimpleBound" "string __dsoname" "a_sphere"
            "float[6] __bound" [-3 3 -3 3 -3 3]
            "float radius" [3]

More complex initialization requirements require either more structure in the initial string data for Procedural or more parameters for Procedural2. As the initial data requirements grow, the benefits of Procedural2 become more apparent.

In C, the syntax for calling the dynamic load primitive is:

RtString *data;
RtBound bound;
RiProcedural(data, bound, RiProcDynamicLoad, freedata)

RtInt nargs;
RtToken toks[...];
RtPointer vals[...];
toks[0] = (RtToken) "float[6] __bound"; // RI_BOUND
vals[0] = (RtPointer) bound;
nargs = 1;
// .. plugin data here ..
RiProcedural2(RiProc2DynamicLoad, RiSimpleBound, nargs, toks, vals);

data is a pointer to an array of two RtStrings (i.e. data is a char **) that contains the DSO archive name in data[0], and the initial primitive data set in ASCII in data[1] (this will be sent to the ConvertParameters routine). bound is a pointer to six floats that contain the bounding box. freedata is the user free method that frees data. data could be a global variable, but almost certainly cannot be an automatic (local) variable, as it must persist until the renderer calls the free routine. In practice, data and its strings would be allocated with malloc and freedata would call free on data[0], data[1], and data.

Note that when the Subdivide routine wants to create a child procedural primitive of the same type as itself, it should call

RiProcedural(data, bound, Subdivide, Free)

passing its private notions of data and free, and not

RiProcedural(asciidata,bound,RiProcDynamicLoad,RiProcDLFree)

whose primary role is to simply load the DSO and to be representable in a RIB file. In the case of RiProcedural2 it may be advantageous for recursive procedural primitives to revert to the use of RiProcedural since it supports an unmarshalled representation of the plugin's state. In other words, the value of Procedural2 interface is primarily in the automatic serialization and deserialization to and from RIB. Once a plugin has been loaded and initialized, it's more convenient to convey data to sub-procedurals directly in the most natural (and private) struct or object representation.

RIB Generating Program

Another tool in our procprim toolkit is the RIB Generating Program. Here, the idea is that a separate program is harnessed to generate RIB at the behest of the renderer. As will be seen below, each generated procedural primitive is described by a request to the helper program, in the form of an ASCII datablock that describes the primitive to be generated. This datablock can be anything that is meaningful and adequate to the helper program, such as a sequence of a few floating point numbers, a filename, or a snippet of code in an interpreted modeling language. In addition, as above, the renderer supplies the detail of the primitive's bounding box, so that the generating program can make decisions on what to generate based on how large the object will appear on-screen.

In RIB, the syntax for specifying a RIB-generating program procedural primitive is:

Procedural "RunProgram" [ "program" "datablock" ] [ bound ]

program is the name of the helper program to execute, and may include command line options. datablock is the generation request datablock. It is an ASCII string which is meaningful to program, and adequately describes the children that are to be generated. Notice that program is a quoted string in the RIB file, so if it contains quote marks or other special characters these must be escaped in the standard way. The bound is an array of six floating point numbers - xmin, xmax, ymin, ymax, zmin, zmax - in the current object space.

In C, the syntax for calling the RIB-generating program procedural primitive is:

RtString *data;
RtBound bound;
RiProcedural(data, bound, RiProcRunProgram, freedata)

data is a pointer to an array of two RtStrings (i.e. data is a char **) that contain the program name in data[0], and the generation request data block in ASCII in data[1]. (The program name may include command line options.) bound is a pointer to six floats which contain the bounding box. freedata is the user free method that frees data. data could be a global variable, but almost certainly cannot be an automatic (local) variable, as it must persist until the renderer calls the free routine. In practice, data and its strings would allocated with malloc, and freedata would call free on data[0], data[1], and data.

Procedural Primitive Pathnames

PRMan provides a mechanism for selecting among several compiled versions of a procedural program or plug-in, depending on which platform the RIB file is being rendered on. The renderer will expand the special strings $ARCH or %ARCH in pathnames for procedural primitive programs and plug-ins, substituting a string that identifies the basic platform for which the running prman was built. For example:

Procedural "DynamicLoad" ["/net/prmanDSOs/$ARCH/a_sphere" ...]

The default architecture names are:

Default $ARCH namePRMan build
linux-x86-64Linux, 64-bit, for x86-64 systems
windows-x86-64Windows 64-bit, for x86-64 systems
osx-x86-64Mac OS XmacOS, 64-bit, for x86-64 systems

These names can be remapped into site-specific abstract names by placing a line in the rendermn.ini file, for example:

/architecture/default_name        new_name

/architecture/linux-x86           gcc41glibc24

The ARCH value can also be set directly, using the shell environment variable RMAN_ARCHITECTURE, which overrides both the default andrendermn.ini values.

...