#include "pch.hpp"
//===========================================================================//
// File:	 Texture Update.cpp												 //
// Project:  GameOS					                                         //
// Contents: Texture alteration (after creation)							 //
//---------------------------------------------------------------------------//
// 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 "DXRasterizer.hpp"
#include "3DRasterizer.hpp"
#include "DirectX.hpp"

// From ConvertPixelFormat.cpp:
extern void ConvertRect( DWORD Bpp, IDirectDrawSurface7* DestSurface, DWORD *Source, DWORD SourcePitch, DWORD Hints, DWORD left, DWORD top, DWORD width, DWORD height );
extern HGOSHEAP Heap_TextureOther;


// Functions defined below
// Rebuild / Reload / Convert / Upload as required to get a usable texture handle
DWORD CTexInfo::GetD3DTextureHandle(); // HW Rasterizer: VidMemSurf, SW Rasterizer: SysMemSurf
// Altering the surface contents of a texture
void CTexInfo::Lock( DWORD MipMapWidth, bool ReadOnly, TEXTUREPTR* TextureInfo );
void CTexInfo::Unlock();
void CTexInfo::UpdateRect( DWORD DestLeft, DWORD DestTop, DWORD *Source, DWORD SourcePitch, DWORD Width, DWORD Height );
// Various setters
void CTexInfo::SetPreload();
void CTexInfo::SetName( const char *name );
// Memory usage analysis
int CTexInfo::MemoryUsage( bool Current, EGraphicsMemType MemType); // current or required bytes

// Statistic maintained for debugging
DWORD TextureUnlocked = 0;

// Rebuild / Reload / Convert / Upload as required to get a usable texture handle
DWORD CTexInfo::GetD3DTextureHandle() // HW Rasterizer: VidMemSurf, SW Rasterizer: SysMemSurf
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::GetD3DTextureHandle %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

#ifdef LAB_ONLY
	TexturesChanged++;
#endif

	// Register that this testure has been used this frame
	SetUsed();

	// Choose the Sysmem or VidMem surface depending upon whether SW or HW rasterizer
	// (require texture to be populated by passing in populate=true)
	if( !HardwareRenderer && SysMemSurfUsable() )
		return (DWORD)GetSysMemSurf( true );
	else
		return (DWORD)GetVidMemSurf( true );
}

void CTexInfo::Lock( DWORD MipMapWidth, bool ReadOnly, TEXTUREPTR* pTextureInfo )
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::Lock %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	// Set flags
	m_Flags |= tFlag_Locked | ( ReadOnly ? 0 : tFlag_LockRW );
	// Work down mipmap chain as required to find correct level
	DDSCAPS2 ddsCaps;
	memset( &ddsCaps, 0, sizeof(ddsCaps) );
	ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;
	IDirectDrawSurface7 *pSurf = GetOriginalSurf( false );
	for( m_nLockedLevel=0; MipMapWidth && ( ( m_nWidth >> m_nLockedLevel ) != MipMapWidth ); m_nLockedLevel++ )
	{
		wGetAttachedSurface( pSurf, &ddsCaps, &pSurf );
		wRelease( pSurf );
	}
	// Lock the surface just long enough to find its address (gos lock may persist many frames)
	DDSURFACEDESC2 ddsd;
	ddsd.dwSize = sizeof(ddsd);
	wLock( pSurf, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );
	wUnlock( pSurf, NULL );
	// Fill in structure with surface description
	pTextureInfo->pTexture=(DWORD*)ddsd.lpSurface;
	pTextureInfo->Width = ddsd.dwWidth;
	pTextureInfo->Height = ddsd.dwHeight;
	pTextureInfo->Pitch = ddsd.lPitch>>2;
	pTextureInfo->Type = Format();
