#include "pch.hpp"
//===========================================================================//
// File:	 Texture Original.cpp											 //
// Project:  GameOS					                                         //
// Contents: Allocate,Reload, and Rebuild of 32bpp surface					 //
//---------------------------------------------------------------------------//
// 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 "DirectX.hpp"
#include "DXRasterizer.hpp"

// Functions defined below

IDirectDrawSurface7*	CTexInfo::GetOriginalSurf( bool populate, WORD MipMapWidth );
void					CTexInfo::AllocateOriginal();	// Allocates original 32bpp surface
void					CTexInfo::PopulateOriginal();	// Rebuilds or Reloads (assumes surface exists but is not valid)
void					CTexInfo::Reload( BYTE *pData, DWORD Size, bool Detect ); // reloads 32bpp surface from file or pData
void					CTexInfo::Rebuild();			// Calls rebuild function of app to re-populate the surface
void					CTexInfo::FreeOriginal();		// Frees the original surface (assumes it exists and is ok to free)
void					CTexInfo::DecodeImageLevel( const char *FileName, BYTE *pSourceData, DWORD Size, IDirectDrawSurface7 *pSurface );
extern HINSTANCE LibJpeg;



IDirectDrawSurface7* CTexInfo::GetOriginalSurf( bool populate, WORD MipMapWidth )
{
	if( !m_pOriginalSurf )
	{
		AllocateOriginal();

		// If the original surface was present, it is considered to be up to date,
		// since we just had to allocate it though, - rebuild or reload is required
		// unless populate wasn't specified in which case it is uninitialized

		if( populate )
			PopulateOriginal();
	}

	// now find the desired level ( if MipMapWidth is 0 then return the top level )

	return FindTextureLevel( m_pOriginalSurf, MipMapWidth );
}

// Allocates original 32bpp surface
void CTexInfo::AllocateOriginal()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::AllocateOriginal %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	gosASSERT( !m_pOriginalSurf );

	DDSURFACEDESC2 ddsd;
 
	if( ( MostRecentOriginal.m_pOriginalSurf ) &&
		( m_nWidth == MostRecentOriginal.m_nWidth ) &&
		( m_nHeight == MostRecentOriginal.m_nHeight ) &&
		!( ( m_Hints ^ MostRecentOriginal.m_Hints ) & ( gosHint_UserMipMaps ) )	)
	{
		// Reuse the most recent original:
		m_pOriginalSurf = MostRecentOriginal.m_pOriginalSurf;
		MostRecentOriginal.m_pOriginalSurf = NULL;
		return;
	}


	memset( &ddsd, 0, sizeof(ddsd) );
	ddsd.dwSize							= sizeof(ddsd);

	if( m_Hints & gosHint_UserMipMaps )
	{
		ddsd.dwFlags					= DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT;
		ddsd.ddsCaps.dwCaps				= DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;
		ddsd.dwMipMapCount				= MipMapLevels();
	}
	else
	{
		ddsd.dwFlags					= DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
		ddsd.ddsCaps.dwCaps				= DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;
	}

	ddsd.dwWidth							= m_nWidth;
	ddsd.dwHeight							= m_nHeight;
	ddsd.ddpfPixelFormat.dwSize				= sizeof(DDPIXELFORMAT);
	ddsd.ddpfPixelFormat.dwFlags			= DDPF_RGB;
	ddsd.ddpfPixelFormat.dwRGBBitCount		= 32;
	ddsd.ddpfPixelFormat.dwRGBAlphaBitMask	= 0xff000000;
	ddsd.ddpfPixelFormat.dwRBitMask			= 0x00ff0000;
	ddsd.ddpfPixelFormat.dwGBitMask			= 0x0000ff00;
	ddsd.ddpfPixelFormat.dwBBitMask			= 0x000000ff;
//
// Create system memory surface and record memory usage
//
	wCreateSurface( DDobject, &ddsd, &m_pOriginalSurf, NULL );
	gos_ChangeHeapSize( Heap_Texture32, MemoryUsage( false, gmt_Original ), 1 );

}

void CTexInfo::PopulateOriginal()
{
	LOG_BLOCK("CTexInfo::PopulateOriginal");
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::PopulateOriginal %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));
	if( m_Hints & gosHint_ReloadFromDisk )
		Reload( NULL, 0, false );
	else if ( m_pRebuild )
		Rebuild();
	//else
	//	STOP(("Trying to rebuild an original that should not have been lost !?"));
}

void CTexInfo::Reload( BYTE *pData, DWORD Size, bool Detect )
{
	LOG_BLOCK("CTexInfo::Reload");
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::Reload %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));
	BYTE *pSourceImage;

	// Read source file if pData not passed in
	if( pData )
		pSourceImage = pData;
	else
	{
		LOG_BLOCK("CTexInfo::Reload GetFile()");
		gos_PushCurrentHeap( Heap_TextureOther );
		gos_GetFile( m_pName, &pSourceImage, &Size );
		gos_PopCurrentHeap();
	}

	// Fill the top (or only) level from the memory image
	DecodeImageLevel( m_pName, pSourceImage, Size, m_pOriginalSurf );

	// Release image of file if allocated here
	if( !pData && !(m_Hints & gosHint_ROMFile) )
		gos_Free( pSourceImage );

	DDSURFACEDESC2 ddsd;
	memset( &ddsd, 0, sizeof(ddsd) );
	ddsd.dwSize = sizeof(ddsd);
	bool locked = false;

	// if the format is "detect" then scan the surface for transparency
	if( Detect )
	{
		locked = true;
		wLock( m_pOriginalSurf, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );

		BYTE *pRow = (BYTE *)(ddsd.lpSurface);

		if( ( MipFilter() == 1 ) || ( Hints() & gosHint_DisableMipmap ) )
		{
			// for point filter or non-mipmap, allow keyed or full-alpha

			for( int y=0; (y<m_nHeight) && (Format() != gos_Texture_Alpha); y++ )
			{
				BYTE *pAlpha = pRow+3;
				pRow += ddsd.lPitch;
				for( int x=0 ; x<m_nWidth; x++, pAlpha += 4 )
				{
					BYTE alpha = (BYTE)((~*pAlpha) & 0xf0);		// trim bottom 4 bits to ignore noise
					if( alpha )
					{
						// Not totally opaque
						if( alpha != 0xf0 )
						{
							// Full-alpha surface, - stop searching
							SetFormat(gos_Texture_Alpha);
							break;
						}
						SetFormat(gos_Texture_Keyed);
					}
				}
			}
		}
		else
		{
			// for mipmaps with box filtering, disable keyed (applies to "detect" only)
			DWORD pixel = ~0L;
			for( int y=0; y<m_nHeight; y++ )
			{
				DWORD *pPixel = (DWORD *)pRow;
				pRow += ddsd.lPitch;
				for( int x=0 ; x<m_nWidth; x++ )
					pixel &= *pPixel++;
				if( 0xf0000000 & ~pixel )	// ignore bottom 4 bits of alpha
				{
					// Not totally opaque
					// Full-alpha surface, - stop searching
					SetFormat(gos_Texture_Alpha);
					break;
				}
			}
		}
	}
		
	// If user requested force_alpha on a solid texture then convert blue to alpha
	// ... and set the color to white

	if( ( Hints() & gosHint_ForceAlpha ) && ( Format() == gos_Texture_Solid ) )
	{
		if( !locked )
		{
			locked = true;
			wLock( m_pOriginalSurf, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );
		}

		BYTE *pRow = (BYTE *)(ddsd.lpSurface);
		for( int y=0; y<m_nHeight; y++, pRow += ddsd.lPitch )
		{
			DWORD *pPix = (DWORD *)pRow;
			for( int x=0 ; x<m_nWidth; x++, pPix++ )
				*pPix = (*pRow<<24) | 0x00ffffff;
		}
	}

	// For pink color key, scan through the surface and change pink to transparent

	if( Hints() & gosHint_PinkColorKey )
	{
		BYTE *pRow;
		DWORD lastVisible = 0x00ff00ff;	// the last visible texel;
		DWORD texel;
		int x,y;

		if( !locked )
		{
			locked = true;
			wLock( m_pOriginalSurf, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );
		}

		if( Detect )
            SetFormat(gos_Texture_Keyed);
		
		// First, - find the first visible pixel and use that for the first transparent pixels

		pRow = (BYTE *)(ddsd.lpSurface);
		for( y=0; (y<m_nHeight)&&(lastVisible == 0x00ff00ff); y++, pRow += ddsd.lPitch )
		{
			DWORD *pPix = (DWORD *)pRow;
			for( x=0; x<m_nWidth; x++)
			{
				lastVisible = *pPix++ & 0x00ffffff;
				if( ( lastVisible & 0x00ffffff ) != 0x00ff00ff )
					break;
			}
		}

		pRow = (BYTE *)(ddsd.lpSurface);
		for( y=0; y<m_nHeight; y++, pRow += ddsd.lPitch )
		{
			DWORD *pPix = (DWORD *)pRow;
			int copyLeft = 0;
			for( x=0; x<m_nWidth; x++ )
			{
				texel = *pPix & 0x00ffffff;
				if( texel==0xff00ff )
				{
					*pPix=lastVisible;
					copyLeft++;
				}
				else
				{
					if( copyLeft && ( lastVisible != texel ) )
					{
						// encountered an opaque pixel to the right of translucent ones
						// .. copy a few of the new value backwards
						if( copyLeft < x )
						{
							// there are opaque pixels left of here so don't copy back too far
							copyLeft >>=1;
						}
						// copy at most four texels left of here
						if( copyLeft > 4 )
							copyLeft = 4;
						pPix -= copyLeft;
						while( copyLeft-- )
							*pPix++ = texel;
					}
					copyLeft = 0;
					lastVisible = texel;
					*pPix = texel | 0xff000000;
				}
				pPix++;
			}
		}
	}

	if( locked )
		wUnlock( m_pOriginalSurf, NULL );

	// Load subsequent Mip levels if user mipmaps are stored on disk

	if( Hints() & gosHint_UserMipMaps )
	{
		char LevelName[256];
		DDSCAPS2 ddsCaps;
		IDirectDrawSurface7 *pSurface = m_pOriginalSurf;

		strcpy( LevelName, m_pName );
		char *pLevelChar = &LevelName[strlen(LevelName)-5];
		if( ( *pLevelChar < '0' ) || ( *pLevelChar > '9' ) )
			STOP(( " Must have 0..9 at end of filename '%s' for user loaded mipmaps", LevelName ));

		memset( (void *)&ddsCaps, 0, sizeof(ddsCaps) );
		ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;

		for( int level = 1; level < MipMapLevels(); level++ )
		{
			(*pLevelChar)++;	// "path/File0.bmp", "path/File1.bmp",...
			// Find the next target mipmap level
			wGetAttachedSurface( pSurface, &ddsCaps, &pSurface );
			wRelease( pSurface );
			// Read the image from disk
			gos_PushCurrentHeap( Heap_TextureOther );
			gos_GetFile( LevelName, &pSourceImage, &Size );
			gos_PopCurrentHeap();
			// Decode into surface
			DecodeImageLevel( LevelName, pSourceImage, Size, pSurface );
			if( !(m_Hints & gosHint_ROMFile) )
			{
				// Free the intermediate allocation
				gos_Free( pSourceImage );
			}
		}
	}
}

