#include "pch.hpp"
//===========================================================================//
// File:	 Texture Manager.cpp											 //
// Project:  GameOS					                                         //
// Contents: Texture Manager - Global Functions								 //
//---------------------------------------------------------------------------//
// 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 "MemoryManager.hpp"
#include "3DRasterizer.hpp"
#include "DXRasterizer.hpp"
#include "DirectX.hpp"
#include "Windows.hpp"

// Functions defined below
void CTexInfo::InitializeTextureManager(); // System startup
void CTexInfo::DestroyTextureManager(); // System shutdown
void CTexInfo::PreloadTextures(); // Once per frame, upload any "preload" textures to VRAM/AGP
bool CTexInfo::RecreateHeaps(); // Startup & Mode changes: initializes texture types, calls ReleaseTextureHeaps()
void CTexInfo::ReleaseTextures( bool ReleaseSysmem ); // releases vram and optionally sysmem copies
int MipLevelsRequired(WORD Width, WORD Height); // number of levels for a mipmap of these dimensions


// Instantiate & Pre-initialize static variables for CTexInfo
bool		CTexInfo::Initialized = false;		// Texture manager has been initialized
CTexInfo **	CTexInfo::PreloadList = NULL;		// List of unique textures to preload
int			CTexInfo::NumPreload = 0;			// Number of textures in PreloadList
CTexInfo *	CTexInfo::TexInfo = NULL;			// Array of CTexInfo for all textures
CTexInfo *	CTexInfo::pFirstFreeTexture = NULL;	// Free linked list (using m_NextOffest)
CTexInfo *	CTexInfo::pFirstUsedTexture = NULL;	// Used linked list (using m_NextOffest)
DWORD		CTexInfo::FrameNo = 0;				// Current Frame number (incremented by GetTexturesUsed())
DWORD		CTexInfo::TimeNo = 0;				// Count of setrenderstate calls
#ifdef LAB_ONLY
DWORD		CTexInfo::AGPUsed = 0;				// Bytes of AGP used (so we can simulate AGP memory limits)
#endif
int			CTexInfo::ScaleShift=0;				// 1=/2, 2=/4,... for image shrinking when out of memory
int			CTexInfo::NumVidTextures=0;			// The number of textures currently in video+AGP
CTexInfo	CTexInfo::MostRecentOriginal;		// A recently freed "original" - ony m_pOriginalSurf is valid
CTexInfo	CTexInfo::MostRecentSysMem;			// A recently freed "sysmem" - ony m_pSysMemSurf is valid

//
// Statistics
//
DWORD TexturesUsed=0;
DWORD TextureKUsed=0;
DWORD TextureVKUsed=0;
DWORD TextureNSVKUsed=0;
DWORD AlphaTextureKUsed=0;
DWORD TexturesSwapped=0;
DWORD TextureBytesPaged=0;
DWORD TextureBytesPaged1=0;
DWORD TexturesDestroyed=0;
DWORD TotalTexMemoryAllocated=0;
DWORD TexturesChanged=0;
DWORD VidMemUsed=0;				// Number of textures actually allocated in video memory
DWORD TexturesBumped=0;
float TextureAGPMegs=0;
float TextureLocalMegs=0;
DWORD TexturesLoaded=0;
DWORD TexturesCreated=0;
extern DWORD TextureShift;

//
// GOS Internal textures
//
DWORD			FontTexture=0;	// The font and mouse pointer
DWORD			ChessTexture=0;	// The chess texture
DWORD			MipColorTexture=0;	// The mipmap color texture

const DWORD MipColor[9] =
{
	0xff00ff00,		// green	1x1		=level+5
	0xffffff00,		// yellow	2x2		=level+4
	0xffff8000,		// orange	4x4		=level+3
	0xffff0000,		// red		8x8		=level+2
	0xff808000,		// brown	16x16	=level+1
	0xff000000,		// black	32x32	=level+0
	0xffffffff,		// white	64x64	=level-1
	0xff808080,		// grey		128x128	=level-2
	0xffff00ff,		// violet	256x256	=level-3
};

// From Texture Format.cpp:
extern DWORD ValidTextures;
HRESULT CALLBACK EnumProc( DDPIXELFORMAT* ddpfPixelFormat, LPVOID lpContext );

