OpenGL, Windows and 3dfx FAQs

These questions are commonly asked in the comp.graphics.api.opengl newsgroup. Hopefully, when a FAQ is written, these answers can be incorporated in it.

How can I get OpenGL and Glut to work with my Voodoo1/2 card?

If you have a voodoo1/2 card, then you need to do a bit of work to get hardware acceleration for OpenGL. First, some overview about how things fit together...

If you have a voodoo1/2 card (which is not integrated 2d+3d), then opengl32.dll will not be able to use your 3d hardware acceleration. You will have to use one of two possible workarounds.

But first, go to www.3dfx.com and download the latest voodoo1/2 reference drivers. (As of March 2000 they are at the bottom of the page and are called "Voodoo2 Windows 9x DirectX7 Drivers - Version 3.02.02"). When you install them, you will find a file called "3DFXVGL.DLL". This is the 3dfx opengl driver.

Extra notes:

When you create a context and make it current, the 3dfx opengl driver will examine the size of your window. If it is a 512x384 window, then the graphics card will initialize itself to full-screen at 512x384 resolution. Similarly for 640x480, 800x600 and 1024x768. (But this highest resolution is only supported with two cards in SLI mode). If you create a window bigger than it can handle, then it will choose the most fitting screen resolution. It is always 16bit colour depth.

Even when the voodoo screen mode has changed, Windows still believes that you are in your regular 2d desktop. You must do something about this! Otherwise, you might end up with the user clicking their mouse on some other window that the 2d desktop thinks is still visible, even though the monitor doesn't show it. There are two ways: (None of this applies if you're using glut. I don't know how glut works).

//
// (1) Change screen mode and create popup window
//
DEVMODE dm; ZeroMemory(&dm,sizeof(dm)); dm.dmSize=sizeof(dm);
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
dm.dmPelsWidth = 640; dm.dmPelsHeight = 480;
LONG res = ChangeDisplaySettings(&dm,CDS_TEST);
if (res!=DISP_CHANGE_SUCCESFULL) return; // error
ChangeDisplaySettings(&dm,CDS_FULLSCREEN);
CreateWindow(WS_EX_TOPMOST,WS_POPUP|WS_VISIBLE,..,0,0,640,480...);
   ... and then create your opengl context on that window
   ... and do stuff with it
DestroyWindow(...);
ChangeDisplaySettings(NULL,NULL);
//
// (2) Clip mouse
//
HWND hwnd=CreateWindow(WS_POPUP|WS_VISIBLE,...0,0,640,480,..);
int sw=GetSystemMetrics(SM_CXSCREEN);
int sh=GetSystemMetrics(SM_CYSCREEN);
int cw=640, ch=480;
// First, move the mouse proportionately inside our window
POINT tl; tl.x=0; tl.y=0; ::ClientToScreen(hwnd,&tl);
POINT pt; GetCursorPos(&pt);
pt.x=pt.x*cw/sw+tl.x; pt.y=pt.y*ch/sh+tl.y;
SetCursorPos(pt.x,pt.y);
// Second, clip the mouse
RECT rc; pt.x=0; pt.y=0; ClientToScreen(hwnd,&pt); rc.left=pt.x; rc.top=pt.y;
pt.x=640; pt.y=480; ClientToScreen(hwnd,&pt); rc.right=pt.x; rc.bottom=pt.y;
ClipCursor(&rc);
// Finally, adjust mouse ballistics proportionately
DWORD param[3]; SystemParametersInfo(SPI_GETMOUSE,0,&param,FALSE); OldMouseSpeed=param[2]; param[2]=param[2]*cw/sw;
SystemParametersInfo(SPI_SETMOUSE,0,&param,FALSE);
   ... now create opengl stuff in the window
   ... and run your program
// First, restore mouse ballistics
DWORD param[3]; SystemParametersInfo(SPI_GETMOUSE,0,&param,FALSE);
param[2]=OldMouseSpeed;
SystemParametersInfo(SPI_SETMOUSE,0,&param,FALSE);
// Next, restore the mouse clip rectangle
RECT rc; rc.left=0; rc.top=0; rc.right=GetSystemMetrics(SM_CXSCREEN);
rc.bottom=GetSystemMetrics(SM_CYSCREEN);
ClipCursor(&rc);
// Finally, move it to the same place on the screen proportionately
POINT tl; tl.x=0; tl.y=0; ClientToScreen(hwnd,&tl);
POINT pt; GetCursorPos(&pt);
pt.x=(pt.x-tl.x)*sw/cw; pt.y=(pt.y-tl.y)*sh/ch;
SetCursorPos(pt.x,pt.y);

