home << prev next >> contents  


Initialization and Cleanup

mental ray provides a way to define initialization and cleanup functions for each user defined function. Many shaders need to perform operations such as initializing color tables or allocating arrays before they are called for the first time. They may also need to do cleanup operations after rendering has finished, for operations like releasing storage to prevent memory leaks.

Before a shader is called for the first time, ray checks if a function of the same name with _init appended exists. If so, it assumes that this is an initialization function (also called init shader ) and calls it once before the first call of the shader. The state passed to the initialization function is the same as passed to the first call of the actual shader to be initialized. Note that the order of shader calls is unpredictable because the order of pixel samples is unpredictable, so the initialization function should not rely on sample-specific state variables such as state→point. It may also not perform any operations that cause other shaders to be called, such as tracing a ray.

The initialization function has the option of requesting shader instance initializations by setting the boolean variable that its third argument points to to miTRUE. A shader instance is a unique pair of shader and shader parameters. For example, if the shader mib_illum_phong is used in two different materials it is said to have two different instances (even if the parameter values happen to be equal). When an init shader is called as part of the shader instance initialization, it receives the same parameters that the main shader will receive. As always, shaders may not write back into their shader parameters because that confuses other threads or hosts that access the shader simultaneously, because it changes the scene for the next frame, and because a shader or interface assignment may get mangled.

When a rendering phase has finished, mental ray checks for each user provided shader that was called whether a function of the same name with _exit appended exists. If yes, it assumes that this is a cleanup function and calls it once. For example, if a shader myshader exists, the functions myshader_init and myshader_exit are called for initialization and cleanup if they exist. The exact time the exit function is called depends on the rendering phase: geometry shaders are exited when preprocessing has finished; most other shaders are exited when the frame has finished on the master host or after each rectangle has finished on slave host (slaves do not manage entire frames, they contribute individual rectangles only). Exit functions cannot be used to detect end of rendering, use an output shader or a call statement for that.

The functions are assumed to have the following type:

     void myshader_init(miState   *state,
                        void      *paras,
                        miBoolean *inst_init_req);

     void myshader_exit(miState   *state,
                        void      *paras);

The -i option of the mkmishader utility automatically creates init and exit shader prototypes. Here is an example for init and exit shaders for a shader named myshader. When myshader is about to be used for the first time in a frame, the calling order is:

  1. myshader_init with a null paras argument
  2. myshader_init with non-null paras argument
  3. myshader itself with the same non-null paras argument
  4. more calls to myshader with the same paras argument, and calls to other instances of myshader_init and myshader with different paras arguments,
  5. one mi_shader_exit with a non-null paras argument for each corresponding myshader_init
  6. finally one myshader_exit with a null paras argument.

Steps 2 and 5 would have been omitted if myshader_init in step 1 had not set its third argument inst_req to miTRUE. Two different instances of the same shader always have different paras argument pointers. However, a shadow or photon shader and a material shader in the same material may share parameters as described above; in this case both shaders are called with the same paras argument. Most scenes are built this way, it is recommended to write material shaders so they can also be used as shadow and photon shaders to simplify scene construction.

     DLLEXPORT void myshader_init(   /* must end with "_init" */
         miState         *state,
         struct myshader *paras,     /* valid for inst inits */
         miBoolean       *inst_req)  /* for inst init request */
     {
         if (!paras) {               /* main shader init */
             *inst_req = miTRUE;     /* want inst inits too */
             ...
         } else {                    /* shader instance init */
                                     /* just an example: */
             void **user;
             mi_query(miQ_FUNC_USERPTR, state, 0, &user);
             *user = mi_mem_allocate(...);
             ...
         }
     }

     DLLEXPORT void myshader_exit(   /* must end with "_exit" */
         miState         *state,
         struct myshader *paras)     /* valid for inst inits */
     {
         if (!paras) {               /* main shader exit */
             ...                     /* no further inst exits
                                      * will occur */
         } else {                    /* shader instance exit */
                                     /* just an example: */
             void **user;                           
             mi_query(miQ_FUNC_USERPTR, state, 0, &user);
             mi_mem_release(*user);
             ...
         }
     }

Note that there will generally be many instance init/exits (if enabled), but only one shader init/exit. If an init/exit shader is not available, it is not called; this is not an error. Initialization and cleanup are done on every host where the function was used, but only once on shared memory parallel machines. They are done for each frame separately. Init and exit shaders will never run concurrently with each other or the actual shader on shared-memory multiprocessor machines; mental ray takes care of the appropriate locking using the shader lock. For this reason, init and exit shaders should never lock the shader lock (obtainable with the miQ_FUNC_LOCK mode of the mi_query function) because that would deadlock mental ray.

Note that mental ray 2.x had a state variable that contained the user pointer user above, with no need for calling mi_query. This variable no longer exists in mental ray 3.x because the dataflow architecture of mental ray 3.0 cannot permit shaders to write back into the scene. Such a write might be lost at any time if mental ray decides to temporarily drop elements from its geometry cache. The mi_query method is recommended because it works with both 2.1 and 3.0. This is one of the two main incompatibilities between mental ray 2.1 and 3.0. (The other is that the number of threads is no longer fixed.)

home << prev next >> contents  


Copyright © 1986-2006 by mental images GmbH