Moving To Windows Part 1

Pheon

Well we've covered the Win32 essentials in our first tute, now let's get into something a little more useful. We'll be adding a GDI 'frame buffer' to our application and draw something really fancy like a pixel! Anyway let's get to it.

GDI

Let's start and create a 'Virtual Frame Buffer', this will be our 'Back Buffer' and is where we do all our drawing. It does not get displayed on the window, that's the 'Front buffer', so we can modify it without the user knowing. If the user did see it they will see when individual pixels get written instead of seeing the final product, a completed frame. The 'Front Buffer' on the other hand is what the user can see. When we've finished writing to the Back Buffer we 'Flip' the Back Buffer to the Front Buffer, this is known as 'Page Flipping' and allows a smooth transition between the currently displayed image and the image we've just created.

For GDI our Front Buffer is the Win32 Window created in Tute0, and the Back Buffer we'll create by simply allocating a 320x240x32bpp piece of memory and call it our VPage (Virtual Page). We'll use the name Virtual Page so we don't get confused with implementation specific details like a DirectDraw back buffer.

Creating the back buffer is just a simple malloc.

    // allocate back buffer
    g_VPage = (unsigned short *)malloc( 320*240*4 );

So we use this just like a linear frame buffer, no banking, no weird stride, no dogey pixel format, just a nice flat 8888 (BGRA) frame buffer, simple. Personally I prefer ARGB but Win98 and WinNT DIBs only support BGRA, we'll deal with color space conversions in a different tute. Now before you jump up and down and shout '32bpp MY ARSE! Use 16bpp!', 32bpp is much nicer to work with and this is meant to be a simple tutorial with less emphasis on speed and performance and more on ease of use.

The heart of our GDI VPage will be the StretchDIBits() function. We create a DIB (Device Independent Bitmap) with its memory as our VPage, then let Win32 stretch/squash/mangle/whatever this image into the Win32 window we've created. This creates a very flexible, safe, and easy to debug frame buffer that will always work, i.e. doesn't require other packages like DirectX to be installed to work.

Here's the setup code:

    // create a 32bit color DIB
    memset( bih, 0, sizeof(bih) );
    bih.biSize=sizeof(bih);
    bih.biWidth=320;
    bih.biHeight=-240;                      // NOTE: -240. this is because
    bih.biPlanes=1;                         // DIBs are upside down
    bih.biBitCount=32;
    bih.biCompression=BI_BITFIELDS;
    bih.biSizeImage=0;
    bih.biXPelsPerMeter=0;
    bih.biYPelsPerMeter=0;
    bih.biClrUsed=0;
    bih.biClrImportant=0;

    // setup the pixel format our DIB is in

    // 32bit color
    ((unsigned long*)bi.bmiColors)[0]=0x00ff0000;
    ((unsigned long*)bi.bmiColors)[1]=0x0000ff00;
    ((unsigned long*)bi.bmiColors)[2]=0x000000ff;

One gotcha you should look out for is Win32 GDI color space conversions, they are extremely slow, so you should always use the same bpp as the Win32 Desktop, i.e. use a 32bpp DIB if the Win32 desktop is set to 32bpp.

Now to display aka flip our Virtual Page we first get a Win32 Device Context. This is used for performing GDI 'graphics' operations on the window, e.g. Line, Circle, etc. and is usually quite slow. There are an extremely limited number of DCs (Device Contexts) so always make sure you release them when you're finished. It's also good programming practice to check if we got a DC, there will be cases where this fails, as unusual as they are it's best to be sure.

    // get the windows Device Context
    hDC = GetDC( g_hWnd );

    // if it's available
    if (hDC != NULL)
    {
        .
        .
        .

        // now release the Device Context.
        ReleaseDC( g_hWnd, hDC );
    }

Next we get the current dimensions of our window and perform the blt with StreachDIBits(), this function will magnify/reduce/sheer/scale whatever our DIB to the area described. You can find out more in the Win32 help files. StreachDIBits() can do other things besides a plain copy, like color keying, but we want to maximize its performance and thus do the simplest thing possible.

    // Get the window dimensions
    GetClientRect(g_hWnd, &client);

    // Stretch it
    StreachDIBits( hDC,
                   0,              // Destination top left hand corner X Pos
                   0,              // Destination top left hand corner Y Pos
                   client.right,   // Destinations Width
                   client.bottom,  // Destinations Height

                   0,              // Source top left hand corner's X Pos
                   0,              // Source top left hand corner's Y Pos
                   320,            // Sources Width
                   240,            // Sources Height

                   g_VPage,        // Source's data
                   &bi,            // Bitmap Info
                   DIB_RGB_COLORS, SRCCOPY); // operations

