/****************************************************************************************/

/*  THandle.c                                                                           */

/*                                                                                      */

/*  Author: George McBay (gfm@my-deja.com)                                              */

/*	Jet3D Modifications: Christopher Plymire (chrisjp@eudoramail.com)					*/

/*																						*/

/*  Description: Texture handle manager for OpenGL driver                               */

/*                                                                                      */

/*  The contents of this file are subject to the Genesis3D Public License               */

/*  Version 1.01 (the "License"); you may not use this file except in                   */

/*  compliance with the License. You may obtain a copy of the License at                */

/*  http://www.genesis3d.com                                                            */

/*                                                                                      */

/*  Software distributed under the License is distributed on an "AS IS"                 */

/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See                */

/*  the License for the specific language governing rights and limitations              */

/*  under the License.                                                                  */

/*                                                                                      */

/*                                                                                      */

/****************************************************************************************/



#define TCACHE_DUMP_DEBUG_USAGE_INFO 1



#include <memory.h>

#include <math.h>

#include <stdio.h>

#include <alloc.h>



#define min(a,b) (((a)<(b))?(a):(b))

#define max(a,b) (((a)>(b))?(a):(b))



#define OutputDebugString printf



#include "JetDirect.h"

#include "Errorlog.h"

#include "THandle.h"

#include "OGLDrv.h"

#include "Render.h"



extern JetDirectWindow* pDirectGLWindow;



//extern PFNGLCOLORTABLEEXTPROC glColorTable;



jeRDriver_THandle	TextureHandles[MAX_TEXTURE_HANDLES];



#ifdef TCACHE_DUMP_DEBUG_USAGE_INFO

static char DebugDumpBuffer[1024];

#endif



// Init THandle system (do nothing, for now)

jeBoolean THandle_Startup(void)

{



	return JE_TRUE;

}





// Find an empty texture handle

jeRDriver_THandle *FindTextureHandle()

{

	int32				i;

	jeRDriver_THandle	*THandle;



	THandle = TextureHandles;



	for(i = 0; i < MAX_TEXTURE_HANDLES; i++, THandle++)

	{

		if(!THandle->Active)

		{

			memset(THandle, 0, sizeof(jeRDriver_THandle));



			THandle->Active = JE_TRUE;



			return THandle;

		}

	}



	return NULL;

}





// Cleanup a texture handle.  Remove texture from texture memory and free up related

// system memory.

jeBoolean DRIVERCC THandle_Destroy(jeRDriver_THandle *THandle)

{

	GLint i;



	pDirectGLWindow->MakeCurrent();



	if(!THandle->Active)

	{

		return	JE_FALSE;

	}



	if(THandle->PalHandle)

	{

		THandle_Destroy((THandle->PalHandle));

	}



	glDeleteTextures(1, &THandle->TextureID);



	for(i = 0; i < THANDLE_MAX_MIP_LEVELS; i++)

	{

		if(THandle->Data[i] != NULL)

		{

			free(THandle->Data[i]);

			THandle->Data[i] = NULL;

		}

	}



	THandle->Active = JE_FALSE;



	// Memsetting here is isn't neccesary as it is done in findtexturehandle.. just make sure it is unactive.

	// Just doing this because memset is appearing very high in the vtune call graph.

	// memset(THandle, 0, sizeof(jeRDriver_THandle));	



	pDirectGLWindow->ReleaseCurrent();



	return	JE_TRUE;

}





// Cleanup all currently in-use texture handles

jeBoolean FreeAllTextureHandles(void)

{

	int32				i;

	jeRDriver_THandle	*pTHandle;



	pTHandle = TextureHandles;





	for(i = 0; i < MAX_TEXTURE_HANDLES; i++, pTHandle++)

	{

		if(!pTHandle->Active)

		{

			continue;

		}

		

		THandle_Destroy(pTHandle);

	}



	glDeleteTextures(1, &decalTexObj);

	decalTexObj = 0;



	return JE_TRUE;

}





// Create a new texture handle...

jeRDriver_THandle *DRIVERCC THandle_Create(int32 Width, int32 Height, int32 NumMipLevels, 

										   const jeRDriver_PixelFormat *PixelFormat)

