text: //============ Copyright (c) Valve Corporation, All rights reserved. ============
  //
  // cglmtex.cpp
  //
  //===============================================================================
  
  #include <vprof.h>
  #include "togl/rendermechanism.h"
  
  #include "tier0/icommandline.h"
  #include "glmtexinlines.h"
  
  // memdbgon -must- be the last include file in a .cpp file.
  #include "tier0/memdbgon.h"
  
  
  #if defined(OSX)
  #include "appframework/ilaunchermgr.h"
  extern ILauncherMgr *g_pLauncherMgr;
  #endif
  
  
  //===============================================================================
  
  #if GLMDEBUG
  CGLMTex *g_pFirstCGMLTex;
  #endif
  #define TEXSPACE_LOGGING 0
  
  // encoding layout to an index where the bits read
  //	4	:	1 if compressed
  //	2	:	1 if not power of two
  //	1	:	1 if mipmapped
  
  bool pwroftwo (int val )
  {
  	return (val & (val-1)) == 0;
  }
  
  int	sEncodeLayoutAsIndex( GLMTexLayoutKey *key )
  {
  	int index = 0;
  	
  	if (key->m_texFlags & kGLMTexMipped)
  	{
  		index |= 1;
  	}
  
  	if ( ! ( pwroftwo(key->m_xSize) && pwroftwo(key->m_ySize) && pwroftwo(key->m_zSize) ) )
  	{
  		// if not all power of two
  		index |= 2;
  	}
  	
  	if (GetFormatDesc( key->m_texFormat )->m_chunkSize >1 )
  	{
  		index |= 4;
  	}
  
  	return index;
  }
  
  static unsigned long g_texGlobalBytes[8];
  
  //===============================================================================
  
  const GLMTexFormatDesc g_formatDescTable[] = 
  {
  	//  not yet handled by this table:
  	//	D3DFMT_INDEX16, D3DFMT_VERTEXDATA	// D3DFMT_INDEX32,
  		// WTF { D3DFMT_R5G6R5 ???,		GL_RGB,								GL_RGB,					GL_UNSIGNED_SHORT_5_6_5,		1, 2 },
  		// WTF { D3DFMT_A ???,				GL_ALPHA8,							GL_ALPHA,				GL_UNSIGNED_BYTE,				1, 1 },
  		// ??? D3DFMT_V8U8,
  		// ??? D3DFMT_Q8W8V8U8,
  		// ??? D3DFMT_X8L8V8U8,
  		// ??? D3DFMT_R32F,
  		// ??? D3DFMT_D24X4S4 unsure how to handle or if it is ever used..
  		// ??? D3DFMT_D15S1 ever used ?
  		// ??? D3DFMT_D24X8 ever used?
  
  	// summ-name		d3d-format				gl-int-format						gl-int-format-srgb					gl-data-format			gl-data-type					chunksize, bytes-per-sqchunk
  	{ "_D16",			D3DFMT_D16,				GL_DEPTH_COMPONENT16,				0,									GL_DEPTH_COMPONENT,		GL_UNSIGNED_SHORT,				1, 2 },
  	{ "_D24X8",			D3DFMT_D24X8,			GL_DEPTH_COMPONENT24,				0,									GL_DEPTH_COMPONENT,		GL_UNSIGNED_INT,				1, 4 },	// ??? unsure on this one
  	{ "_D24S8",			D3DFMT_D24S8,			GL_DEPTH24_STENCIL8_EXT,			0,									GL_DEPTH_STENCIL_EXT,	GL_UNSIGNED_INT_24_8_EXT,		1, 4 },
  
  	{ "_A8R8G8B8",		D3DFMT_A8R8G8B8,		GL_RGBA8,							GL_SRGB8_ALPHA8_EXT,				GL_BGRA,				GL_UNSIGNED_INT_8_8_8_8_REV,	1, 4 },
  	{ "_A4R4G4B4",		D3DFMT_A4R4G4B4,		GL_RGBA4,							0,									GL_BGRA,				GL_UNSIGNED_SHORT_4_4_4_4_REV,	1, 2 },
  	{ "_X8R8G8B8",		D3DFMT_X8R8G8B8,		GL_RGB8,							GL_SRGB8_EXT,						GL_BGRA,				GL_UNSIGNED_INT_8_8_8_8_REV,	1, 4 },
  
  	{ "_X1R5G5B5",		D3DFMT_X1R5G5B5,		GL_RGB5,							0,									GL_BGRA,				GL_UNSIGNED_SHORT_1_5_5_5_REV,	1, 2 },
  	{ "_A1R5G5B5",		D3DFMT_A1R5G5B5,		GL_RGB5_A1,							0,									GL_BGRA,				GL_UNSIGNED_SHORT_1_5_5_5_REV,	1, 2 },
  
  	{ "_L8",			D3DFMT_L8,				GL_LUMINANCE8,						GL_SLUMINANCE8_EXT,					GL_LUMINANCE,			GL_UNSIGNED_BYTE,				1, 1 },
  	{ "_A8L8",			D3DFMT_A8L8,			GL_LUMINANCE8_ALPHA8,				GL_SLUMINANCE8_ALPHA8_EXT,			GL_LUMINANCE_ALPHA,		GL_UNSIGNED_BYTE,				1, 2 },
  
  	{ "_DXT1",			D3DFMT_DXT1,			GL_COMPRESSED_RGB_S3TC_DXT1_EXT,	GL_COMPRESSED_SRGB_S3TC_DXT1_EXT,		GL_RGB,				GL_UNSIGNED_BYTE,				4, 8 },
  	{ "_DXT3",			D3DFMT_DXT3,			GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,	GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,	GL_RGBA,			GL_UNSIGNED_BYTE,				4, 16 },
  	{ "_DXT5",			D3DFMT_DXT5,			GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,	GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,	GL_RGBA,			GL_UNSIGNED_BYTE,				4, 16 },
  
  	{ "_A16B16G16R16F",	D3DFMT_A16B16G16R16F,	GL_RGBA16F_ARB,						0,									GL_RGBA,				GL_HALF_FLOAT_ARB,				1, 8 },
  	{ "_A16B16G16R16",	D3DFMT_A16B16G16R16,	GL_RGBA16,							0,									GL_RGBA,				GL_UNSIGNED_SHORT,				1, 8 },		// 16bpc integer tex
  
  	{ "_A32B32G32R32F",	D3DFMT_A32B32G32R32F,	GL_RGBA32F_ARB,						0,									GL_RGBA,				GL_FLOAT,						1, 16 },
  
  	{ "_R8G8B8",		D3DFMT_R8G8B8,			GL_RGB8,							GL_SRGB8_EXT,						GL_BGR,					GL_UNSIGNED_BYTE,				1, 3 },
  
  	{ "_A8",			D3DFMT_A8,				GL_ALPHA8,							0,									GL_ALPHA,				GL_UNSIGNED_BYTE,				1, 1 },
  	{ "_R5G6B5",		D3DFMT_R5G6B5,			GL_RGB,								GL_SRGB_EXT,						GL_RGB,					GL_UNSIGNED_SHORT_5_6_5,		1, 2 },
  
  	// fakey tex formats: the stated GL format and the memory layout may not agree (U8V8 for example)
  	
  	// _Q8W8V8U8 we just pass through as RGBA bytes.  Shader does scale/bias fix
  	{ "_Q8W8V8U8",		D3DFMT_Q8W8V8U8,		GL_RGBA8,							0,									GL_BGRA,				GL_UNSIGNED_INT_8_8_8_8_REV,	1, 4 },		// straight ripoff of D3DFMT_A8R8G8B8
  
  	// U8V8 is exposed to the client as 2-bytes per texel, but we download it as 3-byte RGB.
  	// WriteTexels needs to do that conversion from rg8 to rgb8 in order to be able to download it correctly
  	{ "_V8U8",			D3DFMT_V8U8,			GL_RGB8,							0,									GL_RG,					GL_BYTE,						1, 2 },
  	
  	{ "_R32F",			D3DFMT_R32F,			GL_R32F,							GL_R32F,							GL_RED,					GL_FLOAT,						1, 4 },
  	{ "_A2R10G10B10",	D3DFMT_A2R10G10B10,		GL_RGB10_A2,						GL_RGB10_A2,						GL_RGBA,				GL_UNSIGNED_INT_10_10_10_2,		1, 4 },
  	{ "_A2B10G10R10",	D3DFMT_A2B10G10R10,		GL_RGB10_A2,						GL_RGB10_A2,						GL_BGRA,				GL_UNSIGNED_INT_10_10_10_2,		1, 4 },
  
  	/*
  		// NV shadow depth tex
  		D3DFMT_NV_INTZ		= 0x5a544e49,	// MAKEFOURCC('I','N','T','Z')
  		D3DFMT_NV_RAWZ		= 0x5a574152,	// MAKEFOURCC('R','A','W','Z')
  
  		// NV null tex
  		D3DFMT_NV_NULL		= 0x4c4c554e,	// MAKEFOURCC('N','U','L','L')
  
  		// ATI shadow depth tex
  		D3DFMT_ATI_D16		= 0x36314644,	// MAKEFOURCC('D','F','1','6')
  		D3DFMT_ATI_D24S8	= 0x34324644,	// MAKEFOURCC('D','F','2','4')
  
  		// ATI 1N and 2N compressed tex
  		D3DFMT_ATI_2N		= 0x32495441,	// MAKEFOURCC('A', 'T', 'I', '2')
  		D3DFMT_ATI_1N		= 0x31495441,	// MAKEFOURCC('A', 'T', 'I', '1')
  	*/
  };
  
  int	g_formatDescTableCount = sizeof(g_formatDescTable) / sizeof( g_formatDescTable[0] );
  
  const GLMTexFormatDesc *GetFormatDesc( D3DFORMAT format )
  {
  	for( int i=0; i<g_formatDescTableCount; i++)
  	{
  		if (g_formatDescTable[i].m_d3dFormat == format)
  		{
  			return &g_formatDescTable[i];
  		}
  	}
  	return (const GLMTexFormatDesc *)NULL;	// not found
  }
  
  //===============================================================================
  
  
  void	InsertTexelComponentFixed( float value, int width, unsigned long *valuebuf )
  {
  	unsigned long	range = (1<<width);
  	unsigned long	scaled = (value * (float) range) * (range-1) / (range);
  	
  	if (scaled >= range)	DebuggerBreak();
  	
  	*valuebuf = (*valuebuf << width) | scaled;	
  }
  
  // return true if successful
  bool	GLMGenTexels( GLMGenTexelParams *params )
  {
  	unsigned char chunkbuf[256];	// can't think of any chunk this big..
  		
  	const GLMTexFormatDesc *format = GetFormatDesc( params->m_format );
  
  	if (!format)
  	{
  		return FALSE;	// fail
  	}
  	
  	// this section just generates one square chunk in the desired format
  	unsigned long *temp32 = (unsigned long*)chunkbuf;
  	unsigned int chunksize = 0;		// we can sanity check against the format table with this
  	
  	switch( params->m_format )
  	{
  		// comment shows byte order in RAM
  		// lowercase is bit arrangement in a byte
  		
  		case D3DFMT_A8R8G8B8:	// B G R A
  			InsertTexelComponentFixed( params->a, 8, temp32 );	// A is inserted first and winds up at most significant bits after insertions follow
  			InsertTexelComponentFixed( params->r, 8, temp32 );
  			InsertTexelComponentFixed( params->g, 8, temp32 );
  			InsertTexelComponentFixed( params->b, 8, temp32 );
  			chunksize = 4;
  		break;
  		
  		case D3DFMT_A4R4G4B4:	// [ggggbbbb] [aaaarrrr] RA	 (nibbles)
  			InsertTexelComponentFixed( params->a, 4, temp32 );
  			InsertTexelComponentFixed( params->r, 4, temp32 );
  			InsertTexelComponentFixed( params->g, 4, temp32 );
  			InsertTexelComponentFixed( params->b, 4, temp32 );
  			chunksize = 2;
  		break;
  		
  		case D3DFMT_X8R8G8B8:	// B G R X
  			InsertTexelComponentFixed( 0.0, 8, temp32 );
  			InsertTexelComponentFixed( params->r, 8, temp32 );
  			InsertTexelComponentFixed( params->g, 8, temp32 );
  			InsertTexelComponentFixed( params->b, 8, temp32 );
  			chunksize = 4;
  		break;
  		
  		case D3DFMT_X1R5G5B5:	// [gggbbbbb] [xrrrrrgg]
  			InsertTexelComponentFixed( 0.0, 1, temp32 );
  			InsertTexelComponentFixed( params->r, 5, temp32 );
  			InsertTexelComponentFixed( params->g, 5, temp32 );
  			InsertTexelComponentFixed( params->b, 5, temp32 );
  			chunksize = 2;
  		break;
  
  		case D3DFMT_A1R5G5B5:	// [gggbbbbb] [arrrrrgg]
  			InsertTexelComponentFixed( params->a, 1, temp32 );
  			InsertTexelComponentFixed( params->r, 5, temp32 );
  			InsertTexelComponentFixed( params->g, 5, temp32 );
  			InsertTexelComponentFixed( params->b, 5, temp32 );
  			chunksize = 2;
  		break;
  
  		case D3DFMT_L8:			// L							// caller, use R for L
  			InsertTexelComponentFixed( params->r, 8, temp32 );
  			chunksize = 1;
  		break;
  		
  		case D3DFMT_A8L8:		// L A							// caller, use R for L and A for A
  			InsertTexelComponentFixed( params->a, 8, temp32 );
  			InsertTexelComponentFixed( params->r, 8, temp32 );
  			chunksize = 2;
  		break;
  		
  		case D3DFMT_R8G8B8:		// B G R
  			InsertTexelComponentFixed( params->r, 8, temp32 );
  			InsertTexelComponentFixed( params->g, 8, temp32 );
  			InsertTexelComponentFixed( params->b, 8, temp32 );
  			chunksize = 3;
  		break;
  
  		case D3DFMT_A8:			// A
  			InsertTexelComponentFixed( params->a, 8, temp32 );
  			chunksize = 1;
  		break;
  		
  		case D3DFMT_R5G6B5:		// [gggbbbbb] [rrrrrggg]
  			InsertTexelComponentFixed( params->r, 5, temp32 );
  			InsertTexelComponentFixed( params->g, 6, temp32 );
  			InsertTexelComponentFixed( params->b, 5, temp32 );
  			chunksize = 2;
  		break;
  
  		case D3DFMT_DXT1:
  		{
  			memset( temp32, 0, 8 );		// zap 8 bytes
  			
  			// two 565 RGB words followed by 32 bits of 2-bit interp values for a 4x4 block
  			// we write the same color to both slots and all zeroes for the mask (one color total)
  			
  			unsigned long dxt1_color = 0;
  			
  			// generate one such word and clone it
  			InsertTexelComponentFixed( params->r, 5, &dxt1_color );
  			InsertTexelComponentFixed( params->g, 6, &dxt1_color );
  			InsertTexelComponentFixed( params->b, 5, &dxt1_color );
  			
  			// dupe
  			dxt1_color = dxt1_color | (dxt1_color<<16);
  			
  			// write into chunkbuf
  			*(unsigned long*)&chunkbuf[0] = dxt1_color;
  			
  			// color mask bits after that are already set to all zeroes.  chunk is done.
  			chunksize = 8;
  		}
  		break;
  		
  		case D3DFMT_DXT3:
  		{
  			memset( temp32, 0, 16 );		// zap 16 bytes
  			
  			// eight bytes of alpha (16 4-bit alpha nibbles)
  			// followed by a DXT1 block
  			
  			unsigned long dxt3_alpha = 0;
  			for( int i=0; i<8; i++)
  			{
  				// splat same alpha through block
  				InsertTexelComponentFixed( params->a, 4, &dxt3_alpha );
  			}
  
  			unsigned long dxt3_color = 0;
  			
  			// generate one such word and clone it
  			InsertTexelComponentFixed( params->r, 5, &dxt3_color );
  			InsertTexelComponentFixed( params->g, 6, &dxt3_color );
  			InsertTexelComponentFixed( params->b, 5, &dxt3_color );
  			
  			// dupe
  			dxt3_color = dxt3_color | (dxt3_color<<16);
  			
  			// write into chunkbuf
  			*(unsigned long*)&chunkbuf[0] = dxt3_alpha;
  			*(unsigned long*)&chunkbuf[4] = dxt3_alpha;
  			*(unsigned long*)&chunkbuf[8] = dxt3_color;
  			*(unsigned long*)&chunkbuf[12] = dxt3_color;
  
  			chunksize = 16;
  		}			
  		break;
  
  		case D3DFMT_DXT5:
  		{
  			memset( temp32, 0, 16 );		// zap 16 bytes
  			
  			// DXT5 has 8 bytes of compressed alpha, then 8 bytes of compressed RGB like DXT1.
  			
  			// the 8 alpha bytes are 2 bytes of endpoint alpha values, then 16x3 bits of interpolants.
  			// so to write a single alpha value, just figure out the value, store it in both the first two bytes then store zeroes.
  			
  			InsertTexelComponentFixed( params->a, 8, (unsigned long*)&chunkbuf[0] );
  			InsertTexelComponentFixed( params->a, 8, (unsigned long*)&chunkbuf[0] );
  			// rest of the alpha mask was already zeroed.
  			
  			// now do colors
  			unsigned long dxt5_color = 0;
  			
  			// generate one such word and clone it
  			InsertTexelComponentFixed( params->r, 5, &dxt5_color );
  			InsertTexelComponentFixed( params->g, 6, &dxt5_color );
  			InsertTexelComponentFixed( params->b, 5, &dxt5_color );
  			
  			// dupe
  			dxt5_color = dxt5_color | (dxt5_color<<16);
  			
  			// write into chunkbuf
  			*(unsigned long*)&chunkbuf[8] = dxt5_color;
  			*(unsigned long*)&chunkbuf[12] = dxt5_color;
  
  			chunksize = 16;
  		}
  		break;
  
  
  		case D3DFMT_A32B32G32R32F:		
  		{
  			*(float*)&chunkbuf[0] = params->r;
  			*(float*)&chunkbuf[4] = params->g;
  			*(float*)&chunkbuf[8] = params->b;
  			*(float*)&chunkbuf[12] = params->a;
  			
  			chunksize = 16;
  		}
  		break;
  
  		case D3DFMT_A16B16G16R16:
  			memset( chunkbuf, 0, 8 );
  			// R and G wind up in the first 32 bits
  			// B and A wind up in the second 32 bits
  			
  			InsertTexelComponentFixed( params->a, 16, (unsigned long*)&chunkbuf[4] );	// winds up as MSW of second word (note [4]) - thus last in RAM
  			InsertTexelComponentFixed( params->b, 16, (unsigned long*)&chunkbuf[4] );
  
  			InsertTexelComponentFixed( params->g, 16, (unsigned long*)&chunkbuf[0] );
  			InsertTexelComponentFixed( params->r, 16, (unsigned long*)&chunkbuf[0] );	// winds up as LSW of first word, thus first in RAM
  			
  			chunksize = 8;
  		break;
  		
  		// not done yet		
  		
  
  		//case D3DFMT_D16:				
  		//case D3DFMT_D24X8:			
  		//case D3DFMT_D24S8:			
  
  		//case D3DFMT_A16B16G16R16F:	
  		
  		default:
  			return FALSE;	// fail
  		break;
  	}
  	
  	// once the chunk buffer is filled..
  	
  	// sanity check the reported chunk size.
  	if (static_cast<int>(chunksize) != format->m_bytesPerSquareChunk)
  	{
  		DebuggerBreak();
  		return FALSE;
  	}
  	
  	// verify that the amount you want to write will not exceed the limit byte count
  	unsigned long destByteCount = chunksize * params->m_chunkCount;
  	
  	if (static_cast<int>(destByteCount) > params->m_byteCountLimit)
  	{
  		DebuggerBreak();
  		return FALSE;
  	}
  	
  	// write the bytes.
  	unsigned char *destP = (unsigned char*)params->m_dest;
  	for( int chunk=0; chunk < params->m_chunkCount; chunk++)
  	{
  		for( uint byteindex = 0; byteindex < chunksize; byteindex++)
  		{
  			*destP++ = chunkbuf[byteindex];
  		}
  	}
  	params->m_bytesWritten = destP - (unsigned char*)params->m_dest;
  	
  	return TRUE;
  }
  
  
  //===============================================================================
  bool LessFunc_GLMTexLayoutKey( const GLMTexLayoutKey &a, const GLMTexLayoutKey &b )
  {
  	#define	DO_LESS(fff) if (a.fff != b.fff) { return (a.fff< b.fff); }
  
  	DO_LESS(m_texGLTarget);
  	DO_LESS(m_texFormat);
  	DO_LESS(m_texFlags);
  	DO_LESS(m_texSamples);
  	DO_LESS(m_xSize);
  	DO_LESS(m_ySize)
  	DO_LESS(m_zSize);
  		
  	#undef DO_LESS
  	
  	return false;	// they are equal
  }
  
  CGLMTexLayoutTable::CGLMTexLayoutTable()
  {
  	m_layoutMap.SetLessFunc( LessFunc_GLMTexLayoutKey );
  }
  
  GLMTexLayout *CGLMTexLayoutTable::NewLayoutRef( GLMTexLayoutKey *pDesiredKey )
  {
  	GLMTexLayoutKey tempKey;
  	GLMTexLayoutKey *key = pDesiredKey;
  
  	// look up 'key' in the map and see if it's a hit, if so, bump the refcount and return
  	// if not, generate a completed layout based on the key, add to map, set refcount to 1, return that
  	
  	const GLMTexFormatDesc	*formatDesc = GetFormatDesc( key->m_texFormat );
  
  	//bool					compression = (formatDesc->m_chunkSize > 1) != 0;
  	if (!formatDesc)
  	{
  		GLMStop();	// bad news
  	}
  
  	if ( gGL->m_bHave_GL_EXT_texture_sRGB_decode )
  	{
  		if ( ( formatDesc->m_glIntFormatSRGB != 0 ) && ( ( key->m_texFlags & kGLMTexSRGB ) == 0 ) )
  		{
  			tempKey = *pDesiredKey;
  			key = &tempKey;
  
  			// Slam on SRGB texture flag, and we'll use GL_EXT_texture_sRGB_decode to selectively turn it off in the samplers
  			key->m_texFlags |= kGLMTexSRGB;
  		}
  	}
  
  	unsigned short index = m_layoutMap.Find( *key );
  	if (index != m_layoutMap.InvalidIndex())
  	{
  		// found it
  		//printf(" -hit- ");
  		GLMTexLayout *layout = m_layoutMap[ index ];
  		
  		// bump ref count
  		layout->m_refCount ++;
  		
  		return layout;
  	}
  	else
  	{
  		//printf(" -miss- ");
  		// need to make a new one
  		// to allocate it, we need to know how big to make it (slice count)
  		
  		// figure out how many mip levels are in play
  		int mipCount = 1;
  		if (key->m_texFlags & kGLMTexMipped)
  		{
  			int largestAxis = key->m_xSize;
  			
  			if (key->m_ySize > largestAxis)
  				largestAxis = key->m_ySize;
  				
  			if (key->m_zSize > largestAxis)
  				largestAxis = key->m_zSize;
  			
  			mipCount = 0;
  			while( largestAxis > 0 )
  			{
  				mipCount ++;
  				largestAxis >>= 1;
  			}
  		}
  
  		int faceCount = 1;
  		if (key->m_texGLTarget == GL_TEXTURE_CUBE_MAP)
  		{
  			faceCount = 6;
  		}
  		
  		int sliceCount = mipCount * faceCount;
  		
  		if (key->m_texFlags & kGLMTexMultisampled)
  		{
  			Assert( (key->m_texGLTarget == GL_TEXTURE_2D) );
  			Assert( sliceCount == 1 );
  			
  			// assume non mipped
  			Assert( (key->m_texFlags & kGLMTexMipped) == 0 );
  			Assert( (key->m_texFlags & kGLMTexMippedAuto) == 0 );
  			
  			// assume renderable and srgb
  			Assert( (key->m_texFlags & kGLMTexRenderable) !=0 );
  			//Assert( (key->m_texFlags & kGLMTexSRGB) !=0 );			//FIXME don't assert on making depthstencil surfaces which are non srgb
  			
  			// double check sample count (FIXME need real limit check here against device/driver)
  			Assert( (key->m_texSamples==2) || (key->m_texSamples==4) || (key->m_texSamples==6) || (key->m_texSamples==8) );
  		}
  		
  		// now we know enough to allocate and populate the new tex layout.
  		
  		// malloc the new layout
  		int layoutSize = sizeof( GLMTexLayout ) + (sliceCount * sizeof( GLMTexLayoutSlice ));
  		GLMTexLayout *layout = (GLMTexLayout *)malloc( layoutSize );
  		memset( layout, 0, layoutSize );
  		
  		// clone the key in there
  		memset( &layout->m_key, 0x00, sizeof(layout->m_key) );
  		layout->m_key = *key;
  
  		// set refcount
  		layout->m_refCount = 1;
  		
  		// save the format desc
  		layout->m_format = (GLMTexFormatDesc *)formatDesc;
  		
  		// we know the mipcount from before
  		layout->m_mipCount = mipCount;
  		
  		// we know the face count too
  		layout->m_faceCount = faceCount;
  		
  		// slice count is the product
  		layout->m_sliceCount = mipCount * faceCount;
  		
  		// we can now fill in the slices.
  		GLMTexLayoutSlice	*slicePtr = &layout->m_slices[0];
  		int					storageOffset = 0;
  		
  		//bool compressed = (formatDesc->m_chunkSize > 1);	// true if DXT
  		
  		for( int mip = 0; mip < mipCount; mip ++ )
  		{
  			for( int face = 0; face < faceCount; face++ )
  			{
  				// note application of chunk size which is 1 for uncompressed, and 4 for compressed tex (DXT)
  				// note also that the *dimensions* must scale down to 1
  				// but that the *storage* cannot go below 4x4.
  				// we introduce the "storage sizes" which are clamped, to compute the storage footprint.
  				
  				int storage_x,storage_y,storage_z;
  				
  				slicePtr->m_xSize = layout->m_key.m_xSize >> mip;
  				slicePtr->m_xSize = MAX( slicePtr->m_xSize, 1 );				// dimension can't go to zero
  				storage_x = MAX( slicePtr->m_xSize, formatDesc->m_chunkSize );	// storage extent can't go below chunk size
  				
  				slicePtr->m_ySize = layout->m_key.m_ySize >> mip;
  				slicePtr->m_ySize = MAX( slicePtr->m_ySize, 1 );				// dimension can't go to zero
  				storage_y = MAX( slicePtr->m_ySize, formatDesc->m_chunkSize );	// storage extent can't go below chunk size
  				
  				slicePtr->m_zSize = layout->m_key.m_zSize >> mip;
  				slicePtr->m_zSize = MAX( slicePtr->m_zSize, 1 );				// dimension can't go to zero
  				storage_z = MAX( slicePtr->m_zSize, 1);							// storage extent for Z cannot go below '1'.
  				
  				//if (compressed)  NO NO NO do not lie about the dimensionality, just fudge the storage.
  				//{
  				//	// round up to multiple of 4 in X and Y axes
  				//	slicePtr->m_xSize = (slicePtr->m_xSize+3) & (~3);
  				//	slicePtr->m_ySize = (slicePtr->m_ySize+3) & (~3);
  				//}
  				
  				int xchunks = (storage_x / formatDesc->m_chunkSize );
  				int ychunks = (storage_y / formatDesc->m_chunkSize );
  				
  				slicePtr->m_storageSize = (xchunks * ychunks * formatDesc->m_bytesPerSquareChunk) * storage_z;				
  				slicePtr->m_storageOffset = storageOffset;
  				
  				storageOffset += slicePtr->m_storageSize;
  				storageOffset = ( (storageOffset+0x0F) & (~0x0F));		// keep each MIP starting on a 16 byte boundary.
  				
  				slicePtr++;
  			}		
  		}
  		
  		layout->m_storageTotalSize = storageOffset;
  		//printf("\n size %08x for key (x=%d y=%d z=%d, fmt=%08x, bpsc=%d)", layout->m_storageTotalSize, key->m_xSize, key->m_ySize, key->m_zSize, key->m_texFormat, formatDesc->m_bytesPerSquareChunk );
  		
  		// generate summary
  		// "target, format, +/- mips, base size"
  		char scratch[1024];
  
  		const char	*targetname = "?";
  		switch( key->m_texGLTarget )
  		{
  			case GL_TEXTURE_2D:			targetname = "2D  ";		break;
  			case GL_TEXTURE_3D:			targetname = "3D  ";		break;
  			case GL_TEXTURE_CUBE_MAP:	targetname = "CUBE";		break;
  		}
  		
  		sprintf( scratch, "[%s %s %dx%dx%d mips=%d slices=%d flags=%02lX%s]",
  					targetname,
  					formatDesc->m_formatSummary,
  					layout->m_key.m_xSize, layout->m_key.m_ySize, layout->m_key.m_zSize,
  					mipCount,
  					sliceCount,
  					layout->m_key.m_texFlags,
  					(layout->m_key.m_texFlags & kGLMTexSRGB) ? " SRGB" : ""					
  				);
  		layout->m_layoutSummary = strdup( scratch );
  		//GLMPRINTF(("-D- new tex layout [ %s ]", scratch ));
  		
  		// then insert into map. disregard returned index.
  		m_layoutMap.Insert( layout->m_key, layout );
  		
  		return layout;
  	}
  }
  
  void CGLMTexLayoutTable::DelLayoutRef( GLMTexLayout *layout )
  {
  	// locate layout in hash, drop refcount
  	// (some GC step later on will harvest expired layouts - not like it's any big challenge to re-generate them)
  	
  	unsigned short index = m_layoutMap.Find( layout->m_key );
  	if (index != m_layoutMap.InvalidIndex())
  	{
  		// found it
  		GLMTexLayout *layout = m_layoutMap[ index ];
  		
  		// drop ref count
  		layout->m_refCount --;
  		
  		//assert( layout->m_refCount >= 0 );
  	}
  	else
  	{
  		// that's bad
  		GLMStop();
  	}
  }
  
  void CGLMTexLayoutTable::DumpStats( )
  {
  	for (uint i=0; i<m_layoutMap.Count(); i++ )
  	{
  		GLMTexLayout *layout = m_layoutMap[ i ];
  		
  		// print it out
  		printf("\n%05d instances %08d bytes  %08d totbytes  %s", layout->m_refCount, layout->m_storageTotalSize, (layout->m_refCount*layout->m_storageTotalSize), layout->m_layoutSummary );
  	}
  }
  
  
  ConVar gl_texmsaalog ( "gl_texmsaalog", "0");
  
  ConVar gl_rt_forcergba ( "gl_rt_forcergba", "1" );	// on teximage of a renderable tex, pass GL_RGBA in place of GL_BGRA
  
  ConVar gl_minimize_rt_tex ( "gl_minimize_rt_tex", "0" );	// if 1, set the GL_TEXTURE_MINIMIZE_STORAGE_APPLE texture parameter to cut off mipmaps for RT's
  ConVar gl_minimize_all_tex ( "gl_minimize_all_tex", "1" );	// if 1, set the GL_TEXTURE_MINIMIZE_STORAGE_APPLE texture parameter to cut off mipmaps for textures which are unmipped
  ConVar gl_minimize_tex_log ( "gl_minimize_tex_log", "0" );	// if 1, printf the names of the tex that got minimized
  
  CGLMTex::CGLMTex( GLMContext *ctx, GLMTexLayout *layout, uint levels, const char *debugLabel )
  {
  #if GLMDEBUG
  	m_pPrevTex = NULL;
  	m_pNextTex = g_pFirstCGMLTex;
  	if ( m_pNextTex )
  	{
  		Assert( m_pNextTex->m_pPrevTex == NULL );
  		m_pNextTex->m_pPrevTex = this;
  	}
  	g_pFirstCGMLTex = this;
  #endif
  	// caller has responsibility to make 'ctx' current, but we check to be sure.
  	ctx->CheckCurrent();
  						
  	m_nLastResolvedBatchCounter = ctx->m_nBatchCounter;
  		
  	// note layout requested
  	m_layout = layout;
  	m_texGLTarget = m_layout->m_key.m_texGLTarget;
  	
  	m_nSamplerType = SAMPLER_TYPE_UNUSED;
  	switch ( m_texGLTarget )
  	{
  		case GL_TEXTURE_CUBE_MAP:	m_nSamplerType = SAMPLER_TYPE_CUBE; break;
  		case GL_TEXTURE_2D:			m_nSamplerType = SAMPLER_TYPE_2D; break;
  		case GL_TEXTURE_3D:			m_nSamplerType = SAMPLER_TYPE_3D; break;
  		default:
  			Assert( 0 );
  			break;
  	}
  
  	m_maxActiveMip = -1;	//index of highest mip that has been written - increase as each mip arrives
  	m_minActiveMip = 999;	//index of lowest mip that has been written - lower it as each mip arrives
  	
  	// note context owner
  	m_ctx = ctx;
  	
  	// clear the bind point flags
  	//m_bindPoints.ClearAll();
  	
  	// clear the RT attach count
  	m_rtAttachCount = 0;
  	
  	// come up with a GL name for this texture.
  	// for MTGL friendliness, we should generate our own names at some point..
  	gGL->glGenTextures( 1, &m_texName );
  
  	m_pBlitSrcFBO = NULL;
  	m_pBlitDstFBO = NULL;
  
  	// Sense whether to try and apply client storage upon teximage/subimage.
  	//  This should only be true if we're running on OSX 10.6 or it was explicitly
  	//  enabled with -gl_texclientstorage on the command line.
  	m_texClientStorage = ctx->m_bTexClientStorage;
  	
  	// flag that we have not yet been explicitly kicked into VRAM..
  	m_texPreloaded = false;
  	
  	// clone the debug label if there is one.
  	m_debugLabel = debugLabel ? strdup(debugLabel) : NULL;
  
  	// if tex is MSAA renderable, make an RBO, else zero the RBO name and dirty bit
  	if (layout->m_key.m_texFlags & kGLMTexMultisampled)
  	{
  		gGL->glGenRenderbuffersEXT( 1, &m_rboName );
  				
  		// so we have enough info to go ahead and bind the RBO and put storage on it?
  		// try it.
  		gGL->glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_rboName );
  
  		// quietly clamp if sample count exceeds known limit for the device
  		int sampleCount = layout->m_key.m_texSamples;
  		
  		if (sampleCount > ctx->Caps().m_maxSamples)
  		{
  			sampleCount = ctx->Caps().m_maxSamples;	// clamp
  		}
  		
  		GLenum	msaaFormat = (layout->m_key.m_texFlags & kGLMTexSRGB) ? layout->m_format->m_glIntFormatSRGB : layout->m_format->m_glIntFormat;
  		gGL->glRenderbufferStorageMultisampleEXT(	GL_RENDERBUFFER_EXT,
  												sampleCount,	// not "layout->m_key.m_texSamples"
  												msaaFormat,
  												layout->m_key.m_xSize,
  												layout->m_key.m_ySize );	
  
  		if (gl_texmsaalog.GetInt())
  		{
  			printf( "\n == MSAA Tex %p %s : MSAA RBO is intformat %s (%x)", this, m_debugLabel?m_debugLabel:"", GLMDecode( eGL_ENUM, msaaFormat ), msaaFormat );
  		}
  
  		gGL->glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, 0 );
  	}
  	else
  	{
  		m_rboName = 0;
  	}
  		
  	// at this point we have the complete description of the texture, and a name for it, but no data and no actual GL object.
  	// we know this name has bever seen duty before, so we're going to hard-bind it to TMU 0, displacing any other tex that might have been bound there.
  	// any previously bound tex will be unbound and appropriately marked as a result.
  	// the active TMU will be set as a side effect.
  	CGLMTex *pPrevTex = ctx->m_samplers[0].m_pBoundTex;
  	ctx->BindTexToTMU( this, 0 );
  
  	m_SamplingParams.SetToDefaults();
  	m_SamplingParams.SetToTarget( m_texGLTarget );
  	
  	// OK, our texture now exists and is bound on the active TMU.  Not drawable yet though.
  		
  	// if not an RT, create backing storage and fill it
  	if ( !(layout->m_key.m_texFlags & kGLMTexRenderable) )
  	{
  		m_backing = (char *)malloc( m_layout->m_storageTotalSize );
  		memset( m_backing, 0, m_layout->m_storageTotalSize );
  		
  		// track bytes allocated for non-RT's
  		int formindex = sEncodeLayoutAsIndex( &layout->m_key );
  		
  		g_texGlobalBytes[ formindex ] += m_layout->m_storageTotalSize;
  		
  		#if TEXSPACE_LOGGING
  			printf( "\n Tex %s added %d bytes in form %d which is now %d bytes", m_debugLabel ? m_debugLabel : "-", m_layout->m_storageTotalSize, formindex, g_texGlobalBytes[ formindex ] );
  			printf( "\n\t\t[ %d %d %d %d  %d %d %d %d ]",
  				   g_texGlobalBytes[ 0 ],g_texGlobalBytes[ 1 ],g_texGlobalBytes[ 2 ],g_texGlobalBytes[ 3 ],
  				   g_texGlobalBytes[ 4 ],g_texGlobalBytes[ 5 ],g_texGlobalBytes[ 6 ],g_texGlobalBytes[ 7 ]
  				   );
  		#endif
  	}
  	else
  	{
  		m_backing = NULL;
  		
  		m_texClientStorage = false;
  	}		
  
  	// init lock count
  	// lock reqs are tracked by the owning context
  	m_lockCount = 0;
  
  	m_sliceFlags.SetCount( m_layout->m_sliceCount );
  	for( int i=0; i< m_layout->m_sliceCount; i++)
  	{
  		m_sliceFlags[i] = 0;
  			// kSliceValid			=	false	(we have not teximaged each slice yet)
  			// kSliceStorageValid	=	false	(the storage allocated does not reflect what is in the tex)
  			// kSliceLocked			=	false	(the slices are not locked)
  			// kSliceFullyDirty		=	false	(this does not come true til first lock)
  	}
  	
  	// texture minimize parameter keeps driver from allocing mips when it should not, by being explicit about the ones that have no mips.
  	
  	bool setMinimizeParameter = false;
  	bool minimize_rt = (gl_minimize_rt_tex.GetInt()!=0);
  	bool minimize_all = (gl_minimize_all_tex.GetInt()!=0);
  	
  	if (layout->m_key.m_texFlags & kGLMTexRenderable)
  	{
  		// it's an RT.  if mips were not explicitly requested, and "gl_minimize_rt_tex" is true, set the minimize parameter.
  		if (  (minimize_rt || minimize_all) && ( !(layout->m_key.m_texFlags & kGLMTexMipped) ) )
  		{
  			setMinimizeParameter = true;
  		}
  	}
  	else
  	{
  		// not an RT. if mips were not requested, and "gl_minimize_all_tex" is true, set the minimize parameter.
  		if ( minimize_all && ( !(layout->m_key.m_texFlags & kGLMTexMipped) ) )
  		{
  			setMinimizeParameter = true;
  		}
  	}
  
  	if (setMinimizeParameter)
  	{
  		if (gl_minimize_tex_log.GetInt())
  		{
  			printf("\n minimizing storage for tex '%s' [%s] ", m_debugLabel?m_debugLabel:"-", m_layout->m_layoutSummary );
  		}
  		
  		if (gGL->m_bHave_GL_APPLE_texture_range)
  			gGL->glTexParameteri( m_layout->m_key.m_texGLTarget, GL_TEXTURE_MINIMIZE_STORAGE_APPLE, 1 );
  	}
  
  	// after a lot of pain with texture completeness...
  	// always push black into all slices of all newly created textures.
  	
  	#if 0
  		bool pushRenderableSlices = (m_layout->m_key.m_texFlags & kGLMTexRenderable) != 0;
  		bool pushTexSlices = true;	// just do it everywhere  (m_layout->m_mipCount>1) && (m_layout->m_format->m_chunkSize !=1) ;
  		if (pushTexSlices)
  		{
  			// fill storage with mostly-opaque purple
  			
  			GLMGenTexelParams genp;
  			memset( &genp, 0, sizeof(genp) );
  			
  			genp.m_format = m_layout->m_format->m_d3dFormat;
  			const GLMTexFormatDesc *format = GetFormatDesc( genp.m_format );
  			
  			genp.m_dest				= m_backing;		// dest addr
  			genp.m_chunkCount		= m_layout->m_storageTotalSize / format->m_bytesPerSquareChunk; // fill the whole slab
  			genp.m_byteCountLimit	= m_layout->m_storageTotalSize;	// limit writes to this amount
  
  			genp.r = 1.0;
  			genp.g = 0.0;
  			genp.b = 1.0;
  			genp.a = 0.75;
  			
  			GLMGenTexels( &genp );
  		}
  	#endif
  	
  	//if (pushRenderableSlices || pushTexSlices)
      	if ( !( ( layout->m_key.m_texFlags & kGLMTexMipped ) && ( levels == m_layout->m_mipCount ) ) )
  	{
  		for( int face=0; face <m_layout->m_faceCount; face++)
  		{
  			for( int mip=0; mip <m_layout->m_mipCount; mip++)
  			{
  				// we're not really going to lock, we're just going to write the blank data from the backing store we just made
  				GLMTexLockDesc	desc;
  				
  				desc.m_req.m_tex = this;
  				desc.m_req.m_face = face;
  				desc.m_req.m_mip = mip;
  
  				desc.m_sliceIndex = CalcSliceIndex( face, mip );
  
  				GLMTexLayoutSlice *slice = &m_layout->m_slices[ desc.m_sliceIndex ];
  				
  				desc.m_req.m_region.xmin = desc.m_req.m_region.ymin = desc.m_req.m_region.zmin = 0;
  				desc.m_req.m_region.xmax = slice->m_xSize;
  				desc.m_req.m_region.ymax = slice->m_ySize;
  				desc.m_req.m_region.zmax = slice->m_zSize;
  
  				desc.m_sliceBaseOffset = slice->m_storageOffset;	// doesn't really matter... we're just pushing zeroes..
  				desc.m_sliceRegionOffset = 0;
  
  				WriteTexels( &desc, true, (layout->m_key.m_texFlags & kGLMTexRenderable)!=0 );					// write whole slice - but disable data source if it's an RT, as there's no backing
  			}
  		}
  	}
  	GLMPRINTF(("-A- -**TEXNEW '%-60s' name=%06d  size=%09d  storage=%08x label=%s ", m_layout->m_layoutSummary, m_texName, m_layout->m_storageTotalSize, m_backing, m_debugLabel ? m_debugLabel : "-" ));
  
  	ctx->BindTexToTMU( pPrevTex, 0 );
  }
  
  CGLMTex::~CGLMTex( )
  {
  #if GLMDEBUG
  	if ( m_pPrevTex )
  	{
  		Assert( m_pPrevTex->m_pNextTex == this );
  		m_pPrevTex->m_pNextTex = m_pNextTex;
  	}
  	else
  	{
  		Assert( g_pFirstCGMLTex == this );
  		g_pFirstCGMLTex = m_pNextTex;
  	}
  	if ( m_pNextTex )
  	{
  		Assert( m_pNextTex->m_pPrevTex == this );
  		m_pNextTex->m_pPrevTex = m_pPrevTex;
  	}
  	m_pNextTex = m_pPrevTex = NULL;
  #endif
  	if ( !(m_layout->m_key.m_texFlags & kGLMTexRenderable) )
  	{
  		int formindex = sEncodeLayoutAsIndex( &m_layout->m_key );
  
  		g_texGlobalBytes[ formindex ] -= m_layout->m_storageTotalSize;
  		
  		#if TEXSPACE_LOGGING
  			printf( "\n Tex %s freed %d bytes in form %d which is now %d bytes", m_debugLabel ? m_debugLabel : "-", m_layout->m_storageTotalSize, formindex, g_texGlobalBytes[ formindex ] );
  			printf( "\n\t\t[ %d %d %d %d  %d %d %d %d ]",
  				   g_texGlobalBytes[ 0 ],g_texGlobalBytes[ 1 ],g_texGlobalBytes[ 2 ],g_texGlobalBytes[ 3 ],
  				   g_texGlobalBytes[ 4 ],g_texGlobalBytes[ 5 ],g_texGlobalBytes[ 6 ],g_texGlobalBytes[ 7 ]
  				   );			   		
  		#endif
  	}
  	
  	GLMPRINTF(("-A- -**TEXDEL '%-60s' name=%06d  size=%09d  storage=%08x label=%s ", m_layout->m_layoutSummary, m_texName, m_layout->m_storageTotalSize, m_backing, m_debugLabel ? m_debugLabel : "-" ));
  	// check first to see if we were still bound anywhere or locked... these should be failures.
  	
  	if ( m_pBlitSrcFBO )
  	{
  		m_ctx->DelFBO( m_pBlitSrcFBO );
  		m_pBlitSrcFBO = NULL;
  	}
  
  	if ( m_pBlitDstFBO )
  	{
  		m_ctx->DelFBO( m_pBlitDstFBO );
  		m_pBlitDstFBO = NULL;
  	}
  
  	if ( m_rboName )
  	{
  		gGL->glDeleteRenderbuffersEXT( 1, &m_rboName );
  		m_rboName = 0;
  	}
  
  	// if all that is OK, then delete the underlying tex
  	if ( m_texName )
  	{
  		gGL->glDeleteTextures( 1, &m_texName );
  		m_texName = 0;
  	}
  		
  	// release our usage of the layout
  	m_ctx->m_texLayoutTable->DelLayoutRef( m_layout );
  	m_layout = NULL;
  	
  	if (m_backing)
  	{
  		free( m_backing );
  		m_backing = NULL;
  	}
  	
  	if (m_debugLabel)
  	{
  		free( m_debugLabel );
  		m_debugLabel = NULL;
  	}
  	
  	m_ctx = NULL;
  }
  
  int	CGLMTex::CalcSliceIndex( int face, int mip )
  {
  	// faces of the same mip level are adjacent. "face major" storage
  	int index = (mip * m_layout->m_faceCount) + face;
  
  	return index;
  }
  
  void CGLMTex::CalcTexelDataOffsetAndStrides( int sliceIndex, int x, int y, int z, int *offsetOut, int *yStrideOut, int *zStrideOut )
  {
      VPROF( "CGLMTex::CalcTexelDataOffsetAndStrikes" );
  	int offset = 0;
  	int yStride = 0;
  	int zStride = 0;
  	
  	GLMTexFormatDesc *format = m_layout->m_format;
  	if (format->m_chunkSize==1)
  	{
  		// figure out row stride and layer stride
  		yStride = format->m_bytesPerSquareChunk * m_layout->m_slices[sliceIndex].m_xSize;	// bytes per texel row (y stride)
  		zStride = yStride * m_layout->m_slices[sliceIndex].m_ySize;							// bytes per texel layer (if 3D tex)
  		
  		offset = x * format->m_bytesPerSquareChunk;		// lateral offset
  		offset += (y * yStride);							// scanline offset
  		offset += (z * zStride);							// should be zero for 2D tex
  	}
  	else
  	{
  		yStride = format->m_bytesPerSquareChunk * (m_layout->m_slices[sliceIndex].m_xSize / format->m_chunkSize);
  		zStride = yStride * (m_layout->m_slices[sliceIndex].m_ySize / format->m_chunkSize);
  		
  		// compressed format.  scale the x,y values into chunks. Z isn't chunked.
  		// assert if any of them are not multiples of a chunk.
  		int chunkx = x / format->m_chunkSize;
  		int chunky = y / format->m_chunkSize;
  		
  		if ( (chunkx * format->m_chunkSize) != x)
  		{
  			GLMStop();
  		}
  		
  		if ( (chunky * format->m_chunkSize) != y)
  		{
  			GLMStop();
  		}
  		
  		offset = chunkx * format->m_bytesPerSquareChunk;	// lateral offset
  		offset += (chunky * yStride);						// chunk row offset
  		offset += (z * zStride);						// should be zero for 2D tex		
  	}
  	
  	*offsetOut	= offset;
  	*yStrideOut	= yStride;
  	*zStrideOut	= zStride;
  }
  
  void CGLMTex::ReadTexels( GLMTexLockDesc *desc, bool readWholeSlice )
  {
  	GLMRegion	readBox;
  	
  	if (readWholeSlice)
  	{
  		readBox.xmin = readBox.ymin = readBox.zmin = 0;
  
  		readBox.xmax = m_layout->m_slices[ desc->m_sliceIndex ].m_xSize;
  		readBox.ymax = m_layout->m_slices[ desc->m_sliceIndex ].m_ySize;
  		readBox.zmax = m_layout->m_slices[ desc->m_sliceIndex ].m_zSize;
  	}
  	else
  	{
  		readBox = desc->m_req.m_region;
  	}
  
  	CGLMTex *pPrevTex = m_ctx->m_samplers[0].m_pBoundTex;
  	m_ctx->BindTexToTMU( this, 0 );		// SelectTMU(n) is a side effect
  
  	if (readWholeSlice)
  	{
  		// make this work first.... then write the partial path
  		// (Hmmmm, I don't think we will ever actually need a partial path - 
  		// since we have no notion of a partially valid slice of storage
  
  		GLMTexFormatDesc *format = m_layout->m_format;
  		GLenum target = m_layout->m_key.m_texGLTarget;
  		
  		void *sliceAddress = m_backing + m_layout->m_slices[ desc->m_sliceIndex ].m_storageOffset;	// this would change for PBO
  		//int sliceSize = m_layout->m_slices[ desc->m_sliceIndex ].m_storageSize;
  		
  		// interestingly enough, we can use the same path for both 2D and 3D fetch
  		
  		switch( target )
  		{
  			case GL_TEXTURE_CUBE_MAP:
  
  				// adjust target to steer to the proper face, then fall through to the 2D texture path.
  				target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + desc->m_req.m_face;
  				
  			case GL_TEXTURE_2D:
  			case GL_TEXTURE_3D:
  			{
  				// check compressed or not
  				if (format->m_chunkSize != 1)
  				{
  					// compressed path
  					// http://www.opengl.org/sdk/docs/man/xhtml/glGetCompressedTexImage.xml
  					VPROF( "glGetCompressedTexImage" );
  					gGL->glGetCompressedTexImage(	target,					// target
  												desc->m_req.m_mip,		// level
  												sliceAddress );			// destination
  				}
  				else
  				{
  					// uncompressed path
  					// http://www.opengl.org/sdk/docs/man/xhtml/glGetTexImage.xml
  					VPROF( "glGetTexImage" );
  					gGL->glGetTexImage(			target,						// target
  											desc->m_req.m_mip,			// level
  											format->m_glDataFormat,		// dataformat
  											format->m_glDataType,		// datatype
  											sliceAddress );				// destination
  				}
  			}
  			break;				
  		}
  	}
  	else
  	{
  		GLMStop();
  	}
  
  	m_ctx->BindTexToTMU( pPrevTex, 0 );
  }
  
  // TexSubImage should work properly on every driver stack and GPU--enabling by default.
  ConVar	gl_enabletexsubimage( "gl_enabletexsubimage", "1" );
  
  void CGLMTex::WriteTexels( GLMTexLockDesc *desc, bool writeWholeSlice, bool noDataWrite )
  {
  	//if ( m_nBindlessHashNumEntries )
  	//	return;
  	
  	GLMRegion	writeBox;
  
  	bool needsExpand = false;
  	char *expandTemp = NULL;
  	
  	switch( m_layout->m_format->m_d3dFormat)
  	{
  		case D3DFMT_V8U8:
  		{
  			needsExpand = true;
  			writeWholeSlice = true;
  			
  			// shoot down client storage if we have to generate a new flavor of the data
  			m_texClientStorage = false;
  		}
  		break;
  	}
  	
  	if (writeWholeSlice)
  	{
  		writeBox.xmin = writeBox.ymin = writeBox.zmin = 0;
  
  		writeBox.xmax = m_layout->m_slices[ desc->m_sliceIndex ].m_xSize;
  		writeBox.ymax = m_layout->m_slices[ desc->m_sliceIndex ].m_ySize;
  		writeBox.zmax = m_layout->m_slices[ desc->m_sliceIndex ].m_zSize;
  	}
  	else
  	{
  		writeBox = desc->m_req.m_region;
  	}
  
  	// first thing is to get the GL texture bound to a TMU, or just select one if already bound
  	// to get this running we will just always slam TMU 0 and let the draw time code fix it back
  	// a later optimization would be to hoist the bind call to the caller, do it exactly once
  	
  	CGLMTex *pPrevTex = m_ctx->m_samplers[0].m_pBoundTex;
  	m_ctx->BindTexToTMU( this, 0 );		// SelectTMU(n) is a side effect
  
  	GLMTexFormatDesc *format = m_layout->m_format;
  	
  	GLenum target		= m_layout->m_key.m_texGLTarget;
  	GLenum glDataFormat	= format->m_glDataFormat;				// this could change if expansion kicks in 
  	GLenum glDataType	= format->m_glDataType;
  	
  	GLMTexLayoutSlice *slice = &m_layout->m_slices[ desc->m_sliceIndex ];		
  	void *sliceAddress = m_backing ? (m_backing + slice->m_storageOffset) : NULL;	// this would change for PBO
  
  	// allow use of subimage if the target is texture2D and it has already been teximage'd
  	bool mayUseSubImage = false;
  	if ( (target==GL_TEXTURE_2D) && (m_sliceFlags[ desc->m_sliceIndex ] & kSliceValid) )
  	{
  		mayUseSubImage = gl_enabletexsubimage.GetInt() != 0;
  	}
  			
  	// check flavor, 2D, 3D, or cube map
  	// we also have the choice to use subimage if this is a tex already created. (open question as to benefit)
  	
  	
  	// SRGB select. At this level (writetexels) we firmly obey the m_texFlags.
  	// (mechanism not policy)
  	
  	GLenum intformat = (m_layout->m_key.m_texFlags & kGLMTexSRGB) ? format->m_glIntFormatSRGB : format->m_glIntFormat;
  	if (CommandLine()->FindParm("-disable_srgbtex"))
  	{
  		// force non srgb flavor - experiment to make ATI r600 happy on 10.5.8 (maybe x1600 too!)
  		intformat = format->m_glIntFormat;
  	}
  	
  	Assert( intformat != 0 );
  	
  	if (m_layout->m_key.m_texFlags & kGLMTexSRGB)
  	{
  		Assert( m_layout->m_format->m_glDataFormat != GL_DEPTH_COMPONENT );
  		Assert( m_layout->m_format->m_glDataFormat != GL_DEPTH_STENCIL_EXT );
  		Assert( m_layout->m_format->m_glDataFormat != GL_ALPHA );
  	}
  	
  	// adjust min and max mip written
  	if (desc->m_req.m_mip > m_maxActiveMip)
  	{
  		m_maxActiveMip = desc->m_req.m_mip;
  
  		//gGL->glTexParameteri( target, GL_TEXTURE_MAX_LEVEL, desc->m_req.m_mip);
  	}
  	
  	if (desc->m_req.m_mip < m_minActiveMip)
  	{
  		m_minActiveMip = desc->m_req.m_mip;
  		
  		//gGL->glTexParameteri( target, GL_TEXTURE_BASE_LEVEL, desc->m_req.m_mip);
  	}
  
  	if (needsExpand)
  	{
  		int expandSize = 0;
  		
  		switch( m_layout->m_format->m_d3dFormat)
  		{
  			case D3DFMT_V8U8:
  			{
  				// figure out new size based on 3byte RGB format
  				// easy, just take the two byte size and grow it by 50%
  				expandSize = (slice->m_storageSize * 3) / 2;
  				expandTemp = (char*)malloc( expandSize );
  				
  				char *src = (char*)sliceAddress;
  				char *dst = expandTemp;
  
  				// transfer RG's to RGB's
  				while(expandSize>0)
  				{
  					*dst = *src++;	// move first byte
  					*dst = *src++;	// move second byte
  					*reinterpret_cast<uint8*>(dst) = 0xBB;	// pad third byte
  					
  					expandSize -= 3;
  				}
  				
  				// move the slice pointer
  				sliceAddress = expandTemp;
  				
  				// change the data format we tell GL about
  				glDataFormat = GL_RGB;
  			}
  			break;
  			
  			default:	Assert(!"Don't know how to expand that format..");
  		}
  		
  	}
  
  	// set up the client storage now, one way or another
  	// If this extension isn't supported, we just end up with two copies of the texture, one in the GL and one in app memory.
  	//  So it's safe to just go on as if this extension existed and hold the possibly-unnecessary extra RAM.
  	if (gGL->m_bHave_GL_APPLE_client_storage)
  	{
  		gGL->glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, m_texClientStorage );
  	}
  
  	switch( target )
  	{
  		case GL_TEXTURE_CUBE_MAP:
  
  			// adjust target to steer to the proper face, then fall through to the 2D texture path.
  			target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + desc->m_req.m_face;
  			
  		case GL_TEXTURE_2D:
  		{			
  			// check compressed or not
  			if (format->m_chunkSize != 1)
  			{
  				Assert( writeWholeSlice );	//subimage not implemented in this path yet
  												
  				// compressed path
  				// http://www.opengl.org/sdk/docs/man/xhtml/glCompressedTexImage2D.xml
  				gGL->glCompressedTexImage2D( target,						// target
  										desc->m_req.m_mip,			// level
  										intformat,					// internalformat - don't use format->m_glIntFormat because we have the SRGB select going on above
  										slice->m_xSize,				// width
  										slice->m_ySize,				// height
  										0,							// border
  										slice->m_storageSize,		// imageSize
  										sliceAddress );				// data
  				
  				
  			}
  			else
  			{
  				if (mayUseSubImage)
  				{
  					// go subimage2D if it's a replacement, not a creation
  
  
  					gGL->glPixelStorei( GL_UNPACK_ROW_LENGTH, slice->m_xSize );			// in pixels
  					gGL->glPixelStorei( GL_UNPACK_SKIP_PIXELS, writeBox.xmin );		// in pixels
  					gGL->glPixelStorei( GL_UNPACK_SKIP_ROWS, writeBox.ymin );		// in pixels
  
  					gGL->glTexSubImage2D(	target,
  										desc->m_req.m_mip,				// level
  										writeBox.xmin,					// xoffset into dest
  										writeBox.ymin,					// yoffset into dest
  										writeBox.xmax - writeBox.xmin,	// width	(was slice->m_xSize)
  										writeBox.ymax - writeBox.ymin,	// height	(was slice->m_ySize)
  										glDataFormat,					// format
  										glDataType,						// type
  										sliceAddress					// data (will be offsetted by the SKIP_PIXELS and SKIP_ROWS - let GL do the math to find the first source texel)
  										);					
  
  					gGL->glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
  					gGL->glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 );
  					gGL->glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 );
  
  						/*
  							//http://www.opengl.org/sdk/docs/man/xhtml/glTexSubImage2D.xml
  							glTexSubImage2D(	target,
  												desc->m_req.m_mip,			// level
  												0,							// xoffset
  												0,							// yoffset
  												slice->m_xSize,				// width
  												slice->m_ySize,				// height
  												glDataFormat,				// format
  												glDataType,					// type
  												sliceAddress				// data
  												);					
  						*/				
  				}
  				else
  				{
  					if (m_layout->m_key.m_texFlags & kGLMTexRenderable)
  					{
  						if (gl_rt_forcergba.GetInt())
  						{
  							if (glDataFormat == GL_BGRA)
  							{
  								// change it
  								glDataFormat = GL_RGBA;
  							}
  						}
  					}
  					
  					// uncompressed path
  					// http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/teximage2d.html
  					gGL->glTexImage2D(			target,						// target
  											desc->m_req.m_mip,			// level
  											intformat,					// internalformat - don't use format->m_glIntFormat because we have the SRGB select going on above
  											slice->m_xSize,				// width
  											slice->m_ySize,				// height
  											0,							// border
  											glDataFormat,				// dataformat
  											glDataType,					// datatype
  											noDataWrite ? NULL : sliceAddress );	// data (optionally suppressed in case ResetSRGB desires)
  
  					if (m_layout->m_key.m_texFlags & kGLMTexMultisampled)
  					{
  						if (gl_texmsaalog.GetInt())
  						{
  							printf( "\n == MSAA Tex %p %s : glTexImage2D for flat tex using intformat %s (%x)", this, m_debugLabel?m_debugLabel:"", GLMDecode( eGL_ENUM, intformat ), intformat );
  							printf( "\n" );			
  						}
  					}
  
  					m_sliceFlags[ desc->m_sliceIndex ] |= kSliceValid; // for next time, we can subimage..
  				}
  			}
  		}
  		break;
  			
  		case GL_TEXTURE_3D:
  		{
  			// check compressed or not
  			if (format->m_chunkSize != 1)
  			{
  				// compressed path
  				// http://www.opengl.org/sdk/docs/man/xhtml/glCompressedTexImage3D.xml
  				
  				gGL->glCompressedTexImage3D(	target,						// target
  										desc->m_req.m_mip,			// level
  										intformat,					// internalformat
  										slice->m_xSize,				// width
  										slice->m_ySize,				// height
  										slice->m_zSize,				// depth
  										0,							// border
  										slice->m_storageSize,		// imageSize
  										sliceAddress );				// data
  			}
  			else
  			{
  				// uncompressed path
  				// http://www.opengl.org/sdk/docs/man/xhtml/glTexImage3D.xml
  				gGL->glTexImage3D(			target,						// target
  										desc->m_req.m_mip,			// level
  										intformat,					// internalformat
  										slice->m_xSize,				// width
  										slice->m_ySize,				// height
  										slice->m_zSize,				// depth
  										0,							// border
  										glDataFormat,				// dataformat
  										glDataType,					// datatype
  										noDataWrite ? NULL : sliceAddress );	// data (optionally suppressed in case ResetSRGB desires)
  			}
  		}
  		break;
  	}
  
  	if (gGL->m_bHave_GL_APPLE_client_storage)
  	{
  		gGL->glPixelStorei( GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE );
  	}
  
  	if ( expandTemp )
  	{
  		free( expandTemp );
  	}
  
  	m_ctx->BindTexToTMU( pPrevTex, 0 );
  }
  	
  
  void CGLMTex::Lock( GLMTexLockParams *params, char** addressOut, int* yStrideOut, int *zStrideOut )
  {
  #if GL_TELEMETRY_GPU_ZONES
  	CScopedGLMPIXEvent glmPIXEvent( "CGLMTex::Lock" );
  	g_TelemetryGPUStats.m_nTotalTexLocksAndUnlocks++;
  #endif
      VPROF( "CGLMTex::Lock" );
  	// locate appropriate slice in layout record
  	int sliceIndex = CalcSliceIndex( params->m_face, params->m_mip );
  	
  	GLMTexLayoutSlice *slice = &m_layout->m_slices[sliceIndex];
  
  	// obtain offset
  	//int sliceBaseOffset = slice->m_storageOffset;
  	
  	// cross check region req against slice bounds - figure out if it matches, exceeds, or is less than the whole slice.
  	char exceed =	(params->m_region.xmin < 0) || (params->m_region.xmax > slice->m_xSize) || 
  					(params->m_region.ymin < 0) || (params->m_region.ymax > slice->m_ySize) || 
  					(params->m_region.zmin < 0) || (params->m_region.zmax > slice->m_zSize);
  
  	char partial =	(params->m_region.xmin > 0) || (params->m_region.xmax < slice->m_xSize) ||
  					(params->m_region.ymin > 0) || (params->m_region.ymax < slice->m_ySize) ||
  					(params->m_region.zmin > 0) || (params->m_region.zmax < slice->m_zSize);
  					
  	bool	copyout = false;	// set if a readback of the texture slice from GL is needed
  
  	if (exceed)
  	{
  		// illegal rect, out of bounds		
  		GLMStop();
  	}
  	
  	// on return, these things need to be true
  	
  	// a - there needs to be storage allocated, which we will return an address within
  	// b - the region corresponding to the slice being locked, will have valid data there for the whole slice.
  	// c - the slice is marked as locked
  	// d - the params of the lock request have been saved in the lock table (in the context)
  	
  	// so step 1 is unambiguous.  If there's no backing storage, make some.
  	if (!m_backing)
  	{
  		m_backing = (char *)malloc( m_layout->m_storageTotalSize );
  		memset( m_backing, 0, m_layout->m_storageTotalSize );
  		
  		// clear the kSliceStorageValid bit on all slices
  		for( int i=0; i<m_layout->m_sliceCount; i++)
  		{
  			m_sliceFlags[i] &= ~kSliceStorageValid;
  		}
  	}
  	
  	// work on this slice now
  	
  	// storage is known to exist at this point, but we need to check if its contents are valid for this slice.
  	// this is tracked per-slice so we don't hoist all the texels back out of GL across all slices if caller only
  	// wanted to lock some of them.
  	
  	// (i.e. if we just alloced it, it's blank)
  	// if storage is invalid, but the texture itself is valid, hoist the texels back to the storage and mark it valid.
  	// if storage is invalid, and texture itself is also invalid, go ahead and mark storage as valid and fully dirty... to force teximage.
  	
  	//	???????????? we need to go over this more carefully re "slice valid" (it has been teximaged) vs "storage valid" (it has been copied out).
  		
  	unsigned char *sliceFlags = &m_sliceFlags[ sliceIndex ];
  	
  	if (params->m_readback)
  	{
  		// caller is letting us know that it wants to readback the real texels.
  		*sliceFlags |= kSliceStorageValid;
  		*sliceFlags |= kSliceValid;
  		*sliceFlags &= ~(kSliceFullyDirty);
  		copyout = true;
  	}
  	else
  	{
  		// caller is pushing texels.
  		if (! (*sliceFlags & kSliceStorageValid) )
  		{
  			// storage is invalid.  check texture state
  			if ( *sliceFlags & kSliceValid )
  			{
  				// kSliceValid set: the texture itself has a valid slice, but we don't have it in our backing copy, so copy it out.
  				copyout = true;
  			}
  			else
  			{
  				// kSliceValid not set: the texture does not have a valid slice to copy out - it hasn't been teximage'd yet.
  				// set the "full dirty" bit to make sure we teximage the whole thing on unlock.
  				*sliceFlags |= kSliceFullyDirty;
  				
  				// assert if they did not ask to lock the full slice size on this go-round
  				if (partial)
  				{
  					// choice here - 
  					// 1 - stop cold, we don't know how to subimage yet.
  					// 2 - grin and bear it, mark whole slice dirty (ah, we already did... so, do nothing).
  					// choice 2: // GLMStop();
  				}
  			}
  			
  			// one way or another, upon reaching here the slice storage is valid for read.
  			*sliceFlags |= kSliceStorageValid;
  		}
  	}
  
  	
  	// when we arrive here, there is storage, and the content of the storage for this slice is valid
  	// (or zeroes if it's the first lock)
  
  	// log the lock request in the context.
  	int newdesc = m_ctx->m_texLocks.AddToTail();
  	
  	GLMTexLockDesc *desc = &m_ctx->m_texLocks[newdesc];
  	
  	desc->m_req = *params;
  	desc->m_active = true;
  	desc->m_sliceIndex = sliceIndex;
  	desc->m_sliceBaseOffset = m_layout->m_slices[sliceIndex].m_storageOffset;
  
  	// to calculate the additional offset we need to look at the rect's min corner
  	// combined with the per-texel size and Y/Z stride
  	// also cross check it for 4x multiple if there is compression in play
  	
  	int offsetInSlice = 0;
  	int	yStride = 0;
  	int zStride = 0;
  
  	CalcTexelDataOffsetAndStrides( sliceIndex, params->m_region.xmin, params->m_region.ymin, params->m_region.zmin, &offsetInSlice, &yStride, &zStride );
  
  	// for compressed case...
  	// since there is presently no way to texsubimage a DXT when the rect does not cover the whole width,
  	// we will probably need to inflate the dirty rect in the recorded lock req so that the entire span is
  	// pushed across at unlock time.
  
  	desc->m_sliceRegionOffset = offsetInSlice + desc->m_sliceBaseOffset;
  
  	if (copyout)
  	{
  	    VPROF( "ReadTexels" );
  		// read the whole slice
  		// (odds are we'll never request anything but a whole slice to be read..)
  		ReadTexels( desc, true );
  	}	// this would be a good place to fill with scrub value if in debug...
  	
  	*addressOut = m_backing + desc->m_sliceRegionOffset;
  	*yStrideOut = yStride;
  	*zStrideOut = zStride;
  
  	m_lockCount++;
  }
  
  void CGLMTex::Unlock( GLMTexLockParams *params )
  {
  #if GL_TELEMETRY_GPU_ZONES
  	CScopedGLMPIXEvent glmPIXEvent( "CGLMTex::Unlock" );
  	g_TelemetryGPUStats.m_nTotalTexLocksAndUnlocks++;
  #endif
  
  	// look for an active lock request on this face and mip (doesn't necessarily matter which one, if more than one)
  	// and mark it inactive.
  	// --> if you can't find one, fail. first line of defense against mismatched locks/unlocks..
  
  	int i=0;
  	bool found = false;
  	while( !found && (i<m_ctx->m_texLocks.Count()) )
  	{
  		GLMTexLockDesc *desc = &m_ctx->m_texLocks[i];
  		
  		// is lock at index 'i' targeted at the texture/face/mip in question?
  		if ( (desc->m_req.m_tex == this) && (desc->m_req.m_face == params->m_face) & (desc->m_req.m_mip == params->m_mip) && (desc->m_active) )
  		{
  			// matched and active, so retire it
  			desc->m_active = false;
  			
  			// stop searching
  			found = true;
  		}
  		i++;
  	}
  
  	if (!found)
  	{
  		GLMStop();	// bad news
  	}
  	
  	// found - so drop lock count
  	m_lockCount--;
  	
  	if (m_lockCount <0)
  	{
  		GLMStop();	// bad news
  	}			
  
  	if (m_lockCount==0)
  	{
  		// there should not be any active locks remaining on this texture.
  
  		// motivation to defer all texel pushing til *all* open locks are closed out - 
  		// if/when we back the texture with a PBO, we will need to unmap that PBO before teximaging from it;
  		// by waiting for all the locks to clear this gives us an unambiguous signal to act on.
  		
  		// scan through all the retired locks for this texture and push the texels for each one.
  		// after each one is dispatched, remove it from the pile.
  		
  		int j=0;
  		while( j<m_ctx->m_texLocks.Count() )
  		{
  			GLMTexLockDesc *desc = &m_ctx->m_texLocks[j];
  			
  			if ( desc->m_req.m_tex == this )
  			{
  				// if it's active, something is wrong
  				if (desc->m_active)
  				{
  					GLMStop();
  				}
  				
  				// write the texels
  				bool fullyDirty = false;
  				
  				fullyDirty |= ((m_sliceFlags[ desc->m_sliceIndex ] & kSliceFullyDirty) != 0);
  
  				// this is not optimal and will result in full downloads on any dirty.
  				// we're papering over the fact that subimage isn't done yet.
  				// but this is safe if the slice of storage is all valid.
  				
  				// at some point we'll need to actually compare the lock box against the slice bounds.
  				
  				// fullyDirty |= (m_sliceFlags[ desc->m_sliceIndex ] & kSliceStorageValid);
  				
  				WriteTexels( desc, fullyDirty  );
  
  				// logical place to trigger preloading
  				// only do it for an RT tex, if it is not yet attached to any FBO.
  				// also, only do it if the slice number is the last slice in the tex.
  				if ( desc->m_sliceIndex == (m_layout->m_sliceCount-1) )
  				{
  					if ( !(m_layout->m_key.m_texFlags & kGLMTexRenderable) || (m_rtAttachCount==0) )
  					{
  						m_ctx->PreloadTex( this );
  						// printf("( slice %d of %d )", desc->m_sliceIndex, m_layout->m_sliceCount );
  					}
  				}
  
  				m_ctx->m_texLocks.FastRemove( j );	// remove from the pile, don't advance index
  			}
  			else
  			{
  				j++; // move on to next one
  			}
  		}
  		
  		// clear the locked and full-dirty flags for all slices
  		for( int slice=0; slice < m_layout->m_sliceCount; slice++)
  		{
  			m_sliceFlags[slice] &= ~( kSliceLocked | kSliceFullyDirty );
  		}
          
          
  		// The 3D texture upload code seems to rely on the host copy, probably
  		// because it reuploads the whole thing each slice; we only use 3D textures
  		// for the 32x32x32 colorpsace conversion lookups and debugging the problem
  		// would not save any more memory.
  		if ( !m_texClientStorage && ( m_texGLTarget == GL_TEXTURE_2D ) )
  		{
  			free(m_backing);
  			m_backing = NULL;
  		}
  	}
  }
  
  #if defined( OSX )
  
  void CGLMTex::HandleSRGBMismatch( bool srgb, int &srgbFlipCount )
  {
  	bool srgbCapableTex = false; // not yet known
  	bool renderableTex = false; // not yet known.
  
  	srgbCapableTex	= m_layout->m_format->m_glIntFormatSRGB != 0;
  	renderableTex	= ( m_layout->m_key.m_texFlags & kGLMTexRenderable ) != 0;
  	// we can fix it if it's not a renderable, and an sRGB enabled format variation is available.
  
  	if ( srgbCapableTex && !renderableTex )
  	{
  		char *texname = m_debugLabel;
  		if (!texname) texname = "-";
  
  		m_srgbFlipCount++;
  
  		//policy: print the ones that have flipped 1 or N times
  		bool print_allflips		= CommandLine()->FindParm("-glmspewallsrgbflips");
  		bool print_firstflips	= CommandLine()->FindParm("-glmspewfirstsrgbflips");
  		bool print_freqflips	= CommandLine()->FindParm("-glmspewfreqsrgbflips");
  		bool print_crawls		= CommandLine()->FindParm("-glmspewsrgbcrawls");
  		bool print_maxcrawls	= CommandLine()->FindParm("-glmspewsrgbmaxcrawls");
  		bool print_it = false;
  
  		if (print_allflips)
  		{
  			print_it = true;
  		}
  		if (print_firstflips)		// report on first flip
  		{
  			print_it |= m_srgbFlipCount==1;
  		}
  		if (print_freqflips)	// report on 50th flip
  		{
  			print_it |= m_srgbFlipCount==50;
  		}
  
  		if ( print_it )
  		{
  			char	*formatStr;
  			formatStr = "srgb change (samp=%d): tex '%-30s' %08x %s (srgb=%d, %d times)";
  
  			if (strlen(texname) >= 30)
  			{
  				formatStr = "srgb change (samp=%d): tex '%s' %08x %s (srgb=%d, %d times)";
  			}
  
  			printf( "\n" );
  			printf( formatStr, index, texname, m_layout->m_layoutSummary, (int)srgb, m_srgbFlipCount );
  
  #ifdef POSIX
  			if (print_crawls)
  			{
  				static char *interesting_crawl_substrs[] = { "CShader::OnDrawElements", NULL };		// add more as needed
  
  				CStackCrawlParams	cp;
  				memset( &cp, 0, sizeof(cp) );
  				cp.m_frameLimit = 20;
  
  				g_pLauncherMgr->GetStackCrawl(&cp);
  
  				for( int i=0; i< cp.m_frameCount; i++)
  				{
  					// for each row of crawl, decide if name is interesting
  					bool hit = print_maxcrawls;
  
  					for( char **match = interesting_crawl_substrs; (!hit) && (*match != NULL); match++)
  					{
  						if (strstr(cp.m_crawlNames[i], *match))
  						{
  							hit = true;
  						}
  					}
  
  					if (hit)
  					{
  						printf( "\n\t%s", cp.m_crawlNames[i] );
  					}
  				}
  				printf( "\n");
  			}
  #endif
  		}
  
  #if GLMDEBUG && 0
  		//"toi" = texture of interest
  		static char s_toi[256] = "colorcorrection";
  		if (strstr( texname, s_toi ))
  		{
  			// breakpoint on this if you like
  			GLMPRINTF(( "srgb change %d for %s", m_srgbFlipCount, texname ));
  		}
  #endif
  
  		// re-submit the tex unless we're stifling it
  		if (!CommandLine()->FindParm( "-glmnosrgbflips" ))
  		{
  			ResetSRGB( srgb, false );
  		}
  	}
  	else
  	{
  		//GLMPRINTF(("-Z- srgb sampling conflict: NOT fixing tex %08x [%s] (srgb req: %d) because (tex-srgb-capable=%d  tex-renderable=%d)", m_textures[index], m_textures[index]->m_tex->m_layout->m_layoutSummary, (int)glsamp->m_srgb, (int)srgbCapableTex, (int)renderableTex ));
  		// we just leave the sampler state where it is, and that's life
  	}
  }
  
  
  void CGLMTex::ResetSRGB( bool srgb, bool noDataWrite )
  {
  	// see if requested SRGB state differs from the known one
  	bool			wasSRGB = (m_layout->m_key.m_texFlags & kGLMTexSRGB);
  	GLMTexLayout	*oldLayout = m_layout;	// need to m_ctx->m_texLayoutTable->DelLayoutRef on this one if we flip
  
  	if (srgb != wasSRGB)
  	{
  		// we're going to need a new layout (though the storage size should be the same - check it)
  		GLMTexLayoutKey newKey = m_layout->m_key;
  		
  		newKey.m_texFlags &= (~kGLMTexSRGB);	// turn off that bit
  		newKey.m_texFlags |= srgb ? kGLMTexSRGB : 0;	// turn on that bit if it should be so
  		
  		// get new layout 
  		GLMTexLayout *newLayout = m_ctx->m_texLayoutTable->NewLayoutRef( &newKey );
  
  		// if SRGB requested, verify that the layout we just got can do it.
  		// if it can't, delete the new layout ref and bail.
  		if (srgb && (newLayout->m_format->m_glIntFormatSRGB == 0))
  		{
  			Assert( !"Can't enable SRGB mode on this format" );			
  			m_ctx->m_texLayoutTable->DelLayoutRef( newLayout );
  			return;
  		}
  
  		// check sizes and fail if no match
  		if( newLayout->m_storageTotalSize != oldLayout->m_storageTotalSize )
  		{
  			Assert( !"Bug: layout sizes don't match on SRGB change" );
  			m_ctx->m_texLayoutTable->DelLayoutRef( newLayout );
  			return;
  		}
  
  		// commit to new layout
  		m_layout = newLayout;
  		m_texGLTarget = m_layout->m_key.m_texGLTarget;
  	
  		// check same size
  		Assert( m_layout->m_storageTotalSize == oldLayout->m_storageTotalSize );
  			
  		Assert( newLayout != oldLayout );
  
  		// release old
  		m_ctx->m_texLayoutTable->DelLayoutRef( oldLayout );
  		oldLayout = NULL;
  
  		// force texel re-DL
  
  		// note this messes with TMU 0 as side effect of WriteTexels
  		// so we save and restore the TMU 0 binding first
  		
  		// since we're likely to be called in dxabstract when it is syncing sampler state, we can't go trampling the bindings.
  		// a refinement would be to have each texture make a note of which TMU they're bound on, and just use that active TMU for DL instead of 0.
  		CGLMTex *tmu0save = m_ctx->m_samplers[0].m_pBoundTex;
  		
  		for( int face=0; face <m_layout->m_faceCount; face++)
  		{
  			for( int mip=0; mip <m_layout->m_mipCount; mip++)
  			{
  				// we're not really going to lock, we're just going to rewrite the orig data
  				GLMTexLockDesc	desc;
  				
  				desc.m_req.m_tex = this;
  				desc.m_req.m_face = face;
  				desc.m_req.m_mip = mip;
  
  				desc.m_sliceIndex = CalcSliceIndex( face, mip );
  
  				GLMTexLayoutSlice *slice = &m_layout->m_slices[ desc.m_sliceIndex ];
  				
  				desc.m_req.m_region.xmin = desc.m_req.m_region.ymin = desc.m_req.m_region.zmin = 0;
  				desc.m_req.m_region.xmax = slice->m_xSize;
  				desc.m_req.m_region.ymax = slice->m_ySize;
  				desc.m_req.m_region.zmax = slice->m_zSize;
  
  				desc.m_sliceBaseOffset = slice->m_storageOffset;	// doesn't really matter... we're just pushing zeroes..
  				desc.m_sliceRegionOffset = 0;
  
  				WriteTexels( &desc, true, noDataWrite );	// write whole slice. and avoid pushing real bits if the caller requests (RT's)
  			}
  		}
  		
  		// put it back
  		m_ctx->BindTexToTMU( tmu0save, 0 );
  	}
  }
  #endif
  
  bool CGLMTex::IsRBODirty() const 
  { 
  	return m_nLastResolvedBatchCounter != m_ctx->m_nBatchCounter; 
  }
  
  void CGLMTex::ForceRBONonDirty() 
  { 
  	m_nLastResolvedBatchCounter = m_ctx->m_nBatchCounter; 
  }
  
  void CGLMTex::ForceRBODirty() 
  { 
  	m_nLastResolvedBatchCounter = m_ctx->m_nBatchCounter - 1; 
  }
  