Keywords: main

Next: 3.6 Parameter Assignments and Up: 3. Using and Writing Previous: 3.4 State Variables

# 3.5 Shader Parameter Declarations

In addition to the state variables that are provided by mental ray and are shared by all shaders, every shader has shader parameters. In the .mi scene file, shader references look much like a function call: the shader name is given along with a list of parameters. Every shader call may have a different list of parameters. mental ray does not restrict or predefine the number and types of shader parameters, any kind of information may be passed to the shader. Typical examples for shader parameters are ambient, diffuse, and specular colors for material shaders, attenuation parameters for light shaders, and so on. An empty parameter list in a shader call (as opposed to a shader declaration) has a special meaning; see the note at the end of this chapter.

In this manual, the term `` parameters'' refers to shader parameters in the .mi scene file; the term ``arguments'' is used for arguments to C functions.

Shaders need both state variables and shader parameters. Generally, variables that are computed by mental ray, or whose interpretation is otherwise known to mental ray, and that are useful to different types or instances of shaders are found in state variables. Variables that are specific to a shader, and that may change for each instance of the shader, are found in shader parameters. mental ray does not access or compute shader parameters in any way, it merely passes them from the .mi file to the shader when it is invoked.

To interpret these parameters in the .mi file, mental ray needs a declaration of parameter names and types that is equivalent to the C struct that the shader later uses to access the parameters. The declaration in the .mi file must be exactly equivalent to the C struct, or the shader will mis-interpret the parameter data structure constructed by mental ray. (Using the mkmishader utility ensures that both declarations agree, see section .) This means that three parts are needed to write a shader: the .mi declaration, the C parameter struct, and the C source of the shader. The .mi declaration is normally stored in a separate file that is included into the .mi scene file using a `$include` statement.

The syntax of .mi shader declarations is fully described in section . Here, only a brief overview is given. Shaders must be declared with shader name, return type, and the types and names of all parameters. Options such as the shader version may be specified also:

        declare shader
            [ type ]  "shader_name" (
                type  "parameter_name",
                type  "parameter_name",
                ...
                type  "parameter_name"
            )
            [ version  versionint ]
            [ options ]
        end declare

For example, a simple material shader containing ambient, diffuse, and specular colors, transparency, an optional array of bump map textures, and an array of lights could be declared in the .mi file as:

        declare shader
            color "my_material" (
                color          "ambient",
                color          "diffuse",
                color          "specular",
                scalar         "shiny",
                scalar         "reflect",
                scalar         "transparency",
                scalar         "ior",
                vector texture "bump",
                array light    "lights"
            )
            version 1
        end declare

If there is only one array, there is a small efficiency advantage in listing it last. It is recommended that the largest array (arrays of large structs are larger than arrays of primitives) is given as the last parameter. The material shader declared in this example can be used in a `material` statement like this:

        material "mat1"
            "my_material" (
                "specular" 1.0 1.0 1.0,
                "ambient"  0.3 0.3 0.0,
                "diffuse"  0.7 0.7 0.0,
                "shiny"    50.0,
                "bump"     "tex1",
                "lights"   [ "light1", "light2", "light3" ],
                "reflect"  0.5
            )
        end material

Note that the parameters can be defined in any order that does not have to agree with the order in the declaration, and that parameters can be omitted. Omitted parameters default to 0. This example assumes that the texture `tex1` and the three lights have been defined prior to this material definition. Again, be sure to use the names of the textures and lights, not the names of the texture and light shaders. All names in the above two examples were written as strings enclosed in double quotes to disambiguate names from reserved keywords, and to allow special characters in the names that would otherwise be illegal.

When choosing names, avoid double colons and periods, which have a special meaning when accessing structured shader return values and interface parameters in phenomenon subshaders.

When the shader `my_material` is called, its third argument will be a pointer to a data structure built by mental ray from the declaration and the actual parameters in the .mi file. In order for the C source of the shader to access the parameters, it needs an equivalent declaration in C syntax that must agree exactly with the .mi declaration. The type names can be translated according to the following table:

.mi syntax | C syntax
`boolean` | miBoolean
integer | miInteger
scalar | miScalar
vector | miVector
transform | miMatrix
color | miColor
shader | miTag of a miFunction
color texture | miTag of a miFunction or miImg_image
scalar texture | miTag of a miFunction or miImg_image
vector texture | miTag of a miFunction or miImg_image
light | miTag of a miInstance for a light
material | miTag of a miMaterial
geometry | miTag of a miObject, miGroup, or miInstance
struct | struct
array | int, int, type[1]