//
// InitializeTextureManager - called once at the start of GameOS (after D3D setup) to init system memory textures
//
void CTexInfo::InitializeTextureManager()
{
// Debugging information
	SPEW(( GROUP_TEXTURE, "InitTextureManager()" ));
	if( Initialized )
		STOP(("InitializeTextureManager called twice!"));
// Initialize the static/global variables of the texture manager
	PreloadList = (CTexInfo **)gos_Malloc( MaximumTextures * sizeof(CTexInfo *), Heap_TextureOther );
	NumPreload = 0;
	TexInfo=(CTexInfo*)gos_Malloc( MaximumTextures * sizeof(CTexInfo), Heap_TextureOther );
	memset( TexInfo, 0, MaximumTextures * sizeof(CTexInfo) );
	memset( &MostRecentOriginal, 0, sizeof(MostRecentOriginal) );
	memset( &MostRecentSysMem, 0, sizeof(MostRecentSysMem) );
	pFirstUsedTexture = NULL;
// Set up the linked list of free texture info structures
	pFirstFreeTexture = TexInfo;
	for( int i=0; i<MaximumTextures-1; i++ )
		TexInfo[i].m_NextOffset=1;
// Texture manager can now be used
	Initialized=true;
//	SolidFormat=-1;	
//	KeyedFormat=-1;
//	AlphaFormat=-1;
//	OldGammaSetting=1.0f;
//	OldSolidFormat=-2;

//
// Make sure there is no 'current texture'
//
	if( RenderStates[gos_State_Texture] )
	{
		RenderStates[gos_State_Texture]=0;
		UpdatedState[gos_State_Texture]=1;
		DirtyStates=1;
	}
}



void CTexInfo::DestroyTextureManager()
{
	SPEW(( GROUP_TEXTURE, "DestroyTextureManager()" ));

	if( Initialized )
	{
		while( pFirstUsedTexture )
			pFirstUsedTexture->Free();
		FreeDummyTextures();
		// Free all textures
		if( MostRecentOriginal.m_pOriginalSurf )
			MostRecentOriginal.FreeOriginal();
		if( MostRecentSysMem.m_pSysMemSurf )
			MostRecentSysMem.FreeSysMem();
		// Free memory allocations
		gos_Free( (void *)TexInfo );
		gos_Free( (void *)PreloadList );
		// Dump handles to preloaded textures
		FontTexture=0;	// The font and mouse pointer
		ChessTexture=0;	// The chess texture
		MipColorTexture=0;	// The mipmap color texture
		// Mark texture manager as uninitialized
		Initialized=false;
	}

	SPEW(( GROUP_TEXTURE, "DestroyTextureManager() - Finished" ));
}

void CTexInfo::PreloadTextures()
{
	while( NumPreload )
	{
		CTexInfo *ptr = PreloadList[ --NumPreload ];
		ptr->GetD3DTextureHandle();
		ptr->m_Flags &= ~tFlag_Preload;
	}
}


//
// Add an empty texture to the list of 'free' ones
//
bool AddEmptyTexture( int Format, int Size )
{
	bool NotEnoughRoom=0;
	
	DDSURFACEDESC2 ddsd;
	IDirectDrawSurface7* m_pVidMemSurf;

	memset( &ddsd, 0, sizeof(ddsd) );
	memcpy( &ddsd.ddpfPixelFormat, &TextureDesc[Format][0][0].ddpfPixelFormat, sizeof(ddsd.ddpfPixelFormat) );

	ddsd.dwSize					= sizeof(ddsd);
	ddsd.dwFlags				= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | (HasMipMap ? DDSD_MIPMAPCOUNT : 0);
	ddsd.ddsCaps.dwCaps			= DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM | (HasMipMap ? (DDSCAPS_MIPMAP | DDSCAPS_COMPLEX) : 0 );
	ddsd.dwMipMapCount			= HasMipMap ? 1 : 0;
	ddsd.dwHeight				= Size;
	ddsd.dwWidth				= Size;

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

	if( FAILED(result) )
		NotEnoughRoom=1;

	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;

	return NotEnoughRoom;
}


// This is called at startup and whenever the display mode changes
//
// Basic sequence:
//
//		Determine available texture types in new mode by calling wEnumTextureFormats()
//		Release all vidmem textures & if formats changed, release system memory versions also
//		Recreates (reload &/or rebuild) all of the system memory versions if they were released
//		Creates the font, chessboard, and colored mipmap texture
//		Returns:  true if valid texture modes exist, false otherwise

