Programming Tutorial 6 - Drawing graphics!

Hi, good to see you again!

This time we're going to try & make a big jump forwards.  With all we've learnt so far we can start to get some graphics running.  Unlike the last parts I'll race through this quite quickly, but hopefully you should still get a good idea what's going on.

 Rendering Things on a modern PC

One of the best way to access the hardware inside your PC using C or C++ is using Microsoft's DirectX.  Back in 1995 Microsoft realised that game developers needed a standard way to talk to the increasingly complex hardware powering the graphics for games.  They invented DirectX - a series of libraries (you know what they are now) split into 2 parts - lib files and DLLs.  They did this for a very important reason.  DirectX is a very large set of functions that take up a lot of space, and it would be wasteful if every game had a copy of DirectX linked in with it.  A game player's machine might have 100 copies of DirectX scattered throughout their harddrive - not much good.  So the DirectX libraries are actually just hooks into a set of DLL files that get loaded and called when needed.  A DLL is very much like a library - in fact DLL stands for "Dynamically Linked Library" - that a PC only needs one copy of.  Every game links with the hook libraries, but the real code is stored in the DLLs once per machine.

 So let's use DirectX!

Well - it's not that easy.  DirectX gives you full control over just about e very feature of a modern graphics card.  It's quite complex and a very simple program doesn't look very simple once DirectX has got involved.  To enable me to write game tests and demos I've written a library that uses the DirectX library, but provides me with some very simple BASIC-like functions for drawing things.  We're going to use that library here to get some things rendered.

 What you'll need

First you'll need the DirectX SDK (Software Development Kit).  This is something I already have and can't risk knackering my machine by uninstalling & reinstalling, so I'll have to point you at : This Page.  Click the Download button and leave it to Do Its Stuff.  You should close Developer Studio before running the installer because (hopefully) it'll be adding in lots of paths and things for you.

Next  you'll need my library.  This is available here. It's been developed over a number of years in pure C, so it's not a great example of programming style, but it certainly does the job.  You'll need to pop it somewhere that developer studio can find it.  Unzip Lib and Include from EZ3DLibrary.zip into new directories called Lib and Include in Some Safe Place (for example My Documents/Visual Studio 2008/Projects/) and then add those directories to the list that Visual Studio has.  This list is under Tools/Options.  Then select Projects and Solutions on the left, open it up and choose VC++ Directories.  On the right first choose Include Files and use the "New" icon to add the Include directory.  Finally do the same for the "Libraries" list and Libs.

Let's test out what we've installed.  Delete the contents of main.cpp and type this:

#include <D3DX9.h>
#include "EZ3D.h"

Then compile it (F7).  Did it compile?  If so, we're ready to carry on!

 A program!

 Right.  From now on we're going to leave our Console Application behind.  There's loads more you can do with consoles, and it's a great place to write utilities, but we're not going to go there at the moment.  So let's make a new project that's a bit more up-to-date.  Select File/New->Project... and on the left hand side select "Win32" as before.  This time on the right choose "Win32 project" and call it GFXTest.  I can't remember if we made a directory for the solution, but I'm not going to.  Click "OK" and hopefully the Win32 Application Wizard will pop up.  We don't need any help at the moment, so change to "Application settings" on the list on the left and choose "Windows application" and "Empty project" then Finish.

Finally right click on the project name in the Solution View on the left and "Add...Existing Item".  Code/C++ File and name it "Main.cpp" again.

 Into this lovely empty application paste the following:

#include <D3DX9.h>
#include "EZ3D.h"
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, char *lpCmdLine, int nShow)
{
	EZ_InitialiseRenderer(hInstance, 800,600, FALSE);
	EZ_SetBackgroundColour(0xff8080ff);
 
	float time = 0.0f;
	while (!EZ_DoWeQuitNow() && !EZ_KeyPressed(VK_ESCAPE))
	{
		EZ_BeginScene();
 
		float x = sin(time)*0.3f + 0.5f;
		EZ_RenderFlat(x,0.5f, x+0.04f,0.5f+0.05f, 0,ROT_NONE,0xffff0000);
 
		EZ_EndScene();
 
		time += EZ_ReturnFrameTime();
	}
}

 If you "F7" this to compile it, you'll get some Link Errors.  That's because we haven't told the project where to find all the functions it needs - what libraries to link with.  I've used lots of new functions and Stuff here, but let's get it running and talk about it afterwards.

So - right click on the project name and select "Properties" at the bottom. Under Configuration Properties choose Linker, expand it and select "Input".  Now at the top right you'll see "Additional Dependencies".  These are the extra libs (over and above the standard ones) that your project needs.  We need:

d3d9.lib D3dx9.lib easy3d.lib libutil.lib dinput8.lib dxguid.lib 

