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 seethrough
cd seethrough
python gen_template.py seethrough
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
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".
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();
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.
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
.
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.
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.
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";
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"
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 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.
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!
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!
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; } } }
![]() |
GlassQuake, with BlendFunc( GL_SRC_ALPHA, GL_ONE )
|
Here are a few more screenshots of GlassQuake in various blending modes.