bool CTexInfo::RecreateHeaps()
{
//
// On Riva 128 limit maximum video memory textures to 500, all other cards 1000
//
	if( Compatibility3D&Limit500VidTextures )
		MaximumVidTextures=500;
	else
		MaximumVidTextures=1000;

	// Analyze the texture formats now available
	AnalyzeTextureFormats();

	// Release all textures
	ReleaseTextures(true);

	ScaleShift = 0;	// reset the scale factor

// Establish the chessboard, font, and colored mipmap textures used by GOS

//
// Load in default 128*128 font texture used by GameOS debugger and 'video mode display'
//
	#ifdef LAB_ONLY
	if( 0==FontTexture )
	{
		char tempPath[MAX_PATH];

		strcpy( tempPath, AssetsDirectory1 );
		strcat( tempPath, "\\assets\\Graphics\\font.tga" );

		if( !gos_DoesFileExist(tempPath) )
		{
			strcpy( tempPath, AssetsDirectory2 );
			strcat( tempPath, "\\font.tga" );

			if( !gos_DoesFileExist(tempPath) )
				STOP(( "Cannot find font.tga in %s\\assets\\graphics or %s",AssetsDirectory1,AssetsDirectory2 ));
		}

		FontTexture = gos_NewTextureFromFile( gos_Texture_Keyed, tempPath, gosHint_DontShrink|gosHint_DisableMipmap );
		TEXHANDLE_TO_PTR(FontTexture)->SetSpecial();
	}
//
// Create chessboard texture (used for debugging)
//
	if( 0==ChessTexture )
	{
		ChessTexture=gos_NewEmptyTexture( gos_Texture_Solid, "Chessboard Texture", 16, gosHint_DontShrink );
		TEXHANDLE_TO_PTR(ChessTexture)->SetSpecial();
		TEXTUREPTR tp;
		gos_LockTexture( ChessTexture, 0, 0, &tp );
		for( int y0=0; y0<16; y0++ )
		{
			DWORD pat0= y0&2 ? -1 : 0xff000000;
			DWORD pat1= (pat0==-1) ? 0xff000000 : -1;
			*(tp.pTexture+ 0)=pat0;
			*(tp.pTexture+ 1)=pat0;
			*(tp.pTexture+ 2)=pat1;
			*(tp.pTexture+ 3)=pat1;
			*(tp.pTexture+ 4)=pat0;
			*(tp.pTexture+ 5)=pat0;
			*(tp.pTexture+ 6)=pat1;
			*(tp.pTexture+ 7)=pat1;
			*(tp.pTexture+ 8)=pat0;
			*(tp.pTexture+ 9)=pat0;
			*(tp.pTexture+10)=pat1;
			*(tp.pTexture+11)=pat1;
			*(tp.pTexture+12)=pat0;
			*(tp.pTexture+13)=pat0;
			*(tp.pTexture+14)=pat1;
			*(tp.pTexture+15)=pat1;
			tp.pTexture+=tp.Pitch;
		}
		gos_UnLockTexture( ChessTexture );
	}
//
// Create colored mipmap texture (used for debugging)
//
	if( 0==MipColorTexture )
	{
		MipColorTexture=gos_NewEmptyTexture( gos_Texture_Solid, "MipMap Color Texture", 256, gosHint_DontShrink|gosHint_UserMipMaps );
		TEXHANDLE_TO_PTR(MipColorTexture)->SetSpecial();
		TEXTUREPTR tp;
		for( int level=8; level>=5; level-- )
		{
			int width=1<<level;
			DWORD color = MipColor[level];
			gos_LockTexture( MipColorTexture, width, 0, &tp );
			for( int y0=0; y0<width; y0++ )
			{
				for( int x0=0; x0<width; x0++ )
					*tp.pTexture++ = color;
			}
			gos_UnLockTexture( MipColorTexture );
			if( !HasMipMap )
				break;
		}
	}
	#endif
//
// On cards that cannot delete textures, create a texture pool to use
//
	if( Compatibility3D&DisableTextureDeletes && 0==pFreeVidMemTextures )
	{
		bool NotEnoughRoom=0;
		
		for( int Size=256; Size>8 && !NotEnoughRoom; Size>>=1 )
		{
			for( int Format=0; Format<3 && !NotEnoughRoom; Format++ )
			{
				NotEnoughRoom=AddEmptyTexture( Format, Size );
			}
		}
	
		if( NotEnoughRoom )
			return 0;

		AddEmptyTexture( gos_Texture_Alpha, 16 );
		AddEmptyTexture( gos_Texture_Alpha, 16 );
		AddEmptyTexture( gos_Texture_Alpha, 16 );
		AddEmptyTexture( gos_Texture_Alpha, 16 );
		AddEmptyTexture( gos_Texture_Alpha, 32 );
		AddEmptyTexture( gos_Texture_Alpha, 32 );
		AddEmptyTexture( gos_Texture_Alpha, 32 );			// Make sure enough spare texture's for TTF fonts!
	}

	return (ValidTextures>0);
}

