What's New in RSLMarch 2007 |
|
Short-circuiting logical operators (in 13.5)
Shader parameter scope and externs (in 13.5)
Class definitions, member variables, and methods (in 13.5)
Illuminance caching control (in 13.5)
Output argument initialization warning (in 13.0.4)
New shader plugin API (in 13.0)
Transform generalized (in 13.0)
Miscellaneous improvements in 13.0
This document summarizes recent changes to the RenderMan shading language. The focus here is generally on core language features and new shadeops. Changes to existing shadeops are generally documented in the release notes, unless they require a detailed description.
Return statements may now appear anywhere in a function, rather than being restricted to the end of the function. For example:
float positive(float x) { if (x > 0) return 1; else return 0; }
Uniform values returned from varying conditionals are promoted to varying. For example, the function above returns a varying value if x is varying.
The logical operators && and || now correctly "short circuit", whereas previously they would evaluate both their arguments. This is crucial for correctness, since logical operators might be used to "guard" potentially unsafe operations:
if (i < arraylength(nums) && nums[i] != 0) { // This used to test nums[i] with an out-of-range index. }
A shader parameter an now be used anywhere inside the body of the shader definition without requiring an extern declaration. (This change was motivated by the addition of member variables, which have similarly broad scope.) To avoid confusion, we recommend that a naming convention be used to distinguish between shader parameters and local variables.
Extern declarations are now required only when a nested function refers to variables defined in an enclosing function (e.g. local variables or function parameters). Extern declarations may still be used even if they're unnecessary (e.g. for documentation purposes).
A resizable array can be obtained by declaring a local array variable with an unspecified or non-constant length. Initialization is optional. For example:
float A[]; // initially empty float B[] = {1,2}; // initial length is 2 float C[1+arraylength(B)]; // initial length is 3
Only arrays declared with an unspecified or non-constant length are resizable. This ensures that the performance of existing code (with fixed-length arrays) will be unaffected. Only arrays in local variables are resizable. Note that the length of a shader parameter can also be unspecified, but its length is fixed when the shader parameter is bound, and it cannot be resized.
All built-in functions that operate on fixed-length arrays can also operate on resizable arrays. For example, arraylength returns the length of a fixed or resizable array. An array can be resized using a new resize function:
Array assignment resizes the destination array as necessary:
float A[]; // initially empty. A = B; // resizes A to accommodate B.
Message passing will also resize the result array to match the length of of the source. For example:
float A[]; surface("A", A); // resizes A if necessary.
Arrays are also resized by the push and pop functions:
A resizable array has both a length and a capacity (similar to an STL vector). Resizing an array increases its capacity if necessary. However, reducing the length does not decrease the capacity (unless it is set to zero, in which case the storage is reclaimed).
The capacity can be increased without changing the length, thus reserving storage for future operations:
Reserving storage is preferable to incrementally resizing an array, since increasing capacity generally requires copying the array contents. This is especially useful when repeatedly pushing items on an array:
reserve(A, arraylength(A)+3); // reallocate A before pushing. push(A, 1); push(A, 2); push(A, 3);
The capacity of an array can be obtained using the following function:
void sum(float nums[]) { // argument can be a fixed or resizable array. }
A shader can now be written as a class definition with member variables and methods. The class definition specifies shader parameters, and the methods accept additional parameters:
class myshader(string mapname="", ...) { varying color Ct = 1.0; public void displacement(output point P; output normal N) { if (mapnamp != "") Ct = texture(mapname, ...); ... } public void surface(output color Ci, Oi) { ... Ci = Ct * ... ; } }
The values of member variables are preserved between method calls, e.g. allowing state to be shared between displacement and surface shading. Uniform member variables can also be preserved across multiple executions of the same shader, allowing expensive calculations to be performed less frequently.
Shaders can call methods and read member variables of other shaders. For example, an illuminance loop can be replaced by a for loop that calls a method in each light shader, and a light shader can call methods in the surface shader. For example, diffuse illumination can be calculated as follows:
shader lights[] = getlights("category", "diffuse"); uniform float i, n = arraylength(lights); for (i = 0; i < n; i += 1) { vector L; color Cl; lights[i]->light(L, Cl); C += Cl * (Nn . normalize(-L)); }
For more information, see the Shader Objects and Co-Shaders application note.
Scene descriptions can now include co-shaders that allow custom shading pipelines to be expressed in the shading language itself. For example, layers of a surface appearance can be expressed as separate shader instances, which are combined by a shader that calls the displacement, opacity, and surface methods of each layer.
Co-shaders are simply shader objects with no explicitly specified purpose; they are not executed directly by the renderer, but rather are called from other shaders. Co-shader objects are created like lights in RIB, by specifying the shader name, a handle name, and any desired parameter values:
Shader "basic" "baselayer" "float Ks" [.5] Shader "rust" "rustlayer" "float Ks" [0]
In the shading language, a co-shader can be obtained from its handle, yielding a shader (which might be null if the handle was not found). Method calls use the obvious syntax:
shader baselayer = getshader("baselayer"); if (baselayer != null) baselayer->surface(Ci, Oi);
The parameters and public member variables of a co-shader can be inspected by other shaders:
float baseKd = baselayer->Kd;
For more information, see the Shader Objects and Co-Shaders application note.
In release 12.5 a "lightcache" argument was added to the illuminance shadeop to allow light caching to be disabled (previously this was accomplished by setting "P = P"):
illuminance(P, ..., "lightcache", "refresh") { ... }
It's now possible to specify illuminance caching behavior on a per-light basis, rather than this kind of all-or-nothing basis. A light shader can define a string parameter called __lightcache. If the value is "refresh", the illuminance shadeop will ignore any previously cached results and re-execute the light; otherwise the usual caching rules apply. This is useful for lights that employ stateful plugins.
Note that the __lightcache parameter is ignored if it equals "reuse"; that does not force cache reuse. Also note that the value of the __lightcache parameter can be specified in an RiLightSource call (or even as a vertex variable).
It is risky to rely on the input value of a shader output parameter. The situation has improved slightly in release 13.0: varying output parameters always have correct input values now, but uniform output parameters sometimes do not. (Fixing this behavior is difficult because the cost of guaranteeing validity is surprisingly high.)
The shader compiler now reports a waring when a shader relies on the input value of a uniform output parameter. The most reliable way to work around this issue is to have the shader explicitly copy the value from an input argument to an output argument.
Alternatively, in 13.5 a member variable can be used instead. Member variable initialization is well defined and easy to control (e.g. using a constant initializer or an assignment in a construct or begin method). For more information, see the Shader Objects and Co-Shaders application note.
Starting in prman 13.0, shader plugins (DSOs) must be thread safe (unless the renderer is run in single-threaded mode). This is facilitated by a new API that provides per-thread storage management, thread-safe global storage, and other useful features.
The new API also provides higher performance by allowing a plugin function to operate on an entire batch of data, rather than being called once for each point shaded. For example, here is a plugin function that computes "a = b + c" for floating-point values:
int add(RslContext* rslContext, int argc, const RslArg** argv) { RslFloatIter a(argv[0]); // an iterator is like a pointer RslFloatIter b(argv[1]); RslFloatIter c(argv[2]); int n = argv[0]->NumValues(); // number of values in this batch for (int i = 0; i < n; ++i) { *a = *b + *c; ++a; ++b; ++c; // advance iterators } return 0; }
The API is described in detail here:
Additional changes include the following:
Use the following option to enable shader profiling:
Option "statistics" "shaderprofile" ["profile.xml"]
Then load the XML file into your Web brower to view the results.
Shader profiling identifies hotspots in shaders, taking the guesswork out of optimization. Shader hotspots include call stacks, allowing a detailed understanding of the context in which heavily used library routines are called. Each hotspot is linked to its source code, simplifying browsing and analysis.
See the Shader Profiling application note for more information.
Comments in shader source code can now specify arbitrary meta data that is recorded in compiled shaders. The meta data can be accessed via the PRMan SDK, and scripts can use sloinfo to fetch meta data as XML.
The syntax is as follows:
/* This is a comment. <meta id="name">data</meta> */
The data is arbitrary; it can span multiple lines in block comments (but not in "//" comments). The identifier is a key that can be used to fetch the meta data. Meta data identifiers can have the same names as identifiers in shader code; they reside in a separate namespace.
For more information, see the Shader Meta Data developer note.
The wavelet noise function has the following prototype:
float wnoise( point pt, float filterWidth, [parameterlist] )
It produces one or more bands of noise constructed with wavelets, and is based on the 2005 SIGGRAPH paper by Cook and DeRose. Wavelet noise has a similar appearance to Perlin noise but can be band-limited more effectively for the sake of antialiasing. Unlike the noise function, which returns values between 0 and 1, wnoise by default returns values between -1 and 1. The distribution and range of these values may be changed using the parameters described in the RenderMan shading language reference.
The RSL transform function has been generalized. It now has the following prototypes, where T = point, vector, normal, or matrix:
T transform([string fromspace,] string tospace, T src); T transform([string fromspace,] matrix m, T src);
The fromspace is optional and defaults to "current". Calling transform on a point works as before. When called with a vector or normal it is equivalent to a vtransform or ntransform call. When called with a matrix, it returns the from/to transformation matrix times the given matrix.
float A[64] = { 0 }; // all zeros
Array initializers can also end with "...", which indicates that the last element should be repeated to fill the remainder of the array:
float B[64] = { 1, 1, 1, 0, ... }; // the last number is repeated.
Pixar
Animation Studios
|