RenderMan procedural primitives are user-provided subroutines which manipulate private data structures containing geometric primitives that the renderer knows nothing about. There are two entry points for each procedural primitive, the subdivide method, and the free method. Each instance of a procedural primitive contains pointers to the appropriate methods, a blind (to the renderer) data pointer which points at data allocated by, and meaningful to, the methods, and a bounding box which completely contains any geometry of the primitive.
When the renderer reaches the bounding box of a particular procedural primitive instance, it calls the subdivide method, passing in the blind data pointer and a floating point number which indicates the number of pixels which the bounding box covers (called the detail). The subdivide method splits the primitive into smaller primitives. It can either generate standard RenderMan primitives, or it can generate more instances of procedural primitives with their own blind data pointers and (presumably smaller) bounding boxes, or some combination.
At some point, the renderer knows that it will not need a particular blind pointer ever again. It then calls the free routine to destroy that blind pointer. It is never safe for the subdivide routine to assume renderer behavior with respect to freeing data pointers. In particular, the renderer may free a blind pointer without ever subdividing it, or may subdivide a particular pointer multiple times.
Prior to PhotoRealistic RenderMan 3.7,
the only way to create and use procedural primitives was to link
the methods into a modeling program,
along with the library version of PRMan (libprman.a).
In PhotoRealistic RenderMan 3.7,
three built-in procedural primitives will be available
from inside the stand-alone renderer executable
via simple RIB directives.
Two of these are extension mechanisms,
which allow users to create and execute their own procedural primitives
which are loaded and evaluated at rendering time.
Delayed RIB File Reading
The simplest of the new RIB procedural primitives is Delayed Read Archive. The existing interface for "including" one RIB file into another is RiReadArchive. 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" [ "filename" ] [ bound ]Any filename can be used, however, there is currently no searchpath mechanism so relative filenames are always relative to the current working directory of the process. As with all RIB parameters which are bounding boxes, the bound is an array of six floating point numbers which is xmin, xmax, ymin, ymax, zmin, zmax 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.
A more dynamic method of generating procedural primitives is to call a helper program which generates geometry on-the-fly in response to procedural primitive requests in the RIB stream. 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 which describes the primitive to be generated. This datablock can be anything which 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 a 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.
The generation program reads requests on its standard input stream, and emits RIB streams on its standard output stream. These RIB streams are read into the renderer as though they were read from a file (as with ReadArchive above), and may include any standard RenderMan attributes and primitives (including procedural primitive calls to itself or other helper programs). As long as any procedural primitives exist in the rendering database which require the identical helper program for processing, the socket connection to the program will remain open. This means that the program should be written with a loop which accepts any number of requests and generates a RIB "snippet" for each one.
The specific syntax of the request from the renderer to the helper program is extremely simple, as follows:
fprintf(socket, "%g %s\n", detail, datablock);The detail is provided first, as a single floating-point number, followed by a space, followed by the datablock and finally a newline. The datablock is completely uninterpreted by the renderer or by the socket write, so any newlines or special characters should be preserved (but the quotation marks that make it a string in the RIB file will, of course, be missing).
The helper program's response should be to create a RIB file on stdout, presumably using the RIB client library, such as follows:
RiBegin(RI_NULL); RiAttributeBegin(); /* various attributes */ /* various primitives */ RiAttributeEnd(); RiArchiveRecord(RI_COMMENT,"\377"); RiEnd();Notice, in particular, the special trick which the helper program must use to stay in synchronized communication with the renderer. Stdout should not be closed when the RIB snippet is complete, but rather a single '\377' character should be emitted and the stdout stream flushed. This will signal the renderer that this particular snippet is complete, yet leave the stream open in order to write the next snippet. The use of RiArchiveRecord and RiEnd as above will accomplish this when the RIB client library is used. Warning: if the '\377' character is not emitted, nor is accidentally not flushed through the stdout pipe to the renderer, the render will hang.
When the renderer is done with the helper program, it will close its end of the IPC socket, so reading an EOF on stdin is the signal for the helper program to exit.
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 data block. It is an ASCII string which is meaningful to program, and adequately describes the children which are to be generated. Notice that program 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 which is 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 (ie. data is a char **). which contains 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 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 and its strings would be malloced, and freedata would free data[0], data[1] and data.
A more efficient method for accessing subdivision routines is to write them as dynamic shared objects (DSOs), and Dynamic Load them into the renderer executable at run-time. In this case, you write your subdivision and free routines exactly as you would if you were writing them to be statically linked with the renderer. They are compiled with special compiler options to make them run-time loadable, and you specify the name of the shared .so 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 writing a procedural primitive DSO, you must create three specific public subroutine entry points, named Subdivide, Free and ConvertParameters. Subdivide is a standard RI procedural primitive subdivision routine, taking a blind data pointer to be subdivided, and a floating-point detail to estimate screen size. Free is a standard RI procedural primitive free routine, taking a blind data pointer which is to be released. ConvertParameters is a special routine which takes a string and returns a blind data pointer. It will be called for each Dynamic Load procedural primitive in the RIB file, and its job is to convert a printable string version of the progenator's blind data (which must be in ASCII in the RIB file), into something that the Subdivide routine will accept.
The ANSI-C prototypes for these three required entry points are as follows:
RtPointer ConvertParameters(RtString paramstr); RtVoid Subdivide(RtPointer data, RtFloat detail); RtVoid Free(RtPointer data);As an example, consider the following trivial procedural primitive which merely emits a sphere of a specified radius into the renderer. This example ignores the subdivision detail, since the primitive is so simple.
RtPointer ConvertParameters(RtString paramstr) { RtFloat *radius_p; /* convert the string to a float */ radius_p = (RtFloat *)malloc(sizeof(RtFloat)); *radius_p = atof(paramstr); /* return the blind data pointer */ return (RtPointer)radius_p; } RtVoid Subdivide(RtPointer data, RtFloat detail) { RtFloat *radius_p = (RtFloat *)data; /* output a sphere with the given radius */ RiSphere( *radius_p, -*radius_p, *radius_p, 360.0, RI_NULL ); } RtVoid Free(RtPointer data) { free(data); }
To create the DSO, one first compiles the C source code in the standard way. Then one prelinks the object file to create a shared object, which by convention has the extension .so. The required loader prelinking flags are different on various operating systems, but the SGI version is as follows:
cc -c -I/work/rman/include a_sphere.c ld -shared -B dynamic -o a_sphere.so a_sphere.oThe C source code can call any routine in RI, or any function from the standard C and math libraries (libc and libm). If the code needs to call routines from other libraries, there are additional options in the prelinking phase for resolving these. See your compiler documentation for details.
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 which contains the three required entry-points, and has been compiled and prelinked as described above. 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. If a slash character does not appear anywhere in dsoname, a "./" will be prepended and the DSO will be loaded from the current working directory of the renderer process. initial is the ASCII printable string which 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.
In the above simple sphere example, an appropriate RIB file statement might be:
Procedural "DynamicLoad" ["a_sphere" "3.0" ] [-3 3 -3 3 -3 3]
In C, the syntax for calling the dynamic load primitive is:
RtString *data; RtBound bound; RiProcedural(data, bound, RiProcDynamicLoad, freedata)data is a pointer to an array of two RtStrings (ie. data is a char **). which 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 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 and its strings would be malloced, and freedata would free data[0], data[1] and data.
Note that when the Subdivide routine wants to create a child procedural primitives of the same type as itself, it should call RiProcedural(data,bound,Subdivide,Free), not RiProcedural(asciidata,bound,RiProcDynamicLoad,RiProcDLFree).
In older versions of PRMan, program and DSO names had to be absolute pathnames, and there was no way to specify a different executable depending on which machine-architecture the RIB file was being rendered on. Since PRMan 3.8, the renderer will expand the string $ARCH or %ARCH in any procedural primitive program and DSO pathnames, converting it into a string that identifies the machine architecture.
The default architecture names are:
linux | Intel x86 (running RedHat Linux) |
NT | Intel x86 (running Windows 2000, XP) |
sgi_m3 | SGI workstation (compiled with -mips3 -n32) |
A significant bug that made procedural primitives significantly inefficient if they crossed the eye plane has been fixed. Now procedural primitives are never opened during the RIB parsing phase, and will never be called recursively due to eyesplits.
RiProcRunProgram used to require that its program ran in a loop, reading input “description” strings and emitting the results until the renderer signalled that there was no more input. It is now significantly more robust, and will handle programs that read exactly one input string, process it and immediately exit. Also, RiProcRunProgram will no longer hang or core dump if the specified program does not exist or cannot be executed.
Prior to subdividing a procedural primitive, the renderer restores the graphics state to the state that existed when the procedural primitive was itself created. The subdivision routine can call any RI routine that is legal within the world scope, so it may modify any attribute in the graphics state machine before generating new primitives (procedural or otherwise). Thus, procedural primitives are free to manipulate the color, shaders, or any other attribute of their children.
Motion blur of procedural primitives is tricky. Procedural primitives, per se, cannot exist within motion blocks, because this would force the renderer to do some sort of correspondence matching between the children of the time 0.0 procedural primitive and the children of the time 1.0 procedural primitive. However, once the procedural primitive has decided to subdivide itself into standard RenderMan primitives, it is completely legal for those primitives to be emitted with full motion blocks. Thus, it is the responsibility of the procedural primitive methods themselves to understand "vertex motion" and generate the appropriate moving primitives at the leaf level. Note, however, that normal transformation matrix motion blur is perfectly legal on procedural primitives.
In situations where the renderer is unable to calculate the correct detail to pass into the subdivide method (for example, if the procedural primitive crosses the camera plane), the subdivision detail will be set to some extremely large number (quite possibly infinity). Subdivision methods must be able handle this case without generating infinite amounts of leaf data.
The RenderMan Interface has a command for modifying the detail which is sent to a procedural primitive. RiRelativeDetail is an attribute which provides a scaling factor for the details sent to the subdivision routine. This is a separate control from the RiShadingRate, which will determine the shading rate of the final "standard" RenderMan primitives that are generated. Unfortunately, there is no way for the procedural primitive to see the shading rate values.
If the bounding box of a child of a procedural primitive extends outside the bounding box of the parent, the resulting image will very likely have dropouts, however the renderer does not generate any warning messages in this case. Be sure that your bounding boxes do in fact contain all the geometry which will be generated at all lower levels.
Pixar Animation Studios
|