diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/l_studio.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/l_studio.cpp')
| -rw-r--r-- | engine/l_studio.cpp | 5056 |
1 files changed, 5056 insertions, 0 deletions
diff --git a/engine/l_studio.cpp b/engine/l_studio.cpp new file mode 100644 index 0000000..ef96ab9 --- /dev/null +++ b/engine/l_studio.cpp @@ -0,0 +1,5056 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// models are the only shared resource between a client and server running +// on the same machine. +//===========================================================================// + +#include "render_pch.h" +#include "client.h" +#include "gl_model_private.h" +#include "studio.h" +#include "phyfile.h" +#include "cdll_int.h" +#include "istudiorender.h" +#include "client_class.h" +#include "float.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/ivballoctracker.h" +#include "modelloader.h" +#include "lightcache.h" +#include "studio_internal.h" +#include "cdll_engine_int.h" +#include "vphysics_interface.h" +#include "utllinkedlist.h" +#include "studio.h" +#include "icliententitylist.h" +#include "engine/ivmodelrender.h" +#include "optimize.h" +#include "icliententity.h" +#include "sys_dll.h" +#include "debugoverlay.h" +#include "enginetrace.h" +#include "l_studio.h" +#include "filesystem_engine.h" +#include "ModelInfo.h" +#include "cl_main.h" +#include "tier0/vprof.h" +#include "r_decal.h" +#include "vstdlib/random.h" +#include "datacache/idatacache.h" +#include "materialsystem/materialsystem_config.h" +#include "materialsystem/itexture.h" +#include "IHammer.h" +#if defined( _WIN32 ) && !defined( _X360 ) +#include <xmmintrin.h> +#endif +#include "staticpropmgr.h" +#include "materialsystem/hardwaretexels.h" +#include "materialsystem/hardwareverts.h" +#include "tier1/callqueue.h" +#include "filesystem/IQueuedLoader.h" +#include "tier2/tier2.h" +#include "tier1/UtlSortVector.h" +#include "tier1/lzmaDecoder.h" +#include "ipooledvballocator.h" +#include "shaderapi/ishaderapi.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// #define VISUALIZE_TIME_AVERAGE 1 + +extern ConVar r_flashlight_version2; + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +void R_StudioInitLightingCache( void ); +float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta, bool bNoRadiusCheck = false ); +void SetRootLOD_f( IConVar *var, const char *pOldString, float flOldValue ); +void r_lod_f( IConVar *var, const char *pOldValue, float flOldValue ); +void FlushLOD_f(); + +class CColorMeshData; +static void CreateLightmapsFromData(CColorMeshData* _colorMeshData); + + +//----------------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------------- + +ConVar r_drawmodelstatsoverlay( "r_drawmodelstatsoverlay", "0", FCVAR_CHEAT ); +ConVar r_drawmodelstatsoverlaydistance( "r_drawmodelstatsoverlaydistance", "500", FCVAR_CHEAT ); +ConVar r_drawmodellightorigin( "r_DrawModelLightOrigin", "0", FCVAR_CHEAT ); +extern ConVar r_worldlights; +ConVar r_lod( "r_lod", "-1", 0, "", r_lod_f ); +static ConVar r_entity( "r_entity", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +static ConVar r_lightaverage( "r_lightaverage", "1", 0, "Activates/deactivate light averaging" ); +static ConVar r_lightinterp( "r_lightinterp", "5", FCVAR_CHEAT, "Controls the speed of light interpolation, 0 turns off interpolation" ); +static ConVar r_eyeglintlodpixels( "r_eyeglintlodpixels", "20.0", FCVAR_CHEAT, "The number of pixels wide an eyeball has to be before rendering an eyeglint. Is a floating point value." ); +ConVar r_rootlod( "r_rootlod", "0", FCVAR_MATERIAL_SYSTEM_THREAD | FCVAR_ARCHIVE, "Root LOD", true, 0, true, MAX_NUM_LODS-1, SetRootLOD_f ); +static ConVar r_decalstaticprops( "r_decalstaticprops", "1", 0, "Decal static props test" ); +static ConCommand r_flushlod( "r_flushlod", FlushLOD_f, "Flush and reload LODs." ); +ConVar r_debugrandomstaticlighting( "r_debugrandomstaticlighting", "0", FCVAR_CHEAT, "Set to 1 to randomize static lighting for debugging. Must restart for change to take affect." ); +ConVar r_proplightingfromdisk( "r_proplightingfromdisk", "1", FCVAR_CHEAT, "0=Off, 1=On, 2=Show Errors" ); +static ConVar r_itemblinkmax( "r_itemblinkmax", ".3", FCVAR_CHEAT ); +static ConVar r_itemblinkrate( "r_itemblinkrate", "4.5", FCVAR_CHEAT ); +static ConVar r_proplightingpooling( "r_proplightingpooling", "-1.0", FCVAR_CHEAT, "0 - off, 1 - static prop color meshes are allocated from a single shared vertex buffer (on hardware that supports stream offset)" ); + +//----------------------------------------------------------------------------- +// StudioRender config +//----------------------------------------------------------------------------- +static ConVar r_showenvcubemap( "r_showenvcubemap", "0", FCVAR_CHEAT ); +static ConVar r_eyemove ( "r_eyemove", "1", FCVAR_ARCHIVE ); // look around +static ConVar r_eyeshift_x ( "r_eyeshift_x", "0", FCVAR_ARCHIVE ); // eye X position +static ConVar r_eyeshift_y ( "r_eyeshift_y", "0", FCVAR_ARCHIVE ); // eye Y position +static ConVar r_eyeshift_z ( "r_eyeshift_z", "0", FCVAR_ARCHIVE ); // eye Z position +static ConVar r_eyesize ( "r_eyesize", "0", FCVAR_ARCHIVE ); // adjustment to iris textures +static ConVar mat_softwareskin( "mat_softwareskin", "0", FCVAR_CHEAT ); +static ConVar r_nohw ( "r_nohw", "0", FCVAR_CHEAT ); +static ConVar r_nosw ( "r_nosw", "0", FCVAR_CHEAT ); +static ConVar r_teeth ( "r_teeth", "1" ); +static ConVar r_drawentities ( "r_drawentities", "1", FCVAR_CHEAT ); +static ConVar r_flex ( "r_flex", "1" ); +static ConVar r_eyes ( "r_eyes", "1" ); +static ConVar r_skin ( "r_skin", "0", FCVAR_CHEAT ); +static ConVar r_modelwireframedecal ( "r_modelwireframedecal", "0", FCVAR_CHEAT ); +static ConVar r_maxmodeldecal ( "r_maxmodeldecal", "50" ); + +static StudioRenderConfig_t s_StudioRenderConfig; + +void UpdateStudioRenderConfig( void ) +{ + // This can happen during initialization + if ( !g_pMaterialSystemConfig || !g_pStudioRender ) + return; + + memset( &s_StudioRenderConfig, 0, sizeof(s_StudioRenderConfig) ); + + s_StudioRenderConfig.bEyeMove = !!r_eyemove.GetInt(); + s_StudioRenderConfig.fEyeShiftX = r_eyeshift_x.GetFloat(); + s_StudioRenderConfig.fEyeShiftY = r_eyeshift_y.GetFloat(); + s_StudioRenderConfig.fEyeShiftZ = r_eyeshift_z.GetFloat(); + s_StudioRenderConfig.fEyeSize = r_eyesize.GetFloat(); + if ( IsPC() && ( mat_softwareskin.GetInt() || ShouldDrawInWireFrameMode() ) ) + { + s_StudioRenderConfig.bSoftwareSkin = true; + } + else + { + s_StudioRenderConfig.bSoftwareSkin = false; + } + s_StudioRenderConfig.bNoHardware = !!r_nohw.GetInt(); + s_StudioRenderConfig.bNoSoftware = !!r_nosw.GetInt(); + s_StudioRenderConfig.bTeeth = !!r_teeth.GetInt(); + s_StudioRenderConfig.drawEntities = r_drawentities.GetInt(); + s_StudioRenderConfig.bFlex = !!r_flex.GetInt(); + s_StudioRenderConfig.bEyes = !!r_eyes.GetInt(); + s_StudioRenderConfig.bWireframe = ShouldDrawInWireFrameMode(); + s_StudioRenderConfig.bDrawNormals = mat_normals.GetBool(); + s_StudioRenderConfig.skin = r_skin.GetInt(); + s_StudioRenderConfig.maxDecalsPerModel = r_maxmodeldecal.GetInt(); + s_StudioRenderConfig.bWireframeDecals = r_modelwireframedecal.GetInt() != 0; + + s_StudioRenderConfig.fullbright = g_pMaterialSystemConfig->nFullbright; + s_StudioRenderConfig.bSoftwareLighting = g_pMaterialSystemConfig->bSoftwareLighting; + + s_StudioRenderConfig.bShowEnvCubemapOnly = r_showenvcubemap.GetInt() ? true : false; + s_StudioRenderConfig.fEyeGlintPixelWidthLODThreshold = r_eyeglintlodpixels.GetFloat(); + + g_pStudioRender->UpdateConfig( s_StudioRenderConfig ); +} + +void R_InitStudio( void ) +{ +#ifndef SWDS + R_StudioInitLightingCache(); +#endif +} + +//----------------------------------------------------------------------------- +// Converts world lights to materialsystem lights +//----------------------------------------------------------------------------- + +#define MIN_LIGHT_VALUE 0.03f + +bool WorldLightToMaterialLight( dworldlight_t* pWorldLight, LightDesc_t& light ) +{ + // BAD + light.m_Attenuation0 = 0.0f; + light.m_Attenuation1 = 0.0f; + light.m_Attenuation2 = 0.0f; + + switch(pWorldLight->type) + { + case emit_spotlight: + light.m_Type = MATERIAL_LIGHT_SPOT; + light.m_Attenuation0 = pWorldLight->constant_attn; + light.m_Attenuation1 = pWorldLight->linear_attn; + light.m_Attenuation2 = pWorldLight->quadratic_attn; + light.m_Theta = 2.0 * acos( pWorldLight->stopdot ); + light.m_Phi = 2.0 * acos( pWorldLight->stopdot2 ); + light.m_ThetaDot = pWorldLight->stopdot; + light.m_PhiDot = pWorldLight->stopdot2; + light.m_Falloff = pWorldLight->exponent ? pWorldLight->exponent : 1.0f; + break; + + case emit_surface: + // A 180 degree spotlight + light.m_Type = MATERIAL_LIGHT_SPOT; + light.m_Attenuation2 = 1.0; + light.m_Theta = M_PI; + light.m_Phi = M_PI; + light.m_ThetaDot = 0.0f; + light.m_PhiDot = 0.0f; + light.m_Falloff = 1.0f; + break; + + case emit_point: + light.m_Type = MATERIAL_LIGHT_POINT; + light.m_Attenuation0 = pWorldLight->constant_attn; + light.m_Attenuation1 = pWorldLight->linear_attn; + light.m_Attenuation2 = pWorldLight->quadratic_attn; + break; + + case emit_skylight: + light.m_Type = MATERIAL_LIGHT_DIRECTIONAL; + break; + + // NOTE: Can't do quake lights in hardware (x-r factor) + case emit_quakelight: // not supported + case emit_skyambient: // doesn't factor into local lighting + // skip these + return false; + } + + // No attenuation case.. + if ((light.m_Attenuation0 == 0.0f) && (light.m_Attenuation1 == 0.0f) && + (light.m_Attenuation2 == 0.0f)) + { + light.m_Attenuation0 = 1.0f; + } + + // renormalize light intensity... + memcpy( &light.m_Position, &pWorldLight->origin, 3 * sizeof(float) ); + memcpy( &light.m_Direction, &pWorldLight->normal, 3 * sizeof(float) ); + light.m_Color[0] = pWorldLight->intensity[0]; + light.m_Color[1] = pWorldLight->intensity[1]; + light.m_Color[2] = pWorldLight->intensity[2]; + + // Make it stop when the lighting gets to min%... + float intensity = sqrtf( DotProduct( light.m_Color, light.m_Color ) ); + + // Compute the light range based on attenuation factors + if (pWorldLight->radius != 0) + { + light.m_Range = pWorldLight->radius; + } + else + { + // FALLBACK: older lights use this + if (light.m_Attenuation2 == 0.0f) + { + if (light.m_Attenuation1 == 0.0f) + { + light.m_Range = sqrtf(FLT_MAX); + } + else + { + light.m_Range = (intensity / MIN_LIGHT_VALUE - light.m_Attenuation0) / light.m_Attenuation1; + } + } + else + { + float a = light.m_Attenuation2; + float b = light.m_Attenuation1; + float c = light.m_Attenuation0 - intensity / MIN_LIGHT_VALUE; + float discrim = b * b - 4 * a * c; + if (discrim < 0.0f) + light.m_Range = sqrtf(FLT_MAX); + else + { + light.m_Range = (-b + sqrtf(discrim)) / (2.0f * a); + if (light.m_Range < 0) + light.m_Range = 0; + } + } + } + light.m_Flags = LIGHTTYPE_OPTIMIZATIONFLAGS_DERIVED_VALUES_CALCED; + if( light.m_Attenuation0 != 0.0f ) + { + light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; + } + if( light.m_Attenuation1 != 0.0f ) + { + light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; + } + if( light.m_Attenuation2 != 0.0f ) + { + light.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Sets the hardware lighting state +//----------------------------------------------------------------------------- + +static void R_SetNonAmbientLightingState( int numLights, dworldlight_t *locallight[MAXLOCALLIGHTS], + int *pNumLightDescs, LightDesc_t *pLightDescs, bool bUpdateStudioRenderLights ) +{ + Assert( numLights >= 0 && numLights <= MAXLOCALLIGHTS ); + + // convert dworldlight_t's to LightDesc_t's and send 'em down to g_pStudioRender-> + *pNumLightDescs = 0; + + LightDesc_t *pLightDesc; + for ( int i = 0; i < numLights; i++) + { + pLightDesc = &pLightDescs[*pNumLightDescs]; + if (!WorldLightToMaterialLight( locallight[i], *pLightDesc )) + continue; + + // Apply lightstyle + float bias = LightStyleValue( locallight[i]->style ); + + // Deal with overbrighting + bias + pLightDesc->m_Color[0] *= bias; + pLightDesc->m_Color[1] *= bias; + pLightDesc->m_Color[2] *= bias; + + *pNumLightDescs += 1; + Assert( *pNumLightDescs <= MAXLOCALLIGHTS ); + } + + if ( bUpdateStudioRenderLights ) + { + g_pStudioRender->SetLocalLights( *pNumLightDescs, pLightDescs ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the center of the studio model for illumination purposes +//----------------------------------------------------------------------------- +void R_ComputeLightingOrigin( IClientRenderable *pRenderable, studiohdr_t* pStudioHdr, const matrix3x4_t &matrix, Vector& center ) +{ + int nAttachmentIndex = pStudioHdr->IllumPositionAttachmentIndex(); + if ( nAttachmentIndex <= 0 ) + { + VectorTransform( pStudioHdr->illumposition, matrix, center ); + } + else + { + matrix3x4_t attachment; + pRenderable->GetAttachment( nAttachmentIndex, attachment ); + VectorTransform( pStudioHdr->illumposition, attachment, center ); + } +} + + + +#if 0 +// garymct - leave this in here for now. . we might need this for bumped models +void R_StudioCalculateVirtualLightAndLightCube( Vector& mid, Vector& virtualLightPosition, + Vector& virtualLightColor, Vector* lightBoxColor ) +{ + int i, j; + Vector delta; + float dist2, ratio; + byte *pvis; + float t; + static ConVar bumpLightBlendRatioMin( "bump_light_blend_ratio_min", "0.00002" ); + static ConVar bumpLightBlendRatioMax( "bump_light_blend_ratio_max", "0.00004" ); + + if ( g_pMaterialSystemConfig->nFullbright == 1 ) + return; + + VectorClear( virtualLightPosition ); + VectorClear( virtualLightColor ); + for( i = 0; i < 6; i++ ) + { + VectorClear( lightBoxColor[i] ); + } + byte pvs[MAX_MAP_LEAFS/8]; + pvis = CM_Vis( pvs, sizeof(pvs), CM_LeafCluster( CM_PointLeafnum( mid ), DVIS_PVS ); + + float sumBumpBlendParam = 0; + for (i = 0; i < host_state.worldbrush->numworldlights; i++) + { + dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; + + if (wl->cluster < 0) + continue; + + // only do it if the entity can see into the lights leaf + if (!BIT_SET( pvis, (wl->cluster))) + continue; + + // hack: for this test, only deal with point light sources. + if( wl->type != emit_point ) + continue; + + // check distance + VectorSubtract( wl->origin, mid, delta ); + dist2 = DotProduct( delta, delta ); + + ratio = R_WorldLightDistanceFalloff( wl, delta ); + + VectorNormalize( delta ); + + ratio = ratio * R_WorldLightAngle( wl, wl->normal, delta, delta ); + + float bumpBlendParam; // 0.0 = all cube, 1.0 = all bump + + // lerp + bumpBlendParam = + ( ratio - bumpLightBlendRatioMin.GetFloat() ) / + ( bumpLightBlendRatioMax.GetFloat() - bumpLightBlendRatioMin.GetFloat() ); + + if( bumpBlendParam > 0.0 ) + { + // Get the bit that goes into the bump light + sumBumpBlendParam += bumpBlendParam; + VectorMA( virtualLightPosition, bumpBlendParam, wl->origin, virtualLightPosition ); + VectorMA( virtualLightColor, bumpBlendParam, wl->intensity, virtualLightColor ); + } + + if( bumpBlendParam < 1.0f ) + { + // Get the bit that goes into the cube + float cubeBlendParam; + cubeBlendParam = 1.0f - bumpBlendParam; + if( cubeBlendParam < 0.0f ) + { + cubeBlendParam = 0.0f; + } + for (j = 0; j < numBoxDir; j++) + { + t = DotProduct( r_boxdir[j], delta ); + if (t > 0) + { + VectorMA( lightBoxColor[j], ratio * t * cubeBlendParam, wl->intensity, lightBoxColor[j] ); + } + } + } + } + // Get the final virtual light position and color. + VectorMultiply( virtualLightPosition, 1.0f / sumBumpBlendParam, virtualLightPosition ); + VectorMultiply( virtualLightColor, 1.0f / sumBumpBlendParam, virtualLightColor ); +} +#endif + + +// TODO: move cone calcs to position +// TODO: cone clipping calc's wont work for boxlight since the player asks for a single point. Not sure what the volume is. +float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta, bool bNoRadiusCheck ) +{ + float falloff; + + switch (wl->type) + { + case emit_surface: +#if 1 + // Cull out stuff that's too far + if (wl->radius != 0) + { + if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); +#else + // 1/r*r + falloff = DotProduct( delta, delta ); + if (falloff < 1) + return 1.f; + else + return 1.f / falloff; +#endif + + break; + + case emit_skylight: + return 1.f; + break; + + case emit_quakelight: + // X - r; + falloff = wl->linear_attn - FastSqrt( DotProduct( delta, delta ) ); + if (falloff < 0) + return 0.f; + + return falloff; + break; + + case emit_skyambient: + return 1.f; + break; + + case emit_point: + case emit_spotlight: // directional & positional + { + float dist2, dist; + + dist2 = DotProduct( delta, delta ); + dist = FastSqrt( dist2 ); + + // Cull out stuff that's too far + if (!bNoRadiusCheck && (wl->radius != 0) && (dist > wl->radius)) + return 0.f; + + return 1.f / (wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2); + } + + break; + default: + // Bug: need to return an error + break; + } + return 1.f; +} + +/* + light_normal (lights normal translated to same space as other normals) + surface_normal + light_direction_normal | (light_pos - vertex_pos) | +*/ +float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) +{ + float dot, dot2, ratio = 0; + + switch (wl->type) + { + case emit_surface: + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + + dot2 = -DotProduct (delta, lnormal); + if (dot2 <= ON_EPSILON/10) + return 0; // behind light surface + + return dot * dot2; + + case emit_point: + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + return dot; + + case emit_spotlight: +// return 1.0; // !!! + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + + dot2 = -DotProduct (delta, lnormal); + if (dot2 <= wl->stopdot2) + return 0; // outside light cone + + ratio = dot; + if (dot2 >= wl->stopdot) + return ratio; // inside inner cone + + if ((wl->exponent == 1) || (wl->exponent == 0)) + { + ratio *= (dot2 - wl->stopdot2) / (wl->stopdot - wl->stopdot2); + } + else + { + ratio *= pow((dot2 - wl->stopdot2) / (wl->stopdot - wl->stopdot2), wl->exponent ); + } + return ratio; + + case emit_skylight: + dot2 = -DotProduct( snormal, lnormal ); + if (dot2 < 0) + return 0; + return dot2; + + case emit_quakelight: + // linear falloff + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + return dot; + + case emit_skyambient: + // not supported + return 1; + + default: + // Bug: need to return an error + break; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Allocator for color mesh vertex buffers (for use with static props only). +// It uses a trivial allocation scheme, which assumes that allocations and +// deallocations are not interleaved (you do all allocs, then all deallocs). +//----------------------------------------------------------------------------- +class CPooledVBAllocator_ColorMesh : public IPooledVBAllocator +{ +public: + + CPooledVBAllocator_ColorMesh(); + virtual ~CPooledVBAllocator_ColorMesh(); + + // Allocate the shared mesh (vertex buffer) + virtual bool Init( VertexFormat_t format, int numVerts ); + // Free the shared mesh (after Deallocate is called for all sub-allocs) + virtual void Clear(); + + // Get the shared mesh (vertex buffer) from which sub-allocations are made + virtual IMesh *GetSharedMesh() { return m_pMesh; } + + // Get a pointer to the start of the vertex buffer data + virtual void *GetVertexBufferBase() { return m_pVertexBufferBase; } + virtual int GetNumVertsAllocated() { return m_totalVerts; } + + // Allocate a sub-range of 'numVerts' from free space in the shared vertex buffer + // (returns the byte offset from the start of the VB to the new allocation) + virtual int Allocate( int numVerts ); + // Deallocate an existing allocation + virtual void Deallocate( int offset, int numVerts ); + +private: + + // Assert/warn that the allocator is in a clear/empty state (returns FALSE if not) + bool CheckIsClear( void ); + + IMesh *m_pMesh; // The shared mesh (vertex buffer) from which sub-allocations are made + void *m_pVertexBufferBase; // A pointer to the start of the vertex buffer data + int m_totalVerts; // The number of verts in the shared vertex buffer + int m_vertexSize; // The stride of the shared vertex buffer + + int m_numAllocations; // The number of extant allocations + int m_numVertsAllocated; // The number of vertices in extant allocations + int m_nextFreeOffset; // The offset to be returned by the next call to Allocate() + // (incremented as a simple stack) + bool m_bStartedDeallocation; // This is set when Deallocate() is called for the first time, + // at which point Allocate() cannot be called again until all + // extant allocations have been deallocated. +}; + +struct colormeshparams_t +{ + int m_nMeshes; + int m_nTotalVertexes; + // Given memory alignment (VBs must be 4-KB aligned on X360, for example), it can be more efficient + // to allocate many color meshes out of a single shared vertex buffer (using vertex 'stream offset') + IPooledVBAllocator *m_pPooledVBAllocator; + int m_nVertexes[256]; + FileNameHandle_t m_fnHandle; +}; + +class CColorMeshData +{ +public: + void DestroyResource() + { + g_pFileSystem->AsyncFinish( m_hAsyncControlVertex, true ); + g_pFileSystem->AsyncRelease( m_hAsyncControlVertex ); + + g_pFileSystem->AsyncFinish( m_hAsyncControlTexel, true ); + g_pFileSystem->AsyncRelease( m_hAsyncControlTexel ); + + // release the array of meshes + CMatRenderContextPtr pRenderContext( materials ); + + for ( int i=0; i<m_nMeshes; i++ ) + { + if ( m_pMeshInfos[i].m_pPooledVBAllocator ) + { + // Let the pooling allocator dealloc this sub-range of the shared vertex buffer + m_pMeshInfos[i].m_pPooledVBAllocator->Deallocate( m_pMeshInfos[i].m_nVertOffsetInBytes, m_pMeshInfos[i].m_nNumVerts ); + } + else + { + // Free this standalone mesh + pRenderContext->DestroyStaticMesh( m_pMeshInfos[i].m_pMesh ); + } + + if (m_pMeshInfos[i].m_pLightmap) + { + m_pMeshInfos[i].m_pLightmap->Release(); + m_pMeshInfos[i].m_pLightmap = NULL; + } + + if (m_pMeshInfos[i].m_pLightmapData) + { + delete [] m_pMeshInfos[i].m_pLightmapData->m_pTexelData; + delete m_pMeshInfos[i].m_pLightmapData; + } + } + + + delete [] m_pMeshInfos; + delete [] m_ppTargets; + delete this; + } + + CColorMeshData *GetData() + { + return this; + } + + unsigned int Size() + { + // TODO: This is wrong because we don't currently account for the size of the textures we create. + // However, that data isn't available until way after this query is made, so just live with + // this for now I guess? + return m_nTotalSize; + } + + static CColorMeshData *CreateResource( const colormeshparams_t ¶ms ) + { + CColorMeshData *data = new CColorMeshData; + + data->m_bHasInvalidVB = false; + data->m_bColorMeshValid = false; + data->m_bColorTextureValid = false; + data->m_bColorTextureCreated = false; + data->m_bNeedsRetry = false; + data->m_hAsyncControlVertex = NULL; + data->m_hAsyncControlTexel = NULL; + data->m_fnHandle = params.m_fnHandle; + + data->m_nTotalSize = params.m_nMeshes * sizeof( IMesh* ) + params.m_nTotalVertexes * 4; + data->m_nMeshes = params.m_nMeshes; + data->m_pMeshInfos = new ColorMeshInfo_t[params.m_nMeshes]; + Q_memset( data->m_pMeshInfos, 0, params.m_nMeshes*sizeof( ColorMeshInfo_t ) ); + data->m_ppTargets = new unsigned char *[params.m_nMeshes]; + + CMeshBuilder meshBuilder; + CMatRenderContextPtr pRenderContext( materials ); + + for ( int i=0; i<params.m_nMeshes; i++ ) + { + VertexFormat_t vertexFormat = VERTEX_SPECULAR; + + data->m_pMeshInfos[i].m_pMesh = NULL; + data->m_pMeshInfos[i].m_pPooledVBAllocator = params.m_pPooledVBAllocator; + data->m_pMeshInfos[i].m_nVertOffsetInBytes = 0; + data->m_pMeshInfos[i].m_nNumVerts = params.m_nVertexes[i]; + data->m_pMeshInfos[i].m_pLightmapData = NULL; + data->m_pMeshInfos[i].m_pLightmap = NULL; + + if ( params.m_pPooledVBAllocator != NULL ) + { + // Allocate a portion of a single, shared VB for each color mesh + data->m_pMeshInfos[i].m_nVertOffsetInBytes = params.m_pPooledVBAllocator->Allocate( params.m_nVertexes[i] ); + + if ( data->m_pMeshInfos[i].m_nVertOffsetInBytes == -1 ) + { + // Failed (fall back to regular allocations) + data->m_pMeshInfos[i].m_pPooledVBAllocator = NULL; + data->m_pMeshInfos[i].m_nVertOffsetInBytes = 0; + } + else + { + // Set up the mesh+data pointers + data->m_pMeshInfos[i].m_pMesh = params.m_pPooledVBAllocator->GetSharedMesh(); + data->m_ppTargets[i] = ( (unsigned char *)params.m_pPooledVBAllocator->GetVertexBufferBase() ) + data->m_pMeshInfos[i].m_nVertOffsetInBytes; + } + } + + if ( data->m_pMeshInfos[i].m_pMesh == NULL ) + { + if ( g_VBAllocTracker ) + g_VBAllocTracker->TrackMeshAllocations( "CColorMeshData::CreateResource" ); + + // Allocate a standalone VB per color mesh + data->m_pMeshInfos[i].m_pMesh = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_COLOR ); + + if ( g_VBAllocTracker ) + g_VBAllocTracker->TrackMeshAllocations( NULL ); + } + + Assert( data->m_pMeshInfos[i].m_pMesh ); + if ( !data->m_pMeshInfos[i].m_pMesh ) + { + data->DestroyResource(); + data = NULL; + break; + } + } + return data; + } + + static unsigned int EstimatedSize( const colormeshparams_t ¶ms ) + { + // each vertex is a 4 byte color + return params.m_nMeshes * sizeof( IMesh* ) + params.m_nTotalVertexes * 4; + } + + int m_nMeshes; + ColorMeshInfo_t *m_pMeshInfos; + unsigned char **m_ppTargets; + unsigned int m_nTotalSize; + FSAsyncControl_t m_hAsyncControlVertex; + FSAsyncControl_t m_hAsyncControlTexel; + unsigned int m_bHasInvalidVB : 1; + unsigned int m_bColorMeshValid : 1; + unsigned int m_bColorTextureValid : 1; // Whether the texture data is valid, but not necessarily created + unsigned int m_bColorTextureCreated : 1; // Whether the texture data has actually been created. + unsigned int m_bNeedsRetry : 1; + + FileNameHandle_t m_fnHandle; +}; + +//----------------------------------------------------------------------------- +// +// Implementation of IVModelRender +// +//----------------------------------------------------------------------------- + +// UNDONE: Move this to hud export code, subsume previous functions +class CModelRender : public IVModelRender, + public CManagedDataCacheClient< CColorMeshData, colormeshparams_t > +{ +public: + // members of the IVModelRender interface + virtual void ForcedMaterialOverride( IMaterial *newMaterial, OverrideType_t nOverrideType = OVERRIDE_NORMAL ); + virtual int DrawModel( + int flags, IClientRenderable *cliententity, + ModelInstanceHandle_t instance, int entity_index, const model_t *model, + const Vector& origin, QAngle const& angles, + int skin, int body, int hitboxset, + const matrix3x4_t* pModelToWorld, + const matrix3x4_t *pLightingOffset ); + + virtual void SetViewTarget( const CStudioHdr *pStudioHdr, int nBodyIndex, const Vector& target ); + + // Creates, destroys instance data to be associated with the model + virtual ModelInstanceHandle_t CreateInstance( IClientRenderable *pRenderable, LightCacheHandle_t* pHandle ); + virtual void SetStaticLighting( ModelInstanceHandle_t handle, LightCacheHandle_t* pCache ); + virtual LightCacheHandle_t GetStaticLighting( ModelInstanceHandle_t handle ); + virtual void DestroyInstance( ModelInstanceHandle_t handle ); + virtual bool ChangeInstance( ModelInstanceHandle_t handle, IClientRenderable *pRenderable ); + + // Creates a decal on a model instance by doing a planar projection + // along the ray. The material is the decal material, the radius is the + // radius of the decal to create. + virtual void AddDecal( ModelInstanceHandle_t handle, Ray_t const& ray, + const Vector& decalUp, int decalIndex, int body, bool noPokethru = false, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); + virtual void AddColoredDecal( ModelInstanceHandle_t handle, Ray_t const& ray, + const Vector& decalUp, int decalIndex, int body, Color cColor, bool noPokethru = false, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); + + virtual void GetMaterialOverride( IMaterial** ppOutForcedMaterial, OverrideType_t* pOutOverrideType ); + + // Removes all the decals on a model instance + virtual void RemoveAllDecals( ModelInstanceHandle_t handle ); + + // Remove all decals from all models + virtual void RemoveAllDecalsFromAllModels(); + + // Shadow rendering (render-to-texture) + virtual matrix3x4_t* DrawModelShadowSetup( IClientRenderable *pRenderable, int body, int skin, DrawModelInfo_t *pInfo, matrix3x4_t *pBoneToWorld ); + virtual void DrawModelShadow( IClientRenderable *pRenderable, const DrawModelInfo_t &info, matrix3x4_t *pBoneToWorld ); + + // Used to allow the shadow mgr to manage a list of shadows per model + unsigned short& FirstShadowOnModelInstance( ModelInstanceHandle_t handle ) { return m_ModelInstances[handle].m_FirstShadow; } + + // This gets called when overbright, etc gets changed to recompute static prop lighting. + virtual bool RecomputeStaticLighting( ModelInstanceHandle_t handle ); + + // Handlers for alt-tab + virtual void ReleaseAllStaticPropColorData( void ); + virtual void RestoreAllStaticPropColorData( void ); + + // Extended version of drawmodel + virtual bool DrawModelSetup( ModelRenderInfo_t &pInfo, DrawModelState_t *pState, matrix3x4_t *pBoneToWorld, matrix3x4_t** ppBoneToWorldOut ); + virtual int DrawModelEx( ModelRenderInfo_t &pInfo ); + virtual int DrawModelExStaticProp( ModelRenderInfo_t &pInfo ); + virtual int DrawStaticPropArrayFast( StaticPropRenderInfo_t *pProps, int count, bool bShadowDepth ); + + // Sets up lighting context for a point in space + virtual void SetupLighting( const Vector &vecCenter ); + virtual void SuppressEngineLighting( bool bSuppress ); + + inline vertexFileHeader_t *CacheVertexData() { return g_pMDLCache->GetVertexData( (MDLHandle_t)(int)m_pStudioHdr->virtualModel&0xffff ); } + + bool Init(); + void Shutdown(); + + bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ); + + struct staticPropAsyncContext_t + { + DataCacheHandle_t m_ColorMeshHandle; + CColorMeshData *m_pColorMeshData; + int m_nMeshes; + unsigned int m_nRootLOD; + char m_szFilenameVertex[MAX_PATH]; + char m_szFilenameTexel[MAX_PATH]; + }; + + + void StaticPropColorMeshCallback( void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus ); + void StaticPropColorTexelCallback(void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus); + + + // 360 holds onto static prop color meshes during same map transitions + void PurgeCachedStaticPropColorData(); + bool IsStaticPropColorDataCached( const char *pName ); + DataCacheHandle_t GetCachedStaticPropColorData( const char *pName ); + + virtual void SetupColorMeshes( int nTotalVerts ); + +private: + enum + { + CURRENT_LIGHTING_UNINITIALIZED = -999999 + }; + + enum ModelInstanceFlags_t + { + MODEL_INSTANCE_HAS_STATIC_LIGHTING = 0x1, + MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR = 0x2, + MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD = 0x4, + MODEL_INSTANCE_HAS_COLOR_DATA = 0x8 + }; + + struct ModelInstance_t + { + IClientRenderable* m_pRenderable; + + // Need to store off the model. When it changes, we lose all instance data.. + model_t* m_pModel; + StudioDecalHandle_t m_DecalHandle; + + // Stores off the current lighting state + LightingState_t m_CurrentLightingState; + LightingState_t m_AmbientLightingState; + Vector m_flLightIntensity[MAXLOCALLIGHTS]; + float m_flLightingTime; + + // First shadow projected onto the model + unsigned short m_FirstShadow; + unsigned short m_nFlags; + + // Static lighting + LightCacheHandle_t m_LightCacheHandle; + + // Color mesh managed by cache + DataCacheHandle_t m_ColorMeshHandle; + }; + + // Sets up the render state for a model + matrix3x4_t* SetupModelState( IClientRenderable *pRenderable ); + + int ComputeLOD( const ModelRenderInfo_t &info, studiohwdata_t *pStudioHWData ); + + void DrawModelExecute( const DrawModelState_t &state, const ModelRenderInfo_t &pInfo, matrix3x4_t *pCustomBoneToWorld = NULL ); + + void InitColormeshParams( ModelInstance_t &instance, studiohwdata_t *pStudioHWData, colormeshparams_t *pColorMeshParams ); + CColorMeshData *FindOrCreateStaticPropColorData( ModelInstanceHandle_t handle ); + void DestroyStaticPropColorData( ModelInstanceHandle_t handle ); + bool UpdateStaticPropColorData( IHandleEntity *pEnt, ModelInstanceHandle_t handle ); + void ProtectColorDataIfQueued( DataCacheHandle_t ); + + void ValidateStaticPropColorData( ModelInstanceHandle_t handle ); + bool LoadStaticPropColorData( IHandleEntity *pProp, DataCacheHandle_t colorMeshHandle, studiohwdata_t *pStudioHWData ); + + // Returns true if the model instance is valid + bool IsModelInstanceValid( ModelInstanceHandle_t handle ); + + void DebugDrawLightingOrigin( const DrawModelState_t& state, const ModelRenderInfo_t &pInfo ); + + LightingState_t *TimeAverageLightingState( ModelInstanceHandle_t handle, + LightingState_t *pLightingState, int nEntIndex, const Vector *pLightingOrigin ); + + // Cause the current lighting state to match the given one + void SnapCurrentLightingState( ModelInstance_t &inst, LightingState_t *pLightingState ); + + // Sets up lighting state for rendering + void StudioSetupLighting( const DrawModelState_t &state, const Vector& absEntCenter, + LightCacheHandle_t* pLightcache, bool bVertexLit, bool bNeedsEnvCubemap, bool &bStaticLighting, + DrawModelInfo_t &drawInfo, const ModelRenderInfo_t &pInfo, int drawFlags ); + + // Time average the ambient term + void TimeAverageAmbientLight( LightingState_t &actualLightingState, ModelInstance_t &inst, + float flAttenFactor, LightingState_t *pLightingState, const Vector *pLightingOrigin ); + + // Old-style computation of vertex lighting + void ComputeModelVertexLightingOld( mstudiomodel_t *pModel, + matrix3x4_t& matrix, const LightingState_t &lightingState, color24 *pLighting, + bool bUseConstDirLighting, float flConstDirLightAmount ); + + // New-style computation of vertex lighting + void ComputeModelVertexLighting( IHandleEntity *pProp, + mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD, + matrix3x4_t& matrix, Vector4D *pTempMem, color24 *pLighting ); + + // Internal Decal + void AddDecalInternal( ModelInstanceHandle_t handle, Ray_t const& ray, const Vector& decalUp, int decalIndex, int body, bool bUseColor, Color cColor, bool noPokeThru, int maxLODToDecal); + + // Model instance data + CUtlLinkedList< ModelInstance_t, ModelInstanceHandle_t > m_ModelInstances; + + // current active model + studiohdr_t *m_pStudioHdr; + + bool m_bSuppressEngineLighting; + + CUtlDict< DataCacheHandle_t, int > m_CachedStaticPropColorData; + CThreadFastMutex m_CachedStaticPropMutex; + + // Allocator for static prop color mesh vertex buffers (all are pooled into one VB) + CPooledVBAllocator_ColorMesh m_colorMeshVBAllocator; +}; + + +static CModelRender s_ModelRender; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CModelRender, IVModelRender, VENGINE_HUDMODEL_INTERFACE_VERSION, s_ModelRender ); +IVModelRender* modelrender = &s_ModelRender; + +//----------------------------------------------------------------------------- +// Resource loading for static prop lighting +//----------------------------------------------------------------------------- +class CResourcePreloadPropLighting : public CResourcePreload +{ + virtual bool CreateResource( const char *pName ) + { + if ( !r_proplightingfromdisk.GetBool() ) + { + // do nothing, not an error + return true; + } + + char szBasename[MAX_PATH]; + char szFilename[MAX_PATH]; + V_FileBase( pName, szBasename, sizeof( szBasename ) ); + V_snprintf( szFilename, sizeof( szFilename ), "%s%s.vhv", szBasename, GetPlatformExt() ); + + // static props have the same name across maps + // can check if loading the same map and early out if data present + if ( g_pQueuedLoader->IsSameMapLoading() && s_ModelRender.IsStaticPropColorDataCached( szFilename ) ) + { + // same map is loading, all disk prop lighting was left in the cache + // otherwise the pre-purge operation below will do the cleanup + return true; + } + + // create an anonymous job to get the lighting data in memory, claim during static prop instancing + LoaderJob_t loaderJob; + loaderJob.m_pFilename = szFilename; + loaderJob.m_pPathID = "GAME"; + loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + g_pQueuedLoader->AddJob( &loaderJob ); + return true; + } + + //----------------------------------------------------------------------------- + // Pre purge operation before i/o commences + //----------------------------------------------------------------------------- + virtual void PurgeUnreferencedResources() + { + if ( g_pQueuedLoader->IsSameMapLoading() ) + { + // do nothing, same map is loading, correct disk prop lighting will still be in data cache + return; + } + + // Map is different, need to purge any existing disk prop lighting + // before anonymous i/o commences, otherwise 2x memory usage + s_ModelRender.PurgeCachedStaticPropColorData(); + } + + virtual void PurgeAll() + { + s_ModelRender.PurgeCachedStaticPropColorData(); + } +}; +static CResourcePreloadPropLighting s_ResourcePreloadPropLighting; + +//----------------------------------------------------------------------------- +// Init, shutdown studiorender +//----------------------------------------------------------------------------- +void InitStudioRender( void ) +{ + UpdateStudioRenderConfig(); + s_ModelRender.Init(); +} + +void ShutdownStudioRender( void ) +{ + s_ModelRender.Shutdown(); +} + +//----------------------------------------------------------------------------- +// Hook needed for shadows to work +//----------------------------------------------------------------------------- +unsigned short& FirstShadowOnModelInstance( ModelInstanceHandle_t handle ) +{ + return s_ModelRender.FirstShadowOnModelInstance( handle ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void R_RemoveAllDecalsFromAllModels() +{ + s_ModelRender.RemoveAllDecalsFromAllModels(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CModelRender::Init() +{ + // start a managed section in the cache + CCacheClientBaseClass::Init( g_pDataCache, "ColorMesh" ); + + if ( IsX360() ) + { + g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_STATICPROPLIGHTING, &s_ResourcePreloadPropLighting ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CModelRender::Shutdown() +{ + // end the managed section + CCacheClientBaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Used by the client to allow it to set lighting state instead of this code +//----------------------------------------------------------------------------- +void CModelRender::SuppressEngineLighting( bool bSuppress ) +{ + m_bSuppressEngineLighting = bSuppress; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CModelRender::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ) +{ + CColorMeshData *pColorMeshData = (CColorMeshData *)pItem; + g_pFileSystem->String( pColorMeshData->m_fnHandle, pDest, nMaxLen ); + return true; +} + + +//----------------------------------------------------------------------------- +// Cause the current lighting state to match the given one +//----------------------------------------------------------------------------- +void CModelRender::SnapCurrentLightingState( ModelInstance_t &inst, LightingState_t *pLightingState ) +{ + inst.m_CurrentLightingState = *pLightingState; + for ( int i = 0; i < MAXLOCALLIGHTS; ++i ) + { + if ( i < pLightingState->numlights ) + { + inst.m_flLightIntensity[i] = pLightingState->locallight[i]->intensity; + } + else + { + inst.m_flLightIntensity[i].Init( 0.0f, 0.0f, 0.0f ); + } + } + +#ifndef SWDS + inst.m_flLightingTime = cl.GetTime(); +#endif +} + + +#define AMBIENT_MAX 8.0f + +//----------------------------------------------------------------------------- +// Time average the ambient term +//----------------------------------------------------------------------------- +void CModelRender::TimeAverageAmbientLight( LightingState_t &actualLightingState, + ModelInstance_t &inst, float flAttenFactor, LightingState_t *pLightingState, const Vector *pLightingOrigin ) +{ + flAttenFactor = clamp( flAttenFactor, 0.f, 1.f ); // don't need this but alex is a coward + Vector vecDelta; + for ( int i = 0; i < 6; ++i ) + { + VectorSubtract( pLightingState->r_boxcolor[i], inst.m_CurrentLightingState.r_boxcolor[i], vecDelta ); + vecDelta *= flAttenFactor; + inst.m_CurrentLightingState.r_boxcolor[i] = pLightingState->r_boxcolor[i] - vecDelta; + +#if defined( VISUALIZE_TIME_AVERAGE ) && !defined( SWDS ) + if ( pLightingOrigin ) + { + Vector vecDir = vec3_origin; + vecDir[ i >> 1 ] = (i & 0x1) ? -1.0f : 1.0f; + CDebugOverlay::AddLineOverlay( *pLightingOrigin, *pLightingOrigin + vecDir * 20, + 255 * inst.m_CurrentLightingState.r_boxcolor[i].x, + 255 * inst.m_CurrentLightingState.r_boxcolor[i].y, + 255 * inst.m_CurrentLightingState.r_boxcolor[i].z, 255, false, 5.0f ); + + CDebugOverlay::AddLineOverlay( *pLightingOrigin + Vector(5, 5, 5), *pLightingOrigin + vecDir * 50, + 255 * pLightingState->r_boxcolor[i].x, + 255 * pLightingState->r_boxcolor[i].y, + 255 * pLightingState->r_boxcolor[i].z, 255, true, 5.0f ); + } +#endif + // haven't been able to find this rare bug which results in ambient light getting "stuck" + // on the viewmodel extremely rarely , presumably with infinities. So, mask the bug + // (hopefully) and warn by clamping. +#ifndef NDEBUG + Assert( inst.m_CurrentLightingState.r_boxcolor[i].IsValid() ); + for( int nComp = 0 ; nComp < 3; nComp++ ) + { + Assert( inst.m_CurrentLightingState.r_boxcolor[i][nComp] >= 0.0 ); + Assert( inst.m_CurrentLightingState.r_boxcolor[i][nComp] <= AMBIENT_MAX ); + } +#endif + inst.m_CurrentLightingState.r_boxcolor[i].x = clamp( inst.m_CurrentLightingState.r_boxcolor[i].x, 0.f, AMBIENT_MAX ); + inst.m_CurrentLightingState.r_boxcolor[i].y = clamp( inst.m_CurrentLightingState.r_boxcolor[i].y, 0.f, AMBIENT_MAX ); + inst.m_CurrentLightingState.r_boxcolor[i].z = clamp( inst.m_CurrentLightingState.r_boxcolor[i].z, 0.f, AMBIENT_MAX ); + } + memcpy( &actualLightingState.r_boxcolor, &inst.m_CurrentLightingState.r_boxcolor, sizeof(inst.m_CurrentLightingState.r_boxcolor) ); +} + + + + +//----------------------------------------------------------------------------- +// Do time averaging of the lighting state to avoid popping... +//----------------------------------------------------------------------------- +LightingState_t *CModelRender::TimeAverageLightingState( ModelInstanceHandle_t handle, LightingState_t *pLightingState, int nEntIndex, const Vector *pLightingOrigin ) +{ + if ( r_lightaverage.GetInt() == 0 ) + return pLightingState; + +#ifndef SWDS + + float flInterpFactor = r_lightinterp.GetFloat(); + if ( flInterpFactor == 0 ) + return pLightingState; + + if ( handle == MODEL_INSTANCE_INVALID) + return pLightingState; + + ModelInstance_t &inst = m_ModelInstances[handle]; + if ( inst.m_flLightingTime == CURRENT_LIGHTING_UNINITIALIZED ) + { + SnapCurrentLightingState( inst, pLightingState ); + return pLightingState; + } + + float dt = (cl.GetTime() - inst.m_flLightingTime); + if ( dt <= 0.0f ) + { + dt = 0.0f; + } + else + { + inst.m_flLightingTime = cl.GetTime(); + } + + static LightingState_t actualLightingState; + static dworldlight_t s_WorldLights[MAXLOCALLIGHTS]; + + // I'm creating the equation v = vf - (vf-vi)e^-at + // where vf = this frame's lighting value, vi = current time averaged lighting value + int i; + Vector vecDelta; + float flAttenFactor = exp( -flInterpFactor * dt ); + TimeAverageAmbientLight( actualLightingState, inst, flAttenFactor, pLightingState, pLightingOrigin ); + + // Max # of lights... + int nWorldLights; + if ( !g_pMaterialSystemConfig->bSoftwareLighting ) + { + nWorldLights = min( g_pMaterialSystemHardwareConfig->MaxNumLights(), r_worldlights.GetInt() ); + } + else + { + nWorldLights = r_worldlights.GetInt(); + } + + // Create a mapping of identical lights + int nMatchCount = 0; + bool pMatch[MAXLOCALLIGHTS]; + Vector pLight[MAXLOCALLIGHTS]; + dworldlight_t *pSourceLight[MAXLOCALLIGHTS]; + + memset( pMatch, 0, sizeof(pMatch) ); + for ( i = 0; i < pLightingState->numlights; ++i ) + { + // By default, assume the light doesn't match an existing light, so blend up from 0 + pLight[i].Init( 0.0f, 0.0f, 0.0f ); + int j; + for ( j = 0; j < inst.m_CurrentLightingState.numlights; ++j ) + { + if ( pLightingState->locallight[i] == inst.m_CurrentLightingState.locallight[j] ) + { + // Ok, we found a matching light, so use the intensity of that light at the moment + ++nMatchCount; + pMatch[j] = true; + pLight[i] = inst.m_flLightIntensity[j]; + break; + } + } + } + + // For the lights in the current lighting state, attenuate them toward their actual value + for ( i = 0; i < pLightingState->numlights; ++i ) + { + actualLightingState.locallight[i] = &s_WorldLights[i]; + memcpy( &s_WorldLights[i], pLightingState->locallight[i], sizeof(dworldlight_t) ); + + // Light already exists? Attenuate to it... + VectorSubtract( pLightingState->locallight[i]->intensity, pLight[i], vecDelta ); + vecDelta *= flAttenFactor; + + s_WorldLights[i].intensity = pLightingState->locallight[i]->intensity - vecDelta; + pSourceLight[i] = pLightingState->locallight[i]; + } + + // Ramp down any light we can; we may not be able to ramp them all down + int nCurrLight = pLightingState->numlights; + for ( i = 0; i < inst.m_CurrentLightingState.numlights; ++i ) + { + if ( pMatch[i] ) + continue; + + // Has it faded out to black? Then remove it. + if ( inst.m_flLightIntensity[i].LengthSqr() < 1 ) + continue; + + if ( nCurrLight >= MAXLOCALLIGHTS ) + break; + + actualLightingState.locallight[nCurrLight] = &s_WorldLights[nCurrLight]; + memcpy( &s_WorldLights[nCurrLight], inst.m_CurrentLightingState.locallight[i], sizeof(dworldlight_t) ); + + // Attenuate to black (fade out) + VectorMultiply( inst.m_flLightIntensity[i], flAttenFactor, vecDelta ); + + s_WorldLights[nCurrLight].intensity = vecDelta; + pSourceLight[nCurrLight] = inst.m_CurrentLightingState.locallight[i]; + + if (( nCurrLight >= nWorldLights ) && pLightingOrigin) + { + AddWorldLightToAmbientCube( &s_WorldLights[nCurrLight], *pLightingOrigin, actualLightingState.r_boxcolor ); + } + + ++nCurrLight; + } + + actualLightingState.numlights = min( nCurrLight, nWorldLights ); + inst.m_CurrentLightingState.numlights = nCurrLight; + + for ( i = 0; i < nCurrLight; ++i ) + { + inst.m_CurrentLightingState.locallight[i] = pSourceLight[i]; + inst.m_flLightIntensity[i] = s_WorldLights[i].intensity; + +#if defined( VISUALIZE_TIME_AVERAGE ) && !defined( SWDS ) + Vector vecColor = pSourceLight[i]->intensity; + float flMax = max( vecColor.x, vecColor.y ); + flMax = max( flMax, vecColor.z ); + if ( flMax == 0.0f ) + { + flMax = 1.0f; + } + vecColor *= 255.0f / flMax; + float flRatio = inst.m_flLightIntensity[i].Length() / pSourceLight[i]->intensity.Length(); + vecColor *= flRatio; + CDebugOverlay::AddLineOverlay( *pLightingOrigin, pSourceLight[i]->origin, + vecColor.x, vecColor.y, vecColor.z, 255, false, 5.0f ); +#endif + } + + return &actualLightingState; +#else + return pLightingState; +#endif +} + +// Ambient boost settings +static ConVar r_ambientboost( "r_ambientboost", "1", FCVAR_ARCHIVE, "Set to boost ambient term if it is totally swamped by local lights" ); +static ConVar r_ambientmin( "r_ambientmin", "0.3", FCVAR_ARCHIVE, "Threshold above which ambient cube will not boost (i.e. it's already sufficiently bright" ); +static ConVar r_ambientfraction( "r_ambientfraction", "0.1", FCVAR_CHEAT, "Fraction of direct lighting that ambient cube must be below to trigger boosting" ); +static ConVar r_ambientfactor( "r_ambientfactor", "5", FCVAR_ARCHIVE, "Boost ambient cube by no more than this factor" ); +static ConVar r_lightcachemodel ( "r_lightcachemodel", "-1", FCVAR_CHEAT, "" ); +static ConVar r_drawlightcache ("r_drawlightcache", "0", FCVAR_CHEAT, "0: off\n1: draw light cache entries\n2: draw rays\n"); + + +//----------------------------------------------------------------------------- +// Sets up lighting state for rendering +//----------------------------------------------------------------------------- +void CModelRender::StudioSetupLighting( const DrawModelState_t &state, const Vector& absEntCenter, + LightCacheHandle_t* pLightcache, bool bVertexLit, bool bNeedsEnvCubemap, bool &bStaticLighting, + DrawModelInfo_t &drawInfo, const ModelRenderInfo_t &pInfo, int drawFlags ) +{ + if ( m_bSuppressEngineLighting ) + return; + +#ifndef SWDS + ITexture *pEnvCubemapTexture = NULL; + LightingState_t lightingState; + + Vector pSaveLightPos[MAXLOCALLIGHTS]; + + Vector *pDebugLightingOrigin = NULL; + Vector vecDebugLightingOrigin = vec3_origin; + + // Cache off lighting data for rendering decals - only on dx8/dx9. + LightingState_t lightingDecalState; + + drawInfo.m_bStaticLighting = bStaticLighting && g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting(); + drawInfo.m_nLocalLightCount = 0; + + // Compute lighting origin from input + Vector vLightingOrigin( 0.0f, 0.0f, 0.0f ); + CMatRenderContextPtr pRenderContext( materials ); + if ( pInfo.pLightingOrigin ) + { + vLightingOrigin = *pInfo.pLightingOrigin; + } + else + { + vLightingOrigin = absEntCenter; + if ( pInfo.pLightingOffset ) + { + VectorTransform( absEntCenter, *pInfo.pLightingOffset, vLightingOrigin ); + } + } + + // Set the lighting origin state + pRenderContext->SetLightingOrigin( vLightingOrigin ); + + ModelInstance_t *pModelInst = NULL; + bool bHasDecals = false; + if ( pInfo.instance != m_ModelInstances.InvalidIndex() ) + { + pModelInst = &m_ModelInstances[pInfo.instance]; + if ( pModelInst ) + { + bHasDecals = ( pModelInst->m_DecalHandle != STUDIORENDER_DECAL_INVALID ); + } + } + + if ( pLightcache ) + { + // static prop case. + if ( bStaticLighting ) + { + if ( g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) + { + LightingState_t *pLightingState = NULL; + // dx8 and dx9 case. . .hardware can do baked lighting plus other dynamic lighting + // We already have the static part baked into a color mesh, so just get the dynamic stuff. + if ( StaticLightCacheAffectedByDynamicLight( *pLightcache ) ) + { + pLightingState = LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture ); + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + } + else + { + pLightingState = LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + } + lightingState = *pLightingState; + } + else + { + // dx6 and dx7 case. . .hardware can either do software lighting or baked lighting only. + if ( StaticLightCacheAffectedByDynamicLight( *pLightcache ) || + StaticLightCacheAffectedByAnimatedLightStyle( *pLightcache ) ) + { + bStaticLighting = false; + } + else if ( StaticLightCacheNeedsSwitchableLightUpdate( *pLightcache ) ) + { + // Need to rebake lighting since a switch has turned off/on. + UpdateStaticPropColorData( state.m_pRenderable->GetIClientUnknown(), pInfo.instance ); + } + } + } + + if ( !bStaticLighting ) + { + lightingState = *(LightcacheGetStatic( *pLightcache, &pEnvCubemapTexture )); + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + } + + if ( r_decalstaticprops.GetBool() && pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) + { + for ( int iCube = 0; iCube < 6; ++iCube ) + { + drawInfo.m_vecAmbientCube[iCube] = pModelInst->m_AmbientLightingState.r_boxcolor[iCube] + lightingState.r_boxcolor[iCube]; + } + + lightingDecalState.CopyLocalLights( pModelInst->m_AmbientLightingState ); + lightingDecalState.AddAllLocalLights( lightingState ); + + Assert( lightingDecalState.numlights >= 0 && lightingDecalState.numlights <= MAXLOCALLIGHTS ); + } + } + else // !pLightcache + { + vecDebugLightingOrigin = vLightingOrigin; + pDebugLightingOrigin = &vecDebugLightingOrigin; + + // If we don't have a lightcache entry, but we have bStaticLighting, that means + // that we are a prop_physics that has fallen asleep. + if ( bStaticLighting ) + { + LightcacheGetDynamic_Stats stats; + pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, + stats, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + + // Deal with all the dx6/dx7 issues (ie. can't do anything besides either baked lighting + // or software dynamic lighting. + if ( !g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) + { + if ( ( stats.m_bHasDLights || stats.m_bHasNonSwitchableLightStyles ) ) + { + // We either have a light switch, or a dynamic light. . do it in software. + // We'll reget the cache entry with different flags below. + bStaticLighting = false; + } + else if ( stats.m_bNeedsSwitchableLightStyleUpdate ) + { + // Need to rebake lighting since a switch has turned off/on. + UpdateStaticPropColorData( state.m_pRenderable->GetIClientUnknown(), pInfo.instance ); + } + } + } + + if ( !bStaticLighting ) + { + LightcacheGetDynamic_Stats stats; + + // For special r_drawlightcache mode, we only draw models containing the substring set in r_lightcachemodel + bool bDebugModel = false; + if( r_drawlightcache.GetInt() == 5 ) + { + if ( pModelInst && pModelInst->m_pModel && !pModelInst->m_pModel->strName.IsEmpty() ) + { + const char *szModelName = r_lightcachemodel.GetString(); + bDebugModel = V_stristr( pModelInst->m_pModel->strName, szModelName ) != NULL; + } + } + + pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, stats, + LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST, bDebugModel ); + + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + } + + if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) + { + for ( int i = 0; i < lightingState.numlights; ++i ) + { + pSaveLightPos[i] = lightingState.locallight[i]->origin; + VectorITransform( pSaveLightPos[i], *pInfo.pLightingOffset, lightingState.locallight[i]->origin ); + } + } + + // Cache lighting for decals. + if ( pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) + { + // Only do this on dx8/dx9. + LightcacheGetDynamic_Stats stats; + LightcacheGetDynamic( vLightingOrigin, lightingDecalState, stats, + LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST ); + + Assert( lightingDecalState.numlights >= 0 && lightingDecalState.numlights <= MAXLOCALLIGHTS); + + for ( int iCube = 0; iCube < 6; ++iCube ) + { + VectorCopy( lightingDecalState.r_boxcolor[iCube], drawInfo.m_vecAmbientCube[iCube] ); + } + + if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) + { + for ( int i = 0; i < lightingDecalState.numlights; ++i ) + { + pSaveLightPos[i] = lightingDecalState.locallight[i]->origin; + VectorITransform( pSaveLightPos[i], *pInfo.pLightingOffset, lightingDecalState.locallight[i]->origin ); + } + } + } + } + + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + + // Do time averaging of the lighting state to avoid popping... + LightingState_t *pState; + if ( !bStaticLighting && !pLightcache ) + { + pState = TimeAverageLightingState( pInfo.instance, &lightingState, pInfo.entity_index, pDebugLightingOrigin ); + } + else + { + pState = &lightingState; + } + + if ( bNeedsEnvCubemap && pEnvCubemapTexture ) + { + pRenderContext->BindLocalCubemap( pEnvCubemapTexture ); + } + + if ( g_pMaterialSystemConfig->nFullbright == 1 ) + { + pRenderContext->SetAmbientLight( 1.0, 1.0, 1.0 ); + + static Vector white[6] = + { + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + }; + + g_pStudioRender->SetAmbientLightColors( white ); + + // Disable all the lights.. + pRenderContext->DisableAllLocalLights(); + } + else if ( bVertexLit ) + { + if( drawFlags & STUDIORENDER_DRAW_ITEM_BLINK ) + { + float add = r_itemblinkmax.GetFloat() * ( FastCos( r_itemblinkrate.GetFloat() * Sys_FloatTime() ) + 1.0f ); + Vector additiveColor( add, add, add ); + static Vector temp[6]; + int i; + for( i = 0; i < 6; i++ ) + { + temp[i] = pState->r_boxcolor[i] + additiveColor; + } + g_pStudioRender->SetAmbientLightColors( temp ); + } + else + { + // If we have any lights and want to do ambient boost on this model + if ( (pState->numlights > 0) && (pInfo.pModel->flags & MODELFLAG_STUDIOHDR_AMBIENT_BOOST) && r_ambientboost.GetBool() ) + { + Vector lumCoeff( 0.3f, 0.59f, 0.11f ); + float avgCubeLuminance = 0.0f; + float minCubeLuminance = FLT_MAX; + float maxCubeLuminance = 0.0f; + + // Compute average luminance of ambient cube + for( int i = 0; i < 6; i++ ) + { + float luminance = DotProduct( pState->r_boxcolor[i], lumCoeff ); // compute luminance + minCubeLuminance = fpmin(minCubeLuminance, luminance); // min luminance + maxCubeLuminance = fpmax(maxCubeLuminance, luminance); // max luminance + avgCubeLuminance += luminance; // accumulate luminance + } + avgCubeLuminance /= 6.0f; // average luminance + + // Compute the amount of direct light reaching the center of the model (attenuated by distance) + float fDirectLight = 0.0f; + for( int i = 0; i < pState->numlights; i++ ) + { + Vector vLight = pState->locallight[i]->origin - vLightingOrigin; + float d2 = DotProduct( vLight, vLight ); + float d = sqrtf( d2 ); + float fAtten = 1.0f; + + float denom = pState->locallight[i]->constant_attn + + pState->locallight[i]->linear_attn * d + + pState->locallight[i]->quadratic_attn * d2; + + if ( denom > 0.00001f ) + { + fAtten = 1.0f / denom; + } + + Vector vLit = pState->locallight[i]->intensity * fAtten; + fDirectLight += DotProduct( vLit, lumCoeff ); + } + + // If ambient cube is sufficiently dim in absolute terms and ambient cube is swamped by direct lights + if ( avgCubeLuminance < r_ambientmin.GetFloat() && (avgCubeLuminance < (fDirectLight * r_ambientfraction.GetFloat())) ) + { + Vector vFinalAmbientCube[6]; + float fBoostFactor = min( (fDirectLight * r_ambientfraction.GetFloat()) / maxCubeLuminance, 5.f ); // boost no more than a certain factor + for( int i = 0; i < 6; i++ ) + { + vFinalAmbientCube[i] = pState->r_boxcolor[i] * fBoostFactor; + } + g_pStudioRender->SetAmbientLightColors( vFinalAmbientCube ); // Boost + } + else + { + g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); // No Boost + } + } + else // Don't bother with ambient boost, just use the ambient cube as is + { + g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); // No Boost + } + } + + pRenderContext->SetAmbientLight( 0.0, 0.0, 0.0 ); + R_SetNonAmbientLightingState( pState->numlights, pState->locallight, + &drawInfo.m_nLocalLightCount, &drawInfo.m_LocalLightDescs[0], true ); + + // Cache lighting for decals. + if( pModelInst && drawInfo.m_bStaticLighting && bHasDecals ) + { + R_SetNonAmbientLightingState( lightingDecalState.numlights, lightingDecalState.locallight, + &drawInfo.m_nLocalLightCount, &drawInfo.m_LocalLightDescs[0], false ); + } + } + + if ( pInfo.pLightingOffset && !pInfo.pLightingOrigin ) + { + for ( int i = 0; i < lightingState.numlights; ++i ) + { + lightingState.locallight[i]->origin = pSaveLightPos[i]; + } + } +#endif +} + + +//----------------------------------------------------------------------------- +// Uses this material instead of the one the model was compiled with +//----------------------------------------------------------------------------- + +// FIXME: a duplicate of what's in CEngineTool::GetLightingConditions +int GetLightingConditions( const Vector &vecLightingOrigin, Vector *pColors, int nMaxLocalLights, LightDesc_t *pLocalLights, ITexture *&pEnvCubemapTexture ) +{ +#ifndef SWDS + LightcacheGetDynamic_Stats stats; + LightingState_t state; + pEnvCubemapTexture = NULL; + pEnvCubemapTexture = LightcacheGetDynamic( vecLightingOrigin, state, stats ); + Assert( state.numlights >= 0 && state.numlights <= MAXLOCALLIGHTS ); + memcpy( pColors, state.r_boxcolor, sizeof(state.r_boxcolor) ); + + int nLightCount = 0; + for ( int i = 0; i < state.numlights; ++i ) + { + LightDesc_t *pLightDesc = &pLocalLights[nLightCount]; + if (!WorldLightToMaterialLight( state.locallight[i], *pLightDesc )) + continue; + + // Apply lightstyle + float bias = LightStyleValue( state.locallight[i]->style ); + + // Deal with overbrighting + bias + pLightDesc->m_Color[0] *= bias; + pLightDesc->m_Color[1] *= bias; + pLightDesc->m_Color[2] *= bias; + + if ( ++nLightCount >= nMaxLocalLights ) + break; + } + return nLightCount; +#endif + return 0; +} + +// FIXME: a duplicate of what's in CCDmeMdlRenderable<T>::SetUpLighting and CDmeEmitter::SetUpLighting +void CModelRender::SetupLighting( const Vector &vecCenter ) +{ +#ifndef SWDS + // Set up lighting conditions + Vector vecAmbient[6]; + Vector4D vecAmbient4D[6]; + LightDesc_t desc[2]; + ITexture *pEnvCubemapTexture = NULL; + int nLightCount = GetLightingConditions( vecCenter, vecAmbient, 2, desc, pEnvCubemapTexture ); + int nMaxLights = g_pMaterialSystemHardwareConfig->MaxNumLights(); + if( nLightCount > nMaxLights ) + { + nLightCount = nMaxLights; + } + + int i; + for( i = 0; i < 6; i++ ) + { + VectorCopy( vecAmbient[i], vecAmbient4D[i].AsVector3D() ); + vecAmbient4D[i][3] = 1.0f; + } + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->SetAmbientLightCube( vecAmbient4D ); + + if ( pEnvCubemapTexture ) + { + pRenderContext->BindLocalCubemap( pEnvCubemapTexture ); + } + + for( i = 0; i < nLightCount; i++ ) + { + LightDesc_t *pLight = &desc[i]; + pLight->m_Flags = 0; + if( pLight->m_Attenuation0 != 0.0f ) + { + pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; + } + if( pLight->m_Attenuation1 != 0.0f ) + { + pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; + } + if( pLight->m_Attenuation2 != 0.0f ) + { + pLight->m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; + } + + pRenderContext->SetLight( i, desc[i] ); + } + + for( ; i < nMaxLights; i++ ) + { + LightDesc_t disableDesc; + disableDesc.m_Type = MATERIAL_LIGHT_DISABLE; + pRenderContext->SetLight( i, disableDesc ); + } +#endif +} + + + +//----------------------------------------------------------------------------- +// Uses this material instead of the one the model was compiled with +//----------------------------------------------------------------------------- +void CModelRender::ForcedMaterialOverride( IMaterial *newMaterial, OverrideType_t nOverrideType ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + g_pStudioRender->ForcedMaterialOverride( newMaterial, nOverrideType ); +} + + +//----------------------------------------------------------------------------- +// Sets up the render state for a model +//----------------------------------------------------------------------------- +matrix3x4_t* CModelRender::SetupModelState( IClientRenderable *pRenderable ) +{ + const model_t *pModel = pRenderable->GetModel(); + if ( !pModel ) + return NULL; + + studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( const_cast<model_t*>(pModel) ); + if ( pStudioHdr->numbodyparts == 0 ) + return NULL; + + matrix3x4_t *pBoneMatrices = NULL; +#ifndef SWDS + // Set up skinning state + Assert ( pRenderable ); + { + int nBoneCount = pStudioHdr->numbones; + pBoneMatrices = g_pStudioRender->LockBoneMatrices( pStudioHdr->numbones ); + pRenderable->SetupBones( pBoneMatrices, nBoneCount, BONE_USED_BY_ANYTHING, cl.GetTime() ); // hack hack + g_pStudioRender->UnlockBoneMatrices(); + } +#endif + + return pBoneMatrices; +} + + +struct ModelDebugOverlayData_t +{ + DrawModelInfo_t m_ModelInfo; + DrawModelResults_t m_ModelResults; + Vector m_Origin; + + ModelDebugOverlayData_t() {} + +private: + ModelDebugOverlayData_t( const ModelDebugOverlayData_t &vOther ); +}; + +static CUtlVector<ModelDebugOverlayData_t> s_SavedModelInfo; + +void DrawModelDebugOverlay( const DrawModelInfo_t& info, const DrawModelResults_t &results, const Vector &origin, float r = 1.0f, float g = 1.0f, float b = 1.0f ) +{ +#ifndef SWDS + float alpha = 1; + if( r_drawmodelstatsoverlaydistance.GetFloat() == 1 ) + { + alpha = 1.f - clamp( CurrentViewOrigin().DistTo( origin ) / r_drawmodelstatsoverlaydistance.GetFloat(), 0.f, 1.f ); + } + else + { + float flDistance = CurrentViewOrigin().DistTo( origin ); + + // The view model keeps throwing up its data and it looks like garbage, so I am trying to get rid of it. + if ( flDistance < 36.0f ) + return; + + if ( flDistance > r_drawmodelstatsoverlaydistance.GetFloat() ) + return; + } + + Assert( info.m_pStudioHdr ); + Assert( info.m_pStudioHdr->pszName() ); + Assert( info.m_pHardwareData ); + float duration = 0.0f; + int lineOffset = 0; + if( !info.m_pStudioHdr || !info.m_pStudioHdr->pszName() || !info.m_pHardwareData ) + { + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, "This model has problems. . see a programmer." ); + return; + } + + char buf[1024]; + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, info.m_pStudioHdr->pszName() ); + Q_snprintf( buf, sizeof( buf ), "lod: %d/%d\n", results.m_nLODUsed+1, ( int )info.m_pHardwareData->m_NumLODs ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + Q_snprintf( buf, sizeof( buf ), "tris: %d\n", results.m_ActualTriCount ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + Q_snprintf( buf, sizeof( buf ), "hardware bones: %d\n", results.m_NumHardwareBones ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + Q_snprintf( buf, sizeof( buf ), "num batches: %d\n", results.m_NumBatches ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + Q_snprintf( buf, sizeof( buf ), "has shadow lod: %s\n", ( info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) ? "true" : "false" ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + Q_snprintf( buf, sizeof( buf ), "num materials: %d\n", results.m_NumMaterials ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + + int i; + for( i = 0; i < results.m_Materials.Count(); i++ ) + { + IMaterial *pMaterial = results.m_Materials[i]; + if( pMaterial ) + { + int numPasses = pMaterial->GetNumPasses(); + Q_snprintf( buf, sizeof( buf ), "\t%s (%d %s)\n", results.m_Materials[i]->GetName(), numPasses, numPasses > 1 ? "passes" : "pass" ); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + } + } + if( results.m_Materials.Count() > results.m_NumMaterials ) + { + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, "(Remaining materials not shown)\n" ); + } + if( r_drawmodelstatsoverlay.GetInt() == 2 ) + { + Q_snprintf( buf, sizeof( buf ), "Render Time: %0.1f ms\n", results.m_RenderTime.GetDuration().GetMillisecondsF()); + CDebugOverlay::AddTextOverlay( origin, lineOffset++, duration, r, g, b, alpha, buf ); + } + + //Q_snprintf( buf, sizeof( buf ), "Render Time: %0.1f ms\n", info.m_pClientEntity +#endif +} + +void AddModelDebugOverlay( const DrawModelInfo_t& info, const DrawModelResults_t &results, const Vector& origin ) +{ + ModelDebugOverlayData_t &tmp = s_SavedModelInfo[s_SavedModelInfo.AddToTail()]; + tmp.m_ModelInfo = info; + tmp.m_ModelResults = results; + tmp.m_Origin = origin; +} + +void ClearSaveModelDebugOverlays( void ) +{ + s_SavedModelInfo.RemoveAll(); +} + +int SavedModelInfo_Compare_f( const void *l, const void *r ) +{ + ModelDebugOverlayData_t *left = ( ModelDebugOverlayData_t * )l; + ModelDebugOverlayData_t *right = ( ModelDebugOverlayData_t * )r; + return left->m_ModelResults.m_RenderTime.GetDuration().GetSeconds() < right->m_ModelResults.m_RenderTime.GetDuration().GetSeconds(); +} + +static ConVar r_drawmodelstatsoverlaymin( "r_drawmodelstatsoverlaymin", "0.1", FCVAR_ARCHIVE, "time in milliseconds that a model must take to render before showing an overlay in r_drawmodelstatsoverlay 2" ); +static ConVar r_drawmodelstatsoverlaymax( "r_drawmodelstatsoverlaymax", "1.5", FCVAR_ARCHIVE, "time in milliseconds beyond which a model overlay is fully red in r_drawmodelstatsoverlay 2" ); + +void DrawSavedModelDebugOverlays( void ) +{ + if( s_SavedModelInfo.Count() == 0 ) + { + return; + } + float min = r_drawmodelstatsoverlaymin.GetFloat(); + float max = r_drawmodelstatsoverlaymax.GetFloat(); + float ooRange = 1.0f / ( max - min ); + + int i; + for( i = 0; i < s_SavedModelInfo.Count(); i++ ) + { + float r, g, b; + float t = s_SavedModelInfo[i].m_ModelResults.m_RenderTime.GetDuration().GetMillisecondsF(); + if( t > min ) + { + if( t >= max ) + { + r = 1.0f; g = 0.0f; b = 0.0f; + } + else + { + r = ( t - min ) * ooRange; + g = 1.0f - r; + b = 0.0f; + } + DrawModelDebugOverlay( s_SavedModelInfo[i].m_ModelInfo, s_SavedModelInfo[i].m_ModelResults, s_SavedModelInfo[i].m_Origin, r, g, b ); + } + } + ClearSaveModelDebugOverlays(); +} + +void CModelRender::DebugDrawLightingOrigin( const DrawModelState_t& state, const ModelRenderInfo_t &pInfo ) +{ +#ifndef SWDS + // determine light origin in world space + Vector illumPosition; + Vector lightOrigin; + if ( pInfo.pLightingOrigin ) + { + illumPosition = *pInfo.pLightingOrigin; + lightOrigin = illumPosition; + } + else + { + R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, illumPosition ); + lightOrigin = illumPosition; + if ( pInfo.pLightingOffset ) + { + VectorTransform( illumPosition, *pInfo.pLightingOffset, lightOrigin ); + } + } + + // draw z planar cross at lighting origin + Vector pt0; + Vector pt1; + pt0 = lightOrigin; + pt1 = lightOrigin; + pt0.x -= 4; + pt1.x += 4; + CDebugOverlay::AddLineOverlay( pt0, pt1, 0, 255, 0, 255, true, 0.0f ); + pt0 = lightOrigin; + pt1 = lightOrigin; + pt0.y -= 4; + pt1.y += 4; + CDebugOverlay::AddLineOverlay( pt0, pt1, 0, 255, 0, 255, true, 0.0f ); + + // draw lines from the light origin to the hull boundaries to identify model + Vector pt; + pt0.x = state.m_pStudioHdr->hull_min.x; + pt0.y = state.m_pStudioHdr->hull_min.y; + pt0.z = state.m_pStudioHdr->hull_min.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_min.x; + pt0.y = state.m_pStudioHdr->hull_max.y; + pt0.z = state.m_pStudioHdr->hull_min.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_max.x; + pt0.y = state.m_pStudioHdr->hull_max.y; + pt0.z = state.m_pStudioHdr->hull_min.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_max.x; + pt0.y = state.m_pStudioHdr->hull_min.y; + pt0.z = state.m_pStudioHdr->hull_min.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + + pt0.x = state.m_pStudioHdr->hull_min.x; + pt0.y = state.m_pStudioHdr->hull_min.y; + pt0.z = state.m_pStudioHdr->hull_max.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_min.x; + pt0.y = state.m_pStudioHdr->hull_max.y; + pt0.z = state.m_pStudioHdr->hull_max.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_max.x; + pt0.y = state.m_pStudioHdr->hull_max.y; + pt0.z = state.m_pStudioHdr->hull_max.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); + pt0.x = state.m_pStudioHdr->hull_max.x; + pt0.y = state.m_pStudioHdr->hull_min.y; + pt0.z = state.m_pStudioHdr->hull_max.z; + VectorTransform( pt0, *state.m_pModelToWorld, pt1 ); + CDebugOverlay::AddLineOverlay( lightOrigin, pt1, 100, 100, 150, 255, true, 0.0f ); +#endif +} +//----------------------------------------------------------------------------- +// Actually renders the model +//----------------------------------------------------------------------------- +void CModelRender::DrawModelExecute( const DrawModelState_t &state, const ModelRenderInfo_t &pInfo, matrix3x4_t *pBoneToWorld ) +{ +#ifndef SWDS + bool bShadowDepth = (pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE) != 0; + bool bSSAODepth = ( pInfo.flags & STUDIO_SSAODEPTHTEXTURE ) != 0; + + // Bail if we're rendering into shadow depth map and this model doesn't cast shadows + if ( bShadowDepth && ( ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_DO_NOT_CAST_SHADOWS ) != 0 ) ) + return; + + // Shadow state... + g_pShadowMgr->SetModelShadowState( pInfo.instance ); + + if ( g_bTextMode ) + return; + + // Sets up flexes + float *pFlexWeights = NULL; + float *pFlexDelayedWeights = NULL; + int nFlexCount = state.m_pStudioHdr->numflexdesc; + if ( nFlexCount > 0 ) + { + // Does setup for flexes + Assert( pBoneToWorld ); + bool bUsesDelayedWeights = state.m_pRenderable->UsesFlexDelayedWeights(); + g_pStudioRender->LockFlexWeights( nFlexCount, &pFlexWeights, bUsesDelayedWeights ? &pFlexDelayedWeights : NULL ); + state.m_pRenderable->SetupWeights( pBoneToWorld, nFlexCount, pFlexWeights, pFlexDelayedWeights ); + g_pStudioRender->UnlockFlexWeights(); + } + + // OPTIMIZE: Try to precompute part of this mess once a frame at the very least. + bool bUsesBumpmapping = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) && ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING ); + + bool bStaticLighting = ( state.m_drawFlags & STUDIORENDER_DRAW_STATIC_LIGHTING ) && + ( state.m_pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) && + ( !bUsesBumpmapping ) && + ( pInfo.instance != MODEL_INSTANCE_INVALID ) && + g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream(); + + bool bVertexLit = ( pInfo.pModel->flags & MODELFLAG_VERTEXLIT ) != 0; + + bool bNeedsEnvCubemap = r_showenvcubemap.GetInt() || ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); + + if ( r_drawmodellightorigin.GetBool() && !bShadowDepth && !bSSAODepth ) + { + DebugDrawLightingOrigin( state, pInfo ); + } + + ColorMeshInfo_t *pColorMeshes = NULL; + DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; + if ( bStaticLighting ) + { + // have static lighting, get from cache + hColorMeshData = m_ModelInstances[pInfo.instance].m_ColorMeshHandle; + CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); + if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) + { + // color meshes are not present, try to re-establish + if ( RecomputeStaticLighting( pInfo.instance ) ) + { + pColorMeshData = CacheGet( hColorMeshData ); + } + else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) + { + // can't draw + return; + } + } + + if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) + { + pColorMeshes = pColorMeshData->m_pMeshInfos; + if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) + { + CreateLightmapsFromData(pColorMeshData); + } + } + else + { + // failed, draw without static lighting + bStaticLighting = false; + } + } + + DrawModelInfo_t info; + info.m_bStaticLighting = false; + + // get lighting from ambient light sources and radiosity bounces + // also set up the env_cubemap from the light cache if necessary. + if ( ( bVertexLit || bNeedsEnvCubemap ) && !bShadowDepth && !bSSAODepth ) + { + // See if we're using static lighting + LightCacheHandle_t* pLightCache = NULL; + if ( pInfo.instance != MODEL_INSTANCE_INVALID ) + { + if ( ( m_ModelInstances[pInfo.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[pInfo.instance].m_LightCacheHandle ) + { + pLightCache = &m_ModelInstances[pInfo.instance].m_LightCacheHandle; + } + } + + // Choose the lighting origin + Vector entOrigin; + R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, entOrigin ); + + // Set up lighting based on the lighting origin + StudioSetupLighting( state, entOrigin, pLightCache, bVertexLit, bNeedsEnvCubemap, bStaticLighting, info, pInfo, state.m_drawFlags ); + } + + // Set up the camera state + g_pStudioRender->SetViewState( CurrentViewOrigin(), CurrentViewRight(), CurrentViewUp(), CurrentViewForward() ); + + // Color + alpha modulation + g_pStudioRender->SetColorModulation( r_colormod ); + g_pStudioRender->SetAlphaModulation( r_blend ); + + Assert( modelloader->IsLoaded( pInfo.pModel ) ); + info.m_pStudioHdr = state.m_pStudioHdr; + info.m_pHardwareData = state.m_pStudioHWData; + info.m_Skin = pInfo.skin; + info.m_Body = pInfo.body; + info.m_HitboxSet = pInfo.hitboxset; + info.m_pClientEntity = (void*)state.m_pRenderable; + info.m_Lod = state.m_lod; + info.m_pColorMeshes = pColorMeshes; + + // Don't do decals if shadow depth mapping... + info.m_Decals = ( bShadowDepth || bSSAODepth ) ? STUDIORENDER_DECAL_INVALID : state.m_decals; + + // Get perf stats if we are going to use them. + int overlayVal = r_drawmodelstatsoverlay.GetInt(); + int drawFlags = state.m_drawFlags; + + if ( bShadowDepth ) + { + drawFlags |= STUDIORENDER_DRAW_OPAQUE_ONLY; + drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; + } + + if ( bSSAODepth == true ) + { + drawFlags |= STUDIORENDER_DRAW_OPAQUE_ONLY; + drawFlags |= STUDIORENDER_SSAODEPTHTEXTURE; + } + + if ( overlayVal && !bShadowDepth && !bSSAODepth ) + { + drawFlags |= STUDIORENDER_DRAW_GET_PERF_STATS; + } + + if ( ( pInfo.flags & STUDIO_GENERATE_STATS ) != 0 ) + { + drawFlags |= STUDIORENDER_GENERATE_STATS; + } + + DrawModelResults_t results; + g_pStudioRender->DrawModel( &results, info, pBoneToWorld, pFlexWeights, + pFlexDelayedWeights, pInfo.origin, drawFlags ); + info.m_Lod = results.m_nLODUsed; + + if ( overlayVal && !bShadowDepth && !bSSAODepth ) + { + if ( overlayVal != 2 ) + { + DrawModelDebugOverlay( info, results, pInfo.origin ); + } + else + { + AddModelDebugOverlay( info, results, pInfo.origin ); + } + } + + if ( pColorMeshes) + { + ProtectColorDataIfQueued( hColorMeshData ); + } + +#endif +} + +//----------------------------------------------------------------------------- +// Main entry point for model rendering in the engine +//----------------------------------------------------------------------------- +int CModelRender::DrawModel( + int flags, + IClientRenderable *pRenderable, + ModelInstanceHandle_t instance, + int entity_index, + const model_t *pModel, + const Vector &origin, + const QAngle &angles, + int skin, + int body, + int hitboxset, + const matrix3x4_t* pModelToWorld, + const matrix3x4_t *pLightingOffset ) +{ + ModelRenderInfo_t sInfo; + sInfo.flags = flags; + sInfo.pRenderable = pRenderable; + sInfo.instance = instance; + sInfo.entity_index = entity_index; + sInfo.pModel = pModel; + sInfo.origin = origin; + sInfo.angles = angles; + sInfo.skin = skin; + sInfo.body = body; + sInfo.hitboxset = hitboxset; + sInfo.pModelToWorld = pModelToWorld; + sInfo.pLightingOffset = pLightingOffset; + + if ( (r_entity.GetInt() == -1) || (r_entity.GetInt() == entity_index) ) + { + return DrawModelEx( sInfo ); + } + + return 0; +} + +int CModelRender::ComputeLOD( const ModelRenderInfo_t &info, studiohwdata_t *pStudioHWData ) +{ + int lod = r_lod.GetInt(); + // FIXME!!! This calc should be in studiorender, not here!!!!! But since the bone setup + // is done here, and we need the bone mask, we'll do it here for now. + if ( lod == -1 ) + { + CMatRenderContextPtr pRenderContext( materials ); + float screenSize = pRenderContext->ComputePixelWidthOfSphere(info.pRenderable->GetRenderOrigin(), 0.5f ); + float metric = pStudioHWData->LODMetric(screenSize); + lod = pStudioHWData->GetLODForMetric(metric); + } + else + { + if ( ( info.flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) && ( lod > pStudioHWData->m_NumLODs - 2 ) ) + { + lod = pStudioHWData->m_NumLODs - 2; + } + else if ( lod > pStudioHWData->m_NumLODs - 1 ) + { + lod = pStudioHWData->m_NumLODs - 1; + } + else if( lod < 0 ) + { + lod = 0; + } + } + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= pStudioHWData->m_NumLODs ) + { + lod = pStudioHWData->m_NumLODs - 1; + } + + // clamp to root lod + if (lod < pStudioHWData->m_RootLOD) + { + lod = pStudioHWData->m_RootLOD; + } + + Assert( lod >= 0 && lod < pStudioHWData->m_NumLODs ); + return lod; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &pInfo - +//----------------------------------------------------------------------------- +bool CModelRender::DrawModelSetup( ModelRenderInfo_t &pInfo, DrawModelState_t *pState, matrix3x4_t *pCustomBoneToWorld, matrix3x4_t** ppBoneToWorldOut ) +{ + *ppBoneToWorldOut = NULL; + +#ifdef SWDS + return false; +#endif + +#if _DEBUG + if ( (char*)pInfo.pRenderable < (char*)1024 ) + { + Error( "CModelRender::DrawModel: pRenderable == 0x%p", pInfo.pRenderable ); + } +#endif + + // Can only deal with studio models + Assert( pInfo.pModel->type == mod_studio ); + Assert( modelloader->IsLoaded( pInfo.pModel ) ); + + DrawModelState_t &state = *pState; + state.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pInfo.pModel->studio ); + state.m_pRenderable = pInfo.pRenderable; + + // Quick exit if we're just supposed to draw a specific model... + if ( (r_entity.GetInt() != -1) && (r_entity.GetInt() != pInfo.entity_index) ) + return false; + + // quick exit + if (state.m_pStudioHdr->numbodyparts == 0) + return false; + + if ( !pInfo.pModelToWorld ) + { + Assert( 0 ); + return false; + } + + state.m_pModelToWorld = pInfo.pModelToWorld; + + Assert ( pInfo.pRenderable ); + + state.m_pStudioHWData = g_pMDLCache->GetHardwareData( pInfo.pModel->studio ); + if ( !state.m_pStudioHWData ) + return false; + + state.m_lod = ComputeLOD( pInfo, state.m_pStudioHWData ); + + int boneMask = BONE_USED_BY_VERTEX_AT_LOD( state.m_lod ); + // Why isn't this always set?!? + + if ( ( pInfo.flags & STUDIO_RENDER ) == 0 ) + { + // no rendering, just force a bone setup. Don't copy the bones + bool bOk = pInfo.pRenderable->SetupBones( NULL, MAXSTUDIOBONES, boneMask, cl.GetTime() ); + return bOk; + } + + int nBoneCount = state.m_pStudioHdr->numbones; + matrix3x4_t *pBoneToWorld = pCustomBoneToWorld; + if ( !pCustomBoneToWorld ) + { + pBoneToWorld = g_pStudioRender->LockBoneMatrices( nBoneCount ); + } + const bool bOk = pInfo.pRenderable->SetupBones( pBoneToWorld, nBoneCount, boneMask, cl.GetTime() ); + if ( !pCustomBoneToWorld ) + { + g_pStudioRender->UnlockBoneMatrices(); + } + if ( !bOk ) + return false; + + *ppBoneToWorldOut = pBoneToWorld; + + // Convert the instance to a decal handle. + state.m_decals = STUDIORENDER_DECAL_INVALID; + if (pInfo.instance != MODEL_INSTANCE_INVALID) + { + state.m_decals = m_ModelInstances[pInfo.instance].m_DecalHandle; + } + + state.m_drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; + if ( pInfo.flags & STUDIO_TWOPASS ) + { + if (pInfo.flags & STUDIO_TRANSPARENCY) + { + state.m_drawFlags = STUDIORENDER_DRAW_TRANSLUCENT_ONLY; + } + else + { + state.m_drawFlags = STUDIORENDER_DRAW_OPAQUE_ONLY; + } + } + if ( pInfo.flags & STUDIO_STATIC_LIGHTING ) + { + state.m_drawFlags |= STUDIORENDER_DRAW_STATIC_LIGHTING; + } + + if( pInfo.flags & STUDIO_ITEM_BLINK ) + { + state.m_drawFlags |= STUDIORENDER_DRAW_ITEM_BLINK; + } + + if ( pInfo.flags & STUDIO_WIREFRAME ) + { + state.m_drawFlags |= STUDIORENDER_DRAW_WIREFRAME; + } + + if ( pInfo.flags & STUDIO_NOSHADOWS ) + { + state.m_drawFlags |= STUDIORENDER_DRAW_NO_SHADOWS; + } + + if ( r_drawmodelstatsoverlay.GetInt() == 2) + { + state.m_drawFlags |= STUDIORENDER_DRAW_ACCURATETIME; + } + + if ( pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE ) + { + state.m_drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; + } + + return true; +} + +int CModelRender::DrawModelEx( ModelRenderInfo_t &pInfo ) +{ +#ifndef SWDS + DrawModelState_t state; + + matrix3x4_t tmpmat; + if ( !pInfo.pModelToWorld ) + { + pInfo.pModelToWorld = &tmpmat; + + // Turns the origin + angles into a matrix + AngleMatrix( pInfo.angles, pInfo.origin, tmpmat ); + } + + matrix3x4_t *pBoneToWorld; + if ( !DrawModelSetup( pInfo, &state, NULL, &pBoneToWorld ) ) + return 0; + + if ( pInfo.flags & STUDIO_RENDER ) + { + DrawModelExecute( state, pInfo, pBoneToWorld ); + } + + return 1; +#else + return 0; +#endif +} + +int CModelRender::DrawModelExStaticProp( ModelRenderInfo_t &pInfo ) +{ +#ifndef SWDS + bool bShadowDepth = ( pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE ) != 0; + +#if _DEBUG + if ( (char*)pInfo.pRenderable < (char*)1024 ) + { + Error( "CModelRender::DrawModel: pRenderable == %p", pInfo.pRenderable ); + } + + // Can only deal with studio models + if ( pInfo.pModel->type != mod_studio ) + return 0; +#endif + Assert( modelloader->IsLoaded( pInfo.pModel ) ); + + DrawModelState_t state; + state.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pInfo.pModel->studio ); + state.m_pRenderable = pInfo.pRenderable; + + // quick exit + if ( state.m_pStudioHdr->numbodyparts == 0 || g_bTextMode ) + return 1; + + state.m_pStudioHWData = g_pMDLCache->GetHardwareData( pInfo.pModel->studio ); + if ( !state.m_pStudioHWData ) + return 0; + + Assert( pInfo.pModelToWorld ); + state.m_pModelToWorld = pInfo.pModelToWorld; + Assert ( pInfo.pRenderable ); + + int lod = ComputeLOD( pInfo, state.m_pStudioHWData ); + // int boneMask = BONE_USED_BY_VERTEX_AT_LOD( lod ); + // Why isn't this always set?!? + if ( !(pInfo.flags & STUDIO_RENDER) ) + return 0; + + // Convert the instance to a decal handle. + StudioDecalHandle_t decalHandle = STUDIORENDER_DECAL_INVALID; + if ( (pInfo.instance != MODEL_INSTANCE_INVALID) && !(pInfo.flags & STUDIO_SHADOWDEPTHTEXTURE) ) + { + decalHandle = m_ModelInstances[pInfo.instance].m_DecalHandle; + } + + int drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; + if ( pInfo.flags & STUDIO_TWOPASS ) + { + if ( pInfo.flags & STUDIO_TRANSPARENCY ) + { + drawFlags = STUDIORENDER_DRAW_TRANSLUCENT_ONLY; + } + else + { + drawFlags = STUDIORENDER_DRAW_OPAQUE_ONLY; + } + } + + if ( pInfo.flags & STUDIO_STATIC_LIGHTING ) + { + drawFlags |= STUDIORENDER_DRAW_STATIC_LIGHTING; + } + + if ( pInfo.flags & STUDIO_WIREFRAME ) + { + drawFlags |= STUDIORENDER_DRAW_WIREFRAME; + } + + if ( pInfo.flags & STUDIO_GENERATE_STATS ) + { + drawFlags |= STUDIORENDER_GENERATE_STATS; + } + + // Shadow state... + g_pShadowMgr->SetModelShadowState( pInfo.instance ); + + // OPTIMIZE: Try to precompute part of this mess once a frame at the very least. + bool bUsesBumpmapping = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) && ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING ); + + bool bStaticLighting = (( drawFlags & STUDIORENDER_DRAW_STATIC_LIGHTING ) && + ( state.m_pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) && + ( !bUsesBumpmapping ) && + ( pInfo.instance != MODEL_INSTANCE_INVALID ) && + g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ); + + bool bVertexLit = ( pInfo.pModel->flags & MODELFLAG_VERTEXLIT ) != 0; + bool bNeedsEnvCubemap = r_showenvcubemap.GetInt() || ( pInfo.pModel->flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); + + if ( r_drawmodellightorigin.GetBool() ) + { + DebugDrawLightingOrigin( state, pInfo ); + } + + ColorMeshInfo_t *pColorMeshes = NULL; + DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; + if ( bStaticLighting ) + { + // have static lighting, get from cache + hColorMeshData = m_ModelInstances[pInfo.instance].m_ColorMeshHandle; + CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); + if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) + { + // color meshes are not present, try to re-establish + if ( RecomputeStaticLighting( pInfo.instance ) ) + { + pColorMeshData = CacheGet( hColorMeshData ); + } + else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) + { + // can't draw + return 0; + } + } + + if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) + { + pColorMeshes = pColorMeshData->m_pMeshInfos; + if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) + { + CreateLightmapsFromData(pColorMeshData); + } + } + else + { + // failed, draw without static lighting + bStaticLighting = false; + } + } + + DrawModelInfo_t info; + info.m_bStaticLighting = false; + + // Get lighting from ambient light sources and radiosity bounces + // also set up the env_cubemap from the light cache if necessary. + // Don't bother if we're rendering to shadow depth texture + if ( ( bVertexLit || bNeedsEnvCubemap ) && !bShadowDepth ) + { + // See if we're using static lighting + LightCacheHandle_t* pLightCache = NULL; + if ( pInfo.instance != MODEL_INSTANCE_INVALID ) + { + if ( ( m_ModelInstances[pInfo.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[pInfo.instance].m_LightCacheHandle ) + { + pLightCache = &m_ModelInstances[pInfo.instance].m_LightCacheHandle; + } + } + + // Choose the lighting origin + Vector entOrigin; + if ( !pLightCache ) + { + R_ComputeLightingOrigin( state.m_pRenderable, state.m_pStudioHdr, *state.m_pModelToWorld, entOrigin ); + } + + // Set up lighting based on the lighting origin + StudioSetupLighting( state, entOrigin, pLightCache, bVertexLit, bNeedsEnvCubemap, bStaticLighting, info, pInfo, drawFlags ); + } + + Assert( modelloader->IsLoaded( pInfo.pModel ) ); + info.m_pStudioHdr = state.m_pStudioHdr; + info.m_pHardwareData = state.m_pStudioHWData; + info.m_Decals = decalHandle; + info.m_Skin = pInfo.skin; + info.m_Body = pInfo.body; + info.m_HitboxSet = pInfo.hitboxset; + info.m_pClientEntity = (void*)state.m_pRenderable; + info.m_Lod = lod; + info.m_pColorMeshes = pColorMeshes; + + if ( bShadowDepth ) + { + drawFlags |= STUDIORENDER_SHADOWDEPTHTEXTURE; + } + +#ifdef _DEBUG + Vector tmp; + MatrixGetColumn( *pInfo.pModelToWorld, 3, &tmp ); + Assert( VectorsAreEqual( pInfo.origin, tmp, 1e-3 ) ); +#endif + + g_pStudioRender->DrawModelStaticProp( info, *pInfo.pModelToWorld, drawFlags ); + + if ( pColorMeshes) + { + ProtectColorDataIfQueued( hColorMeshData ); + } + + return 1; +#else + return 0; +#endif +} + +struct robject_t +{ + const matrix3x4_t *pMatrix; + IClientRenderable *pRenderable; + ColorMeshInfo_t *pColorMeshes; + ITexture *pEnvCubeMap; + Vector *pLightingOrigin; + short modelIndex; + short lod; + ModelInstanceHandle_t instance; + short skin; + short lightIndex; + short pad0; +}; + +struct rmodel_t +{ + const model_t * pModel; + studiohdr_t* pStudioHdr; + studiohwdata_t* pStudioHWData; + float maxArea; + short lodStart; + byte lodCount; + byte bVertexLit : 1; + byte bNeedsCubemap : 1; + byte bStaticLighting : 1; +}; + +class CRobjectLess +{ +public: + bool Less( const robject_t& lhs, const robject_t& rhs, void *pContext ) + { + rmodel_t *pModels = static_cast<rmodel_t *>(pContext); + if ( lhs.modelIndex == rhs.modelIndex ) + { + if ( lhs.skin != rhs.skin ) + return lhs.skin < rhs.skin; + return lhs.lod < rhs.lod; + } + if ( pModels[lhs.modelIndex].maxArea == pModels[rhs.modelIndex].maxArea ) + return lhs.modelIndex < rhs.modelIndex; + return pModels[lhs.modelIndex].maxArea > pModels[rhs.modelIndex].maxArea; + } +}; + +struct rdecalmodel_t +{ + short objectIndex; + short lightIndex; +}; +/* +// ---------------------------------------- +// not yet implemented + +struct rlod_t +{ + short groupCount; + short groupStart; +}; + +struct rgroup_t +{ + IMesh *pMesh; + short batchCount; + short batchStart; + short colorMeshIndex; + short pad0; +}; + +struct rbatch_t +{ + IMaterial *pMaterial; + short primitiveType; + short pad0; + unsigned short indexOffset; + unsigned short indexCount; +}; +// ---------------------------------------- +*/ + +inline int FindModel( const CUtlVector<rmodel_t> &list, const model_t *pModel ) +{ + for ( int j = list.Count(); --j >= 0 ; ) + { + if ( list[j].pModel == pModel ) + return j; + } + return -1; +} + + +// NOTE: UNDONE: This is a work in progress of a new static prop rendering pipeline +// UNDONE: Expose drawing commands from studiorender and draw here +// UNDONE: Build a similar pipeline for non-static prop models +// UNDONE: Split this into several functions in a sub-object +ConVar r_staticprop_lod("r_staticprop_lod", "-1"); +int CModelRender::DrawStaticPropArrayFast( StaticPropRenderInfo_t *pProps, int count, bool bShadowDepth ) +{ +#ifndef SWDS + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + CMatRenderContextPtr pRenderContext( materials ); + const int MAX_OBJECTS = 1024; + CUtlSortVector<robject_t, CRobjectLess> objectList(0, MAX_OBJECTS); + CUtlVector<rmodel_t> modelList(0,256); + CUtlVector<short> lightObjects(0,256); + CUtlVector<short> shadowObjects(0,64); + CUtlVector<rdecalmodel_t> decalObjects(0,64); + CUtlVector<LightingState_t> lightStates(0,256); + bool bForceCubemap = r_showenvcubemap.GetBool(); + int drawnCount = 0; + int forcedLodSetting = r_lod.GetInt(); + if ( r_staticprop_lod.GetInt() >= 0 ) + { + forcedLodSetting = r_staticprop_lod.GetInt(); + } + + // build list of objects and unique models + for ( int i = 0; i < count; i++ ) + { + drawnCount++; + // UNDONE: This is a perf hit in some scenes! Use a hash? + int modelIndex = FindModel( modelList, pProps[i].pModel ); + if ( modelIndex < 0 ) + { + modelIndex = modelList.AddToTail(); + modelList[modelIndex].pModel = pProps[i].pModel; + modelList[modelIndex].pStudioHWData = g_pMDLCache->GetHardwareData( modelList[modelIndex].pModel->studio ); + } + if ( modelList[modelIndex].pStudioHWData ) + { + robject_t obj; + obj.pMatrix = pProps[i].pModelToWorld; + obj.pRenderable = pProps[i].pRenderable; + obj.modelIndex = modelIndex; + obj.instance = pProps[i].instance; + obj.skin = pProps[i].skin; + obj.lod = 0; + obj.pColorMeshes = NULL; + obj.pEnvCubeMap = NULL; + obj.lightIndex = -1; + obj.pLightingOrigin = pProps[i].pLightingOrigin; + objectList.InsertNoSort(obj); + } + } + + // process list of unique models + int lodStart = 0; + for ( int i = 0; i < modelList.Count(); i++ ) + { + const model_t *pModel = modelList[i].pModel; + Assert( modelloader->IsLoaded( pModel ) ); + unsigned int flags = pModel->flags; + modelList[i].pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + modelList[i].maxArea = 1.0f; + modelList[i].lodStart = lodStart; + modelList[i].lodCount = modelList[i].pStudioHWData ? modelList[i].pStudioHWData->m_NumLODs : 0; + bool bBumpMapped = (flags & MODELFLAG_STUDIOHDR_USES_BUMPMAPPING) != 0; + modelList[i].bStaticLighting = (( modelList[i].pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) != 0) && !bBumpMapped; + modelList[i].bVertexLit = ( flags & MODELFLAG_VERTEXLIT ) != 0; + modelList[i].bNeedsCubemap = ( flags & MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ) != 0; + + lodStart += modelList[i].lodCount; + } + + // -1 is automatic lod + if ( forcedLodSetting < 0 ) + { + // compute the lod of each object + for ( int i = 0; i < objectList.Count(); i++ ) + { + Vector org; + MatrixGetColumn( *objectList[i].pMatrix, 3, org ); + float screenSize = pRenderContext->ComputePixelWidthOfSphere(org, 0.5f ); + const rmodel_t &model = modelList[objectList[i].modelIndex]; + float metric = model.pStudioHWData->LODMetric(screenSize); + objectList[i].lod = model.pStudioHWData->GetLODForMetric(metric); + if ( objectList[i].lod < model.pStudioHWData->m_RootLOD ) + { + objectList[i].lod = model.pStudioHWData->m_RootLOD; + } + modelList[objectList[i].modelIndex].maxArea = max(modelList[objectList[i].modelIndex].maxArea, screenSize); + } + } + else + { + // force the lod of each object + for ( int i = 0; i < objectList.Count(); i++ ) + { + const rmodel_t &model = modelList[objectList[i].modelIndex]; + objectList[i].lod = clamp(forcedLodSetting, model.pStudioHWData->m_RootLOD, model.lodCount-1); + } + } + // UNDONE: Don't sort if rendering transparent objects - for now this isn't called in the transparent case + // sort by model, then by lod + objectList.SetLessContext( static_cast<void *>(modelList.Base()) ); + objectList.RedoSort(true); + + ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); + + // now build out the lighting states + if ( !bShadowDepth ) + { + for ( int i = 0; i < objectList.Count(); i++ ) + { + robject_t &obj = objectList[i]; + rmodel_t &model = modelList[obj.modelIndex]; + bool bStaticLighting = (model.bStaticLighting && obj.instance != MODEL_INSTANCE_INVALID); + bool bVertexLit = model.bVertexLit; + bool bNeedsEnvCubemap = bForceCubemap || model.bNeedsCubemap; + bool bHasDecals = ( m_ModelInstances[obj.instance].m_DecalHandle != STUDIORENDER_DECAL_INVALID ) ? true : false; + LightingState_t *pDecalLightState = NULL; + if ( bHasDecals ) + { + rdecalmodel_t decalModel; + decalModel.lightIndex = lightStates.AddToTail(); + pDecalLightState = &lightStates[decalModel.lightIndex]; + decalModel.objectIndex = i; + decalObjects.AddToTail( decalModel ); + } + // for now we skip models that have shadows - will update later to include them in a post-pass + if ( g_pShadowMgr->ModelHasShadows( obj.instance ) ) + { + shadowObjects.AddToTail(i); + } + + // get the static lighting from the cache + DataCacheHandle_t hColorMeshData = DC_INVALID_HANDLE; + if ( bStaticLighting ) + { + // have static lighting, get from cache + hColorMeshData = m_ModelInstances[obj.instance].m_ColorMeshHandle; + CColorMeshData *pColorMeshData = CacheGet( hColorMeshData ); + if ( !pColorMeshData || pColorMeshData->m_bNeedsRetry ) + { + // color meshes are not present, try to re-establish + + if ( UpdateStaticPropColorData( obj.pRenderable->GetIClientUnknown(), obj.instance ) ) + { + pColorMeshData = CacheGet( hColorMeshData ); + } + else if ( !pColorMeshData || !pColorMeshData->m_bNeedsRetry ) + { + // can't draw + continue; + } + } + + if ( pColorMeshData && ( pColorMeshData->m_bColorMeshValid || pColorMeshData->m_bColorTextureValid ) ) + { + obj.pColorMeshes = pColorMeshData->m_pMeshInfos; + if (pColorMeshData->m_bColorTextureValid && !pColorMeshData->m_bColorTextureCreated) + { + CreateLightmapsFromData(pColorMeshData); + } + + if ( pCallQueue ) + { + if ( CacheLock( hColorMeshData ) ) // CacheCreate above will call functions that won't take place until later. If color mesh isn't used right away, it could get dumped + { + pCallQueue->QueueCall( this, &CModelRender::CacheUnlock, hColorMeshData ); + } + } + } + else + { + // failed, draw without static lighting + bStaticLighting = false; + } + } + + // Get lighting from ambient light sources and radiosity bounces + // also set up the env_cubemap from the light cache if necessary. + if ( ( bVertexLit || bNeedsEnvCubemap ) ) + { + // See if we're using static lighting + LightCacheHandle_t* pLightCache = NULL; + ITexture *pEnvCubemapTexture = NULL; + if ( obj.instance != MODEL_INSTANCE_INVALID ) + { + if ( ( m_ModelInstances[obj.instance].m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) && m_ModelInstances[obj.instance].m_LightCacheHandle ) + { + pLightCache = &m_ModelInstances[obj.instance].m_LightCacheHandle; + } + } + + Assert(pLightCache); + LightingState_t lightingState; + LightingState_t *pState = &lightingState; + if ( pLightCache ) + { + // dx8 and dx9 case. . .hardware can do baked lighting plus other dynamic lighting + // We already have the static part baked into a color mesh, so just get the dynamic stuff. + if ( !bStaticLighting || StaticLightCacheAffectedByDynamicLight( *pLightCache ) ) + { + pState = LightcacheGetStatic( *pLightCache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_STATIC | LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); + Assert( pState->numlights >= 0 && pState->numlights <= MAXLOCALLIGHTS ); + } + else + { + pState = LightcacheGetStatic( *pLightCache, &pEnvCubemapTexture, LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE ); + Assert( pState->numlights >= 0 && pState->numlights <= MAXLOCALLIGHTS ); + } + if ( bHasDecals ) + { + for ( int iCube = 0; iCube < 6; ++iCube ) + { + pDecalLightState->r_boxcolor[iCube] = m_ModelInstances[obj.instance].m_AmbientLightingState.r_boxcolor[iCube] + pState->r_boxcolor[iCube]; + } + pDecalLightState->CopyLocalLights( m_ModelInstances[obj.instance].m_AmbientLightingState ); + pDecalLightState->AddAllLocalLights( *pState ); + } + } + else // !pLightcache + { + // UNDONE: is it possible to end up here in the static prop case? + Vector vLightingOrigin = *obj.pLightingOrigin; + int lightCacheFlags = bStaticLighting ? (LIGHTCACHEFLAGS_DYNAMIC | LIGHTCACHEFLAGS_LIGHTSTYLE) + : (LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST); + LightcacheGetDynamic_Stats stats; + pEnvCubemapTexture = LightcacheGetDynamic( vLightingOrigin, lightingState, + stats, lightCacheFlags, false ); + Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); + pState = &lightingState; + + if ( bHasDecals ) + { + LightcacheGetDynamic_Stats tempStats; + LightcacheGetDynamic( vLightingOrigin, *pDecalLightState, tempStats, + LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST ); + } + } + + if ( bNeedsEnvCubemap && pEnvCubemapTexture ) + { + obj.pEnvCubeMap = pEnvCubemapTexture; + } + + if ( bVertexLit ) + { + // if we have any real lighting state we need to save it for this object + if ( pState->numlights || pState->HasAmbientColors() ) + { + obj.lightIndex = lightStates.AddToTail(*pState); + lightObjects.AddToTail( i ); + } + } + } + } + } + // now render the baked lighting props with no lighting state + float color[3]; + color[0] = color[1] = color[2] = 1.0f; + g_pStudioRender->SetColorModulation(color); + g_pStudioRender->SetAlphaModulation(1.0f); + g_pStudioRender->SetViewState( CurrentViewOrigin(), CurrentViewRight(), CurrentViewUp(), CurrentViewForward() ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + g_pStudioRender->ClearAllShadows(); + pRenderContext->DisableAllLocalLights(); + DrawModelInfo_t info; + for ( int i = 0; i < 6; i++ ) + info.m_vecAmbientCube[i].Init(); + g_pStudioRender->SetAmbientLightColors( info.m_vecAmbientCube ); + pRenderContext->SetAmbientLight( 0.0, 0.0, 0.0 ); + info.m_nLocalLightCount = 0; + info.m_bStaticLighting = false; + + int drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_STATIC_LIGHTING; + if (bShadowDepth) + drawFlags |= STUDIO_SHADOWDEPTHTEXTURE; + info.m_Decals = STUDIORENDER_DECAL_INVALID; + info.m_Body = 0; + info.m_HitboxSet = 0; + for ( int i = 0; i < objectList.Count(); i++ ) + { + robject_t &obj = objectList[i]; + if ( obj.lightIndex >= 0 ) + continue; + rmodel_t &model = modelList[obj.modelIndex]; + if ( obj.pEnvCubeMap ) + { + pRenderContext->BindLocalCubemap( obj.pEnvCubeMap ); + } + + info.m_pStudioHdr = model.pStudioHdr; + info.m_pHardwareData = model.pStudioHWData; + info.m_Skin = obj.skin; + info.m_pClientEntity = static_cast<void*>(obj.pRenderable); + info.m_Lod = obj.lod; + info.m_pColorMeshes = obj.pColorMeshes; + g_pStudioRender->DrawModelStaticProp( info, *obj.pMatrix, drawFlags ); + } + + // now render the vertex lit props + int nLocalLightCount = 0; + LightDesc_t localLightDescs[4]; + drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_STATIC_LIGHTING; + if ( lightObjects.Count() ) + { + for ( int i = 0; i < lightObjects.Count(); i++ ) + { + robject_t &obj = objectList[lightObjects[i]]; + rmodel_t &model = modelList[obj.modelIndex]; + + if ( obj.pEnvCubeMap ) + { + pRenderContext->BindLocalCubemap( obj.pEnvCubeMap ); + } + + LightingState_t *pState = &lightStates[obj.lightIndex]; + g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); + pRenderContext->SetLightingOrigin( *obj.pLightingOrigin ); + R_SetNonAmbientLightingState( pState->numlights, pState->locallight, &nLocalLightCount, localLightDescs, true ); + info.m_pStudioHdr = model.pStudioHdr; + info.m_pHardwareData = model.pStudioHWData; + info.m_Skin = obj.skin; + info.m_pClientEntity = static_cast<void*>(obj.pRenderable); + info.m_Lod = obj.lod; + info.m_pColorMeshes = obj.pColorMeshes; + g_pStudioRender->DrawModelStaticProp( info, *obj.pMatrix, drawFlags ); + } + } + + if ( !IsX360() && ( r_flashlight_version2.GetInt() == 0 ) && shadowObjects.Count() ) + { + drawFlags = STUDIORENDER_DRAW_ENTIRE_MODEL; + for ( int i = 0; i < shadowObjects.Count(); i++ ) + { + // draw just the shadows! + robject_t &obj = objectList[shadowObjects[i]]; + rmodel_t &model = modelList[obj.modelIndex]; + g_pShadowMgr->SetModelShadowState( obj.instance ); + + info.m_pStudioHdr = model.pStudioHdr; + info.m_pHardwareData = model.pStudioHWData; + info.m_Skin = obj.skin; + info.m_pClientEntity = static_cast<void*>(obj.pRenderable); + info.m_Lod = obj.lod; + info.m_pColorMeshes = obj.pColorMeshes; + g_pStudioRender->DrawStaticPropShadows( info, *obj.pMatrix, drawFlags ); + } + g_pStudioRender->ClearAllShadows(); + } + + for ( int i = 0; i < decalObjects.Count(); i++ ) + { + // draw just the decals! + robject_t &obj = objectList[decalObjects[i].objectIndex]; + rmodel_t &model = modelList[obj.modelIndex]; + LightingState_t *pState = &lightStates[decalObjects[i].lightIndex]; + g_pStudioRender->SetAmbientLightColors( pState->r_boxcolor ); + pRenderContext->SetLightingOrigin( *obj.pLightingOrigin ); + R_SetNonAmbientLightingState( pState->numlights, pState->locallight, &nLocalLightCount, localLightDescs, true ); + info.m_pStudioHdr = model.pStudioHdr; + info.m_pHardwareData = model.pStudioHWData; + info.m_Decals = m_ModelInstances[obj.instance].m_DecalHandle; + info.m_Skin = obj.skin; + info.m_pClientEntity = static_cast<void*>(obj.pRenderable); + info.m_Lod = obj.lod; + info.m_pColorMeshes = obj.pColorMeshes; + g_pStudioRender->DrawStaticPropDecals( info, *obj.pMatrix ); + } + + // Restore the matrices if we're skinning + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PopMatrix(); + return drawnCount; +#else // SWDS + return 0; +#endif // SWDS +} + +//----------------------------------------------------------------------------- +// Shadow rendering +//----------------------------------------------------------------------------- +matrix3x4_t* CModelRender::DrawModelShadowSetup( IClientRenderable *pRenderable, int body, int skin, DrawModelInfo_t *pInfo, matrix3x4_t *pCustomBoneToWorld ) +{ +#ifndef SWDS + DrawModelInfo_t &info = *pInfo; + static ConVar r_shadowlod("r_shadowlod", "-1"); + static ConVar r_shadowlodbias("r_shadowlodbias", "2"); + + model_t const* pModel = pRenderable->GetModel(); + if ( !pModel ) + return NULL; + + // FIXME: Make brush shadows work + if ( pModel->type != mod_studio ) + return NULL; + + Assert( modelloader->IsLoaded( pModel ) && ( pModel->type == mod_studio ) ); + + info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + info.m_pColorMeshes = NULL; + + // quick exit + if (info.m_pStudioHdr->numbodyparts == 0) + return NULL; + + Assert ( pRenderable ); + info.m_pHardwareData = g_pMDLCache->GetHardwareData( pModel->studio ); + if ( !info.m_pHardwareData ) + return NULL; + + info.m_Decals = STUDIORENDER_DECAL_INVALID; + info.m_Skin = skin; + info.m_Body = body; + info.m_pClientEntity = (void*)pRenderable; + info.m_HitboxSet = 0; + + info.m_Lod = r_shadowlod.GetInt(); + // If the .mdl has a shadowlod, force the use of that one instead + if ( info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_HASSHADOWLOD ) + { + info.m_Lod = info.m_pHardwareData->m_NumLODs-1; + } + else if ( info.m_Lod == USESHADOWLOD ) + { + int lastlod = info.m_pHardwareData->m_NumLODs - 1; + info.m_Lod = lastlod; + } + else if ( info.m_Lod < 0 ) + { + CMatRenderContextPtr pRenderContext( materials ); + // Compute the shadow LOD... + float factor = r_shadowlodbias.GetFloat() > 0.0f ? 1.0f / r_shadowlodbias.GetFloat() : 1.0f; + float screenSize = factor * pRenderContext->ComputePixelWidthOfSphere( pRenderable->GetRenderOrigin(), 0.5f ); + info.m_Lod = g_pStudioRender->ComputeModelLod( info.m_pHardwareData, screenSize ); + info.m_Lod = info.m_pHardwareData->m_NumLODs-2; + if ( info.m_Lod < 0 ) + { + info.m_Lod = 0; + } + } + + // clamp to root lod + if (info.m_Lod < info.m_pHardwareData->m_RootLOD) + { + info.m_Lod = info.m_pHardwareData->m_RootLOD; + } + + matrix3x4_t *pBoneToWorld = pCustomBoneToWorld; + if ( !pBoneToWorld ) + { + pBoneToWorld = g_pStudioRender->LockBoneMatrices( info.m_pStudioHdr->numbones ); + } + const bool bOk = pRenderable->SetupBones( pBoneToWorld, info.m_pStudioHdr->numbones, BONE_USED_BY_VERTEX_AT_LOD(info.m_Lod), cl.GetTime() ); + g_pStudioRender->UnlockBoneMatrices(); + if ( !bOk ) + return NULL; + return pBoneToWorld; +#else + return NULL; +#endif +} + +void CModelRender::DrawModelShadow( IClientRenderable *pRenderable, const DrawModelInfo_t &info, matrix3x4_t *pBoneToWorld ) +{ +#ifndef SWDS + // Needed because we don't call SetupWeights + g_pStudioRender->SetEyeViewTarget( info.m_pStudioHdr, info.m_Body, vec3_origin ); + + // Color + alpha modulation + Vector white( 1, 1, 1 ); + g_pStudioRender->SetColorModulation( white.Base() ); + g_pStudioRender->SetAlphaModulation( 1.0f ); + + if ((info.m_pStudioHdr->flags & STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS) == 0) + { + g_pStudioRender->ForcedMaterialOverride( g_pMaterialShadowBuild, OVERRIDE_BUILD_SHADOWS ); + } + + g_pStudioRender->DrawModel( NULL, info, pBoneToWorld, NULL, NULL, pRenderable->GetRenderOrigin(), + STUDIORENDER_DRAW_NO_SHADOWS | STUDIORENDER_DRAW_ENTIRE_MODEL | STUDIORENDER_DRAW_NO_FLEXES ); + g_pStudioRender->ForcedMaterialOverride( 0 ); +#endif +} + +void CModelRender::SetViewTarget( const CStudioHdr *pStudioHdr, int nBodyIndex, const Vector& target ) +{ + g_pStudioRender->SetEyeViewTarget( pStudioHdr->GetRenderHdr(), nBodyIndex, target ); +} + +void CModelRender::InitColormeshParams( ModelInstance_t &instance, studiohwdata_t *pStudioHWData, colormeshparams_t *pColorMeshParams ) +{ + pColorMeshParams->m_nMeshes = 0; + pColorMeshParams->m_nTotalVertexes = 0; + pColorMeshParams->m_pPooledVBAllocator = NULL; + + if ( ( instance.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) && + g_pMaterialSystemHardwareConfig->SupportsStreamOffset() && + ( r_proplightingpooling.GetInt() == 1 ) ) + { + // Color meshes can be allocated in a shared pool for static props + // (saves memory on X360 due to 4-KB VB alignment) + pColorMeshParams->m_pPooledVBAllocator = (IPooledVBAllocator *)&m_colorMeshVBAllocator; + } + + for ( int lodID = pStudioHWData->m_RootLOD; lodID < pStudioHWData->m_NumLODs; lodID++ ) + { + studioloddata_t *pLOD = &pStudioHWData->m_pLODs[lodID]; + for ( int meshID = 0; meshID < pStudioHWData->m_NumStudioMeshes; meshID++ ) + { + studiomeshdata_t *pMesh = &pLOD->m_pMeshData[meshID]; + for ( int groupID = 0; groupID < pMesh->m_NumGroup; groupID++ ) + { + pColorMeshParams->m_nVertexes[pColorMeshParams->m_nMeshes++] = pMesh->m_pMeshGroup[groupID].m_NumVertices; + Assert( pColorMeshParams->m_nMeshes <= ARRAYSIZE( pColorMeshParams->m_nVertexes ) ); + + pColorMeshParams->m_nTotalVertexes += pMesh->m_pMeshGroup[groupID].m_NumVertices; + } + } + } +} + +//----------------------------------------------------------------------------- +// Allocates the static prop color data meshes +//----------------------------------------------------------------------------- +// FIXME? : Move this to StudioRender? +CColorMeshData *CModelRender::FindOrCreateStaticPropColorData( ModelInstanceHandle_t handle ) +{ + if ( handle == MODEL_INSTANCE_INVALID || !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ) + { + // the card can't support it + return NULL; + } + + ModelInstance_t& instance = m_ModelInstances[handle]; + CColorMeshData *pColorMeshData = CacheGet( instance.m_ColorMeshHandle ); + if ( pColorMeshData ) + { + // found in cache + return pColorMeshData; + } + + if ( !instance.m_pModel ) + { + return NULL; + } + + Assert( modelloader->IsLoaded( instance.m_pModel ) && ( instance.m_pModel->type == mod_studio ) ); + studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( instance.m_pModel->studio ); + Assert( pStudioHWData ); + if ( !pStudioHWData ) + return NULL; + + colormeshparams_t params; + InitColormeshParams( instance, pStudioHWData, ¶ms ); + if ( params.m_nMeshes <= 0 ) + { + // nothing to create + return NULL; + } + + // create the meshes + params.m_fnHandle = instance.m_pModel->fnHandle; + instance.m_ColorMeshHandle = CacheCreate( params ); + ProtectColorDataIfQueued( instance.m_ColorMeshHandle ); + pColorMeshData = CacheGet( instance.m_ColorMeshHandle ); + + return pColorMeshData; +} + +//----------------------------------------------------------------------------- +// Allocates the static prop color data meshes +//----------------------------------------------------------------------------- +// FIXME? : Move this to StudioRender? +void CModelRender::ProtectColorDataIfQueued( DataCacheHandle_t hColorMesh ) +{ + if ( hColorMesh != DC_INVALID_HANDLE) + { + CMatRenderContextPtr pRenderContext( materials ); + ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); + if ( pCallQueue ) + { + if ( CacheLock( hColorMesh ) ) // CacheCreate above will call functions that won't take place until later. If color mesh isn't used right away, it could get dumped + { + pCallQueue->QueueCall( this, &CModelRender::CacheUnlock, hColorMesh ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Old-style computation of vertex lighting ( Currently In Use ) +//----------------------------------------------------------------------------- +void CModelRender::ComputeModelVertexLightingOld( mstudiomodel_t *pModel, + matrix3x4_t& matrix, const LightingState_t &lightingState, color24 *pLighting, + bool bUseConstDirLighting, float flConstDirLightAmount ) +{ + Vector worldPos, worldNormal, destColor; + int nNumLightDesc; + LightDesc_t lightDesc[MAXLOCALLIGHTS]; + LightingState_t *pLightingState; + + pLightingState = (LightingState_t*)&lightingState; + + // build the lighting descriptors + R_SetNonAmbientLightingState( pLightingState->numlights, pLightingState->locallight, &nNumLightDesc, lightDesc, false ); + + const thinModelVertices_t *thinVertData = NULL; + const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData(); + mstudiovertex_t *pFatVerts = NULL; + if ( vertData ) + { + pFatVerts = vertData->Vertex( 0 ); + } + else + { + thinVertData = pModel->GetThinVertexData(); + if ( !thinVertData ) + return; + } + + bool bHasSSE = MathLib_SSEEnabled(); + + // light all vertexes + for ( int i = 0; i < pModel->numvertices; ++i ) + { + if ( vertData ) + { +#ifdef _WIN32 + if (bHasSSE) + { + // hint the next vertex + // data is loaded with one extra vertex for read past +#if !defined( _X360 ) // X360TBD + _mm_prefetch( (char*)&pFatVerts[i+1], _MM_HINT_T0 ); +#endif + } +#endif + + VectorTransform( pFatVerts[i].m_vecPosition, matrix, worldPos ); + VectorRotate( pFatVerts[i].m_vecNormal, matrix, worldNormal ); + } + else + { + Vector position; + Vector normal; + thinVertData->GetModelPosition( pModel, i, &position ); + thinVertData->GetModelNormal( pModel, i, &normal ); + VectorTransform( position, matrix, worldPos ); + VectorRotate( normal, matrix, worldNormal ); + } + + if ( bUseConstDirLighting ) + { + g_pStudioRender->ComputeLightingConstDirectional( pLightingState->r_boxcolor, + nNumLightDesc, lightDesc, worldPos, worldNormal, destColor, flConstDirLightAmount ); + } + else + { + g_pStudioRender->ComputeLighting( pLightingState->r_boxcolor, + nNumLightDesc, lightDesc, worldPos, worldNormal, destColor ); + } + + // to gamma space + destColor[0] = LinearToVertexLight( destColor[0] ); + destColor[1] = LinearToVertexLight( destColor[1] ); + destColor[2] = LinearToVertexLight( destColor[2] ); + + Assert( (destColor[0] >= 0.0f) && (destColor[0] <= 1.0f) ); + Assert( (destColor[1] >= 0.0f) && (destColor[1] <= 1.0f) ); + Assert( (destColor[2] >= 0.0f) && (destColor[2] <= 1.0f) ); + + pLighting[i].r = FastFToC(destColor[0]); + pLighting[i].g = FastFToC(destColor[1]); + pLighting[i].b = FastFToC(destColor[2]); + } +} + + +//----------------------------------------------------------------------------- +// New-style computation of vertex lighting ( Not Used Yet ) +//----------------------------------------------------------------------------- +void CModelRender::ComputeModelVertexLighting( IHandleEntity *pProp, + mstudiomodel_t *pModel, OptimizedModel::ModelLODHeader_t *pVtxLOD, + matrix3x4_t& matrix, Vector4D *pTempMem, color24 *pLighting ) +{ +#ifndef SWDS + if ( IsX360() ) + return; + + int i; + unsigned char *pInSolid = (unsigned char*)stackalloc( ((pModel->numvertices + 7) >> 3) * sizeof(unsigned char) ); + Vector worldPos, worldNormal; + + const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData(); + Assert( vertData ); + if ( !vertData ) + return; + + for ( i = 0; i < pModel->numvertices; ++i ) + { + const Vector &pos = *vertData->Position( i ); + const Vector &normal = *vertData->Normal( i ); + VectorTransform( pos, matrix, worldPos ); + VectorRotate( normal, matrix, worldNormal ); + bool bNonSolid = ComputeVertexLightingFromSphericalSamples( worldPos, worldNormal, pProp, &(pTempMem[i].AsVector3D()) ); + + int nByte = i >> 3; + int nBit = i & 0x7; + + if ( bNonSolid ) + { + pTempMem[i].w = 1.0f; + pInSolid[ nByte ] &= ~(1 << nBit); + } + else + { + pTempMem[i].Init( ); + pInSolid[ nByte ] |= (1 << nBit); + } + } + + // Must iterate over each triangle to average out the colors for those + // vertices in solid. + // Iterate over all the meshes.... + for (int meshID = 0; meshID < pModel->nummeshes; ++meshID) + { + Assert( pModel->nummeshes == pVtxLOD->numMeshes ); + mstudiomesh_t* pMesh = pModel->pMesh(meshID); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(meshID); + + // Iterate over all strip groups. + for( int stripGroupID = 0; stripGroupID < pVtxMesh->numStripGroups; ++stripGroupID ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(stripGroupID); + + // Iterate over all indices + Assert( pStripGroup->numIndices % 3 == 0 ); + for (i = 0; i < pStripGroup->numIndices; i += 3) + { + unsigned short nIndex1 = *pStripGroup->pIndex( i ); + unsigned short nIndex2 = *pStripGroup->pIndex( i + 1 ); + unsigned short nIndex3 = *pStripGroup->pIndex( i + 2 ); + + int v[3]; + v[0] = pStripGroup->pVertex( nIndex1 )->origMeshVertID + pMesh->vertexoffset; + v[1] = pStripGroup->pVertex( nIndex2 )->origMeshVertID + pMesh->vertexoffset; + v[2] = pStripGroup->pVertex( nIndex3 )->origMeshVertID + pMesh->vertexoffset; + + Assert( v[0] < pModel->numvertices ); + Assert( v[1] < pModel->numvertices ); + Assert( v[2] < pModel->numvertices ); + + bool bSolid[3]; + bSolid[0] = ( pInSolid[ v[0] >> 3 ] & ( 1 << ( v[0] & 0x7 ) ) ) != 0; + bSolid[1] = ( pInSolid[ v[1] >> 3 ] & ( 1 << ( v[1] & 0x7 ) ) ) != 0; + bSolid[2] = ( pInSolid[ v[2] >> 3 ] & ( 1 << ( v[2] & 0x7 ) ) ) != 0; + + int nValidCount = 0; + int nAverage[3]; + if ( !bSolid[0] ) { nAverage[nValidCount++] = v[0]; } + if ( !bSolid[1] ) { nAverage[nValidCount++] = v[1]; } + if ( !bSolid[2] ) { nAverage[nValidCount++] = v[2]; } + + if ( nValidCount == 3 ) + continue; + + Vector vecAverage( 0, 0, 0 ); + for ( int j = 0; j < nValidCount; ++j ) + { + vecAverage += pTempMem[nAverage[j]].AsVector3D(); + } + + if (nValidCount != 0) + { + vecAverage /= nValidCount; + } + + if ( bSolid[0] ) { pTempMem[ v[0] ].AsVector3D() += vecAverage; pTempMem[ v[0] ].w += 1.0f; } + if ( bSolid[1] ) { pTempMem[ v[1] ].AsVector3D() += vecAverage; pTempMem[ v[1] ].w += 1.0f; } + if ( bSolid[2] ) { pTempMem[ v[2] ].AsVector3D() += vecAverage; pTempMem[ v[2] ].w += 1.0f; } + } + } + } + + Vector destColor; + for ( i = 0; i < pModel->numvertices; ++i ) + { + if ( pTempMem[i].w != 0.0f ) + { + pTempMem[i] /= pTempMem[i].w; + } + + destColor[0] = LinearToVertexLight( pTempMem[i][0] ); + destColor[1] = LinearToVertexLight( pTempMem[i][1] ); + destColor[2] = LinearToVertexLight( pTempMem[i][2] ); + + ColorClampTruncate( destColor ); + + pLighting[i].r = FastFToC(destColor[0]); + pLighting[i].g = FastFToC(destColor[1]); + pLighting[i].b = FastFToC(destColor[2]); + } +#endif +} + +//----------------------------------------------------------------------------- +// Sanity check and setup the compiled color mesh for an optimal async load +// during runtime. +//----------------------------------------------------------------------------- +void CModelRender::ValidateStaticPropColorData( ModelInstanceHandle_t handle ) +{ + if ( !r_proplightingfromdisk.GetBool() ) + { + return; + } + + ModelInstance_t *pInstance = &m_ModelInstances[handle]; + IHandleEntity* pProp = pInstance->m_pRenderable->GetIClientUnknown(); + + if ( !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() || !StaticPropMgr()->IsStaticProp( pProp ) ) + { + // can't support it or not a static prop + return; + } + + if ( !g_bLoadedMapHasBakedPropLighting || StaticPropMgr()->PropHasBakedLightingDisabled( pProp ) ) + { + return; + } + + MEM_ALLOC_CREDIT(); + + // fetch the header + CUtlBuffer utlBuf; + char fileName[MAX_PATH]; + if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE || g_bBakedPropLightingNoSeparateHDR ) + { + Q_snprintf( fileName, sizeof( fileName ), "sp_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); + } + else + { + Q_snprintf( fileName, sizeof( fileName ), "sp_hdr_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); + } + + if ( IsX360() ) + { + DataCacheHandle_t hColorMesh = GetCachedStaticPropColorData( fileName ); + if ( hColorMesh != DC_INVALID_HANDLE ) + { + // already have it + pInstance->m_ColorMeshHandle = hColorMesh; + pInstance->m_nFlags &= ~MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; + pInstance->m_nFlags |= MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR; + return; + } + } + + if ( !g_pFileSystem->ReadFile( fileName, "GAME", utlBuf, sizeof( HardwareVerts::FileHeader_t ), 0 ) ) + { + // not available + return; + } + + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pInstance->m_pModel->studio ); + + HardwareVerts::FileHeader_t *pVhvHdr = (HardwareVerts::FileHeader_t *)utlBuf.Base(); + if ( pVhvHdr->m_nVersion != VHV_VERSION || + pVhvHdr->m_nChecksum != (unsigned int)pStudioHdr->checksum || + pVhvHdr->m_nVertexSize != 4 ) + { + // out of sync + // mark for debug visualization + pInstance->m_nFlags |= MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; + return; + } + + // async callback can safely stream data into targets + pInstance->m_nFlags &= ~MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD; + pInstance->m_nFlags |= MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR; +} + +//----------------------------------------------------------------------------- +// Async loader callback +// Called from async i/o thread - must spend minimal cycles in this context +//----------------------------------------------------------------------------- +void CModelRender::StaticPropColorMeshCallback( void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus ) +{ + // get our preserved data + Assert( pContext ); + staticPropAsyncContext_t *pStaticPropContext = (staticPropAsyncContext_t *)pContext; + + HardwareVerts::FileHeader_t *pVhvHdr; + byte *pOriginalData = NULL; + int numLightingComponents = 1; + + if ( asyncStatus != FSASYNC_OK ) + { + // any i/o error + goto cleanUp; + } + + if ( IsX360() ) + { + // only the 360 has compressed VHV data + // the compressed data is after the header + byte *pCompressedData = (byte *)pData + sizeof( HardwareVerts::FileHeader_t ); + if ( CLZMA::IsCompressed( pCompressedData ) ) + { + // create a buffer that matches the original + int actualSize = CLZMA::GetActualSize( pCompressedData ); + pOriginalData = (byte *)malloc( sizeof( HardwareVerts::FileHeader_t ) + actualSize ); + + // place the header, then uncompress directly after it + V_memcpy( pOriginalData, pData, sizeof( HardwareVerts::FileHeader_t ) ); + int outputLength = CLZMA::Uncompress( pCompressedData, pOriginalData + sizeof( HardwareVerts::FileHeader_t ) ); + if ( outputLength != actualSize ) + { + goto cleanUp; + } + pData = pOriginalData; + } + } + + pVhvHdr = (HardwareVerts::FileHeader_t *)pData; + + int startMesh; + for ( startMesh=0; startMesh<pVhvHdr->m_nMeshes; startMesh++ ) + { + // skip past higher detail lod meshes that must be ignored + // find first mesh that matches desired lod + if ( pVhvHdr->pMesh( startMesh )->m_nLod == pStaticPropContext->m_nRootLOD ) + { + break; + } + } + + int meshID; + for ( meshID = startMesh; meshID<pVhvHdr->m_nMeshes; meshID++ ) + { + int numVertexes = pVhvHdr->pMesh( meshID )->m_nVertexes; + if ( numVertexes != pStaticPropContext->m_pColorMeshData->m_pMeshInfos[meshID-startMesh].m_nNumVerts ) + { + // meshes are out of sync, discard data + break; + } + + int nID = meshID-startMesh; + + unsigned char *pIn = (unsigned char *) pVhvHdr->pVertexBase( meshID ); + unsigned char *pOut = NULL; + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ nID ].m_pMesh, MATERIAL_HETEROGENOUS, numVertexes, 0 ); + if ( numLightingComponents > 1 ) + { + pOut = reinterpret_cast< unsigned char * >( const_cast< float * >( meshBuilder.Normal() ) ); + } + else + { + pOut = meshBuilder.Specular(); + } + +#ifdef DX_TO_GL_ABSTRACTION + // OPENGL_SWAP_COLORS + for ( int i=0; i < (numVertexes * numLightingComponents ); i++ ) + { + unsigned char red = *pIn++; + unsigned char green = *pIn++; + unsigned char blue = *pIn++; + *pOut++ = blue; + *pOut++ = green; + *pOut++ = red; + *pOut++ = *pIn++; // Alpha goes straight across + } +#else + V_memcpy( pOut, pIn, numVertexes * 4 * numLightingComponents ); +#endif + meshBuilder.End(); + } +cleanUp: + if ( IsX360() ) + { + AUTO_LOCK( m_CachedStaticPropMutex ); + // track the color mesh's datacache handle so that we can find it long after the model instance's are gone + // the static prop filenames are guaranteed uniquely decorated + m_CachedStaticPropColorData.Insert( pStaticPropContext->m_szFilenameVertex, pStaticPropContext->m_ColorMeshHandle ); + + // No support for lightmap textures on X360. + } + + // mark as completed in single atomic operation + pStaticPropContext->m_pColorMeshData->m_bColorMeshValid = true; + CacheUnlock( pStaticPropContext->m_ColorMeshHandle ); + delete pStaticPropContext; + + if ( pOriginalData ) + { + free( pOriginalData ); + } +} + +//----------------------------------------------------------------------------- +// Async loader callback +// Called from async i/o thread - must spend minimal cycles in this context +//----------------------------------------------------------------------------- +void CModelRender::StaticPropColorTexelCallback(void *pContext, const void *pData, int numReadBytes, FSAsyncStatus_t asyncStatus) +{ + // get our preserved data + Assert(pContext); + staticPropAsyncContext_t *pStaticPropContext = (staticPropAsyncContext_t *)pContext; + + HardwareTexels::FileHeader_t *pVhtHdr; + + // This needs to be above the goto or clang complains "goto into protected scope." + bool anyTextures = false; + + if (asyncStatus != FSASYNC_OK) + { + // any i/o error + goto cleanUp; + } + + pVhtHdr = (HardwareTexels::FileHeader_t *)pData; + + int startMesh; + for (startMesh = 0; startMesh < pVhtHdr->m_nMeshes; startMesh++) + { + // skip past higher detail lod meshes that must be ignored + // find first mesh that matches desired lod + if (pVhtHdr->pMesh(startMesh)->m_nLod == pStaticPropContext->m_nRootLOD) + { + break; + } + } + + + + int meshID; + for ( meshID = startMesh; meshID < pVhtHdr->m_nMeshes; meshID++ ) + { + const HardwareTexels::MeshHeader_t* pMeshData = pVhtHdr->pMesh( meshID ); + + // We can't create the real texture here because that's just how the material system works. + // So instead, squirrel away what we need for later. + ColorTexelsInfo_t* newCTI = new ColorTexelsInfo_t; + newCTI->m_nWidth = pMeshData->m_nWidth; + newCTI->m_nHeight = pMeshData->m_nHeight; + newCTI->m_nMipmapCount = ImageLoader::GetNumMipMapLevels( newCTI->m_nWidth, newCTI->m_nHeight ); + newCTI->m_ImageFormat = ( ImageFormat ) pVhtHdr->m_nTexelFormat; + newCTI->m_nByteCount = pVhtHdr->pMesh( meshID )->m_nBytes; + newCTI->m_pTexelData = new byte[ newCTI->m_nByteCount ]; + Q_memcpy( newCTI->m_pTexelData, pVhtHdr->pTexelBase( meshID ), newCTI->m_nByteCount ); + + pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ meshID - startMesh ].m_pLightmapData = newCTI; + Assert( pStaticPropContext->m_pColorMeshData->m_pMeshInfos[ meshID - startMesh ].m_pLightmap == NULL ); + anyTextures = true; + } + + // This only gets set if we actually have texel data. Otherwise, it remains false. + pStaticPropContext->m_pColorMeshData->m_bColorTextureValid = anyTextures; + +cleanUp: + // mark as completed in single atomic operation + CacheUnlock( pStaticPropContext->m_ColorMeshHandle ); + delete pStaticPropContext; +} + +//----------------------------------------------------------------------------- +// Async loader callback +// Called from async i/o thread - must spend minimal cycles in this context +//----------------------------------------------------------------------------- +static void StaticPropColorMeshCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) +{ + s_ModelRender.StaticPropColorMeshCallback( request.pContext, request.pData, numReadBytes, asyncStatus ); +} + +//----------------------------------------------------------------------------- +// Async loader callback +// Called from async i/o thread - must spend minimal cycles in this context +//----------------------------------------------------------------------------- +static void StaticPropColorTexelCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) +{ + s_ModelRender.StaticPropColorTexelCallback( request.pContext, request.pData, numReadBytes, asyncStatus ); +} + + +//----------------------------------------------------------------------------- +// Queued loader callback +// Called from async i/o thread - must spend minimal cycles in this context +//----------------------------------------------------------------------------- +static void QueuedLoaderCallback_PropLighting( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) +{ + // translate error + FSAsyncStatus_t asyncStatus = ( loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_READING ); + + // mimic async i/o completion + s_ModelRender.StaticPropColorMeshCallback( pContext, pData, nSize, asyncStatus ); +} + +//----------------------------------------------------------------------------- +// Loads the serialized static prop color data. +// Returns false if legacy path should be used. +//----------------------------------------------------------------------------- +bool CModelRender::LoadStaticPropColorData( IHandleEntity *pProp, DataCacheHandle_t colorMeshHandle, studiohwdata_t *pStudioHWData ) +{ + if ( !g_bLoadedMapHasBakedPropLighting || !r_proplightingfromdisk.GetBool() ) + { + return false; + } + + // lock the mesh memory during async transfer + // the color meshes should already have low quality data to be used during rendering + CColorMeshData *pColorMeshData = CacheLock( colorMeshHandle ); + if ( !pColorMeshData ) + { + return false; + } + + if ( pColorMeshData->m_hAsyncControlVertex || pColorMeshData->m_hAsyncControlTexel ) + { + // load in progress, ignore additional request + // or already loaded, ignore until discarded from cache + CacheUnlock( colorMeshHandle ); + return true; + } + + // each static prop has its own compiled color mesh + char fileName[MAX_PATH]; + if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE || g_bBakedPropLightingNoSeparateHDR ) + { + Q_snprintf( fileName, sizeof( fileName ), "sp_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); + } + else + { + Q_snprintf( fileName, sizeof( fileName ), "sp_hdr_%d%s.vhv", StaticPropMgr()->GetStaticPropIndex( pProp ), GetPlatformExt() ); + } + + // mark as invalid, async callback will set upon completion + // prevents rendering during async transfer into locked mesh, otherwise d3drip + pColorMeshData->m_bColorMeshValid = false; + pColorMeshData->m_bColorTextureValid = false; + pColorMeshData->m_bColorTextureCreated = false; + + + // async load high quality lighting from file + // can't optimal async yet, because need flat ppColorMesh[], so use callback to distribute + // create our private context of data for the callback + staticPropAsyncContext_t *pContextVertex = new staticPropAsyncContext_t; + pContextVertex->m_nRootLOD = pStudioHWData->m_RootLOD; + pContextVertex->m_nMeshes = pColorMeshData->m_nMeshes; + pContextVertex->m_ColorMeshHandle = colorMeshHandle; + pContextVertex->m_pColorMeshData = pColorMeshData; + V_strncpy( pContextVertex->m_szFilenameVertex, fileName, sizeof( pContextVertex->m_szFilenameVertex ) ); + + if ( IsX360() && g_pQueuedLoader->IsMapLoading() ) + { + if ( !g_pQueuedLoader->ClaimAnonymousJob( fileName, QueuedLoaderCallback_PropLighting, (void *)pContextVertex ) ) + { + // not there as expected + // as a less optimal fallback during loading, issue as a standard queued loader job + LoaderJob_t loaderJob; + loaderJob.m_pFilename = fileName; + loaderJob.m_pPathID = "GAME"; + loaderJob.m_pCallback = QueuedLoaderCallback_PropLighting; + loaderJob.m_pContext = (void *)pContextVertex; + loaderJob.m_Priority = LOADERPRIORITY_BEFOREPLAY; + g_pQueuedLoader->AddJob( &loaderJob ); + } + return true; + } + + // async load the file + FileAsyncRequest_t fileRequest; + fileRequest.pContext = (void *)pContextVertex; + fileRequest.pfnCallback = ::StaticPropColorMeshCallback; + fileRequest.pData = NULL; + fileRequest.pszFilename = fileName; + fileRequest.nOffset = 0; + fileRequest.flags = 0; // FSASYNC_FLAGS_SYNC; + fileRequest.nBytes = 0; + fileRequest.priority = -1; + fileRequest.pszPathID = "GAME"; + + // This must be done before sending pContextVertex down + staticPropAsyncContext_t* pContextTexel = new staticPropAsyncContext_t( *pContextVertex ); + + // queue vertex data for async load + { + MEM_ALLOC_CREDIT(); + g_pFileSystem->AsyncRead( fileRequest, &pColorMeshData->m_hAsyncControlVertex ); + } + + Q_snprintf( fileName, sizeof( fileName ), "texelslighting_%d.ppl", StaticPropMgr()->GetStaticPropIndex( pProp ) ); + V_strncpy( pContextTexel->m_szFilenameTexel, fileName, sizeof( pContextTexel->m_szFilenameTexel ) ); + + + // We are already locked, but we will unlock twice--so lock once more for the texel processing. + CacheLock( colorMeshHandle ); + + // queue texel data for async load + fileRequest.pContext = pContextTexel; + fileRequest.pfnCallback = ::StaticPropColorTexelCallback; + fileRequest.pData = NULL; + fileRequest.pszFilename = fileName; // This doesn't need to happen, but included for clarity. + + { + MEM_ALLOC_CREDIT(); + g_pFileSystem->AsyncRead( fileRequest, &pColorMeshData->m_hAsyncControlTexel ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Computes the static prop color data. +// Data calculation may be delayed if data is disk based. +// Returns FALSE if data not available or error. For retry polling pattern. +// Resturns TRUE if operation succesful or in progress (succeeds later). +//----------------------------------------------------------------------------- +bool CModelRender::UpdateStaticPropColorData( IHandleEntity *pProp, ModelInstanceHandle_t handle ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + +#ifndef SWDS + // find or allocate color meshes + CColorMeshData *pColorMeshData = FindOrCreateStaticPropColorData( handle ); + if ( !pColorMeshData ) + { + return false; + } + + // HACK: on PC, VB creation can fail due to device loss + if ( IsPC() && pColorMeshData->m_bHasInvalidVB ) + { + // Don't retry until color data is flushed by device restore + pColorMeshData->m_bColorMeshValid = false; + pColorMeshData->m_bNeedsRetry = false; + return false; + } + + unsigned char debugColor[3] = {0}; + bool bDebugColor = false; + if ( r_debugrandomstaticlighting.GetBool() ) + { + // randomize with bright colors, skip black and white + // purposely not deterministic to catch bugs with excessive re-baking (i.e. disco) + Vector fRandomColor; + int nColor = RandomInt(1,6); + fRandomColor.x = (nColor>>2) & 1; + fRandomColor.y = (nColor>>1) & 1; + fRandomColor.z = nColor & 1; + VectorNormalize( fRandomColor ); + debugColor[0] = fRandomColor[0] * 255.0f; + debugColor[1] = fRandomColor[1] * 255.0f; + debugColor[2] = fRandomColor[2] * 255.0f; + bDebugColor = true; + } + + // FIXME? : Move this to StudioRender? + ModelInstance_t &inst = m_ModelInstances[handle]; + Assert( inst.m_pModel ); + Assert( modelloader->IsLoaded( inst.m_pModel ) && ( inst.m_pModel->type == mod_studio ) ); + + if ( r_proplightingfromdisk.GetInt() == 2 ) + { + // This visualization debug mode is strictly to debug which static prop models have valid disk + // based lighting. There should be no red models, only green or yellow. Yellow models denote the legacy + // lower quality runtime baked lighting. + if ( inst.m_nFlags & MODEL_INSTANCE_DISKCOMPILED_COLOR_BAD ) + { + // prop was compiled for static prop lighting, but out of sync + // bad disk data for model, show as red + debugColor[0] = 255.0f; + debugColor[1] = 0; + debugColor[2] = 0; + } + else if ( inst.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) + { + // valid disk data, show as green + debugColor[0] = 0; + debugColor[1] = 255.0f; + debugColor[2] = 0; + } + else + { + // no disk based data, using runtime method, show as yellow + // identifies a prop that wasn't compiled for static prop lighting + debugColor[0] = 255.0f; + debugColor[1] = 255.0f; + debugColor[2] = 0; + } + bDebugColor = true; + } + + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( inst.m_pModel->studio ); + studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( inst.m_pModel->studio ); + Assert( pStudioHdr && pStudioHWData ); + + if ( !bDebugColor && ( inst.m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) ) + { + // start an async load on available higher quality disc based data + if ( LoadStaticPropColorData( pProp, inst.m_ColorMeshHandle, pStudioHWData ) ) + { + // async in progress, operation expected to succeed + // async callback handles finalization + return true; + } + } + + // lighting calculation path + // calculation may abort due to lack of async requested data, caller should retry + pColorMeshData->m_bColorMeshValid = false; + pColorMeshData->m_bColorTextureValid = false; + pColorMeshData->m_bColorTextureCreated = false; + pColorMeshData->m_bNeedsRetry = true; + + if ( !bDebugColor ) + { + // vertexes must be available for lighting calculation + vertexFileHeader_t *pVertexHdr = g_pMDLCache->GetVertexData( (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff ); + if ( !pVertexHdr ) + { + // data not available yet + return false; + } + } + + inst.m_nFlags |= MODEL_INSTANCE_HAS_COLOR_DATA; + + // calculate lighting, set for access to verts + m_pStudioHdr = pStudioHdr; + + // Sets the model transform state in g_pStudioRender + matrix3x4_t matrix; + AngleMatrix( inst.m_pRenderable->GetRenderAngles(), inst.m_pRenderable->GetRenderOrigin(), matrix ); + + // Get static lighting only!! We'll add dynamic and lightstyles in in the vertex shader. . . + unsigned int lightCacheFlags = LIGHTCACHEFLAGS_STATIC; + if ( !g_pMaterialSystemHardwareConfig->SupportsStaticPlusDynamicLighting() ) + { + // . . . unless we can't do anything but static or dynamic simulaneously. . then + // we'll bake the lightstyle info here. + lightCacheFlags |= LIGHTCACHEFLAGS_LIGHTSTYLE; + } + + LightingState_t lightingState; + if ( (inst.m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING) && inst.m_LightCacheHandle ) + { + lightingState = *(LightcacheGetStatic( inst.m_LightCacheHandle, NULL, lightCacheFlags )); + } + else + { + // Choose the lighting origin + Vector entOrigin; + R_ComputeLightingOrigin( inst.m_pRenderable, pStudioHdr, matrix, entOrigin ); + LightcacheGetDynamic_Stats stats; + LightcacheGetDynamic( entOrigin, lightingState, stats, lightCacheFlags ); + } + + // See if the studiohdr wants to use constant directional light, ie + // the surface normal plays no part in determining light intensity + bool bUseConstDirLighting = false; + float flConstDirLightingAmount = 0.0; + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT ) + { + bUseConstDirLighting = true; + flConstDirLightingAmount = (float)( pStudioHdr->constdirectionallightdot ) / 255.0; + } + + CUtlMemory< color24 > tmpLightingMem; + + // Iterate over every body part... + for ( int bodyPartID = 0; bodyPartID < pStudioHdr->numbodyparts; ++bodyPartID ) + { + mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart( bodyPartID ); + + // Iterate over every submodel... + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + mstudiomodel_t* pModel = pBodyPart->pModel(modelID); + + if ( pModel->numvertices == 0 ) + continue; + + // Make sure we've got enough space allocated + tmpLightingMem.EnsureCapacity( pModel->numvertices ); + + if ( !bDebugColor ) + { + // Compute lighting for each unique vertex in the model exactly once + ComputeModelVertexLightingOld( pModel, matrix, lightingState, tmpLightingMem.Base(), bUseConstDirLighting, flConstDirLightingAmount ); + } + else + { + for ( int i=0; i<pModel->numvertices; i++ ) + { + tmpLightingMem[i].r = debugColor[0]; + tmpLightingMem[i].g = debugColor[1]; + tmpLightingMem[i].b = debugColor[2]; + } + } + + // distribute the lighting results to the mesh's vertexes + for ( int lodID = pStudioHWData->m_RootLOD; lodID < pStudioHWData->m_NumLODs; ++lodID ) + { + studioloddata_t *pStudioLODData = &pStudioHWData->m_pLODs[lodID]; + studiomeshdata_t *pStudioMeshData = pStudioLODData->m_pMeshData; + + // Iterate over all the meshes.... + for ( int meshID = 0; meshID < pModel->nummeshes; ++meshID) + { + mstudiomesh_t* pMesh = pModel->pMesh( meshID ); + + // Iterate over all strip groups. + for ( int stripGroupID = 0; stripGroupID < pStudioMeshData[pMesh->meshid].m_NumGroup; ++stripGroupID ) + { + studiomeshgroup_t* pMeshGroup = &pStudioMeshData[pMesh->meshid].m_pMeshGroup[stripGroupID]; + ColorMeshInfo_t* pColorMeshInfo = &pColorMeshData->m_pMeshInfos[pMeshGroup->m_ColorMeshID]; + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pColorMeshInfo->m_pMesh, MATERIAL_HETEROGENOUS, pMeshGroup->m_NumVertices, 0 ); + + if ( !meshBuilder.VertexSize() ) + { + meshBuilder.End(); + return false; // Aborting processing, since something was wrong with D3D + } + + // We need to account for the stream offset used by pool-allocated (static-lit) color meshes: + int streamOffset = pColorMeshInfo->m_nVertOffsetInBytes / meshBuilder.VertexSize(); + meshBuilder.AdvanceVertices( streamOffset ); + + // Iterate over all vertices + for ( int i = 0; i < pMeshGroup->m_NumVertices; ++i) + { + int nVertIndex = pMesh->vertexoffset + pMeshGroup->m_pGroupIndexToMeshIndex[i]; + Assert( nVertIndex < pModel->numvertices ); + meshBuilder.Specular3ub( tmpLightingMem[nVertIndex].r, tmpLightingMem[nVertIndex].g, tmpLightingMem[nVertIndex].b ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + } + } + } + } + } + + pColorMeshData->m_bColorMeshValid = true; + pColorMeshData->m_bNeedsRetry = false; +#endif + + return true; +} + + +//----------------------------------------------------------------------------- +// FIXME? : Move this to StudioRender? +//----------------------------------------------------------------------------- +void CModelRender::DestroyStaticPropColorData( ModelInstanceHandle_t handle ) +{ +#ifndef SWDS + if ( handle == MODEL_INSTANCE_INVALID ) + return; + + if ( m_ModelInstances[handle].m_ColorMeshHandle != DC_INVALID_HANDLE ) + { + CacheRemove( m_ModelInstances[handle].m_ColorMeshHandle ); + m_ModelInstances[handle].m_ColorMeshHandle = DC_INVALID_HANDLE; + } +#endif +} + + +void CModelRender::ReleaseAllStaticPropColorData( void ) +{ + FOR_EACH_LL( m_ModelInstances, i ) + { + DestroyStaticPropColorData( i ); + } + if ( IsX360() ) + { + PurgeCachedStaticPropColorData(); + } +} + + +void CModelRender::RestoreAllStaticPropColorData( void ) +{ +#if !defined( SWDS ) + if ( !host_state.worldmodel ) + return; + + // invalidate all static lighting cache data + InvalidateStaticLightingCache(); + + // rebake + FOR_EACH_LL( m_ModelInstances, i ) + { + UpdateStaticPropColorData( m_ModelInstances[i].m_pRenderable->GetIClientUnknown(), i ); + } +#endif +} + +void RestoreAllStaticPropColorData( void ) +{ + s_ModelRender.RestoreAllStaticPropColorData(); +} + + +//----------------------------------------------------------------------------- +// Creates, destroys instance data to be associated with the model +//----------------------------------------------------------------------------- +ModelInstanceHandle_t CModelRender::CreateInstance( IClientRenderable *pRenderable, LightCacheHandle_t *pCache ) +{ + Assert( pRenderable ); + + // ensure all components are available + model_t *pModel = (model_t*)pRenderable->GetModel(); + + // We're ok, allocate a new instance handle + ModelInstanceHandle_t handle = m_ModelInstances.AddToTail(); + ModelInstance_t& instance = m_ModelInstances[handle]; + + instance.m_pRenderable = pRenderable; + instance.m_DecalHandle = STUDIORENDER_DECAL_INVALID; + instance.m_pModel = (model_t*)pModel; + instance.m_ColorMeshHandle = DC_INVALID_HANDLE; + instance.m_flLightingTime = CURRENT_LIGHTING_UNINITIALIZED; + instance.m_nFlags = 0; + instance.m_LightCacheHandle = 0; + + instance.m_AmbientLightingState.ZeroLightingState(); + for ( int i = 0; i < 6; ++i ) + { + // To catch errors with uninitialized m_AmbientLightingState... + // force to pure red + instance.m_AmbientLightingState.r_boxcolor[i].x = 1.0; + } + +#ifndef SWDS + instance.m_FirstShadow = g_pShadowMgr->InvalidShadowIndex(); +#endif + + // Static props use baked lighting for performance reasons + if ( pCache ) + { + SetStaticLighting( handle, pCache ); + + // validate static color meshes once, now at load/create time + ValidateStaticPropColorData( handle ); + + // 360 persists the color meshes across same map loads + if ( !IsX360() || instance.m_ColorMeshHandle == DC_INVALID_HANDLE ) + { + // builds out color meshes or loads disk colors, now at load/create time + RecomputeStaticLighting( handle ); + } + else + if ( r_decalstaticprops.GetBool() && instance.m_LightCacheHandle ) + { + instance.m_AmbientLightingState = *(LightcacheGetStatic( *pCache, NULL, LIGHTCACHEFLAGS_STATIC )); + } + } + + return handle; +} + + +//----------------------------------------------------------------------------- +// Assigns static lighting to the model instance +//----------------------------------------------------------------------------- +void CModelRender::SetStaticLighting( ModelInstanceHandle_t handle, LightCacheHandle_t *pCache ) +{ + // FIXME: If we make static lighting available for client-side props, + // we must clean up the lightcache handles as the model instances are removed. + // At the moment, since only the static prop manager uses this, it cleans up all LightCacheHandles + // at level shutdown. + + // The reason I moved the lightcache handles into here is because this place needs + // to know about lighting overrides when restoring meshes for alt-tab reasons + // It was a real pain to do this from within the static prop mgr, where the + // lightcache handle used to reside + if (handle != MODEL_INSTANCE_INVALID) + { + ModelInstance_t& instance = m_ModelInstances[handle]; + if ( pCache ) + { + instance.m_LightCacheHandle = *pCache; + instance.m_nFlags |= MODEL_INSTANCE_HAS_STATIC_LIGHTING; + } + else + { + instance.m_LightCacheHandle = 0; + instance.m_nFlags &= ~MODEL_INSTANCE_HAS_STATIC_LIGHTING; + } + } +} + +LightCacheHandle_t CModelRender::GetStaticLighting( ModelInstanceHandle_t handle ) +{ + if (handle != MODEL_INSTANCE_INVALID) + { + ModelInstance_t& instance = m_ModelInstances[handle]; + if ( instance.m_nFlags & MODEL_INSTANCE_HAS_STATIC_LIGHTING ) + return instance.m_LightCacheHandle; + return 0; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// This gets called when overbright, etc gets changed to recompute static prop lighting. +// Returns FALSE if needed async data not available to complete computation or an error (don't draw). +// Returns TRUE if operation succeeded or computation skipped (ok to draw). +// Callers use this to track state in a retry pattern, so the expensive computation +// only happens once as needed or can continue to be polled until success. +//----------------------------------------------------------------------------- +bool CModelRender::RecomputeStaticLighting( ModelInstanceHandle_t handle ) +{ +#ifndef SWDS + if ( handle == MODEL_INSTANCE_INVALID ) + { + return false; + } + + if ( !g_pMaterialSystemHardwareConfig->SupportsColorOnSecondStream() ) + { + // static lighting not supported, but callers can proceed + return true; + } + + ModelInstance_t& instance = m_ModelInstances[handle]; + Assert( modelloader->IsLoaded( instance.m_pModel ) && ( instance.m_pModel->type == mod_studio ) ); + + // get data, possibly delayed due to async + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( instance.m_pModel->studio ); + if ( !pStudioHdr ) + { + // data not available + return false; + } + + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) + { + // get data, possibly delayed due to async + studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( instance.m_pModel->studio ); + if ( !pStudioHWData ) + { + // data not available + return false; + } + + if ( r_decalstaticprops.GetBool() && instance.m_LightCacheHandle ) + { + instance.m_AmbientLightingState = *(LightcacheGetStatic( instance.m_LightCacheHandle, NULL, LIGHTCACHEFLAGS_STATIC )); + } + + return UpdateStaticPropColorData( instance.m_pRenderable->GetIClientUnknown(), handle ); + } + +#endif + // success + return true; +} + +void CModelRender::PurgeCachedStaticPropColorData( void ) +{ + // valid for 360 only + Assert( IsX360() ); + if ( IsPC() ) + { + return; + } + + // flush all the color mesh data + GetCacheSection()->Flush( true, true ); + DataCacheStatus_t status; + GetCacheSection()->GetStatus( &status ); + if ( status.nBytes ) + { + DevWarning( "CModelRender: ColorMesh %d bytes failed to flush!\n", status.nBytes ); + } + + m_colorMeshVBAllocator.Clear(); + m_CachedStaticPropColorData.Purge(); +} + +bool CModelRender::IsStaticPropColorDataCached( const char *pName ) +{ + // valid for 360 only + Assert( IsX360() ); + if ( IsPC() ) + { + return false; + } + + DataCacheHandle_t hColorMesh = DC_INVALID_HANDLE; + { + AUTO_LOCK( m_CachedStaticPropMutex ); + int iIndex = m_CachedStaticPropColorData.Find( pName ); + if ( m_CachedStaticPropColorData.IsValidIndex( iIndex ) ) + { + hColorMesh = m_CachedStaticPropColorData[iIndex]; + } + } + + CColorMeshData *pColorMeshData = CacheGetNoTouch( hColorMesh ); + if ( pColorMeshData ) + { + // color mesh data is in cache + return true; + } + + return false; +} + +DataCacheHandle_t CModelRender::GetCachedStaticPropColorData( const char *pName ) +{ + // valid for 360 only + Assert( IsX360() ); + if ( IsPC() ) + { + return DC_INVALID_HANDLE; + } + + DataCacheHandle_t hColorMesh = DC_INVALID_HANDLE; + { + AUTO_LOCK( m_CachedStaticPropMutex ); + int iIndex = m_CachedStaticPropColorData.Find( pName ); + if ( m_CachedStaticPropColorData.IsValidIndex( iIndex ) ) + { + hColorMesh = m_CachedStaticPropColorData[iIndex]; + } + } + + return hColorMesh; +} + +void CModelRender::SetupColorMeshes( int nTotalVerts ) +{ + Assert( IsX360() ); + if ( IsPC() ) + { + return; + } + + if ( !g_pQueuedLoader->IsMapLoading() ) + { + // oops, the queued loader didn't run which does the pre-purge cleanup + // do the cleanup now + PurgeCachedStaticPropColorData(); + } + + // Set up the appropriate default value for color mesh pooling + if ( r_proplightingpooling.GetInt() == -1 ) + { + // This is useful on X360 because VBs are 4-KB aligned, so using a shared VB saves tons of memory + r_proplightingpooling.SetValue( true ); + } + + if ( r_proplightingpooling.GetInt() == 1 ) + { + if ( m_colorMeshVBAllocator.GetNumVertsAllocated() == 0 ) + { + if ( nTotalVerts ) + { + // Allocate a mesh (vertex buffer) big enough to accommodate all static prop color meshes + // (which are allocated inside CModelRender::FindOrCreateStaticPropColorData() ): + m_colorMeshVBAllocator.Init( VERTEX_SPECULAR, nTotalVerts ); + } + } + else + { + // already allocated + // 360 keeps the color meshes during same map loads + // vb allocator already allocated, needs to match + Assert( m_colorMeshVBAllocator.GetNumVertsAllocated() == nTotalVerts ); + } + } +} + +void CModelRender::DestroyInstance( ModelInstanceHandle_t handle ) +{ + if ( handle == MODEL_INSTANCE_INVALID ) + return; + + g_pStudioRender->DestroyDecalList( m_ModelInstances[handle].m_DecalHandle ); +#ifndef SWDS + g_pShadowMgr->RemoveAllShadowsFromModel( handle ); +#endif + + // 360 holds onto static prop disk color data only, to avoid redundant work during same map load + // can only persist props with disk based lighting + // check for dvd mode as a reasonable assurance that the queued loader will be responsible for a possible purge + // if the queued loader doesn't run, the purge will get caught later than intended + bool bPersistLighting = IsX360() && + ( m_ModelInstances[handle].m_nFlags & MODEL_INSTANCE_HAS_DISKCOMPILED_COLOR ) && + ( g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT ); + if ( !bPersistLighting ) + { + DestroyStaticPropColorData( handle ); + } + + m_ModelInstances.Remove( handle ); +} + +bool CModelRender::ChangeInstance( ModelInstanceHandle_t handle, IClientRenderable *pRenderable ) +{ + if ( handle == MODEL_INSTANCE_INVALID || !pRenderable ) + return false; + + ModelInstance_t& instance = m_ModelInstances[handle]; + + if ( instance.m_pModel != pRenderable->GetModel() ) + { + DevMsg("MoveInstanceHandle: models are different!\n"); + return false; + } + + // ok, models are the same, change renderable pointer + instance.m_pRenderable = pRenderable; + + return true; +} + + +//----------------------------------------------------------------------------- +// It's not valid if the model index changed + we have non-zero instance data +//----------------------------------------------------------------------------- +bool CModelRender::IsModelInstanceValid( ModelInstanceHandle_t handle ) +{ + if ( handle == MODEL_INSTANCE_INVALID ) + return false; + + ModelInstance_t& inst = m_ModelInstances[handle]; + if ( inst.m_DecalHandle == STUDIORENDER_DECAL_INVALID ) + return false; + + model_t const* pModel = inst.m_pRenderable->GetModel(); + return inst.m_pModel == pModel; +} + + +//----------------------------------------------------------------------------- +// Creates a decal on a model instance by doing a planar projection +//----------------------------------------------------------------------------- +void CModelRender::AddDecal( ModelInstanceHandle_t handle, Ray_t const& ray, + const Vector& decalUp, int decalIndex, int body, bool noPokeThru, int maxLODToDecal ) +{ + Color cColorTemp; + AddDecalInternal( handle, ray, decalUp, decalIndex, body, false, cColorTemp, noPokeThru, maxLODToDecal ); +} + +//----------------------------------------------------------------------------- +void CModelRender::AddColoredDecal( ModelInstanceHandle_t handle, Ray_t const& ray, + const Vector& decalUp, int decalIndex, int body, Color cColor, bool noPokeThru, int maxLODToDecal ) +{ + AddDecalInternal( handle, ray, decalUp, decalIndex, body, true, cColor, noPokeThru, maxLODToDecal ); +} + +//----------------------------------------------------------------------------- +void CModelRender::GetMaterialOverride( IMaterial** ppOutForcedMaterial, OverrideType_t* pOutOverrideType ) +{ + g_pStudioRender->GetMaterialOverride( ppOutForcedMaterial, pOutOverrideType ); +} + +//----------------------------------------------------------------------------- +void CModelRender::AddDecalInternal( ModelInstanceHandle_t handle, Ray_t const& ray, + const Vector& decalUp, int decalIndex, int body, bool bUseColor, Color cColor, bool noPokeThru, int maxLODToDecal) +{ + if (handle == MODEL_INSTANCE_INVALID) + return; + + // Get the decal material + radius + IMaterial* pDecalMaterial; + float w, h; + R_DecalGetMaterialAndSize( decalIndex, pDecalMaterial, w, h ); + if ( !pDecalMaterial ) + { + DevWarning("Bad decal index %d\n", decalIndex ); + return; + } + w *= 0.5f; + h *= 0.5f; + + // FIXME: For now, don't render fading decals on props... + bool found = false; + pDecalMaterial->FindVar( "$decalFadeDuration", &found, false ); + if ( found ) + return; + + if ( bUseColor ) + { + IMaterialVar *pColor = pDecalMaterial->FindVar( "$color2", &found, false ); + if ( found ) + { + // expects a 0..1 value. Input is 0 to 255 + pColor->SetVecValue( cColor.r() / 255.0f, cColor.g() / 255.0f, cColor.b() / 255.0f ); + } + } + + // FIXME: Pass w and h into AddDecal + float radius = (w > h) ? w : h; + + ModelInstance_t& inst = m_ModelInstances[handle]; + if (!IsModelInstanceValid(handle)) + { + g_pStudioRender->DestroyDecalList(inst.m_DecalHandle); + inst.m_DecalHandle = STUDIORENDER_DECAL_INVALID; + } + + Assert( modelloader->IsLoaded( inst.m_pModel ) && ( inst.m_pModel->type == mod_studio ) ); + if ( inst.m_DecalHandle == STUDIORENDER_DECAL_INVALID ) + { + studiohwdata_t *pStudioHWData = g_pMDLCache->GetHardwareData( inst.m_pModel->studio ); + inst.m_DecalHandle = g_pStudioRender->CreateDecalList( pStudioHWData ); + } + + matrix3x4_t *pBoneToWorld = SetupModelState( inst.m_pRenderable ); + g_pStudioRender->AddDecal( inst.m_DecalHandle, g_pMDLCache->GetStudioHdr( inst.m_pModel->studio ), + pBoneToWorld, ray, decalUp, pDecalMaterial, radius, body, noPokeThru, maxLODToDecal ); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes all the decals on a model instance +//----------------------------------------------------------------------------- +void CModelRender::RemoveAllDecals( ModelInstanceHandle_t handle ) +{ + if (handle == MODEL_INSTANCE_INVALID) + return; + + ModelInstance_t& inst = m_ModelInstances[handle]; + if (!IsModelInstanceValid(handle)) + return; + + g_pStudioRender->DestroyDecalList( inst.m_DecalHandle ); + inst.m_DecalHandle = STUDIORENDER_DECAL_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelRender::RemoveAllDecalsFromAllModels() +{ + for ( ModelInstanceHandle_t i = m_ModelInstances.Head(); + i != m_ModelInstances.InvalidIndex(); + i = m_ModelInstances.Next( i ) ) + { + RemoveAllDecals( i ); + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) +{ + // make requested data resident + Assert( pModelData == NULL ); + return s_ModelRender.CacheVertexData(); +} + +bool CheckVarRange_r_rootlod() +{ + return CheckVarRange_Generic( &r_rootlod, 0, 2 ); +} + +bool CheckVarRange_r_lod() +{ + return CheckVarRange_Generic( &r_lod, -1, 2 ); +} + + +// Convar callback to change lod +//----------------------------------------------------------------------------- +void r_lod_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + CheckVarRange_r_lod(); +} + +//----------------------------------------------------------------------------- +// Convar callback to change root lod +//----------------------------------------------------------------------------- +void SetRootLOD_f( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + // Make sure the variable is in range. + if ( CheckVarRange_r_rootlod() ) + return; // was called recursively. + + ConVarRef var( pConVar ); + UpdateStudioRenderConfig(); + if ( !g_LostVideoMemory && Q_strcmp( var.GetString(), pOldString ) ) + { + // reload only the necessary models to desired lod + modelloader->Studio_ReloadModels( IModelLoader::RELOAD_LOD_CHANGED ); + } +} + +//----------------------------------------------------------------------------- +// Discard and reload (rebuild, rebake, etc) models to the current lod +//----------------------------------------------------------------------------- +void FlushLOD_f() +{ + UpdateStudioRenderConfig(); + if ( !g_LostVideoMemory ) + { + // force a full discard and rebuild of all loaded models + modelloader->Studio_ReloadModels( IModelLoader::RELOAD_EVERYTHING ); + } +} + +//----------------------------------------------------------------------------- +// +// CPooledVBAllocator_ColorMesh implementation +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// CPooledVBAllocator_ColorMesh constructor +//----------------------------------------------------------------------------- +CPooledVBAllocator_ColorMesh::CPooledVBAllocator_ColorMesh() +: m_pMesh( NULL ) +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// CPooledVBAllocator_ColorMesh destructor +// - Clear should have been called +//----------------------------------------------------------------------------- +CPooledVBAllocator_ColorMesh::~CPooledVBAllocator_ColorMesh() +{ + CheckIsClear(); + + // Clean up, if it hadn't been done already + Clear(); +} + +//----------------------------------------------------------------------------- +// Init +// - Allocate the internal shared mesh (vertex buffer) +//----------------------------------------------------------------------------- +bool CPooledVBAllocator_ColorMesh::Init( VertexFormat_t format, int numVerts ) +{ + if ( !CheckIsClear() ) + return false; + + if ( g_VBAllocTracker ) + g_VBAllocTracker->TrackMeshAllocations( "CPooledVBAllocator_ColorMesh::Init" ); + + CMatRenderContextPtr pRenderContext( materials ); + m_pMesh = pRenderContext->CreateStaticMesh( format, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_COLOR ); + if ( m_pMesh ) + { + // Build out the underlying vertex buffer + CMeshBuilder meshBuilder; + int numIndices = 0; + meshBuilder.Begin( m_pMesh, MATERIAL_HETEROGENOUS, numVerts, numIndices ); + { + m_pVertexBufferBase = meshBuilder.Specular(); + m_totalVerts = numVerts; + m_vertexSize = meshBuilder.VertexSize(); + // Probably good to catch any change to vertex size... there may be assumptions based on it: + Assert( m_vertexSize == 4 ); + // Start at the bottom of the VB and work your way up like a simple stack + m_nextFreeOffset = 0; + } + meshBuilder.End(); + } + + if ( g_VBAllocTracker ) + g_VBAllocTracker->TrackMeshAllocations( NULL ); + + return ( m_pMesh != NULL ); +} + +//----------------------------------------------------------------------------- +// Clear +// - frees the shared mesh (vertex buffer), resets member variables +//----------------------------------------------------------------------------- +void CPooledVBAllocator_ColorMesh::Clear( void ) +{ + if ( m_pMesh != NULL ) + { + if ( m_numAllocations > 0 ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Clear should not be called until all allocations released!" ); + Assert( m_numAllocations == 0 ); + } + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->DestroyStaticMesh( m_pMesh ); + m_pMesh = NULL; + } + + m_pVertexBufferBase = NULL; + m_totalVerts = 0; + m_vertexSize = 0; + + m_numAllocations = 0; + m_numVertsAllocated = 0; + m_nextFreeOffset = -1; + m_bStartedDeallocation = false; +} + +//----------------------------------------------------------------------------- +// CheckIsClear +// - assert/warn if the allocator isn't in a clear state +// (no extant allocations, no internal mesh) +//----------------------------------------------------------------------------- +bool CPooledVBAllocator_ColorMesh::CheckIsClear( void ) +{ + if ( m_pMesh ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh's internal mesh (vertex buffer) should have been freed!" ); + Assert( m_pMesh == NULL ); + return false; + } + + if ( m_numAllocations > 0 ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh has unfreed allocations!" ); + Assert( m_numAllocations == 0 ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Allocate +// - Allocate a sub-range of 'numVerts' from free space in the shared vertex buffer +// (returns the byte offset from the start of the VB to the new allocation) +// - returns -1 on failure +//----------------------------------------------------------------------------- +int CPooledVBAllocator_ColorMesh::Allocate( int numVerts ) +{ + if ( m_pMesh == NULL ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate cannot be called before Init (expect a crash)" ); + Assert( m_pMesh ); + return -1; + } + + // Once we start deallocating, we have to keep going until everything has been freed + if ( m_bStartedDeallocation ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate being called after some (but not all) calls to Deallocate have been called - invalid! (expect visual artifacts)" ); + Assert( !m_bStartedDeallocation ); + return -1; + } + + if ( numVerts > ( m_totalVerts - m_numVertsAllocated ) ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Allocate failing - not enough space left in the vertex buffer!" ); + Assert( numVerts <= ( m_totalVerts - m_numVertsAllocated ) ); + return -1; + } + + int result = m_nextFreeOffset; + + m_numAllocations += 1; + m_numVertsAllocated += numVerts; + m_nextFreeOffset += numVerts*m_vertexSize; + + return result; +} + +//----------------------------------------------------------------------------- +// Deallocate +// - Deallocate an existing allocation +//----------------------------------------------------------------------------- +void CPooledVBAllocator_ColorMesh::Deallocate( int offset, int numVerts ) +{ + if ( m_pMesh == NULL ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate cannot be called before Init" ); + Assert( m_pMesh != NULL ); + return; + } + + if ( m_numAllocations == 0 ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate called too many times! (bug in calling code)" ); + Assert( m_numAllocations > 0 ); + return; + } + + if ( numVerts > m_numVertsAllocated ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate called with too many verts, trying to free more than were allocated (bug in calling code)" ); + Assert( numVerts <= m_numVertsAllocated ); + numVerts = m_numVertsAllocated; // Hack (avoid counters ever going below zero) + } + + // Now all extant allocations must be freed before we make any new allocations + m_bStartedDeallocation = true; + + m_numAllocations -= 1; + m_numVertsAllocated -= numVerts; + m_nextFreeOffset = 0; // (we shouldn't be returning this until everything's free, at which point 0 is valid) + + // Are we empty? + if ( m_numAllocations == 0 ) + { + if ( m_numVertsAllocated != 0 ) + { + Warning( "ERROR: CPooledVBAllocator_ColorMesh::Deallocate, after all allocations have been freed too few verts total have been deallocated (bug in calling code)" ); + Assert( m_numVertsAllocated == 0 ); + } + + // We can start allocating again, now + m_bStartedDeallocation = false; + } +} + +//----------------------------------------------------------------------------- +// CreateLightmapsFromData +// - Creates Lightmap Textures from data that was squirreled away during ASYNC load. +// This is necessary because the material system doesn't like us creating things from ASYNC loaders. +//----------------------------------------------------------------------------- +static void CreateLightmapsFromData(CColorMeshData* _colorMeshData) +{ + Assert(_colorMeshData->m_bColorTextureValid); + Assert(!_colorMeshData->m_bColorTextureCreated); + + for (int mesh = 0; mesh < _colorMeshData->m_nMeshes; ++mesh) + { + ColorMeshInfo_t* meshInfo = &_colorMeshData->m_pMeshInfos[mesh]; + + // Ensure that we haven't somehow already messed with these. + Assert(meshInfo->m_pLightmapData); + Assert(!meshInfo->m_pLightmap); + + ColorTexelsInfo_t* cti = meshInfo->m_pLightmapData; + + Assert(cti->m_pTexelData); + + meshInfo->m_pLightmap = g_pMaterialSystem->CreateTextureFromBits(cti->m_nWidth, cti->m_nHeight, cti->m_nMipmapCount, cti->m_ImageFormat, cti->m_nByteCount, cti->m_pTexelData); + + // If this triggers, we need to figure out if it's reasonable to fail. If it is, then we should figure out how to signal back + // that we shouldn't try to create this again (probably by clearing _colorMeshData->m_bColoTextureValid) + Assert(meshInfo->m_pLightmap); + + // Cleanup after ourselves. + delete [] cti->m_pTexelData; + delete cti; + + meshInfo->m_pLightmapData = NULL; + } + + _colorMeshData->m_bColorTextureCreated = true; +} |