Moving To Windows Part 3

Pheon

Hidey ho stranger, last time we wrote most of the VV frame work and shoved our GDI code from tute1 into it. Now we must clean up some of the mess and formalize everything, starting with that nasty BGRA 32Bpp pixel format.

BGRA Bullocks

As you might have noticed, if you set a Color32_t to equal (0, 0xff, 0, 0) i.e. straight red then it would come out as green! This occurs because GDI 32bpp format is BGRA and our Color32_t format is ARGB, to remedy this we must do some color space conversions.

To convert BGRA to ARGB is very simple, all we do is swap the byte order. This can be accomplished by using shifts and masks.

     // convert from 32bpp ARGB to BGRA
     ARGB = ((b<<24)&0xff000000) + ((g<<16)&0x00ff0000) +((r<<8)&0x0000ff00);

We could be more cunning and just copy the bytes over but I'll leave that one up to you. Next we must do this for all the pixels in our VPage and place them in a separate memory page. Using data pointers we'll loop over every pixel, convert, then write it. This will all be done in the Flip() routine as Flip is called when the frame is complete and thus also ready for conversion.

    void GDI_Flip( void )
    {
        RECT client;
        HDC hDC;
        int i;
        unsigned short *WData;
        unsigned int *DData;
        Color32_t *VPData;
        unsigned int *DIBData;

        // set up pointers to destination and source
        DData = m_DPage;
        VPData = m_VPage;

        // for all pixels
        for (i=0; i < 320*240; i++)
        {
            // convert from 32bpp ARGB to BGRA
            *DData = ((VPData->b<<24)&0xff000000) +
                     ((VPData->g<<16)&0x00ff0000) +
                     ((VPData->r<<8)&0x0000ff00);

            // next pixel
            DData++;
            VPData++;
        }

Where m_DPage is our second BGRA memory page, m_VPage (from tute2) is our ARGB Virtual Page.

Now instead of using m_VPage as the data source for our DIB we use m_DData. As we'll be extending the GDI interface for 16bpp, I'll assign DIBData to m_DData and use DIBData for the StreachDIBs() call.

        // set DIB's Source data
        DIBData = m_DPage;

        //
        // 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
                           DIBData,       // Source's data

                           &m_bi,         // Bitmap Info
                           DIB_RGB_COLORS, // operations
                           SRCCOPY);

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

And that's it, we have our lovely ARGB 32Bpp format and when you write Color32_t.r = 0xff you actually get a Red pixel!

Methamphetaspeed

Speed is good, I like speed and although our Framework isn't the most speedy thing letting GDI do color space conversions just don't cut it. I mentioned when using GDI you want to use a DIB the same pixel depth as the current desktop, if you don't believe me try it and watch it crawl. So what do we do? Well detect the desktop bpp and create and use a DIB of that depth. We'll ignore 8bit and assume 16bpp or 24/32bpp.

To detect the desktop's bpp use GetDeviceCaps() with the BITSPIXEL parameter, it needs a DeviceContex to work which we covered back in tute1 so I'll just paste the code.

        HDC hDC;

        // get the windows Device Context
        hDC = GetDC( g_hWnd );
        if (hDC == NULL)
        {
            return 1;
        }

        // work out what the desktop bpp is
        m_Bpp  =  GetDeviceCaps( hDC, BITSPIXEL);

        // close resource
        ReleaseDC( g_hWnd, hDC );

So once we have set our module variable m_Bpp we setup the Bitmap Header Info as follows:

        // Set most of the header info fields
        memset( &m_bi.bmiHeader, 0, sizeof(m_bi.bmiHeader) );
        m_bi.bmiHeader.biSize = sizeof(m_bi.bmiHeader);
        m_bi.bmiHeader.biWidth = 320;
        m_bi.bmiHeader.biHeight = -240;     // NOTE: -240. this is
        m_bi.bmiHeader.biPlanes = 1;        // because DIBs are upside down
        m_bi.bmiHeader.biBitCount = m_Bpp;
        m_bi.bmiHeader.biCompression = BI_BITFIELDS;

Now because we are supporting multiple bit depths our Color masks and VPage memory will be different. Using two memory pointers we'll specify a 16bpp W(ord)Page and a 32bpp D(word)Page respectively.

        static unsigned short           *m_WPage = NULL;
        static unsigned int             *m_DPage = NULL;

We'll allocate memory for these when specify our DIB Pixelformat (in GDI_Init()) using m_Bpp. It goes like this.

        // create a dib the same bit depth as the desktop, as GDI color space
        // conversion are very slow
        switch (m_Bpp)
        {
            // 16bpp
            case 16:
                // set format 555
                ((unsigned long*)m_bi.bmiColors)[0]=0x00007c00;
                ((unsigned long*)m_bi.bmiColors)[1]=0x000003e0;
                ((unsigned long*)m_bi.bmiColors)[2]=0x0000001f;

                // allocate 16 GDI memory
                m_WPage = (unsigned short *)malloc( 320 * 240 *
                                                    sizeof(unsigned short) );
                    break;

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

                // allocate 32bit GDI memory
                m_DPage = (unsigned int *)malloc( 320 * 240 *
                                                  sizeof(unsignedint) );
                break;

            // unknown
            default:
                return 1;
        }

