#include "pch.hpp"
//===========================================================================//
// File:	 Texture VidMem.cpp												 //
// Project:  GameOS					                                         //
// Contents: Texture Video Memory Management								 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 04/03/00  NK     Created													 //
//---------------------------------------------------------------------------//
// Copyright (C) 2000 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "Texture Manager.hpp"
#include "DirectX.hpp"
#include "DXRasterizer.hpp"
#include "3DRasterizer.hpp"
#include "DirectXDebugging.hpp"

volatile __int64 TimeUploadTextures;
extern DWORD gTestAGPLimit;
extern DWORD gTestVIDLimit;
extern DWORD hwTexture[2];
extern DWORD hwTexture1[2];
extern DWORD hwTexture2[2];
DWORD LastPurge=0;
DWORD MaximumVidTextures=500;		// limit the number simultaneously loaded
//
// List of video memory textures (so we don't delete on some cards)
//
VidMemHeap* pFreeVidMemTextures=0;


int CTexInfo::NumDummyTextures = 0;				
IDirectDrawSurface7 *CTexInfo::pDummyTex[1024];	// Video surfaces created to fill vidmem for debugger

// Functions defined below
IDirectDrawSurface7*	CTexInfo::GetVidMemSurf( bool populate, WORD MipMapWidth );
void					CTexInfo::AllocateVidMem();	// Uses various techniques to allocate video memory
void					CTexInfo::PopulateVidMem();	// Uploads level(s) from SysMem to VidMem/AGP
void					CTexInfo::UploadLevel( IDirectDrawSurface7 *dest, IDirectDrawSurface7 *source );
void					CTexInfo::FreeVidMem();		// Frees the SysMem surface/chain (assumes it exists and is ok to free)


// Find or allocate video memory or AGP surface; populate if required; return appropriate level
IDirectDrawSurface7* CTexInfo::GetVidMemSurf( bool populate, WORD MipMapWidth )
{
	if( !m_pVidMemSurf )
		AllocateVidMem();
	if( populate && !InVidMem() )
		PopulateVidMem();
	return FindTextureLevel( m_pVidMemSurf, MipMapWidth );
}

// Removes all app-specific textures from vidmem and does a flip to let them *really* go
void CTexInfo::PurgeTextures()
{
	CTexInfo *pTexInfo;
	for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
	{
		// clear the precalculated memory usage for textures so they are recalculated
		pTexInfo->m_nOriginalBytes = 0;

		if( pTexInfo->m_pVidMemSurf )
		{
			TexturesBumped++;
			pTexInfo->LogTextureEvent( "Bumped (clearing all)" );
			pTexInfo->FreeVidMem();
			pTexInfo->InvalidateVidMem();
		}
	}
}



