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:
- This color means the code or text so designated is specific to the use of
Microsoft Visual Studio
- This color means the code or text so designated is specific to the use of
OpenGL
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:
- Go to File-->New, and choose the Projects tab
- Choose Win32 Application
- In the next screen, choose to create an empty project
- In the project, go to File-->New, the Files tab, and create a new .rc
file (Resource Script).
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