How can I get a list of OpenGL drivers, like Quake?

You first search for appropriate DLL files in appropriate directories, and display them in a list. Then you use dynamic loading to load the dll. (See following question about dynamic loading).

There is no system-defined location for where opengl drivers should go, and there is no naming convention. The only thing you know is that there is OPENGL32.DLL in the windows system directory, and that this file provides a software opengl driver, and also a hardware opengl driver for your integrated 2d+3d graphics card if you have an integrated 2d+3d graphics card and if your drivers are installed.

Other drivers (e.g. MESA, Cosmo, MiniGL, 3dfxvgl.dll) might be in any location at all.

The best thing to do is to have a list of search-paths, and to search for files in them. You might have <app>\drivers\*.dll, <winsys>\opengl32.dll. And also have a 'Driver...' button in your configuration dialog which lets users select other files in other directories. Then, use FindFirstFile/FindNextFile to find all files that match those paths.

You might be tempted to load up each DLL, just to test whether it includes particular GL entry points (and therefore confirm that it is an OpenGL dll and not some other random DLL). But you should not do this! If it happens to be an opengl driver for some hardware that is not already on your system, then loading it will probably cause a system crash. Therefore, you must stick to querying its version info and checking its filename.

Next, you want to extract version information about the driver. The only safe way to do this is to use the Windows API function GetFileVersionInfo, to extract the details. These are the same version details you see if you right-click on a dll and click on the 'version' tab. Source code for this is given below.

There are two issues. First, binary distributions of Mesa typically do not include version information. This is really pants. If you are as irritated by this as I am, then email the people who do mesa and tell them to put version information in their DLLs.

