Blitter Function, RAW Texture Loading

This tutorial was originally written by Andreas Löffler. He also wrote all of the original HTML for the tutorial. A few days later Rob Fletcher emailed me an Irix version of lesson 29. In his version he rewrote most of the code. So I ported Rob's Irix / GLUT code to Visual C++ / Win32. I then modified the message loop code, and the fullscreen code. When the program is minimized it should use 0% of the CPU (or close to). When switching to and from fullscreen mode, most of the problems should be gone (screen not restoring properly, messed up display, etc).

Andreas tutorial is now better than ever. Unfortunately, the code has been modifed quite a bit, so all of the HTML has been rewritten by myself. Huge Thanks to Andreas for getting the ball rolling, and working his butt off to make a killer tutorial. Thanks to Rob for the modifications!

Lets begin... We create a device mode structure called DMsaved. We will use this structure to store information about the users default desktop resolution, color depth, etc., before we switch to fullscreen mode. More on this later! Notice we only allocate enough storage space for one texture (texture[1]).

#include	<windows.h>								// Header File For Windows
#include	<gl\gl.h>								// Header File For The OpenGL32 Library
#include	<gl\glu.h>								// Header File For The GLu32 Library
#include	<stdio.h>								// Header File For File Operation Needed

HDC		hDC=NULL;								// Private GDI Device Context
HGLRC		hRC=NULL;								// Permanent Rendering Context
HWND		hWnd=NULL;								// Holds Our Window Handle
HINSTANCE	hInstance = NULL;							// Holds The Instance Of The Application

bool		keys[256];								// Array Used For The Keyboard Routine
bool		active=TRUE;								// Window Active Flag Set To TRUE By Default
bool		fullscreen=TRUE;							// Fullscreen Flag Set To Fullscreen Mode By Default

DEVMODE		DMsaved;								// Saves The Previous Screen Settings (NEW)

GLfloat		xrot;									// X Rotation
GLfloat		yrot;									// Y Rotation
GLfloat		zrot;									// Z Rotation

GLuint		texture[1];								// Storage For 1 Texture

Now for the fun stuff. We create a structure called TEXTURE_IMAGE. The structure contains information about our images width, height, and format (bytes per pixel). data is a pointer to unsigned char. Later on data will point to our image data.

typedef struct Texture_Image
{
	int width;									// Width Of Image In Pixels
	int height;									// Height Of Image In Pixels
	int format;									// Number Of Bytes Per Pixel
	unsigned char *data;								// Texture Data
} TEXTURE_IMAGE;

We then create a pointer called P_TEXTURE_IMAGE to the TEXTURE_IMAGE data type. The variables t1 and t2 are of type P_TEXTURE_IMAGE where P_TEXTURE_IMAGE is a redefined type of pointer to TEXTURE_IMAGE.

typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE;							// A Pointer To The Texture Image Data Type

P_TEXTURE_IMAGE t1;									// Pointer To The Texture Image Data Type
P_TEXTURE_IMAGE t2;									// Pointer To The Texture Image Data Type

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);					// Declaration For WndProc

Below is the code to allocate memory for a texture. When we call this code, we pass it the width, height and bytes per pixel information of the image we plan to load. ti is a pointer to our TEXTURE_IMAGE data type. It's given a NULL value. c is a pointer to unsigned char, it is also set to NULL.