{

	int32				SWidth, SHeight;		

	jeRDriver_THandle	*THandle;	

	GLubyte				Log;



	pDirectGLWindow->MakeCurrent();

	



#ifdef TCACHE_DUMP_DEBUG_USAGE_INFO

	sprintf(DebugDumpBuffer,"Creating texture of size %i X %i with %i miplevels\n",Width,Height,NumMipLevels);

	OutputDebugString(DebugDumpBuffer);

#endif

	

	THandle = FindTextureHandle();



	if (!THandle)

	{

		SetLastDrvError(DRV_ERROR_GENERIC, "OGL_THandleCreate: No more handles left.");

		goto ExitWithError;

	}



	if(PixelFormat->Flags & RDRIVER_PF_3D)

	{

		char errMsg[64];



		if(Width > maxTextureSize)

		{

			sprintf(errMsg, "OGL_THandleCreate: Width > GL_MAX_TEXTURE_SIZE (%d)", maxTextureSize);

			SetLastDrvError(DRV_ERROR_GENERIC, errMsg);

			goto ExitWithError;

		}



		if (Height > maxTextureSize)

		{

			sprintf(errMsg, "OGL_THandleCreate: Height > GL_MAX_TEXTURE_SIZE (%d)", maxTextureSize);

			SetLastDrvError(DRV_ERROR_GENERIC, errMsg);

			goto ExitWithError;

		}

	

		SWidth = SnapToPower2(Width);

		SHeight = SnapToPower2(Height);



		if (Width != SWidth)

		{

			SetLastDrvError(DRV_ERROR_GENERIC, "OGL_THandleCreate: Not a power of 2.");

			goto ExitWithError;

		}



		if (Height != SHeight)

		{

			SetLastDrvError(DRV_ERROR_GENERIC, "OGL_THandleCreate: Not a power of 2.");

			goto ExitWithError;

		}

	}



	THandle->MipLevels			= NumMipLevels;

	THandle->Width				= Width;

	THandle->Height				= Height;

	THandle->PixelFormat		= *PixelFormat;

	THandle->Flags				= 0;

	

	Log							= (uint8)GetLog(Width, Height);

	

	if(THandle->PixelFormat.Flags & RDRIVER_PF_2D)

	{

		THandle->PaddedWidth = SnapToPower2(THandle->Width);

		THandle->PaddedHeight = SnapToPower2(THandle->Height);

	}

	else if(THandle->PixelFormat.Flags & RDRIVER_PF_3D)

	{

		THandle->InvScale = 1.0f / (GLfloat)((1<<Log));

	} 

	else if(THandle->PixelFormat.Flags & RDRIVER_PF_PALETTE)

	{



	}

	else

	{

		// Must be a lightmap

		THandle->Flags |= THANDLE_UPDATE_LM;

		THandle->InvScale = 1.0f / (GLfloat)((1<<Log)<<4);	

	}



	// Init an OpenGL texture object to hold this texture

	glGenTextures(1, &THandle->TextureID);



	pDirectGLWindow->ReleaseCurrent();



	return THandle;

		

	ExitWithError:

	{

		return NULL;

	}

}





jeBoolean DRIVERCC THandle_GetInfo(jeRDriver_THandle *THandle, int32 MipLevel, jeRDriver_THandleInfo *Info)

{



	if(!THandle->Active)

	{

		return JE_FALSE;

	}



	Info->Width			= THandle->Width >> MipLevel;

	Info->Height		= THandle->Height >> MipLevel;

	Info->Stride		= Info->Width;

	Info->Flags  		= 0;

	Info->PixelFormat	= THandle->PixelFormat;



	if(THandle->PixelFormat.Flags & RDRIVER_PF_CAN_DO_COLORKEY)

	{

		Info->Flags = RDRIVER_THANDLE_HAS_COLORKEY;

		Info->ColorKey = 1;

	}

	else

	{

		Info->Flags = 0;

		Info->ColorKey = 0;

	}



	return	JE_TRUE;

}





// Lock a texture for editing by the engine

jeBoolean DRIVERCC THandle_Lock(jeRDriver_THandle *THandle, int32 MipLevel, void **Data)