So paste this in and OK the dialog box.  Now F7 and the project should compile and link (with some warnings - more on those later).  Finally Ctrl+F5 and....  Yay! 

 So - what does it do??

Loads of new stuff here, but nothing we can't get through quite quickly - especially now you're such a master at all the rest.  The first two lines include loads of definitions - thousands of them in fact - everything you need to use DirectX and the 3D library.  Then we define a new function called "WinMain".  The definition of this function looks a bit complex, but it can safely be ignored for now.  Suffice it to say that just as when a console application was run we started with "main", with a Windows application we start with "WinMain".  Behind the scenes the libraries that we get linked with when writing a Windows application are actually providing "main" for us.  Their "main" then calls "WinMain" and the linker will ensure that at this point our code is called - if you miss out "WinMain" or call it something else the linker will complain.

So what does WinMain do?  Well, all these new "commands" are really just functions in the 3D library, and these functions call DirectX functions - it's all just a big pile of function calls.  We'll go through the commands one by one now.

 EZ_InitialiseRenderer

All the functions in this library start with "EZ_".  Generally any library you use will have some convention for naming its functions - either a few consistent letters at the start or the end or something similar.  This function does all the setting up.  It takes 4 parameters, the HINSTANCE that WinMain was provided with (just ignore this for now), the width & height of the screen we'd like to use, and whether or not we'd like to run full-screen.  In this case we say "FALSE".  What's "FALSE"?  Well, within the DirectX includes are lots of function definitions, just like we've been making, but there are also definitions of new types and new macros.  We'll cover these properly in the next tutorial, but for now it's enough to know that "FALSE" is just a readable way to write "0", and 0 would do just as well.  Generally in C (as in most languages) 0 is used to represent FALSE, NO, OFF etc., and with DirectX and Windows programming in general someone's defined the word FALSE to help you remember.  There's also a TRUE which is set to 1.

 EZ_SetBackgroundColour

This function only takes one parameter - the colour of the background as a 32 bit hexadecimal number.  Notice that C assumes you mean decimal normally, but you can change to hexadecimal by adding 0x to the start of any (whole) number.  In this case 0xff8080ff can be broken down into (from left to right) : 0xff = 255 = solid "alpha" (all background colours should generally have this).  0x80 = 128 red = 50% red, 0x80 = 50% green and 0xff = 100% blue.  So it's a light blue.

float time=0.0f;

Now, I've just noticed here that I've lapsed into C++.  The line looks like perfectly valid C, and it is, but in C you can't declare new variables in the middle of a block (the bits of program with between { and } ).  If this was in a .c file rather than a .cpp file then we'd have to move this line up to above the "EZ_InitialiseRenderer".  This would work perfectly, but generally placing variables nearer where they are going to be used makes a progrm easier to follow - especially if you sprinkle white space, gaps and comments around the place.

 while (!EZ_DoWeQuitNow() && !EZ_KeyPressed(VK_ESCAPE))

 Wow - that's a complex line!  Well, for starters, what's a "while" loop then?  A while loop is very simple - it loops "while" something holds true.  Note that if the thing isn't true to start with then a while loop won't do anything at all - the program will skip it entirely.  So as an example:

while (PriceOfHBOSSharesLessThan(80))

