Moving To Windows Part 2

Pheon

Well we've covered the bones needed for a Win32 application and a basic GDI Virtual frame buffer, now we shall extend this into something a bit more robust and extendable. Our goal is to create the beloved simplicity of mov ax, 13h; int 10h; mov edi, 0xa0000. We'll be creating a 'frame work' (noooo, don't run away) for various different 'Virtual Video memory drivers, including GDI and DDraw, and without further ado let's get down and dirty.

Why is the sky blue?

So why are we doing this framework bs? Well it's mainly to abstract the underlying API and hardware from the user, which promotes portability. So we could write a SVGALib, GGI or even a BeOS port and not have to change any of the higher level code... well at least in theory. It also allows us easier debugging and not having to mess with #define GDI or #define DDRAW kind of switches, and we need a recompile when changing rendering outputs. Something that's always handy when dealing with large amounts of source. In general it's a nice and simple way of creating a modular runtime 2d graphics interface.

The Framework

So what are the basics of a 2D graphics interface?

1. Write stuff to it.
2. Page flip.
3. Clear the frame.
4. If you're insane read stuff from it.

The Clear probably isn't as important and certainly isn't always used ie. feedback effects, Dirty rectangles, etc. so we'll exclude it for now. Writing to the frame buffer is tricky, we can go for the lower level, Lock()/Unlock() and worry about pixel format and stride, or we can go for a less efficient 32bpp linear ARGB chunk of memory. We'll go for the latter, because it's simpler and before anyone has a cow, yes it isn't the most efficient way of doing things and it'll piss some your bandwidth down the hole but I want to make this as simple as possible and thus it's up to you to improve and extend it. You only get spoon feed for so long.

For our C interface we need generic global functions, we'll use function pointers. These are good because we can dynamically change each function runtime, i.e. just set a new function pointer. While we could use Class the aim of these tutes is bare bones as simple and clear as possible so we'll be going straight C all the time.

The guts of the Interface will be two functions, GetAddress(), which returns the top of our 'Virtual Video Page', e.g. for mode 0x13 it would simply return 0xa0000, and Flip(), which copies and converts our 32bpp Virtual Video Page to whatever the display is and shows it to the world. We also need to initialize and release resources which will be done in Init() and Kill() respectively.

Function Fetish

The functions are pretty straight forward, we'll make them types.

First we create a universal Pixel format, 32bpp ARGB:

    typedef struct
    {
        unsigned char a;
        unsigned char r;
        unsigned char g;
        unsigned char b;
    } Color32_t;

The Framework functions.

    typedef int               VV_Init_f( void );
    typedef void              VV_Kill_f( void );
    typedef void Color32_t *  VV_GetAddress_f( void );
    typedef void              VV_Flip_f( void );

Now we'll make a 'Driver' of this, so we can group different output types, i.e. GDI, or DDraw, selectable into their own structure.

    typedef struct
    {
         char            *Description
         VV_Init_f       *Init;
         VV_Kill_f       *Kill;
         VV_GetAddress_f *GetAddress;
         VV_Flip_f       *Flip;
    } Driver_t;

So what do these things do?

         char    *Description

This gives some human readable description of what the driver is, better than having just 'Driver 0', 'Driver 1' etc. in a user interface.

         VV_Init_f       *Init;
         VV_Kill_f       *Kill;

These functions prime the driver for use. They typically init the API or allocate the resources required.

         VV_GetAddress_f *GetAddress;
         VV_Flip_f       *Flip;

And these this is the guts of our framework, they provide the methods for displaying 'stuff'.

Code dam you

Now we've got the data structures out of the way, let's hit the code. Going back to our tute1 project, we'll add a new file called VVideo.cpp, short for Virtual Video. Now we could just shove it into Winmain.cpp but it will be a lot cleaner to make a separate module of it, even though its body won't be very large.

Firstly we need to setup our 'interface' to the rest of the application, we'll do this by creating four global function pointers that will behave exactly like global functions to the application.

    // Our Framework Interface
    VV_Init_f       *VV_Init        = NULL;
    VV_Kill_f       *VV_Kill        = NULL;
    VV_GetAddress_f *VV_GetAddress  = NULL;
    VV_Flip_f       *VV_Flip        = NULL;

We'll prefix it with VV because we are in the file VV(ideo) and it gives the user an indication of where and what the functions do. Now we need to fill these functions out with 'stuff' usually different driver functions. But firstly we must catalog each driver's functions, we'll use the Driver_t structure above for this. Using the GDI functions from tute1, we'll create a single driver entry. Note that these functions will need to be modified for our universal pixel format Color32_t.

    // List of Drivers
    #define NUMBER_DRIVERS 1
    static Driver_t         m_DriverList[ NUMBER_DRIVERS ] =
    {
        {
            "GDI",
            GDI_Init,
            GDI_Kill,
            GDI_GetAddres,
            GDI_Flip
        }
    }
    static int              m_NumberDrivers = NUMBER_DRIVERS;

So we have one driver's functions, the GDI Driver. Now we need a way to select them, as there is a 1-1 mapping of the global function pointers to the Driver_t structure all we do is fill out the global function pointers we defined above with the drivers Driver_t function pointers. As we only have one driver this seems nearly trivial but this is a general robust frame work and we must go the extra mile. Here's the code, DriverNo is the driver number we want to set.

    // set functions
    VV_Init         = m_DriverList[ DriverNo ].Init;
    VV_Kill         = m_DriverList[ DriverNo ].Kill;
    VV_GetAddress   = m_DriverList[ DriverNo ].GetAddress;
    VV_Flip         = m_DriverList[ DriverNo ].Flip;

When we set a driver's function pointers it usually means we want to use the driver. Setting the function pointers only halfway there, we also need to initialize the new driver and kill the previous driver (if one was set). This must be done before the VV interface can be used.

Firstly we see if a previous interface has been used, and if so it needs to be killed before the initialization of the new one.

    // has a previous driver been set?
    if (VV_Kill != NULL)
    {
        // need to shut down the previous driver
        VV_Kill();
    }

Then copy the DriverNo's Interface to the current global Interface.

    // set functions
    VV_Init         = m_DriverList[ DriverNo ].Init;
    VV_Kill         = m_DriverList[ DriverNo ].Kill;
    VV_GetAddress   = m_DriverList[ DriverNo ].GetAddress;
    VV_Flip         = m_DriverList[ DriverNo ].Flip;

Next we must Initialize the Interface before it can be used, typically doing things like allocating memory for its back buffer or DirectDraw initialization.

    // Initialize the driver
    VV_Init();

Then save what the current driver is for later reference.

    // Save driver no
    m_CurrentDriver = DriveNo;

We need other helper functions like, getting the number of drivers, and retrieving driver descriptions for user selection which are all pretty straight forward, so I won't dribble shit about them, just look at the code.

   /**** Snip *****/

   /*************************************************************************
   *
   *       Title:  VVideo.cpp
   *       Desc:   Provides a Virtual Video Page frame work, for use with
   *               various rendering methods
   *
   *       Note:
   *
   *************************************************************************/

   #include <Windows.h>
   #include "Windmain.h"
   #include "VVideo.h"
   #include "GDI.h"        // described later on in the text

   /*************************************************************************
   // External Data
   /************************************************************************/

   /*************************************************************************
   // Global Data
   /************************************************************************/

   // Interface
   VV_GetAddress_f         *VV_GetAddress  = NULL;
   VV_Flip_f               *VV_Flip                = NULL;

   /*************************************************************************
   // Module Data
   /************************************************************************/

   // the list
   static Driver_t         m_DriverList[ 1 ] =
                           {
                                   {       "GDI",
                                           GDI_Init,
                                           GDI_Kill,
                                           GDI_GetAddres,
                                           GDI_Flip,
                                   }
                           }
   static int              m_NumberDrivers = 1;

   // current driver
   static int              m_CurrentDriver = -1;

   /*************************************************************************
   // Functions
   /************************************************************************/

   /*************************************************************************
   *
   *       Function: VV_Init()
   *
   *       Desc:     Initlizes anything
   *
   *       Notes:    1) Currently this is nothing, but will used soon
   *
   *************************************************************************/
   void VV_Init( void )
   {
   }

   /*************************************************************************
   *
   *       Function: VV_Kill()
   *
   *       Desc:     Kills anything
   *
   *       Notes:    1) this also is nothing. wow. spectacular
   *
   *************************************************************************/
   void VV_Kill( void )
   {
           // was a selected
           if( VV_Kill != NULL)
           {
                   // close the current driver
                   VV_Kill();
           }
   }

   /*************************************************************************
   *
   *       Function: VV_GetNumberDrivers()
   *
   *       Desc:     Gets the number of drivers we have
   *
   *       Notes:
   *
   *************************************************************************/
   int VV_GetNumberDrivers( void )
   {
           return m_NumberDrivers;
   }

   /*************************************************************************
   *
   *       Function: VV_GetCurrentrDrivers()
   *
   *       Desc:     Gets the currently selected driver
   *
   *       Notes:    1) returns -1 if no driver has been selected
   *
   *************************************************************************/
   int VV_GetCurrentDriver( void )
   {
           return m_CurrentDriver;
   }

   /*************************************************************************
   *
   *       Function: VV_GetDriverDesc()
   *
   *       Desc:     Gets a description on the specified driver
   *
   *       Notes:
   *
   *************************************************************************/
   char *VV_GetDriverDesc( int DriverNo )
   {
           // Bounds check
           if ( (DriverNo < 0) || (DriverNo >= m_NumberDrives) ) return NULL;

           // get Drivers decryption
           return m_DriverList[ DriverNo ].Description;
   }

   /*************************************************************************
   *
   *       Function: VV_SetDriver()
   *
   *       Desc:     Sets the current driver
   *
   *       Notes:
   *
   *************************************************************************/
   void VV_SetDriver( int DriverNo )
   {
           // Bounds check
           if ( (DriverNo < 0) || (DriverNo >= m_NumberDrives) ) return;

           // has a previous driver been set?
           if (VV_Kill != NULL)
           {
                   // need to shut down the previous driver
                   VV_Kill();
           }

           // set functions
           VV_Init         = m_DriverList[ DriverNo ].Init;
           VV_Kill         = m_DriverList[ DriverNo ].Kill;
           VV_GetAddress   = m_DriverList[ DriverNo ].GetAddress;
           VV_Flip         = m_DriverList[ DriverNo ].Flip;

           // Initialize the driver
           VV_Init();

           // save driver no
           m_CurrentDriver = DriverNo;
   }

Virtual Video ain't a budget porn movie

And that's just about it for VVideo.cpp, now it's time to integrate our previous GDI drawing routines to fit this model.

If you've read Tute1 then you will see it fits very well (mmm tight) and all we need to do is put these functions into a header, GDI.h, and include them into VVideo.cpp.

   /*************************************************************************
   *
   *       Title:  GDI.h
   *       Desc:   GDI Header file
   *
   *       Note:   1) This is a boring header file isn't it?
   *
   *************************************************************************/
   #ifndef GDI_H
   #define GDI_H

   /*************************************************************************
   // Global Data
   /************************************************************************/

   /*************************************************************************
   // Global Functions
   /************************************************************************/

   int             GDI_Init( void );
   void            GDI_Kill( void );
   void            GDI_Flip( void );
   unsigned int    *GDI_GetVPage( void );

   #endif //GDI_H

Well that was easy, now we must modify Winmain.cpp to initialize and select our framework. We'll do this in WinMain(), as we only have one driver, GDI, we'll 'hardwire' this value in.

        // Init Virtual Video Page
        VV_Init();

        // Select GDI Rendering
        VV_SetDriver( 0 ); // Hardwired 0 value here

        .
        .
        .

        // Application exit

        // Clean up
        VV_Kill();

We also need to set the new VPage frame buffer address, this is just changing GDI_GetVPage() to VV_GetAddress().

        // get virtual video page address
        m_VPage = VV_GetAddress();

That about wraps this tute up, the sample program we wrote in Tute1 will still work, only this time it's using a generic frame buffer architecture (wohowo hear the crowd roar). And that gray scrolling still looks really cool, we'll try make this a little more interesting in the future. Also the GDI code from tute1 assumes a BGRA 32Bpp format, so our Color32_t structure will be wrong, we'll fix this up next time.

Conclusion

Now we've created a framework for basic 2D operations, next we'll neaten this up, mainly move to a ARGB pixel format (I hate BGRA), put some detection code into GDI to determine the desktop's Bpp and do some color space conversion our self. Anyway I'm dribbling shit as usual and will stop now.

Pheon/Aaron Foo . pheons@hotmail.com