Automatically generating code for a new SPU

This page will describe the creation of a more complicated SPU -- the "seethrough" SPU. This SPU will force everything to draw transparently. The alpha setting for every color will be configurable, along with the blend mode. Unlike the invert SPU, this SPU will not omit any of the relevant functions. Instead, it will use a Python script to automatically generate the code.

Step 1: Creating the SPU directory

This step is the same as the first step in the invert SPU walkthrough. Simply create a copy of the template SPU and run the provided script:

cp -r template seethrough
cd seethrough
python gen_template.py seethrough

Step 2: List the functions you will be implementing

The provided helper libraries for auto-generating OpenGL code have efficient routines for accessing lists of functions in external files that end in "_special" (note the underscore). For this example, we'll create a file called seethrough_special that will list every function we plan to implement.

Create a new file spu/seethrough/seethrough_special.  We're going to need to override all of the glColor functions, as well as the glMaterialfv and glMaterialiv functions. In addition, we will need our own implementation of glDisable (to prevent the user from turning off blending), glEnable (to prevent the user from enabling the depth test), and glBlendFunc (to prevent the user from messing with our chosen blend function).  MakeCurrent is needed in order to override default OpenGL state. Therefore, add the following lines to the new "special" file (they don't need to be in alphabetical order):

BlendFunc
Color3b
Color3bv
Color3d
Color3dv
Color3f
Color3fv
Color3i
Color3iv
Color3s
Color3sv
color3ub
Color3ubv
Color3ui
Color3uiv
Color3us
Color3usv
Color4b
Color4bv
Color4d
Color4dv
Color4f
Color4fv
Color4i
Color4iv
Color4s
Color4sv
Color4ub
Color4ubv
Color4ui
Color4uiv
Color4us
Color4usv
Disable
Enable
Materialfv
Materialiv
MakeCurrent

Step 3: Auto-generate seethroughspu.c

This step is fairly involved.  If you're having trouble following along, you can always check out the completed SeeThrough SPU (although see step 13 for one required edit to a file outside the spu/seethrough/ directory).

For this example, we're going to automatically generate the named dispatch table contained in seethroughspu.c. Create a file called seethrough.py that will be used to generate the code.

At the top of the file, put the lines:

import sys, os, cPickle, string, re
sys.path.append( "../../glapi_parser" )
import apiutil

The apiutil module provides functions for getting information about all the OpenGL API functions, as well as related utilities.

Although we won't need to in this example, we can easily get a list of all OpenGL functions in a sorted list with the command:

keys = apiutil.GetDispatchedFunctions("../../glapi_parser/APIspec.txt")

Many of the code generating scripts throughout Chromium will iterate over this list of keys.

Now we print out the header code of seethroughspu.c. in our case, this will be identical to the existing seethroughspu.c (created in step 1), so just pull the first 6 lines of that file into our script, wrapped in a Python print statement:

print """
#include <stdio.h>
#include "cr_spu.h"
#include "chromium.h"
#include "cr_string.h"
#include "seethroughspu.h"

SeethroughSPU seethrough_spu;
"""

In Python, strings enclosed in triple quotes ("""""") can have embedded newlines in them. Notice the include of "chromium.h" -- this is a wrapper for the system's OpenGL header, required on Windows (since <windows.h> has to be included before <GL/gl.h> will work on Windows). Also, we've added "cr_string.h", which will be used when building the named function table.

Because there are so many glColor calls, we'll auto-generate the code for those, and hand-code the few remaining functions. We're going to want a typical color function to look like:

void SEETHROUGHSPU_APIENTRY seethroughColor3f( GLfloat r, GLfloat g, GLfloat b )
{
    seethrough_spu.super.Color4f( r, g, b, seethrough_spu.opacityf );
}

Notice the variable "seethrough_spu.opacityf" -- we'll assume that the configuration routine has pre-computed the user-specified opacity in all the various types that we will need -- float, byte, short, double, and their unsigned variants. To generate the 32 glColor calls, we will loop over the legal types and legal component numbers (in this case, 3 and 4).

Add the following code to the python script:

for type in ['b', 'd', 'f', 'i', 's', 'ub', 'ui', 'us']:
    for components in [3, 4]:
        func_name = 'Color%d%s' % (components, type)
        return_type = apiutil.ReturnType(func_name)
        params = apiutil.Parameters(func_name)
        print 'void SEETHROUGHSPU_APIENTRY seethrough%s(%s)' % (func_name,
                apiutil.MakeDeclarationString(params) )
        print '{'
        print '}'
        print ''

        func_name = 'Color%d%sv' % (components, type)
        return_type = apiutil.ReturnType(func_name)
        params = apiutil.Parameters(func_name)
        print 'void SEETHROUGHSPU_APIENTRY seethrough%s(%s)' % (func_name,
                apiutil.MakeDeclarationString(params) )
        print '{'
        print '}'
        print ''

