//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================

#include "pch_materialsystem.h"

#define MATSYS_INTERNAL

#include "cmatlightmaps.h"

#include "colorspace.h"
#include "IHardwareConfigInternal.h"

#include "cmaterialsystem.h"

// NOTE: This must be the last file included!!!
#include "tier0/memdbgon.h"
#include "bitmap/float_bm.h"

static ConVar mat_lightmap_pfms( "mat_lightmap_pfms", "0", FCVAR_MATERIAL_SYSTEM_THREAD, "Outputs .pfm files containing lightmap data for each lightmap page when a level exits." ); // Write PFM files for each lightmap page in the game directory when exiting a level 

#define USE_32BIT_LIGHTMAPS_ON_360 //uncomment to use 32bit lightmaps, be sure to keep this in sync with the same #define in stdshaders/lightmappedgeneric_ps2_3_x.h

#ifdef _X360
#define X360_USE_SIMD_LIGHTMAP
#endif

//-----------------------------------------------------------------------------

inline IMaterialInternal* CMatLightmaps::GetCurrentMaterialInternal() const
{
	return GetMaterialSystem()->GetRenderContextInternal()->GetCurrentMaterialInternal();
}

inline void CMatLightmaps::SetCurrentMaterialInternal(IMaterialInternal* pCurrentMaterial)
{
	return GetMaterialSystem()->GetRenderContextInternal()->SetCurrentMaterialInternal( pCurrentMaterial );
}

inline IMaterialInternal *CMatLightmaps::GetMaterialInternal( MaterialHandle_t idx ) const
{
	return GetMaterialSystem()->GetMaterialInternal( idx );
}

inline const IMatRenderContextInternal *CMatLightmaps::GetRenderContextInternal() const
{
	return GetMaterialSystem()->GetRenderContextInternal();
}

inline IMatRenderContextInternal *CMatLightmaps::GetRenderContextInternal()
{
	return GetMaterialSystem()->GetRenderContextInternal();
}

inline const CMaterialDict *CMatLightmaps::GetMaterialDict() const
{
	return GetMaterialSystem()->GetMaterialDict();
}

