/******************************************************************************
 * $Id: gfx_texture2_opengl.cc,v 1.9 2006/11/19 05:45:45 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	2D texture class (OpenGL).
 * @remarks	OpenGL texture functions affect the current "bound" texture object
 *          specified by glBindTexture().
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

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

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  Texture2  //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#if DEBUG == 2
int Texture2::msDisabled;      // override
int Texture2::msBeginEndNest;  // catch mismatched Begin/End()
int Texture2::msPushPopNest;   // catch mismatched PushEnable()/Pop()
#endif
#if STATS
int Texture2::msTotalMemoryUsed;
#endif
bool Texture2::msSavedTexturing;

/*****************************************************************************
 * Construct a 2D texture object (which won't yet have a texture image).
 * @param   texelType
 *          Recommend using BGRA8 (gfx_opengl.hh explains why).
 * @param	width, height
 *			Both must be a power of 2 and equal.
 *****************************************************************************/
Texture2::Texture2( eTexelType texelType, uint width, uint height )
{
	Init( texelType, width, height );
}
void
Texture2::Init( eTexelType texelType, uint width, uint height )
{
ASSERT( height > 1 );  // 1D textures not supported

	// mBits.
	CLEAR_BIT_FIELD( &mBits );
    mBits.mTexelLen = TexelTypeToTexelLen( texelType );

    // Texture dimensions.
	mDim[WIDTH]		= width;
	mDim[HEIGHT]	= height;
	mTexelType		= texelType;

	// Assign an ID to this texture.
	// glGenTextures() is a misnomer: it just gives an ID to a texture.
	mId = TEXTURE_ID_INVALID;
	glGenTextures( 1, &mId );
	ASSERT( mId != TEXTURE_ID_INVALID );

    // Check texture dimensions.
	// Texture dimensions must be a power of 2 (though newer OpenGL allows non power-of-2).
	ASSERT( IfPow2(width) && IfPow2(height) );
	ASSERT( (width >= 1) && (height >=1) );
	ASSERT( width == height );

    mTexFunc        = eFunc_MODULATE;

	// No texture image loaded yet (no memory to track/estimate).
}
Texture2::~Texture2()
{
	// Free OpenGL texture resources.
	if ( ! gExitingGfx  &&  mId != TEXTURE_ID_INVALID )
	{
		glDeleteTextures( 1, &mId );
	}

#if STATS
	// Track texture memory consumption (don't count if not loaded).
	if ( mBits.mLoaded ) msTotalMemoryUsed -= EstimateByteCnt( mBits.mMipmap );
#endif
}

/*****************************************************************************
 * Construct Texture2 object from a Targa graphics file.
 * @param	targa
 * @param	mipmap
 *			DO_MIPMAP, DONT_MIPMAP
 *****************************************************************************/
Texture2::Texture2( const Targa& targa, bool mipmap )
{
	ASSERT( targa.IfValid() );
	Init( eTexelType_RGBA8, targa.GetWidth(), targa.GetHeight() );
	SetTextureImage( targa.GetConstBuf(), targa.GetBufLen(), mipmap );
}

/*****************************************************************************
 * Load texture image into OpenGL.
 * @param	texels, texelCnt
 *          This class checks that caller used the right texel count.
 * @param	mipmap
 *			DO_MIPMAP, DONT_MIPMAP
 *****************************************************************************/
void
Texture2::SetTextureImage( const Texel32* texels, const uint texelCnt, bool mipmap )
{
ASSERT( texelCnt > 0 );
ASSERT( texelCnt == GetTexelCnt() );  // verify caller used exact texel count

	// Pass as raw byte array.
	const uint byteCnt = texelCnt * TEXEL32_LEN;
	SetTextureImage( reinterpret_cast<const uint8*>(texels), byteCnt, mipmap );
}

/*****************************************************************************
 * Load texture image into OpenGL (from raw byte array).
 * @param	texels, texelCnt
 *          This class checks that caller used the right count.
 * @param	mipmap
 *			DO_MIPMAP, DONT_MIPMAP
 *****************************************************************************/
void
Texture2::SetTextureImage( const uint8* texels, const uint byteCnt, bool mipmap )
{
ASSERT( byteCnt > 0 );
ASSERT( byteCnt <= MAX_TEXTURE_BYTES );
ASSERT( byteCnt == GetByteCnt() );  // verify caller used exact texel count
OPENGL_CLEAR_ERROR();
#if STATS
	// Track texture memory consumption.  Don't count reloading, only !loaded-to-loaded transition.
	if ( ! mBits.mLoaded ) msTotalMemoryUsed += EstimateByteCnt( mipmap );
#endif

PushEnableTexturing();  // to be effective, enable texturing and bind to texture object

	mBits.mMipmap = mipmap;

	glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

	// See man glTexImage2D.
	// format has a bit suffix: RGBA8, BGRA8, etc.
	// internalFormat is just RGBA or BGRA (not bit suffix).
	const GLenum internalFormat = GetTexelInternalFormat( mTexelType );
	const GLenum format = GetTexelFormat( mTexelType );
	const GLenum type = GL_UNSIGNED_BYTE;

	// Call appropriate OpenGL function.
	// If texture has been loaded before, use glSubTexImage()
	// which is faster than glTexImage().
	if ( ! mipmap )
	{
        if ( mBits.mLoaded )
        {
    	    glTexSubImage2D( GL_TEXTURE_2D, 0,              // target, level
                             0, 0,                          // xoffs, yoffs
                             mDim[WIDTH], mDim[HEIGHT],		// w, h,
                             format, type, texels );        // format, type
        }
        else
        {
            glTexImage2D( GL_TEXTURE_2D, 0, internalFormat, // target, level, internalFormat
                          mDim[WIDTH], mDim[HEIGHT], 0,		// w, h, border
    	                  format, type, texels );			// format, type
        }
	}
	else
	{
		DEBUG_CODE( GLint err = )
		gluBuild2DMipmaps( GL_TEXTURE_2D, internalFormat,	// target, internalFormat
                           mDim[WIDTH], mDim[HEIGHT],		// w, h
                           format, type, texels );			// format, type, texels
		ASSERT( err == 0 );
	}

	// Afterwards, remember texture was loaded so next time will be faster
	mBits.mLoaded = true;

PopTexturing();

OPENGL_ASSERT();
}