This code will generate empty function bodies for all the glColor functions that we want to implement, alternating between the parameter-passing (e.g., glColor3f) and vector (e.g., glColor3fv) varieties. Notice that the apiutil module has given us the return type of our function, as well as a list of argument names and argument types. The apiutil library uses this information to build the argument list for the function definition.

The easiest way to implement these functions is to have the apiutil library build the SuperSPU call string for you, just like it built the argument declarations. To do this, all we need to have is a Python array that contains the names of the arguments we want to pass. For the parameter-passing glColor functions, we either append the name of the opacity variable to the arg_names array (if there are three components), or we modify the fourth parameter name (if there are four components).

So, for the parameter-passing code (i.e., between the first printed pair of curly braces), add the following code, taking extra care to make sure that the indentation matches with the surrounding code (i.e., this code should be indented two block levels):

# Modify params list to add/change the fourth parameter so it is the opacity
if components == 3:
    tuple = (('seethrough_spu.opacity%s' % type), "notype", "0")
    params.append( tuple )
else:
    print '\t(void) %s;' % params[3][0]
    tuple = ( ('seethrough_spu.opacity%s' % type), params[3][1], params[3][2])
    params[3] = tuple

new_func_name = 'Color4%s' % type
print '\tseethrough_spu.super.%s(%s);' % (new_func_name, apiutil.MakeCallString( params ) )

Now we have half of our glColor functions working. The implementation for the vector passing functions is even easier. Just add this code:

    new_func_name = 'Color4%s' % type
    print '\tseethrough_spu.super.%s(v[0], v[1], v[2], seethrough_spu.opacity%s);' % (new_func_name, type)

Notice that in both cases, we have dispatched to the 4-parameter version of the glColor functions. Also note that if we are implementing a glColor that itself has four parameters, we need to insert a bogus reference to the fourth (alpha) parameter to avoid "unused variable" warnings from the compiler.

Now that all of our color functions are implemented, let's generate the named function table. To do this, we're going to use a method of apiutil called "AllSpecials", which returns an the array of function names in a given "_special" file. There is also a "FindSpecial" predicate function that tells whether a given function is contained in a "_special" file or not.

Finally, let's make the named function table. Unfortunately, since we're not implementing all of our functions in seethroughspu.c, we can't just create a statically initialized table (since the four extern'ed function pointers aren't compile-time constants). So we have to build the function programmatically, which is just slightly harder. First, let's declare the table. Since we're going to fill it ourselves, we know how big it is. Don't forget to leave space at the end for the NULL terminator!

print 'SPUNamedFunctionTable _cr_seethrough_table[%d];' % ( len(apiutil.AllSpecials( "seethrough" )) + 1 )

I like to use a helper function called "__fillin" for this table-building task. We'll need to print it out at the bottom of this file:

print """
static void __fillin( int offset, char *name, SPUGenericFunction func )
{
    _cr_seethrough_table[offset].name = crStrdup( name );
    _cr_seethrough_table[offset].fn = func;
}
"""

Now, we're finally ready to build our named function table.

print 'void seethroughspuBuildFunctionTable( void )'
print '{'
offset = 0
for func_name in apiutil.AllSpecials( "seethrough" ):
    print '\t__fillin( %d, "%s", (SPUGenericFunction) seethrough%s );' % (offset, func_name, func_name )
    offset += 1
print '\t__fillin( %d, NULL, (SPUGenericFunction) NULL );' % offset
print '}'

So that we can call this function from seethroughSPUInit, add a declaration for it to seethroughspu.h:

void seethroughspuBuildFunctionTable( void );

You need to now run that python script to generate the file seethroughspu.c. Type the following command "python seethrough.py > seethroughspu.c".

Step 4: Take a break

That was brutal. Go have a good glass of wine. When you get back, let's make sure that the initialization step for the SPU actually builds the function table before we return it. Add a call to the function defined in step 3 at the bottom of the seethroughSPUInit function:

seethroughspuBuildFunctionTable();

Step 5: Declare and initialize the opacity variables

Let's stop implementing functions for a moment and go deal with all of these opacity variables that are floating around.

Add the following declarations to the SeethroughSPU structure in seethroughspu.h:

    GLbyte opacityb;
    GLdouble opacityd;
    GLfloat opacityf;
    GLint opacityi;
    GLshort opacitys;
    GLubyte opacityub;
    GLuint opacityui;
    GLushort opacityus;

Once you do this, the project should compile without warnings, although it won't link because we still haven't implemented all the functions.

In order to initialize these variables, we need to get some configuration information, so we need to talk to the mothership!

Open the file seethroughspu_config.c. You should see a comment near the bottom that says "CONFIGURATION STUFF HERE". This is where we will be asking the mothership questions. If you read the code in this file, you'll see that the template SPU doesn't consider inability to contact the mothership fatal. Some SPU's (such as the tilesort SPU) need to talk to the mothership, and can't run if they don't. In our case, the template SPU's behavior is correct.

