sqwarmed/sdk_src/game/client/modelrendersystem.cpp

1625 lines
64 KiB
C++

//===== Copyright © 1996-2007, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $Revision: $
// $NoKeywords: $
//
// Fast path model rendering
//
//===========================================================================//
#include "cbase.h"
#include "modelrendersystem.h"
#include "model_types.h"
#include "iviewrender.h"
#include "tier3/tier3.h"
#include <algorithm>
#include "tier1/memstack.h"
#include "engine/ivdebugoverlay.h"
#include "shaderapi/ishaderapi.h"
#include "materialsystem/materialsystemutil.h"
#include "tier0/vprof.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Convars defined by other systems
//-----------------------------------------------------------------------------
ConVar r_lod( "r_lod", "-1" );
//ConVar r_shadowlod( "r_shadowlod", "-1" );
ConVar r_drawmodellightorigin( "r_DrawModelLightOrigin", "0", FCVAR_CHEAT );
extern ConVar g_CV_FlexSmooth;
extern ConVar r_fastzreject;
//-----------------------------------------------------------------------------
// The client leaf system
//-----------------------------------------------------------------------------
class CModelRenderSystem : public CAutoGameSystem, public IModelRenderSystem
{
// Methods of IModelRenderSystem
public:
virtual void DrawModels( ModelRenderSystemData_t *pEntities, int nCount, ModelRenderMode_t renderMode );
virtual void ComputeTranslucentRenderData( ModelRenderSystemData_t *pModels, int nCount, TranslucentInstanceRenderData_t *pRenderData, TranslucentTempData_t *pTempData );
virtual void CleanupTranslucentTempData( TranslucentTempData_t *pTempData );
virtual IMaterial *GetFastPathColorMaterial() { return m_DebugMaterial; }
// Methods of IGameSystem
public:
virtual void LevelInitPostEntity();
virtual void LevelShutdownPreEntity();
// Other public methods
public:
CModelRenderSystem();
virtual ~CModelRenderSystem();
private:
struct ModelListNode_t
{
ModelRenderSystemData_t m_Entry;
int32 m_nInitialListIndex : 24;
int32 m_bBoneMerge : 1;
int32 m_nLOD : 7;
ShaderStencilState_t *m_pStencilState;
ModelListNode_t *m_pNext;
};
struct RenderModelInfo_t : public StudioArrayInstanceData_t
{
ModelRenderSystemData_t m_Entry;
ModelInstanceHandle_t m_hInstance;
matrix3x4a_t* m_pBoneToWorld;
uint32 m_nInitialListIndex : 24;
uint32 m_bSetupBonesOnly : 1;
uint32 m_bBoneMerge : 1;
};
struct ModelListByType_t : public StudioModelArrayInfo_t
{
RenderableLightingModel_t m_nLightingModel;
const model_t *m_pModel;
ModelListNode_t *m_pFirstNode;
int m_nCount;
int m_nSetupBoneCount;
uint32 m_nParentDepth : 31;
uint32 m_bWantsStencil : 1;
RenderModelInfo_t *m_pRenderModels;
ModelListByType_t *m_pNextLightingModel;
// speed up std::sort by implementing these
ModelListByType_t &operator=( const ModelListByType_t &rhs )
{
memcpy( this, &rhs, sizeof( ModelListByType_t ) );
return *this;
}
ModelListByType_t() {}
ModelListByType_t( const ModelListByType_t &rhs )
{
memcpy( this, &rhs, sizeof( ModelListByType_t ) );
}
};
struct LightingList_t
{
ModelListByType_t *m_pFirstModel;
int m_nCount;
int m_nTotalModelCount;
};
private:
int BucketModelsByMDL( ModelListByType_t *pModelList, ModelListNode_t *pModelListNodes, ModelRenderSystemData_t *pEntities, int nCount, ModelRenderMode_t renderMode, int *pModelsRenderingStencilCountOut );
bool AddModelToLists( int &nModelTypeCount, ModelListByType_t *pModelList, int &nModelNodeCount, ModelListNode_t *pModelListNodes, int nDataIndex, ModelRenderSystemData_t &data, ModelRenderMode_t renderMode );
void SortBucketsByDependency( int nModelTypeCount, ModelListByType_t *pModelList, LightingList_t *pLightingList );
void ComputeModelLODs( int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode, ModelRenderMode_t renderMode );
void SlamModelLODs( int nLOD, int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode );
void SortModels( RenderModelInfo_t *pSortedModelListNode, int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode );
static bool SortLessFunc( const RenderModelInfo_t &left, const RenderModelInfo_t &right );
void SetupBones( int nModelTypeCount, ModelListByType_t *pModelList );
void SetupFlexes( int nModelTypeCount, ModelListByType_t *pModelList );
void ComputeLightingOrigin( ModelListByType_t &list, LightingQuery_t *pLightingQuery, int nQueryStride );
int SetupLighting( LightingList_t *pLightingList, int nModelTypeCount, ModelListByType_t *pModelList, DataCacheHandle_t *pColorMeshHandles, ModelRenderMode_t renderMode );
void RenderModels( StudioModelArrayInfo2_t *pInfo, int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, ModelRenderMode_t renderMode );
void SetupTranslucentData( int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, TranslucentInstanceRenderData_t *pRenderData );
void SetupFlashlightsAndDecals( StudioModelArrayInfo2_t *pInfo, int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, RenderModelInfo_t *pModelInfo, ModelRenderMode_t renderMode );
void SetupPerInstanceColorModulation( int nModelTypeCount, ModelListByType_t *pModelList );
void DebugDrawLightingOrigin( const ModelListByType_t &list, const RenderModelInfo_t &model );
int BuildLightingList( ModelListByType_t **ppLists, unsigned char *pFlags, int *pTotalModels, const LightingList_t &lightingList );
int SetupStaticPropLighting( LightingList_t &lightingList, DataCacheHandle_t *pColorMeshHandles );
void SetupStandardLighting( LightingList_t &lightingList );
int SetupPhysicsPropLighting( LightingList_t &lightingList, DataCacheHandle_t *pColorMeshHandles );
void HookUpStaticLightingState( int nCount, ModelListByType_t **ppLists, unsigned char *pFlags, ITexture **ppEnvCubemap, MaterialLightingState_t *pLightingState, MaterialLightingState_t *pDecalLightingState, ColorMeshInfo_t **ppColorMeshInfo );
void RenderDebugOverlays( int nModelTypeCount, ModelListByType_t *pModelList, ModelRenderMode_t renderMode );
void RenderVCollideDebugOverlay( int nModelTypeCount, ModelListByType_t *pModelList );
void RenderBBoxDebugOverlay( int nModelTypeCount, ModelListByType_t *pModelList );
int ComputeParentDepth( C_BaseEntity *pEnt );
static bool DependencySortLessFunc( const ModelListByType_t &left, const ModelListByType_t &right );
static bool StencilSortLessFunc( const ModelListByType_t &left, const ModelListByType_t &right );
CMemoryStack m_BoneToWorld;
CTextureReference m_DefaultCubemap;
CMaterialReference m_DebugMaterial;
CMaterialReference m_ShadowBuild;
IMatRenderContext *m_pRenderContext;
CUtlMemoryFixedGrowable< DataCacheHandle_t, 1024 > m_ColorMeshHandles;
CUtlMemoryFixedGrowable< ModelListByType_t, 128 > m_ModelList;
CUtlMemoryFixedGrowable< ModelListNode_t, 1024 > m_ModelListNode;
CUtlMemoryFixedGrowable< RenderModelInfo_t, 1024 > m_RenderModelInfo;
int m_nColorMeshHandles;
int m_nModelTypeCount;
int m_nTotalModelCount;
bool m_bShadowDepth;
bool m_bHasInstanceData;
};
//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
static CModelRenderSystem s_ModelRenderSystem;
extern IModelRenderSystem *g_pModelRenderSystem = &s_ModelRenderSystem;
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CModelRenderSystem::CModelRenderSystem()
{
m_bHasInstanceData = false;
m_BoneToWorld.Init( 1 * 1024 * 1024, 32 * 1024, 0, 32 );
}
CModelRenderSystem::~CModelRenderSystem()
{
m_BoneToWorld.Term();
}
//-----------------------------------------------------------------------------
// Level init, shutdown
//-----------------------------------------------------------------------------
void CModelRenderSystem::LevelInitPostEntity()
{
m_DefaultCubemap.Init( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP );
m_DebugMaterial.Init( "debug/debugempty", TEXTURE_GROUP_OTHER );
m_ShadowBuild.Init( "engine/shadowbuild", TEXTURE_GROUP_OTHER );
}
void CModelRenderSystem::LevelShutdownPreEntity()
{
m_DefaultCubemap.Shutdown();
m_DebugMaterial.Shutdown();
m_ShadowBuild.Shutdown();
}
//-----------------------------------------------------------------------------
// returns bone setup dependency depth
//-----------------------------------------------------------------------------
int CModelRenderSystem::ComputeParentDepth( C_BaseEntity *pEnt )
{
if ( !pEnt )
return 0;
int nDepth = 0;
while ( pEnt->IsFollowingEntity() || ( pEnt->GetMoveParent() && pEnt->GetParentAttachment() > 0 ) )
{
++nDepth;
pEnt = pEnt->GetMoveParent();
}
return nDepth;
}
//-----------------------------------------------------------------------------
// Adds a model to the appropriate render lists
//-----------------------------------------------------------------------------
bool CModelRenderSystem::AddModelToLists( int &nModelTypeCount, ModelListByType_t *pModelList,
int &nModelNodeCount, ModelListNode_t *pModelListNodes, int nInitialListIndex, ModelRenderSystemData_t &data, ModelRenderMode_t renderMode )
{
// NOTE: we actually are bucketing both by model + also by lighting model
// Bucketing by lighting model is not strictly necessary, but doing so
// simplifies the code a lot in exchange for having two batches if the
// same model is used but a different lighting model, something that could
// theoretically happen if a static prop + physics prop use the same .mdl
// My thought is that even if this split happens, it will be rare, and
// we still will get a lot of sharing.
// L4D2: We'll also bucket by whether the model renders stencil or not.
// This allows us to keep stenciling models in the fastpath but group them together
// to be rendered AFTER the 360 Z prepass ends. (360 doesn't allow stencil rendering
// during the Z prepass).
const model_t *pModel = data.m_pRenderable->GetModel();
Assert( modelinfo->GetModelType( pModel ) == mod_studio );
RenderableLightingModel_t nLightingModel = LIGHTING_MODEL_NONE;
uint bWantsStencil = 0;
ShaderStencilState_t tempStencil;
if ( data.m_pModelRenderable )
{
data.m_pModelRenderable->GetRenderData( &nLightingModel, MODEL_DATA_LIGHTING_MODEL );
if ( renderMode == MODEL_RENDER_MODE_NORMAL )
{
// I considered making a MODEL_DATA_STENCIL_ENABLE renderdata type that would only return a bool
// if stencil was enabled, but it turns out most of the work for MODEL_DATA_STENCIL is computing
// that bool, so pulling this out into a separate piece didn't turn out to be a perf win.
bWantsStencil = data.m_pModelRenderable->GetRenderData( &tempStencil, MODEL_DATA_STENCIL ) ? 1 : 0;
}
}
else
{
ExecuteOnce( DevWarning( "data.m_pModelRenderable is NULL for %s\n", modelinfo->GetModelName( pModel ) ) );
}
bool bRetVal = bWantsStencil ? true : false;
int j;
for ( j = 0; j < nModelTypeCount; ++j )
{
if ( pModelList[j].m_pModel == pModel &&
pModelList[j].m_nLightingModel == nLightingModel &&
pModelList[j].m_bWantsStencil == bWantsStencil )
break;
}
if ( j == nModelTypeCount )
{
// Bail if we're rendering into shadow depth map and this model doesn't cast shadows
// NOTE: if m_pModelRenderable is NULL, it's a dependent bone setup so we need to keep it
studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pModel );
if ( ( renderMode != MODEL_RENDER_MODE_NORMAL ) && data.m_pModelRenderable && ( ( pStudioHdr->flags & STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS ) != 0 ) )
{
return bRetVal;
}
MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
studiohwdata_t *pHardwareData = g_pMDLCache->GetHardwareData( hMDL );
// This can occur if there was an error loading the model; for instance
// if the vtx and mdl are out of sync.
if ( !pHardwareData || !pHardwareData->m_pLODs )
{
AssertMsg( 0, UTIL_VarArgs( "%s failed to load and is causing EVIL in the model render system!!", pStudioHdr->name ) );
return bRetVal;
}
ModelListByType_t &list = pModelList[ nModelTypeCount ];
list.m_pModel = pModel;
list.m_nLightingModel = nLightingModel;
list.m_bWantsStencil = bWantsStencil;
list.m_pStudioHdr = pStudioHdr;
list.m_pHardwareData = pHardwareData;
list.m_nFlashlightCount = 0;
list.m_pFlashlights = NULL;
list.m_nCount = 0;
list.m_pFirstNode = 0;
list.m_pRenderModels = 0;
list.m_nParentDepth = 0;
list.m_pNextLightingModel = NULL;
j = nModelTypeCount++;
}
C_BaseEntity *pEntity = data.m_pRenderable->GetIClientUnknown()->GetBaseEntity();
uint nParentDepth = ComputeParentDepth( pEntity );
ModelListByType_t &list = pModelList[ j ];
ModelListNode_t &node = pModelListNodes[ nModelNodeCount++ ];
node.m_Entry = data;
node.m_nInitialListIndex = nInitialListIndex;
node.m_bBoneMerge = pEntity && pEntity->IsEffectActive( EF_BONEMERGE );
if ( bWantsStencil && ( renderMode == MODEL_RENDER_MODE_NORMAL ) )
{
CMatRenderData< ShaderStencilState_t > rdStencil( m_pRenderContext, 1 );
memcpy( &rdStencil[0], &tempStencil, sizeof( tempStencil ) );
node.m_pStencilState = &rdStencil[0];
}
else
{
node.m_pStencilState = NULL;
}
node.m_pNext = list.m_pFirstNode;
list.m_nParentDepth = MAX( list.m_nParentDepth, nParentDepth );
list.m_pFirstNode = &node;
++list.m_nCount;
return bRetVal;
}
//-----------------------------------------------------------------------------
// bucket models by type, return # of unique types
//-----------------------------------------------------------------------------
int CModelRenderSystem::BucketModelsByMDL( ModelListByType_t *pModelList, ModelListNode_t *pModelListNodes, ModelRenderSystemData_t *pEntities, int nCount, ModelRenderMode_t renderMode,
int *pModelsRenderingStencilCountOut )
{
int nModelTypeCount = 0;
int nModelNodeCount = 0;
*pModelsRenderingStencilCountOut = 0;
for ( int i = 0; i < nCount; ++i )
{
bool bModelWantsStencil = AddModelToLists( nModelTypeCount, pModelList, nModelNodeCount, pModelListNodes, i, pEntities[i], renderMode );
*pModelsRenderingStencilCountOut += bModelWantsStencil ? 1 : 0;
}
return nModelTypeCount;
}
//-----------------------------------------------------------------------------
// Sort model types function
//-----------------------------------------------------------------------------
inline bool CModelRenderSystem::DependencySortLessFunc( const ModelListByType_t &left, const ModelListByType_t &right )
{
// Ensures bone setup occurs in the correct order
if ( left.m_nParentDepth != right.m_nParentDepth )
return left.m_nParentDepth < right.m_nParentDepth;
// Ensure stenciling models are at the end of the list.
// This doesn't guarantee that stencil stuff is at the end because parent depth trumps it,
// so we'll have to sort again before rendering.
if ( left.m_bWantsStencil != right.m_bWantsStencil )
{
return left.m_bWantsStencil < right.m_bWantsStencil;
}
// Keep same models with different lighting types together
return left.m_pModel < right.m_pModel;
}
//-----------------------------------------------------------------------------
// Sorts so that bone setup occurs in the appropriate order (parents set up first)
//-----------------------------------------------------------------------------
void CModelRenderSystem::SortBucketsByDependency( int nModelTypeCount, ModelListByType_t *pModelList, LightingList_t *pLightingList )
{
std::sort( pModelList, pModelList + nModelTypeCount, DependencySortLessFunc );
// Assign models to the appropriate lighting list
for ( int i = nModelTypeCount; --i >= 0; )
{
ModelListByType_t &list = pModelList[ i ];
// Hook into lighting list
if ( list.m_nLightingModel == LIGHTING_MODEL_NONE )
continue;
LightingList_t &lightList = pLightingList[ list.m_nLightingModel ];
list.m_pNextLightingModel = lightList.m_pFirstModel;
lightList.m_pFirstModel = &list;
++lightList.m_nCount;
lightList.m_nTotalModelCount += list.m_nCount;
}
#ifdef _DEBUG
// Don't want to allow some MDLs of type A to depend on MDLs of type B
// and other MDLS of type B to depend on type A because that would
// dramatically increase complexity of the system here. With this assumption,
// we can always have all models of the same type be set up at the same time,
// also improving cache efficiency.
for ( int i =0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[ i ];
if ( list.m_nParentDepth == 0 )
continue;
for( ModelListNode_t *pNode = list.m_pFirstNode; pNode; pNode = pNode->m_pNext )
{
C_BaseEntity *pEnt = pNode->m_Entry.m_pRenderable->GetIClientUnknown()->GetBaseEntity();
if ( !pEnt )
continue;
C_BaseEntity *pTest = pEnt;
while ( pEnt->IsFollowingEntity() || ( pEnt->GetMoveParent() && pEnt->GetParentAttachment() > 0 ) )
{
pEnt = pEnt->GetMoveParent();
const model_t *pModel = pEnt->GetModel();
bool bFound = false;
for ( int j = 0; j < nModelTypeCount; ++j )
{
if ( pModelList[j].m_pModel != pModel )
continue;
if ( pModelList[j].m_nParentDepth >= list.m_nParentDepth )
{
// NOTE: GetClassname() stores the name in a global, hence need to do the warning on 2 lines
Warning( "Bone setup dependency ordering issue [ent %s ", pTest->GetClassname() );
Warning( " depends on ent %s]!\n", pEnt->GetClassname() );
}
for( ModelListNode_t *pParentNode = pModelList[j].m_pFirstNode; pParentNode; pParentNode = pParentNode->m_pNext )
{
if ( pParentNode->m_Entry.m_pRenderable == pEnt->GetClientRenderable() )
{
bFound = true;
break;
}
}
}
if ( !bFound )
{
// NOTE: GetClassname() stores the name in a global, hence need to do the warning on 2 lines
// Warning( "Missing bone setup dependency [ent %s ", pTest->GetClassname() );
// Warning( "depends on ent %s]!\n", pEnt->GetClassname() );
}
}
}
}
#endif
}
//-----------------------------------------------------------------------------
// Slam model LODs to the appropriate level
//-----------------------------------------------------------------------------
void CModelRenderSystem::SlamModelLODs( int nLOD, int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode )
{
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
int nLODCount = list.m_pHardwareData->m_NumLODs;
int nRootLOD = list.m_pHardwareData->m_RootLOD;
bool bHasShadowLOD = ( list.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) != 0;
int nMaxLOD = bHasShadowLOD ? nLODCount - 2 : nLODCount - 1;
for ( ModelListNode_t *pNode = list.m_pFirstNode; pNode; pNode = pNode->m_pNext )
{
pNode->m_nLOD = clamp( nLOD, nRootLOD, nMaxLOD );
}
}
}
//-----------------------------------------------------------------------------
// Compute model LODs
//-----------------------------------------------------------------------------
void CModelRenderSystem::ComputeModelLODs( int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode, ModelRenderMode_t renderMode )
{
if ( renderMode == MODEL_RENDER_MODE_RTT_SHADOWS )
{
// Slam to shadow lod
//int nShadowLodConVar = r_shadowlod.GetInt();
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
int nLODCount = list.m_pHardwareData->m_NumLODs;
//int nRootLOD = list.m_pHardwareData->m_RootLOD;
int nMaxLOD = nLODCount - 1;
for ( ModelListNode_t *pNode = list.m_pFirstNode; pNode; pNode = pNode->m_pNext )
{
// Just always use the lowest LOD right now
//int nLOD = nShadowLodConVar;
//pNode->m_nLOD = clamp( nLOD, nRootLOD, nMaxLOD );
pNode->m_nLOD = nMaxLOD;
}
}
return;
}
int nLOD = r_lod.GetInt();
if ( nLOD >= 0 )
{
SlamModelLODs( nLOD, nModelTypeCount, pModelList, pModelListNode );
return;
}
ScreenSizeComputeInfo_t info;
ComputeScreenSizeInfo( &info );
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
int nLODCount = list.m_pHardwareData->m_NumLODs;
int nRootLOD = list.m_pHardwareData->m_RootLOD;
int nMaxLOD = nLODCount - 1;
for ( ModelListNode_t *pNode = list.m_pFirstNode; pNode; pNode = pNode->m_pNext )
{
// FIXME: SIMD-ize, eliminate all extraneous calls (get view render state outside of loop)
const Vector &vecRenderOrigin = pNode->m_Entry.m_pRenderable->GetRenderOrigin();
// NOTE: The 2.0 is for legacy reasons
float flScreenSize = 2.0f * ComputeScreenSize( vecRenderOrigin, 0.5f, info );
float flMetric = list.m_pHardwareData->LODMetric( flScreenSize );
nLOD = list.m_pHardwareData->GetLODForMetric( flMetric );
pNode->m_nLOD = clamp( nLOD, nRootLOD, nMaxLOD );
}
}
}
//-----------------------------------------------------------------------------
// Sort models function
//-----------------------------------------------------------------------------
inline bool CModelRenderSystem::SortLessFunc( const RenderModelInfo_t &left, const RenderModelInfo_t &right )
{
// NOTE: Could do this, but it is not faster, because the cost of an integer multiply is about three
// times that of a branch penalty:
// int nLeft = left.m_nSkin * 1000000 + left.m_nLOD * 1000 + left.m_nBody;
// int nRight = right.m_nSkin * 1000000 + right.m_nLOD * 1000 + right.m_nBody;
// return nLeft > nRight;
if ( left.m_bSetupBonesOnly != right.m_bSetupBonesOnly )
return !left.m_bSetupBonesOnly;
if ( left.m_nSkin != right.m_nSkin )
return left.m_nSkin > right.m_nSkin;
if ( left.m_nLOD != right.m_nLOD )
return left.m_nLOD > right.m_nLOD;
return left.m_nBody > right.m_nBody;
}
//-----------------------------------------------------------------------------
// Sort models
//-----------------------------------------------------------------------------
void CModelRenderSystem::SortModels( RenderModelInfo_t *pRenderModelInfo,
int nModelTypeCount, ModelListByType_t *pModelList, ModelListNode_t *pModelListNode )
{
// First place them in arrays
RenderModelInfo_t *pCurrInfo = pRenderModelInfo;
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
list.m_pRenderModels = pCurrInfo;
list.m_nSetupBoneCount = 0;
for ( ModelListNode_t *pNode = list.m_pFirstNode; pNode; pNode = pNode->m_pNext )
{
memset( pCurrInfo, 0, sizeof( RenderModelInfo_t ) );
pCurrInfo->m_Entry = pNode->m_Entry;
pCurrInfo->m_nLOD = pNode->m_nLOD;
pCurrInfo->m_nSkin = pNode->m_Entry.m_pRenderable->GetSkin();
pCurrInfo->m_nBody = pNode->m_Entry.m_pRenderable->GetBody();
pCurrInfo->m_hInstance = pNode->m_Entry.m_pRenderable->GetModelInstance();
pCurrInfo->m_Decals = STUDIORENDER_DECAL_INVALID;
pCurrInfo->m_nInitialListIndex = pNode->m_nInitialListIndex;
pCurrInfo->m_bBoneMerge = pNode->m_bBoneMerge;
pCurrInfo->m_bSetupBonesOnly = ( pNode->m_Entry.m_pModelRenderable == NULL );
pCurrInfo->m_pStencilState = pNode->m_pStencilState;
list.m_nSetupBoneCount += pCurrInfo->m_bSetupBonesOnly;
++pCurrInfo;
}
// Sort within this model type. skin first, then LOD, then body.
Assert( pCurrInfo - list.m_pRenderModels == list.m_nCount );
std::sort( list.m_pRenderModels, list.m_pRenderModels + list.m_nCount, SortLessFunc );
list.m_nCount -= list.m_nSetupBoneCount;
list.m_nSetupBoneCount += list.m_nCount;
}
}
//-----------------------------------------------------------------------------
// Sets up bones on all models
//-----------------------------------------------------------------------------
void CModelRenderSystem::SetupBones( int nModelTypeCount, ModelListByType_t *pModelList )
{
// FIXME: Can we make parallel bone setup faster? Yes, we can!
const float flCurTime = gpGlobals->curtime;
matrix3x4a_t pPoseToBone[MAXSTUDIOBONES];
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
const int nBoneCount = list.m_pStudioHdr->numbones;
// Force setup of attachments if we're going to use an illumposition
const int nAttachmentMask = ( list.m_pStudioHdr->IllumPositionAttachmentIndex() > 0 ) ? BONE_USED_BY_ATTACHMENT : 0;
for ( int j = 0; j < list.m_nSetupBoneCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
const int nBoneMask = BONE_USED_BY_VERTEX_AT_LOD( pModel->m_nLOD ) | nAttachmentMask;
pModel->m_pBoneToWorld = (matrix3x4a_t*)m_BoneToWorld.Alloc( nBoneCount * sizeof(matrix3x4a_t) );
const bool bOk = pModel->m_Entry.m_pRenderable->SetupBones( pModel->m_pBoneToWorld, nBoneCount, nBoneMask, flCurTime );
if ( !bOk )
{
for ( int k = 0; k < nBoneCount; ++k)
{
SetIdentityMatrix( pModel->m_pBoneToWorld[k] );
}
}
}
if ( list.m_nCount == 0 )
continue;
// Get the pose to bone for the model
if ( !list.m_pStudioHdr->pLinearBones() )
{
// convert bone to world transformations into pose to world transformations
for (int k = 0; k < nBoneCount; k++)
{
mstudiobone_t *pCurBone = list.m_pStudioHdr->pBone( k );
MatrixCopy( pCurBone->poseToBone, pPoseToBone[k] );
}
}
else
{
mstudiolinearbone_t *pLinearBones = list.m_pStudioHdr->pLinearBones();
// convert bone to world transformations into pose to world transformations
for ( int k = 0; k < nBoneCount; k++)
{
MatrixCopy( pLinearBones->poseToBone(k), pPoseToBone[k] );
}
}
// Apply the pose-to-bone matrix to all instances
// NOTE: We should be able to optimize this a ton since it's very parallelizable
// NOTE: We may well want to compute the aggregate bone to world here also.
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
CMatRenderData< matrix3x4a_t > rdPoseToWorld( m_pRenderContext, nBoneCount );
pModel->m_pPoseToWorld = rdPoseToWorld.Base();
for ( int b = 0; b < nBoneCount; b++ )
{
ConcatTransforms_Aligned( pModel->m_pBoneToWorld[b], pPoseToBone[b], pModel->m_pPoseToWorld[b] );
}
}
}
}
//-----------------------------------------------------------------------------
// Sets up flexes on all models
//-----------------------------------------------------------------------------
void CModelRenderSystem::SetupFlexes( int nModelTypeCount, ModelListByType_t *pModelList )
{
bool bUsesDelayedWeights = g_CV_FlexSmooth.GetBool();
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
const int nFlexCount = list.m_pStudioHdr->numflexdesc;
if ( !nFlexCount )
continue;
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
CMatRenderData< float > rdFlexWeights( m_pRenderContext );
CMatRenderData< float > rdDelayedFlexWeights( m_pRenderContext );
pModel->m_pFlexWeights = rdFlexWeights.Lock( nFlexCount );
if ( bUsesDelayedWeights )
{
pModel->m_pDelayedFlexWeights = rdDelayedFlexWeights.Lock( nFlexCount );
}
pModel->m_Entry.m_pRenderable->SetupWeights( pModel->m_pBoneToWorld, nFlexCount, pModel->m_pFlexWeights, pModel->m_pDelayedFlexWeights );
}
}
}
//-----------------------------------------------------------------------------
// Draws debugging information for lighting
//-----------------------------------------------------------------------------
void CModelRenderSystem::DebugDrawLightingOrigin( const ModelListByType_t &list, const RenderModelInfo_t &model )
{
if ( !model.m_pLightingState )
return;
const Vector& lightOrigin = model.m_pLightingState->m_vecLightingOrigin;
const matrix3x4_t &modelToWorld = model.m_Entry.m_pRenderable->RenderableToWorldTransform();
// draw z planar cross at lighting origin
Vector pt0;
Vector pt1;
pt0 = lightOrigin;
pt1 = lightOrigin;
pt0.x -= 4;
pt1.x += 4;
debugoverlay->AddLineOverlay( pt0, pt1, 0, 255, 0, true, 0.0f );
pt0 = lightOrigin;
pt1 = lightOrigin;
pt0.y -= 4;
pt1.y += 4;
debugoverlay->AddLineOverlay( pt0, pt1, 0, 255, 0, true, 0.0f );
// draw lines from the light origin to the hull boundaries to identify model
Vector pt;
pt0.x = list.m_pStudioHdr->hull_min.x;
pt0.y = list.m_pStudioHdr->hull_min.y;
pt0.z = list.m_pStudioHdr->hull_min.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_min.x;
pt0.y = list.m_pStudioHdr->hull_max.y;
pt0.z = list.m_pStudioHdr->hull_min.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_max.x;
pt0.y = list.m_pStudioHdr->hull_max.y;
pt0.z = list.m_pStudioHdr->hull_min.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_max.x;
pt0.y = list.m_pStudioHdr->hull_min.y;
pt0.z = list.m_pStudioHdr->hull_min.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_min.x;
pt0.y = list.m_pStudioHdr->hull_min.y;
pt0.z = list.m_pStudioHdr->hull_max.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_min.x;
pt0.y = list.m_pStudioHdr->hull_max.y;
pt0.z = list.m_pStudioHdr->hull_max.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_max.x;
pt0.y = list.m_pStudioHdr->hull_max.y;
pt0.z = list.m_pStudioHdr->hull_max.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
pt0.x = list.m_pStudioHdr->hull_max.x;
pt0.y = list.m_pStudioHdr->hull_min.y;
pt0.z = list.m_pStudioHdr->hull_max.z;
VectorTransform( pt0, modelToWorld, pt1 );
debugoverlay->AddLineOverlay( lightOrigin, pt1, 100, 100, 150, true, 0.0f );
}
//-----------------------------------------------------------------------------
// Compute lighting origin on all models
//-----------------------------------------------------------------------------
void CModelRenderSystem::ComputeLightingOrigin( ModelListByType_t &list, LightingQuery_t *pLightingQueryBase, int nQueryStride )
{
LightingQuery_t *pLightingQuery = pLightingQueryBase;
//int nBoneMergeCount = 0;
int nAttachmentIndex = list.m_pStudioHdr->IllumPositionAttachmentIndex();
bool bAmbientBoost = ( list.m_pStudioHdr->flags & STUDIOHDR_FLAGS_AMBIENT_BOOST ) != 0;
if ( nAttachmentIndex <= 0 || nAttachmentIndex > list.m_pStudioHdr->GetNumAttachments() )
{
const Vector &vecIllumPosition = list.m_pStudioHdr->illumposition;
for ( int j = 0; j < list.m_nCount; ++j, pLightingQuery = (LightingQuery_t*)( (unsigned char*)pLightingQuery + nQueryStride ) )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
//nBoneMergeCount += pModel->m_bBoneMerge;
const matrix3x4_t &renderToWorld = pModel->m_Entry.m_pRenderable->RenderableToWorldTransform();
pLightingQuery->m_InstanceHandle = pModel->m_hInstance;
VectorTransform( vecIllumPosition, renderToWorld, pLightingQuery->m_LightingOrigin );
//pLightingQuery->m_ParentInstanceHandle = MODEL_INSTANCE_INVALID;
pLightingQuery->m_bAmbientBoost = bAmbientBoost;
}
}
else
{
// NOTE: We don't care about orientation here.
// Assume the attachment->bone matrix has identity rotation. Given that,
// compute attachment->world = bone->world * attachment->bone.
// We only care about the translation component of attachment->world
// which can be obtained by transforming the attachment offset by bone->world
// Oh, and tough noogies if you want an illumposition offset also.
// Just make an attachment exactly where you want it. Otherwise this is slower.
#ifdef _DEBUG
if ( list.m_pStudioHdr->illumposition != vec3_origin )
{
static int nWarnCount = 0;
if ( nWarnCount++ < 10 )
{
AssertMsg( false, "Model fast path: illumposition must be (0,0,0) if an attachment is being used!\n" );
}
}
#endif
// Attachment index is 1 too large, 0 means no attachment
--nAttachmentIndex;
Vector attachmentOffset;
const mstudioattachment_t &attachment = list.m_pStudioHdr->pAttachment( nAttachmentIndex );
MatrixGetColumn( attachment.local, 3, attachmentOffset );
int iBone = list.m_pStudioHdr->GetAttachmentBone( nAttachmentIndex );
for ( int j = 0; j < list.m_nCount; ++j, pLightingQuery = (LightingQuery_t*)( (unsigned char*)pLightingQuery + nQueryStride ) )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
//nBoneMergeCount += pModel->m_bBoneMerge;
pLightingQuery->m_InstanceHandle = pModel->m_hInstance;
VectorTransform( attachmentOffset, pModel->m_pBoneToWorld[iBone], pLightingQuery->m_LightingOrigin );
//pLightingQuery->m_ParentInstanceHandle = MODEL_INSTANCE_INVALID;
pLightingQuery->m_bAmbientBoost = bAmbientBoost;
}
}
#if 0
// NOTE: This is more expensive, but hopefully is uncommon
// Bonemerged models will copy the lighting environment from their parent entity.
// This fixes issues with L4D2 infected wounds where the wounds would sometimes receive different lighting
// than the body they're embedded in.
if ( nBoneMergeCount > 0 )
{
pLightingQuery = pLightingQueryBase;
for ( int j = 0; j < list.m_nCount; ++j, pLightingQuery = (LightingQuery_t*)( (unsigned char*)pLightingQuery + nQueryStride ) )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
if ( !pModel->m_bBoneMerge )
continue;
C_BaseEntity *pEnt = pModel->m_Entry.m_pRenderable->GetIClientUnknown()->GetBaseEntity();
C_BaseEntity *pParent = pEnt->GetMoveParent();
if ( !pParent )
continue;
pLightingQuery->m_ParentInstanceHandle = pParent->GetModelInstance();
}
}
#endif
}
//-----------------------------------------------------------------------------
// Builds the model lighting list
//-----------------------------------------------------------------------------
enum
{
LIGHTING_USES_ENV_CUBEMAP = 0x1,
LIGHTING_IS_VERTEX_LIT = 0x2,
LIGHTING_IS_STATIC_LIT = 0x4,
};
int CModelRenderSystem::BuildLightingList( ModelListByType_t **ppLists, unsigned char *pFlags, int *pTotalModels, const LightingList_t &lightingList )
{
// FIXME: This may be better placed in the engine to avoid all the virtual calls?
int nSetupCount = 0;
*pTotalModels = 0;
for ( ModelListByType_t* pList = lightingList.m_pFirstModel; pList; pList = pList->m_pNextLightingModel )
{
// FIXME: Under what conditions can the static prop skip lighting? [unlit materials]
bool bIsLit = modelinfo->IsModelVertexLit( pList->m_pModel );
bool bUsesEnvCubemap = modelinfo->UsesEnvCubemap( pList->m_pModel );
bool bIsStaticLit = modelinfo->UsesStaticLighting( pList->m_pModel );
if ( !bIsLit && !bUsesEnvCubemap && !bIsStaticLit )
continue;
ppLists[ nSetupCount ] = pList;
pFlags[ nSetupCount ] = ( bIsStaticLit << 2 ) | ( bIsLit << 1 ) | ( bUsesEnvCubemap << 0 );
*pTotalModels += pList->m_nCount;
++nSetupCount;
}
return nSetupCount;
}
//-----------------------------------------------------------------------------
// Hook up computed lighting state
//-----------------------------------------------------------------------------
void CModelRenderSystem::HookUpStaticLightingState( int nCount, ModelListByType_t **ppLists,
unsigned char *pFlags, ITexture **ppEnvCubemap, MaterialLightingState_t *pLightingState,
MaterialLightingState_t *pDecalLightingState, ColorMeshInfo_t **ppColorMeshInfo )
{
// FIXME: This has got to be more efficient that this
for ( int i = 0; i < nCount; ++i )
{
ModelListByType_t &list = *( ppLists[i] );
if ( pFlags[i] & LIGHTING_USES_ENV_CUBEMAP )
{
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModel->m_pEnvCubemapTexture = ppEnvCubemap[j] ? ppEnvCubemap[j] : m_DefaultCubemap;
}
}
if ( pFlags[i] & LIGHTING_IS_VERTEX_LIT )
{
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModel->m_pLightingState = &pLightingState[j];
pModel->m_pDecalLightingState = &pDecalLightingState[j];
}
}
if ( pFlags[i] & LIGHTING_IS_STATIC_LIT )
{
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModel->m_pColorMeshInfo = ppColorMeshInfo[j];
}
}
ppEnvCubemap += list.m_nCount;
pLightingState += list.m_nCount;
pDecalLightingState += list.m_nCount;
ppColorMeshInfo += list.m_nCount;
}
}
//-----------------------------------------------------------------------------
// Sets up lighting on all models
//-----------------------------------------------------------------------------
int CModelRenderSystem::SetupStaticPropLighting( LightingList_t &lightingList, DataCacheHandle_t *pColorMeshHandle )
{
if ( lightingList.m_nCount == 0 )
return 0;
// Build list of everything that needs lighting
int nTotalModels;
ModelListByType_t **ppLists = (ModelListByType_t**)stackalloc( lightingList.m_nCount * sizeof(ModelListByType_t*) );
unsigned char *pFlags = (unsigned char*)stackalloc( lightingList.m_nCount * sizeof(unsigned char) );
int nSetupCount = BuildLightingList( ppLists, pFlags, &nTotalModels, lightingList );
if ( nSetupCount == 0 )
return 0;
// Build queries used to compute lighting
StaticLightingQuery_t *pLightingQuery = (StaticLightingQuery_t*)stackalloc( nTotalModels * sizeof(StaticLightingQuery_t) );
int nOffset = 0;
for ( int i = 0; i < nSetupCount; ++i )
{
ModelListByType_t &list = *( ppLists[i] );
for ( int j = 0; j < list.m_nCount; ++j, ++nOffset )
{
pLightingQuery[ nOffset ].m_pRenderable = list.m_pRenderModels[j].m_Entry.m_pRenderable;
pLightingQuery[ nOffset ].m_InstanceHandle = list.m_pRenderModels[j].m_hInstance;
pLightingQuery[ nOffset ].m_bAmbientBoost = false;
}
}
// Compute lighting origins
staticpropmgr->GetLightingOrigins( &pLightingQuery[0].m_LightingOrigin,
sizeof(StaticLightingQuery_t), nTotalModels, &pLightingQuery[0].m_pRenderable, sizeof(StaticLightingQuery_t) );
// Does all lighting computations for all models
ColorMeshInfo_t **ppColorMeshInfo = (ColorMeshInfo_t**)stackalloc( nTotalModels * sizeof(ColorMeshInfo_t*) );
ITexture **ppEnvCubemap = (ITexture**)stackalloc( nTotalModels * sizeof(ITexture*) );
CMatRenderData< MaterialLightingState_t > rdLightingState( m_pRenderContext, 2 * nTotalModels );
MaterialLightingState_t *pLightingState = rdLightingState.Base();
MaterialLightingState_t *pDecalLightingState = &rdLightingState[ nTotalModels ];
modelrender->ComputeStaticLightingState( nTotalModels, pLightingQuery, pLightingState, pDecalLightingState, ppColorMeshInfo, ppEnvCubemap, pColorMeshHandle );
// Hook up pointers
HookUpStaticLightingState( nSetupCount, ppLists, pFlags, ppEnvCubemap, pLightingState, pDecalLightingState, ppColorMeshInfo );
return nTotalModels;
}
void CModelRenderSystem::SetupStandardLighting( LightingList_t &lightingList )
{
if ( lightingList.m_nCount == 0 )
return;
// Determine which groups need lighting
ModelListByType_t **ppLists = (ModelListByType_t**)stackalloc( lightingList.m_nCount * sizeof(ModelListByType_t*) );
unsigned char *pFlags = (unsigned char*)stackalloc( lightingList.m_nCount * sizeof(unsigned char) );
int nTotalModels = 0;
int nSetupCount = BuildLightingList( ppLists, pFlags, &nTotalModels, lightingList );
if ( nSetupCount == 0 )
return;
// Compute data necessary for lighting computations
int nOffset = 0;
LightingQuery_t *pLightingQuery = (LightingQuery_t*)stackalloc( nTotalModels * sizeof(LightingQuery_t) );
CMatRenderData<MaterialLightingState_t> rdLightingState( m_pRenderContext, nTotalModels );
MaterialLightingState_t *pLightingState = rdLightingState.Base();
memset( pLightingState, 0, nTotalModels * sizeof(MaterialLightingState_t) );
for ( int i = 0; i < nSetupCount; ++i )
{
ModelListByType_t &list = *( ppLists[i] );
ComputeLightingOrigin( list, &pLightingQuery[nOffset], sizeof(LightingQuery_t) );
nOffset += list.m_nCount;
}
// Does all lighting computations for all models
ITexture **ppEnvCubemap = (ITexture**)stackalloc( nTotalModels * sizeof(ITexture*) );
modelrender->ComputeLightingState( nTotalModels, pLightingQuery, pLightingState, ppEnvCubemap );
// Hook up pointers
MaterialLightingState_t *pCurrState = pLightingState;
for ( int i = 0; i < nSetupCount; ++i )
{
ModelListByType_t &list = *( ppLists[i] );
if ( pFlags[i] & 0x1 )
{
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModel->m_pEnvCubemapTexture = ppEnvCubemap[j] ? ppEnvCubemap[j] : m_DefaultCubemap;
}
}
if ( pFlags[i] & 0x2 )
{
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModel->m_pLightingState = &pCurrState[j];
}
}
ppEnvCubemap += list.m_nCount;
pCurrState += list.m_nCount;
}
}
int CModelRenderSystem::SetupPhysicsPropLighting( LightingList_t &lightingList, DataCacheHandle_t *pColorMeshHandle )
{
if ( lightingList.m_nCount == 0 )
return 0;
// NOTE: Physics prop lighting is the same as static prop lighting, only
// the static lighting is *always* used (the system goes to the standard path
// for physics props which are moving or which use bumpmapping).
ModelListByType_t **ppLists = (ModelListByType_t**)stackalloc( lightingList.m_nCount * sizeof(ModelListByType_t*) );
unsigned char *pFlags = (unsigned char*)stackalloc( lightingList.m_nCount * sizeof(unsigned char) );
int nTotalModels = 0;
int nSetupCount = BuildLightingList( ppLists, pFlags, &nTotalModels, lightingList );
if ( nSetupCount == 0 )
return 0;
StaticLightingQuery_t *pLightingQuery = (StaticLightingQuery_t*)stackalloc( nTotalModels * sizeof(StaticLightingQuery_t) );
int nOffset = 0;
for ( int i = 0; i < nSetupCount; ++i )
{
ModelListByType_t &list = *( ppLists[i] );
ComputeLightingOrigin( list, &pLightingQuery[nOffset], sizeof(StaticLightingQuery_t) );
for ( int j = 0; j < list.m_nCount; ++j, ++nOffset )
{
pLightingQuery[ nOffset ].m_pRenderable = list.m_pRenderModels[j].m_Entry.m_pRenderable;
}
}
// Does all lighting computations for all models
ColorMeshInfo_t **ppColorMeshInfo = (ColorMeshInfo_t**)stackalloc( nTotalModels * sizeof(ColorMeshInfo_t*) );
ITexture **ppEnvCubemap = (ITexture**)stackalloc( nTotalModels * sizeof(ITexture*) );
CMatRenderData< MaterialLightingState_t > rdLightingState( m_pRenderContext, 2 * nTotalModels );
MaterialLightingState_t *pLightingState = rdLightingState.Base();
MaterialLightingState_t *pDecalLightingState = &pLightingState[ nTotalModels ];
modelrender->ComputeStaticLightingState( nTotalModels, pLightingQuery, pLightingState, pDecalLightingState, ppColorMeshInfo, ppEnvCubemap, pColorMeshHandle );
// Hook up pointers
HookUpStaticLightingState( nSetupCount, ppLists, pFlags, ppEnvCubemap, pLightingState, pDecalLightingState, ppColorMeshInfo );
return nTotalModels;
}
int CModelRenderSystem::SetupLighting( LightingList_t *pLightingList, int nModelTypeCount, ModelListByType_t *pModelList, DataCacheHandle_t *pColorMeshHandles, ModelRenderMode_t renderMode )
{
if ( renderMode != MODEL_RENDER_MODE_NORMAL )
{
return 0;
}
int nCount = SetupStaticPropLighting( pLightingList[ LIGHTING_MODEL_STATIC_PROP ], pColorMeshHandles );
pColorMeshHandles += nCount;
SetupStandardLighting( pLightingList[ LIGHTING_MODEL_STANDARD ] );
nCount += SetupPhysicsPropLighting( pLightingList[ LIGHTING_MODEL_PHYSICS_PROP ], pColorMeshHandles );
// Debugging info
if ( r_drawmodellightorigin.GetBool() )
{
for ( int i = 0; i < nModelTypeCount; ++i )
{
const ModelListByType_t &list = pModelList[ i ];
if ( list.m_nLightingModel == LIGHTING_MODEL_NONE )
continue;
for ( int j = 0; j < list.m_nCount; ++j )
{
const RenderModelInfo_t &info = list.m_pRenderModels[j];
DebugDrawLightingOrigin( list, info );
}
}
}
return nCount;
}
//-----------------------------------------------------------------------------
// Setup render state related to flashlights and decals
//-----------------------------------------------------------------------------
void CModelRenderSystem::SetupFlashlightsAndDecals( StudioModelArrayInfo2_t *pInfo, int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, RenderModelInfo_t *pRenderModels, ModelRenderMode_t renderMode )
{
// Skip lighting + decals if we don't need it
if ( renderMode != MODEL_RENDER_MODE_NORMAL )
return;
ShadowHandle_t pFlashlights[MAX_FLASHLIGHTS_PER_INSTANCE_DRAW_CALL];
int nInstCount = 0;
ModelInstanceHandle_t *pModelInstanceHandle = (ModelInstanceHandle_t*)stackalloc( nTotalModelCount * sizeof(ModelInstanceHandle_t) );
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[ i ];
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
pModelInstanceHandle[nInstCount++] = pModel->m_hInstance;
}
}
Assert( nTotalModelCount == nInstCount );
if ( nTotalModelCount != nInstCount )
{
DevWarning( "CModelRenderSystem::SetupFlashlightsAndDecals sorted model list count incorrect! A model was probably unable to load!" );
nTotalModelCount = nInstCount;
}
// Gets all decals
StudioDecalHandle_t *pDecals = &pRenderModels->m_Decals;
modelrender->GetModelDecalHandles( pDecals, sizeof(RenderModelInfo_t), nTotalModelCount, pModelInstanceHandle );
// Builds a list of all flashlights affecting this model
uint32 *pFlashlightUsage = &pRenderModels->m_nFlashlightUsage;
pInfo->m_nFlashlightCount = shadowmgr->SetupFlashlightRenderInstanceInfo( pFlashlights, pFlashlightUsage, sizeof(RenderModelInfo_t), nTotalModelCount, pModelInstanceHandle );
if ( pInfo->m_nFlashlightCount )
{
// Copy over the flashlight state
// FIXME: Should we do this over the entire list of all instances?
// There's going to be a fair amount of copying of flashlight_ts
CMatRenderData< FlashlightInstance_t > rdFlashlights( m_pRenderContext, pInfo->m_nFlashlightCount );
pInfo->m_pFlashlights = rdFlashlights.Base();
shadowmgr->GetFlashlightRenderInfo( pInfo->m_pFlashlights, pInfo->m_nFlashlightCount, pFlashlights );
}
else
{
pInfo->m_pFlashlights = NULL;
}
// FIXME: Hack!
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[ i ];
list.m_nFlashlightCount = pInfo->m_nFlashlightCount;
list.m_pFlashlights = pInfo->m_pFlashlights;
}
}
void CModelRenderSystem::SetupPerInstanceColorModulation( int nModelTypeCount, ModelListByType_t *pModelList )
{
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[ i ];
if ( !list.m_nCount )
continue;
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t *pModel = &list.m_pRenderModels[j];
IClientRenderable *pRenderable = pModel->m_Entry.m_pRenderable;
#if 0
Vector diffuseModulation;
pRenderable->GetColorModulation( diffuseModulation.Base() );
pModel->m_DiffuseModulation.x = diffuseModulation.x;
pModel->m_DiffuseModulation.y = diffuseModulation.y;
pModel->m_DiffuseModulation.z = diffuseModulation.z;
#else // preferred to do it this way, because it avoids a load-hit-store on 360
pRenderable->GetColorModulation( pModel->m_DiffuseModulation.AsVector3D().Base() );
#endif
pModel->m_DiffuseModulation.w = pModel->m_Entry.m_InstanceData.m_nAlpha * ( 1.0f / 255.0f );
}
}
}
//-----------------------------------------------------------------------------
// Call into studiorender
//-----------------------------------------------------------------------------
ConVar cl_colorfastpath( "cl_colorfastpath", "0" );
void CModelRenderSystem::RenderModels( StudioModelArrayInfo2_t *pInfo, int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, ModelRenderMode_t renderMode )
{
if ( renderMode == MODEL_RENDER_MODE_NORMAL )
{
bool bColorize = cl_colorfastpath.GetBool();
if ( bColorize )
{
g_pStudioRender->ForcedMaterialOverride( m_DebugMaterial );
}
const int nFlags = STUDIORENDER_DRAW_OPAQUE_ONLY;
CMatRenderData< StudioArrayData_t > rdArray( m_pRenderContext, nModelTypeCount );
#ifdef _DEBUG
bool bFoundStencil = false;
#endif
int nNonStencilModelTypeCount = 0;
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
rdArray[ i ].m_pStudioHdr = list.m_pStudioHdr;
rdArray[ i ].m_pHardwareData = list.m_pHardwareData;
rdArray[ i ].m_pInstanceData = list.m_pRenderModels;
rdArray[ i ].m_nCount = list.m_nCount;
nNonStencilModelTypeCount += list.m_bWantsStencil ? 0 : 1;
#ifdef _DEBUG
if ( list.m_bWantsStencil )
{
bFoundStencil = true;
}
else
{
Assert( !bFoundStencil );
}
#endif
}
if ( IsX360() && r_fastzreject.GetBool() && ( nNonStencilModelTypeCount != nModelTypeCount ) )
{
// Render all models without stencil
g_pStudioRender->DrawModelArray( *pInfo, nNonStencilModelTypeCount, rdArray.Base(), sizeof(RenderModelInfo_t), nFlags );
#ifdef _X360
// end z prepass here
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->End360ZPass();
#endif
// Render all models with stencil
g_pStudioRender->DrawModelArray( *pInfo, nModelTypeCount - nNonStencilModelTypeCount, rdArray.Base() + nNonStencilModelTypeCount,
sizeof(RenderModelInfo_t), nFlags );
}
else
{
// PC renders all models in one go regardless of stencil state
g_pStudioRender->DrawModelArray( *pInfo, nModelTypeCount, rdArray.Base(), sizeof(RenderModelInfo_t), nFlags );
}
g_pStudioRender->ForcedMaterialOverride( NULL );
}
else if ( renderMode == MODEL_RENDER_MODE_SHADOW_DEPTH )
{
// NOTE: Use this path because we can aggregate draw calls across mdls
const int nFlags = STUDIORENDER_SHADOWDEPTHTEXTURE | STUDIORENDER_DRAW_OPAQUE_ONLY;
CMatRenderData< StudioArrayData_t > rdShadow( m_pRenderContext, nModelTypeCount );
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
rdShadow[ i ].m_pStudioHdr = list.m_pStudioHdr;
rdShadow[ i ].m_pHardwareData = list.m_pHardwareData;
rdShadow[ i ].m_pInstanceData = list.m_pRenderModels;
rdShadow[ i ].m_nCount = list.m_nCount;
}
g_pStudioRender->DrawModelShadowArray( nModelTypeCount, rdShadow.Base(), sizeof(RenderModelInfo_t), nFlags );
}
else if ( renderMode == MODEL_RENDER_MODE_RTT_SHADOWS )
{
// shouldn't get here unless the code is ported from l4d2 to drive this properly.
Assert(0);
#if 0
// HACK: Assume all models in this batch use the same material. This only works because we submit batches of 1 model from the client shadow manager at the moment
IMaterial* pShadowDrawMaterial = pModelList[0].m_pFirstNode->m_Entry.m_pRenderable->GetShadowDrawMaterial();
g_pStudioRender->ForcedMaterialOverride( pShadowDrawMaterial ? pShadowDrawMaterial : m_ShadowBuild, OVERRIDE_BUILD_SHADOWS );
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
g_pStudioRender->DrawModelArray( list, list.m_nCount, list.m_pRenderModels, sizeof(RenderModelInfo_t), STUDIORENDER_DRAW_OPAQUE_ONLY );
}
g_pStudioRender->ForcedMaterialOverride( NULL );
#endif
}
}
//-----------------------------------------------------------------------------
// Call into studiorender
//-----------------------------------------------------------------------------
void CModelRenderSystem::SetupTranslucentData( int nModelTypeCount, ModelListByType_t *pModelList, int nTotalModelCount, TranslucentInstanceRenderData_t *pRenderData )
{
memset( pRenderData, 0, nTotalModelCount * sizeof( TranslucentInstanceRenderData_t ) );
CMatRenderData< StudioModelArrayInfo_t > arrayInfo( m_pRenderContext, nModelTypeCount );
CMatRenderData< StudioArrayInstanceData_t > instanceData( m_pRenderContext, nTotalModelCount );
int nCurInstance = 0;
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
StudioModelArrayInfo_t *pModelInfo = &arrayInfo[i];
memcpy( pModelInfo, &list, sizeof( StudioModelArrayInfo_t ) );
for ( int j = 0; j < list.m_nCount; ++j )
{
RenderModelInfo_t &info = list.m_pRenderModels[j];
StudioArrayInstanceData_t *pInstanceData = &instanceData[nCurInstance++];
memcpy( pInstanceData, &info, sizeof( StudioArrayInstanceData_t ) );
TranslucentInstanceRenderData_t &data = pRenderData[ info.m_nInitialListIndex ];
data.m_pModelInfo = pModelInfo;
data.m_pInstanceData = pInstanceData;
}
}
}
//-----------------------------------------------------------------------------
// Renders debug overlays
//-----------------------------------------------------------------------------
void CModelRenderSystem::RenderVCollideDebugOverlay( int nModelTypeCount, ModelListByType_t *pModelList )
{
if ( !vcollide_wireframe.GetBool() )
return;
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
for ( int j = 0; j < list.m_nCount; ++j )
{
IClientRenderable *pRenderable = list.m_pRenderModels[j].m_Entry.m_pRenderable;
C_BaseAnimating *pAnim = dynamic_cast< C_BaseAnimating * >( pRenderable );
if ( pAnim && pAnim->IsRagdoll() )
{
pAnim->m_pRagdoll->DrawWireframe();
continue;
}
ICollideable *pCollideable = pRenderable->GetIClientUnknown()->GetCollideable();
if ( pCollideable && ( pCollideable->GetSolid() == SOLID_VPHYSICS ) &&
IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
{
vcollide_t *pCollide = modelinfo->GetVCollide( pCollideable->GetCollisionModel() );
if ( pCollide && pCollide->solidCount == 1 )
{
static color32 debugColor = {0,255,255,0};
engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, pCollideable->CollisionToWorldTransform(), debugColor );
C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity();
if ( pEntity && pEntity->VPhysicsGetObject() )
{
static color32 debugColorPhys = {255,0,0,0};
matrix3x4_t matrix;
pEntity->VPhysicsGetObject()->GetPositionMatrix( &matrix );
engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, matrix, debugColorPhys );
}
}
continue;
}
}
}
}
void CModelRenderSystem::RenderBBoxDebugOverlay( int nModelTypeCount, ModelListByType_t *pModelList )
{
for ( int i = 0; i < nModelTypeCount; ++i )
{
ModelListByType_t &list = pModelList[i];
for ( int j = 0; j < list.m_nCount; ++j )
{
IClientRenderable *pRenderable = list.m_pRenderModels[j].m_Entry.m_pRenderable;
if ( !pRenderable->GetIClientUnknown() )
continue;
C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity();
if ( !pEntity )
continue;
pEntity->DrawBBoxVisualizations();
}
}
}
//-----------------------------------------------------------------------------
// Renders debug overlays
//-----------------------------------------------------------------------------
void CModelRenderSystem::RenderDebugOverlays( int nModelTypeCount, ModelListByType_t *pModelList, ModelRenderMode_t renderMode )
{
if ( renderMode != MODEL_RENDER_MODE_NORMAL )
{
return;
}
RenderVCollideDebugOverlay( nModelTypeCount, pModelList );
RenderBBoxDebugOverlay( nModelTypeCount, pModelList );
}
//-----------------------------------------------------------------------------
// Sort model types function
//-----------------------------------------------------------------------------
inline bool CModelRenderSystem::StencilSortLessFunc( const ModelListByType_t &left, const ModelListByType_t &right )
{
// Ensure stenciling models are at the end of the list
if ( left.m_bWantsStencil != right.m_bWantsStencil )
{
return left.m_bWantsStencil < right.m_bWantsStencil;
}
// Keep same models with different lighting types together
return left.m_pModel < right.m_pModel;
}
//-----------------------------------------------------------------------------
// Draw models
//-----------------------------------------------------------------------------
static ConVar cl_skipfastpath( "cl_skipfastpath", "0", FCVAR_CHEAT, "Set to 1 to stop all models that go through the model fast path from rendering" );
void CModelRenderSystem::DrawModels( ModelRenderSystemData_t *pEntities, int nCount, ModelRenderMode_t renderMode )
{
if ( nCount == 0 || cl_skipfastpath.GetInt() )
return;
VPROF_BUDGET( "CModelRenderSystem::DrawModels", VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING );
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
// While doing this, we need materialsystem to keep around its temp allocations
// which we use for bone matrices + flexes
CMatRenderContextPtr matRenderContext( g_pMaterialSystem );
m_pRenderContext = matRenderContext;
PIXEVENT( m_pRenderContext, "CModelRenderSystem::DrawModels (FASTPATH)" );
CMatRenderDataReference rdLock( m_pRenderContext );
// FIXME: This is infected-specific for perf test reasons.
// Will break into a more fixed pipeline at a later date
DataCacheHandle_t *pColorMeshHandles = NULL;
if ( renderMode == MODEL_RENDER_MODE_NORMAL )
{
pColorMeshHandles = (DataCacheHandle_t*)stackalloc( nCount * sizeof(DataCacheHandle_t) );
}
ModelListByType_t *pModelList = (ModelListByType_t*)stackalloc( nCount * sizeof(ModelListByType_t) );
ModelListNode_t *pModelListNode = (ModelListNode_t*)stackalloc( nCount * sizeof(ModelListNode_t) );
int nModelsRenderingStencilCount = 0;
int nModelTypeCount = BucketModelsByMDL( pModelList, pModelListNode, pEntities, nCount, renderMode, &nModelsRenderingStencilCount );
LightingList_t pLightingList[ LIGHTING_MODEL_COUNT ];
memset( pLightingList, 0, LIGHTING_MODEL_COUNT * sizeof(LightingList_t) );
SortBucketsByDependency( nModelTypeCount, pModelList, pLightingList );
// Compute LODs for each model
ComputeModelLODs( nModelTypeCount, pModelList, pModelListNode, renderMode );
// Sort processing list by body, lod, skin, etc.
CMatRenderData< RenderModelInfo_t > rdRenderModelInfo( m_pRenderContext, nCount );
RenderModelInfo_t *pSortedModelListNode = rdRenderModelInfo.Base();
SortModels( pSortedModelListNode, nModelTypeCount, pModelList, pModelListNode );
// Setup bones
SetupBones( nModelTypeCount, pModelList );
// Setup flexes
if ( renderMode != MODEL_RENDER_MODE_RTT_SHADOWS )
{
SetupFlexes( nModelTypeCount, pModelList );
}
// Setup lighting
int nColorMeshHandles = SetupLighting( pLightingList, nModelTypeCount, pModelList, pColorMeshHandles, renderMode );
// Setup flashlights + decals
StudioModelArrayInfo2_t info;
SetupFlashlightsAndDecals( &info, nModelTypeCount, pModelList, nCount, pSortedModelListNode, renderMode );
// Setup per-instance color modulation
SetupPerInstanceColorModulation( nModelTypeCount, pModelList );
// Setup per-instance wound data
//SetupInfectedWoundRenderData( nModelTypeCount, pModelList, nCount, renderMode );
if ( IsX360() && ( renderMode == MODEL_RENDER_MODE_NORMAL ) && ( nModelsRenderingStencilCount > 0) )
{
// resort here to make sure all models rendering stencil come last
std::sort( pModelList, pModelList + nModelTypeCount, StencilSortLessFunc );
}
// Draw models
RenderModels( &info, nModelTypeCount, pModelList, nCount, renderMode );
rdLock.Release();
if ( renderMode == MODEL_RENDER_MODE_NORMAL )
{
modelrender->CleanupStaticLightingState( nColorMeshHandles, pColorMeshHandles );
stackfree( pColorMeshHandles );
}
// Blat out temporary memory for bone-to-world transforms
m_BoneToWorld.FreeAll( false );
RenderDebugOverlays( nModelTypeCount, pModelList, renderMode );
m_pRenderContext = NULL;
}
//-----------------------------------------------------------------------------
// Computes per-instance data for fast path rendering
//-----------------------------------------------------------------------------
void CModelRenderSystem::ComputeTranslucentRenderData( ModelRenderSystemData_t *pModels, int nCount, TranslucentInstanceRenderData_t *pRenderData, TranslucentTempData_t *pTempData )
{
if ( nCount == 0 )
{
pTempData->m_nColorMeshHandleCount = 0;
pTempData->m_bReleaseRenderData = false;
return;
}
VPROF_BUDGET( "CModelRenderSystem::ComputeTranslucentRenderData", VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING );
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
// While doing this, we need materialsystem to keep around its temp allocations
// which we use for bone matrices + flexes
CMatRenderContextPtr matRenderContext( g_pMaterialSystem );
m_pRenderContext = matRenderContext;
PIXEVENT( m_pRenderContext, "CModelRenderSystem::ComputeTranslucentRenderData (FASTPATH)" );
m_pRenderContext->AddRefRenderData();
pTempData->m_bReleaseRenderData = true;
ModelRenderMode_t renderMode = MODEL_RENDER_MODE_NORMAL;
// FIXME: This is infected-specific for perf test reasons.
// Will break into a more fixed pipeline at a later date
DataCacheHandle_t *pColorMeshHandles = pTempData->m_pColorMeshHandles;
ModelListByType_t *pModelList = (ModelListByType_t*)stackalloc( nCount * sizeof(ModelListByType_t) );
ModelListNode_t *pModelListNode = (ModelListNode_t*)stackalloc( nCount * sizeof(ModelListNode_t) );
int nModelsRenderingStencilCount = 0;
int nModelTypeCount = BucketModelsByMDL( pModelList, pModelListNode, pModels, nCount, renderMode, &nModelsRenderingStencilCount );
LightingList_t pLightingList[ LIGHTING_MODEL_COUNT ];
memset( pLightingList, 0, LIGHTING_MODEL_COUNT * sizeof(LightingList_t) );
SortBucketsByDependency( nModelTypeCount, pModelList, pLightingList );
// Compute LODs for each model
ComputeModelLODs( nModelTypeCount, pModelList, pModelListNode, renderMode );
// Sort processing list by body, lod, skin, etc.
RenderModelInfo_t *pSortedModelListNode = (RenderModelInfo_t*)stackalloc( nCount * sizeof(RenderModelInfo_t) );
SortModels( pSortedModelListNode, nModelTypeCount, pModelList, pModelListNode );
// Setup bones
SetupBones( nModelTypeCount, pModelList );
// Setup flexes
SetupFlexes( nModelTypeCount, pModelList );
// Setup lighting
pTempData->m_nColorMeshHandleCount = SetupLighting( pLightingList, nModelTypeCount, pModelList, pColorMeshHandles, renderMode );
// Setup flashlights + decals
StudioModelArrayInfo2_t info;
SetupFlashlightsAndDecals( &info, nModelTypeCount, pModelList, nCount, pSortedModelListNode, renderMode );
// Setup per-instance color modulation
SetupPerInstanceColorModulation( nModelTypeCount, pModelList );
// Setup per-instance wound data
// SetupInfectedWoundRenderData( nModelTypeCount, pModelList, nCount, renderMode );
// Draw models
SetupTranslucentData( nModelTypeCount, pModelList, nCount, pRenderData );
// Blat out temporary memory for bone-to-world transforms
m_BoneToWorld.FreeAll( false );
RenderDebugOverlays( nModelTypeCount, pModelList, renderMode );
m_pRenderContext = NULL;
}
void CModelRenderSystem::CleanupTranslucentTempData( TranslucentTempData_t *pTempData )
{
if ( pTempData->m_bReleaseRenderData )
{
modelrender->CleanupStaticLightingState( pTempData->m_nColorMeshHandleCount, pTempData->m_pColorMeshHandles );
CMatRenderContextPtr matRenderContext( g_pMaterialSystem );
matRenderContext->ReleaseRenderData();
}
}