/*****************************************************************************
 * Texture modes.
 *
 * Some OpenGL texture functions affect all texture objects, some affect
 * only the bound one.  Thus, since glTexEnv() doesn't affect the
 * bound texture object, Texture2::Begin() must call it every time.
 *
 * http://www.opengl.org/resources/faq/technical/texture.htm
 * The following functions affect and store state in texture objects:
 * glTexImage*(), glTexSubImage*(), glCopyTexImage*(),
 * glCopyTexSubImage*(), glTexParameter*(), and glPrioritizeTextures().
 * Since the GLU routines for building mipmap pyramids ultimately call glTexImage*(),
 * they also affect texture object state.Noticeably absent from this list are:
 * glTexEnv*() and glTexGen*(); they do not store state in texture objects.
 *****************************************************************************/
void
Texture2::SetMinFilter( eMinFilter filter )
{
PushEnableTexturing();  // to be effective, enable texturing and bind to texture object
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter );
PopTexturing();
}
void
Texture2::SetMagFilter( eMagFilter filter )
{
PushEnableTexturing();
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter );
PopTexturing();
}
void
Texture2::SetWrap( eWrap wrap, uint dim )
{
PushEnableTexturing();
	switch ( dim )
	{
		case 0: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap ); break;
		case 1: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap ); break;
		default: ASSERT(0); break;
	}
PopTexturing();
}
void
Texture2::SetWrap( eWrap wrap )
{
PushEnableTexturing();
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap );
PopTexturing();
}
void
Texture2::SetFunc( eFunc texFunc )
{
PushEnableTexturing();
    // glTexEnv*() effects don't stick to the bound texture object.
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texFunc );
    mTexFunc = texFunc;  // remember which texture function for when Texture2::Begin() is called
PopTexturing();
}

/*****************************************************************************
 * @return Convert texel count to byte count.
 *****************************************************************************/
uint
Texture2::TexelCntToByteCnt( uint texelCnt ) const
{
ASSERT( texelCnt > 0 );
ASSERT( mBits.mTexelLen > 0 );
	uint byteCnt = texelCnt * mBits.mTexelLen;
ASSERT( byteCnt <= MAX_TEXTURE_BYTES );
	return byteCnt;
}

/*****************************************************************************
 * @return Amount of texels in texture image (whether loaded or not).
 * Ignoring mipmaps.
 *****************************************************************************/
uint
Texture2::GetTexelCnt( void ) const
{
	uint texelCnt = mDim[WIDTH] * mDim[HEIGHT];
	ASSERT( texelCnt > 0 );
	return texelCnt;
}

/*****************************************************************************
 * @return Amount of bytes a texture image has or will have (whether loaded or not).
 * Excluding mipmaps (if mipmapped, only returns the size of the largest mipmap).
 *****************************************************************************/
uint
Texture2::GetByteCnt( void ) const
{
	uint byteCnt = TexelCntToByteCnt( GetTexelCnt() );
	ASSERT( byteCnt > 0  &&  byteCnt <= MAX_TEXTURE_BYTES );
	return byteCnt;
}

/*****************************************************************************
 * WARNING: Only for profiling/statistics!  Not for buffer lengths!
 * Estimate the amount of memory a texture is ACTIVELY consuming, including mipmaps.
 *****************************************************************************/
#if STATS
uint
Texture2::EstimateByteCnt( bool mipmap ) const
{
	if ( ! mipmap )  // for non-mipmaps: texelCnt * sizeof(texel)
	{
		return GetByteCnt();
	}
	else
	{
		// Total the mipmaps by dividing the texture width by 2
		// stopping at the smallest mipmap (1,1) which is 1 texel.
		uint byteCnt = 0;
		uint w = mDim[WIDTH];
		while ( w )
		{
			uint texelCnt = w * w;
			byteCnt += TexelCntToByteCnt( texelCnt );
			w >>= 1;
		}
		return byteCnt;
	}
}
#endif // STATS

/*****************************************************************************
 * For debugging, disable texturing.
 *****************************************************************************/
void
GFX_DEBUG_ENABLE_TEXTURE( bool enable )
{
#if DEBUG == 2
    Texture2::GlobalEnable( enable );
#elif DEBUG == 1
    // Texture2::msEnable is compiled if DEBUG=2.
    static int done;
    if ( !done++ ) *gStreamError << "WARNING: GFX_DEBUG_ENABLE_TEXTURE() works only if DEBUG=2" << endl;
#endif
}

} // namespace gfx
