/******************************************************************************
 * $Id: gfx_texture_opengl.cc,v 1.25 2006/08/01 23:14:02 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Texture classes (OpenGL).
 * @remarks
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

#define GFX_TEXTURE_OPENGL_CC 1
#include "gfx.hh"
#include "gfx_stream.hh"
#include "gfx_gfxsys.hh"
#include "gfx_texture.hh"
#include "gfx_tga.hh"
namespace gfx {

// cygwin (2006/06) doesn't have full support for 3D textures and mipmaps
// (which isn't an obstacle yet).
// Compile-time check is:
// #if defined(GL_EXT_texture3D) && defined(GLU_VERSION_1_3)

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  Texture  //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Create texture with empty texture data.
 * @param	width, height
 *			Both must be a power of 2 and equal.
 * @param	depth
 *			depth=1 for 2D texture, or depth=width=height for 3D texture.
 *****************************************************************************/
Texture::Texture( uint width, uint height, uint depth )
{
	Init( width, height, depth );
}
void
Texture::Init( uint width, uint height, uint depth )
{
	mValid			= false;
	mEnabled		= true;
	mChangedMode	= true;  // true to set default modes
	mChangedBuf		= false;
	mMutableImage	= false;  // MutableTexture ctor will set true

	// Texture dimensions must be a power of 2.
	ASSERT( IfPow2(width) && IfPow2(height) && IfPow2(depth) );
	ASSERT( width == height );
	ASSERT( depth >= 1 );
	mWidth		= width;
	mHeight		= height;
	mDepth		= depth;
#if defined(GL_EXT_texture3D) && defined(GLU_VERSION_1_3)
	mDimEnum	= (depth == 1) ? GL_TEXTURE_2D : GL_TEXTURE_3D;
#else
	mDimEnum	= GL_TEXTURE_2D; ASSERT( depth == 1 );
#endif

	// Allocate buffer as uninitialized memory for texture image.
	// Client is responsible for writing data to the buffer.
	mBufLen		= width * height * depth * 4; ASSERT( mBufLen < MAX_TEXTURE_SIZE );
	mBuf		= new uint8[mBufLen];  // host-side buffer to hold texture image
	mId			= TEXTURE_ID_INVALID;
}
Texture::~Texture()
{
	DELETE_ARR_NULL( mBuf );
	mValid = false;
}

/*****************************************************************************
 * Construct Texture object from Targa graphics file.
 *****************************************************************************/
Texture::Texture( Targa& targa )
{
	ASSERT( targa.IfValid() );

	Init( targa.GetWidth(), targa.GetHeight(), 1 );

	// Copy Targa buffer into texture buffer.
	ASSERT( targa.GetBufLen() == mBufLen );
	memcpy( mBuf, targa.GetBuf(), mBufLen );

	// This texture is ready.
	//CommitBuffer();  // ctor can't call virtual method
	mChangedBuf = true;
}

/*****************************************************************************
 * Beginning and ending of rendering a texture-mapped primitive(s).
 *****************************************************************************/
static bool sSavedTexturing;
DEBUG2_CODE( static int sTextureBlockNest; )
void
Texture::Begin( void )
{
	DEBUG2_CODE( ASSERT( sTextureBlockNest++ == 0 ); )  // cannot be nested

	// Disabled?
	if ( UX( ! mEnabled ) )
		return;

	// Enable texturing.
	sSavedTexturing = GFX_IF_ENABLED( mDimEnum );
	GFX_ENABLE( mDimEnum );

	// Assign an ID to this texture.
	// This can't be done by the constructor as the gfxsys wasn't ready then.
	if ( UX( mId == TEXTURE_ID_INVALID ) )
	{
		// glGenTextures() is a misnomer.  It just allocates a unique texture ID.
		glGenTextures( 1, &mId );
	}

	// Switch to this texture (bind).
	glBindTexture( mDimEnum, mId );

	// Was texture changed by the client?
	if ( UX( mChangedBuf || mChangedMode ) ) Update();

	// Client is responsible for writing texture image then notifying call,
	// otherwise, texture data is garbage.
	ASSERT( mValid );
}
void
Texture::End( void )
{
	DEBUG2_CODE( ASSERT( --sTextureBlockNest == 0 ); )  // cannot be nested

	// Disabled?
	if ( UX( ! mEnabled ) )
		return;

	// Restore texturing.
	GFX_RESTORE( mDimEnum, sSavedTexturing );
}

