Tutorial 2 - Download Source Code
Now that the compiler is set up properly we can start coding (finally!). In this tutorial we're going to use the three most basic rendering primitives:
grDrawPoint
grDrawLine
and grDrawTriangle
Before any rendering can take place there are a *few* things that need to be done (heh heh heh). Firstly the Voodoo hardware has to be initialised. This is done with a call to grGlideInit() which must always be the first function called in any Glide program (actually that's not strictly true - see the comments in the source code for an example of when this is not the case - however for the sake of consistency in my programs this will always be the first function to be called). This function is basically a black box. It does, I presume, a load of complicated stuff - setting up registers on the Voodoo chips etc. - and frankly I don't really care what it does at a low level. Another point to make here is that only one Glide subsystem can be active at any time - two boards connected with SLI count as a single subsystem. Unless you've spent far too much money on your computer this point is largely redundant anyway...
So we set up a context like this:
GrContext_t myContext; //or something like that...
followed by a call to grGlideInit like this:
grGlideInit(); //Simple huh?
and we're ready to go!
In my code I do a check for the number of boards and set the first one as the active one. The code is self-explanatory and won't be discussed further here.
The next thing is to set up the graphics context with a call like this:
myContext = grSstWinOpen(NULL,
GR_RESOLUTION_640x480, GR_REFRESH_60Hz,
GR_COLORFORMAT_RGBA, GR_ORIGIN_LOWER_LEFT, 2, 0);
A few words of explanation are in order. Although most of you (and me) will be writing programs to run in a Windows (ugh...) environment, writing code that takes account of the fact is a bit tricky and will overcomplicate these tutorials. The first parameter to grSstWinOpen is a window handle. If you were writing a proper Win32 app this would be an hWnd parameter, however we pass NULL as our program is a console app and has no window handle. All my programs will do this and (as you'll soon find out) if you ALT-TAB away from the program and then back again, you'll get a blank screen. Sorry about that but for simplicity's sake it's the best way. The other parameters are pretty much self-explanatory. They refer to the resolution of the screen expressed in pixels, the refresh rate (Glide supports some extremely high ones but for maximum compatibility 'I'll always use 60Hz), the colour format, the screen origin and the numbers of buffers.
The colour format is a slightly strange concept - Glide supports 'Byte-swizzling' - it can interpret the RGBA information in several different orders in case you want them in a different order. I personally feel that Red, Green, Blue, Alpha (more on Alpha in a few weeks...) is the intuitive order to have them in but you don't have to if you're really weird. The origin can be in either bottom-left or top-left corners of the screen - coming from a pure maths background I've gone for the Cartesian option for this one. This leaves us with two rather unfriendly values at the end: 2 and 0. Why these aren't #defined somewhere is anybody's guess but they refer to the number of display buffers (double buffering is normally the standard - hence the 2) and the number of auxiliary buffers - used for alpha buffering, depth buffering etc. We don't need one for such a trivial 2D example so it's 0.
Phew! Right then.
You'll notice from looking at my code (you HAVE downloaded the code, right?) that my vertex structure pVertex contains x,y co-ordinates and r,g,b colour values. This is a very flexible feature of Glide 3. It doesn't matter what your vertex structure looks like as long as you tell Glide about it at some point (unlike Glide 2 which had a strictly defined grVertex structure). The only provision is that x and y must be the first values in the struct, after that you can put whatever you want in there. We use the following function to initialise the way that Glide will interpret Vertices:
grVertexLayout
This command tells Glide at what offset in the struct it will find certain parameters that it needs. If you look at my usage in the program it will be obvious what it's doing. In fact those crafty programmers at 3Dfx have thought of everything - by providing us with data types called things like FxFloat and FxBool we can guarantee how many bytes our data structures will take up - rather than using a standard float or int and discovering that different compilers give them different sizes...
Nearly there now...
The last thing to do before we actually start rendering is to set up the colour combine unit. This is an absolute bastard of a concept to wrap your head around - at least it was for me - so spend a while reading the dox (over and over again if you're anything like me...). Basically the colour combine unit provides a very non specific way of calculating colour values - but with versatility comes complexity as always. Rather than just having a couple of settings (one for flat shading, one for Gouraud shading, one for specular highlighting etc.) you provide 4 factors to the CCU: A function; a factor; and two colour sources. In addition you provide a Boolean value telling the CCU whether it should invert the colour at the end of the process or not. This is (as you can imagine) usually false.
The Colour Combine Function is one of 11 equations of varying complexity (see the docs for a table of them all) which each uses none or more of the factor and colour sources (called the Local combine source, and the Other combine source). Each of the colour sources can be one of the following: an unspecified colour (probably zero although it's not guaranteed), a colour generated from the iterated distances of the colours at the polygon's vertices, or a constant colour set with a call to grConstantColorValue(). Additionally the Other combine source can also take it's colour from a texture map source (we'll not be covering this for a while thank god...)
This is all well and good until you look at the Combine Factor and suddenly a deep seated feeling of confusion sets in - at least it did for me). For the first few tutorials at least this will always be GR_COMBINE_FACTOR_ONE and for now it's best not to worry about it until we do alpha blending or texture mapping or something scary like that...
For instance - to set up the CCU for flat shading with no lighting we could use the following:
grColorCombine (GR_COMBINE_FUNCTION_LOCAL, GR_COMBINE_FACTOR_ONE, GR_COMBINE_LOCAL_CONSTANT, GR_COMBINE_OTHER_NONE, FXFALSE);
From looking at the table of functions on page 52 of the docs we can see that GR_COMBINE_FUNCTION_LOCAL means that the equation used to work out the colour to be drawn is simply Clocal which is the local combine source. As neither the factor nor the other combine source are in the equation you can put whatever you like for those parameters, however I think the CCU computes stuff even if it doesn't need it so if you set the factor to be GR_COMBINE_FACTOR_LOCAL and the other combine source to be GR_COMBINE_OTHER_ITERATED, the CCU will do calculations which it doesn't need. So keep unused terms simple.
I think it's time to compile my code and see what it does.
You'll notice immediately that we're doing iterated colour blending between vertices. It's no more complicated than flat shading really so you should have no problem understanding how it works. And it looks pretty.
The final thing to say is that the rendering primitives themselves are disappointingly straightforward compared to all the setting up you have to do even just to render a single pixel. They each take the appropriate number of pointers to your structs (1 for a point, 2 for a line, 3 for a triangle) and that's it. The code is really well commented so read it and make sure you understand what's going on...
Three more functions to briefly explain before we're finished (but they're perfectly well explained in the dox anyway):
grBufferSwap takes a integer value that denotes how many retraces to wait before swapping the buffers, grSstWinClose closes a supplied context - the opposite of grSstWinOpen - , and grGlideShutdown is the opposite of grGlideInit and MUST be called at the end of your program.
Simple eh...(!)
Mail me your comments - bye for now.