We'll make the design decision that opacity will be specified to the mothership as a floating point value between 0.0 and 1.0. Let's add a function to convert such a floating point value to all the desired types. Add the following function to the top of seethroughspu_config.c:

static void setOpacity( GLfloat o )
{
    if (o < 0.0) o = 0.0;
    if (0 > 1.0) o = 1.0;
    seethrough_spu.opacityb = (GLbyte) (o * CR_MAXBYTE);
    seethrough_spu.opacityd = (GLdouble) (o);
    seethrough_spu.opacityf = (GLfloat) (o);
    seethrough_spu.opacityi = (GLint) (o * CR_MAXINT);
    seethrough_spu.opacitys = (GLshort) (o * CR_MAXSHORT);
    seethrough_spu.opacityub = (GLubyte) (o * CR_MAXUBYTE);
    seethrough_spu.opacityui = (GLuint) (o * CR_MAXUINT);
    seethrough_spu.opacityus = (GLushort) (o * CR_MAXUSHORT);
}

The GL_MAX* constants are defined in include/state/cr_statetypes.h, so let's add an include line to the top of the file:

#include "state/cr_statetypes.h"

Before we go asking the mothership anything, let's set up some reasonable defaults. In the "setDefaults" function at the top of this file, add the following initialization code:

setOpacity( 0.5 );

Now, we're ready to query the mothership. There's almost nothing to it! Add the following line to the table seethroughSPUOptions[] leaving the existing line (with the NULL values) as the last line:

    { "opacity", CR_FLOAT, 1, ".5", ".0", "1.0", "Opacity", (SPUOptionCB)setOpacityCB },

and just above that table add the following function which we just registered in as a callback :

static void setOpacityCB( void *foo, const char *response )
{
    float o;
    sscanf( response, "%f", &(o) );
    setOpacity(o);
}

We're done with this for now, although we will return to configuration in a few steps to get the user-specified blend mode.

Step 6: Implement the remaining functions

Okay, back to seeing through things. There are four functions left to do: glMaterialfv, glMaterialiv, glBlendFunc, and glDisable. We'll present the material functions first. They're totally straightforward, so they're presented without comment. Add these functions to a new file called seethrough_misc.c:

#include "seethroughspu.h"
#include "chromium.h"
#include "cr_error.h"

void SEETHROUGHSPU_APIENTRY seethroughMaterialfv( GLenum face, GLenum mode, const GLfloat *param )
{
    GLfloat local_param[4];
    if (mode == GL_SHININESS)
    {
        /* nothing to do */
        seethrough_spu.super.Materialfv( face, mode, param );
    }
    else
    {
        local_param[0] = param[0];
        local_param[1] = param[1];
        local_param[2] = param[2];
        local_param[3] = seethrough_spu.opacityf;
        seethrough_spu.super.Materialfv( face, mode, local_param );
    }
}

void SEETHROUGHSPU_APIENTRY seethroughMaterialiv( GLenum face, GLenum mode, const GLint *param )
{
    GLint local_param[4];
    if (mode == GL_SHININESS)
    {
        seethrough_spu.super.Materialiv( face, mode, param );
    }
    else
    {
        local_param[0] = param[0];
        local_param[1] = param[1];
        local_param[2] = param[2];
        local_param[3] = seethrough_spu.opacityi;
        seethrough_spu.super.Materialiv( face, mode, local_param );
    }
}

Let's do glDisable next. The idea here is to prevent the user from ever turning off blending. We just look at the enum, and if it's GL_BLEND, we drop the command on the floor. To be polite about it, we'll print a warning to the screen when this happens.

void SEETHROUGHSPU_APIENTRY seethroughDisable( GLenum cap )
{
    if (cap == GL_BLEND)
    {
        crWarning( "SeeThroughSPU: Ignoring disable of blending!" );
    }
    else
    {
        seethrough_spu.super.Disable( cap );
    }
}

glEnable (for preventing the depth test from getting turned on) is almost identical:

void SEETHROUGHSPU_APIENTRY seethroughEnable( GLenum cap )
{
    if (cap == GL_DEPTH_TEST)
    {
        crWarning( "SeeThroughSPU: Ignoring enable of depth!" );
    }
    else
    {
        seethrough_spu.super.Enable( cap );
    }
}

Finally, the simplest one of them all: glBlendFunc. If the user tries to change the blend function, he's out of luck -- our SPU is in charge of blending. We just ignore all glBlendFunc calls, making sure not to get compiler warnings from unused variables. Note that if you're following along in the completed implementation, this function actually doesn't exist, as explained in step 12.

void SEETHROUGHSPU_APIENTRY seethroughBlendFunc( GLenum sfactor, GLenum dfactor )
{
    crWarning( "SeeThroughSPU: Ignoring setting of blend function!" );
    (void) sfactor;
    (void) dfactor;
}

