SIGGRAPH '97

Course 24: OpenGL and Window System Integration

OpenGL and Win32



Pixel Formats & Palettes

Pixel formats specify the properties of OpenGL contexts. Pixel formats in conjunction with palettes are the gateway through which an appropriate context for an application is created. Their use is described below.

Pixel Format Descriptor
Using Palettes

Example source code:
wglinfo.c
index.c



Pixel Format Descriptor

Setting the pixel format seems to be one of the more tricky parts of programming with OpenGL in Win32. This section should dispel most of the mystery surrounding the pixel format descriptor and the setting of pixel formats. A pixel format descriptor is the key to getting and setting pixel formats.

There are several functions that are used to manipulate pixel formats. They are as follows:
FunctionDescription
ChoosePixelFormat Obtains the device context's pixel format that is the closest match to a specified pixel format.
SetPixelFormat Sets a device context's current pixel format to the pixel format specified by a pixel format index.
GetPixelFormat Obtains the pixel format index of a device context's current pixel format.
DescribePixelFormat Given a device context and a pixel format index, fills in a PIXELFORMATDESCRIPTOR data structure with the pixel format's properties.
A lot of the time, the ChoosePixelFormat() function will be adequate to choose a pixel format, but when more precision in pixel format choice is needed, other methods must be employed. An excellent method of selecting a pixel format with specific properties is to enumerate them all and compare them against your own criteria. When one fits all the criteria, stop examining the rest of the formats (if any) and use the one that fit. Weights can even be added to certain criteria if need be. For example, if it was absolutely necessary that a color depth of 24 bits be used, but not so necessary that the depth buffer be 24 bits, the weights could be set accordingly. The following code illustrates this method. It only prints out information for those pixel formats that are OpenGL capable. Of course, when choosing a visual to render with, more criteria should probably be used (such as color depth, z-buffer depth and single/doublebuffering -- all the possible criteria are outlined below).
code defining the VisualInfo() function in wglinfo.c

/* VisualInfo() 
 *  Shows a graph of all the visuals that support OpenGL and their
 *  capabilities.  Just like (well, almost) glxinfo on SGI's.  
 */