//
// On cards that cannot delete textures, see if one is already in VRAM
//
HRESULT TryCreateSurface( IDirectDraw7* lpdd7, LPDDSURFACEDESC2 lpDDSurfaceDesc, IDirectDrawSurface7** lplpDDSurface7, IUnknown* pUnkOuter )
{
//
// If on a card that cannot allow texture deletes, see if a deleted surface exists before creating a new one
//
	if( Compatibility3D&DisableTextureDeletes )
	{
		VidMemHeap* pLast=0;
		VidMemHeap* pNewEmptyTexture=pFreeVidMemTextures;

		while( pNewEmptyTexture )
		{
//
// See if a surface is already in the list
//
			if( lpDDSurfaceDesc->dwHeight == pNewEmptyTexture->ddsd.dwHeight			&&
				lpDDSurfaceDesc->dwWidth == pNewEmptyTexture->ddsd.dwWidth				&&
				lpDDSurfaceDesc->dwMipMapCount == pNewEmptyTexture->ddsd.dwMipMapCount  &&
			   (lpDDSurfaceDesc->ddsCaps.dwCaps&(DDSCAPS_NONLOCALVIDMEM+DDSCAPS_LOCALVIDMEM+DDSCAPS_SYSTEMMEMORY)) == (pNewEmptyTexture->ddsd.ddsCaps.dwCaps&(DDSCAPS_NONLOCALVIDMEM+DDSCAPS_LOCALVIDMEM+DDSCAPS_SYSTEMMEMORY))  &&
				lpDDSurfaceDesc->ddpfPixelFormat.dwFlags == pNewEmptyTexture->ddsd.ddpfPixelFormat.dwFlags  &&
				lpDDSurfaceDesc->ddpfPixelFormat.dwRBitMask == pNewEmptyTexture->ddsd.ddpfPixelFormat.dwRBitMask  &&
				lpDDSurfaceDesc->ddpfPixelFormat.dwRGBAlphaBitMask == pNewEmptyTexture->ddsd.ddpfPixelFormat.dwRGBAlphaBitMask  )
			{
				if( pLast )
				{
					pLast->pNext=pNewEmptyTexture->pNext;
				}
				else
				{
					pFreeVidMemTextures=pNewEmptyTexture->pNext;
				}
				*lplpDDSurface7=pNewEmptyTexture->Surface;
				gos_Free( pNewEmptyTexture );
				return DD_OK;
			}
			pLast=pNewEmptyTexture;
			pNewEmptyTexture=pNewEmptyTexture->pNext;
		}
	}

	return wCreateSurface( lpdd7, lpDDSurfaceDesc, lplpDDSurface7, pUnkOuter );
}