Finally, add seethroughspu_misc.c add to Makefile. Add seethroughspu_misc to the list FILES.

Step 7: Configure and initialize the blend function

We're almost there. We want to allow the user to set the blend function to be used. Declare the following two variables in the SeethroughSPU structure in seethroughspu.h:

GLenum sfactor, dfactor;

Now, let's return to seethroughspu_config.c and set a defaultv alue. In setDefaults, add the code:

    seethrough_spu.sfactor = GL_SRC_ALPHA;
    seethrough_spu.dfactor = GL_ONE_MINUS_SRC_ALPHA;

Now that everything has reasonable defaults, let's get values from the mothership. Add the following two lines to the array seethroughSPUOptions[]:

    { "sfactor", CR_STRING, 1, "GL_SRC_ALPHA", NULL, NULL,
      "sFactor", (SPUOptionCB)setSFactorCB },
    { "dfactor", CR_STRING, 1, "GL_ONE_MINUS_SRC_ALPHA", NULL, NULL,
      "dFactor", (SPUOptionCB)setDFactorCB },

and add the following two callback functions above the table

static void setSFactorCB( void *foo, const char *response )
{
    (void) foo;
    setBlendFuncFactor( response, &seethrough_spu.sfactor );
}

static void setDFactorCB( void *foo, const char *response )
{
    (void) foo;
    setBlendFuncFactor( response, &seethrough_spu.dfactor );
}

Since the mothership is going to return us a string, we need to turn it back into a GLenum type, so implement the setBlendFuncFactor function at the top of this file:

static void setBlendFuncFactor( const char *str, GLenum *factor )
{
#define BLEND_FUNC_COMPARE( s ) if (!crStrcmp( str, #s )) *factor = s
    BLEND_FUNC_COMPARE( GL_ZERO );
    BLEND_FUNC_COMPARE( GL_ONE );
    BLEND_FUNC_COMPARE( GL_DST_COLOR );
    BLEND_FUNC_COMPARE( GL_ONE_MINUS_DST_COLOR );
    BLEND_FUNC_COMPARE( GL_SRC_ALPHA );
    BLEND_FUNC_COMPARE( GL_ONE_MINUS_SRC_ALPHA );
    BLEND_FUNC_COMPARE( GL_DST_ALPHA );
    BLEND_FUNC_COMPARE( GL_ONE_MINUS_DST_ALPHA );
    BLEND_FUNC_COMPARE( GL_SRC_ALPHA_SATURATE );
    BLEND_FUNC_COMPARE( GL_SRC_COLOR );
    BLEND_FUNC_COMPARE( GL_ONE_MINUS_SRC_COLOR );
#undef BLEND_FUNC_COMPARE
}

There is a small robustness problem here -- the legal values for source and destination blending factors are slightly different, but no distinction is made between them here.

Step 8: Set up and turn on blending before rendering

Before any rendering, we need to enable blending and set the blend function. This is analogous to setting the default color in the Invert SPU. To do this we'll override the MakeCurrent function. Add the following to seethroughspu_misc.c:

void SEETHROUGHSPU_APIENTRY seethroughMakeCurrent(GLint crWindow, GLint nativeWindow, GLint ctx)
{
    seethrough_spu.super.Enable( GL_BLEND );
    sethrough_spu.super.BlendFunc( seethrough_spu.sfactor, seethrough_spu.dfactor );
    seethrough_spu.super.MakeCurrent( crWindow, nativeWindow, ctx);
}

Note that we do not have to disable the depth test at this point, as it is off by default, and our implementaton of glEnable will prevent it from ever getting turned on.

Step 9: Claim our inheritance

Don't forget, this SPU is a SubSPU of the PassThrough SPU. Change the second line in SPULoad in seethroughspu_init.c to:

*super = "passthrough";

Step 9.5: Prototype generation

To avoid compilation warnings, all non-static functions must be prototyped. The following python script is used to generate the seethroughspu_proto.h file:

# This is seethrough_proto.py
import sys
sys.path.append( "../../glapi_parser" )
import apiutil

apiutil.CopyrightC()

print """
/* DO NOT EDIT - THIS FILE AUTOMATICALLY GENERATED BY seethrough_proto.py SCRIPT */

#ifndef SEETHROUGHSPU_FUNCTIONS_H
#define SEETHROUGHSPU_FUNCTIONS_H 1
"""

# make apiutil load the GL function info
d = apiutil.GetFunctionDict("../../glapi_parser/APIspec.txt")

# Emit a C prototype for each special function
functions = apiutil.AllSpecials("seethrough") + apiutil.AllSpecials("seethrough_state")
for func_name in functions:
    return_type = apiutil.ReturnType(func_name)
    params = apiutil.Parameters(func_name)
    print 'extern %s SEETHROUGHSPU_APIENTRY seethrough%s( %s );' % ( return_type, func_name, apiutil.MakeDeclarationString(params) )