void VisualInfo(HDC hDC)
{
    int i, maxpf;
    PIXELFORMATDESCRIPTOR pfd;

    /* calling DescribePixelFormat() with NULL args return maximum
       number of pixel formats */
    maxpf = DescribePixelFormat(hDC, 0, 0, NULL);

    /* print the table header */
 printf("   visual  x  bf lv rg d st  r  g  b a  ax dp st accum buffs  ms \n");
 printf(" id dep cl sp sz l  ci b ro sz sz sz sz bf th cl  r  g  b  a ns b\n");
 printf("-----------------------------------------------------------------\n");

    /* loop through all the pixel formats */
    for(i = 1; i <= maxpf; i++) {

	DescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

	/* only describe this format if it supports OpenGL */
	if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL))
	    continue;

	/* other criteria could be tested here for actual pixel format
           choosing in an application:
	   
	   for (...each pixel format...) {

	     if (pfd.dwFlags & PFD_SUPPORT_OPENGL &&
	         pfd.dwFlags & PFD_DOUBLEBUFFER &&
	         pfd.cDepthBits >= 24 &&
	         pfd.cColorBits >= 24)
	         {
	            goto found;
                 }
           }
	   ... not found so exit ...
	    
           found:
	   ... found so use it ...
	*/

	/* print out the information for this pixel format */
	printf("0x%02x ", i);

	printf("%2d ", pfd.cColorBits);
	if(pfd.dwFlags & PFD_DRAW_TO_WINDOW)      printf("wn ");
	else if(pfd.dwFlags & PFD_DRAW_TO_BITMAP) printf("bm ");
	else printf(".  ");

	/* should find transparent pixel from LAYERPLANEDESCRIPTOR */
	printf(" . "); 

	printf("%2d ", pfd.cColorBits);

	/* bReserved field indicates number of over/underlays */
	if(pfd.bReserved) printf(" %d ", pfd.bReserved);
	else printf(" . "); 

	printf(" %c ", pfd.iPixelType == PFD_TYPE_RGBA ? 'r' : 'c');

	printf("%c ", pfd.dwFlags & PFD_DOUBLEBUFFER ? 'y' : '.');

	printf(" %c ", pfd.dwFlags & PFD_STEREO ? 'y' : '.');

	if(pfd.cRedBits)        printf("%2d ", pfd.cRedBits);
	else printf(" . ");

	if(pfd.cGreenBits)      printf("%2d ", pfd.cGreenBits);
	else printf(" . ");

	if(pfd.cBlueBits)       printf("%2d ", pfd.cBlueBits);
	else printf(" . ");

	if(pfd.cAlphaBits)      printf("%2d ", pfd.cAlphaBits);
	else printf(" . ");

	if(pfd.cAuxBuffers)     printf("%2d ", pfd.cAuxBuffers);
	else printf(" . ");

	if(pfd.cDepthBits)      printf("%2d ", pfd.cDepthBits);
	else printf(" . ");

	if(pfd.cStencilBits)    printf("%2d ", pfd.cStencilBits);
	else printf(" . ");

	if(pfd.cAccumRedBits)   printf("%2d ", pfd.cAccumRedBits);
	else printf(" . ");

	if(pfd.cAccumGreenBits) printf("%2d ", pfd.cAccumGreenBits);
	else printf(" . ");

	if(pfd.cAccumBlueBits)  printf("%2d ", pfd.cAccumBlueBits);
	else printf(" . ");

	if(pfd.cAccumAlphaBits) printf("%2d ", pfd.cAccumAlphaBits);
	else printf(" . ");

	/* no multisample in Win32 */
	printf(" . .\n");
    }

    /* print table footer */
 printf("-----------------------------------------------------------------\n");
 printf("   visual  x  bf lv rg d st  r  g  b a  ax dp st accum buffs  ms \n");
 printf(" id dep cl sp sz l  ci b ro sz sz sz sz bf th cl  r  g  b  a ns b\n");
 printf("-----------------------------------------------------------------\n");

}
Following is a detailed description of the PIXELFORMATDESCRIPTOR structures fields as shown in the Microsoft Developer Studio InfoViewer topic PIXELFORMATDESCRIPTOR.

typedef struct tagPIXELFORMATDESCRIPTOR { // pfd    
    WORD  nSize; 
    WORD  nVersion; 
    DWORD dwFlags; 
    BYTE  iPixelType; 
    BYTE  cColorBits; 
    BYTE  cRedBits; 
    BYTE  cRedShift; 
    BYTE  cGreenBits; 
    BYTE  cGreenShift; 
    BYTE  cBlueBits; 
    BYTE  cBlueShift; 
    BYTE  cAlphaBits; 
    BYTE  cAlphaShift; 
    BYTE  cAccumBits; 
    BYTE  cAccumRedBits; 
    BYTE  cAccumGreenBits; 
    BYTE  cAccumBlueBits; 
    BYTE  cAccumAlphaBits; 
    BYTE  cDepthBits; 
    BYTE  cStencilBits; 
    BYTE  cAuxBuffers; 
    BYTE  iLayerType; 
    BYTE  bReserved; 
    DWORD dwLayerMask; 
    DWORD dwVisibleMask; 
    DWORD dwDamageMask; 
} PIXELFORMATDESCRIPTOR; 
Members