It is strongly recommended to use the same parameter names in the C declaration as in the .mi declaration. Also, structures in either declaration should be reflected in the other, even if not enclosed in arrays, to ensure that mental ray inserts the same padding as the C compiler.

Arrays are more complicated than the types in this table because the size of the array is not known at declaration time. The C declaration of an array consists of a start index prefixed with `i_`, the size of the array prefixed with `n_`, and the array itself, declared as an array with one element. mental ray will allocate the structure as large as required by the actual array size at call time. To access array element i in the range 0...n_array - 1, the C expression array[i + i_array] must be used. This expression allows mental ray to store the shader parameters in virtual shared memory regardless of the base address of the shader parameter structure, which is different on every machine on the network.

For the above example .mi declaration, the equivalent C structure declaration using mental ray types looks like this:

        struct my_material {
            miColor  ambient;
            miColor  diffuse;
            miColor  specular;
            miScalar shiny;
            miScalar reflect;
            miScalar transparency;
            miScalar ior;
            miTag    bump;
            int      i_lights;
            int      n_lights;
            miTag    lights[1];
        };

Note that here the order of structure members must match the order in the .mi declaration exactly. For example, suppose a shader has a .mi declaration containing an array of integers:

         declare shader
             color "myshader" ( array integer "list" )
             version 1
         end declare

The C declaration for the shader's parameters is:

         struct myshader {
              int       i_list;
              int       n_list;
              miInteger list[1];
         };

A shader that needs to operate on this array, for example printing all the array elements to stdout, would use a loop like this:

         int *list  =  mi_eval_integer(paras->list);
         int i_list = *mi_eval_integer(&paras->i_list);
         int n_list = *mi_eval_integer(&paras->n_list);
         int i;
         for (i=0; i < n_list; i++)
              mi_info("%d", list[i_list + i]);

assuming that `paras` is the third shader argument and has type `struct myshader *`. The use of the `i_list` parameter may seem strange to C programmers, who may wish to hide it in a macro like

         #define EL(array,nel) array[i_##array + nel]

This macro requires an ANSI C preprocessor; K&R preprocessors do not support the `##` notation and should use `/**/` instead. This macro is not predefined in shader.h. The reason for this peculiar way of accessing arrays is improved performance. The array `list[1]` has space for only one element, because the actual number of array elements depends on the shader instance in the .mi file, which may list an arbitrary number of elements. Since mental ray is based on a virtual shared database that moves pieces of data such as shader parameters transparently from one machine to another, no such piece of data may contain a pointer. Pointers would not be valid in another machine's virtual address space. Adjusting the pointer on the other machine is impractical because it would significantly reduce performance for some scenes, and would require knowledge of the structure layout for finding the pointers that may not be available in versions of mental ray not based on a .mi front-end parser. Therefore, the array is appended to the parameter structure, so the entire block can be moved to another machine in a single network transfer. It is safe to access the first element of the array, because space for it is always allocated by declarations such as `list[1]`, but the second is a problem because in a C declaration like

         struct myshader {
              int       i_list;
              int       n_list;
              miInteger list[1];
              miScalar  factor;
              miBoolean bool;
         };

the second element, `list[1]`, occupies the same address as `factor`, and the third overlays `bool`. The situation becomes more complex for arrays of structures. The solution is to put the value of the first element after the last ``regular'' shader parameter, `bool` in this example, followed by the other element values. This means that the first few C array elements that overlay other parameters must be skipped. The `i_` variable tells the shader exactly how many. In the example, `i_list` would be 3. Assuming the following shader instance, used as part of a material, texture, or some other definition requiring a shader call:

         "myshader" (
              "factor" 1.4142136,
              "list"   [ 42, 123, 486921, 777 ],
              "bool"   on
         )

mental ray would arrange the values in memory like this:

On multithreaded machines, it is possible that any given shader runs in multiple threads simultaneously. When this happens, they share all static variables defined in the sources, all shader parameters, and the user pointer. The only things that are separate in separate threads are ``auto'' variables on the stack, and the state variables. Also, mental ray makes sure that an init shader runs once, in a single thread, before the actual shader runs in one or more threads. This makes init shaders the natural place to set up the shader user pointer, perhaps to store preprocessed shader parameters. See section for more information on multithreaded shader design.

Shaders should never write to shader parameters because they might be accessed by other threads or hosts or by future frames, and because shader and interface assignments might exist that would be destroyed.

Next: 3.6 Parameter Assignments and Up: 3. Using and Writing Previous: 3.4 State Variables