inline CMaterialDict *CMatLightmaps::GetMaterialDict()
{
	return GetMaterialSystem()->GetMaterialDict();
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CMatLightmaps::CMatLightmaps()
{
	m_currentWhiteLightmapMaterial = NULL;
	m_pLightmapPages = NULL;
	m_NumLightmapPages = 0;
	m_numSortIDs = 0;
	m_nUpdatingLightmapsStackDepth = 0;
	m_nLockedLightmap = -1;
	m_pLightmapDataPtrArray = NULL;
	m_eLightmapsState = STATE_DEFAULT;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMatLightmaps::Shutdown( )
{
	// Clean up all lightmaps
	CleanupLightmaps();
}

//-----------------------------------------------------------------------------
// Assign enumeration IDs to all materials
//-----------------------------------------------------------------------------
void CMatLightmaps::EnumerateMaterials( void )
{
	// iterate in sorted order
	int id = 0;
	for (MaterialHandle_t i = GetMaterialDict()->FirstMaterial(); i != GetMaterialDict()->InvalidMaterial(); i = GetMaterialDict()->NextMaterial(i) )
	{
		GetMaterialInternal(i)->SetEnumerationID( id );
		++id;
	}
}


//-----------------------------------------------------------------------------
// Gets the maximum lightmap page size...
//-----------------------------------------------------------------------------
int CMatLightmaps::GetMaxLightmapPageWidth() const
{
	// FIXME: It's unclear which we want here.
	// It doesn't drastically increase primitives per DrawIndexedPrimitive
	// call at the moment to increase it, so let's not for now.
	
	// If we're using dynamic textures though, we want bigger that's for sure.
	// The tradeoff here is how much memory we waste if we don't fill the lightmap

	// We need to go to 512x256 textures because that's the only way bumped
	// lighting on displacements can work given the 128x128 allowance..
	int nWidth = 512;
	if ( nWidth > HardwareConfig()->MaxTextureWidth() )
		nWidth = HardwareConfig()->MaxTextureWidth();

	return nWidth;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CMatLightmaps::GetMaxLightmapPageHeight() const
{
	int nHeight = 256;

	if ( nHeight > HardwareConfig()->MaxTextureHeight() )
		nHeight = HardwareConfig()->MaxTextureHeight();

	return nHeight;
}


//-----------------------------------------------------------------------------
// Returns the lightmap page size
//-----------------------------------------------------------------------------
void CMatLightmaps::GetLightmapPageSize( int lightmapPageID, int *pWidth, int *pHeight ) const
{
	switch( lightmapPageID )
	{
	default:
 		Assert( lightmapPageID >= 0 && lightmapPageID < GetNumLightmapPages() );
		*pWidth = m_pLightmapPages[lightmapPageID].m_Width;
		*pHeight = m_pLightmapPages[lightmapPageID].m_Height;
		break;

	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED:
		*pWidth = *pHeight = 1;
		AssertOnce( !"Can't use CMatLightmaps to get properties of MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED" );
		break;
	
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE:
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP:
		*pWidth = *pHeight = 1;
		break;
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CMatLightmaps::GetLightmapWidth( int lightmapPageID ) const
{
	switch( lightmapPageID )
	{
	default:
 		Assert( lightmapPageID >= 0 && lightmapPageID < GetNumLightmapPages() );
		return m_pLightmapPages[lightmapPageID].m_Width;

	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED:
		AssertOnce( !"Can't use CMatLightmaps to get properties of MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED" );
		return 1;
	
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE:
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP:
		return 1;
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CMatLightmaps::GetLightmapHeight( int lightmapPageID ) const
{
	switch( lightmapPageID )
	{
	default:
 		Assert( lightmapPageID >= 0 && lightmapPageID < GetNumLightmapPages() );
		return m_pLightmapPages[lightmapPageID].m_Height;

	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED:
		AssertOnce( !"Can't use CMatLightmaps to get properties of MATERIAL_SYSTEM_LIGHTMAP_PAGE_USER_DEFINED" );
		return 1;
	
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE:
	case MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP:
		return 1;
	}
}


//-----------------------------------------------------------------------------
// Clean up lightmap pages.
//-----------------------------------------------------------------------------
void CMatLightmaps::CleanupLightmaps()
{
   if ( mat_lightmap_pfms.GetBool())
   {
      // Write PFM files containing lightmap data for this page
      for (int lightmap = 0; lightmap < GetNumLightmapPages(); lightmap++)
      {
         if ((NULL != m_pLightmapDataPtrArray) && (NULL != m_pLightmapDataPtrArray[lightmap]))
         {
            char szPFMFileName[MAX_PATH];

            sprintf(szPFMFileName, "Lightmap-Page-%d.pfm", lightmap);
            m_pLightmapDataPtrArray[lightmap]->WritePFM(szPFMFileName);
         }
      }
   }

   // Remove the lightmap data bitmap representations
   if (m_pLightmapDataPtrArray)
   {
      int i;
      for( i = 0; i < GetNumLightmapPages(); i++ )
      {
         delete m_pLightmapDataPtrArray[i];
      }

      delete [] m_pLightmapDataPtrArray;
      m_pLightmapDataPtrArray = NULL;
   }
   
   // delete old lightmap pages
	if( m_pLightmapPages )
	{
		int i;
		for( i = 0; i < GetNumLightmapPages(); i++ )
		{
			g_pShaderAPI->DeleteTexture( m_LightmapPageTextureHandles[i] );
		}
		delete [] m_pLightmapPages;
		m_pLightmapPages = 0;
	}

	m_NumLightmapPages = 0;
}

//-----------------------------------------------------------------------------
// Resets the lightmap page info for each material
//-----------------------------------------------------------------------------
void CMatLightmaps::ResetMaterialLightmapPageInfo( void )
{
	for (MaterialHandle_t i = GetMaterialDict()->FirstMaterial(); i != GetMaterialDict()->InvalidMaterial(); i = GetMaterialDict()->NextMaterial(i) )
	{
		IMaterialInternal *pMaterial = GetMaterialInternal(i);
		pMaterial->SetMinLightmapPageID( 9999 );
		pMaterial->SetMaxLightmapPageID( -9999 );
		pMaterial->SetNeedsWhiteLightmap( false );
	}
}

//-----------------------------------------------------------------------------
// This is called before any lightmap allocations take place
//-----------------------------------------------------------------------------
void CMatLightmaps::BeginLightmapAllocation()
{	
	// delete old lightmap pages
	CleanupLightmaps();

	m_ImagePackers.RemoveAll();
	int i = m_ImagePackers.AddToTail();
	m_ImagePackers[i].Reset( 0, GetMaxLightmapPageWidth(), GetMaxLightmapPageHeight() );

	SetCurrentMaterialInternal(0);
	m_currentWhiteLightmapMaterial = 0;
	m_numSortIDs = 0;

	// need to set the min and max sorting id number for each material to 
	// a default value that basically means that it hasn't been used yet.
	ResetMaterialLightmapPageInfo();

	EnumerateMaterials();
}


//-----------------------------------------------------------------------------
// Allocates space in the lightmaps; must be called after BeginLightmapAllocation
//-----------------------------------------------------------------------------
int CMatLightmaps::AllocateLightmap( int width, int height, 
		                               int offsetIntoLightmapPage[2],
									   IMaterial *iMaterial )
{
	IMaterialInternal *pMaterial = static_cast<IMaterialInternal *>( iMaterial );
	if ( !pMaterial )
	{
		Warning( "Programming error: CMatRenderContext::AllocateLightmap: NULL material\n" );
		return m_numSortIDs;
	}
	pMaterial = pMaterial->GetRealTimeVersion(); //always work with the real time versions of materials internally
	
	// material change
	int i;
	int nPackCount = m_ImagePackers.Count();
	if ( GetCurrentMaterialInternal() != pMaterial )
	{
		// If this happens, then we need to close out all image packers other than
		// the last one so as to produce as few sort IDs as possible
		for ( i = nPackCount - 1; --i >= 0; )
		{
			// NOTE: We *must* use the order preserving one here so the remaining one
			// is the last lightmap
			m_ImagePackers.Remove( i );
			--nPackCount;
		}

		// If it's not the first material, increment the sort id
		if (GetCurrentMaterialInternal())
		{
			m_ImagePackers[0].IncrementSortId( );
			++m_numSortIDs;
		}

		SetCurrentMaterialInternal(pMaterial);

		// This assertion guarantees we don't see the same material twice in this loop.
		Assert( pMaterial->GetMinLightmapPageID( ) > pMaterial->GetMaxLightmapPageID() );

		// NOTE: We may not use this lightmap page, but we might
		// we won't know for sure until the next material is passed in.
		// So, for now, we're going to forcibly add the current lightmap
		// page to this material so the sort IDs work out correctly.
		GetCurrentMaterialInternal()->SetMinLightmapPageID( GetNumLightmapPages() );
		GetCurrentMaterialInternal()->SetMaxLightmapPageID( GetNumLightmapPages() );
	}

	// Try to add it to any of the current images...
	bool bAdded = false;
	for ( i = 0; i < nPackCount; ++i )
	{
		bAdded = m_ImagePackers[i].AddBlock( width, height, &offsetIntoLightmapPage[0], &offsetIntoLightmapPage[1] );
		if ( bAdded )
			break;
	}

	if ( !bAdded )
	{
		++m_numSortIDs;
		i = m_ImagePackers.AddToTail();
		m_ImagePackers[i].Reset( m_numSortIDs, GetMaxLightmapPageWidth(), GetMaxLightmapPageHeight() );
		++m_NumLightmapPages;
		if ( !m_ImagePackers[i].AddBlock( width, height, &offsetIntoLightmapPage[0], &offsetIntoLightmapPage[1] ) )
		{
			Error( "MaterialSystem_Interface_t::AllocateLightmap: lightmap (%dx%d) too big to fit in page (%dx%d)\n", 
				width, height, GetMaxLightmapPageWidth(), GetMaxLightmapPageHeight() );
		}

		// Add this lightmap to the material...
		GetCurrentMaterialInternal()->SetMaxLightmapPageID( GetNumLightmapPages() );
	}

	return m_ImagePackers[i].GetSortId();
}

// UNDONE: This needs testing, but it appears as though creating these textures managed
// results in huge stalls whenever they are locked for modify.
// That makes sense given the d3d docs, but these have been flagged as managed for quite some time.
#define DYNAMIC_TEXTURES_NO_BACKING 1

void CMatLightmaps::EndLightmapAllocation()
{
	// count the last page that we were on.if it wasn't 
	// and count the last sortID that we were on
	m_NumLightmapPages++; 
	m_numSortIDs++;

	m_firstDynamicLightmap = m_NumLightmapPages;
	// UNDONE: Until we start using the separate dynamic lighting textures don't allocate them
	// NOTE: Enable this if we want to stop locking the base lightmaps and instead only lock update
	// these completely dynamic pages
//	m_NumLightmapPages += COUNT_DYNAMIC_LIGHTMAP_PAGES;
	m_dynamic.Init();

	// Compute the dimensions of the last lightmap 
	int lastLightmapPageWidth, lastLightmapPageHeight;
	int nLastIdx = m_ImagePackers.Count();
	m_ImagePackers[nLastIdx - 1].GetMinimumDimensions( &lastLightmapPageWidth, &lastLightmapPageHeight );
	m_ImagePackers.Purge();

	m_pLightmapPages = new LightmapPageInfo_t[GetNumLightmapPages()];
	Assert( m_pLightmapPages );

   if ( mat_lightmap_pfms.GetBool())
   {
      // This array will be used to write PFM files full of lightmap data
      m_pLightmapDataPtrArray = new FloatBitMap_t*[GetNumLightmapPages()];
   }

	int i;
	m_LightmapPageTextureHandles.EnsureCapacity( GetNumLightmapPages() );
	for ( i = 0; i < GetNumLightmapPages(); i++ )
	{
		// Compute lightmap dimensions
		bool lastStaticLightmap = ( i == (m_firstDynamicLightmap-1));
		m_pLightmapPages[i].m_Width = (unsigned short)(lastStaticLightmap ? lastLightmapPageWidth : GetMaxLightmapPageWidth());
		m_pLightmapPages[i].m_Height = (unsigned short)(lastStaticLightmap ? lastLightmapPageHeight : GetMaxLightmapPageHeight());
		m_pLightmapPages[i].m_Flags = 0;

		AllocateLightmapTexture( i );

        if ( mat_lightmap_pfms.GetBool())
        {
           // Initialize the pointers to lightmap data
           m_pLightmapDataPtrArray[i] = NULL;
        }
	}
}

//-----------------------------------------------------------------------------
// Allocate lightmap textures
//-----------------------------------------------------------------------------
void CMatLightmaps::AllocateLightmapTexture( int lightmap )
{
	bool bUseDynamicTextures = HardwareConfig()->PreferDynamicTextures();

	int flags = bUseDynamicTextures ? TEXTURE_CREATE_DYNAMIC : TEXTURE_CREATE_MANAGED;

	m_LightmapPageTextureHandles.EnsureCount( lightmap + 1 );

	char debugName[256];
	Q_snprintf( debugName, sizeof( debugName ), "[lightmap %d]", lightmap );
	
	ImageFormat imageFormat;
	switch ( HardwareConfig()->GetHDRType() )
	{
	default:
		Assert( 0 );
		// fall through.

	case HDR_TYPE_NONE:
#if !defined( _X360 )
		imageFormat = IMAGE_FORMAT_RGBA8888;
		flags |= TEXTURE_CREATE_SRGB;
#else
		imageFormat = IMAGE_FORMAT_LINEAR_RGBA8888;
#endif
		break;

	case HDR_TYPE_INTEGER:
#if !defined( _X360 )
		imageFormat = IMAGE_FORMAT_RGBA16161616;
#else
#		if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
			imageFormat = IMAGE_FORMAT_LINEAR_RGBA8888;
#		else
			imageFormat = IMAGE_FORMAT_LINEAR_RGBA16161616;
#		endif
#endif
		break;

	case HDR_TYPE_FLOAT:
		imageFormat = IMAGE_FORMAT_RGBA16161616F;
		break;
	}

	switch ( m_eLightmapsState )
	{
	case STATE_DEFAULT:
		// Allow allocations in default state
		{
			m_LightmapPageTextureHandles[lightmap] = g_pShaderAPI->CreateTexture( 
				GetLightmapWidth(lightmap), GetLightmapHeight(lightmap), 1,
				imageFormat, 
				1, 1, flags, debugName, TEXTURE_GROUP_LIGHTMAP );	// don't mipmap lightmaps

			// Load up the texture data
			g_pShaderAPI->ModifyTexture( m_LightmapPageTextureHandles[lightmap] );
			g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_LINEAR );
			g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_LINEAR );

			if ( !bUseDynamicTextures )
			{
				g_pShaderAPI->TexSetPriority( 1 );
			}

			// Blat out the lightmap bits
			InitLightmapBits( lightmap );
		}
		break;

	case STATE_RELEASED:
		// Not assigned m_LightmapPageTextureHandles[lightmap];
		DevMsg( "AllocateLightmapTexture(%d) in released lightmap state (STATE_RELEASED), delayed till \"Restore\".\n", lightmap );
		return;

	default:
		// Not assigned m_LightmapPageTextureHandles[lightmap];
		Warning( "AllocateLightmapTexture(%d) in unknown lightmap state (%d), skipped.\n", lightmap, m_eLightmapsState );
		Assert( !"AllocateLightmapTexture(?) in unknown lightmap state (?)" );
		return;
	}
}


int	CMatLightmaps::AllocateWhiteLightmap( IMaterial *iMaterial )
{
	IMaterialInternal *pMaterial = static_cast<IMaterialInternal *>( iMaterial );
	if( !pMaterial )
	{
		Warning( "Programming error: CMatRenderContext::AllocateWhiteLightmap: NULL material\n" );
		return m_numSortIDs;
	}
	pMaterial = pMaterial->GetRealTimeVersion(); //always work with the real time versions of materials internally

	if ( !m_currentWhiteLightmapMaterial || ( m_currentWhiteLightmapMaterial != pMaterial ) )
	{
		if ( !GetCurrentMaterialInternal() && !m_currentWhiteLightmapMaterial )
		{
			// don't increment if this is the very first material (ie. no lightmaps
			// allocated with AllocateLightmap
			// Assert( 0 );
		}
		else
		{
			// material change
			m_numSortIDs++;
#if 0
			char buf[128];
			Q_snprintf( buf, sizeof( buf ), "AllocateWhiteLightmap: m_numSortIDs = %d %s\n", m_numSortIDs, pMaterial->GetName() );
			OutputDebugString( buf );
#endif
		}
//		Warning( "%d material: \"%s\" lightmapPageID: -1\n", m_numSortIDs, pMaterial->GetName() );
		m_currentWhiteLightmapMaterial = pMaterial;
		pMaterial->SetNeedsWhiteLightmap( true );
	}

	return m_numSortIDs;
}

//-----------------------------------------------------------------------------
// Releases/restores lightmap pages
//-----------------------------------------------------------------------------
void CMatLightmaps::ReleaseLightmapPages()
{
	switch ( m_eLightmapsState )
	{
	case STATE_DEFAULT:
		// Allow release in default state only
		break;
	
	default:
		Warning( "ReleaseLightmapPages is expected in STATE_DEFAULT, current state = %d, discarded.\n", m_eLightmapsState );
		Assert( !"ReleaseLightmapPages is expected in STATE_DEFAULT" );
		return;
	}

	for( int i = 0; i < GetNumLightmapPages(); i++ )
	{
		g_pShaderAPI->DeleteTexture( m_LightmapPageTextureHandles[i] );
	}
	
	// We are now in released state
	m_eLightmapsState = STATE_RELEASED;
}

void CMatLightmaps::RestoreLightmapPages()
{
	switch ( m_eLightmapsState )
	{
	case STATE_RELEASED:
		// Allow restore in released state only
		break;

	default:
		Warning( "RestoreLightmapPages is expected in STATE_RELEASED, current state = %d, discarded.\n", m_eLightmapsState );
		Assert( !"RestoreLightmapPages is expected in STATE_RELEASED" );
		return;
	}

	// Switch to default state to allow allocations
	m_eLightmapsState = STATE_DEFAULT;

	for( int i = 0; i < GetNumLightmapPages(); i++ )
	{
		AllocateLightmapTexture( i );
	}
}


//-----------------------------------------------------------------------------
// This initializes the lightmap bits
//-----------------------------------------------------------------------------
void CMatLightmaps::InitLightmapBits( int lightmap )
{
	VPROF_( "CMatLightmaps::InitLightmapBits", 1, VPROF_BUDGETGROUP_DLIGHT_RENDERING, false, 0 );
	int width = GetLightmapWidth(lightmap);
	int height = GetLightmapHeight(lightmap);

	CPixelWriter writer;

	g_pShaderAPI->ModifyTexture( m_LightmapPageTextureHandles[lightmap] );
	if ( !g_pShaderAPI->TexLock( 0, 0, 0, 0, width, height, writer ) )
		return;

	// Debug mode, make em green checkerboard
	if ( writer.IsUsingFloatFormat() )
	{
		for ( int j = 0; j < height; ++j )
		{
			writer.Seek( 0, j );
			for ( int k = 0; k < width; ++k )
			{
#ifndef _DEBUG
				writer.WritePixel( 1.0f, 1.0f, 1.0f );
#else // _DEBUG
				if( ( j + k ) & 1 )
				{
					writer.WritePixelF( 0.0f, 1.0f, 0.0f );
				}
				else
				{
					writer.WritePixelF( 0.0f, 0.0f, 0.0f );
				}
#endif // _DEBUG
			}
		}
	}
	else
	{
		for ( int j = 0; j < height; ++j )
		{
			writer.Seek( 0, j );
			for ( int k = 0; k < width; ++k )
			{
#ifndef _DEBUG
				// note: make this white to find multisample centroid sampling problems.
				//				writer.WritePixel( 255, 255, 255 );
				writer.WritePixel( 0, 0, 0 );
#else // _DEBUG
				if ( ( j + k ) & 1 )
				{
					writer.WritePixel( 0, 255, 0 );
				}
				else
				{
					writer.WritePixel( 0, 0, 0 );
				}
#endif // _DEBUG
			}
		}
	}

	g_pShaderAPI->TexUnlock();
}

bool CMatLightmaps::LockLightmap( int lightmap )
{
//	Warning( "locking lightmap page: %d\n", lightmap );
	VPROF_INCREMENT_COUNTER( "lightmap fullpage texlock", 1 );
	if( m_nLockedLightmap != -1 )
	{
		g_pShaderAPI->TexUnlock();
	}
	g_pShaderAPI->ModifyTexture( m_LightmapPageTextureHandles[lightmap] );
	int pageWidth  = m_pLightmapPages[lightmap].m_Width;
	int pageHeight = m_pLightmapPages[lightmap].m_Height;
	if (!g_pShaderAPI->TexLock( 0, 0, 0, 0,	pageWidth, pageHeight, m_LightmapPixelWriter ))
	{
		Assert( 0 );
		return false;
	}
	m_nLockedLightmap = lightmap;
	return true;
}

Vector4D ConvertLightmapColorToRGBScale( const float *lightmapColor )
{
	Vector4D result;

	float fScale = lightmapColor[0];
	for( int i = 1; i != 3; ++i )
	{
		if( lightmapColor[i] > fScale )
			fScale = lightmapColor[i];
	}

	fScale = ceil( fScale * (255.0f/16.0f) ) * (16.0f/255.0f);
	fScale = min( fScale, 16.0f );

	float fInvScale = 1.0f / fScale;

	for( int i = 0; i != 3; ++i )
	{
		result[i] = lightmapColor[i] * fInvScale;
		result[i] = ceil( result[i] * 255.0f ) * (1.0f/255.0f);
		result[i] = min( result[i], 1.0f );
	}

	fScale /= 16.0f;

	result.w = fScale;

	return result;
}

#ifdef _X360
// SIMD version of above
// input numbers from pSrc are on the domain [0..16]
// output is RGBA 
// ignores contents of w channel of input
// the shader does this: rOut = Rin * Ain * 16.0f 
// where Rin is [0..1], a float computed from a byte value [0..255]
// Ain is therefore the brightest channel (say R) divided by 16 and quantized
// Rin is computed from pSrc->r by dividing by Ain
// this outputs RGBa where RGB are [0..255] and a is the shader's scaling factor (also 0..255)
//
// WARNING - this code appears to be vulnerable to a compiler bug. Be very careful modifying and be
// sure to test
fltx4 ConvertLightmapColorToRGBScale( FLTX4 lightmapColor )
{
	
	static const fltx4 vTwoFiftyFive = {255.0f, 255.0f, 255.0f, 255.0f};
	static const fltx4 FourPoint1s = { 0.1, 0.1, 0.1, 0.1 };
	static const fltx4 vTwoFiftyFiveOverSixteen = {255.0f / 16.0f, 255.0f / 16.0f, 255.0f / 16.0f, 255.0f / 16.0f};
	// static const fltx4 vSixteenOverTwoFiftyFive = { 16.0f / 255.0f, 16.0f / 255.0f, 16.0f / 255.0f, 16.0f / 255.0f };


	// find the highest color value in lightmapColor and replicate it
	fltx4 scale = FindHighestSIMD3( lightmapColor );
	fltx4 minscale = FindLowestSIMD3( lightmapColor );
	fltx4 fl4OutofRange = OrSIMD( CmpGeSIMD( scale, Four_Ones ), CmpLeSIMD( scale, FourPoint1s ) );
	fl4OutofRange = OrSIMD( fl4OutofRange, CmpGtSIMD( minscale, MulSIMD( Four_PointFives, scale ) ) );

	// scale needs to be divided by 16 (because the shader multiplies it by 16)
	// then mapped to 0..255 and quantized. 
	scale = __vrfip(MulSIMD(scale, vTwoFiftyFiveOverSixteen)); // scale = ceil(scale * 255/16)
		
	fltx4 result = MulSIMD(vTwoFiftyFive, lightmapColor); // start the scale cooking on the final result
		
	fltx4 invScale = ReciprocalEstSIMD(scale); // invScale = (16/255)(1/scale). may be +inf
	invScale = MulSIMD(invScale, vTwoFiftyFiveOverSixteen); // take the quantizing factor back out
															// of the inverse scale (one less
															// dependent op if you do it this way)
		
	// scale the input channels
	// compute so the numbers are all 0..255 ints. (if one happens to 
	// be 256 due to numerical error in the reciprocation, the unsigned-saturate
	// store we'll use later on will bake it back down to 255)
	result = MulSIMD(result, invScale);
		
	// now, output --
	// if the input color was nonzero, slip the scale into return value's w
	// component and return. If the input was zero, return zero.

	result = MaskedAssign( 
		fl4OutofRange,
		SetWSIMD( result, scale ),
		SetWSIMD( MulSIMD( lightmapColor, vTwoFiftyFive ), vTwoFiftyFiveOverSixteen ) );
	return result;
}
#endif


// write bumped lightmap update to LDR 8-bit lightmap
void CMatLightmaps::BumpedLightmapBitsToPixelWriter_LDR( float* pFloatImage, float *pFloatImageBump1, float *pFloatImageBump2, 
	float *pFloatImageBump3, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut )
{
	const int nLightmapSize0 = pLightmapSize[0];
	const int nLightmap0WriterSizeBytes = nLightmapSize0 * m_LightmapPixelWriter.GetPixelSize();
	const int nRewindToNextPixel = -( ( nLightmap0WriterSizeBytes * 3 ) - m_LightmapPixelWriter.GetPixelSize() );

	for( int t = 0; t < pLightmapSize[1]; t++ )
	{
		int srcTexelOffset = ( sizeof( Vector4D ) / sizeof( float ) ) * ( 0 + t * nLightmapSize0 );
		m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

		for( int s = 0; s < nLightmapSize0; 
			s++, m_LightmapPixelWriter.SkipBytes(nRewindToNextPixel),srcTexelOffset += (sizeof(Vector4D)/sizeof(float)))
		{
			unsigned char color[4][3];

			ColorSpace::LinearToBumpedLightmap( &pFloatImage[srcTexelOffset],
				&pFloatImageBump1[srcTexelOffset], &pFloatImageBump2[srcTexelOffset],
				&pFloatImageBump3[srcTexelOffset],
				color[0], color[1], color[2], color[3] );

			unsigned char alpha =  RoundFloatToByte( pFloatImage[srcTexelOffset+3] * 255.0f );
			m_LightmapPixelWriter.WritePixelNoAdvance( color[0][0], color[0][1], color[0][2], alpha );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvance( color[1][0], color[1][1], color[1][2], alpha );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvance( color[2][0], color[2][1], color[2][2], alpha );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvance( color[3][0], color[3][1], color[3][2], alpha );
		}
	}
	if ( pfmOut )
	{
		for( int t = 0; t < pLightmapSize[1]; t++ )
		{
			int srcTexelOffset = ( sizeof( Vector4D ) / sizeof( float ) ) * ( 0 + t * nLightmapSize0 );
			for( int s = 0;  s < nLightmapSize0; s++,srcTexelOffset += (sizeof(Vector4D)/sizeof(float)))
			{
				unsigned char color[4][3];

				ColorSpace::LinearToBumpedLightmap( &pFloatImage[srcTexelOffset],
					&pFloatImageBump1[srcTexelOffset], &pFloatImageBump2[srcTexelOffset],
					&pFloatImageBump3[srcTexelOffset],
					color[0], color[1], color[2], color[3] );

				unsigned char alpha =  RoundFloatToByte( pFloatImage[srcTexelOffset+3] * 255.0f );
				// Write data to the bitmapped represenations so that PFM files can be written
				PixRGBAF pixelData;
				pixelData.Red = color[0][0];                  
				pixelData.Green = color[0][1];                  
				pixelData.Blue = color[0][2];
				pixelData.Alpha = alpha;
				pfmOut->WritePixelRGBAF( pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData);
			}
		}

	}
}

// write bumped lightmap update to HDR float lightmap
void CMatLightmaps::BumpedLightmapBitsToPixelWriter_HDRF( float* pFloatImage, float *pFloatImageBump1, float *pFloatImageBump2, 
												 float *pFloatImageBump3, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut )
{
	if ( IsX360() )
	{
		// 360 does not support HDR float mode 
		Assert( 0 );
		return;
	}

	Assert( !pfmOut );		// unsupported in this mode

	const int nLightmapSize0 = pLightmapSize[0];
	const int nLightmap0WriterSizeBytes = nLightmapSize0 * m_LightmapPixelWriter.GetPixelSize();
	const int nRewindToNextPixel = -( ( nLightmap0WriterSizeBytes * 3 ) - m_LightmapPixelWriter.GetPixelSize() );

	for( int t = 0; t < pLightmapSize[1]; t++ )
	{
		int srcTexelOffset = ( sizeof( Vector4D ) / sizeof( float ) ) * ( 0 + t * nLightmapSize0 );
		m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

		for( int s = 0; 
			s < nLightmapSize0; 
			s++, m_LightmapPixelWriter.SkipBytes(nRewindToNextPixel),srcTexelOffset += (sizeof(Vector4D)/sizeof(float)))
		{
			m_LightmapPixelWriter.WritePixelNoAdvanceF( pFloatImage[srcTexelOffset], pFloatImage[srcTexelOffset+1],
				pFloatImage[srcTexelOffset+2], pFloatImage[srcTexelOffset+3] );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvanceF( pFloatImageBump1[srcTexelOffset], pFloatImageBump1[srcTexelOffset+1],
				pFloatImageBump1[srcTexelOffset+2], pFloatImage[srcTexelOffset+3] );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvanceF( pFloatImageBump2[srcTexelOffset], pFloatImageBump2[srcTexelOffset+1],
				pFloatImageBump2[srcTexelOffset+2], pFloatImage[srcTexelOffset+3] );

			m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
			m_LightmapPixelWriter.WritePixelNoAdvanceF( pFloatImageBump3[srcTexelOffset], pFloatImageBump3[srcTexelOffset+1],
				pFloatImageBump3[srcTexelOffset+2], pFloatImage[srcTexelOffset+3] );
		}
	}
}

#ifdef _X360
#pragma optimize("u", on)
#endif


#ifdef _X360

namespace {
	// pack a pixel into BGRA8888 and return it with the data packed into the w component
FORCEINLINE fltx4 PackPixel_BGRA8888( FLTX4 rgba ) 
{
	// this happens to be in an order such that we can use the handy builtin packing op
	// clamp to 0..255 (coz it might have leaked over)
	static const fltx4 vTwoFiftyFive = {255.0f, 255.0f, 255.0f, 255.0f};

	// the magic number such that when mul-accummulated against rbga,
	// gets us a representation 3.0 + (r)*2^-22 -- puts the bits at
	// the bottom of the float
	static const XMVECTOR   PackScale = { (1.0f / (FLOAT)(1 << 22)), (1.0f / (FLOAT)(1 << 22)), (1.0f / (FLOAT)(1 << 22)), (1.0f / (FLOAT)(1 << 22))}; // 255.0f / (FLOAT)(1 << 22)
	static const XMVECTOR   Three = {3.0f, 3.0f, 3.0f, 3.0f};

	fltx4 N = MinSIMD(vTwoFiftyFive, rgba); 

	N = __vmaddfp(N, PackScale, Three);
	N = __vpkd3d(N, N, VPACK_D3DCOLOR, VPACK_32, 0);  // pack into w word
	return N;
}

// A small store-gather buffer used in the 
// BumpedLightmapBitsToPixelWriter_HDRI_BGRA_X360().
// The store-gather buffers. Hopefully these will live in the L1
// cache, which will make writing to them, then to memory, faster
// than just using __stvewx to write directly into WC memory
// one noncontiguous float at a time. (If there weren't a huge
// compiler bug with __stvewx in the Apr07 XDK, that might not
// be the case.)
struct ALIGN128 CPixelWriterStoreGather
{
	enum {
		kRows = 4,
		kWordsPerRow = 32,
	};

	ALIGN128 uint32 m_data[kRows][kWordsPerRow]; // four rows of bgra data, aligned to 4 cache lines. dwords so memcpy works better.
	int m_wordsGathered;
	int m_bytesBetweenWriterRows; // the number of bytes spacing the maps inside the writer from each other
								// if we weren't gathering, we'd SkipBytes this many between the base map, bump1, etc.

	// write four rows, as SIMD registers, into the buffers
	inline void write( CPixelWriter * RESTRICT pLightmapPixelWriter, FLTX4 row0,  FLTX4 row1,  FLTX4 row2,  FLTX4 row3 ) RESTRICT
	{
		// if full, commit
		Assert(m_wordsGathered <= kWordsPerRow);
		AssertMsg((m_wordsGathered & 3) == 0, "Don't call CPixelWriterStoreGather::write after ::writeJustX"); // single-word writes have misaligned me
		if (m_wordsGathered >= kWordsPerRow)
		{
			commitWhenFull(pLightmapPixelWriter);
		}

		XMStoreVector4A( &m_data[0][m_wordsGathered], row0 );
		XMStoreVector4A( &m_data[1][m_wordsGathered], row1 );
		XMStoreVector4A( &m_data[2][m_wordsGathered], row2 );
		XMStoreVector4A( &m_data[3][m_wordsGathered], row3 );

		m_wordsGathered += 4 ; // four words per simd vec
	}

	// pluck the w component out of each of the rows, and store it into the gather buffer. Don't
	// call the other write function after calling this.
	inline void writeJustW( CPixelWriter * RESTRICT pLightmapPixelWriter, FLTX4 row0,  FLTX4 row1,  FLTX4 row2,  FLTX4 row3 ) RESTRICT
	{
		// if full, commit
		Assert(m_wordsGathered <= kWordsPerRow);
		if (m_wordsGathered >= kWordsPerRow)
		{
			commitWhenFull(pLightmapPixelWriter);
		}

		// for each fltx4, splat out x and then use the __stvewx to store
		// whichever word happens to align with the float pointer through
		// that pointer.

		__stvewx(__vspltw(row0, 3), &m_data[0][m_wordsGathered], 0 );
		__stvewx(__vspltw(row1, 3), &m_data[1][m_wordsGathered], 0 );
		__stvewx(__vspltw(row2, 3), &m_data[2][m_wordsGathered], 0 );
		__stvewx(__vspltw(row3, 3), &m_data[3][m_wordsGathered], 0 );

		m_wordsGathered += 1 ; // only stored one word
	}

	// Commit my buffers to the pixelwriter's memory, and advance its
	// pointer.
	void commit(CPixelWriter * RESTRICT pLightmapPixelWriter) RESTRICT
	{
		if (m_wordsGathered > 0)
		{
			unsigned char* RESTRICT pWriteInto = pLightmapPixelWriter->GetCurrentPixel();
			// we have to use memcpy because we're writing to non-cacheable memory,
			// but we can't even assume that the addresses we're writing to are
			// vector-aligned.
#ifdef memcpy // if someone's overriden the intrinsic, complain
#pragma error("You have overridden memcpy(), which is an XBOX360 intrinsic. This function will not behave optimally.")
#endif

			memcpy(pWriteInto, m_data[0], m_wordsGathered * sizeof(uint32));
			pWriteInto += m_bytesBetweenWriterRows;
			memcpy(pWriteInto, m_data[1], m_wordsGathered * sizeof(uint32));
			pWriteInto += m_bytesBetweenWriterRows;
			memcpy(pWriteInto, m_data[2], m_wordsGathered * sizeof(uint32));
			pWriteInto += m_bytesBetweenWriterRows;
			memcpy(pWriteInto, m_data[3], m_wordsGathered * sizeof(uint32));

			pLightmapPixelWriter->SkipBytes(m_wordsGathered * sizeof(uint32));
			m_wordsGathered = 0;
		}
	}

	// like commit, but the version we use when we know we're full.
	// Takes advantage of better compile-time generation for 
	// memcpy.
	void commitWhenFull(CPixelWriter * RESTRICT pLightmapPixelWriter) RESTRICT
	{
		unsigned char* RESTRICT pWriteInto = pLightmapPixelWriter->GetCurrentPixel();
		// we have to use memcpy because we're writing to non-cacheable memory,
		// but we can't even assume that the addresses we're writing to are
		// vector-aligned.
#ifdef memcpy // if someone's overriden the intrinsic, complain
#pragma error("You have overridden memcpy(), which is an XBOX360 intrinsic. This function will not behave optimally.")
#endif

		// if we're full, use compile-time known version of 
		// mempcy to take advantage of its ability to generate
		// inline code. In fact, use the dword-aligned
		// version so that we use the 64-bit writing funcs.
		Assert( m_wordsGathered == kWordsPerRow );
		COMPILE_TIME_ASSERT((kWordsPerRow & 3) == 0); // the number of words per row has to be a multiple of four
		
		memcpy(pWriteInto, reinterpret_cast<uint64* RESTRICT>(m_data[0]), kWordsPerRow * sizeof(uint32));
		pWriteInto += m_bytesBetweenWriterRows;
		memcpy(pWriteInto, reinterpret_cast<uint64* RESTRICT>(m_data[1]), kWordsPerRow * sizeof(uint32));
		pWriteInto += m_bytesBetweenWriterRows;
		memcpy(pWriteInto, reinterpret_cast<uint64* RESTRICT>(m_data[2]), kWordsPerRow * sizeof(uint32));
		pWriteInto += m_bytesBetweenWriterRows;
		memcpy(pWriteInto, reinterpret_cast<uint64* RESTRICT>(m_data[3]), kWordsPerRow * sizeof(uint32));
		
		pLightmapPixelWriter->SkipBytes(m_wordsGathered * sizeof(uint32));
		m_wordsGathered = 0;
	}

	// parameter: space between bump pages in the pixelwriter
	CPixelWriterStoreGather(int writerSizeBytes) : m_wordsGathered(0), m_bytesBetweenWriterRows(writerSizeBytes) {};

};
}


// this is a function for specifically writing bumped BGRA lightmaps -- in order for it
// to be properly scheduled, I needed to break out the inline functions. Also,
// to make the write-combined memory more efficient (and work around a bug in the
// April 2007 XDK), we need to store-gather our writes on the cache before blasting
// them out to write-combined memory. We can't simply write from the SIMD registers
// into the pixelwriter's data, because the difference between the output rows,
// eg nLightmap0WriterSizeBytes[0], might not be a multiple of 16. Unaligned stores
// to non-cacheable memory cause an alignment exception.
static void BumpedLightmapBitsToPixelWriter_HDRI_BGRA_X360( float* RESTRICT pFloatImage, float * RESTRICT pFloatImageBump1, float * RESTRICT pFloatImageBump2, 
													  float * RESTRICT pFloatImageBump3, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut,
													  CPixelWriter * RESTRICT m_LightmapPixelWriter)
{
	AssertMsg(m_LightmapPixelWriter->GetPixelSize() == 4, "BGRA format is no longer four bytes long? This is unsupported on 360, and probably immoral as well.");
	const int nLightmap0WriterSizeBytes = pLightmapSize[0] * 4 /*m_LightmapPixelWriter->GetPixelSize()*/;
	// const int nRewindToNextPixel = -( ( nLightmap0WriterSizeBytes * 3 ) - 4 );

	// assert that 1 * 4 = 4 
	COMPILE_TIME_ASSERT(sizeof( Vector4D ) == sizeof(float) * 4); 

	AssertMsg(!pfmOut, "Runtime conversion of lightmaps to files is no longer supported on 360.\n");

	
	// The store-gather buffers. Hopefully these will live in the L1
	// cache, which will make writing to them, then to memory, faster
	// than just using __stvewx to write directly into WC memory
	// one noncontiguous float at a time. (If there weren't a huge
	// compiler bug with __stvewx in the Apr07 XDK, that might not
	// be the case.)
	CPixelWriterStoreGather storeGather(nLightmap0WriterSizeBytes);

	for( int t = 0; t < pLightmapSize[1]; t++ )
	{
#define	FOUR (sizeof( Vector4D ) / sizeof( float ))  //  make explicit when we're incrementing by length of a 4dvec
		int srcTexelOffset = ( FOUR ) * ( 0 + t * pLightmapSize[0] );
		m_LightmapPixelWriter->Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

		// Our code works best when we can process luxels in groups of four. So,
		// figure out how many four-luxel groups we can process,
		// then do them in groups, then process the remainder.
		unsigned int groupsOfFourLimit = (((unsigned int)pLightmapSize[0]) & ~3);
		
		// we want to hang on to this index when we're done with groups so we can do the remainder.
		unsigned int s; // counts the number of luxels processed
		for( s = 0; 
			s < groupsOfFourLimit; 
			s += 4, srcTexelOffset += 4 * ( FOUR ))
		{				
			static const fltx4 vSixteen = {16.0f, 16.0f, 16.0f, 16.0f};
			// the store-gather simds
			fltx4 outBaseMap = Four_Zeros, outBump1 = Four_Zeros, outBump2 = Four_Zeros, outBump3 = Four_Zeros;
			// we'll read four at a time
			fltx4 vFloatImage[4], vFloatImageBump1[4], vFloatImageBump2[4], vFloatImageBump3[4];


			// stripe these loads to cause less ERAT thrashing
			vFloatImage[0]	  = LoadUnalignedSIMD(pFloatImage	   + srcTexelOffset );
			vFloatImage[1]	  = LoadUnalignedSIMD(pFloatImage	   + srcTexelOffset + 4 );
			vFloatImage[2]	  = LoadUnalignedSIMD(pFloatImage	   + srcTexelOffset + 8 );
			vFloatImage[3]	  = LoadUnalignedSIMD(pFloatImage	   + srcTexelOffset + 12 );

			vFloatImageBump1[0] = LoadUnalignedSIMD(pFloatImageBump1 + srcTexelOffset );
			vFloatImageBump1[1] = LoadUnalignedSIMD(pFloatImageBump1 + srcTexelOffset + 4 );
			vFloatImageBump1[2] = LoadUnalignedSIMD(pFloatImageBump1 + srcTexelOffset + 8 );
			vFloatImageBump1[3] = LoadUnalignedSIMD(pFloatImageBump1 + srcTexelOffset + 12 );

			vFloatImageBump2[0] = LoadUnalignedSIMD(pFloatImageBump2 + srcTexelOffset );
			vFloatImageBump2[1] = LoadUnalignedSIMD(pFloatImageBump2 + srcTexelOffset + 4 );
			vFloatImageBump2[2] = LoadUnalignedSIMD(pFloatImageBump2 + srcTexelOffset + 8 );
			vFloatImageBump2[3] = LoadUnalignedSIMD(pFloatImageBump2 + srcTexelOffset + 12 );

			vFloatImageBump3[0] = LoadUnalignedSIMD(pFloatImageBump3 + srcTexelOffset );
			vFloatImageBump3[1] = LoadUnalignedSIMD(pFloatImageBump3 + srcTexelOffset + 4 );
			vFloatImageBump3[2] = LoadUnalignedSIMD(pFloatImageBump3 + srcTexelOffset + 8 );
			vFloatImageBump3[3] = LoadUnalignedSIMD(pFloatImageBump3 + srcTexelOffset + 12 );

			// perform an arcane averaging operation upon the bump map values
			// (todo: make this not an inline so it will schedule better -- inlining is 
			//  done by the linker, which is too late for operation scheduling)
			ColorSpace::LinearToBumpedLightmap( vFloatImage[0],	vFloatImageBump1[0],
												vFloatImageBump2[0], vFloatImageBump3[0],
												// transform "in place":
												vFloatImage[0], vFloatImageBump1[0], 
												vFloatImageBump2[0], vFloatImageBump3[0] );
			ColorSpace::LinearToBumpedLightmap( vFloatImage[1],	vFloatImageBump1[1],
												vFloatImageBump2[1], vFloatImageBump3[1],
												// transform "in place":
												vFloatImage[1], vFloatImageBump1[1], 
												vFloatImageBump2[1], vFloatImageBump3[1] );
			ColorSpace::LinearToBumpedLightmap( vFloatImage[2],	vFloatImageBump1[2],
												vFloatImageBump2[2], vFloatImageBump3[2],
												// transform "in place":
												vFloatImage[2], vFloatImageBump1[2], 
												vFloatImageBump2[2], vFloatImageBump3[2] );
			ColorSpace::LinearToBumpedLightmap( vFloatImage[3],	vFloatImageBump1[3],
												vFloatImageBump2[3], vFloatImageBump3[3],
												// transform "in place":
												vFloatImage[3], vFloatImageBump1[3], 
												vFloatImageBump2[3], vFloatImageBump3[3] );
	

			// convert each color to RGB scaled.
			// DO NOT! make this into a for loop. The (April07 XDK) compiler
			// in fact DOES NOT unroll them, and will perform very naive
			// scheduling if you try. 

			// clamp to 0..16 float
			vFloatImage[0]		= MinSIMD(vFloatImage[0], vSixteen);
			vFloatImageBump1[0] = MinSIMD(vFloatImageBump1[0], vSixteen);
			vFloatImageBump2[0] = MinSIMD(vFloatImageBump2[0], vSixteen);
			vFloatImageBump3[0] = MinSIMD(vFloatImageBump3[0], vSixteen);

			vFloatImage[1]		= MinSIMD(vFloatImage[1], vSixteen);
			vFloatImageBump1[1] = MinSIMD(vFloatImageBump1[1], vSixteen);
			vFloatImageBump2[1] = MinSIMD(vFloatImageBump2[1], vSixteen);
			vFloatImageBump3[1] = MinSIMD(vFloatImageBump3[1], vSixteen);

			vFloatImage[2]		= MinSIMD(vFloatImage[2], vSixteen);
			vFloatImageBump1[2] = MinSIMD(vFloatImageBump1[2], vSixteen);
			vFloatImageBump2[2] = MinSIMD(vFloatImageBump2[2], vSixteen);
			vFloatImageBump3[2] = MinSIMD(vFloatImageBump3[2], vSixteen);

			vFloatImage[3]		= MinSIMD(vFloatImage[3], vSixteen);
			vFloatImageBump1[3] = MinSIMD(vFloatImageBump1[3], vSixteen);
			vFloatImageBump2[3] = MinSIMD(vFloatImageBump2[3], vSixteen);
			vFloatImageBump3[3] = MinSIMD(vFloatImageBump3[3], vSixteen);


			// compute the scaling factor, place it in w, and 
			// scale the rest by it. Obliterates whatever was
			// already in alpha.
			// This code is why it is important to not use a for
			// loop: you need to let the compiler keep the value
			// on registers (which it can't do if you use a
			// variable indexed array) and interleave the
			// inlined instructions.

			vFloatImage[0]		= PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImage[0]) );
			vFloatImageBump1[0] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump1[0]) );
			vFloatImageBump2[0] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump2[0]) );
			vFloatImageBump3[0] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump3[0]) );

			vFloatImage[1]		= PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImage[1]) );
			vFloatImageBump1[1] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump1[1]) );
			vFloatImageBump2[1] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump2[1]) );
			vFloatImageBump3[1] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump3[1]) );

			vFloatImage[2]		= PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImage[2]) );
			vFloatImageBump1[2] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump1[2]) );
			vFloatImageBump2[2] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump2[2]) );
			vFloatImageBump3[2] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump3[2]) );

			vFloatImage[3]		= PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImage[3]) );
			vFloatImageBump1[3] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump1[3]) );
			vFloatImageBump2[3] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump2[3]) );
			vFloatImageBump3[3] = PackPixel_BGRA8888( ConvertLightmapColorToRGBScale(vFloatImageBump3[3]) );

			// Each of the registers above contains one RGBA 32-bit struct
			// in their w word. So, combine them such that each of the assignees
			// below contains four RGBAs, in xyzw order (big-endian).

			outBaseMap = __vrlimi(outBaseMap, vFloatImage[0], 8, 3 ); // insert into x
			outBump1 =	 __vrlimi(outBump1, vFloatImageBump1[0], 8, 3 ); // insert into x
			outBump2 =	 __vrlimi(outBump2, vFloatImageBump2[0], 8, 3 ); // insert into x
			outBump3 =	 __vrlimi(outBump3, vFloatImageBump3[0], 8, 3 ); // insert into x

			outBaseMap = __vrlimi(outBaseMap, vFloatImage[1], 4, 2 ); // insert into y
			outBump1 =	 __vrlimi(outBump1, vFloatImageBump1[1], 4, 2 ); // insert into y
			outBump2 =	 __vrlimi(outBump2, vFloatImageBump2[1], 4, 2 ); // insert into y
			outBump3 =	 __vrlimi(outBump3, vFloatImageBump3[1], 4, 2 ); // insert into y

			outBaseMap = __vrlimi(outBaseMap, vFloatImage[2], 2, 1 ); // insert into z
			outBump1 =	 __vrlimi(outBump1, vFloatImageBump1[2], 2, 1 ); // insert into z
			outBump2 =	 __vrlimi(outBump2, vFloatImageBump2[2], 2, 1 ); // insert into z
			outBump3 =	 __vrlimi(outBump3, vFloatImageBump3[2], 2, 1 ); // insert into z

			outBaseMap = __vrlimi(outBaseMap, vFloatImage[3], 1, 0 ); // insert into w
			outBump1 =	 __vrlimi(outBump1, vFloatImageBump1[3], 1, 0 ); // insert into w
			outBump2 =	 __vrlimi(outBump2, vFloatImageBump2[3], 1, 0 ); // insert into w
			outBump3 =	 __vrlimi(outBump3, vFloatImageBump3[3], 1, 0 ); // insert into w

			// push the data through the store-gather buffer.
			storeGather.write(m_LightmapPixelWriter, outBaseMap, outBump1, outBump2, outBump3);

		}

		// Once here, make sure we've committed any leftover changes, then process
		// the remainders singly.
		storeGather.commit(m_LightmapPixelWriter);

		for( ;  // s is where it should be from the loop above
			s < (unsigned int) pLightmapSize[0]; 
			s++, 
				// m_LightmapPixelWriter->SkipBytes(nRewindToNextPixel), // now handled by store-gather
				srcTexelOffset += ( FOUR ))
		{				

			static const fltx4 vSixteen = {16.0f, 16.0f, 16.0f, 16.0f};
			fltx4 vColor[4];
			fltx4 vFloatImage = LoadUnalignedSIMD(&pFloatImage[srcTexelOffset]);
			fltx4 vFloatImageBump1 = LoadUnalignedSIMD(&pFloatImageBump1[srcTexelOffset]);
			fltx4 vFloatImageBump2 = LoadUnalignedSIMD(&pFloatImageBump2[srcTexelOffset]);
			fltx4 vFloatImageBump3 = LoadUnalignedSIMD(&pFloatImageBump3[srcTexelOffset]);

			// perform an arcane averaging operation upon the bump map values
			ColorSpace::LinearToBumpedLightmap( vFloatImage,
				vFloatImageBump1, vFloatImageBump2,
				vFloatImageBump3,
				vColor[0], vColor[1], vColor[2], vColor[3] );		

			// convert each color to RGB scaled.
			// DO NOT! make this into a for loop. The (April07 XDK) compiler
			// in fact DOES NOT unroll them, and will perform very naive
			// scheduling if you try. 

			// clamp to 0..16 float
			vColor[0] = MinSIMD(vColor[0], vSixteen);
			vColor[1] = MinSIMD(vColor[1], vSixteen);
			vColor[2] = MinSIMD(vColor[2], vSixteen);
			vColor[3] = MinSIMD(vColor[3], vSixteen);

			// compute the scaling factor, place it in w, and 
			// scale the rest by it. Obliterates whatever was
			// already in alpha.
			// This code is why it is important to not use a for
			// loop: you need to let the compiler interleave the
			// inlined instructions.
			vColor[0] = ConvertLightmapColorToRGBScale( vColor[0] );
			vColor[1] = ConvertLightmapColorToRGBScale( vColor[1] );
			vColor[2] = ConvertLightmapColorToRGBScale( vColor[2] );
			vColor[3] = ConvertLightmapColorToRGBScale( vColor[3] );


#ifdef X360_DOUBLECHECK_LIGHTMAPS
			unsigned short color[4][4];

			ColorSpace::LinearToBumpedLightmap( &pFloatImage[srcTexelOffset],
				&pFloatImageBump1[srcTexelOffset], &pFloatImageBump2[srcTexelOffset],
				&pFloatImageBump3[srcTexelOffset],
				color[0], color[1], color[2], color[3] );
			unsigned short alpha = ColorSpace::LinearToUnsignedShort( pFloatImage[srcTexelOffset+3], 16 );
			color[0][3] = color[1][3] = color[2][3] = color[3][3] = alpha;

			if( IsX360() )
			{
				for( int i = 0; i != 4; ++i )
				{
					Vector4D vRGBScale;

					vRGBScale.x = color[i][0] * (16.0f / 65535.0f);
					vRGBScale.y = color[i][1] * (16.0f / 65535.0f);
					vRGBScale.z = color[i][2] * (16.0f / 65535.0f);
					vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );
					color[i][0] = RoundFloatToByte( vRGBScale.x * 255.0f );
					color[i][1] = RoundFloatToByte( vRGBScale.y * 255.0f );
					color[i][2] = RoundFloatToByte( vRGBScale.z * 255.0f );
					color[i][3] = RoundFloatToByte( vRGBScale.w * 255.0f );
				}						
			}

			/*
			for (int ii = 0; ii < 4; ++ii)
			{
				uint32 pack = (PackPixel_BGRA8888( vColor[ii] ).u[3]);
				if (color[ii][3] != 0)
				Assert(	color[ii][0] == (pack & 0xFF0000) >> 16	&& 
						color[ii][1] == (pack & 0xFF00) >> 8		&& 
						color[ii][2] == (pack & 0xFF)				&& 
						color[ii][3] == (pack & 0xFF000000) >> 24 );
			}
			*/

