//===== Copyright (c) 1996-2007, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include #include #include "studiorender.h" #include "studiorendercontext.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imaterialsystemhardwareconfig.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "materialsystem/imesh.h" #include "optimize.h" #include "mathlib/vmatrix.h" #include "tier0/vprof.h" #include "tier1/strtools.h" #include "tier1/keyvalues.h" #include "tier0/memalloc.h" #include "convar.h" #include "materialsystem/itexture.h" #include "tier2/tier2.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Singleton instance //----------------------------------------------------------------------------- CStudioRender g_StudioRender; CStudioRender *g_pStudioRenderImp = &g_StudioRender; //----------------------------------------------------------------------------- // Activate to get stats //----------------------------------------------------------------------------- //#define REPORT_FLEX_STATS 1 #ifdef REPORT_FLEX_STATS static int s_nModelsDrawn = 0; static int s_nActiveFlexCount = 0; static ConVar r_flexstats( "r_flexstats", "0", FCVAR_CHEAT ); #endif // Multiplicative factor on LOD switch points. See GetLODForMetric() in studio.h static ConVar r_lod_switch_scale( "r_lod_switch_scale", "1", FCVAR_HIDDEN ); #ifndef _CERT static ConVar mat_rendered_faces_count( "mat_rendered_faces_count", "0", FCVAR_CHEAT, "Set to N to count how many faces each model draws each frame and spew the top N offenders from the last 150 frames (use 'mat_rendered_faces_spew' to spew all models rendered in the current frame)" ); static ConVar mat_print_top_model_vert_counts( "mat_print_top_model_vert_counts", "0", 0, "Constantly print to screen the top N models as measured by total faces rendered this frame"); bool ModelFaceCountHashCompareFunc( studiohwdata_t *const &a, studiohwdata_t *const &b ) { return a == b; } uint32 ModelFaceCountHashKeyFunc( studiohwdata_t *const &a ) { return HashIntConventional( (int32)(intp)a ); } #endif // !_CERT //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CStudioRender::CStudioRender() #ifndef _CERT : m_ModelFaceCountHash( 1024, 0, 0, ModelFaceCountHashCompareFunc, ModelFaceCountHashKeyFunc ) #endif { m_pRC = NULL; m_pBoneToWorld = NULL; m_pFlexWeights = NULL; m_pFlexDelayedWeights = NULL; m_pStudioHdr = NULL; m_pStudioMeshes = NULL; m_pSubModel = NULL; m_pStudioHWData = NULL; m_pGlintTexture = NULL; m_GlintWidth = 0; m_GlintHeight = 0; m_pCurrentFlashlight = 0; // Cache-align our important matrices g_pMemAlloc->PushAllocDbgInfo( __FILE__, __LINE__ ); m_PoseToWorld = (matrix3x4_t*)MemAlloc_AllocAligned( MAXSTUDIOBONES * sizeof(matrix3x4_t), 32 ); m_PoseToDecal = (matrix3x4_t*)MemAlloc_AllocAligned( MAXSTUDIOBONES * sizeof(matrix3x4_t), 32 ); g_pMemAlloc->PopAllocDbgInfo(); m_nDecalId = 1; } CStudioRender::~CStudioRender() { MemAlloc_FreeAligned(m_PoseToWorld); MemAlloc_FreeAligned(m_PoseToDecal); } void CStudioRender::InitDebugMaterials( void ) { // Four Wireframe Materials: ( ZBuffer, DisplacementMapped ) m_pMaterialWireframe[0][0] = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmrmwireframe", TEXTURE_GROUP_OTHER, true ); m_pMaterialWireframe[0][0]->IncrementReferenceCount(); m_pMaterialWireframe[1][0] = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmrmwireframezbuffer", TEXTURE_GROUP_OTHER, true ); m_pMaterialWireframe[1][0]->IncrementReferenceCount(); m_pMaterialWireframe[0][1] = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmrmwireframedisplaced", TEXTURE_GROUP_OTHER, true ); m_pMaterialWireframe[0][1]->IncrementReferenceCount(); m_pMaterialWireframe[1][1] = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmrmwireframezbufferdisplaced", TEXTURE_GROUP_OTHER, true ); m_pMaterialWireframe[1][1]->IncrementReferenceCount(); m_pMaterialMRMNormals = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmrmnormals", TEXTURE_GROUP_OTHER, true ); m_pMaterialMRMNormals->IncrementReferenceCount(); m_pMaterialTangentFrame = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugvertexcolor", TEXTURE_GROUP_OTHER, true ); m_pMaterialTangentFrame->IncrementReferenceCount(); m_pMaterialTranslucentModelHulls = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugtranslucentmodelhulls", TEXTURE_GROUP_OTHER, true ); m_pMaterialTranslucentModelHulls->IncrementReferenceCount(); m_pMaterialSolidModelHulls = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugsolidmodelhulls", TEXTURE_GROUP_OTHER, true ); m_pMaterialSolidModelHulls->IncrementReferenceCount(); m_pMaterialAdditiveVertexColorVertexAlpha = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/additivevertexcolorvertexalpha", TEXTURE_GROUP_OTHER, true ); m_pMaterialAdditiveVertexColorVertexAlpha->IncrementReferenceCount(); m_pMaterialModelBones = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugmodelbones", TEXTURE_GROUP_OTHER, true ); m_pMaterialModelBones->IncrementReferenceCount(); m_pMaterialModelEnvCubemap = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/env_cubemap_model", TEXTURE_GROUP_OTHER, true ); m_pMaterialModelEnvCubemap->IncrementReferenceCount(); m_pMaterialWorldWireframe = g_pMaterialSystem->FindMaterial( "//platform/materials/debug/debugworldwireframe", TEXTURE_GROUP_OTHER, true ); m_pMaterialWorldWireframe->IncrementReferenceCount(); KeyValues *pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[0][0][0] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite000", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[0][0][0]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[0][1][0] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite010", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[0][1][0]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[1][0][0] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite100", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[1][0][0]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[1][1][0] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite110", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[1][1][0]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[0][0][1] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite001", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[0][0][1]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[0][1][1] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite011", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[0][1][1]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[1][0][1] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite101", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[1][0][1]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt("$color_depth", 0); m_pDepthWrite[1][1][1] = g_pMaterialSystem->FindProceduralMaterial("__DepthWrite111", TEXTURE_GROUP_OTHER, pVMTKeyValues); m_pDepthWrite[1][1][1]->IncrementReferenceCount(); // Full frame depth as color (r32f) pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 0 ][ 0 ][ 0 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite000", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 0 ][ 0 ][ 0 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 0 ][ 1 ][ 0 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite010", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 0 ][ 1 ][ 0 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 1 ][ 0 ][ 0 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite100", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 1 ][ 0 ][ 0 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 0 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 1 ][ 1 ][ 0 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite110", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 1 ][ 1 ][ 0 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 0 ][ 0 ][ 1 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite001", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 0 ][ 0 ][ 1 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 0 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 0 ][ 1 ][ 1 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite011", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 0 ][ 1 ][ 1 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 0 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 1 ][ 0 ][ 1 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite101", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 1 ][ 0 ][ 1 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "DepthWrite" ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$alphatest", 1 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$treesway", 1 ); pVMTKeyValues->SetInt( "$color_depth", 1 ); m_pSSAODepthWrite[ 1 ][ 1 ][ 1 ] = g_pMaterialSystem->FindProceduralMaterial( "__ColorDepthWrite111", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pSSAODepthWrite[ 1 ][ 1 ][ 1 ]->IncrementReferenceCount(); pVMTKeyValues = new KeyValues( "EyeGlint" ); m_pGlintBuildMaterial = g_pMaterialSystem->CreateMaterial( "___glintbuildmaterial", pVMTKeyValues ); pVMTKeyValues = new KeyValues( "unlitgeneric" ); pVMTKeyValues->SetInt( "$color", 0 ); pVMTKeyValues->SetInt( "$nocull", 1 ); pVMTKeyValues->SetInt( "$writez", 0 ); m_pMaterialSolidBackfacePrepass = g_pMaterialSystem->FindProceduralMaterial( "__utilBackfacePrepass", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_pMaterialSolidBackfacePrepass->IncrementReferenceCount(); } void CStudioRender::ShutdownDebugMaterials( void ) { for ( int i=0; i<2; i++ ) { for ( int j=0; j<2; j++ ) { if ( m_pMaterialWireframe[i][j] ) { m_pMaterialWireframe[i][j]->DecrementReferenceCount(); m_pMaterialWireframe[i][j] = NULL; } } } if ( m_pMaterialMRMNormals ) { m_pMaterialMRMNormals->DecrementReferenceCount(); m_pMaterialMRMNormals = NULL; } if ( m_pMaterialTangentFrame ) { m_pMaterialTangentFrame->DecrementReferenceCount(); m_pMaterialTangentFrame = NULL; } if ( m_pMaterialTranslucentModelHulls ) { m_pMaterialTranslucentModelHulls->DecrementReferenceCount(); m_pMaterialTranslucentModelHulls = NULL; } if ( m_pMaterialSolidModelHulls ) { m_pMaterialSolidModelHulls->DecrementReferenceCount(); m_pMaterialSolidModelHulls = NULL; } if ( m_pMaterialAdditiveVertexColorVertexAlpha ) { m_pMaterialAdditiveVertexColorVertexAlpha->DecrementReferenceCount(); m_pMaterialAdditiveVertexColorVertexAlpha = NULL; } if ( m_pMaterialModelBones ) { m_pMaterialModelBones->DecrementReferenceCount(); m_pMaterialModelBones = NULL; } if ( m_pMaterialModelEnvCubemap ) { m_pMaterialModelEnvCubemap->DecrementReferenceCount(); m_pMaterialModelEnvCubemap = NULL; } if ( m_pMaterialWorldWireframe ) { m_pMaterialWorldWireframe->DecrementReferenceCount(); m_pMaterialWorldWireframe = NULL; } // DepthWrite materials for ( int32 i = 0; i < 8; i++ ) { if ( m_pDepthWrite[ ( i & 0x4 ) >> 2 ][ ( i & 0x2 ) >> 1 ][ i & 0x1 ] ) { m_pDepthWrite[ ( i & 0x4 ) >> 2 ][ ( i & 0x2 ) >> 1 ][ i & 0x1 ]->DecrementReferenceCount(); } if ( m_pSSAODepthWrite[ ( i & 0x4 ) >> 2 ][ ( i & 0x2 ) >> 1 ][ i & 0x1 ] ) { m_pSSAODepthWrite[ ( i & 0x4 ) >> 2 ][ ( i & 0x2 ) >> 1 ][ i & 0x1 ]->DecrementReferenceCount(); } } if ( m_pGlintBuildMaterial ) { m_pGlintBuildMaterial->DecrementReferenceCount(); m_pGlintBuildMaterial = NULL; } if ( m_pMaterialSolidBackfacePrepass ) { m_pMaterialSolidBackfacePrepass->DecrementReferenceCount(); m_pMaterialSolidBackfacePrepass = NULL; } } static void ReleaseMaterialSystemObjects( int nChangeFlags ) { // g_StudioRender.UncacheGlint(); } static void RestoreMaterialSystemObjects( int nChangeFlags ) { // g_StudioRender.PrecacheGlint(); } //----------------------------------------------------------------------------- // Init, shutdown //----------------------------------------------------------------------------- InitReturnVal_t CStudioRender::Init() { if ( g_pMaterialSystem && g_pMaterialSystemHardwareConfig ) { g_pMaterialSystem->AddReleaseFunc( ReleaseMaterialSystemObjects ); g_pMaterialSystem->AddRestoreFunc( RestoreMaterialSystemObjects ); InitDebugMaterials(); return INIT_OK; } return INIT_FAILED; } void CStudioRender::Shutdown( void ) { UncacheGlint(); ShutdownDebugMaterials(); if ( g_pMaterialSystem ) { g_pMaterialSystem->RemoveReleaseFunc( ReleaseMaterialSystemObjects ); g_pMaterialSystem->RemoveRestoreFunc( RestoreMaterialSystemObjects ); } } //----------------------------------------------------------------------------- // Begin/End frame methods //----------------------------------------------------------------------------- void CStudioRender::BeginFrame( void ) { #ifndef _CERT // Clear the model face count hash table for the coming frame if ( mat_rendered_faces_count.GetBool() || mat_print_top_model_vert_counts.GetBool() ) m_ModelFaceCountHash.RemoveAll(); #endif // !_CERT PrecacheGlint(); } void CStudioRender::EndFrame( void ) { #ifndef _CERT UpdateModelFaceCounts(); #endif // !_CERT CleanupDecals(); } //----------------------------------------------------------------------------- // Sets the lighting render state //----------------------------------------------------------------------------- void CStudioRender::SetLightingRenderState() { CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); // FIXME: What happens when we use the fixed function pipeline but vertex shaders // are active? For the time being this only works because everything that does // vertex lighting does, in fact, have a vertex shader which is used to render it. pRenderContext->SetAmbientLightCube( m_pRC->m_LightBoxColors ); if ( m_pRC->m_Config.bSoftwareLighting ) { pRenderContext->DisableAllLocalLights(); } else { pRenderContext->SetLights( m_pRC->m_NumLocalLights, m_pRC->m_LocalLights ); } } //----------------------------------------------------------------------------- // Shadow state (affects the models as they are rendered) //----------------------------------------------------------------------------- void CStudioRender::AddShadow( IMaterial* pMaterial, void* pProxyData, FlashlightState_t *pFlashlightState, VMatrix *pWorldToTexture, ITexture *pFlashlightDepthTexture ) { int i = m_ShadowState.AddToTail(); ShadowState_t& state = m_ShadowState[i]; state.m_pMaterial = pMaterial; state.m_pProxyData = pProxyData; state.m_pFlashlightState = pFlashlightState; state.m_pWorldToTexture = pWorldToTexture; state.m_pFlashlightDepthTexture = pFlashlightDepthTexture; } void CStudioRender::ClearAllShadows() { m_ShadowState.RemoveAll(); } void CStudioRender::GetFlexStats( ) { #ifdef REPORT_FLEX_STATS static bool s_bLastFlexStats = false; bool bDoStats = r_flexstats.GetInt() != 0; if ( bDoStats ) { if ( !s_bLastFlexStats ) { s_nModelsDrawn = 0; s_nActiveFlexCount = 0; } // Count number of active weights int nActiveFlexCount = 0; for ( int i = 0; i < MAXSTUDIOFLEXDESC; ++i ) { if ( fabs( m_FlexWeights[i] ) >= 0.001f || fabs( m_FlexDelayedWeights[i] ) >= 0.001f ) { ++nActiveFlexCount; } } ++s_nModelsDrawn; s_nActiveFlexCount += nActiveFlexCount; } else { if ( s_bLastFlexStats ) { if ( s_nModelsDrawn ) { Msg( "Average number of flexes/model: %d\n", s_nActiveFlexCount / s_nModelsDrawn ); } else { Msg( "No models rendered to take stats of\n" ); } s_nModelsDrawn = 0; s_nActiveFlexCount = 0; } } s_bLastFlexStats = bDoStats; #endif } ConVar cl_skipslowpath( "cl_skipslowpath", "0", FCVAR_CHEAT | FCVAR_MATERIAL_SYSTEM_THREAD, "Set to 1 to skip any models that don't go through the model fast path" ); //----------------------------------------------------------------------------- // Main model rendering entry point //----------------------------------------------------------------------------- void CStudioRender::DrawModel( const DrawModelInfo_t& info, const StudioRenderContext_t &rc, matrix3x4_t *pBoneToWorld, const FlexWeights_t &flex, int flags ) { if ( cl_skipslowpath.GetBool () ) return; VPROF( "CStudioRender::DrawModel"); if ( ( flags & STUDIORENDER_MODEL_IS_CACHEABLE ) && !g_pMDLCache->IsDataLoaded( VoidPtrToMDLHandle( info.m_pStudioHdr->VirtualModel() ), MDLCACHE_STUDIOHWDATA ) ) { // cacheable models may have had their hw data evicted while they were queued for rendering return; } m_pRC = const_cast< StudioRenderContext_t* >( &rc ); m_pFlexWeights = flex.m_pFlexWeights; m_pFlexDelayedWeights = flex.m_pFlexDelayedWeights; m_pBoneToWorld = pBoneToWorld; CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); // Disable flex if we're told to... bool flexConfig = m_pRC->m_Config.bFlex; if (flags & STUDIORENDER_DRAW_NO_FLEXES) { m_pRC->m_Config.bFlex = false; } // Enable wireframe if we're told to... bool bWireframe = m_pRC->m_Config.bWireframe; if ( flags & STUDIORENDER_DRAW_WIREFRAME ) { m_pRC->m_Config.bWireframe = true; } int boneMask = BONE_USED_BY_VERTEX_AT_LOD( info.m_Lod ); // Preserve the matrices if we're skinning pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); m_VertexCache.StartModel(); m_pStudioHdr = info.m_pStudioHdr; m_pStudioMeshes = info.m_pHardwareData->m_pLODs[info.m_Lod].m_pMeshData; m_pStudioHWData = info.m_pHardwareData; #if PIX_ENABLE char szPIXEventName[128]; sprintf( szPIXEventName, "%s*", m_pStudioHdr->name ); // PIX PIXEVENT( pRenderContext, szPIXEventName ); #endif // Bone to world must be set before calling drawmodel; it uses that here ComputePoseToWorld( m_PoseToWorld, m_pStudioHdr, boneMask, m_pRC->m_ViewOrigin, pBoneToWorld ); bool bOldFlashlightState = false; if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() && IsGameConsole() ) { bOldFlashlightState = pRenderContext->GetFlashlightMode(); pRenderContext->SetFlashlightMode( m_ShadowState.Count() > 0 ); } if ( ( flags & STUDIORENDER_NO_PRIMARY_DRAW ) == 0 ) // if this flag is set, then we are drawing multiple shadows in separate calls ( probably for capture ) { R_StudioRenderModel( pRenderContext, info.m_Skin, info.m_Body, info.m_HitboxSet, info.m_pClientEntity, info.m_pHardwareData->m_pLODs[info.m_Lod].ppMaterials, info.m_pHardwareData->m_pLODs[info.m_Lod].pMaterialFlags, flags, boneMask, info.m_Lod, info.m_pColorMeshes); } if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() && IsGameConsole() ) { pRenderContext->SetFlashlightMode( bOldFlashlightState ); } // Draw all the decals on this model // If the model is not in memory, this code may not function correctly // This code assumes the model has been rendered! // So skip if the model hasn't been rendered // Also, skip if we're rendering to the shadow depth map if ( ( m_pStudioMeshes != 0 ) && !( flags & ( STUDIORENDER_SHADOWDEPTHTEXTURE | STUDIORENDER_SSAODEPTHTEXTURE ) ) ) { // Draw shadows if ( !( flags & STUDIORENDER_DRAW_NO_SHADOWS ) ) { DrawShadows( info, flags, boneMask ); } if ( ( ( flags & STUDIORENDER_DRAW_GROUP_MASK ) != STUDIORENDER_DRAW_TRANSLUCENT_ONLY ) && !( flags & STUDIORENDER_SKIP_DECALS ) ) { DrawDecal( info, info.m_Lod, info.m_Body ); } if( (flags & STUDIORENDER_DRAW_GROUP_MASK) != STUDIORENDER_DRAW_TRANSLUCENT_ONLY && !( flags & STUDIORENDER_DRAW_NO_SHADOWS ) && !( flags & STUDIORENDER_SKIP_DECALS ) ) { DrawFlashlightDecals( info, info.m_Lod ); } } // Restore the matrices if we're skinning pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); // Restore the configs m_pRC->m_Config.bFlex = flexConfig; m_pRC->m_Config.bWireframe = bWireframe; #ifdef REPORT_FLEX_STATS GetFlexStats(); #endif pRenderContext->SetNumBoneWeights( 0 ); m_pRC = NULL; m_pBoneToWorld = NULL; m_pFlexWeights = NULL; m_pFlexDelayedWeights = NULL; m_pStudioHdr = NULL; m_pStudioMeshes = NULL; m_pStudioHWData = NULL; } void CStudioRender::DrawModelStaticProp( const DrawModelInfo_t& info, const StudioRenderContext_t &rc, const matrix3x4_t& rootToWorld, int flags ) { if ( cl_skipslowpath.GetBool () ) return; VPROF( "CStudioRender::DrawModelStaticProp"); m_pRC = const_cast( &rc ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); memcpy( &m_StaticPropRootToWorld, &rootToWorld, sizeof(matrix3x4_t) ); memcpy( &m_PoseToWorld[0], &rootToWorld, sizeof(matrix3x4_t) ); m_pBoneToWorld = &m_StaticPropRootToWorld; bool flexConfig = m_pRC->m_Config.bFlex; m_pRC->m_Config.bFlex = false; bool bWireframe = m_pRC->m_Config.bWireframe; if ( flags & STUDIORENDER_DRAW_WIREFRAME ) { m_pRC->m_Config.bWireframe = true; } int lod = info.m_Lod; m_pStudioHdr = info.m_pStudioHdr; m_pStudioMeshes = info.m_pHardwareData->m_pLODs[lod].m_pMeshData; m_pStudioHWData = info.m_pHardwareData; R_StudioRenderModel( pRenderContext, info.m_Skin, info.m_Body, info.m_HitboxSet, info.m_pClientEntity, info.m_pHardwareData->m_pLODs[lod].ppMaterials, info.m_pHardwareData->m_pLODs[lod].pMaterialFlags, flags, BONE_USED_BY_ANYTHING, lod, info.m_pColorMeshes); // If we're not shadow depth mapping if ( ( flags & ( STUDIORENDER_SHADOWDEPTHTEXTURE | STUDIORENDER_SSAODEPTHTEXTURE ) ) == 0 ) { // Draw shadows if ( !( flags & STUDIORENDER_DRAW_NO_SHADOWS ) ) { DrawShadows( info, flags, BONE_USED_BY_ANYTHING ); } // FIXME: Should this occur in a separate call? // Draw all the decals on this model if ( ( ( flags & STUDIORENDER_DRAW_GROUP_MASK ) != STUDIORENDER_DRAW_TRANSLUCENT_ONLY ) && !( flags & STUDIORENDER_SKIP_DECALS ) ) { DrawDecal( info, lod, info.m_Body ); } if( (flags & STUDIORENDER_DRAW_GROUP_MASK) != STUDIORENDER_DRAW_TRANSLUCENT_ONLY && !( flags & STUDIORENDER_DRAW_NO_SHADOWS ) && !( flags & STUDIORENDER_SKIP_DECALS ) ) { DrawFlashlightDecals( info, lod ); } } // Restore the configs m_pRC->m_Config.bFlex = flexConfig; m_pRC->m_Config.bWireframe = bWireframe; pRenderContext->SetNumBoneWeights( 0 ); m_pBoneToWorld = NULL; m_pRC = NULL; m_pStudioHdr = NULL; m_pStudioMeshes = NULL; m_pStudioHWData = NULL; } //----------------------------------------------------------------------------- // Used to render instances //----------------------------------------------------------------------------- struct BaseMeshRenderData_t { studiomeshgroup_t *m_pGroup; mstudiomesh_t *m_pMesh; IMaterial *m_pMaterial; }; struct ShadowMeshRenderData_t : public BaseMeshRenderData_t { StudioShadowArrayInstanceData_t *m_pInstance; IMaterial *m_pSrcMaterial; VertexCompressionType_t m_nCompressionType; int m_nMeshBoneCount; bool m_bIsAlphaTested; bool m_bUsesTreeSway; }; struct MeshRenderData_t : public BaseMeshRenderData_t { StudioArrayInstanceData_t *m_pInstance; }; struct MeshRenderData2_t : public BaseMeshRenderData_t { StudioArrayInstanceData_t *m_pInstance; int16 m_nCompressionType; // VertexCompressionType_t smooshed to 16 bits int16 m_nMeshBoneCount; }; //----------------------------------------------------------------------------- // Counts the number of meshes to draw //----------------------------------------------------------------------------- int CStudioRender::CountMeshesToDraw( const StudioModelArrayInfo_t &drawInfo, int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride, int nTimesRendered ) { VPROF( "CStudioRender::CountMeshesToDraw" ); #ifndef _CERT bool bCountRenderedFaces = mat_rendered_faces_count.GetBool() || mat_print_top_model_vert_counts.GetBool(); #endif // !_CERT StudioArrayInstanceData_t *pCurInstance = pInstanceData; int nTotalMeshCount = 0; for ( int i = 0; i < nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = drawInfo.m_pHardwareData->m_pLODs[nLod].m_pMeshData; #ifndef _CERT // Each model counts how many rendered faces it accounts for each frame: if ( bCountRenderedFaces ) drawInfo.m_pHardwareData->UpdateFacesRenderedCount( drawInfo.m_pStudioHdr, m_ModelFaceCountHash, nLod, nTimesRendered ); #endif // !_CERT int nBody = pCurInstance->m_nBody; for ( int body = 0; body < drawInfo.m_pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = drawInfo.m_pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; nTotalMeshCount += pMeshData->m_NumGroup; } } } return nTotalMeshCount; } //----------------------------------------------------------------------------- // Counts the number of meshes to draw //----------------------------------------------------------------------------- int CStudioRender::CountMeshesToDraw( const StudioModelArrayInfo2_t &drawInfo, int nCount, StudioArrayData_t *pArrayData, int nInstanceStride, int nTimesRendered ) { VPROF( "CStudioRender::CountMeshesToDraw" ); #ifndef _CERT bool bCountRenderedFaces = mat_rendered_faces_count.GetBool() || mat_print_top_model_vert_counts.GetBool(); #endif // !_CERT int nTotalMeshCount = 0; for ( int i = 0; i < nCount; ++i ) { StudioArrayData_t &arrayData = pArrayData[i]; StudioArrayInstanceData_t *pCurInstance = (StudioArrayInstanceData_t*)( arrayData.m_pInstanceData ); for ( int j = 0; j < arrayData.m_nCount; ++j, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = arrayData.m_pHardwareData->m_pLODs[nLod].m_pMeshData; #ifndef _CERT // Each model counts how many rendered faces it accounts for each frame: if ( bCountRenderedFaces ) arrayData.m_pHardwareData->UpdateFacesRenderedCount( arrayData.m_pStudioHdr, m_ModelFaceCountHash, nLod, nTimesRendered ); #endif // !_CERT int nBody = pCurInstance->m_nBody; for ( int body = 0; body < arrayData.m_pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = arrayData.m_pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; nTotalMeshCount += pMeshData->m_NumGroup; } } } } return nTotalMeshCount; } //----------------------------------------------------------------------------- // Sort models function //----------------------------------------------------------------------------- inline bool CStudioRender::SortLessFunc( const MeshRenderData_t &left, const MeshRenderData_t &right ) { if ( left.m_pMaterial != right.m_pMaterial ) return left.m_pMaterial > right.m_pMaterial; if ( left.m_pGroup->m_pMesh != right.m_pGroup->m_pMesh ) return left.m_pGroup->m_pMesh > right.m_pGroup->m_pMesh; if ( left.m_pInstance->m_pEnvCubemapTexture != right.m_pInstance->m_pEnvCubemapTexture ) return left.m_pInstance->m_pEnvCubemapTexture > right.m_pInstance->m_pEnvCubemapTexture; bool bLeftHasLighting = ( left.m_pInstance->m_pLightingState != NULL ); bool bRightHasLighting = ( right.m_pInstance->m_pLightingState != NULL ); if ( bLeftHasLighting != bRightHasLighting ) return bLeftHasLighting; if ( !bLeftHasLighting ) return false; return left.m_pInstance->m_pLightingState->m_nLocalLightCount > right.m_pInstance->m_pLightingState->m_nLocalLightCount; } //----------------------------------------------------------------------------- // Sort models function //----------------------------------------------------------------------------- inline bool CStudioRender::SortLessFunc2( const MeshRenderData2_t &left, const MeshRenderData2_t &right ) { if ( left.m_pMaterial != right.m_pMaterial ) return left.m_pMaterial > right.m_pMaterial; if ( left.m_nCompressionType != right.m_nCompressionType ) return left.m_nCompressionType > right.m_nCompressionType; if ( left.m_nMeshBoneCount != right.m_nMeshBoneCount ) return left.m_nMeshBoneCount > right.m_nMeshBoneCount; bool bLeftHasColorMesh = ( left.m_pInstance->m_pColorMeshInfo != NULL ); bool bRightHasColorMesh = ( right.m_pInstance->m_pColorMeshInfo != NULL ); if ( bLeftHasColorMesh != bRightHasColorMesh ) return bLeftHasColorMesh < bRightHasColorMesh; if ( left.m_pInstance->m_pEnvCubemapTexture != right.m_pInstance->m_pEnvCubemapTexture ) return left.m_pInstance->m_pEnvCubemapTexture > right.m_pInstance->m_pEnvCubemapTexture; if ( left.m_pGroup->m_pMesh != right.m_pGroup->m_pMesh ) return left.m_pGroup->m_pMesh > right.m_pGroup->m_pMesh; bool bLeftHasLighting = ( left.m_pInstance->m_pLightingState != NULL ); bool bRightHasLighting = ( right.m_pInstance->m_pLightingState != NULL ); if ( bLeftHasLighting != bRightHasLighting ) return bLeftHasLighting; if ( !bLeftHasLighting ) return false; return left.m_pInstance->m_pLightingState->m_nLocalLightCount > right.m_pInstance->m_pLightingState->m_nLocalLightCount; } //----------------------------------------------------------------------------- // Builds the list of things to render in what order //----------------------------------------------------------------------------- int CStudioRender::BuildSortedRenderList( MeshRenderData_t *pRenderData, int *pTotalStripCount, const StudioModelArrayInfo_t &drawInfo, int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride, int nFlags ) { SNPROF( "CStudioRender::BuildSortedRenderList" ); studiohdr_t *pStudioHdr = drawInfo.m_pStudioHdr; short *pSkinRefBase = pStudioHdr->pSkinref( 0 ); bool bSkipTranslucent = ( nFlags & STUDIORENDER_DRAW_OPAQUE_ONLY ) != 0; bool bSkipOpaque = ( nFlags & STUDIORENDER_DRAW_TRANSLUCENT_ONLY ) != 0; bool bSelectiveOverride = ( m_pRC->m_nForcedMaterialType == OVERRIDE_SELECTIVE ); *pTotalStripCount = 0; StudioArrayInstanceData_t *pCurInstance = pInstanceData; int nRenderDataCount = 0; for ( int i = 0; i < nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = drawInfo.m_pHardwareData->m_pLODs[nLod].m_pMeshData; IMaterial **ppMaterials = drawInfo.m_pHardwareData->m_pLODs[nLod].ppMaterials; short *pSkinRef = pSkinRefBase; int skin = pCurInstance->m_nSkin; if ( skin > 0 && skin < pStudioHdr->numskinfamilies ) { pSkinRef += ( skin * pStudioHdr->numskinref ); } int nBody = pCurInstance->m_nBody; for ( int body = 0; body < pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[ pMesh->meshid ]; IMaterial *pMaterial = ppMaterials[ pSkinRef[ pMesh->material ] ]; bool bIsTranslucent = pMaterial->IsTranslucentUnderModulation( pCurInstance->m_DiffuseModulation.w ); if ( bSkipTranslucent && bIsTranslucent ) continue; else if ( bSkipOpaque && !bIsTranslucent ) continue; Assert( pMeshData ); // Assert( pMeshData->m_NumGroup ); // can't Assert on m_NumGroup since it can be zero on lods. for ( int g = 0; g < pMeshData->m_NumGroup; ++g ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[g]; MeshRenderData_t &data = pRenderData[nRenderDataCount++]; int nOverrideIndex = GetForcedMaterialOverrideIndex( pSkinRef[ pMesh->material ] ); if ( bSelectiveOverride && nOverrideIndex != -1 ) { data.m_pMaterial = m_pRC->m_pForcedMaterial[ nOverrideIndex ]; } else { data.m_pMaterial = pMaterial; } data.m_pGroup = pGroup; data.m_pInstance = pCurInstance; data.m_pMesh = pMesh; *pTotalStripCount += pGroup->m_NumStrips; } } } } std::make_heap( pRenderData, pRenderData + nRenderDataCount, SortLessFunc ); std::sort_heap( pRenderData, pRenderData + nRenderDataCount, SortLessFunc ); return nRenderDataCount; } //----------------------------------------------------------------------------- // Builds the list of things to render in what order //----------------------------------------------------------------------------- int CStudioRender::BuildSortedRenderList( MeshRenderData2_t *pRenderData, int *pTotalStripCount, const StudioModelArrayInfo2_t &drawInfo, int nCount, StudioArrayData_t *pArrayData, int nInstanceStride, int nFlags ) { SNPROF( "CStudioRender::BuildSortedRenderList" ); bool bSkipTranslucent = ( nFlags & STUDIORENDER_DRAW_OPAQUE_ONLY ) != 0; bool bSkipOpaque = ( nFlags & STUDIORENDER_DRAW_TRANSLUCENT_ONLY ) != 0; bool bSelectiveOverride = ( m_pRC->m_nForcedMaterialType == OVERRIDE_SELECTIVE ); *pTotalStripCount = 0; int nRenderDataCount = 0; for ( int a = 0; a < nCount; ++a ) { StudioArrayData_t &arrayData = pArrayData[a]; studiohdr_t *pStudioHdr = arrayData.m_pStudioHdr; short *pSkinRefBase = pStudioHdr->pSkinref( 0 ); StudioArrayInstanceData_t *pCurInstance = (StudioArrayInstanceData_t*)( arrayData.m_pInstanceData ); for ( int i = 0; i < arrayData.m_nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = arrayData.m_pHardwareData->m_pLODs[nLod].m_pMeshData; IMaterial **ppMaterials = arrayData.m_pHardwareData->m_pLODs[nLod].ppMaterials; short *pSkinRef = pSkinRefBase; int skin = pCurInstance->m_nSkin; if ( skin > 0 && skin < pStudioHdr->numskinfamilies ) { pSkinRef += ( skin * pStudioHdr->numskinref ); } int nBody = pCurInstance->m_nBody; for ( int body = 0; body < pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[ pMesh->meshid ]; IMaterial *pMaterial = ppMaterials[ pSkinRef[ pMesh->material ] ]; bool bIsTranslucent = pMaterial->IsTranslucentUnderModulation( pCurInstance->m_DiffuseModulation.w ); if ( bSkipTranslucent && bIsTranslucent ) continue; else if ( bSkipOpaque && !bIsTranslucent ) continue; Assert( pMeshData ); // Assert( pMeshData->m_NumGroup ); // can't Assert on m_NumGroup since it can be zero on lods. for ( int g = 0; g < pMeshData->m_NumGroup; ++g ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[g]; MeshRenderData2_t &data = pRenderData[nRenderDataCount++]; int nOverrideIndex = GetForcedMaterialOverrideIndex( pSkinRef[ pMesh->material ] ); if ( bSelectiveOverride && nOverrideIndex != -1 ) { data.m_pMaterial = m_pRC->m_pForcedMaterial[ nOverrideIndex ]; } else { data.m_pMaterial = pMaterial; } data.m_pGroup = pGroup; data.m_pInstance = pCurInstance; data.m_pMesh = pMesh; data.m_nCompressionType = CompressionType( pGroup->m_pMesh->GetVertexFormat() ); data.m_nMeshBoneCount = NumBoneWeights( pGroup->m_pMesh->GetVertexFormat() ); data.m_pInstance->m_bColorMeshHasIndirectLightingOnly = ( pStudioHdr->flags & STUDIOHDR_BAKED_VERTEX_LIGHTING_IS_INDIRECT_ONLY ) ? true : false; *pTotalStripCount += pGroup->m_NumStrips; } } } } } std::make_heap( pRenderData, pRenderData + nRenderDataCount, SortLessFunc2 ); std::sort_heap( pRenderData, pRenderData + nRenderDataCount, SortLessFunc2 ); return nRenderDataCount; } //----------------------------------------------------------------------------- // BuildForcedMaterialRenderList //----------------------------------------------------------------------------- void CStudioRender::BuildForcedMaterialRenderList( MeshRenderData_t *pRenderData, int *pTotalStripCount, const StudioModelArrayInfo_t &drawInfo, const StudioRenderContext_t &rc, int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride ) { VPROF( "CStudioRender::BuildForcedMaterialRenderList" ); studiohdr_t *pStudioHdr = drawInfo.m_pStudioHdr; *pTotalStripCount = 0; StudioArrayInstanceData_t *pCurInstance = pInstanceData; int nRenderDataCount = 0; for ( int i = 0; i < nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = drawInfo.m_pHardwareData->m_pLODs[nLod].m_pMeshData; int nBody = pCurInstance->m_nBody; for ( int body = 0; body < pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; Assert( pMeshData && pMeshData->m_NumGroup ); for ( int g = 0; g < pMeshData->m_NumGroup; ++g ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[g]; MeshRenderData_t &data = pRenderData[nRenderDataCount++]; data.m_pMaterial = rc.m_pForcedMaterial[ 0 ]; data.m_pGroup = pGroup; data.m_pInstance = pCurInstance; data.m_pMesh = pMesh; *pTotalStripCount += pGroup->m_NumStrips; } } } } } //----------------------------------------------------------------------------- // BuildForcedMaterialRenderList //----------------------------------------------------------------------------- void CStudioRender::BuildForcedMaterialRenderList( MeshRenderData2_t *pRenderData, int *pTotalStripCount, const StudioModelArrayInfo2_t &drawInfo, const StudioRenderContext_t &rc, int nCount, StudioArrayData_t *pArrayData, int nInstanceStride ) { VPROF( "CStudioRender::BuildForcedMaterialRenderList" ); *pTotalStripCount = 0; int nRenderDataCount = 0; for ( int a = 0; a < nCount; ++a ) { StudioArrayData_t &arrayData = pArrayData[a]; studiohdr_t *pStudioHdr = arrayData.m_pStudioHdr; StudioArrayInstanceData_t *pCurInstance = (StudioArrayInstanceData_t*)( arrayData.m_pInstanceData ); for ( int i = 0; i < arrayData.m_nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = arrayData.m_pHardwareData->m_pLODs[nLod].m_pMeshData; int nBody = pCurInstance->m_nBody; for ( int body = 0; body < pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; Assert( pMeshData && pMeshData->m_NumGroup ); for ( int g = 0; g < pMeshData->m_NumGroup; ++g ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[g]; MeshRenderData2_t &data = pRenderData[nRenderDataCount++]; data.m_pMaterial = rc.m_pForcedMaterial[ 0 ]; data.m_pGroup = pGroup; data.m_pInstance = pCurInstance; data.m_pMesh = pMesh; data.m_nCompressionType = CompressionType( pGroup->m_pMesh->GetVertexFormat() ); data.m_nMeshBoneCount = NumBoneWeights( pGroup->m_pMesh->GetVertexFormat() ); *pTotalStripCount += pGroup->m_NumStrips; } } } } } } //----------------------------------------------------------------------------- // Restores meshes, if necessary //----------------------------------------------------------------------------- void CStudioRender::RestoreMeshes( int nCount, BaseMeshRenderData_t *pRenderData, int nStride ) { #ifdef IS_WINDOWS_PC // FIXME: Can we build a list of unique studiomeshdata_ts? for ( int i = 0; i < nCount; ++i, pRenderData = (BaseMeshRenderData_t*)( (unsigned char*)pRenderData + nStride ) ) { BaseMeshRenderData_t &data = *pRenderData; studiomeshgroup_t *pGroup = data.m_pGroup; // Older models are merely flexed while new ones are also delta flexed Assert( !( pGroup->m_Flags & MESHGROUP_IS_DELTA_FLEXED ) ); // Needed when we switch back and forth between hardware + software lighting if ( !pGroup->m_MeshNeedsRestore ) continue; IMesh *pMesh = pGroup->m_pMesh; VertexCompressionType_t compressionType = CompressionType( pMesh->GetVertexFormat() ); switch ( compressionType ) { case VERTEX_COMPRESSION_ON: R_StudioRestoreMesh( data.m_pMesh, pGroup ); break; case VERTEX_COMPRESSION_NONE: default: R_StudioRestoreMesh( data.m_pMesh, pGroup ); break; } pGroup->m_MeshNeedsRestore = false; } #endif } //----------------------------------------------------------------------------- // Allocate temporary arrays either on the stack, or from the heap. // Prevents using all the stack when *lots* of objects are rendered to CSM's. //----------------------------------------------------------------------------- #if defined( CSTRIKE15 ) // 7ls && !defined( _GAMECONSOLE ) #define STUDIORENDER_TEMP_DATA_MALLOC( typeName, p, n ) const int nTempDataSize##p = (n); void *pvFree##p = NULL; typeName *p = (typeName *) ( ( nTempDataSize##p < 64*1024 ) ? stackalloc( nTempDataSize##p ) : ( pvFree##p = malloc( nTempDataSize##p ) ) ); #define STUDIORENDER_TEMP_DATA_FREE( p ) free( pvFree##p ) #else #define STUDIORENDER_TEMP_DATA_MALLOC( typeName, p, n ) typeName *p = (typeName *) stackalloc(n); #define STUDIORENDER_TEMP_DATA_FREE( p ) #endif //----------------------------------------------------------------------------- // Draws meshes //----------------------------------------------------------------------------- void CStudioRender::DrawMeshRenderData( IMatRenderContext *pRenderContext, const StudioModelArrayInfo_t &drawInfo, int nCount, MeshRenderData_t *pRenderData, int nTotalStripCount, int nFlashlightMask ) { SNPROF( "CStudioRender::DrawMeshRenderData" ); int nInstanceCount = 0; STUDIORENDER_TEMP_DATA_MALLOC( MeshInstanceData_t, pInstance, nTotalStripCount * sizeof(MeshInstanceData_t) ); IMaterial *pLastMaterial = NULL; IMesh *pLastMesh = NULL; #ifdef _GAMECONSOLE bool bLastUsingFlashlight = false; bool bSavedFlashlightEnable = pRenderContext->GetFlashlightMode(); #endif // _GAMECONSOLE int nMaxBoneCount = 0; int nMaxLightCount = 0; bool bIsSkinned = drawInfo.m_pStudioHdr->numbones > 1; #if PIX_ENABLE char szPIXEventName[128]; sprintf( szPIXEventName, "%s*", drawInfo.m_pStudioHdr->name ); // PIX PIXEVENT( pRenderContext, szPIXEventName ); #endif for ( int i = 0; i < nCount; ++i ) { MeshRenderData_t &data = pRenderData[i]; StudioArrayInstanceData_t *pCurrInstance = data.m_pInstance; // Skip models not affected by this flashlight if ( nFlashlightMask && ( ( nFlashlightMask & pCurrInstance->m_nFlashlightUsage ) == 0 ) ) continue; if ( ( pLastMaterial != data.m_pMaterial ) || ( pLastMesh != data.m_pGroup->m_pMesh ) #ifdef _GAMECONSOLE || ( bLastUsingFlashlight != ( pCurrInstance->m_nFlashlightUsage != 0 ) ) #endif // _GAMECONSOLE ) { if ( nInstanceCount > 0 ) { #ifdef _GAMECONSOLE if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() ) { pRenderContext->SetFlashlightMode( bLastUsingFlashlight ); } #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( bIsSkinned ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } nInstanceCount = 0; nMaxBoneCount = 0; nMaxLightCount = 0; pLastMesh = data.m_pGroup->m_pMesh; pLastMaterial = data.m_pMaterial; #ifdef _GAMECONSOLE bLastUsingFlashlight = pCurrInstance->m_nFlashlightUsage != 0; #endif // _GAMECONSOLE } studiomeshgroup_t* pGroup = data.m_pGroup; for ( int j = 0; j < pGroup->m_NumStrips; ++j ) { OptimizedModel::StripHeader_t* pStrip = &pGroup->m_pStripData[j]; Assert( nInstanceCount < nTotalStripCount ); MeshInstanceData_t &instance = pInstance[nInstanceCount++]; instance.m_pEnvCubemap = pCurrInstance->m_pEnvCubemapTexture; instance.m_pPoseToWorld = pCurrInstance->m_pPoseToWorld; instance.m_pLightingState = pCurrInstance->m_pLightingState; if ( pCurrInstance->m_pColorMeshInfo ) { const ColorMeshInfo_t &colorMesh = pCurrInstance->m_pColorMeshInfo[ pGroup->m_ColorMeshID ]; instance.m_pColorBuffer = colorMesh.m_pMesh; instance.m_nColorVertexOffsetInBytes = colorMesh.m_nVertOffsetInBytes; } else { instance.m_pColorBuffer = NULL; instance.m_nColorVertexOffsetInBytes = 0; } instance.m_nBoneCount = pStrip->numBoneStateChanges; instance.m_pBoneRemap = ( instance.m_nBoneCount > 0 ) ? (MeshBoneRemap_t*)( pStrip->pBoneStateChange(0) ) : NULL; instance.m_nIndexOffset = pStrip->indexOffset; instance.m_nIndexCount = pStrip->numIndices; instance.m_nPrimType = MATERIAL_TRIANGLES; instance.m_pVertexBuffer = data.m_pGroup->m_pMesh; instance.m_pIndexBuffer = data.m_pGroup->m_pMesh; instance.m_nVertexOffsetInBytes = 0; instance.m_pStencilState = pCurrInstance->m_pStencilState; instance.m_DiffuseModulation = pCurrInstance->m_DiffuseModulation; instance.m_nLightmapPageId = MATERIAL_SYSTEM_LIGHTMAP_PAGE_INVALID; instance.m_bColorBufferHasIndirectLightingOnly = ( drawInfo.m_pStudioHdr->flags & STUDIOHDR_BAKED_VERTEX_LIGHTING_IS_INDIRECT_ONLY ) ? true : false; nMaxBoneCount = MAX( nMaxBoneCount, pStrip->numBones ); } } if ( nInstanceCount > 0 ) { #ifdef _GAMECONSOLE if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() ) { pRenderContext->SetFlashlightMode( bLastUsingFlashlight ); } #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( bIsSkinned ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } #ifdef _GAMECONSOLE pRenderContext->SetFlashlightMode( bSavedFlashlightEnable ); #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pInstance ); } //----------------------------------------------------------------------------- // Draws meshes //----------------------------------------------------------------------------- void CStudioRender::DrawMeshRenderData( IMatRenderContext *pRenderContext, const StudioModelArrayInfo2_t &drawInfo, int nCount, MeshRenderData2_t *pRenderData, int nTotalStripCount, int nFlashlightMask ) { SNPROF( "CStudioRender::DrawMeshRenderData" ); int nInstanceCount = 0; int nMaxBatchSize = IsGameConsole() ? CONSOLE_MAX_MODEL_FAST_PATH_BATCH_SIZE : nTotalStripCount; STUDIORENDER_TEMP_DATA_MALLOC( MeshInstanceData_t, pInstance, nMaxBatchSize * sizeof(MeshInstanceData_t) ); IMaterial *pLastMaterial = NULL; int nMaxBoneCount = 0; int nMaxLightCount = 0; int nLastMeshBoneCount = 0; VertexCompressionType_t nLastCompressionType = VERTEX_COMPRESSION_INVALID; bool bLastMeshUsedColorMesh = false; #ifdef _GAMECONSOLE bool bLastUsingFlashlight = false; bool bSavedFlashlightEnable = pRenderContext->GetFlashlightMode(); #endif // _GAMECONSOLE int nStartingStripIndex = 0; // used to interrupt batching within a group if the number of strips exceeds the max batch size for ( int i = 0; i < nCount; ++i ) { MeshRenderData2_t &data = pRenderData[i]; StudioArrayInstanceData_t *pCurrInstance = data.m_pInstance; // Skip models not affected by this flashlight if ( nFlashlightMask && ( ( nFlashlightMask & pCurrInstance->m_nFlashlightUsage ) == 0 ) ) continue; bool bUsingColorMesh = ( pCurrInstance->m_pColorMeshInfo != NULL ); if ( ( pLastMaterial != data.m_pMaterial ) || // shadow material is different ( nLastCompressionType != ( int )data.m_nCompressionType ) || // compression type is different ( nLastMeshBoneCount != data.m_nMeshBoneCount ) || // # of bones in the mesh data is different ( bLastMeshUsedColorMesh != bUsingColorMesh ) || // Lighting type is different ( IsGameConsole() && ( nInstanceCount >= nMaxBatchSize ) ) // max # of batches to render at once due to stack limitations on console #ifdef _GAMECONSOLE || ( bLastUsingFlashlight != ( pCurrInstance->m_nFlashlightUsage != 0 ) ) #endif // _GAMECONSOLE ) { if ( nInstanceCount > 0 ) { #ifdef _GAMECONSOLE if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() ) { pRenderContext->SetFlashlightMode( bLastUsingFlashlight ); } #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( nLastMeshBoneCount > 0 ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } nInstanceCount = 0; nMaxBoneCount = 0; nMaxLightCount = 0; nLastCompressionType = ( VertexCompressionType_t )( int )data.m_nCompressionType; pLastMaterial = data.m_pMaterial; nLastMeshBoneCount = data.m_nMeshBoneCount; bLastMeshUsedColorMesh = bUsingColorMesh; #ifdef _GAMECONSOLE bLastUsingFlashlight = pCurrInstance->m_nFlashlightUsage != 0; #endif // _GAMECONSOLE } studiomeshgroup_t* pGroup = data.m_pGroup; int j; for ( j = nStartingStripIndex; j < pGroup->m_NumStrips; ++j ) { OptimizedModel::StripHeader_t* pStrip = &pGroup->m_pStripData[j]; MeshInstanceData_t &instance = pInstance[nInstanceCount++]; instance.m_pEnvCubemap = pCurrInstance->m_pEnvCubemapTexture; instance.m_pPoseToWorld = pCurrInstance->m_pPoseToWorld; instance.m_pLightingState = pCurrInstance->m_pLightingState; if ( pCurrInstance->m_pColorMeshInfo ) { const ColorMeshInfo_t &colorMesh = pCurrInstance->m_pColorMeshInfo[ pGroup->m_ColorMeshID ]; instance.m_pColorBuffer = colorMesh.m_pMesh; instance.m_nColorVertexOffsetInBytes = colorMesh.m_nVertOffsetInBytes; } else { instance.m_pColorBuffer = NULL; instance.m_nColorVertexOffsetInBytes = 0; } instance.m_nBoneCount = pStrip->numBoneStateChanges; instance.m_pBoneRemap = ( instance.m_nBoneCount > 0 ) ? (MeshBoneRemap_t*)( pStrip->pBoneStateChange(0) ) : NULL; instance.m_nIndexOffset = pStrip->indexOffset; instance.m_nIndexCount = pStrip->numIndices; instance.m_nPrimType = MATERIAL_TRIANGLES; instance.m_pVertexBuffer = data.m_pGroup->m_pMesh; instance.m_pIndexBuffer = data.m_pGroup->m_pMesh; instance.m_nVertexOffsetInBytes = 0; instance.m_pStencilState = pCurrInstance->m_pStencilState; instance.m_DiffuseModulation = pCurrInstance->m_DiffuseModulation; instance.m_nLightmapPageId = MATERIAL_SYSTEM_LIGHTMAP_PAGE_INVALID; instance.m_bColorBufferHasIndirectLightingOnly = pCurrInstance->m_bColorMeshHasIndirectLightingOnly; nMaxBoneCount = MAX( nMaxBoneCount, pStrip->numBones ); if ( IsGameConsole() && nInstanceCount >= nMaxBatchSize ) { break; } } if ( IsGameConsole() && j < pGroup->m_NumStrips ) { // We're going to have to process this pRenderData[] entry again, // but start iterating from a higher strip index next time. nStartingStripIndex = j + 1; -- i; } else { nStartingStripIndex = 0; } } if ( nInstanceCount > 0 ) { #ifdef _GAMECONSOLE if ( pRenderContext->IsCullingEnabledForSinglePassFlashlight() ) { pRenderContext->SetFlashlightMode( bLastUsingFlashlight ); } #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( nLastMeshBoneCount > 0 ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } #ifdef _GAMECONSOLE pRenderContext->SetFlashlightMode( bSavedFlashlightEnable ); #endif // _GAMECONSOLE pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pInstance ); } //----------------------------------------------------------------------------- // Draws meshes illuminated by the flashlight //----------------------------------------------------------------------------- extern ConVar r_flashlightscissor; void CStudioRender::DrawModelArrayFlashlight( IMatRenderContext *pRenderContext, const StudioModelArrayInfo_t &drawInfo, int nCount, MeshRenderData_t *pRenderData, int nTotalStripCount ) { int nFlashlightCount = drawInfo.m_nFlashlightCount; if ( !nFlashlightCount ) return; pRenderContext->SetFlashlightMode( true ); bool bDoScissor = r_flashlightscissor.GetBool() && ( pRenderContext->GetRenderTarget() == NULL ); int i; for ( i = 0; i < nFlashlightCount; ++i ) { FlashlightInstance_t &flashlight = drawInfo.m_pFlashlights[i]; const FlashlightState_t& state = flashlight.m_FlashlightState; if ( bDoScissor ) { if ( state.DoScissor() ) { pRenderContext->PushScissorRect( state.GetLeft(), state.GetTop(), state.GetRight(), state.GetBottom() ); } } if ( !flashlight.m_pDebugMaterial ) { pRenderContext->SetFlashlightStateEx( state, flashlight.m_WorldToTexture, flashlight.m_pFlashlightDepthTexture ); DrawMeshRenderData( pRenderContext, drawInfo, nCount, pRenderData, nTotalStripCount, ( 1 << i ) ); } else { // Debugging mode where we render wireframe on models hit by the flashlight // FIXME: Could make this faster, but why bother? It's a debugging mode. int nSizeInBytes = nCount * sizeof(MeshRenderData_t); STUDIORENDER_TEMP_DATA_MALLOC( MeshRenderData_t, pTempData, nSizeInBytes ); memcpy( pTempData, pRenderData, nSizeInBytes ); for ( int j = 0; j < nCount; ++j ) { pTempData[j].m_pMaterial = flashlight.m_pDebugMaterial; } pRenderContext->SetFlashlightMode( false ); DrawMeshRenderData( pRenderContext, drawInfo, nCount, pTempData, nTotalStripCount, ( 1 << i ) ); pRenderContext->SetFlashlightMode( true ); STUDIORENDER_TEMP_DATA_FREE( pTempData ); } if ( bDoScissor && state.DoScissor() ) { pRenderContext->PopScissorRect(); } } pRenderContext->SetFlashlightMode( false ); } //----------------------------------------------------------------------------- // Draws meshes illuminated by the flashlight //----------------------------------------------------------------------------- void CStudioRender::DrawModelArrayFlashlight( IMatRenderContext *pRenderContext, const StudioModelArrayInfo2_t &drawInfo, int nCount, MeshRenderData2_t *pRenderData, int nTotalStripCount ) { int nFlashlightCount = drawInfo.m_nFlashlightCount; if ( !nFlashlightCount ) return; pRenderContext->SetFlashlightMode( true ); bool bDoScissor = r_flashlightscissor.GetBool() && ( pRenderContext->GetRenderTarget() == NULL ); int i; for ( i = 0; i < nFlashlightCount; ++i ) { FlashlightInstance_t &flashlight = drawInfo.m_pFlashlights[i]; const FlashlightState_t& state = flashlight.m_FlashlightState; if ( bDoScissor ) { if ( state.DoScissor() ) { pRenderContext->PushScissorRect( state.GetLeft(), state.GetTop(), state.GetRight(), state.GetBottom() ); } } if ( !flashlight.m_pDebugMaterial ) { pRenderContext->SetFlashlightStateEx( state, flashlight.m_WorldToTexture, flashlight.m_pFlashlightDepthTexture ); DrawMeshRenderData( pRenderContext, drawInfo, nCount, pRenderData, nTotalStripCount, ( 1 << i ) ); } else { // Debugging mode where we render wireframe on models hit by the flashlight // FIXME: Could make this faster, but why bother? It's a debugging mode. int nSizeInBytes = nCount * sizeof(MeshRenderData2_t); STUDIORENDER_TEMP_DATA_MALLOC( MeshRenderData2_t, pTempData, nSizeInBytes ); memcpy( pTempData, pRenderData, nSizeInBytes ); for ( int j = 0; j < nCount; ++j ) { pTempData[j].m_pMaterial = flashlight.m_pDebugMaterial; } pRenderContext->SetFlashlightMode( false ); DrawMeshRenderData( pRenderContext, drawInfo, nCount, pTempData, nTotalStripCount, ( 1 << i ) ); pRenderContext->SetFlashlightMode( true ); STUDIORENDER_TEMP_DATA_FREE( pTempData ); } if ( bDoScissor && state.DoScissor() ) { pRenderContext->PopScissorRect(); } } pRenderContext->SetFlashlightMode( false ); } //----------------------------------------------------------------------------- // Draws decals illuminated by the flashlight //----------------------------------------------------------------------------- void CStudioRender::DrawModelArrayFlashlightDecals( IMatRenderContext *pRenderContext, studiohdr_t *pStudioHdr, int nFlashlightCount, FlashlightInstance_t *pFlashlights, int nCount, DecalRenderData_t *pRenderData ) { if ( !nFlashlightCount ) return; pRenderContext->SetFlashlightMode( true ); bool bDoScissor = r_flashlightscissor.GetBool() && ( pRenderContext->GetRenderTarget() == NULL ); int i; for ( i = 0; i < nFlashlightCount; ++i ) { FlashlightInstance_t &flashlight = pFlashlights[i]; const FlashlightState_t& state = flashlight.m_FlashlightState; if ( bDoScissor ) { if ( state.DoScissor() ) { pRenderContext->PushScissorRect( state.GetLeft(), state.GetTop(), state.GetRight(), state.GetBottom() ); } } if ( !flashlight.m_pDebugMaterial ) { pRenderContext->SetFlashlightStateEx( state, flashlight.m_WorldToTexture, flashlight.m_pFlashlightDepthTexture ); DrawModelArrayDecals( pRenderContext, pStudioHdr, nCount, pRenderData, ( 1 << i ) ); } else { // Debugging mode where we render wireframe on models hit by the flashlight // FIXME: Could make this faster, but why bother? It's a debugging mode. int nSizeInBytes = nCount * sizeof(DecalRenderData_t); STUDIORENDER_TEMP_DATA_MALLOC( DecalRenderData_t, pTempData, nSizeInBytes ); memcpy( pTempData, pRenderData, nSizeInBytes ); for ( int j = 0; j < nCount; ++j ) { pTempData[j].m_pRenderMaterial = flashlight.m_pDebugMaterial; } pRenderContext->SetFlashlightMode( false ); DrawModelArrayDecals( pRenderContext, pStudioHdr, nCount, pTempData, ( 1 << i ) ); pRenderContext->SetFlashlightMode( true ); STUDIORENDER_TEMP_DATA_FREE( pTempData ); } if ( bDoScissor && state.DoScissor() ) { pRenderContext->PopScissorRect(); } } pRenderContext->SetFlashlightMode( false ); } int CStudioRender::CountDecalMeshesToDraw( int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride ) { VPROF( "CStudioRender::CountDecalMeshesToDraw" ); int nDecalMeshCount = 0; StudioArrayInstanceData_t *pCurInstance = pInstanceData; for ( int i = 0; i < nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { StudioDecalHandle_t handle = pCurInstance->m_Decals; if ( handle == STUDIORENDER_DECAL_INVALID ) continue; const DecalModelList_t& list = m_DecalList[(intp)handle]; unsigned short mat = list.m_pLod[pCurInstance->m_nLOD].m_FirstMaterial; for ( ; mat != m_DecalMaterial.InvalidIndex(); mat = m_DecalMaterial.Next(mat)) { ++nDecalMeshCount; } } return nDecalMeshCount; } //----------------------------------------------------------------------------- // Sort decals function //----------------------------------------------------------------------------- inline bool CStudioRender::SortDecalsLessFunc( const DecalRenderData_t &left, const DecalRenderData_t &right ) { if ( left.m_pDecalMaterial->m_pvProxyUserData != right.m_pDecalMaterial->m_pvProxyUserData ) { int nUniqueID_Left = int( reinterpret_cast< uintp >( left.m_pDecalMaterial->m_pvProxyUserData ) ) & 0xFFFFFF; int nUniqueID_Right = int( reinterpret_cast< uintp >( right.m_pDecalMaterial->m_pvProxyUserData ) ) & 0xFFFFFF; if ( nUniqueID_Left != nUniqueID_Right ) { if ( nUniqueID_Left && nUniqueID_Right ) return nUniqueID_Left < nUniqueID_Right; else return nUniqueID_Left > nUniqueID_Right; // if left is player decal, and right is non-proxied then true means to render player decal earlier } } if ( left.m_pDecalMaterial->m_pMaterial != right.m_pDecalMaterial->m_pMaterial ) return left.m_pDecalMaterial->m_pMaterial > right.m_pDecalMaterial->m_pMaterial; if ( left.m_pInstance->m_pEnvCubemapTexture != right.m_pInstance->m_pEnvCubemapTexture ) return left.m_pInstance->m_pEnvCubemapTexture > right.m_pInstance->m_pEnvCubemapTexture; bool bLeftHasLighting = ( left.m_pInstance->m_pLightingState != NULL ); bool bRightHasLighting = ( right.m_pInstance->m_pLightingState != NULL ); if ( bLeftHasLighting != bRightHasLighting ) return bLeftHasLighting; if ( !bLeftHasLighting ) return false; return left.m_pInstance->m_pLightingState->m_nLocalLightCount > right.m_pInstance->m_pLightingState->m_nLocalLightCount; } //----------------------------------------------------------------------------- // Sorts decals to render //----------------------------------------------------------------------------- int CStudioRender::BuildSortedDecalRenderList( DecalRenderData_t *pDecalRenderData, int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride ) { VPROF( "CStudioRender::BuildSortedDecalRenderList" ); int nDecalMeshCount = 0; StudioArrayInstanceData_t *pCurInstance = pInstanceData; for ( int i = 0; i < nCount; ++i, pCurInstance = (StudioArrayInstanceData_t*)( (char*)pCurInstance + nInstanceStride ) ) { StudioDecalHandle_t handle = pCurInstance->m_Decals; if ( handle == STUDIORENDER_DECAL_INVALID ) continue; const DecalModelList_t& list = m_DecalList[(intp)handle]; unsigned short mat = list.m_pLod[pCurInstance->m_nLOD].m_FirstMaterial; for ( ; mat != m_DecalMaterial.InvalidIndex(); mat = m_DecalMaterial.Next(mat)) { DecalMaterial_t *pDecalMaterial = &m_DecalMaterial[mat]; // It's possible for the index count to become zero due to decal retirement int indexCount = pDecalMaterial->m_Indices.Count(); if ( indexCount == 0 ) continue; DecalRenderData_t &decalRenderData = pDecalRenderData[nDecalMeshCount++]; decalRenderData.m_pDecalMaterial = pDecalMaterial; decalRenderData.m_pInstance = pCurInstance; decalRenderData.m_pRenderMaterial = pDecalMaterial->m_pMaterial; decalRenderData.m_bIsVertexLit = pDecalMaterial->m_pMaterial->IsVertexLit(); } } // Debug mode if ( m_pRC->m_Config.bWireframeDecals ) { for ( int i = 0; i < nDecalMeshCount; ++i ) { pDecalRenderData[i].m_pRenderMaterial = m_pMaterialWireframe[0][0]; // TODO: support displacement mapping } } std::make_heap( pDecalRenderData, pDecalRenderData + nDecalMeshCount, SortDecalsLessFunc ); std::sort_heap( pDecalRenderData, pDecalRenderData + nDecalMeshCount, SortDecalsLessFunc ); return nDecalMeshCount; } //----------------------------------------------------------------------------- // Main entry point for drawing an array of instances //----------------------------------------------------------------------------- void CStudioRender::DrawModelArray( const StudioModelArrayInfo_t &drawInfo, const StudioRenderContext_t &rc, int nCount, StudioArrayInstanceData_t *pInstanceData, int nInstanceStride, int nFlags ) { m_pRC = const_cast< StudioRenderContext_t* >( &rc ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); // Preserve the matrices if we're skinning pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); // Count number of meshes per instance bool bDoShadows = ( ( nFlags & STUDIORENDER_DRAW_NO_SHADOWS ) == 0 ); bool bSinglePassFlashlight = IsGameConsole(); int nTimesRendered = 1 + ( bSinglePassFlashlight ? 0 : ( bDoShadows ? drawInfo.m_nFlashlightCount : 1 ) ); int nTotalMeshCount = CountMeshesToDraw( drawInfo, nCount, pInstanceData, nInstanceStride, nTimesRendered ); // Build list of meshes to render int nTotalStripCount; STUDIORENDER_TEMP_DATA_MALLOC( MeshRenderData_t, pRenderData, nTotalMeshCount * sizeof(MeshRenderData_t) ); if ( !rc.m_pForcedMaterial[ 0 ] || rc.m_nForcedMaterialType == OVERRIDE_SELECTIVE ) { nTotalMeshCount = BuildSortedRenderList( pRenderData, &nTotalStripCount, drawInfo, nCount, pInstanceData, nInstanceStride, nFlags ); } else { BuildForcedMaterialRenderList( pRenderData, &nTotalStripCount, drawInfo, rc, nCount, pInstanceData, nInstanceStride ); } // Restore meshes, if necessary RestoreMeshes( nTotalMeshCount, pRenderData, sizeof(MeshRenderData_t) ); // Draw, baby, draw! DrawMeshRenderData( pRenderContext, drawInfo, nTotalMeshCount, pRenderData, nTotalStripCount, 0 ); // Draw all flashlights if ( bDoShadows && !bSinglePassFlashlight ) { DrawModelArrayFlashlight( pRenderContext, drawInfo, nTotalMeshCount, pRenderData, nTotalStripCount ); } // Count number of decals meshes per instance int nTotalDecalCount = CountDecalMeshesToDraw( nCount, pInstanceData, nInstanceStride ); STUDIORENDER_TEMP_DATA_MALLOC( DecalRenderData_t, pDecalData, nTotalDecalCount * sizeof(DecalRenderData_t) ); // Build list of decals to render nTotalDecalCount = BuildSortedDecalRenderList( pDecalData, nCount, pInstanceData, nInstanceStride ); // Draw all decals DrawModelArrayDecals( pRenderContext, drawInfo.m_pStudioHdr, nTotalDecalCount, pDecalData, 0 ); // Illuminate decals by the flashlight if ( bDoShadows && !bSinglePassFlashlight ) { DrawModelArrayFlashlightDecals( pRenderContext, drawInfo.m_pStudioHdr, drawInfo.m_nFlashlightCount, drawInfo.m_pFlashlights, nTotalDecalCount, pDecalData ); } pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pRenderData ); STUDIORENDER_TEMP_DATA_FREE( pDecalData ); } //----------------------------------------------------------------------------- // More optimal version? //----------------------------------------------------------------------------- void CStudioRender::DrawModelArray2( const StudioModelArrayInfo2_t &drawInfo, const StudioRenderContext_t &rc, int nCount, StudioArrayData_t *pArrayData, int nInstanceStride, int nFlags ) { m_pRC = const_cast< StudioRenderContext_t* >( &rc ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); // Preserve the matrices if we're skinning pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); // Count number of meshes per instance bool bDoShadows = ( ( nFlags & STUDIORENDER_DRAW_NO_SHADOWS ) == 0 ); bool bSinglePassFlashlight = IsGameConsole(); int nTimesRendered = 1 + ( bSinglePassFlashlight ? 0 : ( bDoShadows ? drawInfo.m_nFlashlightCount : 1 ) ); int nTotalMeshCount = CountMeshesToDraw( drawInfo, nCount, pArrayData, nInstanceStride, nTimesRendered ); // Build list of meshes to render int nTotalStripCount; STUDIORENDER_TEMP_DATA_MALLOC( MeshRenderData2_t, pRenderData, nTotalMeshCount * sizeof(MeshRenderData2_t) ); if ( !rc.m_pForcedMaterial[ 0 ] || rc.m_nForcedMaterialType == OVERRIDE_SELECTIVE ) { nTotalMeshCount = BuildSortedRenderList( pRenderData, &nTotalStripCount, drawInfo, nCount, pArrayData, nInstanceStride, nFlags ); } else { BuildForcedMaterialRenderList( pRenderData, &nTotalStripCount, drawInfo, rc, nCount, pArrayData, nInstanceStride ); } // Restore meshes, if necessary RestoreMeshes( nTotalMeshCount, pRenderData, sizeof(MeshRenderData2_t) ); // Draw, baby, draw! DrawMeshRenderData( pRenderContext, drawInfo, nTotalMeshCount, pRenderData, nTotalStripCount, 0 ); // Draw all flashlights if ( bDoShadows && !bSinglePassFlashlight ) { DrawModelArrayFlashlight( pRenderContext, drawInfo, nTotalMeshCount, pRenderData, nTotalStripCount ); } // Count number of decals meshes per instance for ( int i = 0; i < nCount; ++i ) { StudioArrayData_t &arrayData = pArrayData[i]; StudioArrayInstanceData_t *pGroupInstances = ( StudioArrayInstanceData_t * )( arrayData.m_pInstanceData ); int nTotalDecalCount = CountDecalMeshesToDraw( arrayData.m_nCount, pGroupInstances, nInstanceStride ); STUDIORENDER_TEMP_DATA_MALLOC( DecalRenderData_t, pDecalData, nTotalDecalCount * sizeof(DecalRenderData_t) ); // Build list of decals to render nTotalDecalCount = BuildSortedDecalRenderList( pDecalData, arrayData.m_nCount, pGroupInstances, nInstanceStride ); // Draw all decals DrawModelArrayDecals( pRenderContext, arrayData.m_pStudioHdr, nTotalDecalCount, pDecalData, 0 ); // Illuminate decals by the flashlight if ( bDoShadows && !bSinglePassFlashlight ) { DrawModelArrayFlashlightDecals( pRenderContext, arrayData.m_pStudioHdr, drawInfo.m_nFlashlightCount, drawInfo.m_pFlashlights, nTotalDecalCount, pDecalData ); } STUDIORENDER_TEMP_DATA_FREE( pDecalData ); } pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pRenderData ); } //----------------------------------------------------------------------------- // Counts the number of meshes to draw ( shadow rendering ) //----------------------------------------------------------------------------- int CStudioRender::CountMeshesToDraw( int nCount, StudioArrayData_t *pShadowData, int nInstanceStride ) { VPROF( "CStudioRender::CountMeshesToDraw (shadow)" ); #ifndef _CERT bool bCountRenderedFaces = mat_rendered_faces_count.GetBool() || mat_print_top_model_vert_counts.GetBool(); #endif // !_CERT int nTotalMeshCount = 0; for ( int i = 0; i < nCount; ++i ) { StudioArrayData_t &shadow = pShadowData[i]; StudioShadowArrayInstanceData_t *pCurInstance = ( StudioShadowArrayInstanceData_t* )shadow.m_pInstanceData; for ( int j = 0; j < shadow.m_nCount; ++j, pCurInstance = ( StudioShadowArrayInstanceData_t* )( (uint8*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = shadow.m_pHardwareData->m_pLODs[nLod].m_pMeshData; #ifndef _CERT // Each model counts how many rendered faces it accounts for each frame: if ( bCountRenderedFaces ) shadow.m_pHardwareData->UpdateFacesRenderedCount( shadow.m_pStudioHdr, m_ModelFaceCountHash, nLod, 1 ); #endif // !_CERT int nBody = pCurInstance->m_nBody; for ( int body = 0; body < shadow.m_pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = shadow.m_pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; nTotalMeshCount += pMeshData->m_NumGroup; } } } } return nTotalMeshCount; } //----------------------------------------------------------------------------- // Sets up a depth write material based on an initial material //----------------------------------------------------------------------------- void CStudioRender::GetDepthWriteMaterial( IMaterial** ppDepthMaterial, bool *pIsAlphaTested, bool *pUsesTreeSway, IMaterial *pSrcMaterial, bool bIsTranslucentUnderModulation, bool bIsSSAODepthWrite ) { static unsigned int originalTextureVarCache = 0; static unsigned int nOriginalTreeSwayVarCache = 0; IMaterialVar *pOriginalTextureVar = pSrcMaterial->FindVarFast( "$basetexture", &originalTextureVarCache ); IMaterialVar *pOriginalTreeSwayVar = pSrcMaterial->FindVarFast( "$treesway", &nOriginalTreeSwayVarCache ); // Select proper override material int nAlphaTest = (int) ( ( pSrcMaterial->IsAlphaTested() || bIsTranslucentUnderModulation ) && pOriginalTextureVar->IsTexture() ); // alpha tested base texture int nNoCull = (int) pSrcMaterial->IsTwoSided(); int nTreeSway = (int) ( pOriginalTreeSwayVar && pOriginalTreeSwayVar->GetIntValue() ); if ( !bIsSSAODepthWrite ) { *ppDepthMaterial = m_pDepthWrite[ nAlphaTest ][ nNoCull ][ nTreeSway ]; } else { *ppDepthMaterial = m_pSSAODepthWrite[ nAlphaTest ][ nNoCull ][ nTreeSway ]; } *pIsAlphaTested = ( nAlphaTest != 0 ); *pUsesTreeSway = ( nTreeSway != 0 ); } //----------------------------------------------------------------------------- // Sets up a depth write material based on an initial material //----------------------------------------------------------------------------- void CStudioRender::SetupAlphaTestedDepthWrite( IMaterial* pDepthMaterial, IMaterial *pSrcMaterial ) { static unsigned int originalTextureVarCache = 0; IMaterialVar *pOriginalTextureVar = pSrcMaterial->FindVarFast( "$basetexture", &originalTextureVarCache ); static unsigned int originalTextureFrameVarCache = 0; IMaterialVar *pOriginalTextureFrameVar = pSrcMaterial->FindVarFast( "$frame", &originalTextureFrameVarCache ); static unsigned int originalAlphaRefCache = 0; IMaterialVar *pOriginalAlphaRefVar = pSrcMaterial->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache ); static unsigned int textureVarCache = 0; IMaterialVar *pTextureVar = pDepthMaterial->FindVarFast( "$basetexture", &textureVarCache ); static unsigned int textureFrameVarCache = 0; IMaterialVar *pTextureFrameVar = pDepthMaterial->FindVarFast( "$frame", &textureFrameVarCache ); static unsigned int alphaRefCache = 0; IMaterialVar *pAlphaRefVar = pDepthMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache ); if ( pOriginalTextureVar->IsTexture() ) // If $basetexture is defined { if( pTextureVar && pOriginalTextureVar ) { pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() ); } if( pTextureFrameVar && pOriginalTextureFrameVar ) { pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() ); } if( pAlphaRefVar && pOriginalAlphaRefVar ) { pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() ); } } } //----------------------------------------------------------------------------- // Sets up a depth write material based on an initial material //----------------------------------------------------------------------------- void CStudioRender::SetupTreeSwayDepthWrite( IMaterial* pDepthMaterial, IMaterial *pSrcMaterial ) { static const char* paramNames[14] = { "$treeswayheight", "$treeswaystartheight", "$treeswayradius", "$treeswaystartradius", "$treeswayspeed", "$treeswayspeedhighwindmultiplier", "$treeswaystrength", "$treeswayscrumblespeed", "$treeswayscrumblestrength", "$treeswayscrumblefrequency", "$treeswayfalloffexp", "$treeswayscrumblefalloffexp", "$treeswayspeedlerpstart", "$treeswayspeedlerpend", }; static unsigned int originalVarCache[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static unsigned int varCache[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; IMaterialVar *pOriginalVar = NULL; IMaterialVar *pVar = NULL; for ( int32 i = 0; i < ARRAYSIZE( paramNames ); i++ ) { pOriginalVar = pSrcMaterial->FindVarFast( paramNames[i], originalVarCache + i ); pVar = pDepthMaterial->FindVarFast( paramNames[i], varCache + i ); if( pOriginalVar && pVar ) { pVar->SetFloatValue( pOriginalVar->GetFloatValue() ); } } } //----------------------------------------------------------------------------- // Sort models function //----------------------------------------------------------------------------- inline bool CStudioRender::ShadowSortLessFunc( const ShadowMeshRenderData_t &left, const ShadowMeshRenderData_t &right ) { if ( left.m_pMaterial != right.m_pMaterial ) return left.m_pMaterial > right.m_pMaterial; if ( left.m_bIsAlphaTested && right.m_bIsAlphaTested ) { if ( left.m_pSrcMaterial != right.m_pSrcMaterial ) return left.m_pSrcMaterial > right.m_pSrcMaterial; } if ( left.m_bUsesTreeSway && right.m_bUsesTreeSway ) { if ( left.m_pSrcMaterial != right.m_pSrcMaterial ) return left.m_pSrcMaterial > right.m_pSrcMaterial; } if ( left.m_nCompressionType != right.m_nCompressionType ) return left.m_nCompressionType > right.m_nCompressionType; if ( left.m_nMeshBoneCount != right.m_nMeshBoneCount ) return left.m_nMeshBoneCount > right.m_nMeshBoneCount; return left.m_pGroup->m_pMesh > right.m_pGroup->m_pMesh; } //----------------------------------------------------------------------------- // BuildShadowRenderList //----------------------------------------------------------------------------- int CStudioRender::BuildShadowRenderList( ShadowMeshRenderData_t *pRenderData, int *pTotalStripCount, int nCount, StudioArrayData_t *pShadowData, int nInstanceStride, int flags ) { VPROF( "CStudioRender::BuildShadowRenderList" ); *pTotalStripCount = 0; int nRenderDataCount = 0; for ( int i = 0; i < nCount; ++i ) { StudioArrayData_t &shadow = pShadowData[i]; studiohdr_t *pStudioHdr = shadow.m_pStudioHdr; short *pSkinRefBase = pStudioHdr->pSkinref( 0 ); StudioShadowArrayInstanceData_t *pCurInstance = ( StudioShadowArrayInstanceData_t* )shadow.m_pInstanceData; for ( int j = 0; j < shadow.m_nCount; ++j, pCurInstance = ( StudioShadowArrayInstanceData_t* )( (uint8*)pCurInstance + nInstanceStride ) ) { // This subarray has the same skin + body + lod int nLod = pCurInstance->m_nLOD; // get the studio mesh data for this lod studiomeshdata_t *pMeshDataBase = shadow.m_pHardwareData->m_pLODs[nLod].m_pMeshData; IMaterial **ppMaterials = shadow.m_pHardwareData->m_pLODs[nLod].ppMaterials; short *pSkinRef = pSkinRefBase; int skin = pCurInstance->m_nSkin; if ( skin > 0 && skin < pStudioHdr->numskinfamilies ) { pSkinRef += ( skin * pStudioHdr->numskinref ); } int nBody = pCurInstance->m_nBody; for ( int body = 0; body < pStudioHdr->numbodyparts; ++body ) { mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( body ); int index = nBody / pbodypart->base; index = index % pbodypart->nummodels; mstudiomodel_t *pSubmodel = pbodypart->pModel( index ); for ( int meshIndex = 0; meshIndex < pSubmodel->nummeshes; ++meshIndex ) { mstudiomesh_t *pMesh = pSubmodel->pMesh(meshIndex); studiomeshdata_t *pMeshData = &pMeshDataBase[pMesh->meshid]; Assert( pMeshData ); //Assert( pMeshData->m_NumGroup ); // Can't assume that this is non-zero for lods with removed meshes. IMaterial *pMaterial = ppMaterials[ pSkinRef[ pMesh->material ] ]; // Used on cstrike15 to efficiently render translucent renderables into csm shadow buffers if ( ( flags & STUDIORENDER_SHADOWDEPTHTEXTURE_INCLUDE_TRANSLUCENT_MATERIALS ) == 0 ) { // Bail if the material is still considered translucent after setting the AlphaModulate to 1.0 if ( pMaterial->IsTranslucentUnderModulation() ) continue; } IMaterial *pDepthMaterial; bool bIsAlphaTested = false; bool bUsesTreeSway = false; GetDepthWriteMaterial( &pDepthMaterial, &bIsAlphaTested, &bUsesTreeSway, pMaterial, pMaterial->IsTranslucentUnderModulation(), ( flags & STUDIORENDER_SSAODEPTHTEXTURE ) ? true : false ); for ( int g = 0; g < pMeshData->m_NumGroup; ++g ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[g]; VertexCompressionType_t nCompressionType = CompressionType( pGroup->m_pMesh->GetVertexFormat() ); ShadowMeshRenderData_t &data = pRenderData[nRenderDataCount++]; data.m_pGroup = pGroup; data.m_pInstance = pCurInstance; data.m_pMesh = pMesh; data.m_pMaterial = pDepthMaterial; data.m_pSrcMaterial = pMaterial; data.m_nCompressionType = nCompressionType; data.m_nMeshBoneCount = NumBoneWeights( pGroup->m_pMesh->GetVertexFormat() ); data.m_bIsAlphaTested = bIsAlphaTested; data.m_bUsesTreeSway = bUsesTreeSway; *pTotalStripCount += pGroup->m_NumStrips; } } } } } std::make_heap( pRenderData, pRenderData + nRenderDataCount, ShadowSortLessFunc ); std::sort_heap( pRenderData, pRenderData + nRenderDataCount, ShadowSortLessFunc ); return nRenderDataCount; } //----------------------------------------------------------------------------- // Draws shadow meshes //----------------------------------------------------------------------------- void CStudioRender::DrawShadowMeshRenderData( IMatRenderContext *pRenderContext, int nCount, ShadowMeshRenderData_t *pRenderData, int nTotalStripCount ) { VPROF( "CStudioRender::DrawShadowMeshRenderData" ); int nInstanceCount = 0; STUDIORENDER_TEMP_DATA_MALLOC( MeshInstanceData_t, pInstance, nTotalStripCount * sizeof(MeshInstanceData_t) ); int nMaxBoneCount = 0; int nLastMeshBoneCount = 0; IMaterial *pLastMaterial = NULL; IMaterial *pLastSrcMaterial = NULL; bool bIsAlphaTested = false; bool bUsesTreeSway = false; VertexCompressionType_t nLastCompressionType = VERTEX_COMPRESSION_INVALID; for ( int i = 0; i < nCount; ++i ) { ShadowMeshRenderData_t &data = pRenderData[i]; StudioShadowArrayInstanceData_t *pCurrInstance = data.m_pInstance; if ( ( pLastMaterial != data.m_pMaterial ) || // shadow material is different ( bIsAlphaTested && ( pLastSrcMaterial != data.m_pSrcMaterial ) ) || // alpha channel is different ( bUsesTreeSway && ( pLastSrcMaterial != data.m_pSrcMaterial ) ) || // tree sway params are different ( nLastCompressionType != data.m_nCompressionType ) || // compression type is different ( nLastMeshBoneCount != data.m_nMeshBoneCount ) ) // # of bones in the mesh data is different { if ( nInstanceCount > 0 ) { if ( bIsAlphaTested ) { SetupAlphaTestedDepthWrite( pLastMaterial, pLastSrcMaterial ); } if ( bUsesTreeSway ) { SetupTreeSwayDepthWrite( pLastMaterial, pLastSrcMaterial ); } pRenderContext->SetNumBoneWeights( nLastMeshBoneCount > 0 ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } nInstanceCount = 0; nMaxBoneCount = 0; nLastCompressionType = data.m_nCompressionType; pLastMaterial = data.m_pMaterial; pLastSrcMaterial = data.m_pSrcMaterial; nLastMeshBoneCount = data.m_nMeshBoneCount; bIsAlphaTested = data.m_bIsAlphaTested; bUsesTreeSway = data.m_bUsesTreeSway; } studiomeshgroup_t* pGroup = data.m_pGroup; for ( int j = 0; j < pGroup->m_NumStrips; ++j ) { OptimizedModel::StripHeader_t* pStrip = &pGroup->m_pStripData[j]; Assert( nInstanceCount < nTotalStripCount ); MeshInstanceData_t &instance = pInstance[nInstanceCount++]; instance.m_pEnvCubemap = NULL; instance.m_pPoseToWorld = pCurrInstance->m_pPoseToWorld; instance.m_pLightingState = NULL; instance.m_nBoneCount = pStrip->numBoneStateChanges; instance.m_pBoneRemap = ( instance.m_nBoneCount > 0 ) ? (MeshBoneRemap_t*)( pStrip->pBoneStateChange(0) ) : NULL; instance.m_nIndexOffset = pStrip->indexOffset; instance.m_nIndexCount = pStrip->numIndices; instance.m_nPrimType = MATERIAL_TRIANGLES; instance.m_pColorBuffer = NULL; instance.m_nColorVertexOffsetInBytes = 0; instance.m_pStencilState = NULL; instance.m_pVertexBuffer = pGroup->m_pMesh; instance.m_pIndexBuffer = pGroup->m_pMesh; instance.m_nVertexOffsetInBytes = 0; instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f ); instance.m_nLightmapPageId = MATERIAL_SYSTEM_LIGHTMAP_PAGE_INVALID; instance.m_bColorBufferHasIndirectLightingOnly = false; nMaxBoneCount = MAX( nMaxBoneCount, pStrip->numBones ); } } if ( nInstanceCount > 0 ) { if ( bIsAlphaTested ) { SetupAlphaTestedDepthWrite( pLastMaterial, pLastSrcMaterial ); } if ( bUsesTreeSway ) { SetupTreeSwayDepthWrite( pLastMaterial, pLastSrcMaterial ); } pRenderContext->SetNumBoneWeights( nLastMeshBoneCount > 0 ? nMaxBoneCount : 0 ); pRenderContext->Bind( pLastMaterial, NULL ); pRenderContext->DrawInstances( nInstanceCount, pInstance ); } pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pInstance ); } //----------------------------------------------------------------------------- // Draws all models to the shadow depth buffer //----------------------------------------------------------------------------- void CStudioRender::DrawModelShadowArray( const StudioRenderContext_t &rc, int nCount, StudioArrayData_t *pShadowData, int nInstanceStride, int flags ) { m_pRC = const_cast< StudioRenderContext_t* >( &rc ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); // Preserve the matrices if we're skinning pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); // Count number of meshes to draw int nTotalMeshCount = CountMeshesToDraw( nCount, pShadowData, nInstanceStride ); // Build list of meshes to render int nTotalStripCount; STUDIORENDER_TEMP_DATA_MALLOC( ShadowMeshRenderData_t, pRenderData, nTotalMeshCount * sizeof(ShadowMeshRenderData_t) ); nTotalMeshCount = BuildShadowRenderList( pRenderData, &nTotalStripCount, nCount, pShadowData, nInstanceStride, flags ); // Restore meshes, if necessary RestoreMeshes( nTotalMeshCount, pRenderData, sizeof(ShadowMeshRenderData_t) ); // Draw, baby, draw! DrawShadowMeshRenderData( pRenderContext, nTotalMeshCount, pRenderData, nTotalStripCount ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->SetNumBoneWeights( 0 ); STUDIORENDER_TEMP_DATA_FREE( pRenderData ); } void CStudioRender::ComputeDiffuseModulation( Vector4D *pDiffuseModulation ) { if ( ( m_pRC->m_nForcedMaterialType != OVERRIDE_DEPTH_WRITE ) && ( m_pRC->m_nForcedMaterialType != OVERRIDE_SSAO_DEPTH_WRITE ) ) { pDiffuseModulation->Init( m_pRC->m_ColorMod[0], m_pRC->m_ColorMod[1], m_pRC->m_ColorMod[2], m_pRC->m_AlphaMod ); } else { pDiffuseModulation->Init( 1.0f, 1.0f, 1.0f, 1.0f ); } } // this is a fast path that does not support debug modes or transparency (or skeletons obviously) // pass in an array of instances and draw them void CStudioRender::DrawModelArrayStaticProp( const DrawModelInfo_t& info, const StudioRenderContext_t &rc, int nInstanceCount, const MeshInstanceData_t *pInstanceData, ColorMeshInfo_t **pColorMeshes ) { VPROF( "CStudioRender::DrawModelArrayStaticProp"); m_pRC = const_cast( &rc ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->SetNumBoneWeights( 0 ); pRenderContext->MatrixMode( MATERIAL_MODEL ); // this is needed by R_SetupSkinAndLighting bool flexConfig = m_pRC->m_Config.bFlex; m_pRC->m_Config.bFlex = false; bool bWireframe = m_pRC->m_Config.bWireframe; m_pRC->m_Config.bWireframe = false; m_bSkippedMeshes = false; m_bDrawTranslucentSubModels = false; int lod = info.m_Lod; m_pStudioHdr = info.m_pStudioHdr; m_pStudioMeshes = info.m_pHardwareData->m_pLODs[lod].m_pMeshData; m_pStudioHWData = info.m_pHardwareData; #if PIX_ENABLE char szPIXEventName[128]; sprintf( szPIXEventName, "%s*", m_pStudioHdr->name ); // PIX PIXEVENT( pRenderContext, szPIXEventName ); #endif // Build list of submodels Assert(m_pStudioHdr->numbodyparts==1); mstudiobodyparts_t *pbodypart = m_pStudioHdr->pBodypart( 0 ); m_pSubModel = pbodypart->pModel( 0 ); // get skinref array int skin = info.m_Skin; int *pMaterialFlags = info.m_pHardwareData->m_pLODs[lod].pMaterialFlags; short *pskinref = m_pStudioHdr->pSkinref( 0 ); if ( skin > 0 && skin < m_pStudioHdr->numskinfamilies ) { pskinref += ( skin * m_pStudioHdr->numskinref ); } #ifndef _CERT int nFacesPerModel = 0; #endif // !_CERT // draw each mesh for ( int i = 0; i < m_pSubModel->nummeshes; ++i) { mstudiomesh_t *pmesh = m_pSubModel->pMesh(i); studiomeshdata_t *pMeshData = &m_pStudioMeshes[pmesh->meshid]; Assert( pMeshData ); if ( !pMeshData->m_NumGroup ) continue; if ( !pMaterialFlags ) continue; int materialFlags = pMaterialFlags[pskinref[pmesh->material]]; StudioModelLighting_t lighting = LIGHTING_HARDWARE; IMaterial* pMaterial = R_StudioSetupSkinAndLighting( pRenderContext, pskinref[ pmesh->material ], info.m_pHardwareData->m_pLODs[lod].ppMaterials, materialFlags, info.m_pClientEntity, info.m_pColorMeshes, lighting ); if ( !pMaterial ) continue; // if this fails you've got a static prop with an eyeball! Assert( pmesh->materialtype != 1 ); for ( int j = 0; j < pMeshData->m_NumGroup; ++j ) { studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[j]; // Needed when we switch back and forth between hardware + software lighting #ifdef IS_WINDOWS_PC if ( IsPC() && pGroup->m_MeshNeedsRestore ) { VertexCompressionType_t compressionType = CompressionType( pGroup->m_pMesh->GetVertexFormat() ); switch ( compressionType ) { case VERTEX_COMPRESSION_ON: R_StudioRestoreMesh( pmesh, pGroup ); case VERTEX_COMPRESSION_NONE: default: R_StudioRestoreMesh( pmesh, pGroup ); break; } pGroup->m_MeshNeedsRestore = false; } #endif IMesh *pMesh = pGroup->m_pMesh; for ( int k = 0; k < nInstanceCount; k++ ) { const MeshInstanceData_t &instance = pInstanceData[k]; if ( instance.m_pEnvCubemap ) { pRenderContext->BindLocalCubemap( const_cast( instance.m_pEnvCubemap ) ); } if ( pColorMeshes[k] ) { pMesh->SetColorMesh( pColorMeshes[k][pGroup->m_ColorMeshID].m_pMesh, pColorMeshes[k][pGroup->m_ColorMeshID].m_nVertOffsetInBytes ); } else { pMesh->SetColorMesh( NULL, 0 ); } pRenderContext->LoadMatrix( *instance.m_pPoseToWorld ); for ( int strip = 0; strip < pGroup->m_NumStrips; ++strip ) { OptimizedModel::StripHeader_t* pStrip = &pGroup->m_pStripData[strip]; pMesh->SetPrimitiveType( GetPrimitiveTypeForStripHeaderFlags( pStrip->flags ) ); pMesh->DrawModulated( instance.m_DiffuseModulation, pStrip->indexOffset, pStrip->numIndices ); #ifndef _CERT // Count # faces per instance for the first instance only if ( k == 0 ) { // This code does not work with SubD but we're not really using that anyways Assert( GetPrimitiveTypeForStripHeaderFlags( pStrip->flags ) == MATERIAL_TRIANGLES ); nFacesPerModel += pStrip->numIndices / 3; } #endif // !_CERT } } pMesh->SetColorMesh( NULL, 0 ); } } #ifndef _CERT if ( mat_rendered_faces_count.GetBool() || mat_print_top_model_vert_counts.GetBool() ) { // Each model counts how many rendered faces it accounts for each frame: m_pStudioHWData->UpdateFacesRenderedCount( m_pStudioHdr, m_ModelFaceCountHash, lod, nInstanceCount, nFacesPerModel ); } #endif // !_CERT // Restore the configs m_pRC->m_Config.bFlex = flexConfig; m_pRC->m_Config.bWireframe = bWireframe; m_pRC = NULL; m_pStudioHdr = NULL; m_pStudioMeshes = NULL; m_pStudioHWData = NULL; } #ifndef _CERT bool FacesRenderedInfoCompareFunc( IStudioRender::FacesRenderedInfo_t const &a, IStudioRender::FacesRenderedInfo_t const &b ) { return a.pStudioHdr == b.pStudioHdr; } uint32 FacesRenderedInfoKeyFunc( IStudioRender::FacesRenderedInfo_t const &a ) { return HashIntConventional( (int32)(intp)a.pStudioHdr ); } int FacesRenderedInfoSort( const void *a, const void *b ) { IStudioRender::FacesRenderedInfo_t *A = (IStudioRender::FacesRenderedInfo_t *)a, *B = (IStudioRender::FacesRenderedInfo_t *)b; return ( A->nFaceCount > B->nFaceCount ) ? -1 : +1; } void UpdateAndSpewFacesRenderedHistory( CUtlVector< IStudioRender::FacesRenderedInfo_t > &newItems, int nTotal, int nSpewFromCurrentFrame, int nSpewFromHistory, bool bClearHistory ) { static const int NUM_ITEMS_TO_TRACK = 20; static const int NUM_FRAMES_TO_TRACK = 20; static IStudioRender::FacesRenderedInfo_t history[ NUM_FRAMES_TO_TRACK ][ NUM_ITEMS_TO_TRACK ]; static int nItems = 0, nOldestItem = 0; IStudioRender::FacesRenderedInfo_t emptyItem = { NULL, 0 }; if ( bClearHistory ) { nItems = nOldestItem = 0; return; } if ( !nTotal ) return; // Record the top 'NUM_ITEMS_TO_TRACK' models for the last rendered frame: int nCurItem = ( nOldestItem + nItems ) % NUM_FRAMES_TO_TRACK; if ( nItems == NUM_FRAMES_TO_TRACK ) nOldestItem = ( nOldestItem + 1 ) % NUM_FRAMES_TO_TRACK; nItems = MIN( ( nItems + 1 ), NUM_FRAMES_TO_TRACK ); for ( int i = 0; i < NUM_ITEMS_TO_TRACK; i++ ) { history[ nCurItem ][ i ] = ( i < newItems.Count() ) ? newItems[ i ] : emptyItem; } nSpewFromCurrentFrame = MIN( nSpewFromCurrentFrame, newItems.Count() ); if ( nSpewFromCurrentFrame ) { // Spew the top N offenders from this frame to the console Msg( "Faces rendered this frame, by model:\n" ); for ( int i = 0; i < nSpewFromCurrentFrame; i++ ) { Msg( "%-7d (%-3d times, %-7d avg) %s\n", newItems[ i ].nFaceCount, newItems[ i ].nRenderCount, ( int )( ( float )newItems[ i ].nFaceCount / ( float )newItems[ i ].nRenderCount ), newItems[ i ].pStudioHdr->name ); } } else { static float lastSpewTime = 0.0f; if ( Plat_FloatTime() < ( lastSpewTime + 0.25f ) ) return; lastSpewTime = Plat_FloatTime(); // Spew the N most expensive models over the last 'NUM_FRAMES_TO_TRACK' frames: CUtlHash< IStudioRender::FacesRenderedInfo_t > topItemHash( 64, 0, 0, FacesRenderedInfoCompareFunc, FacesRenderedInfoKeyFunc ); for ( int i = 0; i < nItems; i++ ) { IStudioRender::FacesRenderedInfo_t *items = history[ ( nOldestItem + i ) % NUM_FRAMES_TO_TRACK ]; for ( int j = 0; j < NUM_ITEMS_TO_TRACK; j++ ) { if ( !items[ j ].nFaceCount ) continue; UtlHashHandle_t h = topItemHash.Find( items[ j ] ); if ( h != topItemHash.InvalidHandle() ) { IStudioRender::FacesRenderedInfo_t &topItem = topItemHash[ h ]; topItem.nFaceCount = MAX( items[ j ].nFaceCount, topItem.nFaceCount ); continue; } topItemHash.Insert( items[ j ] ); } } CUtlVector< IStudioRender::FacesRenderedInfo_t > topItems; for ( UtlHashHandle_t h = topItemHash.GetFirstHandle(); h != topItemHash.InvalidHandle(); h = topItemHash.GetNextHandle( h ) ) { topItems.AddToTail( topItemHash[ h ] ); } qsort( topItems.Base(), topItems.Count(), sizeof( topItems[0] ), FacesRenderedInfoSort ); for ( int j = 0; j < MIN( nSpewFromHistory, NUM_ITEMS_TO_TRACK ); j++ ) { if ( j < topItems.Count() ) ConMsg( "%-7d (%-3d times, %-7d avg) %s\n", topItems[ j ].nFaceCount, topItems[ j ].nRenderCount, ( int )( ( float )topItems[ j ].nFaceCount / ( float )topItems[ j ].nRenderCount ), topItems[ j ].pStudioHdr->name ); } ConMsg( "%-7d total model faces rendered this frame (mat_rendered_faces_count)\n", nTotal ); } } int BuildFacesRenderedInfoListForMostRecentFrame( CUtlVector< IStudioRender::FacesRenderedInfo_t > &items, CUtlHash< studiohwdata_t * > &hash ) { int nTotal = 0; for ( UtlHashHandle_t h = hash.GetFirstHandle(); h != hash.InvalidHandle(); h = hash.GetNextHandle( h ) ) { studiohwdata_t *pHwData = hash[ h ]; if ( pHwData->m_pStudioHdr ) { IStudioRender::FacesRenderedInfo_t item = { pHwData->m_pStudioHdr, pHwData->m_NumFacesRenderedThisFrame, pHwData->m_NumTimesRenderedThisFrame }; items.AddToTail( item ); nTotal += pHwData->m_NumFacesRenderedThisFrame; } } // Sort models by face count (biggest first) qsort( items.Base(), items.Count(), sizeof( items[0] ), FacesRenderedInfoSort ); return nTotal; } void CStudioRender::UpdateModelFaceCounts( int nSpewFromCurrentFrame, bool bClearHistory ) { CUtlVector< IStudioRender::FacesRenderedInfo_t > items; if ( bClearHistory ) { UpdateAndSpewFacesRenderedHistory( items, 0, 0, 0, bClearHistory ); } else if ( mat_rendered_faces_count.GetBool() ) { int nTotal = BuildFacesRenderedInfoListForMostRecentFrame( items, m_ModelFaceCountHash ); UpdateAndSpewFacesRenderedHistory( items, nTotal, nSpewFromCurrentFrame, mat_rendered_faces_count.GetInt(), false ); mat_rendered_faces_count.SetValue( 0 ); // set back to 0 so we don't spew anymore } } int CStudioRender::GetForcedMaterialOverrideIndex( int nMaterialIndex ) { if ( m_pRC ) { for ( int i = 0; i < m_pRC->m_nForcedMaterialIndexCount; i++ ) { if ( m_pRC->m_nForcedMaterialIndex[ i ] == nMaterialIndex ) { return i; } } } return -1; } void CStudioRender::GatherRenderedFaceInfo( IStudioRender::FaceInfoCallbackFunc_t pFunc ) { int nTopN = mat_print_top_model_vert_counts.GetInt(); if ( nTopN ) { CUtlVector< IStudioRender::FacesRenderedInfo_t > items; int nTotal = BuildFacesRenderedInfoListForMostRecentFrame( items, m_ModelFaceCountHash ); if ( items.Count() > 0 ) { nTopN = MIN( nTopN, items.Count()); pFunc( nTopN, items.Base(), nTotal ); } } } CON_COMMAND( mat_rendered_faces_spew, "'mat_rendered_faces_spew ' Spew the number of faces rendered for the top N models used this frame (mat_rendered_faces_count must be set to use this)" ) { int nNumToSpew = ( args.ArgC() > 1 ) ? Q_atoi( args[ 1 ] ) : INT_MAX; if ( !mat_rendered_faces_count.GetBool() ) { Msg( "ERROR: mat_rendered_faces_count must be set in order to use mat_rendered_faces_spew\n" ); return; } g_StudioRender.UpdateModelFaceCounts( nNumToSpew ); } #endif // !_CERT