source-engine-2018-hl2_src/materialsystem/cmatlightmaps.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

2192 lines
80 KiB
C++

//========= 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 );
}
}
}