#endif


				vColor[0] = PackPixel_BGRA8888( vColor[0] );
				vColor[1] = PackPixel_BGRA8888( vColor[1] );
				vColor[2] = PackPixel_BGRA8888( vColor[2] );
				vColor[3] = PackPixel_BGRA8888( vColor[3] );

				storeGather.writeJustW(m_LightmapPixelWriter, vColor[0], vColor[1], vColor[2], vColor[3] );

				/* // here is the old way of writing pixels:
				// now we store-gather this
				m_LightmapPixelWriter->WritePixelNoAdvance_BGRA8888( vColor[0] );
				Assert(*reinterpret_cast<unsigned int *>(m_LightmapPixelWriter->GetCurrentPixel()) == PackPixel_BGRA8888( vColor[0] ).u[3] );
				void * RESTRICT pBits = m_LightmapPixelWriter->SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter->WritePixelNoAdvance_BGRA8888( vColor[1], pBits );
				Assert(*reinterpret_cast<unsigned int *>(m_LightmapPixelWriter->GetCurrentPixel()) == PackPixel_BGRA8888( vColor[1] ).u[3] );
				pBits = m_LightmapPixelWriter->SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter->WritePixelNoAdvance_BGRA8888( vColor[2], pBits );
				Assert(*reinterpret_cast<unsigned int *>(m_LightmapPixelWriter->GetCurrentPixel()) == PackPixel_BGRA8888( vColor[2] ).u[3] );
				pBits = m_LightmapPixelWriter->SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter->WritePixelNoAdvance_BGRA8888( vColor[3], pBits );
				Assert(*reinterpret_cast<unsigned int *>(m_LightmapPixelWriter->GetCurrentPixel()) == PackPixel_BGRA8888( vColor[3] ).u[3] );

				m_LightmapPixelWriter->SkipBytes(nRewindToNextPixel);
				*/
		}

		storeGather.commit(m_LightmapPixelWriter);

	}
}

