Writing an OpenGL Screensaver for Windows

by Rachel Grey
© 2002

Scope of this paper

This is the paper I wish I'd read in early 2002 when I was starting development of my own screensaver. It covers the basics of creating a screensaver for Windows systems (95, 98, NT, 2000, and XP). All examples given are written in C/C++, do not use MFC, assume that you want to use OpenGL, and assume the use of Microsoft Visual Studio Developer as an IDE. However, reading this should be useful even if one or more of those last two assumptions is wrong. To help it be useful, I'm adopting the following conventions:

I'm also providing an open-source example of a working screensaver using OpenGL and satisfying all my assumptions. It's not the coolest one you'll ever see, showing a simple green square moving in a circle on a black background, but it will provide an example and introduction for a few topics not quite covered here, such as the use of the registry for saving user preferences. Feel free to start with its code when creating your screensavers. Note that only the C/C++ code is given, not the resource file... you'll be better off creating your own project as outlined below anyway. Note that if you do this, you'll need to put a check box in your configuration dialog called IDC_TUMBLE; if you don't do this, or else comment out the logic related to IDC_TUMBLE, it won't compile.

See the example code
Download the executable example .scr file (zipped)

Basic anatomy of a Windows screensaver

A Windows screensaver (at least the way we're going to make one) is an executable file ending in .scr which contains three specific functions and two specific resources, as defined in scrnsave.h on Windows systems. (You can test the equivalency of .exe and .scr by renaming any program on your computer to have the .scr extension. When you invoke it, it will still run normally.) The screensaver is run by the system when no user input has been given for a while, unless a computer-based training (CBT) window is present, the active application is not Windows-based, or some especially complex and unlikely things happen involving the WM_SYSCOMMAND message.

Screensaver executables (.scr files) can run in two modes, the configuration mode and the screensaver mode. The mode is set by a command-line argument of either -c (for configuration) or -s (for screensaver). If there is no argument, the configuration dialog box is run.

You can search for "Handling Screen Savers" in the MSDN to learn more about screensavers. Most of the time, developers use the pre-existing code in scnrnsave.h to get them up and running, and that is what we'll do here.

The rest of this paper focuses on how to make scrnsave.h work with your code, and how to set up OpenGL for use with your animations.


Setting up the project (in Microsoft Visual Studio)

Don't laugh; it's actually not obvious how to do this. You don't want to have a standard window, and yet you want to use the resource editor, so none of the project defaults are quite right. The easiest thing to do is this: The resource script will come up in a file view, which you'll need to close before you can open it in the resource editor.

And as long as we're setting up the project, you will need to include the scrnsave.lib library (which contains scrnsave.h) and the libraries you'll need for OpenGL. In Visual Studio, choose Project-->Settings from the menu options. Under the General tab, make sure Not Using MFC is selected from the combo box. Then go to the Link tab, and in the Object/library modules edit field, add the following:

scrnsave.lib opengl32.lib glu32.lib

If you are planning to use any Windows common controls such as slider bars in your configuration dialog box, you'll also need comctl32.lib. You can add it now, or wait until you're actually working on the configuration dialog.


The Minimal Screensaver

At this point, you're ready to begin coding up your screensaver. This is, at its simplest, a matter of defining three functions and one resource, the screensaver description string. The screensaver library contains the main function and other startup code for the screensaver, so you don't need to write those.

The resource description string and the icon

You must define a description string, which is the string that appears in the system's list of screensavers in the Control Panel when your users go to select your screensaver. This string must be the first string in your resource file's string table, have the identifier IDS_DESCRIPTION, and be further identified with the ordinal value 1. In Visual Studio, do this by creating a new string table (go to Insert-->Resource, choose String Table and click New), creating a new string, then filling IDS_DESCRIPTION=1 into the ID field. Although Microsoft is not kind enough to document this conspicuously, some parsing happens and your resource ID number will be set to the number after the equals sign. In the caption field, fill in a short descriptive string. On some systems, this will appear in the list of screensaver names in the control panel.

Similarly, if you want your program to be identified by an icon, that icon must be identified as ID_APP and have a value of 100. These constants are defined in scrnsave.h, so you will get errors about their redefinition if you don't define them in the same way.

The screen saver procedure (first required function)

You'll need a .cpp file, which I usually call infrastructure.cpp. Include scrnsave.h:
#include <windows.h>
#include <scrnsave.h>

If you plan to have OpenGL functionality in the same file, don't forget to include GL/gl.h and GL/glu.h... you'll need them later.

The first function you need is ScreenSaverProc. This function deals with certain Windows messages, passing the rest back to the screensaver library, and will call your animation subroutines.

If you're somewhat familiar with Windows messaging, this example code should look straightforward. On WM_CREATE, we need to set up OpenGL and create a timer to run our animation. On WM_TIMER, we advance our animation by one time step. On WM_DESTROY, we clean up any messes and shut down OpenGL nicely (the InitGL and CloseGL functions are given in the next section).


//globals used by the function below to hold the screen size
int Width;      
int Height;

//define a Windows timer 
#define TIMER 1 

// Screen Saver Procedure
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, 
                               WPARAM wParam, LPARAM lParam)
{
  static HDC hDC;
  static HGLRC hRC;
  static RECT rect;

  switch ( message ) {

  case WM_CREATE: 
    // get window dimensions
    GetClientRect( hWnd, &rect );
    Width = rect.right;         
    Height = rect.bottom;
    
    //get configuration from registry if applicable

    //set up OpenGL
    InitGL( hWnd, hDC, hRC );

    //Initialize perspective, viewpoint, and
    //any objects you wish to animate

    //create a timer that ticks every 10 milliseconds
    SetTimer( hWnd, TIMER, 10, NULL ); 
    return 0;
 
  case WM_DESTROY:
    KillTimer( hWnd, TIMER );
    
    //delete any objects created during animation
    //and close down OpenGL nicely

     CloseGL( hWnd, hDC, hRC );
    return 0;

  case WM_TIMER:
    //call some function to advance your animation              
    return 0;                           

  }

  //let the screensaver library take care of any
  //other messages

  return DefScreenSaverProc( 
    hWnd, message, wParam, lParam );
}