nSize
Specifies the size of this data structure. This value should be set to sizeof(PIXELFORMATDESCRIPTOR).
nVersion
Specifies the version of this data structure. This value should be set to 1.
dwFlags
A set of bit flags that specify properties of the pixel buffer. The properties are generally not mutually exclusive; you can set any combination of bit flags, with the exceptions noted. The following bit flag constants are defined.
ValueMeaning
PFD_DRAW_TO_WINDOW The buffer can draw to a window or device surface.
PFD_DRAW_TO_BITMAP The buffer can draw to a memory bitmap.
PFD_SUPPORT_GDI The buffer supports GDI drawing. This flag and PFD_DOUBLEBUFFER are mutually exclusive in the current generic implementation.
PFD_SUPPORT_OPENGL The buffer supports OpenGL drawing.
PFD_GENERIC_ACCELERATED The pixel format is supported by a device driver that accelerates the generic implementation. If this flag is clear and the PFD_GENERIC_FORMAT flag is set, the pixel format is supported by the generic implementation only.
PFD_GENERIC_FORMAT The pixel format is supported by the GDI software implementation, which is also known as the generic implementation. If this bit is clear, the pixel format is supported by a device driver or hardware.
PFD_NEED_PALETTE The buffer uses RGBA pixels on a palette-managed device. A logical palette is required to achieve the best results for this pixel type. Colors in the palette should be specified according to the values of the cRedBits, cRedShift, cGreenBits, cGreenShift, cBluebits, and cBlueShift members. The palette should be created and realized in the device context before calling wglMakeCurrent.
PFD_NEED_SYSTEM_PALETTE Used with systems with OpenGL hardware that supports one hardware palette only. For such systems to use hardware acceleration, the hardware palette must be in a fixed order (for example, 3-3-2) when in RGBA mode or must match the logical palette when in color-index mode. When you set this flag, call SetSystemPaletteUse in your program to force a one-to-one mapping of the logical palette and the system palette. If your OpenGL hardware supports multiple hardware palettes and the device driver can allocate spare hardware palettes for OpenGL, you don't need to set PFD_NEED_SYSTEM_PALETTE. This flag is not set in the generic pixel formats.
PFD_DOUBLEBUFFER The buffer is double-buffered. This flag and PFD_SUPPORT_GDI are mutually exclusive in the current generic implementation.
PFD_STEREO The buffer is stereoscopic. This flag is not supported in the current generic implementation.
PFD_SWAP_LAYER_BUFFERS Indicates whether a device can swap individual layer planes with pixel formats that include double-buffered overlay or underlay planes. Otherwise all layer planes are swapped together as a group. When this flag is set, wglSwapLayerBuffers is supported.
You can specify the following bit flags when calling ChoosePixelFormat().
ValueMeaning
PFD_DEPTH_DONTCARE The requested pixel format can either have or not have a depth buffer. To select a pixel format without a depth buffer, you must specify this flag. The requested pixel format can be with or without a depth buffer. Otherwise, only pixel formats with a depth buffer are considered.
PFD_DOUBLEBUFFER_DONTCARE The requested pixel format can be either single- or double-buffered.
PFD_STEREO_DONTCARE The requested pixel format can be either monoscopic or stereoscopic.
With the glAddSwapHintRectWIN extension function, two new flags are included for the PIXELFORMATDESCRIPTOR pixel format structure.
ValueMeaning
PFD_SWAP_COPY Specifies the content of the back buffer in the double-buffered main color plane following a buffer swap. Swapping the color buffers causes the content of the back buffer to be copied to the front buffer. The content of the back buffer is not affected by the swap. PFD_SWAP_COPY is a hint only and might not be provided by a driver.
PFD_SWAP_EXCHANGE Specifies the content of the back buffer in the double-buffered main color plane following a buffer swap. Swapping the color buffers causes the exchange of back buffer's content with the front buffer's content. Following the swap, the back buffer's content contains the front buffer's content before the swap. PFD_SWAP_EXCHANGE is a hint only and might not be provided by a driver.

iPixelType
Specifies the type of pixel data. The following types are defined.
ValueMeaning
PFD_TYPE_RGBA RGBA pixels. Each pixel has four components in this order: red, green, blue, and alpha.
PFD_TYPE_COLORINDEX Color index pixels. Each pixel uses a color-index value.