print "#endif"

At this point, our Makefile should look like this:

# This is spu/seethrough/Makefile
TOP = ../..

SPU = 1
SHARED = 1
LIBRARY = seethroughspu
FILES = seethroughspu \
        seethroughspu_arrays \
        seethroughspu_config \
        seethroughspu_init \
        seethroughspu_misc

LIBRARIES = spuload crutil crmothership

PRECOMP = seethroughspu_proto.h
SLOP = $(PRECOMP)

LIB_DEFS += seethroughspu.def
include ${TOP}/cr.mk

seethroughspu_proto.h: seethrough_proto.py seethrough_special
    @$(ECHO) Building the Seethrough SPU prototypes header
    @$(PYTHON) seethrough_proto.py > $@

Edit the seethroughspu.h header file and add this line:

#include "seethroughspu_proto.h"

Step 10: Write a configuration file

It's hard to believe, but we're done coding! Now all we need to do is update the crdemo.conf file described in the "Configuration scripts" section. Add the creation of a new SPU to the SPU creation section:

seethrough_spu = SPU( 'seethrough' )

In the SPU configuration section, configure this SPU any way you like:

seethrough_spu.Conf('opacity', 0.25 )
seethrough_spu.Conf( 'sfactor', 'GL_SRC_ALPHA' ) # the default
seethrough_spu.Conf( 'dfactor', 'GL_ONE_MINUS_SRC_ALPHA' ) # the default

And finally, add it before the client SPU:

client_node.AddSPU( seethrough_spu )

Step 11: Enjoy

Step 12: Think about what you've done

Will this SPU work reasonably all the time? A little thought reveals that it will not. Although the "bluepony" demo works OK, Quake III doesn't look right at all, for two reasons:

  1. Quake III uses vertex arrays to specify its colors, and our SPU doesn't handle vertex arrays. So we've turned on blending, but we haven't tweaked the alpha of the colors
  2. Quake III has its own transparency in many places, and a single blend function doesn't work.

The solution to problem #2 is easy. We will allow the user to change the blend function to whatever they want. If they ever try to disable blending, we will instead reset the blend function to our configured defaults. This should have the effect of only modifying geometry that was intended to be opaque.

To do this, we just add one line immediately after our warning in seethroughDisable in seethroughspu_misc.c:

    seethrough_spu.super.BlendFunc( seethrough_spu.sfactor, seethrough_spu.dfactor );

With this modification, we don't need to implement seethroughBlendFunc at all! So simply remove it from the seethrough_special file and delete its implementation.

If you run Quake III now, the user interfaces will work again, and the game is playable. Some things are transparent, and some things aren't, mainly because Quake III doesn't change any alpha values if it thinks that blending is turned off. So if a wall is drawn after a transparent water surface, the wall will have the same transparency as the water. Also, Quake III does extremely agressive visibility culling, so you can see elements appearing and disappearing all the time.

One other thing that seems wrong is that backface culling could be turned on. We certainly don't want that, so we'll add it to the list of things that's prohibited in seethroughEnable in seethroughspu_misc.c:

    else if (cap == GL_CULL_FACE)
    {
        crWarning( "SeeThroughSPU: Ignoring enable of face culling!" );
    }

And we disable it in seethroughSPUInit in seethroughspu_init.c:

    seethrough_spu.super.Disable( GL_CULL_FACE );

In order to actually play what I call "GlassQuake", we still need to implement vertex arrays in our SPU.

Step 13: Bring in the state tracker

To get vertex arrays to work, we will need the assistance of the state tracker. The state tracker will keep track of the location of all of the vertex array pointers, which ones are enabled, and what format they're in.

First, let's make sure that we are linking against the state tracker. Because the state tracker has global state, we link against it in a special way to avoid sharing global variables with other SPU's that also track state. To link against the state tracker, do the following.

Edit the file state_tracker/Makefile and add the string "seethroughspu" to both occurances of the variable "LIB_COPIES". The new setting should look something like:

LIB_COPIES = crserverlib \
             packspu \
             tilesortspu \
             arrayspu \
             hiddenlinespu \
             feedbackspu \
             nopspu \
             simplequeryspu \
             pixelsortspu \
             statecopytest \
             seethrough

Once you've done this, type 'make' in the state_tracker/ directory to get a personalized copy of the state tracker built for the SeeThrough SPU.

NOTE: This step won't have been done for you if you're just using the completed implementation, so you have to do it!

Now go back to spu/seethrough/Makefile and delete the line

LIBRARIES = spuload crutil crmothership

and replace it with

ifdef WINDOWS
TRACKS_STATE=1
LIBRARIES = spuload crutil crmothership
else
LIBRARIES = spuload crutil crmothership crstate
endif

ifdef BINUTIL_LINK_HACK
TRACKS_STATE = 1
LIBRARIES -= crstate
endif