{

BuyHBOSShares(); 

 will start by calling PriceOfHBOSSharesLessThan.  If the return value of this function is non-zero (which C always considers to be "true") then the function BuyHBOSShares will be called once.  Then the process will repeat.  If HBOS shares start off above 80p (unlikely, but let's pretend) then you won't be buying anything.

  Ok - back to our while loop then.  It calls 2 functions - EZ_DoWeQuitNow and EZ_KeyPressed.  The purpose of these functions is fairly clear.  EZ_DoWeQuitNow returns TRUE (a non-zero value) if the user quits the program by closing the window.  EZ_KeyPressed returns TRUE if a certain key is pressed.  So what are all the other bits then?

Boolean Logic in C - a short digression

We've just found that C considers non-zero values to be "true" for the purposes of looping in a while loop, and this holds for all conditions - if statements and for statements also consider 0 to be false and anything else to be true.  In fact normal comparisons can be thought of as functions that "return" a non-zero value.  "i = 7<9;" will store 1 in i!

So what else can you do with Boolean things?  Well, C has AND, but you write it &&, and OR, but you write || (that's the vertical bar above \ - but two of them).  So you can say "if (i<7 && i>5)".  You have the comparisons "==" (equals), "<", ">", "<=", ">=" and "!=" (not equal).  And you have the operator ! which means "not" (hence != being not-equal). 

So - what does our line do then?  Reading it as you should, it says:

   Loop while NOT EZ_DoWeQuitNow AND NOT EZ_KeyPressed(VK_ESCAPE)

In other words, loop around as long as the user doesn't want to quit, and they haven't pressed escape (VK_ESCAPE is short for Virtual Key ESCAPE and is another define that comes from Direct X/Windows - I said there were lots...).

Rendering Things on a PC

Ok - now we get to the main part of the program - the bit that does the actual work and draws things.  When rendering things on a PC (on any modern hardware really) we generally have a section of code that does the Drawing of the world, and a section that does the Updating of the position of things in the world (sometimes called the Tick because it ticks time forwards).  There are lots of good reasons for this, but here are a few:

- If you've split the game code into code that Renders and code that Ticks then you can easily add a pause (by not calling Tick any more) and debug features (by removing the Tick and replacing it with something that shifts only the player around, for example)

- Graphics hardware runs at the same time as your main CPU, so if you generate a big block of work for it and tell it to go away and draw it then the rendering can happen whilst you do other things.  By batching up all the Draw calls into one place you can allow the Tick function (often a complex bit of code) to run whilst all the rendering happens.

- It's generally a good idea in any program to split functionality into blocks with similar function, and Rendering and Updating are really totally different processes that can easily be broken apart.

  In this program our "Render" isn't in a separate function, but it is split from the rest of the program with EZ_BeginScene and EZ_EndScene.  EZ_BeginScene tells the renderer to get ready for a new frame of graphics - it makes sure that any previous frame has rendered and waits for a suitable time to start drawing again.  EZ_EndScene tells the renderer that we've finished drawing things now - it is free to send that frame off to the graphics card in one optimised batch.

So - what do we render?  Well, the next line uses the "sin" function to create a smoothly varying value between 0.2 and 0.8 and store it in x.  x is a floating point value (a number that has a decimal point rather than a whole number - called 'float' in C) and this calculation will use floating point maths.  So I guess it's time to talk about floating-point numbers!

Floats and doubles - using decimals in C

As I'm sure you know, computers only really like integer numbers.  They're really only happy when talking about numbers like 7 and 42.  When we want to talk about anything fractional they have to store the number internally as a set of integers somehow, and this representation is called "floating point".  Originally CPUs had to operate on the integers making up the floating point number "by hand" using adds and subtracts of integer values, but these days they all have a Floating Point Unit (FPU) that can do the maths using dedicated circuits.  Because of this floating point values are as fast (or faster) than integers and can be used all over the place.

 Storing those numbers

In order to store a fractional value using integers a computer splits it into 2 pieces - the 'mantissa' and the 'exponent'.  The mantissa stores the significant digits of the number, and the exponent tells you how far the decimal point has moved to the left or the right.  For example, 100 is the same as 1 with the decimal point shifted two places to the right.  If we said that Right was positive and Left was negative, then 100 = 1 with a shift of +2, and 0.00001 = 1 with a shift of -5.  This is very similar to scientific notation where these numbers would be written 1E2 and 1E-5.  So now instead of fractions we have 2 decimal numbers.  Pi can be stored as 31415962 and -7.  12345600000 would be 123456 and 5 (on a computer we don't have Decimal points but Binary points, but the theory is the same)

Great!  So where does that get us?  Well, we can store Fractiony things!  What's the catch?  Well, there's a limit to how Good our numbers can be, depending on how much space we want to spend on our Mantissa and our Exponent.  Imagine we use 4 digits for the mantissa and 2 for the exponent.  Then we can store HUGE and TINY numbers, but only approximately.  For example, we can store 1234000000 without problems (1234 and 6) but not 1234000001 or even 1234100000!  We've run out of accuracy.  Likewise we can store 0.0000000000000001234 but not 12345.  So these Floating Point numbers are a bit odd.  If you make a number really big and then small again it might just fall apart.  For example, with the 4 digits/2 digits version we just used,  (x + 1) - 1 might not equal x!  (try this with 0.1234 for example).

The upshot of all this is that C has 2 types of number - the float which is 32 bits long (roughly 7 decimal digits of mantissa and 2ish of exponent) and the double which is 64 bits long (16 decimal digits mantissa and 3ish of exponent).  Floats are nearly always supported in hardware and are really really fast.  Doubles aren't supported and are really really slow.  To tell the compiler what we mean we use 2 methods - the variable type ('float' and 'double') and an extension on the number - 'f' for float and nothing for double.  So the number 5.67f is a floating point value, and 5.67 is a double value.  This means that if you write "x * 5.67" it could well be a lot slower than "x * 5.67f"!  Worth making sure you don't get them mixed up!


Digg! Reddit! Del.icio.us! Mixx! Google! Live! Facebook! StumbleUpon! TwitThis Joomla Free PHP
Last Updated on Monday, 12 January 2009 21:33