cColorBits
Specifies the number of color bitplanes in each color buffer. For RGBA pixel types, it is the size of the color buffer, excluding the alpha bitplanes. For color index pixels, it is the size of the color-index buffer.
cRedBits
Specifies the number of red bitplanes in each RGBA color buffer.
cRedShift
Specifies the shift count for red bitplanes in each RGBA color buffer.
cGreenBits
Specifies the number of green bitplanes in each RGBA color buffer.
cGreenShift
Specifies the shift count for green bitplanes in each RGBA color buffer.
cBlueBits
Specifies the number of blue bitplanes in each RGBA color buffer.
cBlueShift
Specifies the shift count for blue bitplanes in each RGBA color buffer.
cAlphaBits
Specifies the number of alpha bitplanes in each RGBA color buffer. Alpha bitplanes are not supported.
cAccumBits
Specifies the total number of bitplanes in the accumulation buffer.
cAccumRedBits
Specifies the number of red bitplanes in the accumulation buffer.
cAccumGreenBits
Specifies the number of green bitplanes in the accumulation buffer.
cAccumBlueBits
Specifies the number of blue bitplanes in the accumulation buffer.
cAccumAlphaBits
Specifies the number of alpha bitplanes in the accumulation buffer.
cDepthBits
Specifies the depth of the depth (z-axis) buffer.
cStencilBits
Specifies the depth of the stencil buffer.
cAuxBuffers
Specifies the number of auxiliary buffers. Auxiliary buffers are not supported.
iLayerType
Ignored. Earlier implementations of OpenGL used this member, but it is no longer used.
bReserved
Not used. Must be zero.
dwLayerMask
Ignored. Earlier implementations of OpenGL used this member, but it is no longer used.
dwVisibleMask
Specifies the transparent color or index of an underlay plane. When the pixel type is RGBA, dwLayerMask is a transparent RGB color value. When the pixel type is color index, it is a transparent index value.
dwDamageMask
Ignored. Earlier implementations of OpenGL used this member, but it is no longer used.

Note that in the documentation above, when it says "not supported" it means not supported in the generic implementation of OpenGL supplied by Microsoft. Different hardware types may well support some of these options (such as alpha bitplanes, or auxiliary buffers).

Here's a short code fragment which finds a pixel format that is OpenGL capable, draws to a window, has a depth buffer greater than or equal to 24 bits and is double buffered:
code fragment defining oglPixelFormatExact() in exact.c

/* oglPixelFormatExact()
 *  Sets the pixel format for the context
 */
int oglSetPixelFormatExact(HDC hDC)
{
    int pf, maxpf;
    PIXELFORMATDESCRIPTOR pfd;

    /* get the maximum number of pixel formats */
    maxpf = DescribePixelFormat(hDC, 0, 0, NULL);

    /* loop through all the pixel formats */
    for (pf = 1; pf <= maxpf; pf++) {
	DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
	if (pfd.dwFlags & PFD_DRAW_TO_WINDOW &&
            pfd.dwFlags & PFD_SUPPORT_OPENGL &&
            pfd.dwFlags & PFD_DOUBLEBUFFER   &&
            pfd.cDepthBits >= 24)
          {
	      /* found a matching pixel format */
 
              /* set the pixel format */
              if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
              	MessageBox(NULL,
		   "SetPixelFormat() failed:  Cannot set format specified.",
		   "Error", MB_OK);
		return 0;
	      }

	      return pf;
          } 
    }

    /* couldn't find one, bail out! */
    MessageBox(NULL,
	       "Fatal Error:  Failed to find a suitable pixel format.",
               "Error", MB_OK);
    return 0;
}    


Using Palettes

Up to this point, we've neglected a very important part of the integration of OpenGL with Win32 -- palettes. A palette is a table of colors used when a Truecolor display can't be used or when the application wants exact control over what colors are available (for example, in a height field), or when palette animation functionality is desired.

There are two situations that arise regarding palettes when using OpenGL and Win32. The first is trying to use a color-index context. A discussion of this follows. The second is a bit harder -- using an RGBA context in a paletted mode.