While we're in the Makefile, let's create a new file for our array implementations. Add a file called "seethroughspu_arrays" to the FILES variable. The resulting list should look like:

FILES = seethroughspu \
        seethroughspu_arrays \
        seethroughspu_config \
        seethroughspu_init \
        seethroughspu_misc

Before we start implementing functions, let's figure out what it is we want to do. We will need to use the state tracker's implementation of glEnableClientState, glDisableClientState, glVertexPointer, glColorPointer, glIndexPointer, glNormalPointer, glTexCoordPointer, glEdgeFlagPointer, and glInterleavedArrays. For each of these functions, we will want to call the state tracker's implementation, and then also dispatch to our SuperSPU. Because this is a very simple task and highly repetitive, we'll add it to the auto-generating code in seethrough.py.

First, let's add the 9 functions listed above to a new file called seethrough_state_special. We'll update the auto-generating code so that anything found in this file will automatically dispatch to the state tracker as well as our SuperSPU. The file should look like:

EnableClientState
DisableClientState
VertexPointer
ColorPointer
IndexPointer
NormalPointer
TexCoordPointer
EdgeFlagPointer
InterleavedArrays

Now, let's go back to the seethrough.py script. Right after the code for generating the seethroughColor functions, add the following code:

for func_name in apiutil.AllSpecials( "seethrough_state" ):
    params = apiutil.Parameters(func_name)
    print 'void SEETHROUGHSPU_APIENTRY seethrough%s(%s)' % (func_name, apiutil.MakeDeclarationString( params ) )
    print '{'
    print '\tcrState%s(%s);' % (func_name, apiutil.MakeCallString( params ) )
    print '\tseethrough_spu.super.%s(%s);' % (func_name, apiutil.MakeCallString( params ) )
    print '}'

This will generate functions that look like:

void SEETHROUGH_APIENTRY seethroughEdgeFlagPointer( GLsizei stride, const GLvoid *pointer )
{
    crStateEdgeFlagPointer( stride, pointer );
    seethrough_spu.super.EdgeFlagPointer( stride, pointer );
}

Which is exactly what we want. Because we're going to be using state tracking functions or data in multiple files, let's include the state tracker's header file at the top of seethroughspu.h:

#include "cr_glstate.h"

To get the function declarations for all the state_tracking functions. Now, we need to add these functions to the named function table at the end of seethroughspu.c. First of all, this will make our named function table bigger. Let's update the line in seethroughspu.py where the named function table declaration is printed. The new line should read:

print 'SPUNamedFunctionTable _cr_seethrough_table[%d];' % ( len(apiutil.AllSpecials( "seethrough_state" )) + len(apiutil.AllSpecials( "seethrough" )) + 1 )

This will allow enough space for all the functions from both _special files. Now, just add a second loop to add functions to the table. This should come immediately after the final loop in the script, before the NULL terminator is printed:

for func_name in apiutil.AllSpecials( "seethrough_state" ):
    print '\t__fillin( %d, "%s", (SPUGenericFunction) seethrough%s );' % (offset, func_name, func_name )
    offset += 1

You will of course need to re-run the python script!

Step 14: Initialize the state tracker

The state tracker needs a "context" into which to track all of the OpenGL state (or in our case, the subset we care about). Let's add a "CRContext" structure to the SeethroughSPU structure in seethroughspu.h:

CRContext *ctx;

Now that the variable exists, we can initialize it in seethroughSPUInit in seethroughspu_init.c:

crStateInit();
seethrough_spu.ctx = crStateCreateContext(NULL, 0);
crStateMakeCurrent( seethrough_spu.ctx );

That's it! You're tracking state!

Step 15: Handle vertex array drawing calls

This is by far the most complex part of this SPU. What we're going to do is take calls to glArrayElement, glDrawArrays, and glDrawElements, and pull them apart into individual calls to the non-vertex array equivalents, based on which arrays are enabled. Although this is a complicated thing to get right, the logic to do it already exists in the Chromium packer! We're going to copy functions out of packer/pack_client.c and rework them for our own needs.

Go ahead and add those three functions to the file seethrough_special. (RESULTS NOT SHOWN)

Before we tackle seethroughArrayElement (by far the most complex function), let's write the other two functions in terms of it. Create the file seethroughspu_arrays.c (remember, this was added to the Makefile back in step 13), and write the following two functions. They are shown here without much comment, except to point out that they do a bit of error checking. Chromium tends to consider OpenGL errors to be fatal, rather than setting a flag to be checked later. This is a design decision, and one of the ways in which using Chromium can be slightly different from using vanilla OpenGL.

#include "seethroughspu.h"
#include "chromium.h"
#include "cr_error.h"

void SEETHROUGHSPU_APIENTRY seethroughDrawArrays( GLenum mode, GLint first, GLsizei count )
{
    int i;

    if( count < 0 )
    {
        crError( "seethroughDrawArrays passed negative count: %d", count );
    }

    if( mode > GL_POLYGON )
    {
        crError( "seethroughDrawArrays called with invalid mode: %d", mode );
    }

    seethrough_spu.super.Begin( mode );
    for( i=0; i<count; i++ )
    {
        seethroughArrayElement( first++ );
    }
    seethrough_spu.super.End();
}