// Allocate An Image Structure And Inside Allocate Its Memory Requirements
P_TEXTURE_IMAGE AllocateTextureBuffer( GLint w, GLint h, GLint f)
{
	P_TEXTURE_IMAGE ti=NULL;							// Pointer To Image Struct
	unsigned char *c=NULL;								// Pointer To Block Memory For Image

Here is where we allocate the memory for our image structure. If everything goes well, ti will point to the allocated memory.

After allocating the memory, and checking to make sure ti is not equal to NULL, we can fill the structure with the image attributes. First we set the width (w), then the height (h) and lastly the format (f). Keep in mind format is bytes per pixel.

	ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE));				// One Image Struct Please

	if( ti != NULL ) {
		ti->width  = w;								// Set Width
		ti->height = h;								// Set Height
		ti->format = f;								// Set Format

Now we need to allocate memory for the actual image data. The calculation is easy! We multiply the width of the image (w) by the height of the image (h) then multiply by the format (f - bytes per pixel).

		c = (unsigned char *)malloc( w * h * f);

We check to see if everything went ok. If the value in c is not equal to NULL we set the data variable in our structure to point to the newly allocated memory.

If there was a problem, we pop up an error message on the screen letting the user know that the program was unable to allocate memory for the texture buffer. NULL is returned.

		if ( c != NULL ) {
			ti->data = c;
		}
		else {
			MessageBox(NULL,"Could Not Allocate Memory For A Texture Buffer","BUFFER ERROR",MB_OK | MB_ICONINFORMATION);
			return NULL;
		}
	}

If anything went wrong when we were trying to allocate memory for our image structure, the code below would pop up an error message and return NULL.

If there were no problems, we return ti which is a pointer to our newly allocated image structure. Whew... Hope that all made sense.

	else
	{
		MessageBox(NULL,"Could Not Allocate An Image Structure","IMAGE STRUCTURE ERROR",MB_OK | MB_ICONINFORMATION);
		return NULL;
	}
	return ti;									// Return Pointer To Image Struct
}

When it comes time to release the memory, the code below will deallocate the texture buffer and then free the image structure. t is a pointer to the TEXTURE_IMAGE data structure we want to deallocate.

// Free Up The Image Data
void DeallocateTexture( P_TEXTURE_IMAGE t )
{
	if(t)
	{
		if(t->data)
		{
			free(t->data);							// Free Its Image Buffer
		}

		free(t);								// Free Itself
	}
}

Now we read in our .RAW image. We pass the filename and a pointer to the image structure we want to load the image into. We set up our misc variables, and then calculate the size of a row. We figure out the size of a row by multiplying the width of our image by the format (bytes per pixel). So if the image was 256 pixels wide and there were 4 bytes per pixel, the width of a row would be 1024 bytes. We store the width of a row in stride.

We set up a pointer (p), and then attempt to open the file.

// Read A .RAW File In To The Allocated Image Buffer Using data In The Image Structure Header.
// Flip The Image Top To Bottom.  Returns 0 For Failure Of Read, Or Number Of Bytes Read.
int ReadTextureData ( char *filename, P_TEXTURE_IMAGE buffer)
{
	FILE *f;
	int i,j,k,done=0;
	int stride = buffer->width * buffer->format;					// Size Of A Row (Width * Bytes Per Pixel)
	unsigned char *p = NULL;

	f = fopen(filename, "rb");							// Open "filename" For Reading Bytes
	if( f != NULL )									// If File Exists
	{

If the file exists, we set up the loops to read in our texture. i starts at the bottom of the image and moves up a line at a time. We start at the bottom so that the image is flipped the right way. .RAW images are stored upside down. We have to set our pointer now so that the data is loaded into the proper spot in the image buffer. Each time we move up a line (i is decreased) we set the pointer to the start of the new line. data is where our image buffer starts, and to move an entire line at a time in the buffer, multiply i by stride. Remember that stride is the length of a line in bytes, and i is the current line. So by multiplying the two, we move an entire line at a time.

The j loop moves from left (0) to right (width of line in pixels, not bytes).

		for( i = buffer->height-1; i >= 0 ; i-- )				// Loop Through Height (Bottoms Up - Flip Image)
		{
			p = buffer->data + (i * stride );
			for ( j = 0; j < buffer->width ; j++ )				// Loop Through Width
			{

The k loop reads in our bytes per pixel. So if format (bytes per pixel) is 4, k loops from 0 to 2 which is bytes per pixel minus one (format-1). The reason we subtract one is because most raw images don't have an alpha value. We want to make the 4th byte our alpha value, and we want to set the alpha value manually.

Notice in the loop we also increase the pointer (p) and a variable called done. More about done later.

The line inside the loop reads a character from our file and stores it in the texture buffer at our current pointer location. If our image has 4 bytes per pixel, the first 3 bytes will be read from the .RAW file (format-1), and the 4th byte will be manually set to 255. After we set the 4th byte to 255 we increase the pointer location by one so that our 4th byte is not overwritten with the next byte in the file.

After all of the bytes have been read in per pixel, and all of the pixels have been read in per row, and all of the rows have been read in, we are done! We can close the file.

				for ( k = 0 ; k < buffer->format-1 ; k++, p++, done++ )
				{
					*p = fgetc(f);					// Read Value From File And Store In Memory
				}
				*p = 255; p++;						// Store 255 In Alpha Channel And Increase Pointer
			}
		}
		fclose(f);								// Close The File
	}

If there was a problem opening the file (does not exist, etc), the code below will pop up a message box letting the user know that the file could not be opened.

The last thing we do is return done. If the file couldn't be opened, done will equal 0. If everything went ok, done should equal the number of bytes read from the file. Remember, we were increasing done every time we read a byte in the loop above (k loop).

	else										// Otherwise
	{
		MessageBox(NULL,"Unable To Open Image File","IMAGE ERROR",MB_OK | MB_ICONINFORMATION);
	}
	return done;									// Returns Number Of Bytes Read In
}