When using a color-index context, a logical palette must be created. A logical palette is a table of colors that is selected and realized into a device context. This just means that the user defines a table of colors, then forces windows to use those colors. On a Truecolor display, this isn't a problem, but on a paletted display, Windows must try to match up the system and logical palettes the best it can. Sometimes there is a "flashing" that occurs because of this palette switching.

The following code shows how to initialize a logical palette.
code defining the oglSetPalette() function in index.c

/* globals */
HPALETTE     hPalette;			/* handle to custom palette */

. . .

/* oglSetPalette()
 *  Sets the palette
 */
BOOL oglSetPalette(HDC hDC)
{
    LOGPALETTE   lgpal;			/* custom logical palette */
    int          nEntries = 5;		/* number of entries in palette */
    PALETTEENTRY peEntries[5] = {	/* entries in custom palette */
        0,   0,   0, NULL,		/* black */
      255,   0,   0, NULL,		/* red */
        0, 255,   0, NULL,		/* green */
        0,   0, 255, NULL,		/* blue */
     255, 255, 255, NULL		/* white */
    };

    /* create a logical palette (for color index mode) */
    lgpal.palVersion    = 0x300;	/* version should be 0x300 */
    lgpal.palNumEntries = nEntries;	/* number of entries in palette */
    if((hPalette = CreatePalette(&lgpal)) == NULL) {
	MessageBox(NULL,
		   "CreatePalette() failed:  Cannot create palette.",
		   "Error", MB_OK);
	return FALSE;
    }

    /* set the palette entries */
    SetPaletteEntries(hPalette, 0, nEntries, peEntries);

    /* select the palette */
    SelectPalette(hDC, hPalette, TRUE); /* map logical into physical palette */

    /* realize the palette */
    RealizePalette(hDC);

    return TRUE;
}
In addition to the initialization code, there are some messages that must be dealt with when using palettes. The following shows these messages and the reaction to them.
code fragment from WindowProc() function in index.c

/* globals */
HPALETTE     hPalette;			/* handle to custom palette */

. . .

/* WindowProc()
 *  Minimum Window Procedure
 */
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ 
    LONG        lRet = 1;
    PAINTSTRUCT ps;

    switch(uMsg) {
    . . .

    case WM_QUERYNEWPALETTE:
	SelectPalette(GetDC(hWnd), hPalette, FALSE);/* select custom palette */
	lRet = RealizePalette(GetDC(hWnd));
	break;

    case WM_PALETTECHANGED:
	if(hWnd == (HWND)wParam)        /* make sure we don't loop forever */
	    break;
	SelectPalette(GetDC(hWnd), hPalette, FALSE);/* select custom palette */
	RealizePalette(GetDC(hWnd));	/* remap the custom palette */
	UpdateColors(GetDC(hWnd));
	lRet = 0;
	break;

    . . .

    }

    return lRet; 
}
This next section is very tricky. Palette management in general is tricky, but even more so when trying to simulate Truecolor with a palette. The basic idea is to create a palette that has an adequate range of colors so that a Truecolor display can be simulated with the aid of dithering. There are many ways to generate such a palette. For a full example, see the Microsoft Developer Studio InfoViewer topic RGBA Mode and Windows Palette Management. We'll use a simple palette derived from the example cited above.

Note that this operation need only be done if the dwFlags member of the PIXELFORMATDESCRIPTOR structure has the PFD_NEED_PALETTE bit set.

Following is the code required to setup a new palette for RGBA rendering in a paletted display mode.
code from the GLUT for Win32 sources

static HPALETTE ghpalOld, ghPalette = (HPALETTE) 0;

static unsigned char threeto8[8] = {
  0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};

static unsigned char twoto8[4] = {
  0, 0x55, 0xaa, 0xff
};

static unsigned char oneto8[2] = {
  0, 255
};

static int defaultOverride[13] = {
  0, 3, 24, 27, 64, 67, 88, 173, 181, 236, 247, 164, 91
};

