#include "pch.hpp"
//===========================================================================//
// File:	 Texture Create.cpp												 //
// Project:  GameOS					                                         //
// Contents: Texture Structure Allocation / Destruction						 //
//---------------------------------------------------------------------------//
// 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"

// Functions defined below

CTexInfo *CTexInfo::Allocate();
void CTexInfo::Initialize( gos_TextureFormat Format, const char* FileName, BYTE* pBitmap, DWORD Size, WORD Width, WORD Height, DWORD Hints, gos_RebuildFunction pFunc, void *pInstance );
void CTexInfo::Free();
extern HGOSHEAP Heap_TextureOther;
extern HINSTANCE LibJpeg;

CTexInfo *CTexInfo::Allocate()
{
	LOG_BLOCK("CTexInfo::Allocate");

	if( !pFirstFreeTexture )
		STOP(("Too many textures created!"));
	// Allocate the lowest available structure and unlink from free chain
	CTexInfo *pTexInfo = pFirstFreeTexture;
	pFirstFreeTexture = pTexInfo->NextTexture();

	// Clear the structure contents completely
	memset( (void *)pTexInfo, 0, sizeof(*pTexInfo) );

	// Link into used chain
	if( pTexInfo == TexInfo )
	{
		// Allocating TexInfo[0]...
		pTexInfo->m_NextOffset = (WORD)( (pFirstUsedTexture)?(pFirstUsedTexture - pTexInfo):(0) );
		pFirstUsedTexture = pTexInfo;
	}
	else
	{
		// Allocating TexInfo[n] so TexInfo[n-1] *must* be in use already
		CTexInfo *pPrev = pTexInfo-1;
		pTexInfo->m_NextOffset = pPrev->m_NextOffset;
		if( pTexInfo->m_NextOffset )
			pTexInfo->m_NextOffset--;
		pPrev->m_NextOffset = 1;
	}
#ifdef LAB_ONLY
	TexturesLoaded++;
	TexturesCreated++;
	pTexInfo->m_Flags = tFlag_Valid;	// keep CheckChain() happy
	CheckChain();
#endif

	return pTexInfo;
}

WORD RoundUpSide( WORD Side )
{
	if( Side & ( Side - 1 ) )
	{
		while( Side & ( Side - 1 ) )
			Side &= ( Side - 1 );	// strip off least significant set bit

		Side <<=1;	// double the result
	}
	return Side;
}