#endif // _X360

// write bumped lightmap update to HDR integer lightmap
void CMatLightmaps::BumpedLightmapBitsToPixelWriter_HDRI( float* RESTRICT pFloatImage, float * RESTRICT pFloatImageBump1, float * RESTRICT pFloatImageBump2, 
												 float * RESTRICT pFloatImageBump3, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut ) RESTRICT
{
	const int nLightmapSize0 = pLightmapSize[0];
	const int nLightmap0WriterSizeBytes = nLightmapSize0 * m_LightmapPixelWriter.GetPixelSize();
	const int nRewindToNextPixel = -( ( nLightmap0WriterSizeBytes * 3 ) - m_LightmapPixelWriter.GetPixelSize() );

	if( m_LightmapPixelWriter.IsUsingFloatFormat() )
	{
		AssertMsg(!IsX360(), "Tried to use a floating-point pixel format for lightmaps on 360, which is not supported.");
		if (!IsX360())
		{
			for( int t = 0; t < pLightmapSize[1]; t++ )
			{
				int srcTexelOffset = ( sizeof( Vector4D ) / sizeof( float ) ) * ( 0 + t * nLightmapSize0 );
				m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

				for( int s = 0; 
					s < nLightmapSize0; 
					s++, m_LightmapPixelWriter.SkipBytes(nRewindToNextPixel),srcTexelOffset += (sizeof(Vector4D)/sizeof(float)))
				{
					unsigned short color[4][4];

					ColorSpace::LinearToBumpedLightmap( &pFloatImage[srcTexelOffset],
						&pFloatImageBump1[srcTexelOffset], &pFloatImageBump2[srcTexelOffset],
						&pFloatImageBump3[srcTexelOffset],
						color[0], color[1], color[2], color[3] );
					float alpha = pFloatImage[srcTexelOffset+3];
					Assert( alpha >= 0.0f && alpha <= 1.0f );
					color[0][3] = color[1][3] = color[2][3] = color[3][3] = alpha;

					float toFloat = ( 1.0f / ( float )( 1 << 16 ) );

					/* // This code is now a can't-happen, because we do not allow float formats on 360.
#if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
					if( IsX360() )
					{
						for( int i = 0; i != 4; ++i )
						{
							Vector4D vRGBScale;

							vRGBScale.x = color[i][0] * (16.0f / 65535.0f);
							vRGBScale.y = color[i][1] * (16.0f / 65535.0f);
							vRGBScale.z = color[i][2] * (16.0f / 65535.0f);
							vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );
							color[i][0] = RoundFloatToByte( vRGBScale.x * 255.0f );
							color[i][1] = RoundFloatToByte( vRGBScale.y * 255.0f );
							color[i][2] = RoundFloatToByte( vRGBScale.z * 255.0f );
							color[i][3] = RoundFloatToByte( vRGBScale.w * 255.0f );
						}

						toFloat = ( 1.0f / ( float )( 1 << 8 ) );
					}
#endif
					*/

					m_LightmapPixelWriter.WritePixelNoAdvanceF( toFloat * color[0][0], toFloat * color[0][1], toFloat * color[0][2], toFloat * color[0][3] );

					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvanceF( toFloat * color[1][0], toFloat * color[1][1], toFloat * color[1][2], toFloat * color[1][3] );

					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvanceF( toFloat * color[2][0], toFloat * color[2][1], toFloat * color[2][2], toFloat * color[2][3] );

					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvanceF( toFloat * color[3][0], toFloat * color[3][1], toFloat * color[3][2], toFloat * color[3][3] );
				}
			}
		}
	}
	else
	{
#ifndef X360_USE_SIMD_LIGHTMAP
		for( int t = 0; t < pLightmapSize[1]; t++ )
		{
			int srcTexelOffset = ( sizeof( Vector4D ) / sizeof( float ) ) * ( 0 + t * nLightmapSize0 );
			m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

			for( int s = 0; 
				s < nLightmapSize0; 
				s++, m_LightmapPixelWriter.SkipBytes(nRewindToNextPixel),srcTexelOffset += (sizeof(Vector4D)/sizeof(float)))
			{					
				unsigned short color[4][4];

				ColorSpace::LinearToBumpedLightmap( &pFloatImage[srcTexelOffset],
					&pFloatImageBump1[srcTexelOffset], &pFloatImageBump2[srcTexelOffset],
					&pFloatImageBump3[srcTexelOffset],
					color[0], color[1], color[2], color[3] );
				unsigned short alpha = ColorSpace::LinearToUnsignedShort( pFloatImage[srcTexelOffset+3], 16 );
				color[0][3] = color[1][3] = color[2][3] = color[3][3] = alpha;

#if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
				if( IsX360() )
				{
					for( int i = 0; i != 4; ++i )
					{
						Vector4D vRGBScale;

						vRGBScale.x = color[i][0] * (16.0f / 65535.0f);
						vRGBScale.y = color[i][1] * (16.0f / 65535.0f);
						vRGBScale.z = color[i][2] * (16.0f / 65535.0f);
						vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );
						color[i][0] = RoundFloatToByte( vRGBScale.x * 255.0f );
						color[i][1] = RoundFloatToByte( vRGBScale.y * 255.0f );
						color[i][2] = RoundFloatToByte( vRGBScale.z * 255.0f );
						color[i][3] = RoundFloatToByte( vRGBScale.w * 255.0f );
					}						
				}
#endif
				m_LightmapPixelWriter.WritePixelNoAdvance( color[0][0], color[0][1], color[0][2], color[0][3] );

				m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter.WritePixelNoAdvance( color[1][0], color[1][1], color[1][2], color[1][3] );

				m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter.WritePixelNoAdvance( color[2][0], color[2][1], color[2][2], color[2][3] );

				m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
				m_LightmapPixelWriter.WritePixelNoAdvance( color[3][0], color[3][1], color[3][2], color[3][3] );

				// Write data to the bitmapped represenations so that PFM files can be written
				if ( pfmOut )
				{
					PixRGBAF pixelData;
					pixelData.Red = color[0][0];                  
					pixelData.Green = color[0][1];                  
					pixelData.Blue = color[0][2];
					pixelData.Alpha = alpha;
					pfmOut->WritePixelRGBAF(pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData);
				}
			}
		}
#else
		// this is an optimized XBOX implementation. For a clearer
		// presentation of the algorithm, see the PC implementation
		// above.
		// First check for the most common case, using an efficient
		// branch rather than a switch:
		if (m_LightmapPixelWriter.GetFormat() == IMAGE_FORMAT_LINEAR_BGRA8888)
		{
			// broken out into a static to make things more readable
			// and be nicer to the instruction cache
			BumpedLightmapBitsToPixelWriter_HDRI_BGRA_X360( pFloatImage, pFloatImageBump1, pFloatImageBump2, 
				pFloatImageBump3, pLightmapSize, pOffsetIntoLightmapPage, pfmOut, &m_LightmapPixelWriter );
		}
		else
		{	// This case should actually never be hit -- we do not use RGBA.
			for( int t = 0; t < pLightmapSize[1]; t++ )
			{
				// assert that 1 * 4 = 4 
				COMPILE_TIME_ASSERT(sizeof( Vector4D ) == sizeof(float) * 4); 
#define	FOUR (sizeof( Vector4D ) / sizeof( float ))  // in case this ever changes
				int srcTexelOffset = ( FOUR ) * ( 0 + t * nLightmapSize0 );
				m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );

				for( int s = 0; 
					s < nLightmapSize0; 
					s++, m_LightmapPixelWriter.SkipBytes(nRewindToNextPixel),srcTexelOffset += ( FOUR ))
				{				

					static const fltx4 vSixteen = {16.0f, 16.0f, 16.0f, 16.0f};
					fltx4 vColor[4];
					fltx4 vFloatImage = LoadUnalignedSIMD(&pFloatImage[srcTexelOffset]);
					fltx4 vFloatImageBump1 = LoadUnalignedSIMD(&pFloatImageBump1[srcTexelOffset]);
					fltx4 vFloatImageBump2 = LoadUnalignedSIMD(&pFloatImageBump2[srcTexelOffset]);
					fltx4 vFloatImageBump3 = LoadUnalignedSIMD(&pFloatImageBump3[srcTexelOffset]);
					
					// perform an arcane averaging operation upon the bump map values
					ColorSpace::LinearToBumpedLightmap( vFloatImage,
						vFloatImageBump1, vFloatImageBump2,
						vFloatImageBump3,
						vColor[0], vColor[1], vColor[2], vColor[3] );		

					// convert each color to RGB scaled.
					// DO NOT! make this into a for loop. The (April07 XDK) compiler
					// in fact DOES NOT unroll them, and will perform very naive
					// scheduling if you try. 

					// clamp to 0..16 float
					vColor[0] = MinSIMD(vColor[0], vSixteen);
					vColor[1] = MinSIMD(vColor[1], vSixteen);
					vColor[2] = MinSIMD(vColor[2], vSixteen);
					vColor[3] = MinSIMD(vColor[3], vSixteen);

					// compute the scaling factor, transform the RGB,
					// and place the scale in w. Obliterates whatever was
					// already in alpha.
					// This code is why it is important to not use a for
					// loop: you need to let the compiler interleave the
					// inlined instructions.
					vColor[0] = ConvertLightmapColorToRGBScale( vColor[0] );
					vColor[1] = ConvertLightmapColorToRGBScale( vColor[1] );
					vColor[2] = ConvertLightmapColorToRGBScale( vColor[2] );
					vColor[3] = ConvertLightmapColorToRGBScale( vColor[3] );


					m_LightmapPixelWriter.WritePixelNoAdvance( vColor[0] );
					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvance( vColor[1] );
					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvance( vColor[2] );
					m_LightmapPixelWriter.SkipBytes( nLightmap0WriterSizeBytes );
					m_LightmapPixelWriter.WritePixelNoAdvance( vColor[3] );

					AssertMsg(!pfmOut, "Runtime conversion of lightmaps to files is no longer supported on 360.\n");

					// Write data to the bitmapped represenations so that PFM files can be written
					if ( pfmOut )
					{
						Warning("**************************************************\n"
								"Lightmap output to files on 360 HAS BEEN DISABLED.\n"
								"A grave error has just occurred.\n"
								"**************************************************\n");
						DebuggerBreakIfDebugging();
						/*
						PixRGBAF pixelData;
						pixelData.Red = color[0][0];                  
						pixelData.Green = color[0][1];                  
						pixelData.Blue = color[0][2];
						pixelData.Alpha = alpha;
						pfmOut->WritePixelRGBAF(pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData);
						*/
					}
				}
			}
		}
