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:
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
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
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
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
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: