diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /studiorender/studiorendercontext.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'studiorender/studiorendercontext.cpp')
| -rw-r--r-- | studiorender/studiorendercontext.cpp | 2454 |
1 files changed, 2454 insertions, 0 deletions
diff --git a/studiorender/studiorendercontext.cpp b/studiorender/studiorendercontext.cpp new file mode 100644 index 0000000..2d857da --- /dev/null +++ b/studiorender/studiorendercontext.cpp @@ -0,0 +1,2454 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include <stdlib.h> +#include "tier0/platform.h" +#include "studiorendercontext.h" +#include "optimize.h" +#include "materialsystem/imaterialvar.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imorph.h" +#include "materialsystem/ivballoctracker.h" +#include "vstdlib/random.h" +#include "tier0/tslist.h" +#include "tier0/platform.h" +#include "tier1/refcount.h" +#include "tier1/callqueue.h" +#include "cmodel.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// garymcthack - this should go elsewhere +#define MAX_NUM_BONE_INDICES 4 + + +//----------------------------------------------------------------------------- +// Toggles studio queued mode +//----------------------------------------------------------------------------- +void StudioChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ) +{ + // NOTE: This is necessary to flush the queued thread when this value changes + MaterialLock_t hLock = g_pMaterialSystem->Lock(); + g_pMaterialSystem->Unlock( hLock ); +} + +static ConVar studio_queue_mode( "studio_queue_mode", "1", 0, "", StudioChangeCallback ); + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static float s_pZeroFlexWeights[MAXSTUDIOFLEXDESC]; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +IStudioDataCache *g_pStudioDataCache = NULL; +static CStudioRenderContext s_StudioRenderContext; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CStudioRenderContext, IStudioRender, + STUDIO_RENDER_INTERFACE_VERSION, s_StudioRenderContext ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CStudioRenderContext::CStudioRenderContext() +{ + // Initialize render context + m_RC.m_pForcedMaterial = NULL; + m_RC.m_nForcedMaterialType = OVERRIDE_NORMAL; + m_RC.m_ColorMod[0] = m_RC.m_ColorMod[1] = m_RC.m_ColorMod[2] = 1.0f; + m_RC.m_AlphaMod = 1.0f; + m_RC.m_ViewOrigin.Init(); + m_RC.m_ViewRight.Init(); + m_RC.m_ViewUp.Init(); + m_RC.m_ViewPlaneNormal.Init(); + m_RC.m_Config.m_bEnableHWMorph = true; + m_RC.m_Config.m_bStatsMode = false; + + m_RC.m_NumLocalLights = 0; + for ( int i = 0; i < 6; ++i ) + { + m_RC.m_LightBoxColors[i].Init( 0, 0, 0 ); + } +} + +CStudioRenderContext::~CStudioRenderContext() +{ +} + + +//----------------------------------------------------------------------------- +// Connect, disconnect +//----------------------------------------------------------------------------- +bool CStudioRenderContext::Connect( CreateInterfaceFn factory ) +{ + if ( !BaseClass::Connect( factory ) ) + return false; + + g_pStudioDataCache = ( IStudioDataCache * )factory( STUDIO_DATA_CACHE_INTERFACE_VERSION, NULL ); + if ( !g_pMaterialSystem || !g_pMaterialSystemHardwareConfig || !g_pStudioDataCache ) + { + Msg("StudioRender failed to connect to a required system\n" ); + } + return ( g_pMaterialSystem && g_pMaterialSystemHardwareConfig && g_pStudioDataCache ); +} + +void CStudioRenderContext::Disconnect() +{ + g_pStudioDataCache = NULL; + BaseClass::Disconnect(); +} + + +//----------------------------------------------------------------------------- +// Here's where systems can access other interfaces implemented by this object +// Returns NULL if it doesn't implement the requested interface +//----------------------------------------------------------------------------- +void *CStudioRenderContext::QueryInterface( const char *pInterfaceName ) +{ + // Loading the studiorender DLL mounts *all* interfaces + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary + return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +InitReturnVal_t CStudioRenderContext::Init() +{ + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + if( !g_pMaterialSystem || !g_pMaterialSystemHardwareConfig ) + return INIT_FAILED; + + return g_pStudioRenderImp->Init(); +} + +void CStudioRenderContext::Shutdown( void ) +{ + g_pStudioRenderImp->Shutdown(); + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Used to activate the stub material system. +//----------------------------------------------------------------------------- +void CStudioRenderContext::Mat_Stub( IMaterialSystem *pMatSys ) +{ + g_pMaterialSystem = pMatSys; +} + + +//----------------------------------------------------------------------------- +// Determines material flags +//----------------------------------------------------------------------------- +void CStudioRenderContext::ComputeMaterialFlags( studiohdr_t *phdr, studioloddata_t &lodData, IMaterial *pMaterial ) +{ + // requesting info forces the initial material precache (and its build out) + if ( pMaterial->UsesEnvCubemap() ) + { + phdr->flags |= STUDIOHDR_FLAGS_USES_ENV_CUBEMAP; + } + if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame + { + phdr->flags |= STUDIOHDR_FLAGS_USES_FB_TEXTURE; + } + + // FIXME: I'd rather know that the material is definitely using the bumpmap. + // It could be in the file without actually being used. + static unsigned int bumpvarCache = 0; + IMaterialVar *pBumpMatVar = pMaterial->FindVarFast( "$bumpmap", &bumpvarCache ); + if ( pBumpMatVar && pBumpMatVar->IsDefined() && pMaterial->NeedsTangentSpace() ) + { + phdr->flags |= STUDIOHDR_FLAGS_USES_BUMPMAPPING; + } + + // Make sure material is treated as bump mapped if phong is set + static unsigned int phongVarCache = 0; + IMaterialVar *pPhongMatVar = pMaterial->FindVarFast( "$phong", &phongVarCache ); + if ( pPhongMatVar && pPhongMatVar->IsDefined() && ( pPhongMatVar->GetIntValue() != 0 ) ) + { + phdr->flags |= STUDIOHDR_FLAGS_USES_BUMPMAPPING; + } +} + + +//----------------------------------------------------------------------------- +// Does this material use a mouth shader? +//----------------------------------------------------------------------------- +static bool UsesMouthShader( IMaterial *pMaterial ) +{ + // FIXME: hack, needs proper client side material system interface + static unsigned int clientShaderCache = 0; + IMaterialVar *clientShaderVar = pMaterial->FindVarFast( "$clientShader", &clientShaderCache ); + if ( clientShaderVar ) + return ( Q_stricmp( clientShaderVar->GetStringValue(), "MouthShader" ) == 0 ); + return false; +} + + +//----------------------------------------------------------------------------- +// Returns the actual texture name to use on the model +//----------------------------------------------------------------------------- +static const char *GetTextureName( studiohdr_t *phdr, OptimizedModel::FileHeader_t *pVtxHeader, + int lodID, int inMaterialID ) +{ + OptimizedModel::MaterialReplacementListHeader_t *materialReplacementList = + pVtxHeader->pMaterialReplacementList( lodID ); + int i; + for( i = 0; i < materialReplacementList->numReplacements; i++ ) + { + OptimizedModel::MaterialReplacementHeader_t *materialReplacement = + materialReplacementList->pMaterialReplacement( i ); + if( materialReplacement->materialID == inMaterialID ) + { + const char *str = materialReplacement->pMaterialReplacementName(); + return str; + } + } + return phdr->pTexture( inMaterialID )->pszName(); +} + + +//----------------------------------------------------------------------------- +// Loads materials associated with a particular LOD of a model +//----------------------------------------------------------------------------- +void CStudioRenderContext::LoadMaterials( studiohdr_t *phdr, + OptimizedModel::FileHeader_t *pVtxHeader, studioloddata_t &lodData, int lodID ) +{ + typedef IMaterial *IMaterialPtr; + Assert( phdr ); + + lodData.numMaterials = phdr->numtextures; + if ( lodData.numMaterials == 0 ) + { + lodData.ppMaterials = NULL; + return; + } + + lodData.ppMaterials = new IMaterialPtr[lodData.numMaterials]; + Assert( lodData.ppMaterials ); + + lodData.pMaterialFlags = new int[lodData.numMaterials]; + Assert( lodData.pMaterialFlags ); + + int i, j; + + // get index of each material + // set the runtime studiohdr flags that are material derived + if ( phdr->textureindex == 0 ) + return; + + for ( i = 0; i < phdr->numtextures; i++ ) + { + char szPath[MAX_PATH]; + IMaterial *pMaterial = NULL; + + // search through all specified directories until a valid material is found + for ( j = 0; j < phdr->numcdtextures && IsErrorMaterial( pMaterial ); j++ ) + { + // If we don't do this, we get filenames like "materials\\blah.vmt". + const char *textureName = GetTextureName( phdr, pVtxHeader, lodID, i ); + if ( textureName[0] == CORRECT_PATH_SEPARATOR || textureName[0] == INCORRECT_PATH_SEPARATOR ) + ++textureName; + + // This prevents filenames like /models/blah.vmt. + const char *pCdTexture = phdr->pCdtexture( j ); + if ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR ) + ++pCdTexture; + + V_ComposeFileName( pCdTexture, textureName, szPath, sizeof( szPath ) ); + + if ( phdr->flags & STUDIOHDR_FLAGS_OBSOLETE ) + { + pMaterial = g_pMaterialSystem->FindMaterial( "models/obsolete/obsolete", TEXTURE_GROUP_MODEL, false ); + if ( IsErrorMaterial( pMaterial ) ) + { + Warning( "StudioRender: OBSOLETE material missing: \"models/obsolete/obsolete\"\n" ); + } + } + else + { + pMaterial = g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_MODEL, false ); + } + } + if ( IsErrorMaterial( pMaterial ) ) + { + // hack - if it isn't found, go through the motions of looking for it again + // so that the materialsystem will give an error. + char szPrefix[256]; + Q_strncpy( szPrefix, phdr->pszName(), sizeof( szPrefix ) ); + Q_strncat( szPrefix, " : ", sizeof( szPrefix ), COPY_ALL_CHARACTERS ); + for ( j = 0; j < phdr->numcdtextures; j++ ) + { + Q_strncpy( szPath, phdr->pCdtexture( j ), sizeof( szPath ) ); + const char *textureName = GetTextureName( phdr, pVtxHeader, lodID, i ); + Q_strncat( szPath, textureName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_MODEL, true, szPrefix ); + } + } + + lodData.ppMaterials[i] = pMaterial; + if ( pMaterial ) + { + // Increment the reference count for the material. + pMaterial->IncrementReferenceCount(); + ComputeMaterialFlags( phdr, lodData, pMaterial ); + lodData.pMaterialFlags[i] = UsesMouthShader( pMaterial ) ? 1 : 0; + } + } +} + + +//----------------------------------------------------------------------------- +// Suppresses all hw morphs on a model +//----------------------------------------------------------------------------- +static void SuppressAllHWMorphs( mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD ) +{ + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + for (int i = 0; i < pVtxMesh->numStripGroups; ++i ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(i); + if ( ( pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED ) ) + { + pStripGroup->flags |= OptimizedModel::STRIPGROUP_SUPPRESS_HW_MORPH; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Computes the total flexes on a model +//----------------------------------------------------------------------------- +static int ComputeTotalFlexCount( mstudiomodel_t *pModel ) +{ + int nFlexCount = 0; + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + mstudiomesh_t* pMesh = pModel->pMesh(k); + nFlexCount += pMesh->numflexes; + } + return nFlexCount; +} + + +//----------------------------------------------------------------------------- +// Count deltas affecting a particular stripgroup +//----------------------------------------------------------------------------- +int CStudioRenderContext::CountDeltaFlexedStripGroups( mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD ) +{ + int nFlexedStripGroupCount = 0; + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + for (int i = 0; i < pVtxMesh->numStripGroups; ++i ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(i); + if ( ( pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED ) == 0 ) + continue; + ++nFlexedStripGroupCount; + } + } + return nFlexedStripGroupCount; +} + + +//----------------------------------------------------------------------------- +// Count vertices affected by deltas in a particular strip group +//----------------------------------------------------------------------------- +int CStudioRenderContext::CountFlexedVertices( mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup ) +{ + if ( !pMesh->numflexes ) + return 0; + + // an inverse mapping from mesh index to strip group index + unsigned short *pMeshIndexToGroupIndex = (unsigned short*)_alloca( pMesh->pModel()->numvertices * sizeof(unsigned short) ); + memset( pMeshIndexToGroupIndex, 0xFF, pMesh->pModel()->numvertices * sizeof(unsigned short) ); + for ( int i = 0; i < pStripGroup->numVerts; ++i ) + { + int nMeshVert = pStripGroup->pVertex(i)->origMeshVertID; + pMeshIndexToGroupIndex[ nMeshVert ] = (unsigned short)i; + } + + int nFlexVertCount = 0; + for ( int i = 0; i < pMesh->numflexes; ++i ) + { + mstudioflex_t *pFlex = pMesh->pFlex( i ); + byte *pVAnim = pFlex->pBaseVertanim(); + int nVAnimSizeBytes = pFlex->VertAnimSizeBytes(); + for ( int j = 0; j < pFlex->numverts; ++j ) + { + mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pVAnim + j * nVAnimSizeBytes ); + int nMeshVert = pAnim->index; + unsigned short nGroupVert = pMeshIndexToGroupIndex[nMeshVert]; + + // In this case, this vertex is not part of this meshgroup. Ignore it. + if ( nGroupVert != 0xFFFF ) + { + // Only count it once + pMeshIndexToGroupIndex[nMeshVert] = 0xFFFF; + ++nFlexVertCount; + } + } + } + + return nFlexVertCount; +} + + +//----------------------------------------------------------------------------- +// Determine if any strip groups shouldn't be morphed +//----------------------------------------------------------------------------- +static int* s_pVertexCount; +static int SortVertCount( const void *arg1, const void *arg2 ) +{ + /* Compare all of both strings: */ + return s_pVertexCount[*( const int* )arg2] - s_pVertexCount[*( const int* )arg1]; +} + +#define MIN_HWMORPH_FLEX_COUNT 200 + +void CStudioRenderContext::DetermineHWMorphing( mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD ) +{ + if ( !g_pMaterialSystemHardwareConfig->HasFastVertexTextures() ) + return; + + // There is fixed cost to using HW morphing in the form of setting rendertargets. + // Therefore if there is a low chance of there being enough work, then do it in software. + int nTotalFlexCount = ComputeTotalFlexCount( pModel ); + if ( nTotalFlexCount == 0 ) + return; + + if ( nTotalFlexCount < MIN_HWMORPH_FLEX_COUNT ) + { + SuppressAllHWMorphs( pModel, pVtxLOD ); + return; + } + + // If we have less meshes than the most morphs we can do in a batch, we're done. + int nMaxHWMorphBatchCount = g_pMaterialSystemHardwareConfig->MaxHWMorphBatchCount(); + bool bHWMorph = ( pModel->nummeshes <= nMaxHWMorphBatchCount ); + if ( bHWMorph ) + return; + + // If we have less flexed strip groups than the most we can do in a batch, we're done. + int nFlexedStripGroup = CountDeltaFlexedStripGroups( pModel, pVtxLOD ); + if ( nFlexedStripGroup <= nMaxHWMorphBatchCount ) + return; + + // Finally, the expensive method. Do HW morphing on the N most expensive strip groups + + // FIXME: We should do this at studiomdl time? + // Certainly counting the # of flexed vertices can be done at studiomdl time. + int *pVertexCount = (int*)_alloca( nFlexedStripGroup * sizeof(int) ); + int nCount = 0; + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + mstudiomesh_t* pMesh = pModel->pMesh(k); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + for (int i = 0; i < pVtxMesh->numStripGroups; ++i ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(i); + if ( ( pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED ) == 0 ) + continue; + + pVertexCount[nCount++] = CountFlexedVertices( pMesh, pStripGroup ); + } + } + + int *pSortedVertexIndices = (int*)_alloca( nFlexedStripGroup * sizeof(int) ); + for ( int i = 0; i < nFlexedStripGroup; ++i ) + { + pSortedVertexIndices[i] = i; + } + s_pVertexCount = pVertexCount; + qsort( pSortedVertexIndices, nCount, sizeof(int), SortVertCount ); + + bool *pSuppressHWMorph = (bool*)_alloca( nFlexedStripGroup * sizeof(bool) ); + memset( pSuppressHWMorph, 1, nFlexedStripGroup * sizeof(bool) ); + for ( int i = 0; i < nMaxHWMorphBatchCount; ++i ) + { + pSuppressHWMorph[pSortedVertexIndices[i]] = false; + } + + // Bleah. Pretty lame. We should change StripGroupHeader_t to store the flex vertex count + int nIndex = 0; + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + for (int i = 0; i < pVtxMesh->numStripGroups; ++i ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(i); + if ( ( pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED ) == 0 ) + continue; + + if ( pSuppressHWMorph[nIndex] ) + { + pStripGroup->flags |= OptimizedModel::STRIPGROUP_SUPPRESS_HW_MORPH; + } + ++nIndex; + } + } +} + + +//----------------------------------------------------------------------------- +// Adds a vertex to the meshbuilder. Returns false if boneweights did not sum to 1.0 +//----------------------------------------------------------------------------- +template <VertexCompressionType_t T> bool CStudioRenderContext::R_AddVertexToMesh( const char *pModelName, bool bNeedsTangentSpace, CMeshBuilder& meshBuilder, + OptimizedModel::Vertex_t* pVertex, mstudiomesh_t* pMesh, const mstudio_meshvertexdata_t *vertData, bool hwSkin ) +{ + bool bOK = true; + int idx = pVertex->origMeshVertID; + + mstudiovertex_t &vert = *vertData->Vertex( idx ); + + // FIXME: if this ever becomes perf-critical... these writes are not in memory-ascending order, + // which hurts since VBs are in write-combined memory (See WriteCombineOrdering_t) + meshBuilder.Position3fv( vert.m_vecPosition.Base() ); + meshBuilder.CompressedNormal3fv<T>( vert.m_vecNormal.Base() ); + /* + if( vert.m_vecNormal.Length() < .9f || vert.m_vecNormal.Length() > 1.1f ) + { + static CUtlStringMap<bool> errorMessages; + if( !errorMessages.Defined( pModelName ) ) + { + errorMessages[pModelName] = true; + Warning( "MODELBUG %s: bad normal\n", pModelName ); + Warning( "\tnormal %0.1f %0.1f %0.1f pos: %0.1f %0.1f %0.1f\n", + vert.m_vecNormal.x, vert.m_vecNormal.y, vert.m_vecNormal.z, + vert.m_vecPosition.x, vert.m_vecPosition.y, vert.m_vecPosition.z ); + } + } + */ + meshBuilder.TexCoord2fv( 0, vert.m_vecTexCoord.Base() ); + + if (vertData->HasTangentData()) + { + /* + if( bNeedsTangentSpace && pModelName && vertData->TangentS( idx ) ) + { + const Vector4D &tangentS = *vertData->TangentS( idx ); + float w = tangentS.w; + if( !( w == 1.0f || w == -1.0f ) ) + { + static CUtlStringMap<bool> errorMessages; + if( !errorMessages.Defined( pModelName ) ) + { + errorMessages[pModelName] = true; + Warning( "MODELBUG %s: bad tangent sign\n", pModelName ); + Warning( "\tsign %0.1f at position %0.1f %0.1f %0.1f\n", + w, vert.m_vecPosition.x, vert.m_vecPosition.y, vert.m_vecPosition.z ); + } + } + + float len = tangentS.AsVector3D().Length(); + if( len < .9f || len > 1.1f ) + { + static CUtlStringMap<bool> errorMessages; + if( !errorMessages.Defined( pModelName ) ) + { + errorMessages[pModelName] = true; + Warning( "MODELBUG %s: bad tangent vector\n", pModelName ); + Warning( "\ttangent: %0.1f %0.1f %0.1f with length %0.1f at position %0.1f %0.1f %0.1f\n", + tangentS.x, tangentS.y, tangentS.z, + len, + vert.m_vecPosition.x, vert.m_vecPosition.y, vert.m_vecPosition.z ); + } + } + + #if 0 + float dot = DotProduct( vert.m_vecNormal, tangentS.AsVector3D() ); + if( dot > .95 || dot < -.95 ) + { + static CUtlStringMap<bool> errorMessages; + if( !errorMessages.Defined( pModelName ) ) + { + errorMessages[pModelName] = true; + // this is crashing for some reason. .need to investigate. + Warning( "MODELBUG %s: nearly colinear tangentS (%f %f %f) and normal (%f %f %f) at position %f %f %f Probably have 2 or more texcoords that are the same on a triangle.\n", + pModelName, tangentS.x, tangentS.y, tangentS.y, vert.m_vecNormal.x, vert.m_vecNormal.y, vert.m_vecNormal.z, vert.m_vecPosition.x, vert.m_vecPosition.y, vert.m_vecPosition.z ); + } + } + #endif + } + */ + + // send down tangent S as a 4D userdata vect. + meshBuilder.CompressedUserData<T>( (*vertData->TangentS( idx )).Base() ); + } + + // Just in case we get hooked to a material that wants per-vertex color + meshBuilder.Color4ub( 255, 255, 255, 255 ); + + float boneWeights[ MAX_NUM_BONE_INDICES ]; + if ( hwSkin ) + { + // sum up weights.. + int i; + + // We have to do this because since we're potentially dropping bones + // to get them to fit in hardware, we'll need to renormalize based on + // the actual total. + mstudioboneweight_t *pBoneWeight = vertData->BoneWeights(idx); + + // NOTE: We use pVertex->numbones because that's the number of bones actually influencing this + // vertex. Note that pVertex->numBones is not necessary the *desired* # of bones influencing this + // vertex; we could have collapsed some of those bones out. pBoneWeight->numbones stures the desired # + float totalWeight = 0; + for (i = 0; i < pVertex->numBones; ++i) + { + totalWeight += pBoneWeight->weight[pVertex->boneWeightIndex[i]]; + } + + // The only way we should not add up to 1 is if there's more than 3 *desired* bones + // and more than 1 *actual* bone (we can have 0 vertex bones in the case of static props + if ( (pVertex->numBones > 0) && (pBoneWeight->numbones <= 3) && fabs(totalWeight - 1.0f) > 1e-3 ) + { + // force them to re-normalize + bOK = false; + totalWeight = 1.0f; + } + + // Fix up the static prop case + if ( totalWeight == 0.0f ) + { + totalWeight = 1.0f; + } + + float invTotalWeight = 1.0f / totalWeight; + + // It is essential to iterate over all actual bones so that the bone indices + // are set correctly, even though the last bone weight is computed in a shader program + for (i = 0; i < pVertex->numBones; ++i) + { + if ( pVertex->boneID[i] == -1 ) + { + boneWeights[ i ] = 0.0f; + meshBuilder.BoneMatrix( i, BONE_MATRIX_INDEX_INVALID ); + } + else + { + float weight = pBoneWeight->weight[pVertex->boneWeightIndex[i]]; + boneWeights[ i ] = weight * invTotalWeight; + meshBuilder.BoneMatrix( i, pVertex->boneID[i] ); + } + } + for( ; i < MAX_NUM_BONE_INDICES; i++ ) + { + boneWeights[ i ] = 0.0f; + meshBuilder.BoneMatrix( i, BONE_MATRIX_INDEX_INVALID ); + } + } + else + { + for (int i = 0; i < MAX_NUM_BONE_INDICES; ++i) + { + boneWeights[ i ] = (i == 0) ? 1.0f : 0.0f; + meshBuilder.BoneMatrix( i, BONE_MATRIX_INDEX_INVALID ); + } + } + + // Set all the weights at once (the meshbuilder performs additional, post-compression, normalization): + Assert( pVertex->numBones <= 3 ); + + if ( pVertex->numBones > 0 ) + { + meshBuilder.CompressedBoneWeight3fv<T>( &( boneWeights[ 0 ] ) ); + } + + meshBuilder.AdvanceVertex(); + + return bOK; +} + +// Get (uncompressed) vertex data from a mesh, if available +inline const mstudio_meshvertexdata_t * GetFatVertexData( mstudiomesh_t * pMesh, studiohdr_t * pStudioHdr ) +{ + if ( !pMesh->pModel()->CacheVertexData( pStudioHdr ) ) + { + // not available yet + return NULL; + } + const mstudio_meshvertexdata_t *pVertData = pMesh->GetVertexData( pStudioHdr ); + Assert( pVertData ); + if ( !pVertData ) + { + static unsigned int warnCount = 0; + if ( warnCount++ < 20 ) + Warning( "ERROR: model verts have been compressed, cannot render! (use \"-no_compressed_vvds\")" ); + } + return pVertData; +} + +//----------------------------------------------------------------------------- +// Builds the group +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioBuildMeshGroup( const char *pModelName, bool bNeedsTangentSpace, studiomeshgroup_t* pMeshGroup, + OptimizedModel::StripGroupHeader_t *pStripGroup, mstudiomesh_t* pMesh, + studiohdr_t *pStudioHdr, VertexFormat_t vertexFormat ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + // We have to do this here because of skinning; there may be any number of + // materials that are applied to this mesh. + // Copy over all the vertices + indices in this strip group + pMeshGroup->m_pMesh = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_MODELS ); + + VertexCompressionType_t compressionType = CompressionType( vertexFormat ); + + pMeshGroup->m_ColorMeshID = -1; + + bool hwSkin = (pMeshGroup->m_Flags & MESHGROUP_IS_HWSKINNED) != 0; + + // This mesh could have tristrips or trilists in it + CMeshBuilder meshBuilder; + meshBuilder.SetCompressionType( compressionType ); + meshBuilder.Begin( pMeshGroup->m_pMesh, MATERIAL_HETEROGENOUS, + hwSkin ? pStripGroup->numVerts : 0, pStripGroup->numIndices ); + + int i; + bool bBadBoneWeights = false; + if ( hwSkin ) + { + const mstudio_meshvertexdata_t *vertData = GetFatVertexData( pMesh, pStudioHdr ); + Assert( vertData ); + + for ( i = 0; i < pStripGroup->numVerts; ++i ) + { + bool success; + switch ( compressionType ) + { + case VERTEX_COMPRESSION_ON: + success = R_AddVertexToMesh<VERTEX_COMPRESSION_ON>( pModelName, bNeedsTangentSpace, meshBuilder, pStripGroup->pVertex(i), pMesh, vertData, hwSkin ); + break; + case VERTEX_COMPRESSION_NONE: + default: + success = R_AddVertexToMesh<VERTEX_COMPRESSION_NONE>( pModelName, bNeedsTangentSpace, meshBuilder, pStripGroup->pVertex(i), pMesh, vertData, hwSkin ); + break; + } + if ( !success ) + { + bBadBoneWeights = true; + } + } + } + + if ( bBadBoneWeights ) + { + mstudiomodel_t* pModel = pMesh->pModel(); + ConMsg( "Bad data found in model \"%s\" (bad bone weights)\n", pModel->pszName() ); + } + + for (i = 0; i < pStripGroup->numIndices; ++i) + { + meshBuilder.Index( *pStripGroup->pIndex(i) ); + meshBuilder.AdvanceIndex(); + } + + meshBuilder.End(); + + // Copy over the strip indices. We need access to the indices for decals + pMeshGroup->m_pIndices = new unsigned short[ pStripGroup->numIndices ]; + memcpy( pMeshGroup->m_pIndices, pStripGroup->pIndex(0), + pStripGroup->numIndices * sizeof(unsigned short) ); + + // Compute the number of non-degenerate trianges in each strip group + // for statistics gathering + pMeshGroup->m_pUniqueTris = new int[ pStripGroup->numStrips ]; + for (i = 0; i < pStripGroup->numStrips; ++i ) + { + int numUnique = 0; + if (pStripGroup->pStrip(i)->flags & OptimizedModel::STRIP_IS_TRISTRIP) + { + int last[2] = {-1, -1}; + int curr = pStripGroup->pStrip(i)->indexOffset; + int end = curr + pStripGroup->pStrip(i)->numIndices; + while (curr != end) + { + int idx = *pStripGroup->pIndex(curr); + if (idx != last[0] && idx != last[1] && last[0] != last[1] && last[0] != -1) + ++numUnique; + last[0] = last[1]; + last[1] = idx; + ++curr; + } + } + else + { + numUnique = pStripGroup->pStrip(i)->numIndices / 3; + } + pMeshGroup->m_pUniqueTris[i] = numUnique; + } +} + +//----------------------------------------------------------------------------- +// Builds the group +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioBuildMorph( studiohdr_t *pStudioHdr, + studiomeshgroup_t* pMeshGroup, mstudiomesh_t* pMesh, + OptimizedModel::StripGroupHeader_t *pStripGroup ) +{ + if ( !g_pMaterialSystemHardwareConfig->HasFastVertexTextures() || + ( ( pMeshGroup->m_Flags & MESHGROUP_IS_DELTA_FLEXED ) == 0 ) || + ( ( pStripGroup->flags & OptimizedModel::STRIPGROUP_SUPPRESS_HW_MORPH ) != 0 ) ) + { + pMeshGroup->m_pMorph = NULL; + return; + } + + // Build an inverse mapping from mesh index to strip group index + unsigned short *pMeshIndexToGroupIndex = (unsigned short*)_alloca( pMesh->pModel()->numvertices * sizeof(unsigned short) ); + memset( pMeshIndexToGroupIndex, 0xFF, pMesh->pModel()->numvertices * sizeof(unsigned short) ); + for ( int i = 0; i < pStripGroup->numVerts; ++i ) + { + int nMeshVert = pStripGroup->pVertex(i)->origMeshVertID; + pMeshIndexToGroupIndex[ nMeshVert ] = (unsigned short)i; + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + MorphFormat_t morphType = MORPH_POSITION | MORPH_NORMAL | MORPH_SPEED | MORPH_SIDE; + for ( int i = 0; i < pMesh->numflexes; ++i ) + { + if ( pMesh->pFlex( i )->vertanimtype == STUDIO_VERT_ANIM_WRINKLE ) + { + morphType |= MORPH_WRINKLE; + break; + } + } + + char pTemp[256]; + Q_snprintf( pTemp, sizeof(pTemp), "%s [%p]", pStudioHdr->pszName(), pMeshGroup ); + pMeshGroup->m_pMorph = pRenderContext->CreateMorph( morphType, pTemp ); + + const float flVertAnimFixedPointScale = pStudioHdr->VertAnimFixedPointScale(); + + CMorphBuilder morphBuilder; + morphBuilder.Begin( pMeshGroup->m_pMorph, 1.0f / flVertAnimFixedPointScale ); + + for ( int i = 0; i < pMesh->numflexes; ++i ) + { + mstudioflex_t *pFlex = pMesh->pFlex( i ); + byte *pVAnim = pFlex->pBaseVertanim(); + int nVAnimSizeBytes = pFlex->VertAnimSizeBytes(); + for ( int j = 0; j < pFlex->numverts; ++j ) + { + mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pVAnim + j * nVAnimSizeBytes ); + int nMeshVert = pAnim->index; + unsigned short nGroupVert = pMeshIndexToGroupIndex[nMeshVert]; + + // In this case, this vertex is not part of this meshgroup. Ignore it. + if ( nGroupVert == 0xFFFF ) + continue; + + morphBuilder.PositionDelta3( pAnim->GetDeltaFixed( flVertAnimFixedPointScale ) ); + morphBuilder.NormalDelta3( pAnim->GetNDeltaFixed( flVertAnimFixedPointScale ) ); + morphBuilder.Speed1f( pAnim->speed / 255.0f ); + morphBuilder.Side1f( pAnim->side / 255.0f ); + if ( pFlex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE ) + { + mstudiovertanim_wrinkle_t *pWrinkleAnim = static_cast<mstudiovertanim_wrinkle_t*>( pAnim ); + morphBuilder.WrinkleDelta1f( pWrinkleAnim->GetWrinkleDeltaFixed( flVertAnimFixedPointScale ) ); + } + else + { + morphBuilder.WrinkleDelta1f( 0.0f ); + } + + morphBuilder.AdvanceMorph( nGroupVert, i ); + } + } + + morphBuilder.End(); +} + + +//----------------------------------------------------------------------------- +// Builds the strip data +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioBuildMeshStrips( studiomeshgroup_t* pMeshGroup, + OptimizedModel::StripGroupHeader_t *pStripGroup ) +{ + // FIXME: This is bogus + // Compute the amount of memory we need to store the strip data + int i; + int stripDataSize = 0; + for( i = 0; i < pStripGroup->numStrips; ++i ) + { + stripDataSize += sizeof(OptimizedModel::StripHeader_t); + stripDataSize += pStripGroup->pStrip(i)->numBoneStateChanges * + sizeof(OptimizedModel::BoneStateChangeHeader_t); + } + + pMeshGroup->m_pStripData = (OptimizedModel::StripHeader_t*)malloc(stripDataSize); + + // Copy over the strip info + int boneStateChangeOffset = pStripGroup->numStrips * sizeof(OptimizedModel::StripHeader_t); + for( i = 0; i < pStripGroup->numStrips; ++i ) + { + memcpy( &pMeshGroup->m_pStripData[i], pStripGroup->pStrip(i), + sizeof( OptimizedModel::StripHeader_t ) ); + + // Fixup the bone state change offset, since we have it right after the strip data + pMeshGroup->m_pStripData[i].boneStateChangeOffset = boneStateChangeOffset - + i * sizeof(OptimizedModel::StripHeader_t); + + // copy over bone state changes + int boneWeightSize = pMeshGroup->m_pStripData[i].numBoneStateChanges * + sizeof(OptimizedModel::BoneStateChangeHeader_t); + + if (boneWeightSize != 0) + { + unsigned char* pBoneStateChange = (unsigned char*)pMeshGroup->m_pStripData + boneStateChangeOffset; + memcpy( pBoneStateChange, pStripGroup->pStrip(i)->pBoneStateChange(0), boneWeightSize); + + boneStateChangeOffset += boneWeightSize; + } + } + pMeshGroup->m_NumStrips = pStripGroup->numStrips; +} + + +//----------------------------------------------------------------------------- +// Determine the max. number of bone weights used by a stripgroup +//----------------------------------------------------------------------------- +int CStudioRenderContext::GetNumBoneWeights( const OptimizedModel::StripGroupHeader_t *pGroup ) +{ + int nBoneWeightsMax = 0; + + for (int i = 0;i < pGroup->numStrips; i++) + { + OptimizedModel::StripHeader_t * pStrip = pGroup->pStrip( i ); + nBoneWeightsMax = max( nBoneWeightsMax, (int)pStrip->numBones ); + } + + return nBoneWeightsMax; +} + +//----------------------------------------------------------------------------- +// Determine an actual model vertex format for a mesh based on its material usage. +// Bypasses the homegenous model vertex format in favor of the actual format. +// Ideally matches 1:1 the shader's data requirements without any bloat. +//----------------------------------------------------------------------------- +VertexFormat_t CStudioRenderContext::CalculateVertexFormat( const studiohdr_t *pStudioHdr, const studioloddata_t *pStudioLodData, + const mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t *pGroup, bool bIsHwSkinned ) +{ + bool bSkinnedMesh = ( pStudioHdr->numbones > 1 ); + int nBoneWeights = GetNumBoneWeights( pGroup ); + + bool bIsDX7 = !g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders(); + bool bIsDX8 = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ); + if ( bIsDX7 ) + { + // FIXME: this is untested (as of June '07, the engine currently doesn't work with "-dxlevel 70") + if ( bSkinnedMesh ) + return MATERIAL_VERTEX_FORMAT_MODEL_SKINNED_DX7; + else + return MATERIAL_VERTEX_FORMAT_MODEL_DX7; + } + else if ( bIsDX8 ) + { + if ( bSkinnedMesh ) + return MATERIAL_VERTEX_FORMAT_MODEL_SKINNED; + else + return MATERIAL_VERTEX_FORMAT_MODEL; + } + else + { + // DX9+ path (supports vertex compression) + + // iterate each skin table + // determine aggregate vertex format for specified mesh's material + VertexFormat_t newVertexFormat = 0; + //bool bBumpmapping = false; + short *pSkinref = pStudioHdr->pSkinref( 0 ); + for ( int i = 0; i < pStudioHdr->numskinfamilies; i++ ) + { + // FIXME: ### MATERIAL VERTEX FORMATS ARE UNRELIABLE! ### + // + // IMaterial* pMaterial = pStudioLodData->ppMaterials[ pSkinref[ pMesh->material ] ]; + // Assert( pMaterial ); + // VertexFormat_t vertexFormat = pMaterial->GetVertexFormat(); + // newVertexFormat &= ~VERTEX_FORMAT_COMPRESSED; // Decide whether to compress below + // + // FIXME: ### MATERIAL VERTEX FORMATS ARE UNRELIABLE! ### + // we need to go through all the shader CPP code and make sure that the correct vertex format + // is being specified for every single shader combo! We don't have time to fix that before + // shipping Ep2, but should fix it ASAP afterwards. To make catching such errors easier, we + // should Assert in draw calls that the vertexdecl matches vertex shader inputs (note that D3D + // debug DLLs will do that on PC, though it's not as informative as if we do it ourselves). + // So, in the absence of reliable material vertex formats, use the old 'standard' elements + // (we can still omit skinning data - and COLOR for DX8+, where it should come from the + // second static lighting stream): + VertexFormat_t vertexFormat = bIsDX7 ? MATERIAL_VERTEX_FORMAT_MODEL_DX7 : ( MATERIAL_VERTEX_FORMAT_MODEL & ~VERTEX_COLOR ); + + // aggregate single bit settings + newVertexFormat |= vertexFormat & ( ( 1 << VERTEX_LAST_BIT ) - 1 ); + + int nUserDataSize = UserDataSize( vertexFormat ); + if ( nUserDataSize > UserDataSize( newVertexFormat ) ) + { + newVertexFormat &= ~USER_DATA_SIZE_MASK; + newVertexFormat |= VERTEX_USERDATA_SIZE( nUserDataSize ); + } + + for (int j = 0; j < VERTEX_MAX_TEXTURE_COORDINATES; ++j) + { + int nSize = TexCoordSize( j, vertexFormat ); + if ( nSize > TexCoordSize( j, newVertexFormat ) ) + { + newVertexFormat &= ~VERTEX_TEXCOORD_SIZE( j, 0x7 ); + newVertexFormat |= VERTEX_TEXCOORD_SIZE( j, nSize ); + } + } + + // FIXME: re-enable this test, fix it to work and see how much memory we save (Q: why is this different to CStudioRenderContext::MeshNeedsTangentSpace ?) + /*if ( !bBumpmapping && pMaterial->NeedsTangentSpace() ) + { + bool bFound = false; + IMaterialVar *pEnvmapMatVar = pMaterial->FindVar( "$envmap", &bFound, false ); + if ( bFound && pEnvmapMatVar->IsDefined() ) + { + IMaterialVar *pBumpMatVar = pMaterial->FindVar( "$bumpmap", &bFound, false ); + if ( bFound && pBumpMatVar->IsDefined() ) + { + bBumpmapping = true; + } + } + } */ + + pSkinref += pStudioHdr->numskinref; + } + + // Add skinning elements for non-rigid models (with more than one bone weight) + if ( bSkinnedMesh ) + { + if ( nBoneWeights > 0 ) + { + // Always exactly zero or two weights + newVertexFormat |= VERTEX_BONEWEIGHT( 2 ); + } + newVertexFormat |= VERTEX_BONE_INDEX; + } + + + // FIXME: re-enable this (see above) + /*if ( !bBumpmapping ) + { + // no bumpmapping, user data not needed + newVertexFormat &= ~USER_DATA_SIZE_MASK; + }*/ + + // materials on models should never have tangent space as they use userdata + Assert( !(newVertexFormat & VERTEX_TANGENT_SPACE) ); + + // Don't compress the mesh unless it is HW-skinned (we only want to compress static + // VBs, not dynamic ones - that would slow down the MeshBuilder in dynamic use cases). + // Also inspect the vertex data to see if it's appropriate for the vertex element + // compression techniques that we do (e.g. look at UV ranges). + if ( //IsX360() && // Disabled until the craziness is banished + bIsHwSkinned && + ( g_pMaterialSystemHardwareConfig->SupportsCompressedVertices() == VERTEX_COMPRESSION_ON ) ) + { + // this mesh is appropriate for vertex compression + newVertexFormat |= VERTEX_FORMAT_COMPRESSED; + } + + return newVertexFormat; + } +} + +bool CStudioRenderContext::MeshNeedsTangentSpace( studiohdr_t *pStudioHdr, studioloddata_t *pStudioLodData, mstudiomesh_t* pMesh ) +{ + // iterate each skin table + if( !pStudioHdr || !pStudioHdr->pSkinref( 0 ) || !pStudioHdr->numskinfamilies ) + { + return false; + } + short *pSkinref = pStudioHdr->pSkinref( 0 ); + for ( int i=0; i<pStudioHdr->numskinfamilies; i++) + { + IMaterial* pMaterial = pStudioLodData->ppMaterials[pSkinref[pMesh->material]]; + Assert( pMaterial ); + if( !pMaterial ) + { + continue; + } + + // Warning( "*****%s needstangentspace: %d\n", pMaterial->GetName(), pMaterial->NeedsTangentSpace() ? 1 : 0 ); + if( pMaterial->NeedsTangentSpace() ) + { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Creates a single mesh +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioCreateSingleMesh( studiohdr_t *pStudioHdr, studioloddata_t *pStudioLodData, + mstudiomesh_t* pMesh, OptimizedModel::MeshHeader_t* pVtxMesh, int numBones, + studiomeshdata_t* pMeshData, int *pColorMeshID ) +{ + // Here are the cases where we don't use any meshes at all... + // In the case of eyes, we're just gonna use dynamic buffers + // because it's the fastest solution (prevents lots of locks) + + bool bNeedsTangentSpace = MeshNeedsTangentSpace( pStudioHdr, pStudioLodData, pMesh ); + + // Each strip group represents a locking group, it's a set of vertices + // that are locked together, and, potentially, software light + skinned together + pMeshData->m_NumGroup = pVtxMesh->numStripGroups; + pMeshData->m_pMeshGroup = new studiomeshgroup_t[pVtxMesh->numStripGroups]; + + for (int i = 0; i < pVtxMesh->numStripGroups; ++i ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(i); + studiomeshgroup_t* pMeshGroup = &pMeshData->m_pMeshGroup[i]; + + pMeshGroup->m_MeshNeedsRestore = false; + + // Set the flags... + pMeshGroup->m_Flags = 0; + if (pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_FLEXED) + { + pMeshGroup->m_Flags |= MESHGROUP_IS_FLEXED; + } + + if (pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED) + { + pMeshGroup->m_Flags |= MESHGROUP_IS_DELTA_FLEXED; + } + + bool bIsHwSkinned = !!(pStripGroup->flags & OptimizedModel::STRIPGROUP_IS_HWSKINNED); + if ( bIsHwSkinned ) + { + pMeshGroup->m_Flags |= MESHGROUP_IS_HWSKINNED; + } + + // get the minimal vertex format for this mesh + VertexFormat_t vertexFormat = CalculateVertexFormat( pStudioHdr, pStudioLodData, pMesh, pStripGroup, bIsHwSkinned ); + + // Build the vertex + index buffers + R_StudioBuildMeshGroup( pStudioHdr->pszName(), bNeedsTangentSpace, pMeshGroup, pStripGroup, pMesh, pStudioHdr, vertexFormat ); + + // Copy over the tristrip and triangle list data + R_StudioBuildMeshStrips( pMeshGroup, pStripGroup ); + + // Builds morph targets + R_StudioBuildMorph( pStudioHdr, pMeshGroup, pMesh, pStripGroup ); + + // Build the mapping from strip group vertex idx to actual mesh idx + pMeshGroup->m_pGroupIndexToMeshIndex = new unsigned short[pStripGroup->numVerts + PREFETCH_VERT_COUNT]; + pMeshGroup->m_NumVertices = pStripGroup->numVerts; + + int j; + for ( j = 0; j < pStripGroup->numVerts; ++j ) + { + pMeshGroup->m_pGroupIndexToMeshIndex[j] = pStripGroup->pVertex(j)->origMeshVertID; + } + + // Extra copies are for precaching... + for ( j = pStripGroup->numVerts; j < pStripGroup->numVerts + PREFETCH_VERT_COUNT; ++j ) + { + pMeshGroup->m_pGroupIndexToMeshIndex[j] = pMeshGroup->m_pGroupIndexToMeshIndex[pStripGroup->numVerts - 1]; + } + + // assign the possibly used color mesh id now + pMeshGroup->m_ColorMeshID = (*pColorMeshID)++; + } +} + + +//----------------------------------------------------------------------------- +// Creates static meshes +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioCreateStaticMeshes( studiohdr_t *pStudioHdr, + OptimizedModel::FileHeader_t *pVtxHdr, studiohwdata_t *pStudioHWData, int nLodID, int *pColorMeshID ) +{ + int i, j, k; + + Assert( pStudioHdr && pVtxHdr && pStudioHWData ); + + pStudioHWData->m_pLODs[nLodID].m_pMeshData = new studiomeshdata_t[pStudioHWData->m_NumStudioMeshes]; + + // Iterate over every body part... + for ( i = 0; i < pStudioHdr->numbodyparts; i++ ) + { + mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i); + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart(i); + + // Iterate over every submodel... + for ( j = 0; j < pBodyPart->nummodels; ++j ) + { + mstudiomodel_t* pModel = pBodyPart->pModel(j); + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel(j); + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLodID ); + + // Determine which meshes should be hw morphed + DetermineHWMorphing( pModel, pVtxLOD ); + + // Support tracking of VB allocations + // FIXME: categorise studiomodel allocs more precisely + if ( g_VBAllocTracker ) + { + if ( ( pStudioHdr->numbones > 8 ) || ( pStudioHdr->numflexdesc > 0 ) ) + { + g_VBAllocTracker->TrackMeshAllocations( "R_StudioCreateStaticMeshes (character)" ); + } + else + { + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) + { + g_VBAllocTracker->TrackMeshAllocations( "R_StudioCreateStaticMeshes (prop_static)" ); + } + else + { + g_VBAllocTracker->TrackMeshAllocations( "R_StudioCreateStaticMeshes (prop_dynamic)" ); + } + } + } + + // Iterate over all the meshes.... + for ( k = 0; k < pModel->nummeshes; ++k ) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + mstudiomesh_t* pMesh = pModel->pMesh(k); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + + Assert( pMesh->meshid < pStudioHWData->m_NumStudioMeshes ); + R_StudioCreateSingleMesh( pStudioHdr, &pStudioHWData->m_pLODs[nLodID], + pMesh, pVtxMesh, pVtxHdr->maxBonesPerVert, + &pStudioHWData->m_pLODs[nLodID].m_pMeshData[pMesh->meshid], pColorMeshID ); + } + + if ( g_VBAllocTracker ) + { + g_VBAllocTracker->TrackMeshAllocations( NULL ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Destroys static meshes +//----------------------------------------------------------------------------- +void CStudioRenderContext::R_StudioDestroyStaticMeshes( int numStudioMeshes, studiomeshdata_t **ppStudioMeshes ) +{ + if( !*ppStudioMeshes) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + // Iterate over every body mesh... + for ( int i = 0; i < numStudioMeshes; ++i ) + { + studiomeshdata_t* pMesh = &((*ppStudioMeshes)[i]); + + for (int j = 0; j < pMesh->m_NumGroup; ++j) + { + studiomeshgroup_t* pGroup = &pMesh->m_pMeshGroup[j]; + if (pGroup->m_pGroupIndexToMeshIndex) + { + delete[] pGroup->m_pGroupIndexToMeshIndex; + pGroup->m_pGroupIndexToMeshIndex = 0; + } + + if (pGroup->m_pUniqueTris) + { + delete [] pGroup->m_pUniqueTris; + pGroup->m_pUniqueTris = 0; + } + + if (pGroup->m_pIndices) + { + delete [] pGroup->m_pIndices; + pGroup->m_pIndices = 0; + } + + if (pGroup->m_pMesh) + { + pRenderContext->DestroyStaticMesh( pGroup->m_pMesh ); + pGroup->m_pMesh = 0; + } + + if (pGroup->m_pMorph) + { + pRenderContext->DestroyMorph( pGroup->m_pMorph ); + pGroup->m_pMorph = 0; + } + + if (pGroup->m_pStripData) + { + free( pGroup->m_pStripData ); + pGroup->m_pStripData = 0; + } + } + + if (pMesh->m_pMeshGroup) + { + delete[] pMesh->m_pMeshGroup; + pMesh->m_pMeshGroup = 0; + } + } + + if ( *ppStudioMeshes ) + { + delete *ppStudioMeshes; + *ppStudioMeshes = 0; + } +} + + +//----------------------------------------------------------------------------- +// Builds the decal bone remap for a particular mesh +//----------------------------------------------------------------------------- +void CStudioRenderContext::BuildDecalBoneMap( studiohdr_t *pStudioHdr, int *pUsedBones, int *pBoneRemap, int *pMaxBoneCount, mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup ) +{ + const mstudio_meshvertexdata_t *pVertData = GetFatVertexData( pMesh, pStudioHdr ); + Assert( pVertData ); + for ( int i = 0; i < pStripGroup->numVerts; ++i ) + { + int nMeshVert = pStripGroup->pVertex( i )->origMeshVertID; + mstudioboneweight_t &boneWeight = pVertData->Vertex( nMeshVert )->m_BoneWeights; + int nBoneCount = boneWeight.numbones; + for ( int j = 0; j < nBoneCount; ++j ) + { + if ( boneWeight.weight[j] == 0.0f ) + continue; + + if ( pBoneRemap[ (unsigned)boneWeight.bone[j] ] >= 0 ) + continue; + + pBoneRemap[ (unsigned)boneWeight.bone[j] ] = *pUsedBones; + *pUsedBones = *pUsedBones + 1; + } + } + + for ( int i = 0; i < pStripGroup->numStrips; ++i ) + { + if ( pStripGroup->pStrip(i)->numBones > *pMaxBoneCount ) + { + *pMaxBoneCount = pStripGroup->pStrip(i)->numBones; + } + } +} + + +//----------------------------------------------------------------------------- +// For decals on hardware morphing, we must actually do hardware skinning +// because the flex must occur before skinning. +// For this to work, we have to hope that the total # of bones used by +// hw flexed verts is < than the max possible for the dx level we're running under +//----------------------------------------------------------------------------- +void CStudioRenderContext::ComputeHWMorphDecalBoneRemap( studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr, studiohwdata_t *pStudioHWData, int nLOD ) +{ + if ( pStudioHdr->numbones == 0 ) + return; + + // Remaps sw bones to hw bones during decal rendering + // NOTE: Only bones affecting vertices which have hw flexes will be add to this map. + int nBufSize = pStudioHdr->numbones * sizeof(int); + int *pBoneRemap = (int*)_alloca( nBufSize ); + memset( pBoneRemap, 0xFF, nBufSize ); + int nMaxBoneCount = 0; + + // NOTE: HW bone index 0 is always the identity transform during decals. + pBoneRemap[0] = 0; // necessary for unused bones in a vertex + int nUsedBones = 1; + + studioloddata_t *pStudioLOD = &pStudioHWData->m_pLODs[nLOD]; + for ( int i = 0; i < pStudioHdr->numbodyparts; ++i ) + { + mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i); + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart(i); + + // Iterate over every submodel... + for ( int j = 0; j < pBodyPart->nummodels; ++j ) + { + mstudiomodel_t* pModel = pBodyPart->pModel(j); + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel(j); + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLOD ); + + // Iterate over all the meshes.... + for ( int k = 0; k < pModel->nummeshes; ++k ) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + mstudiomesh_t* pMesh = pModel->pMesh(k); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); + + studiomeshdata_t* pMeshData = &pStudioLOD->m_pMeshData[pMesh->meshid]; + for ( int l = 0; l < pVtxMesh->numStripGroups; ++l ) + { + studiomeshgroup_t* pMeshGroup = &pMeshData->m_pMeshGroup[l]; + if ( !pMeshGroup->m_pMorph ) + continue; + + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(l); + BuildDecalBoneMap( pStudioHdr, &nUsedBones, pBoneRemap, &nMaxBoneCount, pMesh, pStripGroup ); + } + } + } + } + + if ( nUsedBones > 1 ) + { + if ( nUsedBones > g_pMaterialSystemHardwareConfig->MaxVertexShaderBlendMatrices() ) + { + Warning( "Hardware morphing of decals will be busted! Too many unique bones on flexed vertices!\n" ); + } + + pStudioLOD->m_pHWMorphDecalBoneRemap = new int[ pStudioHdr->numbones ]; + memcpy( pStudioLOD->m_pHWMorphDecalBoneRemap, pBoneRemap, nBufSize ); + pStudioLOD->m_nDecalBoneCount = nMaxBoneCount; + } +} + + +//----------------------------------------------------------------------------- +// Hook needed by mdlcache to load the vertex data +//----------------------------------------------------------------------------- +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) +{ + // make requested data resident + return g_pStudioDataCache->CacheVertexData( (studiohdr_t *)pModelData ); +} + + +//----------------------------------------------------------------------------- +// Loads, unloads models +//----------------------------------------------------------------------------- +bool CStudioRenderContext::LoadModel( studiohdr_t *pStudioHdr, void *pVtxBuffer, studiohwdata_t *pStudioHWData ) +{ + int i; + int j; + + Assert( pStudioHdr ); + Assert( pVtxBuffer ); + Assert( pStudioHWData ); + + if ( !pStudioHdr || !pVtxBuffer || !pStudioHWData ) + return false; + + // NOTE: This must be called *after* Mod_LoadStudioModel + OptimizedModel::FileHeader_t* pVertexHdr = (OptimizedModel::FileHeader_t*)pVtxBuffer; + + if ( pVertexHdr->checkSum != pStudioHdr->checksum ) + { + ConDMsg("Error! Model %s .vtx file out of synch with .mdl\n", pStudioHdr->pszName() ); + return false; + } + + pStudioHWData->m_NumStudioMeshes = 0; + for ( i = 0; i < pStudioHdr->numbodyparts; i++ ) + { + mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i); + for (j = 0; j < pBodyPart->nummodels; j++) + { + pStudioHWData->m_NumStudioMeshes += pBodyPart->pModel(j)->nummeshes; + } + } + + // Create static meshes + Assert( pVertexHdr->numLODs ); + pStudioHWData->m_RootLOD = min( (int)pStudioHdr->rootLOD, pVertexHdr->numLODs-1 ); + pStudioHWData->m_NumLODs = pVertexHdr->numLODs; + pStudioHWData->m_pLODs = new studioloddata_t[pVertexHdr->numLODs]; + memset( pStudioHWData->m_pLODs, 0, pVertexHdr->numLODs * sizeof( studioloddata_t )); + + // reset the runtime flags + pStudioHdr->flags &= ~STUDIOHDR_FLAGS_USES_ENV_CUBEMAP; + pStudioHdr->flags &= ~STUDIOHDR_FLAGS_USES_FB_TEXTURE; + pStudioHdr->flags &= ~STUDIOHDR_FLAGS_USES_BUMPMAPPING; + +#ifdef _DEBUG + int totalNumMeshGroups = 0; +#endif + int nColorMeshID = 0; + int nLodID; + for ( nLodID = pStudioHWData->m_RootLOD; nLodID < pStudioHWData->m_NumLODs; nLodID++ ) + { + // Load materials and determine material dependent mesh requirements + LoadMaterials( pStudioHdr, pVertexHdr, pStudioHWData->m_pLODs[nLodID], nLodID ); + + // build the meshes + R_StudioCreateStaticMeshes( pStudioHdr, pVertexHdr, pStudioHWData, nLodID, &nColorMeshID ); + + // Build the hardware bone remap for decal rendering using HW morphing + ComputeHWMorphDecalBoneRemap( pStudioHdr, pVertexHdr, pStudioHWData, nLodID ); + + // garymcthack - need to check for NULL here. + // save off the lod switch point + pStudioHWData->m_pLODs[nLodID].m_SwitchPoint = pVertexHdr->pBodyPart( 0 )->pModel( 0 )->pLOD( nLodID )->switchPoint; + +#ifdef _DEBUG + studioloddata_t *pLOD = &pStudioHWData->m_pLODs[nLodID]; + for ( int meshID = 0; meshID < pStudioHWData->m_NumStudioMeshes; ++meshID ) + { + totalNumMeshGroups += pLOD->m_pMeshData[meshID].m_NumGroup; + } +#endif + } + +#ifdef _DEBUG + Assert( nColorMeshID == totalNumMeshGroups ); +#endif + + return true; +} + + +void CStudioRenderContext::UnloadModel( studiohwdata_t *pHardwareData ) +{ + int i; + for ( i = pHardwareData->m_RootLOD; i < pHardwareData->m_NumLODs; i++ ) + { + int j; + for ( j = 0; j < pHardwareData->m_pLODs[i].numMaterials; j++ ) + { + if ( pHardwareData->m_pLODs[i].ppMaterials[j] ) + { + pHardwareData->m_pLODs[i].ppMaterials[j]->DecrementReferenceCount(); + } + } + delete [] pHardwareData->m_pLODs[i].ppMaterials; + delete [] pHardwareData->m_pLODs[i].pMaterialFlags; + pHardwareData->m_pLODs[i].ppMaterials = NULL; + pHardwareData->m_pLODs[i].pMaterialFlags = NULL; + } + for ( i = pHardwareData->m_RootLOD; i < pHardwareData->m_NumLODs; i++ ) + { + R_StudioDestroyStaticMeshes( pHardwareData->m_NumStudioMeshes, &pHardwareData->m_pLODs[i].m_pMeshData ); + } + delete[] pHardwareData->m_pLODs; + pHardwareData->m_pLODs = NULL; +} + + +//----------------------------------------------------------------------------- +// Refresh the studiohdr since it was lost... +//----------------------------------------------------------------------------- +void CStudioRenderContext::RefreshStudioHdr( studiohdr_t* pStudioHdr, studiohwdata_t* pHardwareData ) +{ +} + +//----------------------------------------------------------------------------- +// Set the eye view target +//----------------------------------------------------------------------------- +void CStudioRenderContext::SetEyeViewTarget( const studiohdr_t *pStudioHdr, int nBodyIndex, const Vector& viewtarget ) +{ + VectorCopy( viewtarget, m_RC.m_ViewTarget ); +} + + +//----------------------------------------------------------------------------- +// Returns information about the ambient light samples +//----------------------------------------------------------------------------- +static TableVector s_pAmbientLightDir[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; + +int CStudioRenderContext::GetNumAmbientLightSamples() +{ + return 6; +} + +const Vector *CStudioRenderContext::GetAmbientLightDirections() +{ + return (const Vector*)s_pAmbientLightDir; +} + + +//----------------------------------------------------------------------------- +// Methods related to LOD +//----------------------------------------------------------------------------- +int CStudioRenderContext::GetNumLODs( const studiohwdata_t &hardwareData ) const +{ + return hardwareData.m_NumLODs; +} + +float CStudioRenderContext::GetLODSwitchValue( const studiohwdata_t &hardwareData, int nLOD ) const +{ + return hardwareData.m_pLODs[nLOD].m_SwitchPoint; +} + +void CStudioRenderContext::SetLODSwitchValue( studiohwdata_t &hardwareData, int nLOD, float flSwitchValue ) +{ + // NOTE: This must block the hardware thread since it reads this data. + // This method is only used in tools, though. + MaterialLock_t hLock = g_pMaterialSystem->Lock(); + hardwareData.m_pLODs[nLOD].m_SwitchPoint = flSwitchValue; + g_pMaterialSystem->Unlock( hLock ); +} + + +//----------------------------------------------------------------------------- +// Returns the first n materials. The studiohdr material list is the superset +// for all lods. +//----------------------------------------------------------------------------- +int CStudioRenderContext::GetMaterialList( studiohdr_t *pStudioHdr, int count, IMaterial** ppMaterials ) +{ + AssertMsg( pStudioHdr, "Don't ignore this assert! CStudioRenderContext::GetMaterialList() has null pStudioHdr." ); + + if ( !pStudioHdr ) + return 0; + + if ( pStudioHdr->textureindex == 0 ) + return 0; + + // iterate each texture + int i; + int j; + int found = 0; + for ( i = 0; i < pStudioHdr->numtextures; i++ ) + { + char szPath[MAX_PATH]; + IMaterial *pMaterial = NULL; + + // iterate quietly through all specified directories until a valid material is found + for ( j = 0; j < pStudioHdr->numcdtextures && IsErrorMaterial( pMaterial ); j++ ) + { + // If we don't do this, we get filenames like "materials\\blah.vmt". + const char *textureName = pStudioHdr->pTexture( i )->pszName(); + if ( textureName[0] == CORRECT_PATH_SEPARATOR || textureName[0] == INCORRECT_PATH_SEPARATOR ) + ++textureName; + + // This prevents filenames like /models/blah.vmt. + const char *pCdTexture = pStudioHdr->pCdtexture( j ); + if ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR ) + ++pCdTexture; + + V_ComposeFileName( pCdTexture, textureName, szPath, sizeof( szPath ) ); + + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_OBSOLETE ) + { + pMaterial = g_pMaterialSystem->FindMaterialEx( "models/obsolete/obsolete", TEXTURE_GROUP_MODEL, MATERIAL_FINDCONTEXT_ISONAMODEL, false ); + } + else + { + pMaterial = g_pMaterialSystem->FindMaterialEx( szPath, TEXTURE_GROUP_MODEL, MATERIAL_FINDCONTEXT_ISONAMODEL, false ); + } + } + + if ( !pMaterial ) + continue; + + if ( found < count ) + { + int k; + for ( k=0; k<found; k++ ) + { + if ( ppMaterials[k] == pMaterial ) + break; + } + if ( k >= found ) + { + // add uniquely + ppMaterials[found++] = pMaterial; + } + } + else + { + break; + } + } + + return found; +} + + +int CStudioRenderContext::GetMaterialListFromBodyAndSkin( MDLHandle_t studio, int nSkin, int nBody, int nCountOutputMaterials, IMaterial** ppOutputMaterials ) +{ + int found = 0; + + studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( studio ); + if ( pStudioHWData == NULL ) + return 0; + + for ( int lodID = pStudioHWData->m_RootLOD; lodID < pStudioHWData->m_NumLODs; lodID++ ) + { + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( studio ); + IMaterial **ppInputMaterials = pStudioHWData->m_pLODs[lodID].ppMaterials; + + if ( nSkin >= pStudioHdr->numskinfamilies ) + { + nSkin = 0; + } + + short *pSkinRef = pStudioHdr->pSkinref( nSkin * pStudioHdr->numskinref ); + + for (int i=0 ; i < pStudioHdr->numbodyparts ; i++) + { + mstudiomodel_t *pModel = NULL; + R_StudioSetupModel( i, nBody, &pModel, pStudioHdr ); + + // Iterate over all the meshes.... each mesh is a new material + for( int k = 0; k < pModel->nummeshes; ++k ) + { + mstudiomesh_t *pMesh = pModel->pMesh(k); + IMaterial *pMaterial = ppInputMaterials[pSkinRef[pMesh->material]]; + Assert( pMaterial ); + + int m; + for ( m=0; m<found; m++ ) + { + if ( ppOutputMaterials[m] == pMaterial ) + break; + } + if ( m >= found ) + { + // add uniquely + ppOutputMaterials[found++] = pMaterial; + + // No more room to store additional materials! + if ( found >= nCountOutputMaterials ) + return found; + } + } + } + } + + return found; +} + + +//----------------------------------------------------------------------------- +// Returns perf stats about a particular model +//----------------------------------------------------------------------------- +void CStudioRenderContext::GetPerfStats( DrawModelResults_t *pResults, const DrawModelInfo_t &info, CUtlBuffer *pSpewBuf ) const +{ + pResults->m_ActualTriCount = pResults->m_TextureMemoryBytes = 0; + pResults->m_Materials.RemoveAll(); + + Assert( info.m_Lod >= 0 ); + if ( info.m_Lod < 0 || !info.m_pHardwareData->m_pLODs ) + return; + + studiomeshdata_t *pStudioMeshes = info.m_pHardwareData->m_pLODs[info.m_Lod].m_pMeshData; + + // Set up an array that keeps up with the number of used hardware bones in the models. + CUtlVector<bool> hardwareBonesUsed; + hardwareBonesUsed.EnsureCount( info.m_pStudioHdr->numbones ); + int i; + for( i = 0; i < info.m_pStudioHdr->numbones; i++ ) + { + hardwareBonesUsed[i] = false; + } + + // Warning( "\n\n\n" ); + pResults->m_NumMaterials = 0; + int numBoneStateChangeBatches = 0; + int numBoneStateChanges = 0; + // Iterate over every submodel... + IMaterial **ppMaterials = info.m_pHardwareData->m_pLODs[info.m_Lod].ppMaterials; + + int nSkin = info.m_Skin; + if ( nSkin >= info.m_pStudioHdr->numskinfamilies ) + { + nSkin = 0; + } + short *pSkinRef = info.m_pStudioHdr->pSkinref( nSkin * info.m_pStudioHdr->numskinref ); + + pResults->m_NumBatches = 0; + + for (i=0 ; i < info.m_pStudioHdr->numbodyparts ; i++) + { + mstudiomodel_t *pModel = NULL; + R_StudioSetupModel( i, info.m_Body, &pModel, info.m_pStudioHdr ); + + // Iterate over all the meshes.... each mesh is a new material + int k; + for( k = 0; k < pModel->nummeshes; ++k ) + { + mstudiomesh_t *pMesh = pModel->pMesh(k); + IMaterial *pMaterial = ppMaterials[pSkinRef[pMesh->material]]; + Assert( pMaterial ); + studiomeshdata_t *pMeshData = &pStudioMeshes[pMesh->meshid]; + if( pMeshData->m_NumGroup == 0 ) + continue; + + Assert( pResults->m_NumMaterials == pResults->m_Materials.Count() ); + pResults->m_NumMaterials++; + if( pResults->m_NumMaterials < MAX_DRAW_MODEL_INFO_MATERIALS ) + { + pResults->m_Materials.AddToTail( pMaterial ); + } + else + { + Assert( 0 ); + } + if( pSpewBuf ) + { + pSpewBuf->Printf( " material: %s\n", pMaterial->GetName() ); + } + int numPasses = m_RC.m_pForcedMaterial ? m_RC.m_pForcedMaterial->GetNumPasses() : pMaterial->GetNumPasses(); + if( pSpewBuf ) + { + pSpewBuf->Printf( " numPasses:%d\n", numPasses ); + } + int bytes = pMaterial->GetTextureMemoryBytes(); + pResults->m_TextureMemoryBytes += bytes; + if( pSpewBuf ) + { + pSpewBuf->Printf( " texture memory: %d (Only valid in a rendering app)\n", bytes ); + } + + // Iterate over all stripgroups + int stripGroupID; + for( stripGroupID = 0; stripGroupID < pMeshData->m_NumGroup; stripGroupID++ ) + { + studiomeshgroup_t *pMeshGroup = &pMeshData->m_pMeshGroup[stripGroupID]; + bool bIsFlexed = ( pMeshGroup->m_Flags & MESHGROUP_IS_FLEXED ) != 0; + bool bIsHWSkinned = ( pMeshGroup->m_Flags & MESHGROUP_IS_HWSKINNED ) != 0; + + if( pSpewBuf ) + { + pSpewBuf->Printf( " %d batch(es):\n", ( int )pMeshGroup->m_NumStrips ); + } + // Iterate over all strips. . . each strip potentially changes bones states. + int stripID; + for( stripID = 0; stripID < pMeshGroup->m_NumStrips; stripID++ ) + { + pResults->m_NumBatches++; + + OptimizedModel::StripHeader_t *pStripData = &pMeshGroup->m_pStripData[stripID]; + numBoneStateChangeBatches++; + numBoneStateChanges += pStripData->numBoneStateChanges; + + if( bIsHWSkinned ) + { + // Only count bones as hardware bones if we are using hardware skinning here. + int boneID; + for( boneID = 0; boneID < pStripData->numBoneStateChanges; boneID++ ) + { + OptimizedModel::BoneStateChangeHeader_t *pBoneStateChange = pStripData->pBoneStateChange( boneID ); + hardwareBonesUsed[pBoneStateChange->newBoneID] = true; + } + } + + if( pStripData->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + // TODO: need to factor in bIsFlexed and bIsHWSkinned + int numTris = pStripData->numIndices / 3; + if( pSpewBuf ) + { + pSpewBuf->Printf( " %s%s", bIsFlexed ? "flexed " : "nonflexed ", + bIsHWSkinned ? "hwskinned " : "swskinned " ); + pSpewBuf->Printf( "tris: %d ", numTris ); + pSpewBuf->Printf( "bone changes: %d bones/strip: %d\n", pStripData->numBoneStateChanges, + ( int )pStripData->numBones ); + } + pResults->m_ActualTriCount += numTris * numPasses; + } + else if( pStripData->flags & OptimizedModel::STRIP_IS_TRISTRIP ) + { + Assert( 0 ); // FIXME: fill this in when we start using strips again. + } + else + { + Assert( 0 ); + } + } + } + } + } + if( pSpewBuf ) + { + char nil = '\0'; + pSpewBuf->Put( &nil, 1 );; + } + + pResults->m_NumHardwareBones = 0; + for( i = 0; i < info.m_pStudioHdr->numbones; i++ ) + { + if( hardwareBonesUsed[i] ) + { + pResults->m_NumHardwareBones++; + } + } +} + + +//----------------------------------------------------------------------------- +// Begin/end frame +//----------------------------------------------------------------------------- +static ConVar r_hwmorph( "r_hwmorph", "1", FCVAR_CHEAT ); + +void CStudioRenderContext::BeginFrame( void ) +{ + // Cache a few values here so I don't have to in software inner loops: + Assert( g_pMaterialSystemHardwareConfig ); + m_RC.m_Config.m_bSupportsVertexAndPixelShaders = g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders(); + m_RC.m_Config.m_bSupportsOverbright = g_pMaterialSystemHardwareConfig->SupportsOverbright(); + m_RC.m_Config.m_bEnableHWMorph = r_hwmorph.GetInt() != 0; + + // Haven't implemented the hw morph with threading yet + if ( g_pMaterialSystem->GetThreadMode() != MATERIAL_SINGLE_THREADED ) + { + m_RC.m_Config.m_bEnableHWMorph = false; + } + + m_RC.m_Config.m_bStatsMode = false; + + g_pStudioRenderImp->PrecacheGlint(); +} + +void CStudioRenderContext::EndFrame( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Methods related to config +//----------------------------------------------------------------------------- +void CStudioRenderContext::UpdateConfig( const StudioRenderConfig_t& config ) +{ + memcpy( &m_RC.m_Config, &config, sizeof( StudioRenderConfig_t ) ); +} + +void CStudioRenderContext::GetCurrentConfig( StudioRenderConfig_t& config ) +{ + memcpy( &config, &m_RC.m_Config, sizeof( StudioRenderConfig_t ) ); +} + + +//----------------------------------------------------------------------------- +// Material overrides +//----------------------------------------------------------------------------- +void CStudioRenderContext::ForcedMaterialOverride( IMaterial *newMaterial, OverrideType_t nOverrideType ) +{ + m_RC.m_pForcedMaterial = newMaterial; + m_RC.m_nForcedMaterialType = nOverrideType; +} + +//----------------------------------------------------------------------------- +// Return the material overrides +//----------------------------------------------------------------------------- +void CStudioRenderContext::GetMaterialOverride( IMaterial** ppOutForcedMaterial, OverrideType_t* pOutOverrideType ) +{ + Assert( ppOutForcedMaterial != NULL && pOutOverrideType != NULL ); + *ppOutForcedMaterial = m_RC.m_pForcedMaterial; + *pOutOverrideType = m_RC.m_nForcedMaterialType; +} + +//----------------------------------------------------------------------------- +// Sets the view state +//----------------------------------------------------------------------------- +void CStudioRenderContext::SetViewState( const Vector& viewOrigin, + const Vector& viewRight, const Vector& viewUp, const Vector& viewPlaneNormal ) +{ + VectorCopy( viewOrigin, m_RC.m_ViewOrigin ); + VectorCopy( viewRight, m_RC.m_ViewRight ); + VectorCopy( viewUp, m_RC.m_ViewUp ); + VectorCopy( viewPlaneNormal, m_RC.m_ViewPlaneNormal ); +} + + +//----------------------------------------------------------------------------- +// Sets lighting state +//----------------------------------------------------------------------------- +void CStudioRenderContext::SetAmbientLightColors( const Vector *pColors ) +{ + for( int i = 0; i < 6; i++ ) + { + VectorCopy( pColors[i], m_RC.m_LightBoxColors[i].AsVector3D() ); + m_RC.m_LightBoxColors[i][3] = 1.0f; + } + + // FIXME: Would like to get this into the render thread, but there's systemic confusion + // about whether to set lighting state here or in the material system + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->SetAmbientLightCube( m_RC.m_LightBoxColors ); +} + +void CStudioRenderContext::SetAmbientLightColors( const Vector4D *pColors ) +{ + memcpy( m_RC.m_LightBoxColors, pColors, 6 * sizeof(Vector4D) ); + + // FIXME: Would like to get this into the render thread, but there's systemic confusion + // about whether to set lighting state here or in the material system + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->SetAmbientLightCube( m_RC.m_LightBoxColors ); +} + +void CStudioRenderContext::SetLocalLights( int nLightCount, const LightDesc_t *pLights ) +{ + m_RC.m_NumLocalLights = CopyLocalLightingState( MAXLOCALLIGHTS, m_RC.m_LocalLights, nLightCount, pLights ); + + // FIXME: Would like to get this into the render thread, but there's systemic confusion + // about whether to set lighting state here or in the material system + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + if ( m_RC.m_Config.bSoftwareLighting || m_RC.m_NumLocalLights == 0 ) + { + pRenderContext->DisableAllLocalLights(); + } + else + { + int i; + int nMaxLightCount = g_pMaterialSystemHardwareConfig->MaxNumLights(); + int nLightCount = min( m_RC.m_NumLocalLights, nMaxLightCount ); + for( i = 0; i < nLightCount; i++ ) + { + pRenderContext->SetLight( i, m_RC.m_LocalLights[i] ); + } + for( ; i < nMaxLightCount; i++ ) + { + LightDesc_t desc; + desc.m_Type = MATERIAL_LIGHT_DISABLE; + pRenderContext->SetLight( i, desc ); + } + } +} + + +//----------------------------------------------------------------------------- +// Sets the color modulation +//----------------------------------------------------------------------------- +void CStudioRenderContext::SetColorModulation( const float* pColor ) +{ + VectorCopy( pColor, m_RC.m_ColorMod ); +} + +void CStudioRenderContext::SetAlphaModulation( float alpha ) +{ + m_RC.m_AlphaMod = alpha; +} + + +//----------------------------------------------------------------------------- +// Used to set bone-to-world transforms. +// FIXME: Should this be a lock/unlock pattern so we can't read after unlock? +//----------------------------------------------------------------------------- +matrix3x4_t* CStudioRenderContext::LockBoneMatrices( int nCount ) +{ + MEM_ALLOC_CREDIT_( "CStudioRenderContext::m_BoneToWorldMatrices" ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + CMatRenderData<matrix3x4_t> rdMatrix( pRenderContext ); + matrix3x4_t *pDest = rdMatrix.Lock( nCount ); + return pDest; +} + +void CStudioRenderContext::UnlockBoneMatrices() +{ +} + + +//----------------------------------------------------------------------------- +// Allocates flex weights +//----------------------------------------------------------------------------- +void CStudioRenderContext::LockFlexWeights( int nWeightCount, float **ppFlexWeights, float **ppFlexDelayedWeights ) +{ + MEM_ALLOC_CREDIT_( "CStudioRenderContext::m_FlexWeights" ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + CMatRenderData<float> rdFlex( pRenderContext ); + CMatRenderData<float> rdFlexDelayed( pRenderContext ); + float *pFlexOut = rdFlex.Lock( nWeightCount ); + for ( int i = 0; i < nWeightCount; i++ ) + { + pFlexOut[i] = 0.0f; + } + *ppFlexWeights = pFlexOut; + if ( ppFlexDelayedWeights ) + { + pFlexOut = rdFlexDelayed.Lock( nWeightCount ); + for ( int i = 0; i < nWeightCount; i++ ) + { + pFlexOut[i] = 0.0f; + } + *ppFlexDelayedWeights = pFlexOut; + } +} + +void CStudioRenderContext::UnlockFlexWeights() +{ +} + + +//----------------------------------------------------------------------------- +// Methods related to flex weights +//----------------------------------------------------------------------------- +static ConVar r_randomflex( "r_randomflex", "0", FCVAR_CHEAT ); + + +//----------------------------------------------------------------------------- +// This will generate random flex data that has a specified # of non-zero values +//----------------------------------------------------------------------------- +void CStudioRenderContext::GenerateRandomFlexWeights( int nWeightCount, float* pWeights, float *pDelayedWeights ) +{ + int nRandomFlex = r_randomflex.GetInt(); + if ( nRandomFlex <= 0 || !pWeights ) + return; + + if ( nRandomFlex > nWeightCount ) + { + nRandomFlex = nWeightCount; + } + + int *pIndices = (int*)_alloca( nWeightCount * sizeof(int) ); + for ( int i = 0; i < nWeightCount; ++i ) + { + pIndices[i] = i; + } + + // Shuffle + for ( int i = 0; i < nWeightCount; ++i ) + { + int n = RandomInt( 0, nWeightCount-1 ); + int nTemp = pIndices[n]; + pIndices[n] = pIndices[i]; + pIndices[i] = nTemp; + } + + memset( pWeights, 0, nWeightCount * sizeof(float) ); + for ( int i = 0; i < nRandomFlex; ++i ) + { + pWeights[ pIndices[i] ] = RandomFloat( 0.0f, 1.0f ); + } + if ( pDelayedWeights ) + { + memset( pDelayedWeights, 0, nWeightCount * sizeof(float) ); + for ( int i = 0; i < nRandomFlex; ++i ) + { + pDelayedWeights[ pIndices[i] ] = RandomFloat( 0.0f, 1.0f ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes LOD +//----------------------------------------------------------------------------- +int CStudioRenderContext::ComputeRenderLOD( IMatRenderContext *pRenderContext, + const DrawModelInfo_t& info, const Vector &origin, float *pMetric ) +{ + int lod = info.m_Lod; + int lastlod = info.m_pHardwareData->m_NumLODs - 1; + + if ( pMetric ) + { + *pMetric = 0.0f; + } + + if ( lod == USESHADOWLOD ) + return lastlod; + + if ( lod != -1 ) + return clamp( lod, info.m_pHardwareData->m_RootLOD, lastlod ); + + float screenSize = pRenderContext->ComputePixelWidthOfSphere( origin, 0.5f ); + lod = ComputeModelLODAndMetric( info.m_pHardwareData, screenSize, pMetric ); + + // make sure we have a valid lod + if ( info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) + { + lastlod--; + } + + lod = clamp( lod, info.m_pHardwareData->m_RootLOD, lastlod ); + return lod; +} + + +//----------------------------------------------------------------------------- +// This invokes proxies of all materials that are queued to be rendered +// It has the effect of ensuring the material vars are in the correct state +// since material var sets generated by the proxy bind are queued. +//----------------------------------------------------------------------------- +void CStudioRenderContext::InvokeBindProxies( const DrawModelInfo_t &info ) +{ + if ( m_RC.m_pForcedMaterial ) + { + if ( m_RC.m_nForcedMaterialType == OVERRIDE_NORMAL && m_RC.m_pForcedMaterial->HasProxy() ) + { + m_RC.m_pForcedMaterial->CallBindProxy( info.m_pClientEntity ); + } + return; + } + + // get skinref array + int nSkin = ( m_RC.m_Config.skin > 0 ) ? m_RC.m_Config.skin : info.m_Skin; + short *pSkinRef = info.m_pStudioHdr->pSkinref( 0 ); + if ( nSkin > 0 && nSkin < info.m_pStudioHdr->numskinfamilies ) + { + pSkinRef += ( nSkin * info.m_pStudioHdr->numskinref ); + } + + // This is used to ensure proxies are only called once + int nBufSize = info.m_pStudioHdr->numtextures * sizeof(bool); + bool *pProxyCalled = (bool*)stackalloc( nBufSize ); + memset( pProxyCalled, 0, nBufSize ); + + IMaterial **ppMaterials = info.m_pHardwareData->m_pLODs[ info.m_Lod ].ppMaterials; + mstudiomodel_t *pModel; + for ( int i=0 ; i < info.m_pStudioHdr->numbodyparts; ++i ) + { + R_StudioSetupModel( i, info.m_Body, &pModel, info.m_pStudioHdr ); + for ( int somethingOtherThanI = 0; somethingOtherThanI < pModel->nummeshes; ++somethingOtherThanI) + { + mstudiomesh_t *pMesh = pModel->pMesh(somethingOtherThanI); + int nMaterialIndex = pSkinRef[ pMesh->material ]; + if ( pProxyCalled[ nMaterialIndex ] ) + continue; + pProxyCalled[ nMaterialIndex ] = true; + IMaterial* pMaterial = ppMaterials[ nMaterialIndex ]; + if ( pMaterial && pMaterial->HasProxy() ) + { + pMaterial->CallBindProxy( info.m_pClientEntity ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Draws a model +//----------------------------------------------------------------------------- +void CStudioRenderContext::DrawModel( DrawModelResults_t *pResults, const DrawModelInfo_t& info, + matrix3x4_t *pBoneToWorld, float *pFlexWeights, float *pFlexDelayedWeights, const Vector &origin, int flags ) +{ + // Set to zero in case we don't render anything. + if ( pResults ) + { + pResults->m_ActualTriCount = pResults->m_TextureMemoryBytes = 0; + } + + if( !info.m_pStudioHdr || !info.m_pHardwareData || + !info.m_pHardwareData->m_NumLODs || !info.m_pHardwareData->m_pLODs ) + { + return; + } + + // Replace the flex weight data with random data for testing + GenerateRandomFlexWeights( info.m_pStudioHdr->numflexdesc, pFlexWeights, pFlexDelayedWeights ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + float flMetric; + const_cast<DrawModelInfo_t*>( &info )->m_Lod = ComputeRenderLOD( pRenderContext, info, origin, &flMetric ); + if ( pResults ) + { + pResults->m_nLODUsed = info.m_Lod; + pResults->m_flLODMetric = flMetric; + } + + MaterialLock_t hLock = 0; + if ( flags & STUDIORENDER_DRAW_ACCURATETIME ) + { + VPROF("STUDIORENDER_DRAW_ACCURATETIME"); + + // Flush the material system before timing this model: + hLock = g_pMaterialSystem->Lock(); + g_pMaterialSystem->Flush(true); + } + + if ( pResults ) + { + pResults->m_RenderTime.Start(); + } + + FlexWeights_t flex; + flex.m_pFlexWeights = pFlexWeights ? pFlexWeights : s_pZeroFlexWeights; + flex.m_pFlexDelayedWeights = pFlexDelayedWeights ? pFlexDelayedWeights : flex.m_pFlexWeights; + + ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); + if ( !pCallQueue || studio_queue_mode.GetInt() == 0 ) + { + g_pStudioRenderImp->DrawModel( info, m_RC, pBoneToWorld, flex, flags ); + } + else + { + CMatRenderData<matrix3x4_t> rdMatrix( pRenderContext, info.m_pStudioHdr->numbones, pBoneToWorld ); + CMatRenderData<float> rdFlex( pRenderContext ); + CMatRenderData<float> rdFlexDelayed( pRenderContext ); + + InvokeBindProxies( info ); + pBoneToWorld = rdMatrix.Base(); + if ( info.m_pStudioHdr->numflexdesc != 0 ) + { + rdFlex.Lock( info.m_pStudioHdr->numflexdesc, flex.m_pFlexWeights ); + flex.m_pFlexWeights = rdFlex.Base(); + if ( !pFlexDelayedWeights ) + { + flex.m_pFlexDelayedWeights = flex.m_pFlexWeights; + } + else + { + rdFlexDelayed.Lock( info.m_pStudioHdr->numflexdesc, flex.m_pFlexDelayedWeights ); + flex.m_pFlexDelayedWeights = rdFlexDelayed.Base(); + } + } + pCallQueue->QueueCall( g_pStudioRenderImp, &CStudioRender::DrawModel, info, m_RC, pBoneToWorld, flex, flags ); + } + + if( flags & STUDIORENDER_DRAW_ACCURATETIME ) + { + VPROF( "STUDIORENDER_DRAW_ACCURATETIME" ); + + // Make sure this model is completely drawn before ending the timer: + g_pMaterialSystem->Flush(true); + g_pMaterialSystem->Flush(true); + g_pMaterialSystem->Unlock( hLock ); + } + + if ( pResults ) + { + pResults->m_RenderTime.End(); + if( flags & STUDIORENDER_DRAW_GET_PERF_STATS ) + { + GetPerfStats( pResults, info, 0 ); + } + } +} + + +void CStudioRenderContext::DrawModelArray( const DrawModelInfo_t &drawInfo, int arrayCount, model_array_instance_t *pInstanceData, int instanceStride, int flags ) +{ + // UNDONE: Support queue mode? + g_pStudioRenderImp->DrawModelArray( drawInfo, m_RC, arrayCount, pInstanceData, instanceStride, flags ); +} + +//----------------------------------------------------------------------------- +// Methods related to rendering static props +//----------------------------------------------------------------------------- +void CStudioRenderContext::DrawModelStaticProp( const DrawModelInfo_t& info, const matrix3x4_t &modelToWorld, int flags ) +{ + if ( info.m_Lod < info.m_pHardwareData->m_RootLOD ) + { + const_cast< DrawModelInfo_t* >( &info )->m_Lod = info.m_pHardwareData->m_RootLOD; + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); + if ( !pCallQueue || studio_queue_mode.GetInt() == 0 ) + { + g_pStudioRenderImp->DrawModelStaticProp( info, m_RC, modelToWorld, flags ); + } + else + { + InvokeBindProxies( info ); + pCallQueue->QueueCall( g_pStudioRenderImp, &CStudioRender::DrawModelStaticProp, info, m_RC, modelToWorld, flags ); + } +} + +void CStudioRenderContext::DrawStaticPropDecals( const DrawModelInfo_t &info, const matrix3x4_t &modelToWorld ) +{ + QUEUE_STUDIORENDER_CALL( DrawStaticPropDecals, CStudioRender, g_pStudioRenderImp, info, m_RC, modelToWorld ); +} + +void CStudioRenderContext::DrawStaticPropShadows( const DrawModelInfo_t &info, const matrix3x4_t &modelToWorld, int flags ) +{ + QUEUE_STUDIORENDER_CALL( DrawStaticPropShadows, CStudioRender, g_pStudioRenderImp, info, m_RC, modelToWorld, flags ); +} + + +//----------------------------------------------------------------------------- +// Methods related to shadows +//----------------------------------------------------------------------------- +void CStudioRenderContext::AddShadow( IMaterial* pMaterial, void* pProxyData, + FlashlightState_t *pFlashlightState, VMatrix *pWorldToTexture, ITexture *pFlashlightDepthTexture ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); + if ( !pCallQueue || studio_queue_mode.GetInt() == 0 ) + { + g_pStudioRenderImp->AddShadow( pMaterial, pProxyData, pFlashlightState, pWorldToTexture, pFlashlightDepthTexture ); + } + else + { + // NOTE: We don't need to make proxies work, because proxies are only ever used + // when casting shadows onto props, which we don't do..that feature is disabled. + // When casting flashlights onto mdls, which we *do* use, the proxy is NULL. + Assert( pProxyData == NULL ); + if ( pProxyData != NULL ) + { + Warning( "Cannot call CStudioRenderContext::AddShadows w/ proxies in queued mode!\n" ); + return; + } + + CMatRenderData< FlashlightState_t > rdFlashlight( pRenderContext, 1, pFlashlightState ); + CMatRenderData< VMatrix > rdMatrix( pRenderContext, 1, pWorldToTexture ); + pCallQueue->QueueCall( g_pStudioRenderImp, &CStudioRender::AddShadow, pMaterial, + (void*)NULL, rdFlashlight.Base(), rdMatrix.Base(), pFlashlightDepthTexture ); + } +} + +void CStudioRenderContext::ClearAllShadows() +{ + QUEUE_STUDIORENDER_CALL( ClearAllShadows, CStudioRender, g_pStudioRenderImp ); +} + + +//----------------------------------------------------------------------------- +// Methods related to decals +//----------------------------------------------------------------------------- +void CStudioRenderContext::DestroyDecalList( StudioDecalHandle_t handle ) +{ + QUEUE_STUDIORENDER_CALL( DestroyDecalList, CStudioRender, g_pStudioRenderImp, handle ); +} + +void CStudioRenderContext::AddDecal( StudioDecalHandle_t handle, studiohdr_t *pStudioHdr, + matrix3x4_t *pBoneToWorld, const Ray_t& ray, const Vector& decalUp, + IMaterial* pDecalMaterial, float radius, int body, bool noPokethru, int maxLODToDecal ) +{ + // This substition always has to be done in the main thread, so do it here. + pDecalMaterial = GetModelSpecificDecalMaterial( pDecalMaterial ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + Assert( pRenderContext->IsRenderData( pBoneToWorld ) ); + QUEUE_STUDIORENDER_CALL_RC( AddDecal, CStudioRender, g_pStudioRenderImp, pRenderContext, + handle, m_RC, pBoneToWorld, pStudioHdr, ray, decalUp, pDecalMaterial, radius, + body, noPokethru, maxLODToDecal ); +} + +// Function to do replacement because we always need to do this from the main thread. +IMaterial* GetModelSpecificDecalMaterial( IMaterial* pDecalMaterial ) +{ + Assert( ThreadInMainThread() ); + // Since we're adding this to a studio model, check the decal to see if + // there's an alternate form used for static props... + bool found; + IMaterialVar* pModelMaterialVar = pDecalMaterial->FindVar( "$modelmaterial", &found, false ); + if ( found ) + { + IMaterial* pModelMaterial = g_pMaterialSystem->FindMaterial( pModelMaterialVar->GetStringValue(), TEXTURE_GROUP_DECAL, false ); + if ( !IsErrorMaterial( pModelMaterial ) ) + { + return pModelMaterial; + } + } + + return pDecalMaterial; +} + + |