#endif
	}
}


void CMatLightmaps::LightmapBitsToPixelWriter_LDR( float* pFloatImage, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut )
{
	// non-HDR lightmap processing
	float *pSrc = pFloatImage;
	for( int t = 0; t < pLightmapSize[1]; ++t )
	{
		m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
		for( int s = 0; s < pLightmapSize[0]; ++s, pSrc += (sizeof(Vector4D)/sizeof(*pSrc)) )
		{
			unsigned char color[4];
			ColorSpace::LinearToLightmap( color, pSrc );
			color[3] =  RoundFloatToByte( pSrc[3] * 255.0f );
			m_LightmapPixelWriter.WritePixel( color[0], color[1], color[2], color[3] );

			if ( pfmOut )
			{
				// Write data to the bitmapped represenations so that PFM files can be written
				PixRGBAF pixelData;
				pixelData.Red = color[0];                  
				pixelData.Green = color[1];                  
				pixelData.Blue = color[2];
				pixelData.Alpha = color[3];
				pfmOut->WritePixelRGBAF( pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData );
			}
		}
	}
}


void CMatLightmaps::LightmapBitsToPixelWriter_HDRF( float* pFloatImage, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t *pfmOut )
{
	if ( IsX360() )
	{
		// 360 does not support HDR float 
		Assert( 0 );
		return;
	}

	// float HDR lightmap processing
	float *pSrc = pFloatImage;
	for ( int t = 0; t < pLightmapSize[1]; ++t )
	{
		m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
		for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += (sizeof(Vector4D)/sizeof(*pSrc)) )
		{
			m_LightmapPixelWriter.WritePixelF( pSrc[0], pSrc[1], pSrc[2], pSrc[3] );
		}
	}
}

