::[ OPENGL WINDOWS SCREEN SAVER TUTORIAL ]::[ Source ]::
 
::[ AUTHOR - Lance Ramoth, http://www.lanceusa.com ]::

 
::[ INTRODUCTION ]::
As my interest for OpenGL and Windows Programming have grown I found displaying graphics as a screen saver was one way to display my OpenGL creations. I, personally, have used many tutorials over the internet and this is the first time I have decided to share what I’ve learned. I do not claim to be an expert, I do not have a computer science degree, nor am I a definitive resource for creating screen savers. I do have a interest in programming and computer graphics and hope to help you in creating your first screen saver.

It is possible that this document has mistakes so use at your own risk. As a newbie myself to C/C++ programming I am only familiar with VC++.Net compilers so I will weight heavily on my understanding of this particular compiler.

I must first explain that I will not teach you how to program in OpenGL. The best source for this is NeHe’s OpenGL tutorial site at: http://nehe.gamedev.net. Nor is this meant to be a tutorial in C programming. Some aspects of this code require you to use a reference. If I were to explain every piece of code in detail this tut would be much longer. I hope you only use this as a backbone and do the required research on your own. I must also give props to Rachel Grey’s paper: "Writing an OpenGL Windows Screensaver” at http://cityintherain.com/howtoscr.html. Actually, if you're interested in creating a screensaver that will save options to the registry then you may want to look at her source code. At this point I will not cover this topic, and it is not completely necessary either to get your screen saver up and running.  

 
::[ BACKGROUND ]::
The most interesting thing about making a screen saver for Windows is that there is no WinMain function. According to Microsoft (lookup “Handling Screen Savers” on MSDN) screen savers contain specific exported functions, resource definitions, and variable declarations. The screen saver library (ScrnSave.lib) contains the main function and other startup code required for the screen saver. So our mission in writing a screen saver is to supply the exported function, resources, and variables.

Before we start coding let me first explain that three functions are absolutely required when writing a screen saver.

  1. The first is the ScreenSaverProc. ScreenSaverProc processes specific messages and passes any unprocessed messages to the screen saver library.
  2. The second required function is ScreenSaverConfigureDialog. As you know, all screen savers can be accessed through the display properties menu (right click on desktop, choose properties, click screen saver tab). When choosing a screen saver you may want to change some of the properties by hitting the settings button. This is where ScreenSaverConfigureDialog comes in. Even though you may not want settings to configure in your saver, this function is necessary.
  3. The last function that is required is the RegisterDialogClasses. This function is reserved for you to create additional windows or custom controls for your screen saver and you should use this function to register these window classes. We will just include this function in our source code but actually will not do anything with it.
After you compile your screen saver you must change the file extension from exe to scr. If you do not see the file extension after the filename you can change this by unchecking the folder option under Tools->Folder Options->View->Hide extensions of known file types. You can then right click the screen saver executable (scr) file and select “Install” or simply place it into your windows directory.

Screen saver executables can run in two modes as specified by the command line argument: configuration mode (-c) or screen saver mode (-s). Windows knows which mode to use when the screen saver is installed but when testing in the VC++ environment this needs to specified. You can specify –s by right clicking on you project in the explorer window, select properties, choose debugging on the left menu, and enter –s in the Command Argument box.

 

::[ RESOURCES ]::
If you want your screen saver name to be displayed in the list of screensavers in the display properties/screen saver list you’ll need to include a description string in a resource file’s string table. Also if you would like to have a custom icon for you screen saver you will need to include a description string for this as well. Doing this is easy in VC++. First right click the folder that says resource in the Solution Explorer window, select add, then select add resource. You will see a list of resources to choose from. Select String Table, then click New. You’ll probably see “IDS_STRING101” with a value of 101. Replace “IDS_STRING101” with IDS_DESCRIPTION=1 and then fill the caption cell with the name of your screensaver.

NOTE: When adding a resource, Visual Studio will automatically add a rc file to your project as well as a resource.h file in the header folder. The resource.h file must be included in you source code for the resources to work correctly. See code below.

Next, if you want a custom icon add another string to the string table: ID_APP=100. To add the icon you’ll need a 32*32 pixel .ico file. Next, right click on your already created .rc file, select add resource, choose icon, then click on the import button. Make sure “Files of type:” is set to .ico files. Make sure you change the ID of the icon to ID_APP!

Lastly, is the dialog box. This is the dialog box the pops up when you choose settings in the display properties/screen saver window. Since dialog boxes deserve a tutorial all to themselves I will only tell you how to add one that displays the author of the screensaver with an OK and CANCEL button. Right click on the the .rc file, select add resource, select dialog, and click the new button. Well good for us the OK and CANCEL buttons have already been added, and the ScreenSaverConfigureDialog function below is already set up to handle these messages. All you have to do is add ‘static text’ to the dialog box and change the ID in its properties window to DLG_SCRNSAVECONFIGURE=2003. Studio Developer will do the rest. If you want to check the dialog box run the screensaver with –c as the command argument.

 

 
::[ SOURCE CODE ]::
Personally, I like to make 2 source files. One will contain the windows specific code (main.cpp) and the other (graphics.cpp) will contain the OpenGL source code. This way I can just copy the main.cpp file to a new project when making a new screensaver.

main.cpp
#include <windows.h>
#include <scrnsave.h>
#include <commctrl.h>
#include <gl\gl.h>                   // Header for OpenGL32 Lib
#include <gl\glu.h>                  // GLu32 Lib
#include <gl\glaux.h>                // GLaux Lib
#include "resource.h"

// Include these libraries

#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "GLu32.lib")
#pragma comment(lib, "GLaux.lib")
#pragma comment(lib, "ScrnSave.lib")
#pragma comment(lib, "comctl32.lib")

// Screen saver library contains the main function and other 
// Startup code required for a scrnsaver user defined vars

#define TIMER 1

// Function Prototypes
// NOTE: In InitGL, hDC and hRC are passed by reference 
//       because both will be altered by this function.  
//       At least that is my understanding!

void InitGL(HWND hWnd, HDC &hDC, HGLRC &hRC);    // Setup OpenGL pixelformat
void CloseGL(HWND hWnd, HDC hDC, HGLRC hRC);     // Clean up and close OpenGL
void SetupAnimation(int Width, int Height);      // Setup for OpenGL
void OnTimer(HDC hDC);                           // Draw OpenGL Scene

// Global variables

int Width, Height;                               // Size of screen variables

// Screensaver procedure...first when window is created: 
// 1) get users window resolution 
// 2) call a function that sets up OpenGL  
// 3) Setup a TIMER that will be used to drive animations

LRESULT WINAPI ScreenSaverProc(HWND hWnd, 
                               UINT message, 
                               WPARAM wParam, 
                               LPARAM lParam)
{
      static HDC   hDC;      // Handle to device context
      static HGLRC hRC;      // Handle to OpenGL rendering context
      static RECT  rect;     // Instance of structure RECT which defines the
                             // coords of the upper-left and lower-right 
                             // corners of a rectangle

      switch(message)
      {
        // Set timer and any other initializations
        case WM_CREATE:                   
            GetClientRect(hWnd, &rect);   // Get window dimensions
            Width = rect.right;           // Store width
            Height = rect.bottom;         // Store height
            InitGL(hWnd, hDC, hRC);       // Initalize OpenGL

            // Setup for OpenGL (look at graphics.cpp)
            SetupAnimation(Width, Height);    
            
            // Create timer with timeout value (10)
            SetTimer(hWnd, TIMER, 10, NULL);  
            return 0;

        case WM_DESTROY:                  // Destroy timer and perform cleanup
            KillTimer(hWnd, TIMER);       // Destroy timer
            CloseGL(hWnd, hDC, hRC);      // Clean up and close OpenGL
            return 0;

        case WM_TIMER:                    // Perform drawing operations
            OnTimer(hDC);                 // Draw OpenGL scene (graphics.cpp)
            return 0;
      }
 
      // Unprocessed messages are handled by the screen saver 
      // library by calling the following:
      return DefScreenSaverProc(hWnd, message, wParam, lParam);
}
 

// The system will call the following function when the user is 
// in the control panel setting up the screensaver and they 
// press the setting button...

BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, 
                                       UINT message, 
                                       WPARAM wParam, 
                                       LPARAM lParam)
{
      // Dialog message handling
      switch(message)
      {
      case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                  case IDOK:
                        EndDialog(hDlg, LOWORD(wParam) == IDOK);
                        return true;

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

 
// The following function registers any nonstandard window 
// classes required by the screen saver if the screen saver 
// does not require this functionality simply return true

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
      return true;
}

 
// Initialize OpenGL

void InitGL(HWND hWnd, HDC &hDC, HGLRC &hRC)
{
      // Next define pixelformat...we use stuct PIXELFORMATDESCRIPTOR
      // This is straight out of NeHe’s framework

      static PIXELFORMATDESCRIPTOR pfd =       // static ensures data 
                                               // is stored between calls
      {
            sizeof(PIXELFORMATDESCRIPTOR),
            1,                     // nVersion should be set to 1
            PFD_DRAW_TO_WINDOW |   // buffer can draw to window
            PFD_SUPPORT_OPENGL |   // buffer supports OpenGL drawing
            PFD_DOUBLEBUFFER,      // buffer is double buffered
            PFD_TYPE_RGBA,         // rgba pixels
            24,                    // 24-bit color depth
            0, 0, 0, 0, 0, 0,      // look up rest at MSDN, color bits ignored
            0,                     // no alpha buffer
            0,                     // shift bit ignored
            0,                     // no accumulation buffer
            0, 0, 0, 0,            // accumulation bits
            16,                    // 16 bit z buffer
            0,                     // no stencil buffer
            0,                     // no auxiliary buffer
            PFD_MAIN_PLANE,        // main drawing layer
            0,                     // reserved
            0, 0, 0                // layer mask ignored
      };

      hDC = GetDC(hWnd);                     // Retrieves a handle to a 
                                             // display device context
      int i = ChoosePixelFormat(hDC, &pfd);  // Try and match a pixel format 
                                             // supported by DC
      SetPixelFormat(hDC, i, &pfd);
      hRC = wglCreateContext(hDC);     // Create a new OpenGL rendering context
      wglMakeCurrent(hDC, hRC);        // Makes OpenGL hRC the calling 
                                       // threads current RC
}


// Shut down OpenGL

void CloseGL(HWND hWnd, HDC hDC, HGLRC hRC)
{
      wglMakeCurrent(NULL, NULL);       // Makes current RC no longer current
      wglDeleteContext(hRC);            // Deletes OpenGL rendering context
      ReleaseDC(hWnd, hDC);             // Releases a DC, freeing it 
                                        // for other apps
}
graphics.cpp
// All graphical functions can go in this file

#include <windows.h>
#include <scrnsave.h>
#include <commctrl.h>
#include <gl\gl.h>     // Header for OpenGL32 Lib
#include <gl\glu.h>    // GLu32 Lib
#include <gl\glaux.h>  // GLaux Lib
#include "resource.h"

// Include these libraries

#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "GLu32.lib")
#pragma comment(lib, "GLaux.lib")
#pragma comment(lib, "ScrnSave.lib")
#pragma comment(lib, "comctl32.lib")

// Screen saver library contains the main function and 
// other startup code required for a scrnsaver
// setup for OpenGL


void SetupAnimation(int Width, int Height)
{
      // Current viewport:x, y, width, height
      glViewport(0, 0, (GLsizei)Width, (GLsizei)Height);    
 
      // Next we set up for a perspective view

      glMatrixMode(GL_PROJECTION);         // Select the Projection Matrix
      glLoadIdentity();                    // Reset(init) the Projection Matrix

      gluPerspective(45.0f, (GLfloat)Width/(GLfloat)Height, 0.1f, 100.0f);    
      glMatrixMode(GL_MODELVIEW);          // Select the modelview matrix
      glLoadIdentity();                    // Reset (init) the modelview matrix
      gluLookAt(0.0f, 0.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

      glClearColor(0.0f, 0.0f, 0.0f, 0.0f);      // Set background to black
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glShadeModel(GL_SMOOTH);             // Enable smooth shading
      glClearDepth(1.0f);                  // Clear depth buffer
      glEnable(GL_DEPTH_TEST);             // Enables depth test
      glDepthFunc(GL_LEQUAL);              // Type of depth test to perform

      // Enhances image quality
      glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    
      glColor3f(0.5f, 0.5f, 1.0f);
}


// Declare rotation variables
static GLfloat spinx, spiny, spinz;


// OpenGL drawing and animation
void OnTimer(HDC hDC)
{
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      // Rotation variables
      spinx = 0.1f;
      spiny = -0.2f;
      spinz = 0.3f;

      glRotatef(spinx, 1.0f, 0.0f, 0.0f);
      glRotatef(spiny, 0.0f, 1.0f, 0.0f);
      glRotatef(spinz, 0.0f, 0.0f, 1.0f);

      glBegin(GL_QUADS);
        // Front Face
        glVertex3f(-1.0f, -1.0f, 1.0f);     // Bottom left
        glVertex3f( 1.0f, -1.0f, 1.0f);     // Bottom right
        glVertex3f( 1.0f,  1.0f, 1.0f);     // Top right
        glVertex3f(-1.0f,  1.0f, 1.0f);     // Top left

        // Back Face
        glVertex3f(-1.0f, -1.0f, -1.0f);    
        glVertex3f(-1.0f,  1.0f, -1.0f);    
        glVertex3f( 1.0f,  1.0f, -1.0f);    
        glVertex3f( 1.0f, -1.0f, -1.0f);    

        // Top Face
        glVertex3f(-1.0f,  1.0f, -1.0f);    
        glVertex3f(-1.0f,  1.0f,  1.0f);    
        glVertex3f( 1.0f,  1.0f,  1.0f);    
        glVertex3f( 1.0f,  1.0f, -1.0f);    

        // Bottom Face
        glVertex3f(-1.0f, -1.0f, -1.0f);    
        glVertex3f( 1.0f, -1.0f, -1.0f);    
        glVertex3f( 1.0f, -1.0f,  1.0f);    
        glVertex3f(-1.0f, -1.0f,  1.0f);    
  
        // Right Face
        glVertex3f( 1.0f, -1.0f, -1.0f);    
        glVertex3f( 1.0f,  1.0f, -1.0f);    
        glVertex3f( 1.0f,  1.0f,  1.0f);    
        glVertex3f( 1.0f, -1.0f,  1.0f);    

        // Left Face
        glVertex3f(-1.0f, -1.0f, -1.0f);    
        glVertex3f(-1.0f, -1.0f,  1.0f);    
        glVertex3f(-1.0f,  1.0f,  1.0f);    
        glVertex3f(-1.0f,  1.0f, -1.0f);    
      glEnd();

      SwapBuffers(hDC);
}

Well I hope this tutorial was as clear as I could make it. I myself copied and pasted the above code into a new C++ project (make sure you choose empty Window project) and followed the same directions I gave you for the resources and everything worked fine. Now you have a way to distribute your OpenGL projects in a form where others can appreciate your work.

If you have any questions or comments let me know at lance@lanceusa.com.


This tutorial was written on: 04.20.2003
:.Site.:.Menu.:
:// .site..news.
:// .tutorials..1.
:// .tutorials..2.
:// .projects.
:// .forums.
:// .gbook.
:// .links.