static PALETTEENTRY defaultPalEntry[20] = {
  { 0,   0,   0,    0 },
  { 0x80,0,   0,    0 },
  { 0,   0x80,0,    0 },
  { 0x80,0x80,0,    0 },
  { 0,   0,   0x80, 0 },
  { 0x80,0,   0x80, 0 },
  { 0,   0x80,0x80, 0 },
  { 0xC0,0xC0,0xC0, 0 },

  { 192, 220, 192,  0 },
  { 166, 202, 240,  0 },
  { 255, 251, 240,  0 },
  { 160, 160, 164,  0 },

  { 0x80,0x80,0x80, 0 },
  { 0xFF,0,   0,    0 },
  { 0,   0xFF,0,    0 },
  { 0xFF,0xFF,0,    0 },
  { 0,   0,   0xFF, 0 },
  { 0xFF,0,   0xFF, 0 },
  { 0,   0xFF,0xFF, 0 },
  { 0xFF,0xFF,0xFF, 0 }
};

static unsigned char ComponentFromIndex(int i, UINT nbits, UINT shift) {
  unsigned char val;

  val = (unsigned char) (i >> shift);
  switch (nbits) {
  case 1:
    val &= 0x1;
    return oneto8[val];

  case 2:
    val &= 0x3;
    return twoto8[val];

  case 3:
    val &= 0x7;
    return threeto8[val];

  default:
    return 0;
  }
}


HPALETTE CreateRGBPalette(HDC hDC) {
  PIXELFORMATDESCRIPTOR pfd;
  LOGPALETTE *pPal;
  int n, i;

  n = GetPixelFormat(hDC);
  DescribePixelFormat(hDC, n, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

  if (pfd.dwFlags & PFD_NEED_PALETTE) { 
    n = 1 << pfd.cColorBits;
    pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
				   n * sizeof(PALETTEENTRY));
    pPal->palVersion = 0x300;
    pPal->palNumEntries = n;
    for (i=0; ipalPalEntry[i].peRed =
	ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
      pPal->palPalEntry[i].peGreen =
	ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
      pPal->palPalEntry[i].peBlue =
	ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
      pPal->palPalEntry[i].peFlags = 0;
    }

    /* fix up the palette to include the default GDI palette */
    if ((pfd.cColorBits == 8)                           &&
	(pfd.cRedBits   == 3) && (pfd.cRedShift   == 0) &&
	(pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) &&
	(pfd.cBlueBits  == 2) && (pfd.cBlueShift  == 6)
	) {
      for (i = 1 ; i <= 12 ; i++)
	pPal->palPalEntry[defaultOverride[i]] = defaultPalEntry[i];
    }

    ghPalette = CreatePalette(pPal);
    if(!ghPalette)
      __glutFatalError("CreatePalette() failed:  Cannot create palette.");
    LocalFree(pPal);

    ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
    n = RealizePalette(hDC);
  }

  return ghPalette;
}
As you can see, it is very messy and very tricky. However, for the most part, this code can simply be "cut and pasted" into an application. When it is determined that the application needs an RGB palette (if the PFD_NEED_PALETTE bit is set as described above), call the CreateRGBPalette() function.

In addition to the initialization code, there are some windows messages that must now be intercepted.
code from the GLUT for Win32 sources

    case WM_QUERYNEWPALETTE:
     if (ghPalette) {
        SelectPalette(GetDC(hWnd), hPalette, FALSE);/* select custom palette */
        lRet = RealizePalette(GetDC(hWnd));
     }
     break;

    case WM_PALETTECHANGED:
     if (ghPalette) {
        if(hWnd == (HWND)wParam)        /* make sure we don't loop forever */
            break;
        SelectPalette(GetDC(hWnd), hPalette, FALSE);/* select custom palette */
        RealizePalette(GetDC(hWnd));    /* remap the custom palette */
        UpdateColors(GetDC(hWnd));
        lRet = 0;
     }
     break;