// Try to create texture as requested (AGP / LOCAL), - if unsuccesful, try opposite way (LOCAL / AGP)
bool CTexInfo::CreateTexture()
{
	DDSURFACEDESC2			ddsd;
	HRESULT					result;

	if( NumVidTextures >= MaximumVidTextures )
	{
		SPEW((GROUP_TEXTURE,"Hit maximum number of vidmem textures(%d), must evict some", MaximumVidTextures ));
		return false;
	}

	int MipLevels=HasMipMap ? MipMapLevels() : 1;

	DDSURFACEDESC2 *pSrcDesc = Description();

	if( pSrcDesc->ddpfPixelFormat.dwSize != sizeof( DDPIXELFORMAT ) )
	{
		STOP(("Invalid size of pixel format %d", pSrcDesc->ddpfPixelFormat.dwSize ));
	}

	memcpy(&ddsd,Description(),sizeof(ddsd));

	if( ddsd.ddpfPixelFormat.dwSize != sizeof( DDPIXELFORMAT ) )
	{
		STOP(("Invalid size of pixel format %d", ddsd.ddpfPixelFormat.dwSize ));
	}

	ddsd.dwSize					= sizeof(DDSURFACEDESC2);
	ddsd.dwFlags				= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | (HasMipMap ? DDSD_MIPMAPCOUNT : 0);
	ddsd.ddsCaps.dwCaps			= DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY | (((m_Hints & gosHint_AGPMemory) && 0==(Compatibility3D&BadAGP))?(DDSCAPS_NONLOCALVIDMEM):(DDSCAPS_LOCALVIDMEM)) | (HasMipMap ? (DDSCAPS_MIPMAP | DDSCAPS_COMPLEX) : 0 );
	ddsd.ddsCaps.dwCaps2		= 0;
	ddsd.dwHeight				= ScaledHeight();
	ddsd.dwWidth				= ScaledWidth();
	ddsd.dwMipMapCount			= HasMipMap ? MipLevels : 0;

	if( ddsd.ddpfPixelFormat.dwSize != sizeof( DDPIXELFORMAT ) )
	{
		STOP(("Invalid size of pixel format %d", ddsd.ddpfPixelFormat.dwSize ));
	}

	if( !HardwareRenderer )
	{
		// Special case - this will occur if using Blade but the texture is not compliant
		// i.e. too large, etc.
		// In this case, we create a "video" surface in system memory and use that
		ddsd.ddsCaps.dwCaps &= ~ ( DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM | DDSCAPS_VIDEOMEMORY );
		ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

		AllowFail=TRUE;
		result=wCreateSurface( CurrentDDobject, &ddsd, &m_pVidMemSurf, NULL );
		AllowFail=FALSE;

		if( !FAILED(result) )
			NumVidTextures++;

		return !FAILED(result);
	}

	#ifdef LAB_ONLY
	bool failAGP = (Compatibility3D&BadAGP) || (( AGPUsed + MemoryUsage(false,gmt_VidMem) ) > gTestAGPLimit);
	bool failVID = (gTestVIDLimit==0);	// Unless simulating 0MB local mem, rely on dummy textures
	// if we are trying AGP and we are over the AGP limit, try local vidmem only
	if( failAGP )
	{
		if( failVID )
			return false;			
		if( ddsd.ddsCaps.dwCaps & DDSCAPS_NONLOCALVIDMEM )
			ddsd.ddsCaps.dwCaps ^= ( DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM );
	}
	else if( failVID )
	{
		if( ddsd.ddsCaps.dwCaps & DDSCAPS_LOCALVIDMEM )
			ddsd.ddsCaps.dwCaps ^= ( DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM );
	}
	#endif

	// Attempt allocation as requested
	AllowFail=TRUE;
	result=TryCreateSurface( CurrentDDobject, &ddsd, &m_pVidMemSurf, NULL );
	AllowFail=FALSE;


	#ifdef LAB_ONLY
	if( FAILED(result) && !failAGP && !failVID )
	#else
	if( FAILED(result) )
	#endif
	{
//
// MW4:7505 - Got WinERR_Ran out of memory on a UMA system
//
//		if( ( result != DDERR_NONONLOCALVIDMEM ) && ( result != DDERR_OUTOFVIDEOMEMORY ) )
//		{
//			char* msg = ErrorNumberToMessage( result );
//
//			STOP(("Failed to create texture. %s - %s", GetSurfaceDescription(&ddsd), msg));
//		}

		// If allocation failed, try opposite ( LOCAL vs NONLOCAL ) allocation
		ddsd.ddsCaps.dwCaps ^= ( DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM );

		if( !( (Compatibility3D&BadAGP) && (ddsd.ddsCaps.dwCaps&DDSCAPS_NONLOCALVIDMEM)) )
		{
			AllowFail=TRUE;
			result=TryCreateSurface( CurrentDDobject, &ddsd, &m_pVidMemSurf, NULL );
			AllowFail=FALSE;
		}
//
// MW4:7505 - Got WinERR_Ran out of memory on a UMA system
//
//		if( FAILED(result) && ( result != DDERR_NONONLOCALVIDMEM ) && ( result != DDERR_OUTOFVIDEOMEMORY ) )
//		{
//			char* msg = ErrorNumberToMessage( result );
//
//			STOP(("Failed to create texture. %s - %s", GetSurfaceDescription(&ddsd), msg));
//		}
	}

	if( !FAILED(result) )
	{
		NumVidTextures++;
		if( ddsd.ddsCaps.dwCaps & DDSCAPS_NONLOCALVIDMEM )
		{
			m_Flags |= tFlag_InAGP;
			#ifdef LAB_ONLY
			AGPUsed += MemoryUsage(false,gmt_VidMem);
			#endif
			LogTextureEvent("AGP Assigned");
		}
		else
		{
			m_Flags &= ~tFlag_InAGP;
			LogTextureEvent("Local Vidmem Assigned");
		}
	}

	return !FAILED(result);
}


