Automatic Source Generation with mkmishader

In order to write a new shader, a number of prototypes and other mechanical work must be done, such as writing prototypes, evaluation of parameters, versioning, and so on. The mkmishader utility can do this automatically. For example, the mib_illum_lambert shader in the base shader library has the following .mi declaration:

     declare shader
         color "mib_illum_lambert" (
             color              "ambience",
             color              "ambient",
             color              "diffuse",
             integer            "mode",
             array light        "lights"
         )
         version 2
     end declare

If this declaration is stored in a file, and the mkmishader utility is run with the -i option (which creates init and exit shaders), a new file mib_illum_lambert.c is created with the following content:

     #include <stdio.h>
     #include <math.h>
     #include <shader.h>

     typedef struct {
        miColor         ambience;
        miColor         ambient;
        miColor         diffuse;
        miInteger       mode;
        int             i_lights;
        int             n_lights;
        miTag           lights[1];
     } mib_illum_lambert_t;

     DLLEXPORT int mib_illum_lambert_version(void) {return(2);}

     DLLEXPORT miBoolean mib_illum_lambert_init(
        miState             *state,
        mib_illum_lambert_t *param,
        miBoolean           *init_req)
     {
        if (!param) {
                /* shader init */
                *init_req = miTRUE; /* do instance inits */
        } else {
                /* shader instance init */
        }
        return(miTRUE);
     }

     DLLEXPORT miBoolean mib_illum_lambert_exit(
        miState             *state,
        mib_illum_lambert_t *param)
     {
        if (param) {
                /* shader instance exit */
        } else {
                /* shader exit */
        }
        return(miTRUE);
     }

     DLLEXPORT miBoolean mib_illum_lambert(
        miColor             *result,
        miState             *state,
        mib_illum_lambert_t *param)
     {
        /*
         * get parameter values. It is inefficient to do this all
         * at the beginning of the code. Move the assignments here
         * to where the values are first used. You may want to use
         * pointers for colors and vectors.
         */
        miColor ambience = *mi_eval_color(&param->ambience);
        miColor ambient = *mi_eval_color(&param->ambient);
        miColor diffuse = *mi_eval_color(&param->diffuse);
        miInteger mode = *mi_eval_integer(&param->mode);
        int i_lights = *mi_eval_integer(&param->i_lights);
        int n_lights = *mi_eval_integer(&param->n_lights);
        struct lights_s *lights = (struct lights_s *)mi_eval(
                                        state, param->lights);
        /*
         * sample array loops
         */
        int i;
        for (i=0; i < n_lights; i++) {
                /* use lights[i_lights + i]; */
        }

        /*
         * set shader results. "+=" etc. is useful for shaders
         * in shader lists but other shaders may need to simply
         * assign result variables.
         */
        result->r += 0.0;
        result->g += 0.0;
        result->b += 0.0;
        result->a += 0.0;
        return(miTRUE);
     }

This skeleton source code can then be filled in with the implementation of the actual algorithm. It is important to move the mi_eval functions to the places where they are first used because if the shader does not need a variable it is faster to not access it. For example, if the light array lights is empty, so n_lights is zero, then there is no need to access i_light, lights, and diffuse. If those parameters are assigned to other shaders that would have to be called on access, not calling mi_eval is a significant performance improvement. The functions used in this skeleton will be described later.

Copyright © 1986-2008 by mental images GmbH