csgo-2018-source/engine/brushbatchrender.cpp

1431 lines
49 KiB
C++
Raw Permalink Normal View History

2021-07-25 12:11:47 +08:00
//===== Copyright <20> 1996-2007, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "render_pch.h"
#include "brushbatchrender.h"
#include "icliententity.h"
#include "r_decal.h"
#include "gl_rsurf.h"
#include "tier0/vprof.h"
#include <algorithm>
CBrushBatchRender g_BrushBatchRenderer;
FORCEINLINE void ModulateMaterial( IMaterial *pMaterial, float *pOldColor )
{
if ( g_bIsBlendingOrModulating )
{
pOldColor[3] = pMaterial->GetAlphaModulation( );
pMaterial->GetColorModulation( &pOldColor[0], &pOldColor[1], &pOldColor[2] );
pMaterial->AlphaModulate( r_blend );
pMaterial->ColorModulate( r_colormod[0], r_colormod[1], r_colormod[2] );
}
}
FORCEINLINE void UnModulateMaterial( IMaterial *pMaterial, float *pOldColor )
{
if ( g_bIsBlendingOrModulating )
{
pMaterial->AlphaModulate( pOldColor[3] );
pMaterial->ColorModulate( pOldColor[0], pOldColor[1], pOldColor[2] );
}
}
int __cdecl CBrushBatchRender::SurfaceCmp(const surfacelist_t *s0, const surfacelist_t *s1 )
{
int sortID0 = MSurf_MaterialSortID( s0->surfID );
int sortID1 = MSurf_MaterialSortID( s1->surfID );
return sortID0 - sortID1;
}
// Builds a transrender_t, then executes it's drawing commands
void CBrushBatchRender::DrawTranslucentBrushModel( IMatRenderContext *pRenderContext, model_t *model, IClientEntity *baseentity )
{
transrender_t render;
render.pLastBatch = NULL;
render.pLastNode = NULL;
render.nodeCount = 0;
render.surfaceCount = 0;
render.batchCount = 0;
render.decalSurfaceCount = 0;
BuildTransLists_r( render, model, model->brush.pShared->nodes + model->brush.firstnode );
void *pProxyData = baseentity ? baseentity->GetClientRenderable() : NULL;
DrawTransLists( pRenderContext, render, pProxyData );
}
void CBrushBatchRender::AddSurfaceToBatch( transrender_t &render, transnode_t *pNode, transbatch_t *pBatch, SurfaceHandle_t surfID )
{
pBatch->surfaceCount++;
Assert(render.surfaceCount < MAX_TRANS_SURFACES);
pBatch->indexCount += (MSurf_VertCount( surfID )-2)*3;
render.surfaces[render.surfaceCount] = surfID;
render.surfaceCount++;
if ( SurfaceHasDecals( surfID ) || MSurf_ShadowDecals( surfID ) != SHADOW_DECAL_HANDLE_INVALID )
{
Assert(render.decalSurfaceCount < MAX_TRANS_DECALS);
pNode->decalSurfaceCount++;
render.decalSurfaces[render.decalSurfaceCount] = surfID;
render.decalSurfaceCount++;
}
}
void CBrushBatchRender::AddTransNode( transrender_t &render )
{
render.pLastNode = &render.nodes[render.nodeCount];
render.nodeCount++;
Assert(render.nodeCount < MAX_TRANS_NODES);
render.pLastBatch = NULL;
render.pLastNode->firstBatch = render.batchCount;
render.pLastNode->firstDecalSurface = render.decalSurfaceCount;
render.pLastNode->batchCount = 0;
render.pLastNode->decalSurfaceCount = 0;
}
void CBrushBatchRender::AddTransBatch( transrender_t &render, SurfaceHandle_t surfID )
{
transbatch_t &batch = render.batches[render.pLastNode->firstBatch + render.pLastNode->batchCount];
Assert(render.batchCount < MAX_TRANS_BATCHES);
render.pLastNode->batchCount++;
render.batchCount++;
batch.firstSurface = render.surfaceCount;
batch.surfaceCount = 0;
batch.pMaterial = MSurf_TexInfo( surfID )->material;
batch.sortID = MSurf_MaterialSortID( surfID );
batch.indexCount = 0;
render.pLastBatch = &batch;
AddSurfaceToBatch( render, render.pLastNode, &batch, surfID );
}
// build node lists
void CBrushBatchRender::BuildTransLists_r( transrender_t &render, model_t *model, mnode_t *node )
{
float dot;
if (node->contents >= 0)
return;
// node is just a decision point, so go down the apropriate sides
// find which side of the node we are on
cplane_t *plane = node->plane;
if ( plane->type <= PLANE_Z )
{
dot = modelorg[plane->type] - plane->dist;
}
else
{
dot = DotProduct (modelorg, plane->normal) - plane->dist;
}
int side = (dot >= 0) ? 0 : 1;
// back side first - translucent surfaces need to render in back to front order
// to appear correctly.
BuildTransLists_r(render, model, node->children[!side]);
// emit all surfaces on node
CUtlVectorFixed<surfacelist_t, 256> sortList;
SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface, model->brush.pShared );
for ( int i = 0; i < node->numsurfaces; i++, surfID++ )
{
// skip opaque surfaces
if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
{
if ( ((MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0) )
{
// Backface cull here, so they won't do any more work
if ( ( side ^ !!(MSurf_Flags( surfID ) & SURFDRAW_PLANEBACK)) )
continue;
}
// If this can be appended to the previous batch, do so
int sortID = MSurf_MaterialSortID( surfID );
if ( render.pLastBatch && render.pLastBatch->sortID == sortID )
{
AddSurfaceToBatch( render, render.pLastNode, render.pLastBatch, surfID );
}
else
{
// save it off for sorting, then a later append
int sortIndex = sortList.AddToTail();
sortList[sortIndex].surfID = surfID;
}
}
}
// We've got surfaces on this node that can't be added to the previous node
if ( sortList.Count() )
{
// sort by material
sortList.Sort( SurfaceCmp );
// add a new sort group
AddTransNode( render );
int lastSortID = -1;
// now add the optimal number of batches to that group
for ( int i = 0; i < sortList.Count(); i++ )
{
surfID = sortList[i].surfID;
int sortID = MSurf_MaterialSortID( surfID );
if ( lastSortID == sortID )
{
// can be drawn in a single call with the current list of surfaces, append
AddSurfaceToBatch( render, render.pLastNode, render.pLastBatch, surfID );
}
else
{
// requires a break (material/lightmap change).
AddTransBatch( render, surfID );
lastSortID = sortID;
}
}
// don't batch across decals or decals will sort incorrectly
if ( render.pLastNode->decalSurfaceCount )
{
render.pLastNode = NULL;
render.pLastBatch = NULL;
}
}
// front side
BuildTransLists_r(render, model, node->children[side]);
}
void CBrushBatchRender::DrawTransLists( IMatRenderContext *pRenderContext, transrender_t &render, void *pProxyData )
{
PIXEVENT( pRenderContext, "DrawTransLists()" );
bool skipLight = false;
if ( g_pMaterialSystemConfig->nFullbright == 1 )
{
pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );
skipLight = true;
}
float pOldColor[4];
for ( int i = 0; i < render.nodeCount; i++ )
{
int j;
const transnode_t &node = render.nodes[i];
for ( j = 0; j < node.batchCount; j++ )
{
const transbatch_t &batch = render.batches[node.firstBatch+j];
CMeshBuilder meshBuilder;
IMaterial *pMaterial = batch.pMaterial;
ModulateMaterial( pMaterial, pOldColor );
if ( !skipLight )
{
pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
}
pRenderContext->Bind( pMaterial, pProxyData );
IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batch.indexCount );
for ( int k = 0; k < batch.surfaceCount; k++ )
{
SurfaceHandle_t surfID = render.surfaces[batch.firstSurface + k];
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
BuildIndicesForSurface( meshBuilder, surfID );
}
meshBuilder.End( false, true );
// Don't leave the material in a bogus state
UnModulateMaterial( pMaterial, pOldColor );
}
if ( node.decalSurfaceCount )
{
for ( j = 0; j < node.decalSurfaceCount; j++ )
{
SurfaceHandle_t surfID = render.decalSurfaces[node.firstDecalSurface + j];
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
if( SurfaceHasDecals( surfID ) )
{
DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
}
// Add shadows too....
ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
if (decalHandle != SHADOW_DECAL_HANDLE_INVALID)
{
g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
}
}
// Now draw the decals + shadows for this node
// This order relative to the surfaces is important for translucency to work correctly.
DecalSurfaceDraw(pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP);
// FIXME: Decals are not being rendered while illuminated by the flashlight
// FIXME: Need to draw decals in flashlight rendering mode
// Retire decals on opaque world surfaces
R_DecalFlushDestroyList();
DecalSurfacesInit( true );
// Draw all shadows on the brush
g_pShadowMgr->RenderProjectedTextures( pRenderContext );
}
if ( g_ShaderDebug.anydebug )
{
CUtlVector<msurface2_t *> brushList;
for ( j = 0; j < node.batchCount; j++ )
{
const transbatch_t &batch = render.batches[node.firstBatch+j];
for ( int k = 0; k < batch.surfaceCount; k++ )
{
brushList.AddToTail( render.surfaces[batch.firstSurface + k] );
}
}
DrawDebugInformation( pRenderContext, brushList.Base(), brushList.Count() );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: This is used when the mat_dxlevel is changed to reset the brush
// models.
//-----------------------------------------------------------------------------
void R_BrushBatchInit( void )
{
g_BrushBatchRenderer.LevelInit();
}
void CBrushBatchRender::LevelInit()
{
AUTO_LOCK( m_Mutex );
unsigned short iNext;
for( unsigned short i=m_renderList.Head(); i != m_renderList.InvalidIndex(); i=iNext )
{
iNext = m_renderList.Next(i);
delete m_renderList.Element(i);
}
m_renderList.Purge();
ClearRenderHandles();
}
void CBrushBatchRender::ClearRenderHandles( void )
{
for ( int iBrush = 1 ; iBrush < host_state.worldbrush->numsubmodels ; ++iBrush )
{
char szBrushModel[5]; // inline model names "*1", "*2" etc
Q_snprintf( szBrushModel, sizeof( szBrushModel ), "*%i", iBrush );
model_t *pModel = modelloader->GetModelForName( szBrushModel, IModelLoader::FMODELLOADER_SERVER );
if ( pModel )
{
pModel->brush.renderHandle = 0;
}
}
}
// Create a compact, optimal list of rendering commands for the opaque parts of a brush model
// NOTE: This just skips translucent surfaces assuming a separate transrender_t pass!
CBrushBatchRender::brushrender_t *CBrushBatchRender::FindOrCreateRenderBatch( model_t *pModel )
{
if ( !pModel->brush.nummodelsurfaces )
return NULL;
AUTO_LOCK( m_Mutex );
unsigned short index = pModel->brush.renderHandle - 1;
if ( m_renderList.IsValidIndex( index ) )
return m_renderList.Element(index);
brushrender_t *pRender = new brushrender_t;
index = m_renderList.AddToTail( pRender );
pModel->brush.renderHandle = index + 1;
brushrender_t &render = *pRender;
render.pPlanes = NULL;
render.pMeshes = NULL;
render.planeCount = 0;
render.meshCount = 0;
render.totalIndexCount = 0;
render.totalVertexCount = 0;
CUtlVector<cplane_t *> planeList;
CUtlVector<surfacelist_t> surfaceList;
int i;
SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
for (i=0 ; i<pModel->brush.nummodelsurfaces; i++, surfID++)
{
// UNDONE: For now, just draw these in a separate pass
if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
continue;
#ifndef _PS3
cplane_t *plane = surfID->plane;
#else
cplane_t *plane = &surfID->m_plane;
#endif
int planeIndex = planeList.Find(plane);
if ( planeIndex == -1 )
{
planeIndex = planeList.AddToTail( plane );
}
surfacelist_t tmp;
tmp.surfID = surfID;
tmp.surfaceIndex = i;
tmp.planeIndex = planeIndex;
surfaceList.AddToTail( tmp );
}
surfaceList.Sort( SurfaceCmp );
render.pPlanes = new cplane_t *[planeList.Count()];
render.planeCount = planeList.Count();
memcpy( render.pPlanes, planeList.Base(), sizeof(cplane_t *)*planeList.Count() );
render.pSurfaces = new brushrendersurface_t[surfaceList.Count()];
render.surfaceCount = surfaceList.Count();
int meshCount = 0;
int batchCount = 0;
int lastSortID = -1;
IMesh *pLastMesh = NULL;
brushrendermesh_t *pMesh = NULL;
brushrendermesh_t tmpMesh[MAX_VERTEX_FORMAT_CHANGES];
brushrenderbatch_t *pBatch = NULL;
brushrenderbatch_t tmpBatch[128];
for ( i = 0; i < surfaceList.Count(); i++ )
{
render.pSurfaces[i].surfaceIndex = surfaceList[i].surfaceIndex;
render.pSurfaces[i].planeIndex = surfaceList[i].planeIndex;
surfID = surfaceList[i].surfID;
int sortID = MSurf_MaterialSortID( surfID );
if ( g_WorldStaticMeshes[sortID] != pLastMesh )
{
pMesh = tmpMesh + meshCount;
pMesh->firstBatch = batchCount;
pMesh->batchCount = 0;
lastSortID = -1; // force a new batch
meshCount++;
}
if ( sortID != lastSortID )
{
pBatch = tmpBatch + batchCount;
pBatch->firstSurface = i;
pBatch->surfaceCount = 0;
pBatch->sortID = sortID;
pBatch->pMaterial = MSurf_TexInfo( surfID )->material;
pBatch->indexCount = 0;
pMesh->batchCount++;
batchCount++;
}
pLastMesh = g_WorldStaticMeshes[sortID];
lastSortID = sortID;
pBatch->surfaceCount++;
int vertCount = MSurf_VertCount( surfID );
int indexCount = (vertCount - 2) * 3;
pBatch->indexCount += indexCount;
render.totalIndexCount += indexCount;
render.totalVertexCount += vertCount;
}
render.pMeshes = new brushrendermesh_t[meshCount];
memcpy( render.pMeshes, tmpMesh, sizeof(brushrendermesh_t) * meshCount );
render.meshCount = meshCount;
render.pBatches = new brushrenderbatch_t[batchCount];
memcpy( render.pBatches, tmpBatch, sizeof(brushrenderbatch_t) * batchCount );
render.batchCount = batchCount;
return &render;
}
//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawOpaqueBrushModel( IMatRenderContext *pRenderContext, IClientEntity *baseentity, model_t *model, ERenderDepthMode_t DepthMode )
{
VPROF( "R_DrawOpaqueBrushModel" );
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( model->brush.firstmodelsurface, model->brush.pShared );
brushrender_t *pRender = FindOrCreateRenderBatch( model );
int i;
if ( !pRender )
return;
bool skipLight = false;
PIXEVENT( pRenderContext, "DrawOpaqueBrushModel()" );
if ( (g_pMaterialSystemConfig->nFullbright == 1) || ( DepthMode == DEPTH_MODE_SHADOW || DepthMode == DEPTH_MODE_SSA0 ) )
{
pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );
skipLight = true;
}
void *pProxyData = baseentity ? baseentity->GetClientRenderable() : NULL;
int backface[1024];
Assert( pRender->planeCount < 1024 );
// NOTE: Backface culling is almost no perf gain. Can be removed from brush model rendering.
// Check the shared planes once
if ( DepthMode == DEPTH_MODE_SHADOW || DepthMode == DEPTH_MODE_SSA0 )
{
for ( i = 0; i < pRender->planeCount; i++ )
{
backface[i] = 0;
}
}
else
{
for ( i = 0; i < pRender->planeCount; i++ )
{
float dot = DotProduct( modelorg, pRender->pPlanes[i]->normal) - pRender->pPlanes[i]->dist;
backface[i] = ( dot < BACKFACE_EPSILON ) ? true : false; // don't backface cull when rendering to shadow map
}
}
float pOldColor[4];
// PORTAL 2 PAINT RENDERING
CUtlVectorFixedGrowable< SurfaceHandle_t, 64 > paintableSurfaces;
CUtlVectorFixedGrowable< int, 16 > batchPaintableSurfaceCount;
CUtlVectorFixedGrowable< int, 16 > batchPaintableSurfaceIndexCount;
for ( i = 0; i < pRender->meshCount; i++ )
{
brushrendermesh_t &mesh = pRender->pMeshes[i];
for ( int j = 0; j < mesh.batchCount; j++ )
{
int nBatchIndex = batchPaintableSurfaceCount.Count();
batchPaintableSurfaceCount.AddToTail( 0 );
batchPaintableSurfaceIndexCount.AddToTail( 0 );
brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];
int k;
for ( k = 0; k < batch.surfaceCount; k++ )
{
brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
if ( !backface[surface.planeIndex] )
break;
}
if ( k == batch.surfaceCount )
continue;
CMeshBuilder meshBuilder;
IMaterial *pMaterial = NULL;
if ( DepthMode != DEPTH_MODE_NORMAL )
{
// Select proper override material
int nAlphaTest = (int) batch.pMaterial->IsAlphaTested();
int nNoCull = (int) batch.pMaterial->IsTwoSided();
IMaterial *pDepthWriteMaterial;
if ( DepthMode == DEPTH_MODE_SHADOW )
{
pDepthWriteMaterial = g_pMaterialDepthWrite[ nAlphaTest ][ nNoCull ];
}
else
{
pDepthWriteMaterial = g_pMaterialSSAODepthWrite[ nAlphaTest ][ nNoCull ];
}
if ( nAlphaTest == 1 )
{
static unsigned int originalTextureVarCache = 0;
IMaterialVar *pOriginalTextureVar = batch.pMaterial->FindVarFast( "$basetexture", &originalTextureVarCache );
static unsigned int originalTextureFrameVarCache = 0;
IMaterialVar *pOriginalTextureFrameVar = batch.pMaterial->FindVarFast( "$frame", &originalTextureFrameVarCache );
static unsigned int originalAlphaRefCache = 0;
IMaterialVar *pOriginalAlphaRefVar = batch.pMaterial->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache );
static unsigned int textureVarCache = 0;
IMaterialVar *pTextureVar = pDepthWriteMaterial->FindVarFast( "$basetexture", &textureVarCache );
static unsigned int textureFrameVarCache = 0;
IMaterialVar *pTextureFrameVar = pDepthWriteMaterial->FindVarFast( "$frame", &textureFrameVarCache );
static unsigned int alphaRefCache = 0;
IMaterialVar *pAlphaRefVar = pDepthWriteMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache );
if( pTextureVar && pOriginalTextureVar )
{
pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() );
}
if( pTextureFrameVar && pOriginalTextureFrameVar )
{
pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() );
}
if( pAlphaRefVar && pOriginalAlphaRefVar )
{
pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() );
}
}
pMaterial = pDepthWriteMaterial;
}
else
{
pMaterial = batch.pMaterial;
// Store off the old color + alpha
ModulateMaterial( pMaterial, pOldColor );
if ( !skipLight )
{
pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
}
}
pRenderContext->Bind( pMaterial, pProxyData );
IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batch.indexCount );
for ( ; k < batch.surfaceCount; k++ )
{
brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
if ( backface[surface.planeIndex] )
continue;
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
// PORTAL 2 PAINT RENDERING
if ( (DepthMode == DEPTH_MODE_NORMAL) && ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED ) )
{
paintableSurfaces.AddToTail( surfID );
++ batchPaintableSurfaceCount[ nBatchIndex ];
int nVertCount, nIndexCount;
Shader_GetSurfVertexAndIndexCount( surfID, &nVertCount, &nIndexCount );
batchPaintableSurfaceIndexCount[ nBatchIndex ] += nIndexCount;
}
BuildIndicesForSurface( meshBuilder, surfID );
if( SurfaceHasDecals( surfID ) && (DepthMode == DEPTH_MODE_NORMAL))
{
DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
}
// Add overlay fragments to list.
// FIXME: A little code support is necessary to get overlays working on brush models
// OverlayMgr()->AddFragmentListToRenderList( MSurf_OverlayFragmentList( surfID ), false );
if ( DepthMode == DEPTH_MODE_NORMAL )
{
// Add render-to-texture shadows too....
ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
if (decalHandle != SHADOW_DECAL_HANDLE_INVALID)
{
g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
}
}
}
meshBuilder.End( false, true );
if ( DepthMode == DEPTH_MODE_NORMAL )
{
// Don't leave the material in a bogus state
UnModulateMaterial( pMaterial, pOldColor );
}
}
}
if ( DepthMode != DEPTH_MODE_NORMAL )
return;
// PORTAL 2 PAINT RENDERING
if ( paintableSurfaces.Count() )
{
pRenderContext->SetRenderingPaint( true );
PIXEVENT( pRenderContext, "Paint" );
int nBatchIndex = 0;
int nSurfaceIndex = 0;
for ( i = 0; i < pRender->meshCount; i++ )
{
brushrendermesh_t &mesh = pRender->pMeshes[i];
for ( int j = 0; j < mesh.batchCount; j++ )
{
int nSurfaceCount = batchPaintableSurfaceCount[ nBatchIndex ];
if ( nSurfaceCount > 0 )
{
brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];
IMaterial *pMaterial = batch.pMaterial;
if ( !skipLight )
{
pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
}
pRenderContext->Bind( pMaterial, pProxyData );
CMeshBuilder meshBuilder;
IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batchPaintableSurfaceIndexCount[ nBatchIndex ] );
for ( int i = 0; i < nSurfaceCount; ++ i, ++ nSurfaceIndex )
{
BuildIndicesForSurface( meshBuilder, paintableSurfaces[ nSurfaceIndex ] );
}
meshBuilder.End( false, true );
}
++ nBatchIndex;
}
}
pRenderContext->SetRenderingPaint( false );
}
if ( g_ShaderDebug.anydebug )
{
for ( i = 0; i < pRender->meshCount; i++ )
{
brushrendermesh_t &mesh = pRender->pMeshes[i];
CUtlVector<msurface2_t *> brushList;
for ( int j = 0; j < mesh.batchCount; j++ )
{
brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];
for ( int k = 0; k < batch.surfaceCount; k++ )
{
brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
if ( backface[surface.planeIndex] )
continue;
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
brushList.AddToTail(surfID);
}
}
// now draw debug for each drawn surface
DrawDebugInformation( pRenderContext, brushList.Base(), brushList.Count() );
}
}
}
//-----------------------------------------------------------------------------
// Draws an translucent (sorted) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawTranslucentBrushModel( IMatRenderContext *pRenderContext, IClientEntity *baseentity, model_t *model, ERenderDepthMode_t DepthMode, bool bDrawOpaque, bool bDrawTranslucent )
{
if ( bDrawOpaque )
{
DrawOpaqueBrushModel( pRenderContext, baseentity, model, DepthMode );
}
if ( ( DepthMode == DEPTH_MODE_NORMAL ) && bDrawTranslucent )
{
DrawTranslucentBrushModel( pRenderContext, model, baseentity );
}
}
//-----------------------------------------------------------------------------
// Purpose: Draws a brush model shadow for render-to-texture shadows
//-----------------------------------------------------------------------------
// UNDONE: This is reasonable, but it could be much faster as follows:
// Build a vertex buffer cache. A block-allocated static mesh with 1024 verts
// per block or something.
// When a new brush is encountered, fill it in to the current block or the
// next one (first fit allocator). Then this routine could simply draw
// a static mesh with a single index buffer build, draw call (no dynamic vb).
void CBrushBatchRender::DrawBrushModelShadow( IMatRenderContext *pRenderContext, model_t *model, IClientRenderable *pRenderable )
{
brushrender_t *pRender = FindOrCreateRenderBatch( (model_t *)model );
if ( !pRender )
return;
pRenderContext->Bind( g_pMaterialShadowBuild, pRenderable );
// Draws all surfaces in the brush model in arbitrary order
SurfaceHandle_t surfID = SurfaceHandleFromIndex( model->brush.firstmodelsurface, model->brush.pShared );
IMesh *pMesh = pRenderContext->GetDynamicMesh();
CMeshBuilder meshBuilder;
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, pRender->totalVertexCount, pRender->totalIndexCount );
for ( int i=0 ; i<model->brush.nummodelsurfaces ; i++, surfID++)
{
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
continue;
int startVert = MSurf_FirstVertIndex( surfID );
int vertCount = MSurf_VertCount( surfID );
int startIndex = meshBuilder.GetCurrentVertex();
int j;
for ( j = 0; j < vertCount; j++ )
{
int vertIndex = model->brush.pShared->vertindices[startVert + j];
// world-space vertex
meshBuilder.Position3fv( model->brush.pShared->vertexes[vertIndex].position.Base() );
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f );
meshBuilder.AdvanceVertex();
}
for ( j = 0; j < vertCount-2; j++ )
{
meshBuilder.FastIndex( startIndex );
meshBuilder.FastIndex( startIndex + j + 1 );
meshBuilder.FastIndex( startIndex + j + 2 );
}
}
meshBuilder.End();
pMesh->Draw();
}
inline bool __cdecl CBrushBatchRender::BatchSortLessFunc( const BrushBatchRenderData_t &left, const BrushBatchRenderData_t &right )
{
brushrenderbatch_t &leftBatch = left.m_pBrushRender->pBatches[ left.m_nBatchIndex ];
brushrenderbatch_t &rightBatch = right.m_pBrushRender->pBatches[ right.m_nBatchIndex ];
if ( left.m_pMaterial != right.m_pMaterial )
return left.m_pMaterial < right.m_pMaterial;
if ( leftBatch.sortID != rightBatch.sortID )
return leftBatch.sortID < rightBatch.sortID;
if ( left.m_pInstanceData->m_pStencilState != right.m_pInstanceData->m_pStencilState )
return left.m_pInstanceData->m_pStencilState < right.m_pInstanceData->m_pStencilState;
if ( left.m_pInstanceData->m_pBrushToWorld != right.m_pInstanceData->m_pBrushToWorld )
return left.m_pInstanceData->m_pBrushToWorld < right.m_pInstanceData->m_pBrushToWorld;
return false;
}
//-----------------------------------------------------------------------------
// Builds the lists of stuff to draw
//-----------------------------------------------------------------------------
void CBrushBatchRender::BuildBatchListToDraw( int nCount, const BrushArrayInstanceData_t *pInstanceData,
CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > &batchesToRender, brushrender_t **ppBrushRender )
{
for ( int i = 0; i < nCount; ++i )
{
const BrushArrayInstanceData_t &instance = pInstanceData[i];
brushrender_t *pRender = g_BrushBatchRenderer.FindOrCreateRenderBatch( const_cast< model_t* >( instance.m_pBrushModel ) );
ppBrushRender[i] = pRender;
if ( !pRender )
continue;
for ( int m = 0; m < pRender->meshCount; ++m )
{
brushrendermesh_t &mesh = pRender->pMeshes[m];
int nBatchIndex = mesh.firstBatch;
for ( int j = 0; j < mesh.batchCount; ++j, ++nBatchIndex )
{
int nIndex = batchesToRender.AddToTail();
BrushBatchRenderData_t &batch = batchesToRender[nIndex];
batch.m_pMaterial = pRender->pBatches[nBatchIndex].pMaterial;
batch.m_pInstanceData = &instance;
batch.m_pBrushRender = pRender;
batch.m_nBatchIndex = nBatchIndex;
Assert( nBatchIndex < ( 1 << 15 ) );
}
}
}
}
//-----------------------------------------------------------------------------
// Computes lightmap pages
//-----------------------------------------------------------------------------
void CBrushBatchRender::ComputeLightmapPages( int nCount, BrushBatchRenderData_t *pRenderData )
{
if ( g_pMaterialSystemConfig->nFullbright != 1 )
{
for ( int i = 0; i < nCount; ++i )
{
brushrenderbatch_t &batch = pRenderData[i].m_pBrushRender->pBatches[ pRenderData[i].m_nBatchIndex ];
int nSortID = batch.sortID;
Assert( nSortID >= 0 && nSortID < g_WorldStaticMeshes.Count() );
pRenderData[i].m_nLightmapPage = materialSortInfoArray[ nSortID ].lightmapPageID;
}
return;
}
// Fullbright case. Bump works for unbumped surfaces too
for ( int i = 0; i < nCount; ++i )
{
pRenderData[i].m_nLightmapPage = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP;
}
}
//-----------------------------------------------------------------------------
// Computes the # of indices in an instance group
//-----------------------------------------------------------------------------
int CBrushBatchRender::ComputeInstanceGroups( IMatRenderContext *pRenderContext, int nCount, BrushBatchRenderData_t *pRenderData, CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > &instanceGroups )
{
int nMaxIndices = pRenderContext->GetMaxIndicesToRender();
int nMaxInstanceCount = 0;
IMaterial *pLastMaterial = NULL;
IMaterial *pLastActualMaterial = NULL;
BrushBatchRenderData_t *pFirstInstance = NULL;
int nInstanceCount = 0;
int nIndexCount = 0;
for ( int i = 0; i < nCount; i++ )
{
BrushBatchRenderData_t &renderData = pRenderData[i];
brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
int nNextIndexCount = nIndexCount + batch.indexCount;
// Fire it away if the material changes or we overflow index count
if ( ( pLastMaterial != batch.pMaterial ) || ( pLastMaterial != pLastActualMaterial ) || ( nNextIndexCount > nMaxIndices ) )
{
if ( nInstanceCount > 0 )
{
int nIndex = instanceGroups.AddToTail();
instanceGroups[nIndex].m_pRenderData = pFirstInstance;
instanceGroups[nIndex].m_nCount = nInstanceCount;
instanceGroups[nIndex].m_nIndexCount = nIndexCount;
instanceGroups[nIndex].m_pMaterial = pLastMaterial;
instanceGroups[nIndex].m_pActualMaterial = pLastActualMaterial;
if ( nInstanceCount > nMaxInstanceCount )
{
nMaxInstanceCount = nInstanceCount;
}
}
nInstanceCount = 0;
pFirstInstance = &renderData;
nIndexCount = batch.indexCount;
pLastMaterial = renderData.m_pMaterial;
// This is necessary for shadow depth rendering. We need to re-bind
// for every alpha tested material
pLastActualMaterial = renderData.m_nIsAlphaTested ? batch.pMaterial : pLastMaterial;
}
else
{
nIndexCount = nNextIndexCount;
}
++nInstanceCount;
}
if ( nInstanceCount > 0 )
{
int nIndex = instanceGroups.AddToTail();
instanceGroups[nIndex].m_pRenderData = pFirstInstance;
instanceGroups[nIndex].m_nCount = nInstanceCount;
instanceGroups[nIndex].m_nIndexCount = nIndexCount;
instanceGroups[nIndex].m_pMaterial = pLastMaterial;
instanceGroups[nIndex].m_pActualMaterial = pLastActualMaterial;
if ( nInstanceCount > nMaxInstanceCount )
{
nMaxInstanceCount = nInstanceCount;
}
}
return nMaxInstanceCount;
}
//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
bool CBrushBatchRender::DrawSortedBatchList( IMatRenderContext* pRenderContext, int nCount, BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
VPROF( "DrawSortedBatchList" );
PIXEVENT( pRenderContext, "DrawSortedBatchList()" );
// Needed to allow the system to detect which samplers are bound to lightmap textures
pRenderContext->BindLightmapPage( 0 );
MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );
bool bHasPaintedSurfaces = false;
for ( int i = 0; i < nCount; i++ )
{
BrushInstanceGroup_t &group = pInstanceGroup[i];
pRenderContext->Bind( group.m_pMaterial, NULL );
// Only writing indices
// FIXME: Can we make this a static index buffer?
IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
indexBuilder.Lock( group.m_nIndexCount, 0 );
int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);
group.m_nHasPaintedSurfaces = false;
for ( int j = 0; j < group.m_nCount; ++j )
{
BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
renderData.m_nHasPaintedSurfaces = false;
const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
for ( int k = 0; k < batch.surfaceCount; k++ )
{
const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
BuildIndicesForSurface( indexBuilder, surfID );
if ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED )
{
group.m_nHasPaintedSurfaces = true;
renderData.m_nHasPaintedSurfaces = true;
bHasPaintedSurfaces = true;
}
}
MeshInstanceData_t &instance = pInstance[ j ];
instance.m_pEnvCubemap = NULL;
instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
instance.m_pLightingState = NULL;
instance.m_nBoneCount = 1;
instance.m_pBoneRemap = NULL;
instance.m_nIndexOffset = nIndexOffset;
instance.m_nIndexCount = batch.indexCount;
instance.m_nPrimType = MATERIAL_TRIANGLES;
instance.m_pColorBuffer = NULL;
instance.m_nColorVertexOffsetInBytes = 0;
instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
instance.m_pIndexBuffer = pBuildIndexBuffer;
instance.m_nVertexOffsetInBytes = 0;
instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
instance.m_nLightmapPageId = renderData.m_nLightmapPage;
instance.m_bColorBufferHasIndirectLightingOnly = false;
nIndexOffset += batch.indexCount;
}
indexBuilder.End( );
pRenderContext->DrawInstances( group.m_nCount, pInstance );
}
return bHasPaintedSurfaces;
}
void CBrushBatchRender::DrawPaintForBatches( IMatRenderContext* pRenderContext, int nCount, const BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );
pRenderContext->SetRenderingPaint( true );
PIXEVENT( pRenderContext, "Paint" );
for ( int i = 0; i < nCount; i++ )
{
const BrushInstanceGroup_t &group = pInstanceGroup[i];
if ( !group.m_nHasPaintedSurfaces )
continue;
pRenderContext->Bind( group.m_pMaterial, NULL );
// Only writing indices, we're potentially allocating too many, but that's ok.
// Unused ones will be freed up
// FIXME: Can we make this a static index buffer?
IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
indexBuilder.Lock( group.m_nIndexCount, 0 );
int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);
int nGroupCount = 0;
for ( int j = 0; j < group.m_nCount; ++j )
{
const BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
if ( !renderData.m_nHasPaintedSurfaces )
continue;
const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
int nBatchIndexCount = 0;
for ( int k = 0; k < batch.surfaceCount; k++ )
{
const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
if ( ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED ) == 0 )
continue;
int nTriCount = BuildIndicesForSurface( indexBuilder, surfID );
nBatchIndexCount += nTriCount * 3;
}
MeshInstanceData_t &instance = pInstance[ nGroupCount ];
instance.m_pEnvCubemap = NULL;
instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
instance.m_pLightingState = NULL;
instance.m_nBoneCount = 1;
instance.m_pBoneRemap = NULL;
instance.m_nIndexOffset = nIndexOffset;
instance.m_nIndexCount = nBatchIndexCount;
instance.m_nPrimType = MATERIAL_TRIANGLES;
instance.m_pColorBuffer = NULL;
instance.m_nColorVertexOffsetInBytes = 0;
instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
instance.m_pIndexBuffer = pBuildIndexBuffer;
instance.m_nVertexOffsetInBytes = 0;
instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
instance.m_nLightmapPageId = renderData.m_nLightmapPage;
instance.m_bColorBufferHasIndirectLightingOnly = false;
nIndexOffset += nBatchIndexCount;
++nGroupCount;
}
indexBuilder.End( );
pRenderContext->DrawInstances( nGroupCount, pInstance );
}
pRenderContext->SetRenderingPaint( false );
}
// Draw decals
void CBrushBatchRender::DrawDecalsForBatches( IMatRenderContext *pRenderContext,
int nCount, const BrushArrayInstanceData_t *pInstanceData, brushrender_t **ppBrushRender )
{
// FIXME: This could be better optimized by rendering across instances
// but that's a deeper change: we'd need to have per-instance transforms
// for each decal + shadow
for ( int i = 0; i < nCount; ++i )
{
// Clear out the render list of decals
DecalSurfacesInit( true );
// Clear out the render lists of shadows
g_pShadowMgr->ClearShadowRenderList( );
const BrushArrayInstanceData_t &instance = pInstanceData[i];
const brushrender_t *pRender = ppBrushRender[i];
if ( !pRender )
continue;
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( instance.m_pBrushModel->brush.firstmodelsurface, instance.m_pBrushModel->brush.pShared );
bool bEncounteredDecals = false;
for ( int s = 0; s < pRender->surfaceCount; ++s )
{
const brushrendersurface_t &surface = pRender->pSurfaces[s];
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
if ( SurfaceHasDecals( surfID ) )
{
bEncounteredDecals = true;
DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
}
// Add overlay fragments to list.
// FIXME: A little code support is necessary to get overlays working on brush models
// OverlayMgr()->AddFragmentListToRenderList( MSurf_OverlayFragmentList( surfID ), false );
// Add render-to-texture shadows too....
ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
if ( decalHandle != SHADOW_DECAL_HANDLE_INVALID )
{
bEncounteredDecals = true;
g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
}
}
if ( bEncounteredDecals )
{
CBrushModelTransform pushTransform( *instance.m_pBrushToWorld, pRenderContext );
// Draw all shadows on the brush
g_pShadowMgr->RenderProjectedTextures( pRenderContext );
DecalSurfaceDraw( pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP, instance.m_DiffuseModulation.w );
// draw the flashlight lighting for the decals on the brush.
g_pShadowMgr->DrawFlashlightDecals( pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP, false, instance.m_DiffuseModulation.w );
// Retire decals on opaque brushmodel surfaces
R_DecalFlushDestroyList();
}
}
}
void CBrushBatchRender::DrawArrayDebugInformation( IMatRenderContext *pRenderContext, int nCount, const BrushBatchRenderData_t *pRenderData )
{
if ( !g_ShaderDebug.anydebug )
return;
const Vector &vecViewOrigin = g_EngineRenderer->ViewOrigin();
for ( int r = 0; r < nCount; ++r )
{
const BrushBatchRenderData_t &renderData = pRenderData[r];
const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
Vector vecModelSpaceViewOrigin;
VectorITransform( vecViewOrigin, *renderData.m_pInstanceData->m_pBrushToWorld, vecModelSpaceViewOrigin );
CUtlVectorFixedGrowable< msurface2_t *, 512 > surfaceList;
for ( int k = 0; k < batch.surfaceCount; k++ )
{
brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
continue;
if ( ( MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0 )
{
// Do a full backface cull here; we're not culling elsewhere in the pipeline
// Yes, it's expensive, but this is a debug mode, so who cares?
cplane_t *pSurfacePlane = renderData.m_pBrushRender->pPlanes[surface.planeIndex];
float flDot = DotProduct( vecModelSpaceViewOrigin, pSurfacePlane->normal ) - pSurfacePlane->dist;
bool bIsBackfacing = ( flDot < BACKFACE_EPSILON ) ? true : false;
if ( bIsBackfacing != false )
continue;
}
surfaceList.AddToTail( surfID );
}
// now draw debug for each drawn surface
DrawDebugInformation( pRenderContext, *renderData.m_pInstanceData->m_pBrushToWorld, surfaceList.Base(), surfaceList.Count() );
}
}
//-----------------------------------------------------------------------------
// Main entry point for rendering an array of brush models
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawBrushModelArray( IMatRenderContext* pRenderContext, int nCount,
const BrushArrayInstanceData_t *pInstanceData )
{
brushrender_t **ppBrushRender = (brushrender_t**)stackalloc( nCount * sizeof(brushrender_t*) );
CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > batchesToRender;
BuildBatchListToDraw( nCount, pInstanceData, batchesToRender, ppBrushRender );
int nBatchCount = batchesToRender.Count();
BrushBatchRenderData_t *pBatchData = batchesToRender.Base();
ComputeLightmapPages( nBatchCount, pBatchData );
std::make_heap( pBatchData, pBatchData + nBatchCount, BatchSortLessFunc );
std::sort_heap( pBatchData, pBatchData + nBatchCount, BatchSortLessFunc );
CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > instanceGroups;
int nMaxInstanceCount = ComputeInstanceGroups( pRenderContext, nBatchCount, pBatchData, instanceGroups );
bool bHasPaintedSurfaces = DrawSortedBatchList( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
if ( bHasPaintedSurfaces )
{
DrawPaintForBatches( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
}
DrawDecalsForBatches( pRenderContext, nCount, pInstanceData, ppBrushRender );
DrawArrayDebugInformation( pRenderContext, nBatchCount, pBatchData );
}
//-----------------------------------------------------------------------------
// Builds the lists of shadow batches to draw
//-----------------------------------------------------------------------------
void CBrushBatchRender::BuildShadowBatchListToDraw( int nCount, const BrushArrayInstanceData_t *pInstanceData,
CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > &batchesToRender, int nModelTypeFlags )
{
for ( int i = 0; i < nCount; ++i )
{
const BrushArrayInstanceData_t &instance = pInstanceData[i];
brushrender_t *pRender = g_BrushBatchRenderer.FindOrCreateRenderBatch( const_cast< model_t* >( instance.m_pBrushModel ) );
if ( !pRender )
continue;
for ( int m = 0; m < pRender->meshCount; ++m )
{
brushrendermesh_t &mesh = pRender->pMeshes[m];
int nBatchIndex = mesh.firstBatch;
for ( int j = 0; j < mesh.batchCount; ++j, ++nBatchIndex )
{
// Select proper override material
const brushrenderbatch_t &renderBatch = pRender->pBatches[ nBatchIndex ];
int nAlphaTest = (int)renderBatch.pMaterial->IsAlphaTested();
int nNoCull = (int)renderBatch.pMaterial->IsTwoSided();
IMaterial *pDepthWriteMaterial;
if ( nModelTypeFlags & STUDIO_SSAODEPTHTEXTURE )
{
pDepthWriteMaterial = g_pMaterialSSAODepthWrite[ nAlphaTest ][ nNoCull ];
}
else
{
pDepthWriteMaterial = g_pMaterialDepthWrite[ nAlphaTest ][ nNoCull ];
}
int nIndex = batchesToRender.AddToTail();
BrushBatchRenderData_t &batch = batchesToRender[nIndex];
batch.m_pInstanceData = &instance;
batch.m_pBrushRender = pRender;
batch.m_nBatchIndex = nBatchIndex;
batch.m_nIsAlphaTested = nAlphaTest;
batch.m_pMaterial = pDepthWriteMaterial;
}
}
}
}
//-----------------------------------------------------------------------------
// Sorts shadows
//-----------------------------------------------------------------------------
inline bool __cdecl CBrushBatchRender::ShadowSortLessFunc( const BrushBatchRenderData_t &left, const BrushBatchRenderData_t &right )
{
if ( left.m_pMaterial != right.m_pMaterial )
return left.m_pMaterial < right.m_pMaterial;
if ( left.m_pInstanceData->m_pBrushToWorld != right.m_pInstanceData->m_pBrushToWorld )
return left.m_pInstanceData->m_pBrushToWorld < right.m_pInstanceData->m_pBrushToWorld;
return false;
}
//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawShadowBatchList( IMatRenderContext* pRenderContext, int nCount, BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
VPROF( "DrawShadowBatchList" );
PIXEVENT( pRenderContext, "DrawShadowBatchList()" );
pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );
MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );
for ( int i = 0; i < nCount; i++ )
{
BrushInstanceGroup_t &group = pInstanceGroup[i];
if ( group.m_pRenderData->m_nIsAlphaTested )
{
static unsigned int originalTextureVarCache = 0;
IMaterialVar *pOriginalTextureVar = group.m_pActualMaterial->FindVarFast( "$basetexture", &originalTextureVarCache );
static unsigned int originalTextureFrameVarCache = 0;
IMaterialVar *pOriginalTextureFrameVar = group.m_pActualMaterial->FindVarFast( "$frame", &originalTextureFrameVarCache );
static unsigned int originalAlphaRefCache = 0;
IMaterialVar *pOriginalAlphaRefVar = group.m_pActualMaterial->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache );
static unsigned int textureVarCache = 0;
IMaterialVar *pTextureVar = group.m_pMaterial->FindVarFast( "$basetexture", &textureVarCache );
static unsigned int textureFrameVarCache = 0;
IMaterialVar *pTextureFrameVar = group.m_pMaterial->FindVarFast( "$frame", &textureFrameVarCache );
static unsigned int alphaRefCache = 0;
IMaterialVar *pAlphaRefVar = group.m_pMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache );
if( pTextureVar && pOriginalTextureVar )
{
pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() );
}
if( pTextureFrameVar && pOriginalTextureFrameVar )
{
pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() );
}
if( pAlphaRefVar && pOriginalAlphaRefVar )
{
pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() );
}
}
pRenderContext->Bind( group.m_pMaterial, NULL );
// Only writing indices
// FIXME: Can we make this a static index buffer?
IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
indexBuilder.Lock( group.m_nIndexCount, 0 );
int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);
for ( int j = 0; j < group.m_nCount; ++j )
{
BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
for ( int k = 0; k < batch.surfaceCount; k++ )
{
const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
BuildIndicesForSurface( indexBuilder, surfID );
}
MeshInstanceData_t &instance = pInstance[ j ];
instance.m_pEnvCubemap = NULL;
instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
instance.m_pLightingState = NULL;
instance.m_nBoneCount = 1;
instance.m_pBoneRemap = NULL;
instance.m_nIndexOffset = nIndexOffset;
instance.m_nIndexCount = batch.indexCount;
instance.m_nPrimType = MATERIAL_TRIANGLES;
instance.m_pColorBuffer = NULL;
instance.m_nColorVertexOffsetInBytes = 0;
instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
instance.m_pIndexBuffer = pBuildIndexBuffer;
instance.m_nVertexOffsetInBytes = 0;
instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
instance.m_nLightmapPageId = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP;
instance.m_bColorBufferHasIndirectLightingOnly = false;
nIndexOffset += batch.indexCount;
}
indexBuilder.End( );
pRenderContext->DrawInstances( group.m_nCount, pInstance );
}
}
//-----------------------------------------------------------------------------
// Main entry point for rendering an array of brush model shadows
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawBrushModelShadowArray( IMatRenderContext* pRenderContext, int nCount,
const BrushArrayInstanceData_t *pInstanceData, int nModelTypeFlags )
{
CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > batchesToRender;
BuildShadowBatchListToDraw( nCount, pInstanceData, batchesToRender, nModelTypeFlags );
int nBatchCount = batchesToRender.Count();
BrushBatchRenderData_t *pBatchData = batchesToRender.Base();
std::make_heap( pBatchData, pBatchData + nBatchCount, ShadowSortLessFunc );
std::sort_heap( pBatchData, pBatchData + nBatchCount, ShadowSortLessFunc );
CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > instanceGroups;
int nMaxInstanceCount = ComputeInstanceGroups( pRenderContext, nBatchCount, pBatchData, instanceGroups );
DrawShadowBatchList( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
}