// Use various techniques to allocate video memory
//
//	This is the heart of the allocation scheme which swaps out other textures etc to make space
void CTexInfo::AllocateVidMem()
{

	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::AllocateVidMem", this ));

	gosASSERT( !m_pVidMemSurf );
	CTexInfo *pTexInfo;

	for( int attempt=0; !m_pVidMemSurf; attempt++ )
	{
		// Attempt to create Video or AGP surface requested
		if( CreateTexture() )
			break;

		// If that failed, take action appropriate for this attempt #
		switch( attempt )
		{
		case 0:
			// look for texture in free chain of appropriate size && type
			// break;
			// TBD - use a free-chain, for now... continue to next attempt immediately
			attempt++;
		
		case 1:

			// Find oldest matching texture we can steal

			DWORD oldestTime;
			CTexInfo *pOldest;
			
			oldestTime = TimeNo+1;
			pOldest = NULL;

			for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
			{
				if( ( !pTexInfo->InVidMem() ) ||
					( pTexInfo->m_Flags & ( tFlag_LockRW | tFlag_Locked ) ) ||
					(((long)( oldestTime - pTexInfo->m_nLastTimeUsed )) <= 0 ) ||
					( pTexInfo->m_nWidth != m_nWidth ) ||
					( pTexInfo->m_nHeight != m_nHeight ) ||
					( pTexInfo->m_Hints & gosHint_PageLast ) ||
					( HasMipMap && ( MipMapLevels() != pTexInfo->MipMapLevels() ) ) ||
					( pTexInfo->Description() != Description() ) )
					continue;
				// found a perfect match for the desired texture and it is older than the last one we found
				oldestTime = pTexInfo->m_nLastTimeUsed;
				pOldest = pTexInfo;
			}

			if( Environment.DirtyRectangle )
			{
				// for "DirtyRect" or non-scene renderers, reject only if texture *very* recently used
				if( TimeNo - oldestTime < 3 )
					break;
			}
			else if (pOldest)
			{
				// For normal programs, reject if texture has been used in last 2 frames
				if( FrameNo - pOldest->m_nLastFrameUsed < 2 )
					break;
			}

			pTexInfo = pOldest;		
						
			if( pTexInfo )
			{
				// found a perfect match for the desired texture, - use it
				m_pVidMemSurf = pTexInfo->m_pVidMemSurf;
				// Mark the vidmem version as invalid
				m_Flags &= ~( tFlag_InVidMem | tFlag_InAGP );
				// Mark as in AGP if that is the case
				m_Flags |= ( pTexInfo->m_Flags & tFlag_InAGP );
				TexturesBumped++;

				pTexInfo->LogTextureEvent( "Bumped(stolen)" );
				LogTextureEvent( "Vidmem Assigned(stolen)" );

				// remove references from old texture
				pTexInfo->m_pVidMemSurf = NULL;
				pTexInfo->m_Flags &= ~( tFlag_InVidMem | tFlag_InAGP );
			}
			break;
//
// Bump every texture not used on current or last frame
//
		case 2:

			if( Environment.DirtyRectangle )
			{
				// Bump every texture not in last three setrenderstate calls
				for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
				{
					if( pTexInfo->m_pVidMemSurf &&
						( ((long)(TimeNo - pTexInfo->m_nLastTimeUsed))>3) &&
						!( pTexInfo->m_Hints & gosHint_PageLast ) )
					{
						TexturesBumped++;
						pTexInfo->LogTextureEvent( "Bumped(attempt 3)" );
						pTexInfo->FreeVidMem();
						pTexInfo->InvalidateVidMem();
					}
				}
			}
			else
			{
				for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
				{
					if( pTexInfo->m_pVidMemSurf &&
						( ((long)(FrameNo - pTexInfo->m_nLastFrameUsed)) > 1 ) &&
						!( pTexInfo->m_Hints & gosHint_PageLast ) )
					{
						TexturesBumped++;
						pTexInfo->LogTextureEvent( "Bumped(attempt 3)" );
						pTexInfo->FreeVidMem();
						pTexInfo->InvalidateVidMem();
					}
				}
			}
			break;
//
// Bump every texture not used on current frame
//		
		case 3:
		
			if( !Environment.DirtyRectangle )
			{
				// Bump every texture not used on current frame
				for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
				{
					if( pTexInfo->m_pVidMemSurf &&
						( ((long)(FrameNo != pTexInfo->m_nLastFrameUsed)) ) &&
						!( pTexInfo->m_Hints & gosHint_PageLast ) )
					{
						TexturesBumped++;
						pTexInfo->LogTextureEvent( "Bumped(attempt 3)" );
						pTexInfo->FreeVidMem();
						pTexInfo->InvalidateVidMem();
					}
				}
			}
//
// Now try to purge / reload video memory
//
		case 4:
			LogTextureEvent( "Purging texture memory" );

			if( FrameNo-LastPurge > 2 )
			{
				PurgeTextures();
				LastPurge=FrameNo;
			}
			break;
//
// Try purge and flip twice (To make sure some drivers 'let go of textures'? )
//
		case 5:
			LogTextureEvent( "Purging texture memory and flipping" );

			if( LastPurge==FrameNo )
			{
				PurgeTextures();
				LastPurge=FrameNo-1;
			}
			break;
//
// On a card that cannot delete textures PURGING is better than shrinking - try a full purge again
//
		case 6:
			if( Compatibility3D&DisableTextureDeletes )
			{
				PurgeTextures();
			}
			break;
//
// Ok - we need to shrink textures
//
		case 7:
			// Up the scaledown....
			if( ScaleShift<3 )
			{
				ScaleShift++;

				LogTextureEvent( "Changed scale-down for future textures!!!" );

				PurgeTextures();
			}
			break;
//
// Bump all textures on last time (before we give up and error)
//
		case 8:
			PurgeTextures();
			break;

		default:
			STOP(("Failed to create texture even after all other textures bumped!"));
		}
	}
}