This shouldn't need explaining. By now you should know how to build a texture. tex is the pointer to the TEXTURE_IMAGE structure that we want to use. We build a linear filtered texture. In this example, we're building mipmaps (smoother looking). We pass the width, height and data just like we would if we were using glaux, but this time we get the information from the selected TEXTURE_IMAGE structure.

void BuildTexture (P_TEXTURE_IMAGE tex)
{
	glGenTextures(1, &texture[0]);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data);
}

Now for the blitter code :) The blitter code is very powerful. It lets you copy any section of a (src) texture and paste it into a destination (dst) texture. You can combine as many textures as you want, you can set the alpha value used for blending, and you can select whether the two images blend together or cancel eachother out.

src is the TEXTURE_IMAGE structure to use as the source image. dst is the TEXTURE_IMAGE structure to use for the destination image. src_xstart is where you want to start copying from on the x axis of the source image. src_ystart is where you want to start copying from on the y axis of the source image. src_width is the width in pixels of the area you want to copy from the source image. src_height is the height in pixels of the area you want to copy from the source image. dst_xstart and dst_ystart is where you want to place the copied pixels from the source image onto the destination image. If blend is 1, the two images will be blended. alpha sets how transparent the copied image will be when it mapped onto the destination image. 0 is completely clear, and 255 is solid.

We set up all our misc loop variables, along with pointers for our source image (s) and destination image (d). We check to see if the alpha value is within range. If not, we clamp it. We do the same for the blend value. If it's not 0-off or 1-on, we clamp it.

