The Main LoopThis next part is fairly quick and simple, yet is important to any Direct3D application - how the thing runs. Anyone who has paid any attention to a commercial game will know about frame rates - how many times every second the computer is updating the game; high frame rate is good, low frame rate is bad. We now need to set up our application so that it runs on a loop; event based (where we only update when something has changed) simply will not cut it here - you’ll be wasting valuable time either doing nothing or trying to work out if something has changed, and on top of that it’s almost always going to be changing. Secondly never ever, ever, ever use a timer control or similar for this job - they are inaccurate and slow, you may well be able to set them to 1ms, but in reality they’re only accurate to about 50-100ms (maximum frame rate is therefore 10-20fps). We’re going to use a loop instead - in theory a never ending loop. This loop will execute as fast as possible, and will form the basis of our frame rate - the faster the loop goes the higher the frame rate. This loop will use a simple Boolean flag to determine if it’s running; as soon as this variable goes false the loop terminates and we do something else (probably close the application down). Here’s the code that the sample program uses: Private Sub Form_Load() Me.Show '//Make sure our window is visible bRunning = Initialize() 'So you can see what happens... Debug.Print "Device Creation Return Code : ", bRunning Do While bRunning Render '//Update the frame... DoEvents '//Allow windows time to think; otherwise you'll '//get into a really tight (and bad) loop... Loop '//Begin the next frame... '//If we've gotten to this point the loop must have been terminated ' So we need to clean up after ourselves. This isn't essential, but it's ' good coding practise. On Error Resume Next 'If the objects were never created; ' (the initialization failed) we might get an ' error when freeing them... which we need to ' handle, but as we're closing anyway... Set D3DDevice = Nothing Set D3D = Nothing Set Dx = Nothing Debug.Print "All Objects Destroyed" '//Final termination: Unload Me End End Sub As you can see straight away this code is all in the Form_Load event, which isn’t the optimal place to put it. Whenever I design a bigger project I always put the control loop as a Sub Main() in a separate control module and leave the form completely empty - and then all the subsequent code goes in classes (one for graphics, utilities, maths, audio, physics, AI, Input, File handling and so on). The first step is to make sure the form is visible, normally this won't happen until this procedure has finished (which in our case wont happen till the program terminates). As already mentioned, Direct3D wont function properly if the form isn’t visible or isn’t loaded. Next we initialize Direct3D. We place it’s return value in the Boolean on/off switch (instead of the original method I showed you); the beauty of this is that if it returns false on the first pass of the loop it’ll terminate itself. In the middle we have the main loop, a Do While … Loop structure. At the moment it’s made up of two statements; these will be the only two statements executed for the vast majority of runtime. Place any additional calls or statements that you want processed on a frame by frame basis. The key part here is to have a DoEvents call at the end of the loop, without it your program will go down the pan very quickly - and in most cases lock up the system. If this statement is not in here we won't receive any messages, no input (keyboard, mouse) and the chances are that pure VB language statements will not be executed properly. The DoEvents yields time for the system (Windows in this case) to think about things and do whatever it sees fit. If other programs are running then they’ll have their time now, and if you’ve asked Windows to do anything it’ll probably happen now. Lastly we have the termination code, as noted in the extract this is not necessarily required - the DirectX library will see that objects are destroyed safely - but you can never be sure from computer to computer, so it’s best to do it for yourself. As with all other COM based interfaces destroy them in the reverse order from which they were created. Render()You should have noticed in the main loop code above that there was a call to the Render() function on every pass of the main loop. It’s this code that actually presents the graphics on the screen - and processes anything relevant to how the graphics are displayed. For this sample this code isn’t going to do anything greatly exciting - this is our first DirectX Graphics application after all. The next couple of articles will explain the more interesting parts… Public Sub Render() '//1. We need to clear the render device before we can draw anything ' This must always happen before you start rendering stuff... D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, &HCCCCFF, 1#, 0 'the hexidecimal value in the middle is the same as when you're using ' colours in HTML - if you're familiar with that. '//2. Next we would render everything. This example doesn't do this, ' but if it did it'd look something like this: D3DDevice.BeginScene 'All rendering calls go between these two lines D3DDevice.EndScene '//3. Update the frame to the screen... ' This is the same as the Primary.Flip method as used in DirectX 7 ' These values below should work for almost all cases... D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 End Sub Not too complicated, but trust me, it gets bigger and bigger as we start adding new stuff. The render procedure always follows the same pattern - Clear, Draw, Display on screen. The clear part removes whatever was left in the frame buffers, then we draw everything - all our triangles, models and whatever else we fancy. Finally we update the screen - when this call is finished whatever we just drew will appear on the monitor. As you learn more about DirectX Graphics, and as these articles go on this function will be adapted and altered quite dramatically - it can get very big and quite complicated. One thing to always bear in mind about this procedure (and any others that you put in the main loop) is that they have to be fine tuned and as smooth as possible - any untidy or slow code will have a massive impact on the overall speed of your application. If each call takes 6ms to process, but something you do makes it take 10ms instead your frame rate will drop from 167fps down to 100fps - it’s still pretty fast, but assuming you have other things to do (AI, Audio, Physics and general gameplay) then this drop will be more significant.
|