Jani Immonen (jani.immonen@inmarsoftware.com), November 12, 2001.
CTO, Co-Founder www.inmarsoftware.com
DieselEngine is a software development kit by Inmar Software Ltd. It is multi platform SDK that allows producing real-time 2D and 3D graphics, along with input mapping and sound engine with 3D sound. DieselEngine SDK is free for non-commercial use, and its demo version downloadable at: www.inmarsoftware.com. Demo version contains all features that licensed version, and works equally well when following this set of articles.
I'm planning to write several articles. At this point I have something like this in my mind:
This is only the preliminary information and might change whatever comes along.
Some articles depend on each other. For example Article 2, Using Diesel3D requires knowledge of basic DieselGraphics services. I try to make articles about input and sound as independent as possible, but I might use some references to previous articles.
I hope to provide some internal information about engine, and try to use it as efficiently as possible. But in sake of simplicity I won't give much attention of error handling etc. so this is not an article of good programming practices, it is article of how to use DieselEngine SDK. You should also go thru every example application bundled with SDK, there is so much information commented on source code.
I expect basic knowledge of C++ programming, by basic knowledge I mean classes, deriving classes and virtual functions. If these areas of C++ programming are not in good shape, following these articles might be difficult.
Also I expect that developing environments are already set. If you can compile the DieselEngine SDK example programs, everything is fine, if not see DieselEngine SDK documentation for details how to set up the developing environments.
OK, that's enough for preface, let's get close up and personal with DieselEngine.
In this article we are going to create DieselEngine application for PocketPC, and we use SDK version 1.04. If you are using older release of SDK, please update. Although we create PocketPC application, all information provided applies equally to other supported platforms, and if not it is clearly stated. We'll be using the Microsoft eMbedded Visual C++ 3.0. I'll call it EVC from now on.
At first I though of that we should create new application from scratch, but second though: why would we do that? DieselEngine SDK has these very handy application wizards to do the job. For good tutorial of how to use Diesel Application Wizards, see SDK documentation. But, I'll walk you thru now.
You'll notice that wizard created one class called CFirstDieselApp and also created the Main.cpp file that contains the WinMain function. In addition the 'resource.h' is created to contain application icon.
Now make sure that 'Active WCE Platform' combo box says 'PocketPC' (in WCE it is by default H/PC Pro 2.11).
Open the 'FirstDieselApp.h' file and notice that wizard generated some functions even that you didn't select them from virtual functions step of wizard. These are: OnInitDone, OnExit and OnFlip.
This function is very important. The IDieselApplication object calls this virtual function when it has finished all initialisation of display device and internal surfaces etc. This is the best place to allocate your own resources such as surfaces etc. You also have possibility to terminate application by returning something else than DE_OK, for example if application runs out of memory while allocation resources. The return value returned by OnInitDone function is passed thru as return value to Startup function, which resides in 'Main.cpp' file.
This is the counterpart to OnInitDone function and is called by IDieselApplication when it is just about to release all internal structures and the display device. This is the best place to release all your application resources.
This is your application main loop. The OnFlip function is called by IDieselApplication just before it draw contents of back buffer to device screen. Go to the 'FirstDieselApp.cpp' file and see the implementation of this function. The first line in function is m_srfBack.Fill(0, NULL). The m_srfBack is CDieselSurface object and is IDieselApplication class member.
The m_srfBack member is essential part of engine. Whatever you draw into the m_srfBack will be copied to screen after OnFlip returns.
Well, on PocketPC platform surface is just a rectangular block of memory owned by CDieselSurface object. Surface has lots of manipulation functions to copy and produce effects to surfaces.
All functions that copy surface to another (one way or another) are called Blitting functions. Surface object supports several formats: 8 bit palettized, 16 bit 555, 16 bit 565 and 32 bit full color surfaces. The 24 bit surface is not supported, and some effects do nothing with 8 bit surfaces. Surface do not support format conversions, so when using any of blitting functions, surfaces must be in same format. Fortunately there is handy format called eDE_COMPATIBLE, which creates surface to same format as application back buffer object. Surface object has automatic clipping, which can be disabled by setting DE_BLTNOCLIP flag to blitting functions.
The one blitting function which is used the most is the CDieselSurface::BltFast function:
To those who have used the Microsoft DirectX, this is very familiar, actually one design goals was to make DieselEngine very accessible to programmers who know DirectX. Along the way you'll notice a lot of similarities, and similarities won't stop there:when building DieselEngine application for Desktop PC, CDieselSurface has function: GetDirectDrawSurface() which returns pointer to actual IDirectDrawSurface interface. But this is article of PocketPC development, so we'll not go into it any further. On PocketPC builds GetDirectDrawSurface() always returns NULL (well, there is no DirectDrawSurface, is there?).
The BltFast function copies surface to another. It also supports some effects like color keying, flipping, mirroring and HalfAlpha, which is quite fast version of true alpha blending, producing 50% transparent surface copy. BltFast flags:
The BltFast cannot stretch or shrink the surfaces, for that there is a function called Blt:
This function is extended version of BltFast function. It can stretch or shrink the surface while copying it to another. This function also supports the same flags as the BltFast counterpart, and in addition the flag DE_BLTQUALITYSTRETCH that interpolates the neighbouring colours while stretching or shrinking the surface. The quality stretch is not meant to use in real time.
There are a lot more blitting functions available such as BltRotate, AlphaBlend and BltEffect but will get into those later. Also there are functions that operate to surface itself, for example: GradientFill, FadeRGB and Fill.
Ok, there was some background about surfaces, now lets do something with our brand new generated application.
Now you have the generated application framework, and basically you can compile and execute it if you want, but don't do it just yet. One quite important thing is missing. The application has no way of terminating and will require reset of PocketPC device to shut it down! If you followed the instructions on using the wizard you should have function called OnKeyDown in your application class. Add the following code to function:
Note, there is some WIN32 specific code, the key code (VK_RETURN) will not be the same with upcoming EPOC release. But anyway, now whenever any button is pressed, the OnKeyDown function is called. The parameter dwKey tells the virtual key code of pressed button. Every PocketPC should have VK_RETURN automatically mapped, for example on Compaq iPaq device it is button in center of directional control button.
Now, when button is pressed we call IDieselApplication::Shutdown function. This function starts to release all internal resources, and in some point calls the virtual OnExit function as described earlier. After that the IDieselApplication::Run function at Main.cpp returns, and application exits the WinMain.
Now we have way of exiting the application, so compile and execute it, although it is a bit boring application:just black screen with nothing on it, but bear with me, we'll add some action shortly.
Many times the first thing we need is way to draw text to screen. CDieselSurface class has means to draw on it with windows GDI functions with GetDC and ReleaseDC functions, but access to GDI is provided only for image loading etc. and is definitely not real time. Fortunate again, DieselGraphics has class called CDieselWriter that can draw text to screen quite efficiently.
Now, go to the 'FirstDieselApp.h' file and add new member object to CFirstDieselApp class. First add the include statement of DieselWriter:
Then add the member object to header:
Now open the 'FirstDieselApp.cpp' file and go to the OnInitDone function. Add following code inside.
Here, we start the writer object. First parameter is pointer to CDieselSurface object to use as font surface. We set it to NULL, which forces writer object to create internal 'default' font surface. You may give pointer to your own surface, which contains image of all 256 ANSI characters ordered into the 8x8 grid. More info about font surfaces is found in SDK documentations.
Second parameter is additional pixels between each character when writing the text. Using negative value makes strings more 'solid' and positive more 'fragmented' (don't know if those are good words to describe it). Experiment different values to see how it works.
Third parameter is length of 'space' characters in pixels. We'll use 12 here.
Next go to the OnExit function and add code:
Which shuts down the writer object. Most DieselEngine objects use Startup/Shutdown semantics on object creation and destruction, but some (like CDieselSurface) uses Create/Release. It is safe for all DieselEngine objects to call Shutdown or Release multiple times.
Now the writer object is ready for writing text. Add code to OnFlip function:
Note that WriteText function supports all BltFast flags as last parameter. It can also use some basic formatting on string like '\n' and '\t'.
Ok, compile and execute again to see the results.
CDieselSurface can load images from files, Diesel Media Packs and from application resource. The formats supported natively are windows bitmaps (BMP), JPEG compressed images (JPG) and gif images (GIF). Now lets load an image!
Create an image, sized 240x320 and save it as jpg or gif. This example uses file called 'diesel.jpg'. Copy the file to same folder on your PocketPC device where example application exe is. By default EVC uses '\Windows\Start Menu'.
Now add some code to CFirstDieselApp header:
Add code to OnInitDone function:
Here we use IDieselApplication function BuildFilepath. That function is made to simplify the creation of file name string. Most platforms handle file systems differently, so this function builds right kind of string pointing to file. It can also use subfolders, but they are always relative to application executable location. See SDK documentation for more information.
Then we use surfaces Load function to load the image. First parameter is file name string, second is pointer to SDE_SURFACEDESC structure. We set it to NULL, which creates the surface that will be compatible with application back buffer and has same dimensions as image we are loading. We could fill up the structure and pass it to Load function to force surface to load to different size than original, but now NULL is fine.
Other image loading functions work basically the same. If you wish, try to load a jpg image from application resource with LoadFromResource function instead of Load
And just as with writer object, add some code to OnExit function:
This frees memory allocated for surface.
Next we need to be able to see the surface. Add code to OnFlip function:
Add this code before the WriteText function call, so blitting the image won't override the text we write. In above code we are saying "Blit into the m_srfBack surface to coordinates 0x0. Use m_srfImage as source surface, and use entire area of source surface when blitting. No additional operations needed". I hope first three parameters need no additional explaining, but fourth is pointer to rectangle that defines the source area used when blitting. This makes possible to copy only portions of source surface. The last parameter is blitting flags described earlier. Just go ahead and experiment with blitting flags.
Now you can remove the line m_srfBack.Fill(0, NULL) from OnFlip function. There is no point of filling the back buffer surface when we are filling it completely with image.
Now, execute application again, and you should see something like this (with your own image of course):
Now we know how to load image into the surface, lets create an empty surface by hand. Loading image to surface effectively creates the surface, but now we create it manually. Add code to CFirstDieselApp class header:
Then go into the OnInitDone and add:
First thing to do when creating surface is to fill the SDE_SURFACEDESC structure. Here we create surface, which is 64 pixels wide, and 64 pixels height (you should always try to use square, powers of 2 sized surfaces. Trust me, it pays off later). The surface format is set to eDE_COMPATIBLE that creates surface that is same format as application back buffer.
The dwFlags member of structure may contain one of the following flags:
Ok, 0 for dwFlags is fine for now. After calling the Create function we call function called Fill. Fill takes colour value as first parameter, just note that it is in format independed form (XXRRGGBB). Second parameter is pointer to rectangle, which specifies area to fill, or NULL to fill entire surface.
Remember to add releasing code to OnExit function, just like you did with m_srfImage object.
Now, unlike the member name m_srfEmpty, surface is no longer empty. It is full of red color. Let's draw it to screen, go to OnFlip function and add following code:
We use the half alpha flag to show it off. Execute the app and you should see something like this:
Here we go, our nice little red surface drawn with alpha blending. Next we'll do something more radical:surface rotation:
For surface rotation we'll use surface object just created, just add code to OnFlip:
Again we are using half alpha, just to show that BltRotate supports it also. Now lets go thru what happens here.
First two parameters are coordinates where into the rotated image will be drawn. These coordinates mean middle point of rotated blit, not the upper left corner as other blitting functions.
Third parameter is pointer to source surface. Fourth parameter is rotation angle in radians.
Fifth parameter is radius of rotated image. This parameter makes it possible to simultaneously rotate and stretch / shrink the image. Because of internal implementation of BltRotate function, there is no extra cost for size change.
Sixth and the last parameter is same old blitting flags used with other blitting functions.
The next line after BltRotate is interesting. It uses the IDieselApplication internal timing services to make fAngle increase PI (defined in DieselMath.h, included automatically) radians per second independed of application frame rate. Many DieselEngine classes use this same variable to time the operations. Anything you multiply with this value will be translated to units per second. As conclusion m_fFrameTime member if time value it took for last frame to draw, in seconds. For example if application is running at 15 frames per second, the m_fFrameTime is approx. 0.066.
Execute the app to see results. Ok I admit it is little bit boring, but rotated surface could contain something else, like any image loaded from file.
Color keying is something that almost every graphics application needs. In DieselEngine, color keying is made as easy as possible. Lets create yet another surface, go to header and add new member:
Create the surface with same parameters as you did with m_srfEmpty, and then fill it with following code:
What we do here is fill the entire surface to blue, and then fill the 16x16 sized upper left corner with green. Next we set up the color keying:
I like the SetAutoColorKey function in particular. It reads the pixel color value from given coordinate, and sets it as transparent color for color keying. Function has default parameters (0, 0), so usually you don't have to set the parameters to function. There is also another function called SetColorKey, which takes actual transparent color value as parameter. That function is little bit more involved, because it needs surface format depended color value. Of course there is no problem if transparent color is black (0) or white (0x00ffffff).
Now, don't forget to add surface release into OnExit function. Now everything is set up, lets draw the color-keyed surface:
Add above code just before the rotation code. That way color keyed surface moves nicely under the alpha blended and rotated surface. If you change the DE_BLTSRCCOLORKEY to 0, you'll see the green upper left corner of surface.
Here is a screenshot of final step:
OK, here is source codes this far.
The Main.cpp file:
CDieselSurface class has a lot more features than I showed here. One very handy feature is automatic sub-frame system, which simplifies drawing animated sprites when all animation frames are stored in one surface. There are also more blitting functions, like Fade, AlphaBlend and their RGB counterparts. I won't go into details here, now you should have enough information to experiment with them by yourself. Remember, the DieselEngine SDK example programs are the best reference for DieselEngine programming, and SDK documentation also contains lots of useful information.
DieselGraphics has higher-level classes, like sprites and particle systems, and they all are basically build on top of IDieselApplication and CDieselSurface operations. For now sprites and particle systems are out of this articles scope, but if you would like to have an article specialising into them, please let me know.
That's it for now, Next time we're going to Startup the CDiesel3DDevice object, and you'll notice that using 3D graphics has never been easier.