void CTexInfo::Initialize( gos_TextureFormat Format, const char* FileName, BYTE* pBitmap, DWORD Size, WORD Width, WORD Height, DWORD Hints, gos_RebuildFunction pFunc, void *pInstance )
{
	LOG_BLOCK("CTexInfo::Initialize");
	BYTE *pSourceImage = pBitmap;

	// If creating the texture from a file, load it into memory
	if( !pSourceImage && !Width )
	{
		LOG_BLOCK("CTexInfo::Initialize GetFile()");
		gos_PushCurrentHeap( Heap_TextureOther );
		gos_GetFile( FileName, &pSourceImage, &Size );
		gos_PopCurrentHeap();
	}

	// If loading the image from memory or a file, determine the image dimensions
	if( !Width )
	{

		DWORD dwWidth=0;
		DWORD dwHeight=0;
		const char *pExt = FileName + strlen(FileName) - 4;
		if( 0==stricmp( pExt, ".bmp" ) )
			DecodeBMPDimensions( FileName, pSourceImage, Size, &dwWidth, &dwHeight );
		else if( LibJpeg && 0==stricmp( pExt, ".jpg" ) )
			DecodeJPGDimensions( FileName, pSourceImage, Size, &dwWidth, &dwHeight );
		#ifdef USEPNG
		else if( 0==stricmp( pExt, ".png" ) )
			DecodePNGDimensions( FileName, pSourceImage, Size, &dwWidth, &dwHeight );
		#endif
		else if( 0==stricmp( pExt, ".tga" ) )
			DecodeTGADimensions( FileName, pSourceImage, Size, &dwWidth, &dwHeight );
		else
			STOP(( "Decoding file with invalid suffix %s", FileName ));

		Width = (WORD)dwWidth;
		Height = (WORD)dwHeight;
	}

    if( Width < 16 )
        Width = 16;

    if( Height < 16 )
        Height = 16;

	// Round up dimensions to the nearest powers of two

	Width = RoundUpSide(Width);
	Height = RoundUpSide(Height);

	// Populate the member variables
	m_nWidth = Width;
	m_nHeight = Height;
	m_Hints = (SHORT)Hints;
	m_Flags = tFlag_Valid;
	m_pRebuild = pFunc;
	m_pInstance = pInstance;
	SetName( FileName?FileName:"User created texture" );

	SPEW((GROUP_TEXTURE,"0x%08x Init %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));


	// Store the requested format
	// Note that the Reload() will examine the data and change the format if it was "detect"
	bool WasDetect = ( Format == gos_Texture_Detect );
	if( WasDetect )
	{
		Format = gos_Texture_Solid;	// until determined otherwise by Reload()
		m_Flags |= tFlag_Detect;
	}

	SetFormat( Format );

	if( WasDetect || (pSourceImage && !(m_Hints & gosHint_ReloadFromDisk) ) )
	{
		// we need to create the surface now
		SPEW((GROUP_TEXTURE,"0x%08x Init-alloc %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

		// Allocate the original surface (or chain if UserMipMaps hint is set)
		AllocateOriginal();


		if( !pSourceImage )
		{

			// Creating an empty 32bpp texture
			// fill the empty texture (top level of mipmap only) with 0's
			DDSURFACEDESC2 ddsd;
			ddsd.dwSize = sizeof(ddsd);
			wLock( m_pOriginalSurf, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL );
			int bytesPerRow = m_nWidth<<2;
			BYTE *pBits = (BYTE *)(ddsd.lpSurface);
			for( int y=0; y<m_nHeight; y++, pBits += bytesPerRow )
				memset( (void *)pBits, 0, bytesPerRow );
			wUnlock( m_pOriginalSurf, NULL );
		}
		else
		{

			// Load the image (or mipmap images)
			Reload( pSourceImage, Size, WasDetect );

		}

		if( m_pRebuild || (m_Hints & gosHint_ReloadFromDisk) || !pSourceImage )
		{
			SPEW((GROUP_TEXTURE,"0x%08x Init-free %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

			// save room for now,...
			if( m_pOriginalSurf )
				FreeOriginal();
			if( m_pSysMemSurf )
				FreeSysMem();
		}
	}

	if( this->Format() == gos_Texture_Keyed || this->Format() == gos_Texture_Alpha )
		m_Flags |= tFlag_Alpha;

	if( pSourceImage && !pBitmap && !(Hints & gosHint_ROMFile) )
		gos_Free( pSourceImage );
}


void CTexInfo::Free()
{

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

	CTexInfo *pPrev;


	// Free all surfaces
	if( m_pOriginalSurf )
		FreeOriginal();
	if( m_pSysMemSurf )
		FreeSysMem();
	if( m_pVidMemSurf )
		FreeVidMem();
	// Free name allocation
	if( m_pName )
	{
		gos_Free(m_pName);
		m_pName = (char *)NULL;
	}

	// Mark as available
	m_Flags = 0;
	// Detach from used chain
	if( pFirstUsedTexture == this )
	{
		pFirstUsedTexture = NextTexture();
	}
	else
	{
		WORD off = 1;
		for( pPrev = this-1; pPrev + pPrev->m_NextOffset != this ; pPrev-- )
			off++;
		if( m_NextOffset )
			pPrev->m_NextOffset = (WORD)(m_NextOffset + off);
		else
			pPrev->m_NextOffset = 0;
	}
	// Re-insert into free chain at appropriate point (i.e. used & free chains are kept in order)
	if( !pFirstFreeTexture || ( this < pFirstFreeTexture ) )
	{
		// This  is now the head of the free chain
		m_NextOffset = (WORD)(pFirstFreeTexture?(pFirstFreeTexture-this):0);
		pFirstFreeTexture = this;
	}
	else
	{
		// Scan through free chain looking for last free element which precedes this 
		for( pPrev=pFirstFreeTexture; pPrev->m_NextOffset && (pPrev+pPrev->m_NextOffset<this); pPrev+=pPrev->m_NextOffset )
		{ ; }
		// Calculate next pointer (0 if this is new end of free chain)
		m_NextOffset = (WORD)((pPrev->m_NextOffset)?(pPrev->m_NextOffset+(pPrev-this)):0);
		// Make pPrev point to this
		pPrev->m_NextOffset = (WORD)(this - pPrev);
	}
#ifdef LAB_ONLY
	TexturesDestroyed++;
	CheckChain();
#endif
}


void CTexInfo::CheckChain()
{
	int numUsed = 0;
	int numFree = 0;
	CTexInfo *pTexInfo;

	for( pTexInfo=pFirstUsedTexture; pTexInfo; pTexInfo=pTexInfo->NextTexture() )
	{
		numUsed++;
		if( ~pTexInfo->m_Flags & tFlag_Valid )
			STOP(("Inalid texture in used chain"));
		if( pTexInfo < &TexInfo[0] )
			STOP(("Used chain broken"));
		if( pTexInfo-MaximumTextures>=TexInfo )
			STOP(("Used texture chain off end of max textures"));
	}

	for( pTexInfo=pFirstFreeTexture; pTexInfo; pTexInfo=pTexInfo->NextTexture() )
	{
		numFree++;
		if( pTexInfo->m_Flags & tFlag_Valid )
			STOP(("Valid texture in free chain"));
		if( pTexInfo < &TexInfo[0] )
			STOP(("Free chain broken"));
		if( pTexInfo-MaximumTextures>=TexInfo )
			STOP(("Free texture chain off end of max textures"));
	}

	if( numUsed + numFree != MaximumTextures )
		STOP(("Free and Used Texture chains dont add up"));

}

WORD CTexInfo::ScaledHeightWidth( bool WantHeight )
{
	WORD width = m_nWidth;
	WORD height = m_nHeight;

	// work out the desired size
	if( ScaleShift && m_nWidth>16 && m_nHeight>16 )
	{
		// scaleshift	noshrink	shrink
		//		0			0			0
		//		1			0			1
		//		2			0			2
		//		3			1			2
		//		4			1			3


		if( m_Hints & gosHint_DontShrink )
		{
			if( ScaleShift > 2 )
			{
				// can't have the debugger's font being shrunk!
				if( (strlen(m_pName)<8) || 0!=stricmp( m_pName+strlen(m_pName)-8, "font.tga" ) )
				{
					width >>=1;
					height >>=1;
				}
			}
		}
		else
		{
			if( ScaleShift > 2 )
			{
				width >>= ( ScaleShift-1 );
				height >>= ( ScaleShift-1 );
			}
			else
			{
				width >>= ScaleShift;
				height >>= ScaleShift;
			}
		}
	}
	// fix extreme aspect ratios (or make square if that is required by the card)
	if( CapsDirect3D.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_SQUAREONLY )
	{
		if( width > height )
			height = width;
		else
			width = height;
	}
	else if( CapsDirect3D.dwMaxTextureAspectRatio )
	{
		if( width > height )
			while( width > height * CapsDirect3D.dwMaxTextureAspectRatio )
				height <<=1;
		if( height > width )
			while( height > width * CapsDirect3D.dwMaxTextureAspectRatio )
				width <<=1;
	}
	// clamp the dimensions to the limits
	if( WantHeight )
	{
		while( CapsDirect3D.dwMaxTextureHeight && (height > CapsDirect3D.dwMaxTextureHeight ) )
			height >>= 1;
		while( height < CapsDirect3D.dwMinTextureHeight )
			height <<= 1;
		return height;
	}
	else
	{
		while( CapsDirect3D.dwMaxTextureWidth && ( width > CapsDirect3D.dwMaxTextureWidth ) )
			width >>= 1;
		while( width < CapsDirect3D.dwMinTextureWidth )
			width <<= 1;
		return width;
	}
}