Initializing and Closing OpenGL

The InitGL and CloseGL functions deserve a little more explanation. They involve a lot of low-level Windows fidgeting, especially InitGL, which is basically the setup of a pixel format descriptor that supports OpenGL and double buffering (in which, during animation, each picture is drawn to a buffer and then put on the screen all at once, as opposed to drawing it directly on the screen. This eliminates unwanted flashing effects and other undesirable byproducts of not having the whole screen drawn at any given time). If your screensaver involves standard animation in which objects move smoothly around the screen, you can probably take these two functions and use them without modification.

If you don't want double buffering, you can look in the MSDN for the details of the PIXELFORMATDESCRIPTOR structure and change the flags to satisfy your requirements.

I should give a quick acknowledgement to Blaine Hodge here, whose open-source screensaver Gravity gave me these functions in almost this form (though without the double buffering).

 
static void InitGL(HWND hWnd, HDC & hDC, HGLRC & hRC)
{
  
  PIXELFORMATDESCRIPTOR pfd;
  ZeroMemory( &pfd, sizeof pfd );
  pfd.nSize = sizeof pfd;
  pfd.nVersion = 1;
  pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
  pfd.iPixelType = PFD_TYPE_RGBA;
  pfd.cColorBits = 24;
  
  hDC = GetDC( hWnd );
  
  int i = ChoosePixelFormat( hDC, &pfd );  
  SetPixelFormat( hDC, i, &pfd );

  hRC = wglCreateContext( hDC );
  wglMakeCurrent( hDC, hRC );

}

 
static void CloseGL(HWND hWnd, HDC hDC, HGLRC hRC)
{
  wglMakeCurrent( NULL, NULL );
  wglDeleteContext( hRC );
  ReleaseDC( hWnd, hDC );
}

Dialog box configuration and the second required function

Almost all screen savers allow the user to set some parameters: how fast to run, what color an object should be, how many of some object to show, etc. Chances are you will want this in your screensaver, too, but even if you don't, you need to provide this function. You will never call it directly, but the system will call it when a user is in the Control Panel setting up your screensaver and they press the "Settings..." button.
BOOL WINAPI
ScreenSaverConfigureDialog(HWND hDlg, UINT message, 
                           WPARAM wParam, LPARAM lParam)
{
    return FALSE;
}

Of course, that function doesn't do anything except satisfy the screensaver library. However, feel free to leave it in that state until you've done all your animation work and have the screensaver itself looking as it should at its default settings. You'll have a much better idea of what you'd like your users to be able to configure by then anyway.