/*****************************************************************************
 * Top-level routine to update graphics object.
 *****************************************************************************/
void
Texture::Update( void )
{
	if ( mChangedMode ) UpdateMode();
	if ( mChangedBuf )  UpdateBuf();  // loads texture image which makes this valid
}

/*****************************************************************************
 * Update graphics object when client changes texture settings/modes/properties.
 *****************************************************************************/
void
Texture::UpdateMode( void )
{
	mChangedMode = false;

	// Set minification filter and magnification filter.
	if ( mMode.mMinFilter != GP_TEXTURE_MIN_FILTER_DEFAULT ) glTexParameteri( mDimEnum, GL_TEXTURE_MIN_FILTER, mMode.mMinFilter );
	if ( mMode.mMagFilter != GP_TEXTURE_MAG_FILTER_DEFAULT ) glTexParameteri( mDimEnum, GL_TEXTURE_MAG_FILTER, mMode.mMagFilter );

	// Set texture wrapping.
	if ( mMode.mWrap[0] != GP_TEXTURE_WRAP_DEFAULT ) glTexParameteri( mDimEnum, GL_TEXTURE_WRAP_S, mMode.mWrap[0] );
	if ( mMode.mWrap[1] != GP_TEXTURE_WRAP_DEFAULT ) glTexParameteri( mDimEnum, GL_TEXTURE_WRAP_T, mMode.mWrap[1] );
	if ( mMode.mWrap[2] != GP_TEXTURE_WRAP_DEFAULT ) glTexParameteri( mDimEnum, GL_TEXTURE_WRAP_R, mMode.mWrap[2] );

	// Set texture function.
	if ( mMode.mFunc != GP_TEXTURE_FUNC_DEFAULT )
	{
		glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mMode.mFunc );

		// If blend function, set blend color too.
		if ( mMode.mFunc == GP_TEXTURE_FUNC_BLEND )
			glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mMode.mBlendColor.Getfv() );
	}
}

/*****************************************************************************
 * Update graphics object when host buffer containing texture image is changed by client.
 *****************************************************************************/
void
Texture::UpdateBuf( void )
{
	mChangedBuf = false;
	mValid = true;  // texture now becomes valid as image data is loaded here

	// Push buffer containing texture image from host to gfxsys.
	if ( mBuf != NULL )
	{
		glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
		if ( EX(mDepth == 1) )
		{
			if ( ! mMode.mMipmap )
			{
			    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, mWidth, mHeight, 0,
			                  GL_RGBA, GL_UNSIGNED_BYTE, mBuf );
			}
			else
			{
			    gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, mWidth, mHeight,
  			                       GL_RGBA, GL_UNSIGNED_BYTE, mBuf );
			}
		}
#if defined(GL_EXT_texture3D) && defined(GLU_VERSION_1_3)
		else
		{
			if ( ! mMode.mMipmap )
			{
			    glTexImage3D( GL_TEXTURE_3D, 0, GL_RGBA, mWidth, mHeight, mDepth,
					          0, GL_RGBA, GL_UNSIGNED_BYTE, mBuf );
			}
			else
			{
			    gluBuild3DMipmaps( GL_TEXTURE_3D, GL_RGBA, mWidth, mHeight, mDepth,
  			                       GL_RGBA, GL_UNSIGNED_BYTE, mBuf );
			}
		}
#endif // GL_EXT_texture3D

		// Buffer containing texture image was pushed into gfxsys.
		// Unless image is mutable, free the buffer.
		if ( ! mMutableImage ) { DELETE_ARR_NULL( mBuf ); }
	}
}

/*****************************************************************************
 * Commit buffer.
 *****************************************************************************/
void
Texture::CommitBuffer( void )
{
	// If gfxsys is ready, immediately update graphics object, else delay update.
	mChangedBuf = true;
	if ( GFX_IF_READY() ) Update();
}

} // namespace gfx