// numbers come in on the domain [0..16]
void CMatLightmaps::LightmapBitsToPixelWriter_HDRI( float* RESTRICT pFloatImage, int pLightmapSize[2], int pOffsetIntoLightmapPage[2], FloatBitMap_t * RESTRICT pfmOut )
{
#ifndef X360_USE_SIMD_LIGHTMAP
	// PC code (and old, pre-SIMD xbox version -- unshippably slow)
	if ( m_LightmapPixelWriter.IsUsingFloatFormat() )
	{
		// integer HDR lightmap processing
		float *pSrc = pFloatImage;
		for ( int t = 0; t < pLightmapSize[1]; ++t )
		{
			m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
			for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += (sizeof(Vector4D)/sizeof(*pSrc)) )
			{
				int r, g, b, a;

				r = ColorSpace::LinearFloatToCorrectedShort( pSrc[0] );
				g = ColorSpace::LinearFloatToCorrectedShort( pSrc[1] );
				b = ColorSpace::LinearFloatToCorrectedShort( pSrc[2] );
				a = ColorSpace::LinearToUnsignedShort( pSrc[3], 16 );

				float toFloat = ( 1.0f / ( float )( 1 << 16 ) );

#if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
				if( IsX360() )
				{
					Vector4D vRGBScale;

					vRGBScale.x = r * (16.0f / 65535.0f);
					vRGBScale.y = g * (16.0f / 65535.0f);
					vRGBScale.z = b * (16.0f / 65535.0f);
					vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );

					r = RoundFloatToByte( vRGBScale.x * 255.0f );
					g = RoundFloatToByte( vRGBScale.y * 255.0f );
					b = RoundFloatToByte( vRGBScale.z * 255.0f );
					a = RoundFloatToByte( vRGBScale.w * 255.0f );

					toFloat = ( 1.0f / ( float )( 1 << 8 ) );
				}

#endif
				Assert( pSrc[3] >= 0.0f && pSrc[3] <= 1.0f );
				m_LightmapPixelWriter.WritePixelF( r * toFloat, g * toFloat, b * toFloat, pSrc[3] );
			}
		}
	}
	else
	{
		// integer HDR lightmap processing
		float *pSrc = pFloatImage;
		for ( int t = 0; t < pLightmapSize[1]; ++t )
		{
			m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
			for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += (sizeof(Vector4D)/sizeof(*pSrc)) )
			{
				int r, g, b, a;

				r = ColorSpace::LinearFloatToCorrectedShort( pSrc[0] );
				g = ColorSpace::LinearFloatToCorrectedShort( pSrc[1] );
				b = ColorSpace::LinearFloatToCorrectedShort( pSrc[2] );
				a = ColorSpace::LinearToUnsignedShort( pSrc[3], 16 );

#if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
				if( IsX360() )
				{
					Vector4D vRGBScale;

					vRGBScale.x = r * (16.0f / 65535.0f);
					vRGBScale.y = g * (16.0f / 65535.0f);
					vRGBScale.z = b * (16.0f / 65535.0f);
					vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );

					r = RoundFloatToByte( vRGBScale.x * 255.0f );
					g = RoundFloatToByte( vRGBScale.y * 255.0f );
					b = RoundFloatToByte( vRGBScale.z * 255.0f );
					a = RoundFloatToByte( vRGBScale.w * 255.0f );
				}
#endif
				m_LightmapPixelWriter.WritePixel( r, g, b, a );

				if ( pfmOut )
				{
					// Write data to the bitmapped represenations so that PFM files can be written
					PixRGBAF pixelData;
					pixelData.Red = pSrc[0];                  
					pixelData.Green = pSrc[1];                  
					pixelData.Blue = pSrc[2];
					pixelData.Alpha = pSrc[3];
					pfmOut->WritePixelRGBAF( pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData );
				}				
			}
		}
	}