{



	// If we've already got data in system mem, return it to the engine as-is

	if(THandle->Data[MipLevel] != NULL)

	{

		THandle->Flags |= (THANDLE_LOCKED << MipLevel);

		*Data = THandle->Data[MipLevel] ;

		return JE_TRUE;

	}



	// Otherwise, grab a new block of memory for the engine to draw on...



if(THandle->PixelFormat.Flags & RDRIVER_PF_PALETTE)

{

		

			if( THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_32BIT_ARGB ||

				THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_32BIT_ABGR)	

			{

				THandle->Data[MipLevel]	=(GLubyte *)malloc(sizeof(U32) * THandle->Width);// * THandle->Height);

//				memset(THandle->Data[MipLevel],0,(sizeof(U32) * THandle->Width * THandle->Height));

			}

			else					

			{

					OutputDebugString("Invalid palette format!\n");

					return JE_FALSE;

			}

}

else

{



	if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_32BIT_ABGR)

	{

		GLint mipWidth, mipHeight;



		if(MipLevel == 0)

		{

			mipWidth = THandle->Width;

			mipHeight = THandle->Height;

		}

		else

		{

			mipWidth =  (THandle->Width / (int)pow(2.0, (double)MipLevel));

			mipHeight = (THandle->Height / (int)pow(2.0, (double)MipLevel));

		}



		THandle->Data[MipLevel] = (GLubyte *)malloc(mipWidth * mipHeight * 4);

	}

	else if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_24BIT_RGB)

	{

		THandle->Data[MipLevel] = (GLubyte *)malloc(THandle->Width * THandle->Height * 3);

	}

	else if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_16BIT_1555_ARGB)

	{

		THandle->Data[MipLevel] = (GLubyte *)malloc(THandle->Width * THandle->Height * 2);

	}

	else if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_8BIT)

	{

		THandle->Data[MipLevel]	=(GLubyte *)malloc(sizeof(uint8) * THandle->Width * THandle->Height);

	}



	else

	{

		*Data = NULL;

		return JE_FALSE;

	}

}



	THandle->Flags |= (THANDLE_LOCKED << MipLevel);

	*Data = THandle->Data[MipLevel];



	return JE_TRUE;

}





// Unlocks a texture locked for editing, and sets the texture to be uploaded next time

// it needs to be visible.

jeBoolean DRIVERCC THandle_UnLock(jeRDriver_THandle *THandle, int32 MipLevel)

{



	if(!(THandle->Flags & (THANDLE_LOCKED << MipLevel)))

	{

		return JE_FALSE;

	}



	if(THandle->Data == NULL)

	{

		return JE_FALSE;

	}



	THandle->Flags	&=~(THANDLE_LOCKED << MipLevel);	



	// Don't update on mips other than level 0.  We ignore the engine-created mips.

	// Somewhat inefficient (since mips will be generated twice), but good visual results.

	// Would be nice if you could tell the engine not to bother with mipping

	if(MipLevel == 0)

	{	

		THandle->Flags	|= THANDLE_UPDATE;					

	}

	

	return JE_TRUE;

}



// Do an actual card upload (well, at least tell the OpenGL driver you'd like one when it 

// gets a chance) of a texture.  Called from the Render_* functions when they require

// use of a texture that is marked for updating (THANDLE_UPDATE)

void THandle_Update(jeRDriver_THandle *THandle)

{	



	int baseLog2;

	unsigned char* ColorPal;



	if(THandle->PixelFormat.Flags & RDRIVER_PF_2D)

	{

		if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_24BIT_RGB)

		{

			if(THandle->Width <= maxTextureSize && THandle->Height <= maxTextureSize)

			{

				GLubyte *dest;



				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

				

				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

			

//				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 0.5f);



				dest = (GLubyte *)malloc(THandle->PaddedWidth * THandle->PaddedHeight * 4);



				CkBlit24_32(dest, THandle->PaddedWidth, THandle->PaddedHeight, THandle->Data[0], 

					THandle->Width, THandle->Height);



				glTexImage2D(GL_TEXTURE_2D, 0, 4, THandle->PaddedWidth, THandle->PaddedHeight, 

					0, GL_RGBA, GL_UNSIGNED_BYTE, dest); 



				free(dest);

			}

			else

			{

				if(THandle->Data[1] != NULL)

				{

					free(THandle->Data[1]);

				}



				THandle->Data[1] = (GLubyte *)malloc(THandle->Width * THandle->Height * 4);



				CkBlit24_32(THandle->Data[1], THandle->Width, THandle->Height, THandle->Data[0],

					THandle->Width, THandle->Height);

			}

		}

	}

	else if(THandle->PixelFormat.Flags & RDRIVER_PF_PALETTE)

	{



	OutputDebugString("Shouldn't get called...\n");







	}

	else

	{

#ifdef USE_LINEAR_INTERPOLATION 

 #ifdef TRILINEAR_INTERPOLATION

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 #else 

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 #endif

#else

 #ifdef TRILINEAR_INTERPOLATION

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

 #else

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

 #endif

#endif 



		if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_32BIT_ABGR)

		{

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

			

//			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 1.0f);

			

			gluBuild2DMipmaps(GL_TEXTURE_2D, 4, THandle->Width, THandle->Height, GL_RGBA,

				GL_UNSIGNED_BYTE, THandle->Data[0]);

		} // (Note) it safe to assume that if they want to create a 16 bit 4444 surface, EXT_packed_pixels is supported

#if 0

		else if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_16BIT_1555_ARGB)

		{

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

			

			    /* use nearest texture filters because there is no mipmapping */

				  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

				  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);



