Hardcore Processing - homepage

DESIGN RATIONALE FOR THE STANDARD ML BINDINGS OF THE RENDERMAN INTERFACE (RI::ML)


Welcome | Company profile | News | Products | Freeware | Researchlabs | Knowledge | Entertainment | Private

Introduction

This document gives a design rationale for the Standard ML bindings of the RenderMan Interface (a.k.a. RI::ML). These Standard ML RenderMan bindings tries to conform to the "RenderMan Interface Specification version 3.2". However, during the creation of these bindings, information from the "RenderMan Companion" and "Advanced RenderMan: Creating CGI for motion pictures" has also been used in an attempt to make things as correct as possible.

Organization of modules in the ML RenderMan Interface

The RenderMan Interface in ML is divided into 4 different invariant structures which should not change across RenderMan implementations in ML and which are all instantiated from functors. Each RenderMan implementation in ML will also have to create it's own 5th structure to implement the interface. The 4 invariant structures are the following:

The 5th structure that RenderMan implementations will have to implement is the Ri structure. This structure must conform to the signature called RENDERMAN_INTERFACE which also includes all the functions already implemented in the structure RIFCs. A structure called RIB which implements the RENDERMAN_INTERFACE signature is included with the RenderMan ML bindings. The structure RIB is basically a RIB file generator. It is actually a good idea to not call a structure implementing the RENDERMAN_INTERFACE signature Ri. There are 2 main reasons for this:

  1. It allows multiple RenderMan implementations to coexsist in the same ML program.
  2. It is always a good idea to encapsulate ML code into a functor and it is recommended that functors using the RenderMan Interace are parametrized with a structure called Ri conforming to the RENDERMAN_INTERFACE signature. See the file RiExample.sml for an example of this.

real vs. Rt.Real.real etc.

One of the design decisions of RI::ML which have been quite hard is how to deal with the precision of integers and reals in ML's safe typesystem. There are a few options:

I believe that using real and int directly is The Right Thing (TM) for the following reasons:

However, I'll agree that it still might be worthwhile to consider Rt.Real and Rt.Int, but I think using Real32 and Int32 is out of the question.

RtBoolean, RtVoid, RtString and RtPointer

I don't believe that it is worth encapsulating the RenderMan types RtBoolean, RtVoid and RtString in ML. The types bool, unit and string are part of Standard ML - with an emphasis on Standard. The RtPointer type is an ugly hack compromising the type system completely. It is mostly needed in C because of the lack of some of ML's language features - such as parametrized types (i.e. ML's 'a types .) and sum types with constructors (i.e. ML's datatype language construct).

RtColor, RtMatrix and RtBasis

RtColor is called Rt.Color in ML and is defined as RealArray.array. If an ML compiler does not have a RealArray module, one can easily be defined by using real Array.array as done in the CompatibilityLayer project found on this homepage. The reason for using an array is for allowing indexing and updating individual components in a simple way as (fast) constant time operations. Using a real list has been considered and would surely be easier sometimes, but it does not seem natural when accessing individual color components. Also remember that colors in RenderMan can have an arbitrary number of color components (set by the RiColorSamples option), so just using a 3-tuple or a record with r, g and b fields is not good enough. RtMatrix and RtBasis are always 4x4 matrices in RenderMan, so it has been defined as a 4-tuple of 4-tuples of reals. Using a real array would have been easier to index and modify. I'm not sure what would be most efficient though, but my quess is that arrays could be more efficient for hardware accelerated matrix transformations and that the current tuple types could be more efficient for software implementations of matrix transformations. FIXME: But maybe it should be an array...

RtToken

In the ML bindings the Token type has been made opaque. This means that to convert from a string to a token, one is required to call Rt.MakeToken. Rt.TokenString converts the other way. To test if 2 tokens are the same, use Rt.EqualTokens. This will just compare 2 integers rather than 2 strings. There is also a function called Rt.TokenInt which returns an associated integer from a token. This integer is what gives RenderMan implementors in ML the ability to quickly test the value of a token. This is also possible by comparing addresses of built-in tokens in C. The approach in C is an ugly hack though and it is not possible to do in ML beacause of ML's typesafety. The approach taken in ML with an opaque token type and a function for getting access to an associated integer actually seems more "correct". It even allows one to create an array based dispatch-table for tokens, so that a real time RenderMan renderer becomes feasable to implement. In ML, one would also be able to compare 2 tokens efficiently if the tokens had been implemented by making a reference to the string or by making a unit ref. However this will not give the ability to implement the array based dispatch-table mentioned before. Lastly it should be noted that the function Rt.MakeToken takes a little time to execute - so it might not be the best idea to call it every frame in a real time animation.

RiObjectBegin, RiLightSource and RiAreaLightSource

In the ML bindings the function Ri.ObjectBegin has got an extra parameter of type Rt.ObjectHandle option. This means that either no object handle is passed to the function (by passing the value NONE) or that an object handle h is passed (by passing the value SOME(h)). If a handle is supplied to the function, then the handle must have been returned earlier by a call to Ri.ObjectBegin. This is actually somewhere in between the RenderMan Specification for the C bindings and the RIB bindings. In the C bindings there is no handle supplied to these functions and in the RIB bindings a handle is required.

The semantics in the ML bindings are that if no handle is supplied (NONE) then the RenderMan implementation creates a new handle (and writes it to the RIB file in case of a RIB generator). If a handle is supplied (SOME(h)) then the implementation is expected to use the handle given by erasing the object which previously used that object handle. This is the same as is done for Ri.ObjectBegin in RIB files. Ri.LightSource and Ri.AreaLightSource works similarly, except that they take a Rt.LightHandle option as argument and return a Rt.LightHandle. However the RenderMan specification does not mention that this is legal to replace a light with some given light handle. So, strictly speaking this is a violation of the RenderMan specification... but it seems like a good way of doing things. Also, since handle numbers must be in the range [0, 65535] it will also make it easier to assert that one doesn't run out of handles because they can be reused. I believe this will be important when one is to implement for instance a realtime RenderMan compliant 3D game engine where many frames are rendered, and new light sources could be introduced often.

Functions passed as arguments

The function types RtFilterFunc, RtErrorHandler, RtProcSubdivFunc, RtProcFreeFunc and RtAchiceCallback has been changed into being a pair of a function and a name. This means that the name of any function can be passed into a RIB file. It can also be used to give certain functions (like the builtin ones) heavily optimized implementations. Part of the reason why this name is needed is that ML does not allow function eqiuvalence testing.

In C one can compare functions because it is really pointers to functions. This can could be done in ML too by creating references to functions. However, just as in C, this approach will not work if a function is given a new reference. Also, using references to functions in ML without an associated name won't allow the use of new function names in RIB files, which might be supported by some RenderMan renderers.

Things which have been removed in the ML bindings compared to the C bindings

Tuples vs. curried function parameters

Reasons to use tuples instead of curried arguments:

However, functions with a parameter list at the end might be changed so that they just take a parameter list in ML to give these functions a more uniform interface - but this would still give the problems described above. It even means that mandatory arguments can be omitted when calling the functions!

lists vs. arrays vs. vectors

I have still not decided whether to use lists, vectors or arrays in ML for the RenderMan Interface. I only have a few statements which I believe to be correct, but I've not examined any of it thoroughly, so it might be wrong:

Functions which differ in ML from the C functions




- Ánoq of the Sun


Modified: 2000-11-03
E-mail:anoq@HardcoreProcessing.com