Well that's basically all there is to a simple GDI frame buffer, now let's put it into our project. We'll create a file called surprisingly GDI.cpp, with functions:

        GDI_Init();
        GDI_Kill();
        GDI_Flip();
        GDI_GetVPage();

Where Init/Kill will handle the DIB creation/destroying, GDI_Flip() will copy the contents of the virtual page to the window, and GDI_GetVPage() will return a pointer to the top left-hand corner of the linear virtual page.

**Snip**

   /*************************************************************************
   *
   *       Title:  GDI.cpp
   *       Desc:   Provides a GDI based virtual frame buffer interface
   *
   *       Note:   1) This is only 32bpp
   *
   *************************************************************************/

   #include <Windows.h>
   #include <memory.h>
   #include "GDI.h"
   #include "Winmain.h"

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

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

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

   static  HWND                    m_hWnd;

   // our virtual page
   static  unsigned int            *m_VPage = NULL;

   static  char                    m_bibuf[ sizeof(BITMAPINFOHEADER) + 12 ];
   static  BITMAPINFO              &m_bi = *(BITMAPINFO*)&m_bibuf;
   static  BITMAPINFOHEADER        &m_bih = m_bi.bmiHeader;

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

   /*************************************************************************
   *
   *       Function: GDI_Init()
   *
   *       Desc:     Sets up our DIB and Virtual page
   *
   *       Notes:
   *
   *************************************************************************/
   int GDI_Init( void )
   {
       // create a 32bit color DIB
           memset( &m_bih, 0, sizeof(m_bih) );
       m_bih.biSize = sizeof(m_bih);
       m_bih.biWidth = 320;
       m_bih.biHeight = -240;                  // NOTE: -240. this is because
       m_bih.biPlanes = 1;                     // DIBs are upside down
       m_bih.biBitCount = 32;
       m_bih.biCompression = BI_BITFIELDS;
       m_bih.biSizeImage = 0;
       m_bih.biXPelsPerMeter = 0;
       m_bih.biYPelsPerMeter = 0;
       m_bih.biClrUsed = 0;
       m_bih.biClrImportant = 0;

       // setup the format our DIB is in

       // 32bit color
       ((unsigned long*)m_bi.bmiColors)[0]=0x00ff0000;
       ((unsigned long*)m_bi.bmiColors)[1]=0x0000ff00;
       ((unsigned long*)m_bi.bmiColors)[2]=0x000000ff;

       // allocate vpage
       m_VPage = (unsigned int *)malloc( 320 * 240 * 4 );
       if (m_VPage == NULL)
       {
           return 1;
       }

       return 0;
   }

   /*************************************************************************
   *
   *       Function: GDI_Kill()
   *
   *       Desc:     Releases the Virtual pages memory
   *
   *       Notes:
   *
   *************************************************************************/
   void GDI_Kill( void )
   {
           // check we allocated it first
           if (m_VPage)
           {
                   free( m_VPage );
           }
   }

   /*************************************************************************
   *
   *       Function: GDI_Flip()
   *
   *       Desc:     Copyies the virtual page to the window
   *
   *       Notes:
   *
   *************************************************************************/
   void GDI_Flip( void )
   {
           RECT client;
           HDC hDC;

           //
           // draw it to our window
           //

           // get the windows Device Context
           hDC = GetDC( g_hWnd );

           // if it's available
           if (hDC != NULL)
           {
                 // Get the window dimensions
                 GetClientRect(g_hWnd, &client);

                 // Stretch it
                 StretchDIBits(  hDC,
                                 0,              // Destination top left hand
                                                 // corner X Position
                                 0,              // Destination top left hand
                                                 // corner Y Position
                                 client.right,   // Destinations width
                                 client.bottom,  // Destinations height
                                 0,              // Source top left hand
                                                 // corner's X Position
                                 0,              // Source top left hand
                                                 // corner's Y Position
                                 320,            // Sources width
                                 240,            // Sources height
                                 m_VPage,        // Source's data
                                 &m_bi,          // Bitmap Info
                                 DIB_RGB_COLORS, // operations
                                 SRCCOPY);

                 // now release the Device Context.
                 ReleaseDC( g_hWnd, hDC );
           }
   }

   /*************************************************************************
   *
   *       Function: GDI_GetVPage()
   *
   *       Desc:     Gets the virtual page's top left hand corners memory
   *                 address
   *
   *       Notes:
   *
   *************************************************************************/
   unsigned int *GDI_GetVPage( void )
   {
           return m_VPage;
   }

Conclusion

Well that's about it for GDI, there are problems with this, mainly the Desktop's bpp and this stupid BGRA format, which will rectify in the next tute. Until then if you have any comments/queries/problems/bugs don't hesitate to contact me.

Bye.

Pheon/Aaron Foo . pheons@hotmail.com