/******************************************************************************
 * ATI 3D RAGE PRO SDK sample code.                                           *
 *                                                                            *
 * RPexpl4.c - RAGE PRO 16 Bit (4444) Windowed Explosion Example.             *
 *                                                                            *
 * Copyright (c) 1997 ATI Technologies Inc. All rights reserved.              *
 *																			  *
 * This application requires a 2MB RAGE PRO and DirectX 3.0 or later.		  *
 * It will only run in a 16-bit mode.										  *
 *																			  *
 * Please see the README.NOW file in this directory for more information.	  *
 *																			  *
 * This example displays a sequence of seven 16-bit alpha blended texture	  *
 * maps in a window. It shows how to display a 2D texture map on a 3D		  *
 * primitive (this is important for converting legacy code which handles	  *
 * only 3D acceleration).													  *
 *																			  *
 * Material covered: 														  *
 * 	Creation of a windowed app when the client area of the window is known    *
 *	DirectDraw initialization for a windowed application					  *
 *	Primary and back buffer creation and usage (in video memory)			  *
 *	Blitting																  *
 *	ATI3DCIF driver interface initialization								  *
 *	ATI3DCIF global state setup for 4444 alpha blended texture mapping		  *
 *	3D hardware setup and primitive rendering using the ATI3DCIF			  *
 *	32 bit texture loading to a 16 bit surface allocated in video memory	  *
 *	Example file format is 32 bit Targa (uncompressed, RGBA)				  *
 *																			  *
 *																			  *
 * There are three portions to any ATI3DCIF application: (a) setup and		  *
 * initialization that get done once in the application's lifetime, 		  *
 * (b) information that must get set/reset for every frame display, 		  *
 * and (c) clean up and exit.												  *
 *																			  *
 * (a) Setup and Initialization (done once at startup) includes:			  *
 *																			  *
 *	Application window creation												  *
 *	DirectDraw initialization												  *
 *	ATI3DCIF initialization													  *
 *	Global ATI3DCIF rendering state initialization 							  *
 *	Other data initialization, including Targa texture loading				  *
 *																			  *
 * (b) Frame specific includes:												  *
 *																			  *
 *	Clearing out the back buffer											  *
 *	Any frame relevant rendering state changes, such as 					  *
 *		current frame buffer pointer and current texture					  *
 *	Set hardware to enable 3D rendering (RenderBegin)						  *
 *	Render the current primitive list										  *
 *	Reset the hardware so other operations may take place (RenderEnd)		  *
 *	Blit (or flip) the back buffer to the front for display					  *
 *																			  *
 * (c) Destruction and clean up (done once at exit) includes:				  *
 *																			  *
 *	Free all data memory allocations										  *
 *	Unload the ATI3DCIF driver interface module								  *
 *	Close DirectDraw														  *
 *	Application window destruction											  *
 *																			  *
 ******************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ddraw.h>
#include <stdio.h>			

#include "ati3dcif.h"

//Targa header info
#pragma pack (push)
#pragma pack (1)	//dont pad the following struct

typedef struct _TGAHeaderInfo
{
	BYTE	idlen;		//length of optional identification sequence
	BYTE	cmtype;		//indicates whether a palette is present
	BYTE	imtype;		//image data type (e.g., uncompressed RGB)
	WORD 	cmorg;		//first palette index, if present
	WORD	cmcnt;		//number of palette entries, if present
	BYTE	cmsize;		//number of bits per palette entry
	WORD	imxorg;		//horiz pixel coordinate of lower left of image
	WORD	imyorg;		//vert pixel coordinate of lower left of image
	WORD	imwidth;	//image width in pixels
	WORD	imheight;	//image height in pixels
	BYTE	imdepth;	//image color depth (bits per pixel)
	BYTE	imdesc;		//image attribute flags
}TGAHeaderInfo;

#pragma pack (pop)

//Convenient texture info structure, we package a 
//surface description, surface pointer and texture
//handle for any textures that we'll be using.
typedef struct _Texture
{
    LPDIRECTDRAWSURFACE lpDDSTex;
    DDSURFACEDESC       ddsd;
    C3D_HTX             hTX;
} Texture;


static LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static BOOL InitDirectDraw (void);
static void CloseDirectDraw (void);
static BOOL InitATI3DCIF (void);
static void CloseATI3DCIF (void);
static BOOL SetATI3DCIFInitialState (void);
static BOOL LoadTargaTextures (void);
static void UnloadTargaTextures (void);
static BOOL LoadTexture (const char *pszTGAFile, Texture *pTex);
static void UnloadTexture (Texture *pTex);
static BOOL CopyTextureData (TGAHeaderInfo *TGAh, unsigned char *pdata, Texture *pTex);
static BOOL InitApp (void);
static void CloseApp (void);
static void DrawFrame (void);
static void RenderScene (void);
static C3D_UINT32 IsValidTexDimension (WORD num);
static BOOL IsDisplay16Bit (void);
static void ErrBox(const char *errStr, ... );
static void _Assert (const char *file, int line, const char *msg);

#define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__, #x)

#define RELEASE(x)	{												\
						if (x == NULL) 								\
							ErrBox ("Tried to free Null pointer");	\
						else										\
						{											\
							x->Release(); 							\
							x = NULL;								\
						}											\
					}

//constants specific to this application
#define OUR_WIDTH			320			//main window client area width
#define OUR_HEIGHT			200			//main window client area height

#define COLORDEPTH_IN_BYTES	2			//16 bit video mode has 2 bytes per pixel
#define IMAGEDEPTH_IN_BYTES 4			//32 bit Targa --> 4 bytes per pixel
#define FILL_COLOR			0x3DEF		//medium gray (16 bit (1555))

#define NTEXS_IN_SEQUENCE	7			//number of textures that we will display
#define NFRAMES_PER_LEVEL	32			//how long we will display each texture for

//Note on texture storage: 
//An array of textures was used here to allow easy use of other 128x128 32bit 
//image files. It may be more efficient (size and performance) to store all 
//textures in larger "page" surfaces and then index into them. (If you do this 
//make sure you put all the textures you are most likely to use at the same 
//time in the same page to make it more likely that they'll wind up cached.)
Texture gTex[NTEXS_IN_SEQUENCE];		//holds our texture sequence

const char *gTGAFile[] = {{"Ex51118.tga"},
						  {"Ex51119.tga"},
						  {"Ex51120.tga"},
						  {"Ex51121.tga"},
						  {"Ex51122.tga"},
						  {"Ex51123.tga"},
						  {"Ex51124.tga"}};


LPDIRECTDRAW        glpDD = NULL;			//DirectDraw object
LPDIRECTDRAWSURFACE glpDDSPrimary = NULL;	//DirectDraw primary surface
LPDIRECTDRAWSURFACE glpDDSBack = NULL;		//DirectDraw back surface
LPDIRECTDRAWCLIPPER glpDDClip = NULL;		//DirectDraw clipper	
C3D_HRC             ghRC = FALSE;			//ATI3DCIF rendering context
BOOL                gbCIFInit = FALSE;		//ATI3DCIF driver interface state

char                gszErrMsg[64];			//global error message text
HWND				ghWnd;					//global window handle 

//3D primitive that we will map our textures to to show off 3D acceleration.
//We will roughly center this in our main window, and make the object size
//128x128 (the size of our textures) to keep things simple. A C3D_VTCF vertex
//type uses x, y, z, s, t, w, r, g, b, and a to represent a vertex.
C3D_VTCF gvtcfPrim1TriList[6]= {
    { 100.0f,  40.0f, 0.0f, 0.0f, 1.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f,  40.0f, 0.0f, 1.0f, 1.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f},   
    { 100.0f, 168.0f, 0.0f, 0.0f, 0.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f, 168.0f, 0.0f, 1.0f, 0.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f}, 
    { 100.0f, 168.0f, 0.0f, 0.0f, 0.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f,  40.0f, 0.0f, 1.0f, 1.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f}   
};

//Primitive list array of vertex pointers.
C3D_PVTCF gvlstPrim1List[6];
 

//WinMain is standard Windows creation code. The PeekMessage form is used
//for the main event loop. When there are no messages pending, control
//drops into RenderScene. 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
										LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
    WNDCLASSEX wndclass;
	RECT rc;
	DWORD dwStyle;
	const char *pszClassName = "RAGE PRO Windowed 4444 Explosion Sequence";			    
	const char *pszWindowCaption = "RAGE PRO Windowed 4444 Explosion Sequence";

	memset (&wndclass, 0, sizeof(WNDCLASSEX));
	wndclass.cbSize = sizeof(WNDCLASSEX);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon (hInstance, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH);
    wndclass.lpszMenuName = pszClassName;
    wndclass.lpszClassName = pszClassName;
	wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

    RegisterClassEx (&wndclass);

	dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;

   	rc.top = rc.left = 0;
	rc.bottom = OUR_HEIGHT;
	rc.right = OUR_WIDTH;

  	//AdjustWindowRect tells us what our adjusted window
	//coords should be so that our client area is OUR_HEIGHT 
	//by OUR_WIDTH -- but it doesn't actually set up the window.
	//We must use the new rect coords in CreateWindowEx.
	AdjustWindowRectEx (&rc, dwStyle, FALSE, WS_EX_APPWINDOW);

	ghWnd = CreateWindowEx (WS_EX_APPWINDOW,
							pszClassName,
   							pszWindowCaption,
							dwStyle,
							50,
							50,
							rc.right - rc.left,
							rc.bottom - rc.top,
							NULL,
							NULL,
							hInstance,
							NULL);

    if (!ghWnd) return FALSE;

    if (!InitApp ()) return FALSE;

	ShowWindow (ghWnd, nCmdShow);
    UpdateWindow (ghWnd);
	
    while (TRUE)
    {
        if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
				break;
             
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        } 
		else
		{
			RenderScene ();
		}
    } 

    return msg.wParam;
} 


//Main window message handling procedure. Since most of 
//the work is done in DrawFrame and RenderScene, we only
//handle window exiting and destruction here. If the user
//tries to change focus we exit. 
LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_KEYDOWN:
            switch (wParam)
            {
                case VK_ESCAPE:
                    DestroyWindow (hWnd);
                    return 0;
            } 
            return 0;

		case WM_KILLFOCUS:	 	  
			PostQuitMessage (0);
			return 0;
			
		case WM_DESTROY:
            CloseApp ();
            PostQuitMessage (0);
            return 0;
    } 

    return DefWindowProc (hWnd, message, wParam, lParam);
} 


//Function wrapper for handling all setup and initialization
//of the application's global, object and texture data. 
static BOOL InitApp (void)
{
    //Create and initialize the main DirectDraw object.
	if (InitDirectDraw ())
	{
		//Initialize ATI3DCIF and set up a rendering context.
		if (InitATI3DCIF ())
		{
			//Initialize global rendering state.
			if(SetATI3DCIFInitialState ())
			{
				//Load all our texture data.
				if (LoadTargaTextures ())
				{
					//Initialize the primitive list.
					for (int i = 0 ; i < 6; i++)
						gvlstPrim1List[i] = &(gvtcfPrim1TriList[i]);

					return TRUE;

					//Reminder for future expansion...
					//UnloadTargaTextures ();
				}
				else
					ErrBox ("Could not load all textures"); 
			}
			else
				ErrBox ("Could not set ATI3DCIF initial state"); 
			CloseATI3DCIF ();
		}
		else
			ErrBox ("ATI3DCIF not initialized");
		CloseDirectDraw ();
	}
	else
		ErrBox ("DirectDraw not initialized"); 
	return FALSE;
}


//Unload Targa textures and the ATI3DCIF driver interface and  
//release the DirectDraw objects.
static void CloseApp (void)
{
	UnloadTargaTextures ();
	CloseATI3DCIF ();
	CloseDirectDraw ();
} 


//Set up global rendering state. Once a state is set it will remain
//active for the life of the application unless it is explicitly 
//modified by a call to ATI3DCIF_ContextSetState. Generallly states that
//need to be modified are not set here, but are set/reset in the frame
//display loop (e.g. DrawFrame). We will set our initial state up for 
//alpha blended texture mapping where we use the source alpha from our 
//texture map to performa alpha blending.  
static BOOL SetATI3DCIFInitialState (void)
{
	BOOL bTMap = TRUE;							//enable texture mapping
	C3D_ETEXOP etexop = C3D_ETEXOP_ALPHA;		//set texture operation
	C3D_EASRC easrc = C3D_EASRC_SRCALPHA;		//set source alpha to a
	C3D_EADST eadst = C3D_EADST_INVSRCALPHA;	//set dest alpha to (1-a)

	if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_EN, &bTMap) == C3D_EC_OK)
	{
		//Select the type of texture operation, in this case use the alpha
		//channel of the texture as the alpha source, and use (1-alpha source)
		//for the destination.
		if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_TEXOP, &etexop) == C3D_EC_OK)
		{
			if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_ALPHA_SRC, &easrc) == C3D_EC_OK)
			{
				if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_ALPHA_DST, &eadst) == C3D_EC_OK)
					return TRUE;
				else
				   	ErrBox ("Couldn't set alpha destination");
			}
			else
			   	ErrBox ("Couldn't set alpha source");
		}
		else
		   	ErrBox ("Couldn't set texture operation");
	}
	else
        ErrBox ("Couldn't enable texture mapping.");
	return FALSE;
}


//Initialize DirectDraw for a windowed application and set up surfaces:
// Set up a DirectDraw object (DirectDrawCreate).
// Make sure we are currently in a 16-bit mode.
// Let Windows know that we will be sharing windows resources (such as the
//   GDI) for our application (SetCooperativeLevel using DDSCL_NORMAL flag).
// Create the primary and back surfaces in video memory.
//	 In order to do this we fill out a surface description and pass it to
//   CreateSurface. We do this once for the front and once for the back.
//	 The primary surface will automatically be allocated to the size of the 
//	 screen, so it is not necessary to set up the .dwWidth and .dwHeight 
//	 fields in the surface description (DDSURFACEDESC). 
// Since we are running in a windowed mode, we must set up a clipper
//	 for our window. In order to do this we create the clipper object
//	 (CreateClipper), associate the clipper to our window (SetHWnd), 
//	 and attach the clipper to the primary surface (SetClipper).
static BOOL InitDirectDraw (void)
{
	if (DirectDrawCreate (NULL, &glpDD, NULL) == DD_OK)
	{
		if (IsDisplay16Bit ())
		{
			if (glpDD->SetCooperativeLevel (ghWnd,DDSCL_NORMAL) == DD_OK)
			{
				//Create the primary surface in video memory.
				DDSURFACEDESC ddsd;
				memset (&ddsd, 0, sizeof (ddsd));
				ddsd.dwSize = sizeof (ddsd);
				ddsd.dwFlags = DDSD_CAPS;
				ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY;

				if (glpDD->CreateSurface (&ddsd, &glpDDSPrimary, NULL) == DD_OK)
				{
					//Create a plain offscreen surface in video memory 
					//for the back buffer.
					memset (&ddsd, 0, sizeof (ddsd));
					ddsd.dwSize = sizeof (ddsd);
					ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
					ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
					ddsd.dwHeight = OUR_HEIGHT;
					ddsd.dwWidth = OUR_WIDTH;

					if (glpDD->CreateSurface (&ddsd, &glpDDSBack, NULL) == DD_OK)
					{
						if (glpDD->CreateClipper (0, &glpDDClip, NULL) == DD_OK)
						{
							if (glpDDClip->SetHWnd (0, ghWnd) == DD_OK)
							{
								if (glpDDSPrimary->SetClipper (glpDDClip) == DD_OK)
									return TRUE;
								else
									ErrBox ("Could not set clipper to front buffer");
							}
							else
								ErrBox ("Could not set clipper to window");
							RELEASE (glpDDClip);
						}
						else
							ErrBox ("Could not create clipper");
						RELEASE (glpDDSBack);
					}
					else
						ErrBox ("Could not create back buffer");
					RELEASE (glpDDSPrimary);
				}
				else
					ErrBox ("Could not create the primary surface");
				glpDD->SetCooperativeLevel (NULL, DDSCL_NORMAL);
			}
			else
				ErrBox ("Could not set the cooperative level");
		}
		else
			ErrBox ("Please reset to 16 (1555) bit display mode");
		RELEASE (glpDD);
	}
	else
        ErrBox ("Could not create DirectDraw object");
    return FALSE;
}     


//Free DirectDraw surfaces and destroy main DirectDraw object.
static void CloseDirectDraw (void)
{
	RELEASE (glpDDClip);
	RELEASE (glpDDSBack);
	RELEASE (glpDDSPrimary);
	glpDD->SetCooperativeLevel (NULL, DDSCL_NORMAL);
	RELEASE (glpDD);
} 


//Load the ATI3DCIF driver interface module and create a rendering context. We 
//set a global flag here (gbCIFInit) indicating that the driver is loaded. This 
//flag will get checked before we attempt to unload the module in CloseATI3DCIF.
static BOOL InitATI3DCIF (void)
{
	gbCIFInit = FALSE;

	if (ATI3DCIF_Init () == C3D_EC_OK)					
    {
		if ( (ghRC = ATI3DCIF_ContextCreate ()) != NULL)
		{
			gbCIFInit = TRUE;		//set flag indicating driver is loaded
			
			return TRUE;

			//Reminder for future expansion...
			//ATI3DCIF_ContextDestroy (ghRC);
		}
		else
	        ErrBox ("Could not create 3D rendering context");
		ATI3DCIF_Term ();
	}
	else
        ErrBox ("Could not initialize ATI3DCIF driver interface");

	return FALSE;
}


//Destroy the rendering context and unload the ATI3DCIF driver interface.
static void CloseATI3DCIF (void)
{
    ASSERT (ghRC);
	ASSERT (gbCIFInit);

	ATI3DCIF_ContextDestroy (ghRC);
    ghRC = NULL;
    
    ATI3DCIF_Term ();
    gbCIFInit = FALSE;
} 
 

//Perform the blit. Remember that DirectDraw defines the width and height
//of the front (primary) buffer to be that of the screen.
void RenderScene (void)
{
	//Prepare the offscreen buffer to be displayed.
	DrawFrame ();

   	//Blt copies the back buffer to the primary buffer.
	//The primary buffer is in screen coords and is the 
	//size of the screen. We want the coords of the rectangle on the 
	//primary buffer that we are blitting to, in SCREEN coords.

	//Get rectangle that describes client area of main window.
	RECT rcFront;
	GetClientRect (ghWnd, &rcFront);

	POINT pt = {0,0};				//Get upper left point in client area
	ClientToScreen (ghWnd, &pt);	//in screen coords
	//Now set up a rectangle that will describe where on the front buffer
	//we will be blitting to. The rectangle's upper left hand corner will be 
	//the coord of the point we just got.
	OffsetRect (&rcFront, pt.x, pt.y); 	 

   	//Now blit the back buffer to the correct area on the front buffer,
	//where it will be displayed. If we were using a full screen mode we could 
	//use page-flipping. It is rumored that windowed page-flipping will be 
	//supported in the future.
	glpDDSPrimary->Blt (&rcFront, glpDDSBack, NULL, DDBLT_WAIT, NULL);
}


//Draw the animation frame.
// Fill background with gray. We do this by filling out a DDBLTFX structure
//	 and blitting the color to the back buffer.
// Obtain a valid surface pointer to the back (rendering) buffer and pass
//	 it to the ATI3DCIF driver interface module.
// Set drawing surface pitch. The pitch value is specified in PIXELS, not in 
//	 bytes per scanline.
// Select a texture map to be displayed. We need to tell the ATI3DCIF what
//	 texture map (bitmap) we want to render. The map is chosen from a
//	 sequence and then selected into the rendering context. A given bitmap 
//	 will be displayed for NFRAMES_PER_LEVEL frames. Increase 
//	 NFRAMES_PER_LEVEL if you wish to slow the sequence down, for example.
// Perform 3D rendering. The final three lines in DrawFrame are the heart
//	 of any ATI3DCIF application. Switch the hardware into it's 3D rendering 
//	 state (ATI3DCIF_RenderBegin), render the primitive list using the current 
//	 rendering context information (set up by all those calls to 
//	 ContextSetState) to the back buffer (ATI3DCIF_RenderPrimList), and 
//	 switch back (ATI3DCIF_RenderEnd).

//Other notes:
// Setting the surface pitch:
//	 The surface pitch is passed to the ATI3DCIF module in:
		
//		ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PITCH, 
//												(C3D_PRSDATA) &pitchInPixels);

//	 where pitchInPixels is specified in PIXELS and may be calculated by 
//	 dividing the surface pitch of the allocated surface (if you are using 
//	 DirectDraw, this is the .lPitch member of the DDSURFACEDESC structure) 
//	 by the color depth in bytes (e.g., for a 16 bit mode, the color depth is 
//	 2 bytes).

//	 If you are using a full screen mode then you do not have to explicitly 
//	 set the pitch of the surface you are rendering to. The front and back
//	 buffers are guaranteed to have the same dimensions, including pitch. 
//	 This is not true for applications using windowed modes, which almost
//	 always have different size front and back buffers. So for windowed mode
//	 applications, we must set the pitch value for the drawing surface (usually
//	 the back buffer) explicitly. 

// Setting the surface pointer:
//	 The surface pointer passed to the ATI3DCIF module in:	

//		ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PTR, 
//											(C3D_PRSDATA) &(ddsd.lpSurface));

//	 usually comes from a surface management layer such as DirectDraw, but 
//	 it doesn't have to, so you masochists out there who do your own surface 
//	 management can still use the ATI3DCIF driver interface.
static void DrawFrame (void)
{
	static int nFrames = 0;
	static int curTex = 0;
    DDSURFACEDESC ddsd;
	DDBLTFX ddbltfx;
	DWORD pitch;
    
    //Color fill the back surface.
    memset (&ddbltfx, 0, sizeof (ddbltfx));
    ddbltfx.dwSize = sizeof (ddbltfx);
    ddbltfx.dwFillColor = FILL_COLOR; 
    glpDDSBack->Blt (NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

    //Lock/Unlock the back surface to get a valid surface pointer.
    memset (&ddsd, 0, sizeof (ddsd));
    ddsd.dwSize = sizeof (ddsd);
    if (glpDDSBack->Lock (NULL, &ddsd, DDLOCK_WAIT, NULL) != DD_OK) return;
	
	glpDDSBack->Unlock (ddsd.lpSurface);

	//Set the pointer to the frame buffer address of the back surface.
	ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PTR, (C3D_PRSDATA) &(ddsd.lpSurface));

	//Set the pitch of the drawing surface. Note that pitch is in PIXELS.
	pitch = ddsd.lPitch / COLORDEPTH_IN_BYTES;
	ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PITCH, (C3D_PRSDATA) &pitch);

	//Update our bitmap, if necessary.
	nFrames++;
	if (nFrames > NFRAMES_PER_LEVEL)	//time to go to next bitmap yet?
	{
		curTex++;
		if (curTex > (NTEXS_IN_SEQUENCE-1)) curTex = 0;

		//Select the appropriate texture into the drawing context. 
		ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_SELECT, &gTex[curTex].hTX);
		
		nFrames = 0;		//reset number of frames to display this bitmap
	}

	//Switch to 3D mode.
	if (ATI3DCIF_RenderBegin (ghRC) != C3D_EC_OK) return;

	//Draw the list.
	ATI3DCIF_RenderPrimList ((C3D_VSTRIP *) gvlstPrim1List, 6);

	//Switch back to 2D mode.
	ATI3DCIF_RenderEnd ();
} 


//Wrapper function for LoadTexture call for each file of interest.
static BOOL LoadTargaTextures (void)
{
 	int nTexsLoaded = 0;
	int i;

	for (i=0; i<NTEXS_IN_SEQUENCE; i++)
	{
		if (!LoadTexture (gTGAFile[i], &gTex[i])) 
		{
			ErrBox ("Texture %d in sequence from %s not loaded", i, gTGAFile[i]);
	        break;
		}
		nTexsLoaded++;
	}
	
	if (nTexsLoaded == NTEXS_IN_SEQUENCE)
		return TRUE;
	else
		ErrBox ("Couldn't load all textures.");

	//If we had a problem, unload the currently loaded textures...
	for (i=nTexsLoaded-1; i>=0; i--)
		UnloadTexture (&gTex[i]);
	
	return FALSE;
} 


//Free previously loaded Targa textures.
static void UnloadTargaTextures (void)
{
	for (int i=NTEXS_IN_SEQUENCE-1; i>=0; i--)
	{
		ASSERT (gTex[i].lpDDSTex && gTex[i].hTX);
		UnloadTexture (&gTex[i]);
	}
}


//Opens and reads a file into system memory and calls CopyTextureData
//which creates and loads a surface in video memory with the texture
//data. If CopyTextureData is successful, a C3D_TMAP struct is filled
//out with the relevant texture information and then registered with
//the ATI3DCIF driver interface. The process is as follows:
//	Read bitmap file data into system memory
//	Create a DirectDraw surface in video memory
//	Copy the bitmap data from system memory to video memory
//	Fill out a C3D_TMAP struct with the required texture information
//	Register the texture using the C3D_TMAP struct with the ATI3DCIF  
static BOOL LoadTexture (const char* pszTGAFile, Texture *pTex)
{
    C3D_UINT32          log2X = 0L;
    C3D_UINT32          log2Y = 0L;					  
    C3D_TMAP            TMap;
    FILE				*fh;
	TGAHeaderInfo		TGAHeader;
	DWORD				imageSize;
	unsigned char       *ptmap;
	DWORD               bytesRead;
 
	ASSERT (pTex);

    //Open texture map file for binary data reading.
    if ((fh = fopen (pszTGAFile, "rb")) != NULL)
	{
    	//Read TARGA header.
	    if ((bytesRead = fread (&TGAHeader, sizeof (unsigned char), sizeof (TGAHeader), fh)) ==
																			sizeof (TGAHeader))
		{		  
			//Skip descriptive bytes at end of header, idlen specifies the number.
			if (fseek (fh, TGAHeader.idlen, SEEK_CUR) == 0)
			{
				//Make sure texture dimensions are powers of 2 and are not greater than
				//the max supported by the hardware (cu32MAX_TMAP_LEV).
				log2X = IsValidTexDimension (TGAHeader.imwidth);
				log2Y = IsValidTexDimension (TGAHeader.imheight);
				if ((log2X <= cu32MAX_TMAP_LEV) && (log2Y <= cu32MAX_TMAP_LEV))
				{
					//Allocate memory for bitmap data.
					imageSize = TGAHeader.imheight * TGAHeader.imwidth * IMAGEDEPTH_IN_BYTES;
					ptmap = (unsigned char *) malloc (imageSize * sizeof (unsigned char));
					if (ptmap) 
					{
					    //Read bitmap data into system memory (ptmap).
						if ((bytesRead = fread (ptmap, sizeof (unsigned char), imageSize, fh)) ==
																						imageSize)
						{
							//Set up a texture surface in video memory and copy the texture
							//data (ptmap) to it.
							if (CopyTextureData (&TGAHeader, ptmap, pTex) == TRUE)
							{
								//Fill out a C3D_TMAP struct.
								memset (&TMap, 0, sizeof (TMap));
								TMap.u32Size = sizeof (TMap);
	
								TMap.apvLevels[0] = pTex->ddsd.lpSurface;
								TMap.bMipMap = FALSE;
								TMap.u32MaxMapXSizeLg2 = log2X;
								TMap.u32MaxMapYSizeLg2 = log2Y;
								TMap.eTexFormat = C3D_ETF_RGB4444;

								//Register the texture using the C3D_TMAP struct. Once that is 
								//done we can free the system memory image and close the file.
								if (ATI3DCIF_TextureReg (&TMap, &(pTex->hTX)) == C3D_EC_OK)
								{
									free (ptmap);
									fclose (fh);

									return TRUE;

									//Reminder for future expansion...
									//ATI3DCIF_TextureUnreg(pTex->hTX);
								}
								else
									ErrBox ("Error registering texture %s", pszTGAFile);
							}
							else
								ErrBox ("Error copying texture data to video memory surface");
						}
						else
							ErrBox ("Error reading bitmap data");
						free (ptmap);
					} 
					else
						ErrBox ("Could not allocate memory for texture map data");
				}
				else
					ErrBox ("Texture %s dimension greater than 1024 or not a power of 2", 
																					pszTGAFile);
			}
			else
		        ErrBox ("Error setting file pointer");
		}
		else
	        ErrBox ("Error reading file header");
		fclose (fh);
	}
	else
        ErrBox ("Could not open texture map %s", pszTGAFile);
    return FALSE;
} 


//Unregister a texture and release it's surface.
static void UnloadTexture (Texture *pTex)
{
	ASSERT (pTex);
    ASSERT (pTex->hTX);
    
    ATI3DCIF_TextureUnreg (pTex->hTX);
	RELEASE (pTex->lpDDSTex);
}


//Create an offscreen surface in video memory and copy the texture data 
//to it. The surface color depth will default to that of the current video 
//mode, in this case 1555. Note that the format of the surface as created
//by DirectDraw is not used by the ATI3DCIF. The ATI3DCIF uses the raw data 
//off the surface and interprets it according to how the texture format is 
//specified in the .eTexFormat member of the C3D_TMAP struct. Therefore,
//it is not strictly necessary to explicitly set up the color format of 
//the DirectDraw surface used for the texture.
static BOOL CopyTextureData (TGAHeaderInfo *TGAh, unsigned char *pdata, Texture *pTex)
{
    HRESULT             ddrval;

	memset (&(pTex->ddsd), 0, sizeof (pTex->ddsd));
	pTex->ddsd.dwSize = sizeof (pTex->ddsd);
	pTex->ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
	pTex->ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
	pTex->ddsd.dwWidth =  TGAh->imwidth;
	pTex->ddsd.dwHeight =  TGAh->imheight;

	ddrval = glpDD->CreateSurface (&(pTex->ddsd), &pTex->lpDDSTex, NULL);
	if (ddrval == DD_OK)
	{
	    //Get a valid pointer to the texture surface.
		memset (&(pTex->ddsd), 0, sizeof (pTex->ddsd));
		pTex->ddsd.dwSize = sizeof (pTex->ddsd);

		if (pTex->lpDDSTex->Lock (NULL, &(pTex->ddsd),	DDLOCK_WAIT, NULL) == DD_OK)
		{
			pTex->lpDDSTex->Unlock (pTex->ddsd.lpSurface);
			
			//Copy 8888 image to 1555 DirectDraw texture surface in video 
			//memory, converting as we go (note that Targa data is stored
			//as BGRA and we are converting to ARGB). For applications that 
			//support multiple texture formats, this code fragment can be 
			//broken out into a separate function, and then copied and 
			//modified for each supported texture format. 

			//Note that although DirectDraw creates the surface at the 
			//current video mode (1555), the ATI3DCIF driver interface
			//will interpret the data from the surface as 4444 (as set
			//(up in the .eTexFormat of the C3D_TMAP), but if you wish
			//you can explicitly tell DirectDraw to create a 4444 texture
			//surface by filling out a DDPIXELFORMAT structure and using 
			//the DDSD_PIXELFORMAT flag when you call CreateSurface.
			unsigned char a,r,g,b;
			unsigned char *oldpdata;
			unsigned short color4444;
			unsigned short *texSurfBase;
			int x, y;
			int skip;		//difference in image width vs. surface width

			oldpdata = pdata;
			texSurfBase = (unsigned short *)pTex->ddsd.lpSurface;
			skip = pTex->ddsd.lPitch - TGAh->imwidth*COLORDEPTH_IN_BYTES;

			for (y=0; y<TGAh->imheight; y++)
			{
				for (x=0; x<TGAh->imwidth; x++)
				{
					b = *pdata++;
					g = *pdata++;
					r = *pdata++;
					a = *pdata++;

					color4444 = (unsigned short)( ((a & 0xF0) << 8) | 
												  ((r & 0xF0) << 4) |
												   (g & 0xF0)		|
												  ((b & 0xF0) >> 4)  );
					*texSurfBase++ = color4444;
				}
				texSurfBase += skip;	//go to next row of texture surface
			}

			pdata = oldpdata;		//reset pdata's original address

			return TRUE;
		}
		else
			ErrBox ("Surface Lock failed");
		RELEASE (pTex->lpDDSTex);
	}
	else
	{
		if (ddrval == DDERR_OUTOFVIDEOMEMORY)
			ErrBox ("Out of video memory for texture");
		else
			ErrBox ("Could not create texture surface in video memory");
	}

	return FALSE;
}


//Make sure the current mode is 16-bit. Windowed applications dont have 
//the option of calling SetDisplayMode, so we must make sure we are in
//a 16 bit display mode.
static BOOL IsDisplay16Bit (void)
{
    DDSURFACEDESC ddsd;
	memset (&ddsd, 0, sizeof (ddsd));
	ddsd.dwSize = sizeof (ddsd);
	glpDD->GetDisplayMode (&ddsd);

	if (ddsd.ddpfPixelFormat.dwRGBBitCount == 16) 
		return TRUE;

	return FALSE;
}


//IsValidTexDimension ensures that the value passed in is a power of 2 and 
//is less than or equal to 1024. ATI3DCIF can only handle bitmaps that are 
//powers of 2 in each dimension (such as 128x64) and where each dimension 
//is less than 1024 (the maximum currently supported by hardware).
static C3D_UINT32 IsValidTexDimension (WORD num)
{
    C3D_UINT32 log2val = 0;
	WORD dimension = 1;

	while (log2val <= cu32MAX_TMAP_LEV)
    {
		if (dimension == num) break;
		dimension <<= 1;	
        log2val++;
    } 

	return log2val;
}

static void ErrBox(const char *errStr, ... )
{	
	va_list vl;

	va_start (vl, errStr);
	wvsprintf (gszErrMsg, errStr, vl);				
	MessageBox (ghWnd, gszErrMsg, NULL, MB_OK);	
	va_end (vl);
}

static void _Assert (const char *file, int line, const char *msg)
{
	int result;	
	static char buf[1024];

	sprintf (buf, "Assertion Failed %s at %d:  %s", file, line, msg);

	result = MessageBox (NULL, buf, "Assertion Failure", MB_OKCANCEL | 
												MB_APPLMODAL | MB_ICONERROR);

	if (result == IDCANCEL)
		PostQuitMessage (0);
}



