/******************************************************************************
 * $Id: gfx_texture2_opengl.hh,v 1.9 2006/11/19 05:49:40 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	2D texture class (OpenGL).
 * @remarks
 *//*
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
/// @class gfx::Texture2
/// @brief OpenGL 2D texture.
/// @verbatim
///
/// Texture2 class that abstracts 2D gfxsys textures.
///
/// - Client is the source of the texture image.
///   Clients pass texture image to Texture2::SetTextureImage() which
///   calls glTexImage()or glSubTexImage() appropriately.
/// - Abstracts texel types such as BGRA8/RGBA8.
/// - Interface for setting texture modes (filters, blend functions, etc).
/// - Provides Texture2::Begin()/End() methods (counterparts to glBegin()/glEnd()).
/// - Can loads texture images from graphics files (Targa).
/// - Texturing can be enabled/disabled (affects rendering only).
///   Useful when Object's LOD changes.
/// - Tracks texture memory consumption (to aid optimizing).
///
/// Example of procedural textures:
/// -------------------------------
///
/// // Creates a Texture2 object.  It has no image yet.
/// Texture2* tex = new Texture2( GFX_TEXEL_BGRA8, width, height );
///
/// // Generate texture image.
/// // Client allocates and fills the texture image.
/// // Texel32 is a typedef that abstracts BGRA8 or RGBA8 format.
/// const uint texelCnt = tex->GetTexelCnt();
/// Texel32* texels = new Texel32[texelCnt];
/// for( uint i = 0; i < texelCnt; i += TEXEL32_LEN )
/// {
///     // Whether Texel32 is in RGBA8 or BGRA8 order
///     // is abstracted by these #defines.
///     ASSERT( i < texelCnt );
///     Texel32 texel = (b << Texture::TEXEL32_BB_BIT_SHIFT)
///                   | (g << Texture::TEXEL32_GG_BIT_SHIFT)
///                   | (r << Texture::TEXEL32_RR_BIT_SHIFT)
///                   | (a << Texture::TEXEL32_AA_BIT_SHIFT);
///     texels[i] = texel;
/// }
///
/// // Tell Texture2 to load the texture image into OpenGL.
/// // glTexImage() or glSubTexImage() will be called.
/// // (Although Texture2 class knows the image size,
/// // it checks that the caller correctly knows it).
/// tex->SetTextureImage( buf, bufLen );
///
/// @endverbatim
////////////////////////////////////////////////////////////////////////////////

#ifndef GFX_TEXTURE2_OPENGL_HH
#define GFX_TEXTURE2_OPENGL_HH 1

namespace gfx {

////////////////////////////////////////////////////////////////////////////////
// Texture2 class.
//
class Texture2 : public Texture
{
    PREVENT_COPYING(Texture2)
public:
	// Create texture with empty texture data.
	Texture2( eTexelType texelType, uint width, uint height );
	Texture2( const Targa& targa, bool mipmap = false );
	~Texture2();

	// Texture rendering can be disabled.
	// Useful to disable texturing when an Object's LOD decreases.
	void	Enable( bool enable = true ) { mBits.mDisabled = !enable; }
	void	Disable( void )				 { mBits.mDisabled = true; }

	// Texture size.
	uint	GetWidth( void ) const	{ return mDim[WIDTH]; }
	uint	GetHeight( void ) const { return mDim[HEIGHT]; }
	uint	DimCnt( void ) { return 2; }
	uint	GetTexelCnt( void ) const;  // no data member for this
	uint	GetByteCnt( void ) const;
#if STATS
	static uint GetTotalMemoryUsed( void ) { return msTotalMemoryUsed; }  // to track texture memory usage
#endif
	// Texture image.  GetTextureImage() omitted.
    void    SetTextureImage( const Texel32* texels, const uint texelCnt, const bool mipmap = false );
    void	SetTextureImage( const uint8* texels, const uint byteCnt, const bool mipmap = false );  // raw byte array

	// Texture mode.
	void	SetMinFilter( eMinFilter filter );
	void	SetMagFilter( eMagFilter filter );
	void	SetWrap( eWrap wrap, uint dim );
	void	SetWrap( eWrap wrap );
	void	SetFunc( eFunc func );

public:
	// Note: These methods are inefficient.
	// They aren't suited to rendering a batch of textured primitives
	// since they may enable/disable texturing every time
	// (hence the reason for the PolygonList class which batches textured primitives).
	//
	// Methods for rendering of texture-mapped primitive, eg:
	//
	//     GFX_BEGIN( GP_QUADS, texture );				// calls Texture2::Begin()
	//         texture->Map( x, y ); GFX_VERTEXV( v );	// map 2D texel to 3D vertex
    //     GFX_END( texture );							// calls Texture2::End()
	//
	void	Begin( void ) const
	{
		// Disabled?  Individual textures can be disabled when object's LOD decreases.
#if DEBUG == 2
        ASSERT( msBeginEndNest++ == 0 );  // cannot be nested
		if ( ! msDisabled )
#else
		if ( EX( ! mBits.mDisabled ) )
#endif
		{
			ASSERT( IfValid() );  // check if texture image was loaded
			PushEnableTexturing();  // enable OpenGL texturing for this primitive
            // glTexEnv*() effects don't stick to any bound texture object (unlike glTexParameter()).
        	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mTexFunc );
		}
	}
	void	End( void ) const
	{
#if DEBUG == 2
        ASSERT( --msBeginEndNest == 0 );  // cannot be nested
		if ( ! msDisabled )
#else
		if ( EX( ! mBits.mDisabled ) )
#endif
		{
			PopTexturing();  // disable texturing in case non-textured primitives follow
		}
	}
    // GFX_MAP_TEXTURE_2D() is faster than Map().
    // Callers that know their texture is always enabled can call GFX_MAP_TEXTURE_2D().
	void	Map( fp x, fp y ) const			 { if ( EX(!mBits.mDisabled) ) glTexCoord2f( x, y ); }
	void	Map( const TexCoord2& tc ) const { Map( tc.x, tc.y ); }

#if DEBUG == 2
    // Globally enable textures.
    static void GlobalEnable( bool enable ) { msDisabled = ! enable; }
#endif

private:
	// Directly enable/disable OpenGL texturing.
	void PushEnableTexturing( void ) const
	{
		DEBUG2_CODE( ASSERT( msPushPopNest++ == 0 ); )

		// Switch/bind to this texture.
		msSavedTexturing = GFX_IF_ENABLED( GP_TEXTURE_2D );
		GFX_ENABLE( GP_TEXTURE_2D );
		glBindTexture( GP_TEXTURE_2D, mId );
	}
	void PopTexturing( void ) const
	{
		DEBUG2_CODE( ASSERT( --msPushPopNest == 0 ); )

		GFX_ENABLE( GP_TEXTURE_2D, msSavedTexturing );
	}

private:
	void	Init( eTexelType texelType, uint width, uint height );
	bool	IfValid( void ) const { return mId != TEXTURE_ID_INVALID; }
    uint    TexelCntToByteCnt( uint texelCnt ) const;
#if STATS
	uint	EstimateByteCnt( bool mipmap ) const;
#endif
	
public:
    enum    { MAX_TEXTURE_WIDTH_BITS = 12 };
    enum    { MAX_TEXTURE_WIDTH = 1 << (MAX_TEXTURE_WIDTH_BITS-1) };
	enum	{ MAX_TEXTURE_BYTES = MAX_TEXTURE_WIDTH * MAX_TEXTURE_WIDTH * 4 };  // limit texture memory consumption

private:
	// Minimal footprint.
	struct Bits
	{
		uint	mDisabled	: 1;	// affects rendering only
        uint    mTexelLen   : 3;    // bytes in texel
		uint	mLoaded		: 1;	// if texture was loaded
		uint	mMipmap	    : 1;    // set SetTextureImage()
	};
	Bits            mBits;
	uint16          mDim[2];
    eTexelType      mTexelType;			// GL_GBRA8, GL_RGBA8, etc
	GLuint          mId;				// OpenGL texture object ID

    // Parameters to glTexParameter*() stick to bound texture object (let OpenGL store them).
    // But parameters to glTexEnv*() don't, so store them in class members,
    // and call glTexEnv() every time this texture is active.
    eFunc           mTexFunc;
#if STATS
	static int msTotalMemoryUsed;		// total memory used by all textures
#endif
	static bool msSavedTexturing;		// for pushing/popping enablement of texturing
#if DEBUG == 2
    static int msDisabled;              // override
	static int msBeginEndNest;			// catch mismatched Begin/End()
	static int msPushPopNest;			// catch mismatched PushEnable/Pop()
#endif

friend bool operator<( Texture2& a, Texture2& b );
};

#if DEBUG
typedef       SafePtr<Texture2> Texture2Ptr;		// means ptr-to-2D-texture (not texture-to-ptr)
typedef const SafePtr<Texture2> Texture2PtrConst;
#else
typedef Texture2*       Texture2Ptr;
typedef Texture2* const Texture2PtrConst;
#endif

/*****************************************************************************
 * Mark code block for drawing of graphics primitives.
 * Note: Only a restricted set of OpenGL functions are available inside glBegin()...glEnd().
 *****************************************************************************/
// Texture2Ptr forms.
static inline void
GFX_BEGIN( int gp, Texture2PtrConst texture )
{
	texture->Begin();  // before glBegin()
	GFX_BEGIN( gp );
}
static inline void
GFX_END( Texture2PtrConst texture )
{
	GFX_END();
	texture->End();  // after glEnd()
}
// Texture2& forms.
static inline void
GFX_BEGIN( int gp, Texture2& texture )
{
    GFX_BEGIN( gp, &texture );
}
static inline void
GFX_END( Texture2& texture )
{
    GFX_END( &texture );
}

/*****************************************************************************
 * Map texture coordinates.
 * These are faster than Texture::Map() but don't check if texture is disabled.
 *****************************************************************************/
static inline void
GFX_MAP_TEXTURE_2D( fp x, fp y )
{
	glTexCoord2f( x, y );
}
static inline void
GFX_MAP_TEXTURE_2D( const TexCoord2& coord )
{
	glTexCoord2f( coord.x, coord.y );
}

/*****************************************************************************
 * For debugging, disable texturing.
 *****************************************************************************/
void
GFX_DEBUG_ENABLE_TEXTURE( bool enable = true );

} // namespace gfx

#endif // GFX_TEXTURE2_OPENGL_HH