#else
	// XBOX360 code
	if ( m_LightmapPixelWriter.IsUsingFloatFormat() )
	{
		if( IsX360() )
		{
			AssertMsg( false, "Float-format pixel writers do not exist on x360." );
		}
		else
		{	// This code is here as an example only, in case floating point
			// format is restored to 360.

			// integer HDR lightmap processing
			float * RESTRICT pSrc = pFloatImage;
			for ( int t = 0; t < pLightmapSize[1]; ++t )
			{
				m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
				for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += (sizeof(Vector4D)/sizeof(*pSrc)) )
				{
					int r, g, b, a;

					r = ColorSpace::LinearFloatToCorrectedShort( pSrc[0] );
					g = ColorSpace::LinearFloatToCorrectedShort( pSrc[1] );
					b = ColorSpace::LinearFloatToCorrectedShort( pSrc[2] );
					a = ColorSpace::LinearToUnsignedShort( pSrc[3], 16 );

					float toFloat = ( 1.0f / ( float )( 1 << 16 ) );

#if ( defined( USE_32BIT_LIGHTMAPS_ON_360 ) )
					if( IsX360() )
					{
						Vector4D vRGBScale;

						vRGBScale.x = r * (16.0f / 65535.0f);
						vRGBScale.y = g * (16.0f / 65535.0f);
						vRGBScale.z = b * (16.0f / 65535.0f);
						vRGBScale = ConvertLightmapColorToRGBScale( &vRGBScale.x );

						r = RoundFloatToByte( vRGBScale.x * 255.0f );
						g = RoundFloatToByte( vRGBScale.y * 255.0f );
						b = RoundFloatToByte( vRGBScale.z * 255.0f );
						a = RoundFloatToByte( vRGBScale.w * 255.0f );

						toFloat = ( 1.0f / ( float )( 1 << 8 ) );
					}

#endif
					Assert( pSrc[3] >= 0.0f && pSrc[3] <= 1.0f );
					m_LightmapPixelWriter.WritePixelF( r * toFloat, g * toFloat, b * toFloat, pSrc[3] );
				}
			}
		}
	}
	else
	{
		// This is the fast X360 pathway.

		// integer HDR lightmap processing
		float * RESTRICT pSrc = pFloatImage;
		// Assert((reinterpret_cast<unsigned int>(pSrc) & 15) == 0); // 16-byte aligned?
		COMPILE_TIME_ASSERT(sizeof(Vector4D)/sizeof(*pSrc) == 4); // assert that 1 * 4 = 4
#ifndef USE_32BIT_LIGHTMAPS_ON_360 
#pragma error("This function only supports 32 bit lightmaps.")
#endif

		// input numbers from pSrc are on the domain [0..+inf]
		// we clamp them to the range [0..16]
		// output is RGBA 
		// the shader does this: rOut = Rin * Ain * 16.0f 
		// where Rin is [0..1], a float computed from a byte value [0..255]
		// Ain is therefore the brightest channel (say R) divided by 16 and quantized
		// Rin is computed from pSrc->r by dividing by Ain
		
		// rather than switching inside WritePixel for each different format,
		// thus causing a 23-cycle pipeline clear for every pixel, we'll
		// branch on the format here. That will allow us to unroll the inline
		// pixel write functions differently depending on their different 
		// latencies. 

		Assert(!pfmOut); // should never happen on 360.
#ifndef ALLOW_PFM_OUTPUT_ON_360
		if ( pfmOut )
		{
			Warning("*****************************************\n"
					"Lightmap output on 360 HAS BEEN DISABLED.\n"
					"A grave error has just occurred.\n"
					"*****************************************\n");
		}
#endif

		// switch once, here, outside the loop, rather than
		// switching inside each pixel. Switches are not fast
		// on x360: they are usually implemented as jumps 
		// through function tables, which have a 24-cycle
		// stall. 
		switch (m_LightmapPixelWriter.GetFormat())
		{
			// note: format names are low-order-byte first. 
		case IMAGE_FORMAT_RGBA8888:
		case IMAGE_FORMAT_LINEAR_RGBA8888:
		{
			for ( int t = 0; t < pLightmapSize[1]; ++t )
			{
				m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
				for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += 4 )
				{	
					static const fltx4 vSixteen = {16.0f, 16.0f, 16.0f, 16.0f};
					fltx4 rgba = LoadUnalignedSIMD(pSrc);

					// clamp to 0..16 float
					rgba = MinSIMD(rgba, vSixteen);
					// compute the scaling factor, place it in w, and 
					// scale the rest by it.
					rgba = ConvertLightmapColorToRGBScale( rgba );
					// rgba is now  float 0..255 in each component
					m_LightmapPixelWriter.WritePixelNoAdvance_RGBA8888(rgba);


					/*  // not supported on X360
					if ( pfmOut )
					{
						// Write data to the bitmapped represenations so that PFM files can be written
						PixRGBAF pixelData;
						XMStoreVector4(&pixelData,rgba);
						pfmOut->WritePixelRGBAF( pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData );
					}			
					*/
				}
			}
			break;
		}

		case IMAGE_FORMAT_BGRA8888: // NOTE! : the low order bits are first in this naming convention.
		case IMAGE_FORMAT_LINEAR_BGRA8888:
		{			
			for ( int t = 0; t < pLightmapSize[1]; ++t )
			{
				m_LightmapPixelWriter.Seek( pOffsetIntoLightmapPage[0], pOffsetIntoLightmapPage[1] + t );
				for ( int s = 0; s < pLightmapSize[0]; ++s, pSrc += 4 )
				{	
					static const fltx4 vSixteen = {16.0f, 16.0f, 16.0f, 16.0f};
					fltx4 rgba = LoadUnalignedSIMD(pSrc);

					// clamp to 0..16 float
					rgba = MinSIMD(rgba, vSixteen);
					// compute the scaling factor, place it in w, and 
					// scale the rest by it.
					rgba = ConvertLightmapColorToRGBScale( rgba );
					// rgba is now  float 0..255 in each component
					m_LightmapPixelWriter.WritePixelNoAdvance_BGRA8888(rgba);
					// forcibly advance
					m_LightmapPixelWriter.SkipBytes(4);

					/* // not supported on X360
					if ( pfmOut )
					{
						// Write data to the bitmapped represenations so that PFM files can be written
						PixRGBAF pixelData;
						XMStoreVector4(&pixelData,rgba);
						pfmOut->WritePixelRGBAF( pOffsetIntoLightmapPage[0] + s, pOffsetIntoLightmapPage[1] + t, pixelData );
					}			
					*/
				}
			}
			break;
		}

		default:
			AssertMsg1(false,"Unsupported pixel format %d while writing lightmaps!", m_LightmapPixelWriter.GetFormat() );
			Warning("Unsupported pixel format used in lightmap. Lightmaps could not be downloaded.\n");
			break;
		}
	}