//			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 1.0f);



			 glTexImage2D(  GL_TEXTURE_2D,0,4,THandle->Width,THandle->Height,0,GL_ABGR_EXT,GL_UNSIGNED_SHORT_5_5_5_1_EXT,    THandle->Data[0]); 



//			glTexImage2d(gluBuild2DMipmaps(GL_TEXTURE_2D, 4, THandle->Width, THandle->Height, GL_RGBA_EXT,

//				GL_UNSIGNED_SHORT_5_5_5_1_EXT , THandle->Data[0]);

		}

		else if(THandle->PixelFormat.PixelFormat == JE_PIXELFORMAT_8BIT)

		{

			char* dataOut;

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

			

//			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 1.0f);

			// Re-load the palette



			/* load the color index texture with internal format GL_COLOR_INDEX8_EXT */



			// You can't use gluBuild2DMipmaps with index8 mode..

			baseLog2 = GetLog(THandle->Width,THandle->Height);



    /* use nearest texture filters because there is no mipmapping */

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);



			glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT,

					THandle->Width, THandle->Height, 0, GL_COLOR_INDEX,

					GL_UNSIGNED_BYTE, &THandle->Data[0]);



		

			THandle_Lock(THandle->PalHandle,0, &ColorPal);



			glColorTable(GL_TEXTURE_2D, GL_RGBA, THandle->PalHandle->Width, GL_RGBA, GL_UNSIGNED_BYTE, ColorPal);



			THandle_UnLock(THandle->PalHandle,0);



			if( GL_NO_ERROR != glGetError() )

			{

				OutputDebugString("GL error has occured\n");

			}



		}

#endif

		else

		{

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);



// This is a dumb ass thing to do.			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, 0.0f);



			gluBuild2DMipmaps(GL_TEXTURE_2D, 3, THandle->Width, THandle->Height, 

				GL_RGB, GL_UNSIGNED_BYTE, THandle->Data[0]);

		}

	}



	THandle->Flags &= ~THANDLE_UPDATE;

}







// Take engine supplied lightmap raw-RGB data and put it into a texture handle.

void THandle_DownloadLightmap(jeRDriver_THandle *THandle, 	jeRDriver_LMapCBInfo *LInfo)

{

	GLubyte *tempBits;



	THandle_Lock(THandle, 0, (void**)&tempBits);



	memcpy(tempBits, LInfo->RGBLight[0], THandle->Width * THandle->Height * 3);



	THandle_UnLock(THandle, 0);

}



// Reset the THandle system

jeBoolean DRIVERCC DrvResetAll(void)

{



	return	FreeAllTextureHandles();

}





S32 SnapToPower2(S32 Width)

{

	if(Width > 1 && Width <= 2) 

	{

		Width = 2;

	}

	else if(Width > 2 && Width <= 4)

	{

		Width = 4;

	}

	else if(Width > 4 && Width <= 8) 

	{

		Width = 8;

	}

	else if(Width > 8 && Width <= 16)

	{

		Width =16;

	}

	else if(Width > 16 && Width <= 32)

	{

		Width = 32;

	}

	else if(Width > 32 && Width <= 64) 

	{

		Width = 64;

	}

	else if(Width > 64 && Width <= 128) 

	{

		Width = 128;

	}

	else if(Width > 128 && Width <= 256) 

	{

		Width = 256;

	}

	else if(Width > 256 && Width <= 512) 

	{

		Width = 512;

	}

	else if(Width > 512 && Width <= 1024) 

	{

		Width = 1024;

	}

	else if(Width > 1024 && Width <= 2048) 

	{

		Width = 2048;

	}



	return Width;

}





uint32 Log2(uint32 P2)

{

	uint32		p = 0;

	int32		i = 0;

	

	for (i = P2; i > 0; i>>=1)

		p++;



	return (p-1);

}





int32 GetLog(int32 Width, int32 Height)

{

	int32	LWidth = SnapToPower2(max(Width, Height));

	

	return Log2(LWidth);

} 



//can be used to null out the pal (cant assert on palhandle)

jeBoolean DRIVERCC THandle_SetPalette(jeRDriver_THandle *THandle, jeRDriver_THandle *PalHandle)

{

	THandle->PalHandle	=PalHandle;



	return	JE_TRUE;

}



//can be used to null out the pal (cant assert on palhandle)

jeRDriver_THandle *DRIVERCC THandle_GetPalette(jeRDriver_THandle *THandle)

{

	if ( ! THandle->Active )

		{

			return JE_FALSE;

		}



	return	THandle->PalHandle;

}