The implementation of glDrawElements is similar:

void SEETHROUGHSPU_APIENTRY seethroughDrawElements( GLenum mode, GLsizei count,
                                                    GLenum type, const GLvoid *indices )
{
    int i;
    GLubyte *p = (GLubyte *)indices;

    if( count < 0 )
    {
        crError( "seethroughDrawElements passed negative count: %d", count );
    }

    if( mode > GL_POLYGON )
    {
        crError( "seethroughDrawElements called with invalid mode: %d", mode );
    }

    if ( type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_SHORT && type != GL_UNSIGNED_INT )
    {
        crError( "seethroughDrawElements called with invalid type: %d", type );
    }

    seethrough_spu.super.Begin (mode);
    switch (type)
    {
    case GL_UNSIGNED_BYTE:
        for (i=0; i<count; i++)
        {
            seethroughArrayElement( (GLint) *p++ );
        }
        break;
    case GL_UNSIGNED_SHORT:
        for (i=0; i<count; i++)
        {
            seethroughArrayElement( (GLint) * (GLushort *) p );
            p += sizeof (GLushort);
        }
        break;
    case GL_UNSIGNED_INT:
        for (i=0; i<count; i++)
        {
            seethroughArrayElement( (GLint) * (GLuint *) p );
            p += sizeof( GLuint );
        }
        break;
    default:
        crError( "this can't happen!" );
        break;
    }
    seethrough_spu.super.End();
}

Okay, now we're down to the final function: glArrayElement. Basically what this function is going to do is use the CRClientState structure to figure out which arrays are enabled, and for each enabled array, generate the appropriate function calls corresponding to that array ( glColor, glTexCoord, etc). This function is big, but it's not complicated. Notice that we call seethroughColor instead of seethrough_spu.super.Color, because that's where the transparency transformation happens.