Second, opengl32.dll only displays the version string "microsoft generic software driver". That's not much use! Especially not on a machine where there is an integrated 2d+3d driver, and you want its name! One way to find the name of this is to look in the registry under

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\OpenGLDrivers
(under Windows NT and Win2000, that's ...\WindowsNT\...) Here it may list several different drivers. I don't know how to tell which one it will use. Below there's a function to retrieve the filename of the ICD, if there is one.

A different solution is to load up opengl32.dll, create a window, set its pixel format, create a context, make the context current, and then call glGetString(GL_VENDOR) &c. Then destroy it all. Very tedious!

typedef struct
{ char FileName[MAX_PATH];
  char CompanyName[MAX_PATH];
  char FileDescription[MAX_PATH];
  char FileVersion[MAX_PATH];
  bool Uses3dfxStyle;
} TGLDriverDetails;


//
// GETDRIVERDETAILS - given a filename, extracts version info
//
bool GetDriverDetails(char *fn,TGLDriverDetails *d)
{ strcpy(d->FileName,fn); strcpy(d->CompanyName,"");
  strcpy(d->FileDescription,""); strcpy(d->FileVersion,"");
  d->Uses3dfxStyle=false;
  DWORD dummy; DWORD vis=GetFileVersionInfoSize(fn,&dummy);
  if (vis==0) return true;
  void *vData;
  vData=(void *)new char[(UINT)vis];
  if (!GetFileVersionInfo(fn,dummy,vis,vData)) {delete vData;return true;}
  char vn[100];
  strcpy(vn,"\\VarFileInfo\\Translation");
  LPVOID transblock; UINT vsize;
  BOOL res=VerQueryValue(vData,vn,&transblock,&vsize);
  if (!res) {delete vData;return true;}
  // Swap the words so wsprintf will print the lang-charset in the correct format.
  DWORD *dw=(DWORD*)transblock;
  DWORD dwhi=HIWORD(*dw); DWORD dwlo=LOWORD(*dw);
  *dw = dwhi | (dwlo << 16);
  char *info;
  info="CompanyName";
  wsprintf(vn,"\\StringFileInfo\\%08lx\\%s",*(DWORD *)transblock,info);
  char *CompanyName=NULL;
  VerQueryValue(vData,vn,(LPVOID*)&CompanyName,&vsize);
  info="FileDescription";
  wsprintf(vn,"\\StringFileInfo\\%08lx\\%s",*(DWORD *)transblock,info);
  char *FileDescription=NULL;
  VerQueryValue(vData,vn,(LPVOID*)&FileDescription,&vsize);
  info="FileVersion";
  wsprintf(vn,"\\StringFileInfo\\%08lx\\%s",*(DWORD *)transblock,info);
  char *FileVersion=NULL;
  VerQueryValue(vData,vn,(LPVOID*)&FileVersion,&vsize);
  strcpy(d->CompanyName,CompanyName);
  strcpy(d->FileDescription,FileDescription);
  strcpy(d->FileVersion,FileVersion);
  delete vData;
  // tidy up in case of duplication
  char *de=d->FileDescription, *ce=d->CompanyName;
  char *inde=strstr(de,ce);
  if (inde!=NULL)
  { int i1 = (int)(inde-de);
    int i2=i1+strlen(ce);
    char t[MAX_PATH]; strcpy(t,"");
    strncpy(t,de,i1); strncat(t,de+i2,strlen(de)-i2);
    strcpy(d->FileDescription,t);
  }
  inde=strstr(d->CompanyName,"3dfx");
  if (inde==NULL) inde=strstr(d->FileDescription,"3dfx");
  if (inde==NULL) inde=strstr(d->FileVersion,"3dfx");
  if (inde==NULL) d->Uses3dfxStyle=false; else d->Uses3dfxStyle=true;
  return true;
}


//
// GETMAINDRIVER - retrieves the filename of the opengl icd driver,
// if you have an integrated 2d+3d card and the opengl driver has been
// installed. You can subsequently use GetDriverDetails on the returned
// driver string.
// returns 'true' or 'false' for whether or not it succeeded.
//
bool GetMainDriver(char *fn,int maxsize)
{ OSVERSIONINFO oi; ZeroMemory(&oi,sizeof(oi)); oi.dwOSVersionInfoSize=sizeof(oi);
  GetVersionEx(&oi);
  char *regpath;
  if (oi.dwPlatformId==VER_PLATFORM_WIN32_NT)
    regpath="SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\OpenGLDrivers";
  else
    regpath="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OpenGLDrivers";
  HKEY hkey;
  LONG res=RegOpenKeyEx(HKEY_LOCAL_MACHINE,regpath,0,KEY_QUERY_VALUE,&hkey);
  if (res!=ERROR_SUCCESS) return false;
  DWORD index=0; char t[MAX_PATH]; DWORD size=MAX_PATH;
  char dat[MAX_PATH]; DWORD dsize=MAX_PATH;
  res=RegEnumValue(hkey,index,t,&size,NULL,NULL,(LPBYTE)dat,&dsize);
  if (res==ERROR_SUCCESS && size==0)
  { index++;
    res=RegEnumValue(hkey,index,t,&size,NULL,NULL,(LPBYTE)dat,&dsize);
  }
  RegCloseKey(hkey);
  if (res!=ERROR_SUCCESS) return false;
  GetSystemDirectory(t,MAX_PATH);
  strcat(t,"\\"); strcat(t,dat);
  strncpy(fn,t,maxsize-1); strcat(fn,"\0");
  return true;
}

How can I runtime-load an OpenGL driver DLL?

Normally, when you make a call to any gl function, your program gets linked against the opengl32.lib import library. This means that, when your program is loaded, the opengl32.dll driver gets loaded as well.

But if you want to load a particular OpenGL DLL at runtime (perhaps one that the user has selected from a list of drivers) then you cannot use the opengl32.lib import library. Instead, you must LoadLibrary(..) and then GetProcAddress(..) to get the address of each and ever gl* function.

There are a number of problems.

Source code for dynamic loading of a DLL, and bypassing appropriate GDI functions, is available from Ryan Haski at http://members.home.com/borealis/opengl_usingq3.html

Why does ChoosePixelFormat fail in my minimal test program?

Internally, ChoosePixelFormat (and SetPixelFormat and all those functions) make calls to OPENGL32.DLL. If your program does not make any gl* calls, then your compiler's smart-linker will probably not link opengl32.lib, so when you run your program then opengl32.dll will not be loaded, and so ChoosePixelFormat will fail.

The solution, simply, is to add a gl* function call somewhere inside your code, and rebuild.

How do I choose an accelerated pixel format under Windows?

Note: many consumer graphics cards cannot accelerate when the display is 24bpp, and many cannot accelerate when the desktop is at 32bpp in high-resolution. I always change to 800x600 x16bpp for my full-screen games. That ensures that the graphics card will have enough memory.

Normally, you call ChoosePixelFormat to choose a pixel format. But it's hard to know whether this will give you an accelerated pixel format. For us gamers, acceleration is the most important thing: we'd be happy to settle for a 16bpp accelerated surface, rather than a 32bpp unaccelerated surface.

The following code uses a gamer's heuristics to choose a suitable pixel format. Call it like this:

  int bpp=-1; // don't care. (or a positive integer)
  int depth=-1; // don't care. (or a positive integer)
  int dbl=1; // we want double-buffering. (or -1 for 'don't care', or 0 for 'none')
  int acc=1; // we want acceleration. (or -1 or 0)
  int pf=ChoosePixelFormatEx(hdc,&bpp,&depth,&dbl,&acc);

The function will return, in those variables, the pixel format that it choose.

int ChoosePixelFormatEx(HDC hdc,int *p_bpp,int *p_depth,int *p_dbl,int *p_acc)
{ int wbpp; if (p_bpp==NULL) wbpp=-1; else wbpp=*p_bpp;
  int wdepth; if (p_depth==NULL) wdepth=16; else wdepth=*p_depth;
  int wdbl; if (p_dbl==NULL) wdbl=-1; else wdbl=*p_dbl;
  int wacc; if (p_acc==NULL) wacc=1; else wacc=*p_acc;
  PIXELFORMATDESCRIPTOR pfd; ZeroMemory(&pfd,sizeof(pfd)); pfd.nSize=sizeof(pfd); pfd.nVersion=1;
  int num=DescribePixelFormat(hdc,1,sizeof(pfd),&pfd);
  if (num==0) return 0;
  unsigned int maxqual=0; int maxindex=0;
  int max_bpp, max_depth, max_dbl, max_acc;
  for (int i=1; i<=num; i++)
  { ZeroMemory(&pfd,sizeof(pfd)); pfd.nSize=sizeof(pfd); pfd.nVersion=1;
    DescribePixelFormat(hdc,i,sizeof(pfd),&pfd);
    int bpp=pfd.cColorBits;
    int depth=pfd.cDepthBits;
    bool pal=(pfd.iPixelType==PFD_TYPE_COLORINDEX);
    bool mcd=((pfd.dwFlags & PFD_GENERIC_FORMAT) && (pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool soft=((pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool icd=(!(pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool opengl=(pfd.dwFlags & PFD_SUPPORT_OPENGL);
    bool window=(pfd.dwFlags & PFD_DRAW_TO_WINDOW);
    bool bitmap=(pfd.dwFlags & PFD_DRAW_TO_BITMAP);
    bool dbuff=(pfd.dwFlags & PFD_DOUBLEBUFFER);
    //
    unsigned int q=0;
    if (opengl && window) q=q+0x8000;
    if (wdepth==-1 || (wdepth>0 && depth>0)) q=q+0x4000;
    if (wdbl==-1 || (wdbl==0 && !dbuff) || (wdbl==1 && dbuff)) q=q+0x2000;
    if (wacc==-1 || (wacc==0 && soft) || (wacc==1 && (mcd || icd))) q=q+0x1000;
    if (mcd || icd) q=q+0x0040; if (icd) q=q+0x0002;
    if (wbpp==-1 || (wbpp==bpp)) q=q+0x0800;
    if (bpp>=16) q=q+0x0020; if (bpp==16) q=q+0x0008;
    if (wdepth==-1 || (wdepth==depth)) q=q+0x0400;
    if (depth>=16) q=q+0x0010; if (depth==16) q=q+0x0004;
    if (!pal) q=q+0x0080;
    if (bitmap) q=q+0x0001;
    if (q>maxqual) {maxqual=q; maxindex=i;max_bpp=bpp; max_depth=depth; max_dbl=dbuff?1:0; max_acc=soft?0:1;}
  }
  if (maxindex==0) return maxindex;
  if (p_bpp!=NULL) *p_bpp=max_bpp;
  if (p_depth!=NULL) *p_depth=max_depth;
  if (p_dbl!=NULL) *p_dbl=max_dbl;
  if (p_acc!=NULL) *p_acc=max_acc;
  return maxindex;
}