#include "pch.hpp"
//===========================================================================//
// File:	 Texture SysMem.cpp												 //
// Project:  GameOS					                                         //
// Contents: Texture System Memory Image 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 "MemoryManager.hpp"
#include "DirectX.hpp"
#include "3DRasterizer.hpp"
#include "DXRasterizer.hpp"

// From Texture Convert.cpp:
extern void ConvertRect( DWORD Bpp, IDirectDrawSurface7* DestSurface, DWORD *Source, DWORD SourcePitch, DWORD Hints, DWORD left, DWORD top, DWORD width, DWORD height );
extern void ConvertBump( DWORD Bpp, IDirectDrawSurface7* DestSurface, DWORD *Source, DWORD SourcePitch, DWORD Hints, DWORD left, DWORD top, DWORD width, DWORD height );
extern void ConvertNormal( DWORD Bpp, IDirectDrawSurface7* DestSurface, DWORD *Source, DWORD SourcePitch, DWORD Hints, DWORD left, DWORD top, DWORD width, DWORD height );

// Functions implemented below

IDirectDrawSurface7*	CTexInfo::GetSysMemSurf( bool populate, WORD MipMapWidth );
void					CTexInfo::AllocateSysMem();	// Allocates (probably 16bpp) system memory surface
void					CTexInfo::PopulateSysMem();	// (Assumes valid original) Converts the Original Level(s)
void					CTexInfo::ConvertLevel( IDirectDrawSurface7 *dest, IDirectDrawSurface7 *source );
													// ...Converts one level from original to sysmem
void					CTexInfo::GenerateMipMaps();	// Filters top level of SysMem chain to all lower levels
void					CTexInfo::FreeSysMem();		// Frees the SysMem surface/chain (assumes it exists and is ok to free)




// Finds or allocates the converted form of the system memory surface
// populates it if it is not populated and populate==true
IDirectDrawSurface7* CTexInfo::GetSysMemSurf( bool populate, WORD MipMapWidth )
{
	if( m_pRebuild && populate && !InSysMem() )
	{
		// rebuild the system memory surface using the app-supplied call
		Rebuild();
	}
	else
	{
		// Load the original version first so we can re-use it if the sysmem format is identical
		if( populate && !InSysMem() )
			GetOriginalSurf(true);	// Ensure we have a good version of the original to work from
		if( !m_pSysMemSurf )
			AllocateSysMem();
		if( populate && !InSysMem() )
			PopulateSysMem();
	}
	return FindTextureLevel( m_pSysMemSurf, MipMapWidth );
}

// Allocates (probably 16bpp) system memory surface
void CTexInfo::AllocateSysMem()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::AllocateSysMem %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	gosASSERT( !m_pSysMemSurf );
//
// Setup format of system memory surface. (Or chain when MipMapping enabled)
//
	DDSURFACEDESC2 ddsd;
	memcpy( &ddsd, Description(), sizeof(ddsd) );

//
// Check whether this matches the original, - if so then we can simply reuse it
//
	if( ( ddsd.ddpfPixelFormat.dwRGBBitCount		== 32 ) &&
		( ddsd.ddpfPixelFormat.dwRBitMask			== 0x00ff0000 ) &&
		( ( m_Hints & gosHint_UserMipMaps ) || ( MipMapLevels() == 1 ) ) &&
		( ( ddsd.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS ) == 0 ) &&
		( m_pOriginalSurf != NULL ))
	{
		// the "original" and "sysmem" surfaces are identical, - reuse the original:
		m_pSysMemSurf = m_pOriginalSurf;
	}
	else
	{
		if( ( MostRecentSysMem.m_pSysMemSurf ) &&
			( m_nWidth == MostRecentSysMem.m_nWidth ) &&
			( m_nHeight == MostRecentSysMem.m_nHeight ) &&
			!( ( m_Hints ^ MostRecentSysMem.m_Hints ) & ( gosHint_DisableMipmap | gosHint_UserMipMaps | gosHint_Try32bpp ) ) &&
			!( ( m_Flags ^ MostRecentSysMem.m_Flags ) & ( tFlag_Alpha | FormatMask ) ) )
		{
			// just reuse the most recent sysmem surface:
			m_pSysMemSurf = MostRecentSysMem.m_pSysMemSurf;
			MostRecentSysMem.m_pSysMemSurf = NULL;
		}
		else
		{
			ddsd.dwSize = sizeof(ddsd);
			ddsd.dwFlags			= DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | ((MipMapLevels()>1 && HasMipMap) ? DDSD_MIPMAPCOUNT : 0);
			ddsd.ddsCaps.dwCaps		= DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY | ((MipMapLevels()>1 && HasMipMap) ? (DDSCAPS_MIPMAP|DDSCAPS_COMPLEX) : 0);
			ddsd.dwMipMapCount		= MipMapLevels();
			ddsd.dwWidth			= Width();
			ddsd.dwHeight			= Height();
//
// Create 16bpp system memory texture surface and record memory usage
//
			wCreateSurface( CurrentDDobject, &ddsd, &m_pSysMemSurf, NULL );
			gos_ChangeHeapSize( Heap_Texture16, MemoryUsage( false, gmt_SysMem ), 1 );
		}
	}
}

