$S@g·, g@S$$$ $$$$$$+-ù ú. `@g, .ú- -+g@ ½üýø^_ +-Ä- - $$$$$$-- -Ä-$b,`$-- --- y '-- Ä- -$$ g@S$$$-Ä- ---+ «¬¬«¬ $$$$$$ ¬¬«« $$l $ ¬¬«¬« $ «¬«¬¬«¬ $$ $$$$$$ «¬«¬¬« ¬¬«¬« $$$$$$ ¬«¬¬ $$$ l «¬«¬« $ ¬«¬¬«¬« $$ $(dt)$ ¬«¬¬«¬ +-- Ä -l$$ü$$-Ä-- -$$$ $Ä- --- $`+,._ _,$l $$$$$$-- Ä- -+ $$$y$$ Sü',$ ü $$,øý½SQ$ ½üýø^~ ,g@$$ ,g@S@, $$$l `ýÓ*S,_ _`*S$$b._ ,SSü' This article comes from Hugi issue 17 ú Released in September 1999 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ MOVING TO WINDOWS PART 3 PHEON ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 its 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 its 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. Its 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