OpenGL Screensaver sourcecode.

Operating System:	Win32	(Win95/98/NT4/NT5, don't bother Windows CE)
Compiler:		Visual C++ 5.0, with Microsoft Foundation Classes (MFC).
			(I installed Service Pack 3, for VC5.)

Overview:		The most important about this screensaver, was to learn a bit more about OpenGL.
			I don't even think about winning any contest with this program, BUT...
			I am sure, many people can have a look at the sources, and learn a bit.

In my opinion, it's useless to 'invent the wheel' over and over again, without making use of other peoples experience. When programming C++, there are many things that make life easier. One of these are API's (Application Programmers Interface) and libraries. Examples of API's are OpenGL, Windows 32bit API (In fact, this consists of several API's), and a good example of a library is cimage. I have used this library to support graphics formats like GIF, BMP and JPG in my screensaver. In case you are interested, I have included the helpfile, and the precompiled lib files, for Visual C++ 5.0 (Debug + Release versions.) as well as the header files.

[technical talk: Debug version has MFC dynamicly linked, and Release version is staticly linked to MFC. end technical talk].

Some time ago, people just wrote straight in C, or even assembly to achieve the speed needed for 3D graphics. I have done this myself too. Days of optimizing the innerloop of the texturemapper, for example. Now we can make use of hardware acceleration (like 3Dfx Voodoo, Rush & Voodoo 2), and this relieves the programmer from the nasty assembly.

The Glide library (an API) is made specially for their hardware. Glide resembles OpenGL, the names look a bit the same. OpenGL is platform independant, and virtually any computer can use it, from cheap to very expensive. 3Dfx has made a miniport of opengl, specially for the game Quake (and Quake 2) of idSoftware. This (partial) opengl library can be used to write your own programs in (like this screensaver), but you have to be very careful, because there is little documentation available.

3Dfx is not the only one, that comes with opengl drivers. Mesa is a project, that has a very good library which is nearly compatible with OpenGL (although not certified). Microsoft (of course), is doing the job (called Fahrenheit), and Scitech has it's MGL, which is another OpenGL-like library.

OpenGL is a state-based system. This means that it 'remembers' it's state, and you only have to tell the changes. This is good, because sending information from the PC to an external device (like a OpenGL graphics card), is always MUCH slower then the communication onboard.

I have written a C++ Class (called COpenGLss). This class handles the initializing of OpenGL hardware, the drawing loop, and other gl related things, like pixel formats, and so on.

The graphics have to be smooth, so frequent updated. A simple windows timer is not enough for this. I wrote a worker thread, that handles the drawing loop. 
It's important to make sure, that you know from where opengl is called, because the 3dfx drivers are NOT reentrant yet. This means, that you cannot call various opengl functions simultaniously. by putting all the opengl related calls in ONE thread, I could establish this.

One important thing is the interface between opengl en windows. As I told before, opengl is platform independant, so there has to be a bridge. This bridge is called GLU (after glue? ). You can connect the windows context to opengl.

One problem I faced was how to create a fullscreen screen, with the 3dfx opengl. I am no opengl expert at all, so I had to search on the internet, for solutions. I posted some questions at a few newsgroups, but no real good answer. At last, I desperately sended an email to John Carmack (the quake programmer, who used the 3dfx opengl itsself). I didn't expect an answer, but within 15 minutes, I got reply.
It was a simple solution, but I was looking to deep. I was thinking about DirectDraw to get the fullscreen stuff working. He told me to use the routine ChangeDisplaySettings, as a general way to do fullscreen. You can find this in the file SaverWnd.CPP.


	//switch screen resolution dynamicly to 640x480x16 if possible

	
	int i=0;
	DEVMODE trydevmode,usedevmode;
	BOOL foundmode=FALSE;
	while (EnumDisplaySettings(NULL,i,&trydevmode)) {
		if (trydevmode.dmPelsWidth==640 && trydevmode.dmPelsHeight==480 
			&& trydevmode.dmBitsPerPel ==16) {
			usedevmode=trydevmode;
			foundmode=TRUE;
		};
	i++;
	}
	if (foundmode) {
		usedevmode.dmFields=DM_PELSWIDTH|DM_PELSHEIGHT;
		ChangeDisplaySettings(&usedevmode,0);
	};
	m_bFullscreen=TRUE;