// Upload level(s) from SysMem to VidMem/AGP
void CTexInfo::PopulateVidMem()
{
	LOG_BLOCK("CTexInfo::PopulateVidMem");
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::PopulateVidMem", this ));

	GetSysMemSurf(true);	// make sure we have a valid, converted version in system memory to work from

	gosASSERT( m_pSysMemSurf );
	gosASSERT( m_pVidMemSurf );

	LogTextureEvent("Upload");

	UploadLevel( m_pVidMemSurf, m_pSysMemSurf );

	// Iterate through any mipmap levels
	if( HasMipMap && ( MipMapLevels() > 1 ) )
	{
		IDirectDrawSurface7* SysMemSurface = m_pSysMemSurf;
		IDirectDrawSurface7* VidMemSurface = m_pVidMemSurf;
		DDSCAPS2 ddsCaps2;
		memset( &ddsCaps2, 0, sizeof(ddsCaps2) );
		ddsCaps2.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;

		for( int level=1; level < MipMapLevels(); level++ )
		{
			wGetAttachedSurface( SysMemSurface, &ddsCaps2, &SysMemSurface );
			wRelease( SysMemSurface );
			wGetAttachedSurface( VidMemSurface, &ddsCaps2, &VidMemSurface );
			wRelease( VidMemSurface );
			UploadLevel( VidMemSurface, SysMemSurface );
		}
	}

	m_Flags |= tFlag_InVidMem;
	// If there is an original version present and we don't need it then free it
	if( m_pOriginalSurf && ( m_pRebuild || ( m_Hints & gosHint_ReloadFromDisk ) ) )
		FreeOriginal();

	// If there is a SysMem version present and we don't need it then free it
	if( m_pSysMemSurf && ( m_pRebuild || ( m_Hints & gosHint_ReloadFromDisk ) ) )
		FreeSysMem();

#ifdef LAB_ONLY
	// Update statistics
	TexturesSwapped++;
	if( m_Flags & tFlag_InAGP )
		TextureAGPMegs += MemoryUsage(true,gmt_VidMem);
	else
		TextureLocalMegs += MemoryUsage(true,gmt_VidMem);
	TextureBytesPaged += MemoryUsage(true,gmt_VidMem);
#endif


}