// Called whenever display mode changes
// Releases all vidmem textures, and if ReleaseSysmem flag is set releases
// converted system memory versions also

void CTexInfo::ReleaseTextures( bool ReleaseSysmem )
{
	CTexInfo *pTexInfo;

	for( pTexInfo = FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
	{
		if( pTexInfo->m_pVidMemSurf )
			pTexInfo->FreeVidMem();
		if( ReleaseSysmem && pTexInfo->m_pSysMemSurf )
			pTexInfo->FreeSysMem();
	}

	FreeDummyTextures();
//
// Release any cached textures
//
	if( MostRecentOriginal.m_pOriginalSurf )
		MostRecentOriginal.FreeOriginal();
	if( MostRecentSysMem.m_pSysMemSurf )
		MostRecentSysMem.FreeSysMem();

#ifdef LAB_ONLY
	if( NumVidTextures != 0 )
	{
		STOP(("NumVidTextures should be 0 !"));
	}
#endif
}


// Finds the level of the desired width from a mipmap chain
// if MipMapWidth is 0 then this returns the top level
IDirectDrawSurface7 *CTexInfo::FindTextureLevel( IDirectDrawSurface7 *pSurf, WORD MipMapWidth )
{
	if( MipMapWidth )
	{
		DDSCAPS2 ddsCaps;
		memset( &ddsCaps, 0, sizeof(ddsCaps) );
		ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;
		for( int level=0; ( m_nWidth >> level ) != MipMapWidth; level++ )
		{
			wGetAttachedSurface( pSurf, &ddsCaps, &pSurf );
			wRelease( pSurf );
		}
	}
	return pSurf;
}

//
// Levels required for a mipmap
//
int MipLevelsRequired(WORD Width, WORD Height)
{
	// Based on smallest level
	if( Height > Width )
		Height = Width;
	int levels = 0;
	while( Height > 16 )
	{
		Height >>=4;
		levels += 4;
	}
	while( Height )
	{
		Height >>=1;
		levels += 1;
	}
	return levels-4;
}

DWORD GetTexturesUsed()
{
	CTexInfo *pTexInfo;

	DWORD Used = 0;

	for( pTexInfo = CTexInfo::FirstTexture(); pTexInfo; pTexInfo = pTexInfo->NextTexture() )
	{
		TotalTexMemoryAllocated += (pTexInfo->MemoryUsage(true,gmt_Original)+pTexInfo->MemoryUsage(true,gmt_SysMem))>>10;

		if( pTexInfo->Used() )
		{
			Used++;

			TextureKUsed += pTexInfo->MemoryUsage(true,gmt_SysMem);

			if( pTexInfo->HasAlpha() )
				AlphaTextureKUsed += pTexInfo->MemoryUsage(true,gmt_SysMem);

			DWORD Vidmem=pTexInfo->MemoryUsage(true,gmt_VidMem);
			
			TextureVKUsed += Vidmem;

			if( pTexInfo->Hints() & gosHint_DontShrink )
				TextureNSVKUsed +=Vidmem;
		}
	}
	// Adjust to KB
	TextureKUsed >>=10;
	AlphaTextureKUsed >>=10;
	TextureVKUsed >>=10;
	TextureNSVKUsed >>=10;

	TextureShift=CTexInfo::GlobalScaleShift();

	return Used;
}

#ifdef LAB_ONLY

// Globals used for texture manager event log
char CTexInfo::LogString[MaximumTextureLogs][256];
int CTexInfo::LogCount=0;			// 0..MaximumTextureLogs
int CTexInfo::LogNext=0;			// index of next string to write to in circular log
bool CTexInfo::Logging=false;


void CTexInfo::LogTextureEvent( char *description )
{
	if( Logging )
	{
		if( LogCount < MaximumTextureLogs )
			LogCount++;
		sprintf( LogString[LogNext], "%6d %08x %4dx%4d %s %s",
			FrameNo, this, m_nWidth, m_nHeight, description, m_pName );
		LogNext = ( LogNext + 1 ) % MaximumTextureLogs;
	}
}

#endif