void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height,
	   int dst_xstart, int dst_ystart, int blend, int alpha)
{
	int i,j,k;
	unsigned char *s, *d;								// Source & Destination

	// Clamp Alpha If Value Is Out Of Range
	if( alpha > 255 ) alpha = 255;
	if( alpha < 0 ) alpha = 0;

	// Check For Incorrect Blend Flag Values
	if( blend < 0 ) blend = 0;
	if( blend > 1 ) blend = 1;

Now we have to set up the pointers. The destination pointer is the location of the destination data plus the starting location on the destination images y axis (dst_ystart) * the destination images width in pixels * the destination images bytes per pixel (format). This should give us the starting row for our destination image.

We do pretty much the same thing for the source pointer. The source pointer is the location of the source data plus the starting location on the source images y axis (src_ystart) * the source images width in pixels * the source images bytes per pixel (format). This should give us the starting row for our source image.

i loops from 0 to src_height which is the number of pixels to copy up and down from the source image.

	d = dst->data + (dst_ystart * dst->width * dst->format);  			// Start Row - dst (Row * Width In Pixels * Bytes Per Pixel)
	s = src->data + (src_ystart * src->width * src->format);			// Start Row - src (Row * Width In Pixels * Bytes Per Pixel)

	for (i = 0 ; i < src_height ; i++ )						// Height Loop
	{

We already set the source and destination pointers to the correct rows in each image. Now we have to move to the correct location from left to right in each image before we can start blitting the data. We increase the location of the source pointer (s) by src_xstart which is the starting location on the x axis of the source image times the source images bytes per pixel. This moves the source (s) pointer to the starting pixel location on the x axis (from left to right) on the source image.

We do the exact same thing for the destination pointer. We increase the location of the destination pointer (d) by dst_xstart which is the starting location on the x axis of the destination image multiplied by the destination images bytes per pixel (format). This moves the destination (d) pointer to the starting pixel location on the x axis (from left to right) on the destination image.

After we have calculated where in memory we want to grab our pixels from (s) and where we want to move them to (d), we start the j loop. We'll use the j loop to travel from left to right through the source image.

		s = s + (src_xstart * src->format);					// Move Through Src Data By Bytes Per Pixel
		d = d + (dst_xstart * dst->format);					// Move Through Dst Data By Bytes Per Pixel
		for (j = 0 ; j < src_width ; j++ )					// Width Loop
		{

The k loop is used to go through all the bytes per pixel. Notice as k increases, our pointers for the source and destination images also increase.

Inside the loop we check to see if blending is on or off. If blend is 1, meaning we should blend, we do some fancy math to calculate the color of our blended pixels. The destination value (d) will equal our source value (s) multiplied by our alpha value + our current destination value (d) times 255 minus the alpha value. The shift operator (>>8) keeps the value in a 0-255 range.

If blending is disabled (0), we copy the data from the source image directly into the destination image. No blending is done and the alpha value is ignored.

			for( k = 0 ; k < src->format ; k++, d++, s++)			// "n" Bytes At A Time
			{
				if (blend)						// If Blending Is On
				*d = ( (*s * alpha) + (*d * (255-alpha)) ) >> 8;	// Multiply Src Data*alpha Add Dst Data*(255-alpha)
				else							// Keep in 0-255 Range With >> 8
				*d = *s;						// No Blending Just Do A Straight Copy
			}
		}
		d = d + (dst->width - (src_width + dst_xstart))*dst->format;		// Add End Of Row
		s = s + (src->width - (src_width + src_xstart))*src->format;		// Add End Of Row
	}
}

The InitGL() code has changed quite a bit. All of the code below is new. We start off by allocating enough memory to hold a 256x256x4 Bytes Per Pixel Image. t1 will point to the allocated ram if everything went well.

After allocating memory for our image, we attempt to load the image. We pass ReadTextureData() the name of the file we wish to open, along with a pointer to our Image Structure (t1).

If we were unable to load the .RAW image, a message box will pop up on the screen to let the user know there was a problem loading the texture.

We then do the same thing for t2. We allocate memory, and attempt to read in our second .RAW image. If anything goes wrong we pop up a message box.