#if 0	//  TBD: re-insert the overrun checking.....
// Checking for over/under run's during locking?
	SysMemTexture[Handle].Info->SysMemoryPointer=(BYTE*)ddsd.lpSurface;
	SysMemTexture[Handle].Info->CopyBase=0;
	TextureInfo->pTexture=(DWORD*)ddsd.lpSurface;

	if( gTextureOverrun && MipMapWidth==0 && SysMemTexture[Handle].Info->Width>=32 )
	{
		BYTE* NewLocation=(BYTE*)VirtualAlloc( NULL, ddsd.lPitch*ddsd.dwHeight + 8192, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE );
		DWORD OldAccess;
		VirtualProtect( NewLocation, 4096, PAGE_NOACCESS, &OldAccess );
		VirtualProtect( NewLocation+4096+ddsd.lPitch*ddsd.dwHeight, 4096, PAGE_NOACCESS, &OldAccess );
		for( int t0=0; t0<ddsd.dwHeight; t0++ )
		{
			memcpy( NewLocation+4096+t0*ddsd.lPitch, (BYTE*)ddsd.lpSurface+t0*ddsd.lPitch, ddsd.lPitch );
		}
		SysMemTexture[Handle].Info->SysMemoryPointer=(BYTE*)ddsd.lpSurface;
		SysMemTexture[Handle].Info->CopyBase=NewLocation;
		TextureInfo->pTexture=(DWORD*)(NewLocation+4096);
	}
#endif
}

//
// Unlocks and updates a texture (may have to build and upload mipmaps)
//
void CTexInfo::Unlock()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::Unlock %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	#ifdef LAB_ONLY
	TextureUnlocked++;
	#endif

	// Reset locked flag(s) and mark sysmem & vidmem versions invalid if locked for R/W
	if( m_Flags & tFlag_LockRW )
	{
		m_Flags &= ~( tFlag_Locked | tFlag_LockRW | tFlag_InSysMem );
		InvalidateVidMem();
	}
	else
		m_Flags &= ~ tFlag_Locked;


#if 0	// TBD: Re-insert overrun checking
//
// Finish overrun / under run checking
//
	if( gTextureOverrun && SysMemTexture[Handle].Info->CopyBase!=0 )
	{
//
// Lock surface, return details
//
		IDirectDrawSurface7* SysMemSurface = SysMemTexture[Handle].Info->OriginalImage;
		DDSURFACEDESC2 ddsd;
		ddsd.dwSize = sizeof(ddsd);
		wLock( SysMemSurface, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );
		wUnlock( SysMemSurface, NULL );
//
// Check the memory pointer has not moved
//
		if( ddsd.lpSurface!=SysMemTexture[Handle].Info->SysMemoryPointer )
			STOP(( "The texture surface was moved!" ));
//
// Update DirectDraw surface with new data (if not read only)
//
		BYTE* NewLocation=SysMemTexture[Handle].Info->CopyBase;
		if( 0!=(SysMemTexture[Handle].Flags&tFlag_LockRW) )
		{
			for( int t0=0; t0<ddsd.dwHeight; t0++ )
			{
				memcpy( (BYTE*)ddsd.lpSurface+t0*ddsd.lPitch, NewLocation+4096+t0*ddsd.lPitch, ddsd.lPitch );
			}
		}
//
// Get Rid of copy surface
//
		VirtualFree( NewLocation, 0, MEM_RELEASE );
	}
#endif
}