void SEETHROUGHSPU_APIENTRY seethroughArrayElement( GLint index )
{
    CRClientState *c = &(seethrough_spu.ctx->client);
    CRVertexArrays *ca = &(seethrough_spu.ctx->client.array);
    unsigned char *p;

    if (index < 0) {
        crError( "seethroughArrayElement called with a negative index: %d", index );
    }

    if (ca->e.enabled) {
        seethrough_spu.super.EdgeFlagv(ca->e.p + index*ca->e.stride);
    }

    if( ca->t[c->curClientTextureUnit].enabled ) {
        p = ca->t[c->curClientTextureUnit].p + index * ca->t[c->curClientTextureUnit].stride;
        switch( ca->t[c->curClientTextureUnit].type )
        {
        case GL_SHORT:
            switch( ca->t[c->curClientTextureUnit].size )
            {
            case 1: seethrough_spu.super.TexCoord1sv( (GLshort *)p ); break;
            case 2: seethrough_spu.super.TexCoord2sv( (GLshort *)p ); break;
            case 3: seethrough_spu.super.TexCoord3sv( (GLshort *)p ); break;
            case 4: seethrough_spu.super.TexCoord4sv( (GLshort *)p ); break;
            }
            break;
        case GL_INT:
            switch( ca->t[c->curClientTextureUnit].size )
            {
            case 1: seethrough_spu.super.TexCoord1iv( (GLint *)p ); break;
            case 2: seethrough_spu.super.TexCoord2iv( (GLint *)p ); break;
            case 3: seethrough_spu.super.TexCoord3iv( (GLint *)p ); break;
            case 4: seethrough_spu.super.TexCoord4iv( (GLint *)p ); break;
            }
            break;
        case GL_FLOAT:
            switch (ca->t[c->curClientTextureUnit].size )
            {
            case 1: seethrough_spu.super.TexCoord1fv( (GLfloat *)p ); break;
            case 2: seethrough_spu.super.TexCoord2fv( (GLfloat *)p ); break;
            case 3: seethrough_spu.super.TexCoord3fv( (GLfloat *)p ); break;
            case 4: seethrough_spu.super.TexCoord4fv( (GLfloat *)p ); break;
            }
            break;
        case GL_DOUBLE:
            switch (ca->t[c->curClientTextureUnit].size )
            {
            case 1: seethrough_spu.super.TexCoord1dv( (GLdouble *)p ); break;
            case 2: seethrough_spu.super.TexCoord2dv( (GLdouble *)p ); break;
            case 3: seethrough_spu.super.TexCoord3dv( (GLdouble *)p ); break;
            case 4: seethrough_spu.super.TexCoord4dv( (GLdouble *)p ); break;
            }
            break;
        }
    }

    if( ca->i.enabled )
    {
        p = ca->i.p + index * ca->i.stride;
        switch( ca->i.type )
        {
        case GL_SHORT: seethrough_spu.super.Indexsv( (GLshort *)p ); break;
        case GL_INT: seethrough_spu.super.Indexiv( (GLint *)p ); break;
        case GL_FLOAT: seethrough_spu.super.Indexfv( (GLfloat *)p ); break;
        case GL_DOUBLE: seethrough_spu.super.Indexdv( (GLdouble *)p ); break;
        }
    }

    if( ca->c.enabled )
    { 
        p = ca->c.p + index * ca->c.stride;
        switch( ca->c.type )
        {
        case GL_BYTE:
            switch( ca->c.size )
            {
            case 3: seethroughColor3bv( (GLbyte *)p ); break;
            case 4: seethroughColor4bv( (GLbyte *)p ); break;
            }
            break;
        case GL_UNSIGNED_BYTE:
            switch( ca->c.size )
            {
            case 3: seethroughColor3ubv( (GLubyte *)p ); break;
            case 4: seethroughColor4ubv( (GLubyte *)p ); break;
            }
            break;
        case GL_SHORT:
            switch( ca->c.size )
            {
            case 3: seethroughColor3sv( (GLshort *)p ); break;
            case 4: seethroughColor4sv( (GLshort *)p ); break;
            }
            break;
        case GL_UNSIGNED_SHORT:
            switch( ca->c.size )
            {
            case 3: seethroughColor3usv( (GLushort *)p ); break;
            case 4: seethroughColor4usv( (GLushort *)p ); break;
            }
            break;
        case GL_INT:
            switch( ca->c.size )
            {
            case 3: seethroughColor3iv( (GLint *)p ); break;
            case 4: seethroughColor4iv( (GLint *)p ); break;
            }
            break;
        case GL_UNSIGNED_INT:
            switch( ca->c.size )
            {
            case 3: seethroughColor3uiv( (GLuint *)p ); break;
            case 4: seethroughColor4uiv( (GLuint *)p ); break;
            }
            break;
        case GL_FLOAT:
            switch( ca->c.size )
            {
            case 3: seethroughColor3fv( (GLfloat *)p ); break;
            case 4: seethroughColor4fv( (GLfloat *)p ); break;
            }
            break;
        case GL_DOUBLE:
            switch( ca->c.size )
            {
            case 3: seethroughColor3dv( (GLdouble *)p ); break;
            case 4: seethroughColor4dv( (GLdouble *)p ); break;
            }
            break;
        }
    }

    if( ca->n.enabled )
    {
        p = ca->n.p + index * ca->n.stride;
        switch( ca->n.type )
        {
        case GL_BYTE: seethrough_spu.super.Normal3bv( (GLbyte *)p ); break;
        case GL_SHORT: seethrough_spu.super.Normal3sv( (GLshort *)p ); break;
        case GL_INT: seethrough_spu.super.Normal3iv( (GLint *)p ); break;
        case GL_FLOAT: seethrough_spu.super.Normal3fv( (GLfloat *)p ); break;
        case GL_DOUBLE: seethrough_spu.super.Normal3dv( (GLdouble *)p ); break;
        }
    }

    if( ca->v.enabled )
    {
        p = ca->v.p + index * ca->v.stride;

        switch( ca->v.type )
        {
        case GL_SHORT:
            switch( ca->v.size )
            {
            case 2: seethrough_spu.super.Vertex2sv( (GLshort *)p ); break;
            case 3: seethrough_spu.super.Vertex3sv( (GLshort *)p ); break;
            case 4: seethrough_spu.super.Vertex4sv( (GLshort *)p ); break;
            }
            break;
        case GL_INT:
            switch( ca->v.size )
            {
            case 2: seethrough_spu.super.Vertex2iv( (GLint *)p ); break;
            case 3: seethrough_spu.super.Vertex3iv( (GLint *)p ); break;
            case 4: seethrough_spu.super.Vertex4iv( (GLint *)p ); break;
            }
            break;
        case GL_FLOAT:
            switch( ca->v.size )
            {
            case 2: seethrough_spu.super.Vertex2fv( (GLfloat *)p ); break;
            case 3: seethrough_spu.super.Vertex3fv( (GLfloat *)p ); break;
            case 4: seethrough_spu.super.Vertex4fv( (GLfloat *)p ); break;
            }
            break;
        case GL_DOUBLE:
            switch( ca->v.size )
            {
            case 2: seethrough_spu.super.Vertex2dv( (GLdouble *)p ); break;
            case 3: seethrough_spu.super.Vertex3dv( (GLdouble *)p ); break;
            case 4: seethrough_spu.super.Vertex4dv( (GLdouble *)p ); break;
            }
            break;
        }
    }
}

Step 16: Enjoy even more

GlassQuake, with BlendFunc( GL_SRC_ALPHA, GL_ONE )

Here are a few more screenshots of GlassQuake in various blending modes.