#endif
}

void CMatLightmaps::BeginUpdateLightmaps( void )
{
	CMatCallQueue *pCallQueue = GetMaterialSystem()->GetRenderContextInternal()->GetCallQueueInternal();
	if ( pCallQueue )
	{
		pCallQueue->QueueCall( this, &CMatLightmaps::BeginUpdateLightmaps );
		return;
	}

	m_nUpdatingLightmapsStackDepth++;
}

void CMatLightmaps::EndUpdateLightmaps( void )
{
	CMatCallQueue *pCallQueue = GetMaterialSystem()->GetRenderContextInternal()->GetCallQueueInternal();
	if ( pCallQueue )
	{
		pCallQueue->QueueCall( this, &CMatLightmaps::EndUpdateLightmaps );
		return;
	}

	m_nUpdatingLightmapsStackDepth--;
	Assert( m_nUpdatingLightmapsStackDepth >= 0 );
	if( m_nUpdatingLightmapsStackDepth <= 0 && m_nLockedLightmap != -1 )
	{
		g_pShaderAPI->TexUnlock();
		m_nLockedLightmap = -1;
	}
}

int CMatLightmaps::AllocateDynamicLightmap( int lightmapSize[2], int *pOutOffsetIntoPage, int frameID )
{
	// check frameID, fail if current
	for ( int i = 0; i < COUNT_DYNAMIC_LIGHTMAP_PAGES; i++ )
	{
		int dynamicIndex = (m_dynamic.currentDynamicIndex + i) % COUNT_DYNAMIC_LIGHTMAP_PAGES;
		int lightmapPageIndex = m_firstDynamicLightmap + dynamicIndex;
		if ( m_dynamic.lightmapLockFrame[dynamicIndex] != frameID )
		{
			m_dynamic.lightmapLockFrame[dynamicIndex] = frameID;
			m_dynamic.imagePackers[dynamicIndex].Reset( 0, m_pLightmapPages[lightmapPageIndex].m_Width, m_pLightmapPages[lightmapPageIndex].m_Height );
		}

		if ( m_dynamic.imagePackers[dynamicIndex].AddBlock( lightmapSize[0], lightmapSize[1], &pOutOffsetIntoPage[0], &pOutOffsetIntoPage[1] ) )
		{
			return lightmapPageIndex;
		}
	}
	
	return -1;
}

//-----------------------------------------------------------------------------
// Updates the lightmap
//-----------------------------------------------------------------------------
void CMatLightmaps::UpdateLightmap( int lightmapPageID, int lightmapSize[2],
									  int offsetIntoLightmapPage[2], 
									  float *pFloatImage, float *pFloatImageBump1,
									  float *pFloatImageBump2, float *pFloatImageBump3 )
{
	VPROF( "CMatRenderContext::UpdateLightmap" );

	bool hasBump = false;
	int uSize = 1;
	FloatBitMap_t *pfmOut = NULL;
	if ( pFloatImageBump1 && pFloatImageBump2 && pFloatImageBump3 )
	{
		hasBump = true;
		uSize = 4;
	}

	if ( lightmapPageID >= GetNumLightmapPages() || lightmapPageID < 0 )
	{
		Error( "MaterialSystem_Interface_t::UpdateLightmap lightmapPageID=%d out of range\n", lightmapPageID );
		return;
	}
	bool bDynamic = IsDynamicLightmap(lightmapPageID);

	if ( bDynamic )
	{
		int dynamicIndex = lightmapPageID-m_firstDynamicLightmap;
		Assert(dynamicIndex < COUNT_DYNAMIC_LIGHTMAP_PAGES);
		m_dynamic.currentDynamicIndex = (dynamicIndex + 1) % COUNT_DYNAMIC_LIGHTMAP_PAGES;
	}

	if ( mat_lightmap_pfms.GetBool())
	{
		// Allocate and initialize lightmap data that will be written to a PFM file
		if (NULL == m_pLightmapDataPtrArray[lightmapPageID])
		{
			m_pLightmapDataPtrArray[lightmapPageID] = new FloatBitMap_t(m_pLightmapPages[lightmapPageID].m_Width, m_pLightmapPages[lightmapPageID].m_Height);
			m_pLightmapDataPtrArray[lightmapPageID]->Clear(0, 0, 0, 1);
		}
		pfmOut = m_pLightmapDataPtrArray[lightmapPageID];
	}

	// NOTE: Change how the lock is taking place if you ever change how bumped
	// lightmaps are put into the page. Right now, we assume that they're all
	// added to the right of the original lightmap.
	bool bLockSubRect;
	{
		VPROF_( "Locking lightmaps", 2, VPROF_BUDGETGROUP_DLIGHT_RENDERING, false, 0 ); // vprof scope

		bLockSubRect = m_nUpdatingLightmapsStackDepth <= 0 && !bDynamic;
		if( bLockSubRect )
		{
			VPROF_INCREMENT_COUNTER( "lightmap subrect texlock", 1 );
			g_pShaderAPI->ModifyTexture( m_LightmapPageTextureHandles[lightmapPageID] );
			if (!g_pShaderAPI->TexLock( 0, 0, offsetIntoLightmapPage[0], offsetIntoLightmapPage[1],
				lightmapSize[0] * uSize, lightmapSize[1], m_LightmapPixelWriter ))
			{
				return;
			}
		}
		else if( lightmapPageID != m_nLockedLightmap )
		{
			if ( !LockLightmap( lightmapPageID ) )
			{
				ExecuteNTimes( 10, Warning( "Failed to lock lightmap\n" ) );
				return;
			}
		}
	}

	int subRectOffset[2] = {0,0};

	{
		// account for the part spent in math:
		VPROF_( "LightmapBitsToPixelWriter", 2, VPROF_BUDGETGROUP_DLIGHT_RENDERING, false, 0 );
		if ( hasBump )
		{
			switch( HardwareConfig()->GetHDRType() )
			{
			case HDR_TYPE_NONE:
				BumpedLightmapBitsToPixelWriter_LDR( pFloatImage, pFloatImageBump1, pFloatImageBump2, pFloatImageBump3, 
					lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;
			case HDR_TYPE_INTEGER:
				BumpedLightmapBitsToPixelWriter_HDRI( pFloatImage, pFloatImageBump1, pFloatImageBump2, pFloatImageBump3, 
					lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;
			case HDR_TYPE_FLOAT:
				BumpedLightmapBitsToPixelWriter_HDRF( pFloatImage, pFloatImageBump1, pFloatImageBump2, pFloatImageBump3, 
					lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;
			}
		}
		else
		{
			switch ( HardwareConfig()->GetHDRType() )
			{
			case HDR_TYPE_NONE:
				LightmapBitsToPixelWriter_LDR( pFloatImage, lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;

			case HDR_TYPE_INTEGER:
				LightmapBitsToPixelWriter_HDRI( pFloatImage, lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;

			case HDR_TYPE_FLOAT:
				LightmapBitsToPixelWriter_HDRF( pFloatImage, lightmapSize, bLockSubRect ? subRectOffset : offsetIntoLightmapPage, pfmOut );
				break;

			default:
				Assert( 0 );
				break;
			}
		}
	}

	if( bLockSubRect )
	{
		VPROF_( "Unlocking Lightmaps", 2, VPROF_BUDGETGROUP_DLIGHT_RENDERING, false, 0 );
		g_pShaderAPI->TexUnlock();
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int	CMatLightmaps::GetNumSortIDs( void )
{
	return m_numSortIDs;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMatLightmaps::ComputeSortInfo( MaterialSystem_SortInfo_t* pInfo, int& sortId, bool alpha )
{
	int lightmapPageID;

	for ( MaterialHandle_t i = GetMaterialDict()->FirstMaterial(); i != GetMaterialDict()->InvalidMaterial(); i = GetMaterialDict()->NextMaterial(i) )
	{
		IMaterialInternal* pMaterial = GetMaterialInternal(i);

		if ( pMaterial->GetMinLightmapPageID() > pMaterial->GetMaxLightmapPageID() )
		{
			continue;
		}
		
		//	const IMaterialVar *pTransVar = pMaterial->GetMaterialProperty( MATERIAL_PROPERTY_OPACITY );
		//	if( ( !alpha && ( pTransVar->GetIntValue() == MATERIAL_TRANSLUCENT ) ) ||
		//		( alpha && !( pTransVar->GetIntValue() == MATERIAL_TRANSLUCENT ) ) )
		//	{
		//		return true;
		//	}

	
//		Warning( "sort stuff: %s %s\n", material->GetName(), bAlpha ? "alpha" : "not alpha" );
		
		// fill in the lightmapped materials
		for ( lightmapPageID = pMaterial->GetMinLightmapPageID(); 
			 lightmapPageID <= pMaterial->GetMaxLightmapPageID(); ++lightmapPageID )
		{
			pInfo[sortId].material = pMaterial->GetQueueFriendlyVersion();
			pInfo[sortId].lightmapPageID = lightmapPageID;
#if 0
			char buf[128];
			Q_snprintf( buf, sizeof( buf ), "ComputeSortInfo: %s lightmapPageID: %d sortID: %d\n", pMaterial->GetName(), lightmapPageID, sortId );
			OutputDebugString( buf );
#endif
			++sortId;
		}
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMatLightmaps::ComputeWhiteLightmappedSortInfo( MaterialSystem_SortInfo_t* pInfo, int& sortId, bool alpha )
{
	for (MaterialHandle_t i = GetMaterialDict()->FirstMaterial(); i != GetMaterialDict()->InvalidMaterial(); i = GetMaterialDict()->NextMaterial(i) )
	{
		IMaterialInternal* pMaterial = GetMaterialInternal(i);

		// fill in the lightmapped materials that are actually used by this level
		if( pMaterial->GetNeedsWhiteLightmap() && 
			( pMaterial->GetReferenceCount() > 0 ) )
		{
			// const IMaterialVar *pTransVar = pMaterial->GetMaterialProperty( MATERIAL_PROPERTY_OPACITY );
			//		if( ( !alpha && ( pTransVar->GetIntValue() == MATERIAL_TRANSLUCENT ) ) ||
			//			( alpha && !( pTransVar->GetIntValue() == MATERIAL_TRANSLUCENT ) ) )
			//		{
			//			return true;
			//		}

			pInfo[sortId].material = pMaterial->GetQueueFriendlyVersion();
			if( pMaterial->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS ) )
			{
				pInfo[sortId].lightmapPageID = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP;
			}
			else
			{
				pInfo[sortId].lightmapPageID = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE;
			}

			sortId++;
		}
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMatLightmaps::GetSortInfo( MaterialSystem_SortInfo_t *pSortInfoArray )
{
	// sort non-alpha blended materials first
	int sortId = 0;
	ComputeSortInfo( pSortInfoArray, sortId, false );
	ComputeWhiteLightmappedSortInfo( pSortInfoArray, sortId, false );
	Assert( m_numSortIDs == sortId );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMatLightmaps::EnableLightmapFiltering( bool enabled )
{
	int i;
	for( i = 0; i < GetNumLightmapPages(); i++ )
	{
		g_pShaderAPI->ModifyTexture( m_LightmapPageTextureHandles[i] );
		if( enabled )
		{
			g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_LINEAR );
			g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_LINEAR );
		}
		else
		{
			g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_NEAREST );
			g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_NEAREST );
		}
	}
}