//
// Convert rectangle of a non-mipmap texture to match pixel format
//
void CTexInfo::UpdateRect(  DWORD DestLeft, DWORD DestTop, DWORD *Source, DWORD SourcePitch, DWORD Width, DWORD Height )
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::UpdateRect %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	// If this is not updating the full texture, we need a fully populated system memory version
	if( DestLeft | DestTop | (Width ^ m_nWidth) | (Height ^ m_nHeight) )
	{
#ifdef LAB_ONLY
		char Buffer[100];
		if( Logging )
		{
			sprintf( Buffer, "UpdateRect(%dx%d at %d,%d)", Width, Height, DestLeft, DestTop );
			LogTextureEvent(Buffer);
			//if( ( Width == 7 ) && ( Height == 77 ) && FrameNo > 3000 )
			//	STOP(("Update 7x77"));
		}
#endif
		ConvertRect( Description()->ddpfPixelFormat.dwRGBBitCount, GetSysMemSurf(true), Source, SourcePitch, Hints(), DestLeft, DestTop, Width, Height );
	}
	else
	{
		LogTextureEvent("UpdateRect(Complete)");
		ConvertRect( Description()->ddpfPixelFormat.dwRGBBitCount, GetSysMemSurf(false), Source, SourcePitch, Hints(), DestLeft, DestTop, Width, Height );
	}

	// We don't want an invalid "original" version around...
	if( m_pOriginalSurf && !IsLocked() && ( m_pRebuild || ( m_Hints & gosHint_ReloadFromDisk ) ) )
		FreeOriginal();

	m_Flags |= tFlag_InSysMem;	// mark the system memory version as valid

	InvalidateVidMem();
}

void CTexInfo::InvalidateVidMem()
{
	m_Flags &= ~ tFlag_InVidMem;	// mark the video memory version as invalid
//
// If current texture, reload it.
//
	if( RenderStates[gos_State_Texture]==TEXPTR_TO_HANDLE(this) )
	{
		UpdatedState[gos_State_Texture]=1;
		DirtyStates=1;
	}
}

void CTexInfo::SetPreload()
{
	// Maintain flag to avoid duplication so list cant overflow
	if( !(m_Flags & tFlag_Preload) )
	{
		m_Flags |= tFlag_Preload;
		PreloadList[NumPreload++] = this;
	}
}

void CTexInfo::SetName( const char *Name )
{
	DWORD len = strlen(Name);
	if( m_pName )
		gos_Free(m_pName);
	m_pName = (char *)gos_Malloc( len+1, Heap_TextureOther );
	strcpy( m_pName, Name );
}

inline DWORD MipSizeCalc( int UseMip, DWORD Bytes )
{
	if( UseMip )
		Bytes = ( Bytes * 0x0155 ) >> 8;
	return Bytes;
}

// Determine current or required bytes
int CTexInfo::MemoryUsage( bool Current, EGraphicsMemType MemType)
{
	if( !m_nOriginalBytes )
	{
		// Haven't calculated the sizes yet, do so now and store in the CTexInfo
		m_nOriginalBytes = MipSizeCalc( m_Hints & gosHint_UserMipMaps, Area() * 4 );
		m_nSysMemBytes = MipSizeCalc( (~m_Hints) & gosHint_DisableMipmap, ( Area() * Description()->ddpfPixelFormat.dwRGBBitCount ) >> 3 );
		m_nVidMemBytes = MipSizeCalc( (~m_Hints) & gosHint_DisableMipmap, ( ScaledWidth() * ScaledHeight() * Description()->ddpfPixelFormat.dwRGBBitCount ) >> 3 );
	}

	if( Current )
	{
		switch( MemType )
		{
		case gmt_All: return (m_pOriginalSurf?m_nOriginalBytes:0)+(m_pSysMemSurf?m_nSysMemBytes:0)+(m_pVidMemSurf?m_nVidMemBytes:0);
		case gmt_Original: return m_pOriginalSurf?m_nOriginalBytes:0;
		case gmt_SysMem: return (m_pSysMemSurf && (m_pSysMemSurf != m_pOriginalSurf))?m_nSysMemBytes:0;
		case gmt_VidMem: return m_pVidMemSurf?m_nVidMemBytes:0;
		}
	}
	else
	{
		switch( MemType )
		{
		case gmt_All: return m_nOriginalBytes+m_nSysMemBytes+m_nVidMemBytes;
		case gmt_Original: return m_nOriginalBytes;
		case gmt_SysMem: return m_nSysMemBytes;
		case gmt_VidMem: return m_nVidMemBytes;
		}
	}

#ifdef LAB_ONLY
	STOP(("Illegal memory type tested!?"));
#endif
	return 0;	
}

