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
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:
Function | Description |
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.
Value | Meaning |
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().
Value | Meaning |
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.
Value | Meaning |
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.
Value | Meaning |
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;