It's fairly straight forward, we treat 24bpp as 32bpp as they are very similar, and we'll return an error code if we don't understand the pixel format.

16 Bottles of Beer on the screen

Once we've got this 16bpp 555 DIB, we need to convert our 32bpp 8888 format to this. It's quite straight forward, simple bit shifting and masking does the job.

        *Data = ((b>>3)&0x001f) + ((g<<2)&0x03e0) + ((r<<7)&0x7c00);

I've put heaps of brackets around everything because C/C++ has some less intuitive operator precedence rules and it's always best to be sure. We have to convert all pixels and will use basically the same loop as the above BGRA->ARGB conversion. This will also be done in the Flip() routine and is pretty simple so hears the New GDI_Flip() routine. We get the desktop's bpp and convert our VPage based on that then StreachDIBs() it as per usual.

   /*************************************************************************
   *
   *       Function:       GDI_Flip()
   *
   *       Desc:           Copies the virtual page to the window
   *
   *       Notes:
   *
   *************************************************************************/
   void GDI_Flip( void )
   {
           RECT client;
           HDC hDC;
           int i;
           unsigned short *WData;
           unsigned int *DData;
           Color32_t *VPData;
           unsigned int *DIBData;

           // convert the page to whatever format the desktop is at
           switch (m_Bpp)
           {
                   // 16bpp
                   case 16:

                           // set pointers to top of destination and source
                           // memory
                           WData = m_WPage;
                           VPData = m_VPage;

                           // for all pixels convert
                           for (i=0; i < 320*240; i++)
                           {
                                   // convert VPage 32bpp 8888 data into
                                   // 16bit 555
                                   *WData = ((VPData->b>>3)&0x001f) +
                                            ((VPData->g<<2)&0x03e0) +
                                            ((VPData->r<<7)&0x7c00);

                                   // next pixel
                                   WData++;
                                   VPData++;
                           }

                           // set the DIB's source data
                           DIBData = (unsigned int *)m_WPage;
                           break;

                   // 24/32 bpp
                   case 24:
                   case 32:
                           // set up pointers to destination and source
                           DData = m_DPage;
                           VPData = m_VPage;

                           // for all pixels
                           for (i=0; i < 320*240; i++)
                           {
                                   // convert from 32bpp ARGB to BGRA
                                   *DData = ((VPData->b<<24)&0xff000000) +
                                            ((VPData->g<<16)&0x00ff0000) +
                                            ((VPData->r<<8)&0x0000ff00);

                                   // next pixel
                                   DData++;
                                   VPData++;
                           }

                           // set DIB's Source data
                           DIBData = m_DPage;
                           break;
           }

           //
           // 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);

                  // Streach 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

                                  DIBData        // Source's data
                                  &m_bi,         // Bitmap Info
                                  DIB_RGB_COLORS, // operations
                                  SRCCOPY);

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

Natural Selection

One thing that hasn't been addressed at all is driver selection. Currently we have:

VV_SetDriver( 0 );

Which certainly isn't the best solution. A better idea is to save this in a configuration file, we'll call it VVConfig.cfg. The basic idea is to save the Driver number in that file and read and use it during initialization. It's pretty simple, we create a function called GetDriver() which returns the saved driver number. It opens the file, searches for a "Driver" string, then uses the number after that as the driver number we want.

   /*************************************************************************
   *
   *       Function:       GetDriver()
   *
   *       Desc:           Gets the driver number saved in VVConfig.cfg.
   *
   *       Notes:          1) if no file is found will use GDI
   *
   *************************************************************************/
   static int GetDriver( void )
   {
           FILE *File;
           int Driver;
           char buf[256];

           // default to GDI
           Driver = 0;

           // open config file
           File = fopen( "VVConfig.cfg", "rt" );
           if (File)
           {
                   // get string
                   fscanf( File, "%s", &buf );

                   // search for Config string
                   if (strcmp(buf, "Driver") == 0)
                   {
                           // get driver setting
                           fscanf( File, "%i", &Driver );
                   }

                   // clenaup
                   fclose( File );
           }

           return Driver;
   }

We also have to use this function, which is pretty simple. I.e. instead of

    // select GDI Device
    VV_SetDriver( 0 );

we use:

    // select GDI Device
    VV_SetDriver( GetDriver() );

This just makes things convenient when we have multiple drivers, just edit the file and it will work, no need to recompile, can be done anywhere (does not require a compiler) and hell it doesn't even require a coder.

Conclusion

Well it's a short one today but we've got a solid frame work now and are ready for DirectDraw in the next tute. I've spiced up the effect, those shades of gray were just getting far to interesting. It's a simple sort of free directional tunnel effect, not the most efficient way of doing but it's easy to muck around with and do a textured /insert any geometric object that can be defined as a function of x, y, z/. Anyway have fun and get ready for DirectDraw.

Pheon/Aaron Foo . pheons@hotmail.com