What is does, is querying for all available modes (using EnumDisplaySettings), and then trying to change the settings dynamicly.

-> Changing back to original size is very easy: only ChangeDisplaySettings(NULL,0) will do the trick !

Another detail is the displaying in a window (when you install the screensaver, or change the settings.)
I wanted to do this in software for the Voodoo, and in hardware acceleration for the Voodoo Rush (Remember, the Voodoo can only do fullscreen, but windows has an opengl software driver, which is fast enough for that very little window).

One solution, which didn't work out for me, was loading the opengl32.dll 'manually' using (Afx)LoadLibrary. But I only got many access violations (0005), and couldn't get rid of the problems. 

There is a program GlTrace, which does exactly this, using LoadLibrary, and GetProcAddress. 

Don't use this with little knowledge, because it's not fault tolerant. Changing addresses of functions, and jumping to them can be fatal for your program... and even windows can crash.

The settings of the screensaver can be easily written into the registry.

void CSaverApp::DoConfig()
{
	CSaverDlg dlg;
	dlg.m_nPicName = GetProfileString(szConfig, _T("Picture"), "test.gif");
	dlg.m_bBilinear = GetProfileInt (szConfig,_T("Bilinear"),TRUE);
	dlg.m_bWinPrev = GetProfileInt (szConfig,_T("WindowPrev"),FALSE);
	dlg.m_effect = GetProfileInt (szConfig,_T("Effect"),EFFECT_WAVE);
	
	m_pMainWnd = &dlg;
	if (dlg.DoModal() == IDOK)
	{
		WriteProfileInt(szConfig,_T("Effect"),dlg.m_effect);
		WriteProfileInt(szConfig,_T("WindowPrev"),dlg.m_bWinPrev);
		WriteProfileInt(szConfig,_T("Bilinear"),dlg.m_bBilinear);
		WriteProfileString(szConfig, _T("Picture"), dlg.m_nPicName);
	}
}

Could it be easier ?

The main effects take place in the the COpenGLss class:


void COpenGLss::Action()
{
	// this is were the 'dynamic action' takes place
	// there isn't a lot going on at the moment
	// but that may change... don't you think ?

	g_cs.Lock();

	switch (m_Actions)
	{

	case ssIdle:	{
		
		// start new effect 
		// lookup in registry what the users wants us to do
		int effect = AfxGetApp()->GetProfileInt("Config", "Effect", EFFECT_ROTATE);
		if (effect==EFFECT_ROTATE) {
			m_Actions=ssZoomIn;
			m_fRadius=MAX_DISTANCE;	
			m_ActionCounter=0;
		} else 
			if (effect==EFFECT_WAVE) {
			m_Actions =ssColorIn;
			m_fRadius=MIN_DISTANCE;
			m_ActionCounter=0;
			} else if (effect==EFFECT_RANDOM) {
				// choose one of other effects randomly...
			};
		break;
					};
	case ssZoomIn:	{
		m_fRadius-=ZOOM_SPEED;
		if (m_fRadius <= MIN_DISTANCE) {
			m_Actions=ssRotateX;
			m_ActionCounter=0;
		};

		break;
				};
	case ssRotateX:	{

I hope you enjoyed reading this, and maybe see you later, on my website. There you can find a little article about collision detection, and some other things.

If you know why this program sometimes crashes on Voodoo Rush (ending in a blank screen, and nothing except the
reset button has effect), please let me know. It's with the 3dfx opengl, and with mesa's 3dfx opengl 
(win32 version).


kind regards,

Erwin Coumans
coockie@stack.nl
www.stack.nl/~coockie