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