When you've reached that point, use the resource editor to create a dialog box. It must have an identifier of DLG_SCRNSAVECONFIGURE and be defined by the constant 2003. As before, then, in its properties you can set the ID to DLG_SCRNSAVECONFIGURE=2003 and Studio Developer will do that invisible parsing for you. You can open the .rc file as text to make sure it worked.

This is not really the place to teach someone how to make a dialog box, but a few general notes are called for. First, keep it simple to start with. Add the fancy stuff later. If you think your dialog box should be coming up but nothing happens when you press Settings... or try to run it in configuration mode from your IDE, try this trick: in the dialog box's styles, go to the More Styles tab and choose No fail create. This will cause your box to come up without whatever offensive part was causing the error, which will let you know what part it was.

A real dialog box will have controls to set up and functionality to write to, and read from, the registry so that your user's preferred settings will persist from session to session; so the ScreenSaverConfigureDialog function can get pretty ungainly. Here's a somewhat more realistic, example showing the basic form you'll want to follow; my example code also shows the reading/writing of the registry information.

BOOL WINAPI
ScreenSaverConfigureDialog(HWND hDlg, UINT message, 
                           WPARAM wParam, LPARAM lParam)
{

  switch ( message ) 
  {

        case WM_INITDIALOG:

                //get configuration from the registry

                return TRUE;

       case WM_COMMAND:
           switch( LOWORD( wParam ) ) 
                { 

                //handle various cases sent when controls
                //are checked, unchecked, etc

                //when OK is pressed, write the configuration
                //to the registry and end the dialog box

                case IDOK:
                        //write configuration

                        EndDialog( hDlg, LOWORD( wParam ) == IDOK ); 
                        return TRUE; 

                case IDCANCEL: 
                        EndDialog( hDlg, LOWORD( wParam ) == IDOK ); 
                        return TRUE;   
                }

  }     //end command switch

  return FALSE; 
}

The third required function

Include this function as-is, unless you are using special windows or custom controls in your dialog box. There, that was easy, wasn't it?
// needed for SCRNSAVE.LIB
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
  return TRUE;
}

Running and Testing Your Screensaver

Remember, screensaver executables (.scr files) can run in two modes, the configuration mode and the screensaver mode. The mode is set by a command-line argument of either -c (for configuration) or -s (for running as a screensaver or in the small Preview window in the Control Panel). If there is no argument, the configuration dialog box is run.

The system adds these command-line arguments automatically; to include them when running from your IDE so that you can test and debug the animation functionality, you'll have to add one of them in yourself. In Microsoft Visual Studio's project settings, choose the Debug tab and make sure you're in the General category in the combo box. Then type "-s" or "-c" (-c gives you the default behavior anyway) into the Program Arguments edit field.

At some point, you'll want to be sure your screensaver works with the Windows system. I advise doing this as soon as you get the three skeleton functions in, before you've done more animation work than making the screen go black. You'll need to move the executable to where the system can find it automatically, usually the C:\Windows\System file. For this, I found it useful to create a little batch file called CopyScr.bat, containing a line like this:

copy c:\dev\myscr\Release\myscr.exe c:\Windows\System\myscr.scr

Of course, I could have set my project settings so that myscr would have had an extension of .scr to begin with.

Here's a table showing some folders where your system will be able to find your screensaver:

Windows 95: C:\Windows (?)
Windows 98: C:\Windows\System or C:\Windows
Windows XP: C:\Windows or C:\Windows\System32
Windows 2000: C:\WINNT\System32

Other References

This paper does not by any means give you enough information to write a commercial-grade screensaver or even a very good one. Some other resources you may find useful (translation: I couldn't live without them my first time) include:

Good luck and have fun writing screensavers. Please let me know if you find errors in this document, or found any part of it especially misleading/unhelpful, so I can change it. I suppose you could even drop me a line if you liked it. I look forward to hearing from you.


Did you find this useful? Really really useful?

If you found this paper useful and have actually written a screensaver based on its instructions, you might consider paying me $3 by pressing the PayPal button below. There's no particular need for you to do so and it will have no bearing on whether I answer those questions you're thinking of emailing me, but I would consider it at least as nice as a thank-you note.

Rachel Grey
lemming@alum.mit.edu
City in the Rain