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.
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 seethroughcd seethroughpython gen_template.py seethroughThe 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 cr/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).
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 |
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 cr/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; import cPickle; import string; import re; sys.path.append( "../../opengl_stub" ) parsed_file = open( "../../glapi_parser/gl_header.parsed", "rb" ) gl_mapping = cPickle.load( parsed_file ) import stub_common; |
This code loads in the parsed representation of the OpenGL headers,
which gives a structured interface to the entire API supported by
Chromium. gl_mapping is an associative array whose keys
are OpenGL function names (without the "gl" prefix).
stub_common is a set of utility functions that we will
use to generate code later.
Although we won't need to in this example, we can easily extract the list of all functions into a sorted list with the commands:
|
keys = gl_mapping.keys() keys.sort() |
Many of the code generating scripts throughout Chromium will iterate over this list of keys.
Now that the parsed representation of the file has been loaded, we should
print out the header 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 #include "cr_spu.h" #include "cr_glwrapper.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 "cr_glwrapper.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, arg_names, arg_types) = gl_mapping[func_name] print 'void SEETHROUGHSPU_APIENTRY seethrough%s%s' % (func_name, stub_common.ArgumentString( arg_names, arg_types ) ) print '{' print '}' print ''
func_name = 'Color%d%sv' % (components, type) |
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 gl_mapping array has given us the
return type of our function, as well as a list of argument names and argument
types. The stub_common library uses this information to build
the argument list for the function definition.
The easiest way to implement these functions is to have the stub_common
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):
| if components == 3: arg_names.append( 'seethrough_spu.opacity%s' % type ) else: print '\t(void) %s;' % arg_names[3] arg_names[3] = 'seethrough_spu.opacity%s' % type new_func_name = 'Color4%s' % type print '\tseethrough_spu.super.%s%s;' % (new_func_name, stub_common.CallString( arg_names ) ) |
Now we have half of our glColor functions working. The implementation for the vector passing functions is even easier. Just add this code:
| new_names = [ 'v[0]',
'v[1]', 'v[2]', 'seethrough_spu.opacity%s' % type ] new_func_name = 'Color4%s' % type print '\tseethrough_spu.super.%s%s;' % (new_func_name, stub_common.CallString( new_names ) ) |
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 declare the
rest of the functions as "extern" and generate the named
function table. To do this, we're going to use a method of stub_common
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.
Append the following code to the end of seethrough.py (note that
this code is not indented at all -- it's at the outermost scope):
| for func_name in
stub_common.AllSpecials( "seethrough" ): if not func_name.startswith( "Color" ): (return_type, arg_names, arg_types) = gl_mapping[func_name] print 'extern void SEETHROUGHSPU_APIENTRY seethrough%s%s;' % (func_name, stub_common.ArgumentString( arg_names, arg_types ) ) |
Notice that we bother declaring the actual prototype for these functions even
though they're going to be cast to SPUGenericFunction in the named
function table anyway, because otherwise the Windows linker won't find them.
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 seethrough_table[%d];' % ( len(stub_common.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 ) { tilesort_table[offset].name = crStrdup( name ); tilesort_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 stub_common.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 SPUInit, add a
declaration for it to seethroughspu.h:
| void seethroughspuBuildFunctionTable( void ); |
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 SPUInit function:
| seethroughspuBuildFunctionTable( ); |
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 * GL_MAXBYTE); seethrough_spu.opacityd = (GLdouble) (o); seethrough_spu.opacityf = (GLfloat) (o); seethrough_spu.opacityi = (GLint) (o * GL_MAXINT); seethrough_spu.opacitys = (GLshort) (o * GL_MAXSHORT); seethrough_spu.opacityub = (GLubyte) (o * GL_MAXUBYTE); seethrough_spu.opacityui = (GLuint) (o * GL_MAXUINT); seethrough_spu.opacityus = (GLushort) (o * GL_MAXUSHORT); } |
The GL_MAX* constants are defined in cr/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! Replace the "CONFIGURATION STUFF HERE" comment
(and, if you want, the cast of "response" immediately
below it, since we're going to use response now) with this:
| if (crMothershipSPUParam( conn, response, "opacity" )) { GLfloat opacity; sscanf( response, "%f", &opacity ); setOpacity( opacity ); } |
crMothershipSPUParam will return a positive value if the SPU has such a named
parameter, and the value will be placed in the "response"
buffer.
We're done with this for now, although we will return to configuration in a few steps to get the user-specified blend mode.
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 "cr_glwrapper.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; } |
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 default
value. 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. After the configuration step for the opacity, add this code:
| if (crMothershipSPUParam( conn, response, "sfactor" )) { setBlendFuncFactor( response, &(seethrough_spu.sfactor) ); } if (crMothershipSPUParam( conn, response, "dfactor" )) { 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.
When the SPU is loaded, we need to enable blending and set the blend
function. This is analogous to setting the default color in the Invert
SPU. At the very end of the SPUInit function in seethroughspu_init.c
(after your previously-added call to build the function table), add the code:
| seethrough_spu.super.Enable( GL_BLEND ); seethrough_spu.super.BlendFunc( seethrough_spu.sfactor, seethrough_spu.dfactor ); |
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.
Don't forget, this SPU is a SubSPU of the PassThrough SPU. Change the
second line in SPULoad in seethroughspu_init.c to:
| *super = "passthroughspu"; |
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 ) |

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:
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 SPUInit 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.
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 cr/spu/state_tracker/Makefile and add the string
"seethroughspu" to the variable "LIB_COPIES". The new
setting should look like:
| LIB_COPIES = crserver \ packspu \ tilesortspu \ seethroughspu |
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 cr/spu/seethrough/Makefile, and add the line:
| TRACKS_STATE = 1 |
Anywhere before the "include" of cr.mk..
I typically put the TRACKS_STATE setting immediately after the LIBRARIES
variable.
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
stub_common.AllSpecials( "seethrough_state" ): (return_type, arg_names, arg_types) = gl_mapping[func_name] print 'void SEETHROUGHSPU_APIENTRY seethrough%s%s' % (func_name, stub_common.ArgumentString( arg_names, arg_types ) ) print '{' print '\tcrState%s%s;' % (func_name, stub_common.CallString( arg_names ) ) print '\tseethrough_spu.super.%s%s;' % (func_name, stub_common.CallString( arg_names ) ) 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 seethrough_table[%d];' % ( len(stub_common.AllSpecials( "seethrough_state" )) + len(stub_common.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 stub_common.AllSpecials( "seethrough_state" ): print '\t__fillin( %d, "%s", (SPUGenericFunction) seethrough%s );' % (offset, func_name, func_name ) offset += 1 |
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 SPUInit in
seethroughspu_init.c:
| crStateInit(); seethrough_spu.ctx = crStateCreateContext(); crStateMakeCurrent( seethrough_spu.ctx ); |
That's it! You're tracking state!
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 cr/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.
| 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. Since those
functions aren't in any header file, we declare them extern here.
| extern void SEETHROUGHSPU_APIENTRY seethroughColor3bv(GLbyte *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4bv(GLbyte *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3ubv(GLubyte *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4ubv(GLubyte *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3sv(GLshort *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4sv(GLshort *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3usv(GLushort *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4usv(GLushort *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3iv(GLint *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4iv(GLint *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3uiv(GLuint *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4uiv(GLuint *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3fv(GLfloat *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4fv(GLfloat *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor3dv(GLdouble *p); extern void SEETHROUGHSPU_APIENTRY seethroughColor4dv(GLdouble *p); void SEETHROUGHSPU_APIENTRY seethroughArrayElement (GLint index) { CRClientState *c = &(seethrough_spu.ctx->client); unsigned char *p; if (index < 0) { crError( "seethroughArrayElement called with a negative index: %d", index ); } if (c->e.enabled) { seethrough_spu.super.EdgeFlagv(c->e.p + index*c->e.stride); } if (c->t[c->curClientTextureUnit].enabled) { p = c->t[c->curClientTextureUnit].p + index*c->t[c->curClientTextureUnit].stride; switch (c->t[c->curClientTextureUnit].type) { case GL_SHORT: switch (c->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 (c->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 (c->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 (c->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 (c->i.enabled) { p = c->i.p + index*c->i.stride; switch (c->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 (c->c.enabled) { p = c->c.p + index*c->c.stride; switch (c->c.type) { case GL_BYTE: switch (c->c.size) { case 3: seethroughColor3bv((GLbyte *)p); break; case 4: seethroughColor4bv((GLbyte *)p); break; } break; case GL_UNSIGNED_BYTE: switch (c->c.size) { case 3: seethroughColor3ubv((GLubyte *)p); break; case 4: seethroughColor4ubv((GLubyte *)p); break; } break; case GL_SHORT: switch (c->c.size) { case 3: seethroughColor3sv((GLshort *)p); break; case 4: seethroughColor4sv((GLshort *)p); break; } break; case GL_UNSIGNED_SHORT: switch (c->c.size) { case 3: seethroughColor3usv((GLushort *)p); break; case 4: seethroughColor4usv((GLushort *)p); break; } break; case GL_INT: switch (c->c.size) { case 3: seethroughColor3iv((GLint *)p); break; case 4: seethroughColor4iv((GLint *)p); break; } break; case GL_UNSIGNED_INT: switch (c->c.size) { case 3: seethroughColor3uiv((GLuint *)p); break; case 4: seethroughColor4uiv((GLuint *)p); break; } break; case GL_FLOAT: switch (c->c.size) { case 3: seethroughColor3fv((GLfloat *)p); break; case 4: seethroughColor4fv((GLfloat *)p); break; } break; case GL_DOUBLE: switch (c->c.size) { case 3: seethroughColor3dv((GLdouble *)p); break; case 4: seethroughColor4dv((GLdouble *)p); break; } break; } } if (c->n.enabled) { p = c->n.p + index*c->n.stride; switch (c->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 (c->v.enabled) { p = c->v.p + (index*c->v.stride); switch (c->v.type) { case GL_SHORT: switch (c->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 (c->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 (c->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 (c->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; } } } |
|
GlassQuake, with BlendFunc( GL_SRC_ALPHA, GL_ONE )
|
Here are a few more screenshots of GlassQuake in various blending modes.