// Upload a single level to video memory or AGP
void CTexInfo::UploadLevel( IDirectDrawSurface7 *dest, IDirectDrawSurface7 *source )
{
	LOG_BLOCK("Gos3D::UploadLevel");
#ifdef LAB_ONLY
	__int64 Temp=GetCycles();
#endif

	HRESULT rc=-1;

	SafeFPU();
	AllowFail = true;
	if( 0==(Compatibility3D&NoStretchTexture) )
	{
		rc = wBlt( dest, NULL, source, NULL, DDBLT_WAIT, 0 );
	}
	AllowFail = false;

	if( FAILED(rc) )
	{
		IDirectDrawSurface7 *pTmpSurf;
		DDSURFACEDESC2 desc;
		memset( &desc, 0, sizeof(desc) );
		desc.dwSize = sizeof(desc);
		wGetSurfaceDesc( dest, &desc );
		desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT ;
		desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;
		wCreateSurface( CurrentDDobject, &desc, &pTmpSurf, NULL );
		wBlt( pTmpSurf, NULL, source, NULL, DDBLT_WAIT, 0 );
		wBlt( dest, NULL, pTmpSurf, NULL, DDBLT_WAIT, 0 );
		wRelease( pTmpSurf );
	}
	GameOSFPU();

#ifdef LAB_ONLY
	// Update statistics
	TimeUploadTextures+=GetCycles()-Temp;
#endif
}


// Free the video memory texture (assumes it exists and is ok to free)
void CTexInfo::FreeVidMem()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::FreeVidMem", this ));
	gosASSERT( m_pVidMemSurf );

	// make sure we never delete a texture in use
	if( (DWORD)m_pVidMemSurf==hwTexture2[1] || (DWORD)m_pVidMemSurf==hwTexture1[1] || (DWORD)m_pVidMemSurf==hwTexture[1] )
	{
		if( (DWORD)m_pVidMemSurf==hwTexture[1] )
		{
			RenderStates[gos_State_Texture]=0;
			hwTexture[0]=0;
			hwTexture[1]=0;
			wSetTexture( d3dDevice7, 0, 0 );
		}
		if( (DWORD)m_pVidMemSurf==hwTexture1[1] )
		{
			RenderStates[gos_State_Texture2]=0;
			hwTexture1[0]=0;
			hwTexture1[1]=0;
			wSetTexture( d3dDevice7, 1, 0 );
		}
		if( (DWORD)m_pVidMemSurf==hwTexture2[1] )
		{
			RenderStates[gos_State_Texture3]=0;
			hwTexture2[0]=0;
			hwTexture2[1]=0;
			wSetTexture( d3dDevice7, 2, 0 );
		}
	}
//
// If on a card that cannot allow texture deletes, save surface for later
//
	if( Compatibility3D&DisableTextureDeletes )
	{
		VidMemHeap* pNewEmptyTexture=(VidMemHeap*)gos_Malloc( sizeof(VidMemHeap) );
		pNewEmptyTexture->pNext=0;
		pNewEmptyTexture->ddsd.dwSize=sizeof(pNewEmptyTexture->ddsd);
		pNewEmptyTexture->Surface=m_pVidMemSurf;
		wGetSurfaceDesc( m_pVidMemSurf, &pNewEmptyTexture->ddsd );

		if( pFreeVidMemTextures )
		{
			pNewEmptyTexture->pNext=pFreeVidMemTextures;
		}
		pFreeVidMemTextures=pNewEmptyTexture;
	}
	else
	{
		wRelease( m_pVidMemSurf );		// delete surface
	}

	NumVidTextures--;
	#ifdef LAB_ONLY
	if( m_Flags & tFlag_InAGP )
		AGPUsed -= MemoryUsage(true,gmt_VidMem);
	#endif
	InvalidateVidMem();				// mark vidmem as not valid and check renderstate
	m_pVidMemSurf = NULL;
	m_Flags &= ~tFlag_InAGP;
}