void CTexInfo::Rebuild()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::Rebuild %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));
	m_pRebuild( TEXPTR_TO_HANDLE(this), m_pInstance );
}


void CTexInfo::FreeOriginal()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::FreeOriginal %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));
	gosASSERT( m_pOriginalSurf );
	gosASSERT(!( m_Flags & tFlag_Locked ));

	if( m_pOriginalSurf == m_pSysMemSurf )
	{
		// the surfaces were cloned ( original == sysmem ) but since the original is being freed,
		// the memory allocation now counts against sysmem
		gos_ChangeHeapSize( Heap_Texture32, - MemoryUsage( false, gmt_Original ), 1 );
		gos_ChangeHeapSize( Heap_Texture16, MemoryUsage( false, gmt_SysMem ), 1 );
		m_pOriginalSurf = NULL;
		return;
	} 

	if( this != &MostRecentOriginal )
	{
		if( ( !MostRecentOriginal.m_pOriginalSurf ) ||
			( m_nWidth != MostRecentOriginal.m_nWidth ) ||
			( m_nHeight != MostRecentOriginal.m_nHeight ) ||
			( ( m_Hints ^ MostRecentOriginal.m_Hints ) & ( gosHint_UserMipMaps ) ) )
		{
			// we want to save this surface for later re-use
			if( MostRecentOriginal.m_pOriginalSurf )
				MostRecentOriginal.FreeOriginal();
			memcpy( (void *)&MostRecentOriginal, (void *)this, sizeof(*this) );
			MostRecentOriginal.m_pName = "MostRecentOriginal";
			m_pOriginalSurf = NULL;
			return;
		}
	}

	// change memory usage
	gos_ChangeHeapSize( Heap_Texture32, - MemoryUsage( false, gmt_Original ), 1 );
	wRelease( m_pOriginalSurf );
	m_pOriginalSurf = NULL;
}

void CTexInfo::DecodeImageLevel( const char *FileName, BYTE *pSourceData, DWORD Size, IDirectDrawSurface7 *pSurface )
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::DecodeImageLevel %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));
	DWORD Width = 0;
	DWORD Height = 0;

	// TBD:
	// For now, we'll use the existing functions to load into a new (temp) DDraw surface,
	// blt the result to the desired target, and release the temp surface

	const char *pExt = FileName + strlen(FileName) - 4;
	if( 0==stricmp( pExt, ".bmp" ) )
		DecodeBMP( FileName, pSourceData, Size, &Width, &Height, 1, pSurface );
	else if( LibJpeg && 0==stricmp( pExt, ".jpg" ) )
		DecodeJPG( FileName, pSourceData, Size, &Width, &Height, 1, pSurface );
	#ifdef USEPNG
	else if( 0==stricmp( pExt, ".png" ) )
		DecodePNG( FileName, pSourceData, Size, &Width, &Height, 1, pSurface );
	#endif
	else if( 0==stricmp( pExt, ".tga" ) )
		DecodeTGA( FileName, pSourceData, Size, &Width, &Height, 1, pSurface );
	else
		STOP(("Error: filename suffix is invald %s",FileName));

}