// SysMemSurfUsable checks that the system memory version is OK to use as a texture
// specifically, things like square, aspect ratio, and dimensions are checked
// mipmap when not available in renderer is OK
bool CTexInfo::SysMemSurfUsable()
{
	// check if square required
	if( CapsDirect3D.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_SQUAREONLY )
		if( Width() != Height() )
			return false;

	// check aspect ratio
	if( CapsDirect3D.dwMaxTextureAspectRatio )
	{
		if( ( Width() > Height() * CapsDirect3D.dwMaxTextureAspectRatio ) ||
			( Height() > Width() * CapsDirect3D.dwMaxTextureAspectRatio ) )
			return false;
	}

	// check height
	if( CapsDirect3D.dwMaxTextureHeight && (Height() > CapsDirect3D.dwMaxTextureHeight ) )
		return false;

	// check width
	if( CapsDirect3D.dwMaxTextureWidth && (Width() > CapsDirect3D.dwMaxTextureWidth ) )
		return false;

	return true;
}

// (Assumes valid original) Converts the Original Level(s)
void CTexInfo::PopulateSysMem()
{
	LOG_BLOCK("CTexInfo::PopulateSysMem");
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::PopulateSysMem %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	if( m_pOriginalSurf == m_pSysMemSurf )
	{
		SPEW((GROUP_TEXTURE, "No need to convert as sysmem is a clone of original"));
		// Mark SysMem version as valid
		m_Flags |= tFlag_InSysMem;
		return;
	}

	// Convert the user-supplied levels
	for( int level = 0; level < MipMapLevels(); level++ )
	{
		IDirectDrawSurface7* pSource = GetOriginalSurf( true, (SHORT)(m_nWidth >> level) );
		IDirectDrawSurface7* pDest = FindTextureLevel( m_pSysMemSurf, (SHORT)(m_nWidth >> level) );
		LogTextureEvent("Converting Level");
		ConvertLevel( pDest, pSource );
		if( !HasMipMap || !(m_Hints & gosHint_UserMipMaps) )
			break;
	}

	// If GOS is to filter the mipmap levels, do so here
	if( HasMipMap && (MipMapLevels()>1) && !(m_Hints & gosHint_UserMipMaps) )
	{
		LogTextureEvent("Generating Mipmaps");
		GenerateMipMaps();
	}

	// Mark SysMem version as valid
	m_Flags |= tFlag_InSysMem;

	// Now, 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();
}

// Convert one level from original to sysmem
void CTexInfo::ConvertLevel( IDirectDrawSurface7 *DestSurface, IDirectDrawSurface7 *SourceSurface )
{
	DDSURFACEDESC2 SRCddsd;
//
// Lock DWORD 32 bit surface and make it the source
//
	SRCddsd.dwSize = sizeof(SRCddsd);
	wLock( SourceSurface, NULL, &SRCddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT | DDLOCK_READONLY, NULL );
	DWORD* Source = (DWORD*)SRCddsd.lpSurface;
	DWORD SourcePitch= SRCddsd.lPitch;

	if( Format() == gos_Texture_Bump )
	{
		DWORD bpp=Description()->ddpfPixelFormat.dwRGBBitCount;
		ConvertBump( bpp, DestSurface, Source, SourcePitch, m_Hints, 0, 0, SRCddsd.dwWidth, SRCddsd.dwHeight );
	}
	else if( Format() == gos_Texture_Normal )
	{
		DWORD bpp=Description()->ddpfPixelFormat.dwRGBBitCount;
		ConvertNormal( bpp, DestSurface, Source, SourcePitch, m_Hints, 0, 0, SRCddsd.dwWidth, SRCddsd.dwHeight );
	}
	else
	{
//
// Convert the entire mip level
//
		DWORD bpp=Description()->ddpfPixelFormat.dwRGBBitCount;
		ConvertRect( bpp, DestSurface, Source, SourcePitch, m_Hints, 0, 0, SRCddsd.dwWidth, SRCddsd.dwHeight );
	}
//
// Unlock the source surface
//
	wUnlock( SourceSurface, NULL );
}



// Frees the SysMem surface/chain (assumes it exists and is ok to free)
void CTexInfo::FreeSysMem()
{
	SPEW((GROUP_TEXTURE,"0x%08x CTexInfo::FreeSysMem %4d x %4d '%s'", this, m_nWidth, m_nHeight, m_pName ));

	gosASSERT( m_pSysMemSurf );

	if( m_pOriginalSurf == m_pSysMemSurf )
	{
		// it is just a clone, - remove the reference and return
		m_Flags &= ~tFlag_InSysMem;
		m_pSysMemSurf = NULL;
		return;
	}
	if( this != &MostRecentSysMem )
	{
		if( ( !MostRecentSysMem.m_pSysMemSurf ) ||
			( m_nWidth != MostRecentSysMem.m_nWidth ) ||
			( m_nHeight != MostRecentSysMem.m_nHeight ) ||
			( ( m_Hints ^ MostRecentSysMem.m_Hints ) & ( gosHint_DisableMipmap | gosHint_UserMipMaps ) ) ||
			( ( m_Flags ^ MostRecentSysMem.m_Flags ) & ( tFlag_Alpha | FormatMask ) ) )
		{
			// we want to save this surface for later re-use
			if( MostRecentSysMem.m_pSysMemSurf )
				MostRecentSysMem.FreeSysMem();
			memcpy( (void *)&MostRecentSysMem, (void *)this, sizeof(*this) );
			MostRecentSysMem.m_pName = "MostRecentSysMem";
			m_Flags &= ~tFlag_InSysMem;
			m_pSysMemSurf = NULL;
			return;
		}
	}
	wRelease( m_pSysMemSurf );
	// change memory usage
	gos_ChangeHeapSize( Heap_Texture16, - MemoryUsage( false, gmt_SysMem ), 1 );
	m_Flags &= ~tFlag_InSysMem;
	m_pSysMemSurf = NULL;
}