void CTexInfo::NewAGPLimit()
{
#ifdef LAB_ONLY
	// The artificial ceiling for AGP memory has been changed so free AGP surfaces if required
	for( CTexInfo *pTexInfo = FirstTexture(); pTexInfo && ( AGPUsed > gTestAGPLimit ); pTexInfo = pTexInfo->NextTexture() )
		if( ( pTexInfo->m_Flags & tFlag_InAGP ) && pTexInfo->m_pVidMemSurf )
			pTexInfo->FreeVidMem();
	// if we are shrinking textures already, rebalance by applying a new local limit
	if( ScaleShift )
		NewLocalLimit();
#endif
	;
}

void CTexInfo::FreeDummyTextures()
{
	for( int i=0; i<NumDummyTextures; i++ )
		wRelease( pDummyTex[i] );
	NumDummyTextures = 0;
}

void CTexInfo::NewLocalLimit()
{
#ifdef LAB_ONLY

	if( NumDummyTextures )
		ScaleShift=0;	// we may have more room now, eliminate scale factor

	// Free any artifical textures we already had
	FreeDummyTextures();

	// The artificial limit for local vidmem has been changed
	// Dump *all* surfaces from vidmem
	PurgeTextures();
	DisplayBackBuffer();
	DisplayBackBuffer();

	// Now calculate the number of 256x256x16bpp textures to allocate from vidmem

	DDSCAPS2 ddscaps;
	DWORD TotalLocalMemory, Junk;
	//DWORD TotalAGPMemory;
	memset( &ddscaps, 0, sizeof(ddscaps) );
	ddscaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM | DDSCAPS_TEXTURE;
	wGetAvailableVidMem( DDobject, &ddscaps, &TotalLocalMemory, &Junk );
	//ddscaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_NONLOCALVIDMEM | DDSCAPS_TEXTURE;
	//wGetAvailableVidMem( DDobject, &ddscaps, &TotalAGPMemory, &Junk );

	// PAUSE(("Vidmem: Total %d Available %d\n",TotalLocalMemory,Junk));


	DWORD MemToFill = (~gTestVIDLimit && gTestVIDLimit)?(TotalLocalMemory - gTestVIDLimit):0;
	NumDummyTextures = MemToFill >> ( 8+8+1 );

	// Create a descriptor for the new textures

	DDSURFACEDESC2 ddsd;
	memcpy( &ddsd,&TextureDesc[0][0][0],sizeof(ddsd));
	ddsd.dwSize					= sizeof(DDSURFACEDESC2);
	ddsd.dwFlags				= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT ;
	ddsd.ddsCaps.dwCaps			= DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;
	ddsd.ddsCaps.dwCaps2		= 0;
	ddsd.dwHeight				= 256;
	ddsd.dwWidth				= 256;

	// Create the dummy surfaces
	for( int i=0; i<NumDummyTextures; i++ )
	{
		AllowFail=TRUE;
		HRESULT result=wCreateSurface( CurrentDDobject, &ddsd, &pDummyTex[i], NULL );
		AllowFail=FALSE;
		if( FAILED(result) )
		{
			// Failed creating dummy textures - e.g. on a huge video card
			// back off 2MB worth  (we've succesfully created i textures already)
			int NumToReverse = (2<<20) >> (8+8+1);
			if( NumToReverse > i )
				NumToReverse = i;
			NumDummyTextures = i - NumToReverse;
			for( i=i-1; i>=NumDummyTextures; i-- )
			{
				wRelease( pDummyTex[i] );
			}
			break;
		}
	}

#endif
	;
}