int InitGL(GLvoid)									// This Will Be Called Right After The GL Window Is Created
{
	t1 = AllocateTextureBuffer( 256, 256, 4 );					// Get An Image Structure
	if (ReadTextureData("Data/Monitor.raw",t1)==0)					// Fill The Image Structure With Data
	{										// Nothing Read?
		MessageBox(NULL,"Could Not Read 'Monitor.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
		return FALSE;
	}

	t2 = AllocateTextureBuffer( 256, 256, 4 );					// Second Image Structure
	if (ReadTextureData("Data/GL.raw",t2)==0)					// Fill The Image Structure With Data
	{										// Nothing Read?
		MessageBox(NULL,"Could Not Read 'GL.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
		return FALSE;
	}

If we got this far, it's safe to assume the memory has been allocated and the images have been loaded. Now to use our Blit() command to merge the two images into one.

We start off by passing Blit() t2 and t1, both point to our TEXTURE_IMAGE structures (t2 is the second image, t1 is the first image).

Then we have to tell blit where to start grabbing data from on the source image. If you load the source image into Adobe Photoshop or any other program capable of loading .RAW images you will see that the entire image is blank except for the top right corner. The top right has a picture of the ball with GL written on it. The bottom left corner of the image is 0,0. The top right of the image is the width of the image-1 (255), the height of the image-1 (255). Knowing that we only want to copy 1/4 of the src image (top right), we tell Blit() to start grabbing from 127,127 (center of our source image).

Next we tell blit how many pixels we want to copy from our source point to the right, and from our source point up. We want to grab a 1/4 chunk of our image. Our image is 256x256 pixels, 1/4 of that is 128x128 pixels. All of the source information is done. Blit() now knows that it should copy from 127 on the x axis to 127+128 (255) on the x axis, and from 127 on the y axis to 127+128 (255) on the y axis.

So Blit() knows what to copy, and where to get the data from, but it doesn't know where to put the data once it's gotten it. We want to draw the ball with GL written on it in the middle of the monitor image. You find the center of the destination image (256x256) which is 128x128 and subtract half the width and height of the source image (128x128) which is 64x64. So (128-64) x (128-64) gives us a starting location of 64,64.

Last thing to do is tell our blitter routine we want to blend the two images (A one means blend, a zero means do not blend), and how much to blend the images. If the last value is 0, we blend the images 0%, meaning anything we copy will replace what was already there. If we use a value of 127, the two images blend together at 50%, and if you use 255, the image you are copying will be completely transparent and will not show up at all.

The pixels are copied from image2 (t2) to image1 (t1). The mixed image will be stored in t1.

	// Image To Blend In, Original Image, Src Start X & Y, Src Width & Height, Dst Location X & Y, Blend Flag, Alpha Value
	Blit(t2,t1,127,127,128,128,64,64,1,127);					// Call The Blitter Routine

After we have mixed the two images (t1 and t2) together, we build a texture from the combined images (t1).

After the texture has been created, we can deallocate the memory holding our two TEXTURE_IMAGE structures.

The rest of the code is pretty standard. We enable texture mapping, depth testing, etc.

	BuildTexture (t1);								// Load The Texture Map Into Texture Memory

	DeallocateTexture( t1 );							// Clean Up Image Memory Because Texture Is
	DeallocateTexture( t2 );							// In GL Texture Memory Now

	glEnable(GL_TEXTURE_2D);							// Enable Texture Mapping

	glShadeModel(GL_SMOOTH);							// Enables Smooth Color Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);						// This Will Clear The Background Color To Black
	glClearDepth(1.0);								// Enables Clearing Of The Depth Buffer
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LESS);								// The Type Of Depth Test To Do

	return TRUE;
}

I shouldn't even have to explain the code below. We move 5 units into the screen, select our single texture, and draw a texture mapped cube. You should notice that both textures are now combined into one. We don't have to render everything twice to map both textures onto the cube. The blitter code combined the images for us.

GLvoid DrawGLScene(GLvoid)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);				// Clear The Screen And The Depth Buffer
	glLoadIdentity();								// Reset The View
	glTranslatef(0.0f,0.0f,-5.0f);

	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);
	glRotatef(zrot,0.0f,0.0f,1.0f);

	glBindTexture(GL_TEXTURE_2D, texture[0]);

	glBegin(GL_QUADS);
		// Front Face
		glNormal3f( 0.0f, 0.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		// Back Face
		glNormal3f( 0.0f, 0.0f,-1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		// Top Face
		glNormal3f( 0.0f, 1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		// Bottom Face
		glNormal3f( 0.0f,-1.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		// Right Face
		glNormal3f( 1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		// Left Face
		glNormal3f(-1.0f, 0.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
	glEnd();

	xrot+=0.3f;
	yrot+=0.2f;
	zrot+=0.4f;
}

The KillGLWindow() code has a few changes. You'll notice the code to switch from fullscreen mode back to your desktop is now at the top of KillGLWindow(). If the user ran the program in fullscreen mode, the first thing we do when we kill the window is try to switch back to the desktop resolution. If the quick way fails to work, we reset the screen using the information stored in DMsaved. This should restore us to our orignal desktop settings.

GLvoid KillGLWindow(GLvoid)								// Properly Kill The Window
{
	if (fullscreen)									// Are We In Fullscreen Mode?
	{
		if (!ChangeDisplaySettings(NULL,CDS_TEST)) {				// If The Shortcut Doesn't Work
			ChangeDisplaySettings(NULL,CDS_RESET);				// Do It Anyway (To Get The Values Out Of The Registry)
			ChangeDisplaySettings(&DMsaved,CDS_RESET);			// Change Resolution To The Saved Settings
		}
		else									// Not Fullscreen
		{
			ChangeDisplaySettings(NULL,CDS_RESET);				// Do Nothing
		}

		ShowCursor(TRUE);							// Show Mouse Pointer
	}

	if (hRC)									// Do We Have A Rendering Context?
	{
		if (!wglMakeCurrent(NULL,NULL))						// Are We Able To Release The DC And RC Contexts?
		{
			MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}

		if (!wglDeleteContext(hRC))						// Are We Able To Delete The RC?
		{
			MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}
		hRC=NULL;								// Set RC To NULL
	}

	if (hDC && !ReleaseDC(hWnd,hDC))						// Are We Able To Release The DC
	{
		MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hDC=NULL;								// Set DC To NULL
	}

	if (hWnd && !DestroyWindow(hWnd))						// Are We Able To Destroy The Window?
	{
		MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hWnd=NULL;								// Set hWnd To NULL
	}

	if (!UnregisterClass("OpenGL",hInstance))					// Are We Able To Unregister Class
	{
		MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hInstance=NULL;								// Set hInstance To NULL
	}
}

I've made some changes in CreateGLWindow. The changes will hopefully elimintate alot of the problems people are having when they switch to and from from fullscreen mode. I've included the first part of CreateGLWindow() so you can easily follow through the code.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
	GLuint		PixelFormat;							// Holds The Results After Searching For A Match
	WNDCLASS	wc;								// Windows Class Structure
	DWORD		dwExStyle;							// Window Extended Style
	DWORD		dwStyle;							// Window Style

	fullscreen=fullscreenflag;							// Set The Global Fullscreen Flag

	hInstance		= GetModuleHandle(NULL);				// Grab An Instance For Our Window
	wc.style		= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;			// Redraw On Size, And Own DC For Window.
	wc.lpfnWndProc		= (WNDPROC) WndProc;					// WndProc Handles Messages
	wc.cbClsExtra		= 0;							// No Extra Window Data
	wc.cbWndExtra		= 0;							// No Extra Window Data
	wc.hInstance		= hInstance;						// Set The Instance
	wc.hIcon		= LoadIcon(NULL, IDI_WINLOGO);				// Load The Default Icon
	wc.hCursor		= LoadCursor(NULL, IDC_ARROW);				// Load The Arrow Pointer
	wc.hbrBackground	= NULL;							// No Background Required For GL
	wc.lpszMenuName		= NULL;							// We Don't Want A Menu
	wc.lpszClassName	= "OpenGL";						// Set The Class Name

The big change here is that we now save the current desktop resolution, bit depth, etc. before we switch to fullscreen mode. That way when we exit the program, we can set everything back exactly how it was. The first line below copies the display settings into the DMsaved Device Mode structure. Nothing else has changed, just one new line of code.

	EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);			// Save The Current Display State (NEW)

	if (fullscreen)									// Attempt Fullscreen Mode?
	{
		DEVMODE dmScreenSettings;						// Device Mode
		memset(&dmScreenSettings,0,sizeof(dmScreenSettings));			// Makes Sure Memory's Cleared
		dmScreenSettings.dmSize=sizeof(dmScreenSettings);			// Size Of The Devmode Structure
		dmScreenSettings.dmPelsWidth	= width;				// Selected Screen Width
		dmScreenSettings.dmPelsHeight	= height;				// Selected Screen Height
		dmScreenSettings.dmBitsPerPel	= bits;					// Selected Bits Per Pixel
		dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

		// Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
		if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
		{
			// If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
			if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
			{
				fullscreen=FALSE;					// Windowed Mode Selected.  Fullscreen = FALSE
			}
			else
			{
				// Pop Up A Message Box Letting User Know The Program Is Closing.
				MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
				return FALSE;						// Return FALSE
			}
		}
	}

WinMain() starts out the same as always. Ask the user if they want fullscreen or not, then start the loop.

int WINAPI WinMain(	HINSTANCE	hInstance,					// Instance
			HINSTANCE	hPrevInstance,					// Previous Instance
			LPSTR		lpCmdLine,					// Command Line Parameters
			int		nCmdShow)					// Window Show State
{
	MSG	msg;									// Windows Message Structure
	BOOL	done=FALSE;								// Bool Variable To Exit Loop

	// Ask The User Which Screen Mode They Prefer
	if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
	{
		fullscreen=FALSE;							// Windowed Mode
	}

	// Create Our OpenGL Window
	if (!CreateGLWindow("Andreas Löffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial", 640, 480, 32, fullscreen))
	{
		return 0;								// Quit If Window Was Not Created
	}

	while(!done)									// Loop That Runs While done=FALSE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))				// Is There A Message Waiting?
		{
			if (msg.message==WM_QUIT)					// Have We Received A Quit Message?
			{
				done=TRUE;						// If So done=TRUE
			}
			else								// If Not, Deal With Window Messages
			{
				TranslateMessage(&msg);					// Translate The Message
				DispatchMessage(&msg);					// Dispatch The Message
			}
		}

I have made some changes to the code below. If the program is not active (minimized) we wait for a message with the command WaitMessage(). Everything stops until the program receives a message (usually maximizing the window). What this means is that the program no longer hogs the processor while it's minimized. Thanks to Jim Strong for the suggestion.

		if (!active)								// Program Inactive?
		{
			WaitMessage();							// Wait For A Message / Do Nothing ( NEW ... Thanks Jim Strong )
		}

		if (keys[VK_ESCAPE])							// Was Escape Pressed?
		{
			done=TRUE;							// ESC Signalled A Quit
		}

		if (keys[VK_F1])							// Is F1 Being Pressed?
		{
			keys[VK_F1]=FALSE;						// If So Make Key FALSE
			KillGLWindow();							// Kill Our Current Window
			fullscreen=!fullscreen;						// Toggle Fullscreen / Windowed Mode
			// Recreate Our OpenGL Window
			if (!CreateGLWindow("Andreas Löffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial",640,480,16,fullscreen))
			{
				return 0;						// Quit If Window Was Not Created
			}
		}

		DrawGLScene();								// Draw The Scene
		SwapBuffers(hDC);							// Swap Buffers (Double Buffering)
	}

	// Shutdown
	KillGLWindow();									// Kill The Window
	return (msg.wParam);								// Exit The Program
}

Well, that´s it! Now the doors are open for creating some very cool blending effects for your games, engines or even applications. With texture buffers we used in this tutorial you could do more cool effects like real-time plasma or water. When combining these effects all together you´re able to do nearly photo-realistic terrain. If something doesn´t work in this tutorial or you have suggestions how to do it better, then please don´t hesitate to E-Mail me. Thank you for reading and good luck in creating your own special effects!

Some information about Andreas: I´m an 18 years old pupil who is currently studying to be a software engineer. I´ve been programming for nearly 10 years now. I've been programming in OpenGL for about 1.5 years.

Andreas Löffler & Rob Fletcher

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code For This Lesson.

* DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl )
* DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Dan )
* DOWNLOAD Euphoria Code For This Lesson. ( Conversion by Evan Marshall )
* DOWNLOAD LCC Win32 Code For This Lesson. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux/GLX Code For This Lesson. ( Conversion by Rodolphe Suescun )
* DOWNLOAD LWJGL Code For This Lesson. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code For This Lesson. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Studio .NET Code For This Lesson. ( Conversion by Grant James )

 

< Lesson 28Lesson 30 >