dmap now contains path tracing optimizations stored in iceproc files.

This commit is contained in:
Justin Marshall
2026-05-26 12:14:39 -07:00
parent 754f5b4aa0
commit 41cbd5669b
50 changed files with 350449 additions and 624741 deletions
+11
View File
@@ -151,6 +151,7 @@ void DmapHelp( void ) {
"Usage: dmap [options] mapfile\n"
"Options:\n"
"noCurves = don't process curves\n"
"noAutoAreaRefine = don't add generated optimization portals inside areas\n"
"noCM = don't create collision map\n"
"noAAS = don't create AAS files\n"
@@ -163,6 +164,7 @@ ResetDmapGlobals
============
*/
void ResetDmapGlobals( void ) {
ClearInterAreaPortals();
dmapGlobals.mapFileBase[0] = '\0';
dmapGlobals.dmapFile = NULL;
dmapGlobals.mapPlanes.Clear();
@@ -180,6 +182,7 @@ void ResetDmapGlobals( void ) {
dmapGlobals.noTJunc = false;
dmapGlobals.nomerge = false;
dmapGlobals.noFlood = false;
dmapGlobals.autoAreaRefine = true;
dmapGlobals.noClipSides = false;
dmapGlobals.noLightCarve = false;
dmapGlobals.noShadow = false;
@@ -243,6 +246,12 @@ void Dmap( const idCmdArgs &args ) {
} else if ( !idStr::Icmp( s, "noFlood" ) ) {
common->Printf( "noFlood = true\n" );
dmapGlobals.noFlood = true;
} else if ( !idStr::Icmp( s, "autoAreaRefine" ) ) {
common->Printf( "autoAreaRefine = true\n" );
dmapGlobals.autoAreaRefine = true;
} else if ( !idStr::Icmp( s, "noAutoAreaRefine" ) ) {
common->Printf( "autoAreaRefine = false\n" );
dmapGlobals.autoAreaRefine = false;
} else if ( !idStr::Icmp( s, "noLightCarve" ) ) {
common->Printf( "noLightCarve = true\n" );
dmapGlobals.noLightCarve = true;
@@ -375,6 +384,8 @@ void Dmap( const idCmdArgs &args ) {
}
}
ClearInterAreaPortals();
// free the common .map representation
delete dmapGlobals.dmapFile;
+6
View File
@@ -153,6 +153,8 @@ typedef struct node_s {
// needed for FindSideForPortal
int area; // determined by flood filling up to areaportals
int originalArea; // area before automatic refinement
int areaRefineFlood; // temporary flood marker for automatic refinement
int occupied; // 1 or greater can reach entity
uEntity_t * occupant; // for leak file testing
@@ -256,6 +258,7 @@ typedef struct {
bool noTJunc;
bool nomerge;
bool noFlood;
bool autoAreaRefine; // split designer-authored areas with generated BSP portals
bool noClipSides; // don't cut sides by solid leafs, use the entire thing
bool noLightCarve; // extra triangle subdivision by light frustums
shadowOptLevel_t shadowOptLevel;
@@ -339,6 +342,8 @@ void GLS_EndScene( void );
typedef struct {
int area0, area1;
side_t *side;
idWinding *winding; // generated portals don't have a map brush side
bool generated;
} interAreaPortal_t;
extern interAreaPortal_t interAreaPortals[MAX_INTER_AREA_PORTALS];
@@ -348,6 +353,7 @@ bool FloodEntities( tree_t *tree );
void FillOutside( uEntity_t *e );
void FloodAreas( uEntity_t *e );
void MakeTreePortals( tree_t *tree );
void ClearInterAreaPortals( void );
void FreePortal( uPortal_t *p );
//=============================================================================
+227 -2
View File
@@ -310,6 +310,134 @@ static void WriteUTriangles( const srfTriangles_t *uTris ) {
}
}
static bool GetImageAverageColorForProc( idImage *image, float averageColor[4] ) {
if ( !image ) {
return false;
}
byte *pic = NULL;
int width = 0;
int height = 0;
textureDepth_t depth = TD_DEFAULT;
// Do not call idImage::ActuallyLoadImage() from dmap. dmap can run inside
// the live game process, and ActuallyLoadImage() mutates GL/D3D texture
// objects. Read the source pixels directly so metadata generation is pure.
R_LoadImageProgram( image->imgName, &pic, &width, &height, NULL, &depth );
if ( !pic || width <= 0 || height <= 0 ) {
if ( pic ) {
R_StaticFree( pic );
}
return false;
}
uint64_t sum[4] = { 0, 0, 0, 0 };
const int pixelCount = width * height;
for ( int i = 0 ; i < pixelCount ; i++ ) {
const byte *rgba = pic + i * 4;
sum[0] += rgba[0];
sum[1] += rgba[1];
sum[2] += rgba[2];
sum[3] += rgba[3];
}
R_StaticFree( pic );
const float scale = 1.0f / ( 255.0f * (float)pixelCount );
averageColor[0] = idMath::ClampFloat( 0.0f, 1.0f, sum[0] * scale );
averageColor[1] = idMath::ClampFloat( 0.0f, 1.0f, sum[1] * scale );
averageColor[2] = idMath::ClampFloat( 0.0f, 1.0f, sum[2] * scale );
averageColor[3] = idMath::ClampFloat( 0.0f, 1.0f, sum[3] * scale );
return true;
}
static bool GetVertexAverageColorForProc( const srfTriangles_t *uTris, float averageColor[4] ) {
float sum[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
if ( !uTris || uTris->numVerts <= 0 ) {
return false;
}
for ( int i = 0 ; i < uTris->numVerts ; i++ ) {
sum[0] += uTris->verts[i].color[0] * ( 1.0f / 255.0f );
sum[1] += uTris->verts[i].color[1] * ( 1.0f / 255.0f );
sum[2] += uTris->verts[i].color[2] * ( 1.0f / 255.0f );
sum[3] += uTris->verts[i].color[3] * ( 1.0f / 255.0f );
}
const float invNumVerts = 1.0f / uTris->numVerts;
averageColor[0] = sum[0] * invNumVerts;
averageColor[1] = sum[1] * invNumVerts;
averageColor[2] = sum[2] * invNumVerts;
averageColor[3] = sum[3] * invNumVerts;
return averageColor[0] > 0.0f || averageColor[1] > 0.0f || averageColor[2] > 0.0f || averageColor[3] > 0.0f;
}
static void WriteRaytraceSurfaceMetadata( const idMaterial *material, const srfTriangles_t *uTris, int areaNum ) {
float averageColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float emissiveColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float bounds[6];
uint32_t flags = 0;
if ( material ) {
idImage *diffuseImage = material->GetDiffuseImage( NULL );
if ( !GetImageAverageColorForProc( diffuseImage, averageColor ) ) {
GetVertexAverageColorForProc( uTris, averageColor );
}
idImage *glowImage = material->GetGlowImage( NULL );
if ( !glowImage ) {
for ( int stageNum = 0 ; stageNum < material->GetNumStages() ; stageNum++ ) {
const shaderStage_t *stage = material->GetStage( stageNum );
if ( stage && stage->lighting == SL_GLOWMAP && stage->texture.image ) {
glowImage = stage->texture.image;
break;
}
}
}
if ( glowImage ) {
GetImageAverageColorForProc( glowImage, emissiveColor );
emissiveColor[3] = Max( Max( emissiveColor[0], emissiveColor[1] ), emissiveColor[2] ) > 0.01f ? 1.0f : 0.0f;
if ( emissiveColor[3] > 0.0f ) {
flags |= GL_RAYTRACING_PROC_SURFACE_EMISSIVE;
}
}
if ( !material->IsLitMaterial() ) {
flags |= GL_RAYTRACING_PROC_SURFACE_UNLIT;
}
if ( material->IsSky() ) {
flags |= GL_RAYTRACING_PROC_SURFACE_SKY;
}
if ( material->Coverage() == MC_PERFORATED ) {
flags |= GL_RAYTRACING_PROC_SURFACE_ALPHA_TESTED;
}
if ( material->GetCullType() == CT_TWO_SIDED ) {
flags |= GL_RAYTRACING_PROC_SURFACE_TWO_SIDED;
}
}
idBounds triBounds;
triBounds.Clear();
for ( int i = 0 ; i < uTris->numVerts ; i++ ) {
triBounds.AddPoint( uTris->verts[i].xyz );
}
bounds[0] = triBounds[0][0];
bounds[1] = triBounds[0][1];
bounds[2] = triBounds[0][2];
bounds[3] = triBounds[1][0];
bounds[4] = triBounds[1][1];
bounds[5] = triBounds[1][2];
procFile->WriteFloatString( "raytraceSurface { flags %u area %i averageColor ", flags, areaNum );
Write1DMatrix( procFile, 4, averageColor );
procFile->WriteFloatString( " emissive " );
Write1DMatrix( procFile, 4, emissiveColor );
procFile->WriteFloatString( " bounds " );
Write1DMatrix( procFile, 6, bounds );
procFile->WriteFloatString( " }\n" );
}
/*
====================
@@ -478,13 +606,15 @@ typedef struct interactionTris_s {
procFile->WriteFloatString( "/* surface %i */ { ", surfaceNum );
surfaceNum++;
procFile->WriteFloatString( "\"%s\" ", ambient->material->GetName() );
const idMaterial *surfaceMaterial = ambient->material;
procFile->WriteFloatString( "\"%s\" ", surfaceMaterial->GetName() );
uTri = ShareMapTriVerts( ambient );
FreeTriList( ambient );
CleanupUTriangles( uTri );
WriteUTriangles( uTri );
WriteRaytraceSurfaceMetadata( surfaceMaterial, uTri, areaNum );
R_FreeStaticTriSurf( uTri );
procFile->WriteFloatString( "}\n\n" );
@@ -583,7 +713,16 @@ static void WriteOutputPortals( uEntity_t *e ) {
procFile->WriteFloatString( "/* interAreaPortal format is: numPoints positiveSideArea negativeSideArea ( point) ... */\n" );
for ( i = 0 ; i < numInterAreaPortals ; i++ ) {
iap = &interAreaPortals[i];
w = iap->side->winding;
if ( iap->winding ) {
w = iap->winding;
} else if ( iap->side && iap->side->visibleHull ) {
w = iap->side->visibleHull;
} else if ( iap->side ) {
w = iap->side->winding;
} else {
common->Error( "WriteOutputPortals: inter-area portal without a winding" );
continue;
}
procFile->WriteFloatString("/* iap %i */ %i %i %i ", i, w->GetNumPoints(), iap->area0, iap->area1 );
for ( j = 0 ; j < w->GetNumPoints() ; j++ ) {
Write1DMatrix( procFile, 3, (*w)[j].ToFloatPtr() );
@@ -594,6 +733,88 @@ static void WriteOutputPortals( uEntity_t *e ) {
procFile->WriteFloatString( "}\n\n" );
}
/*
====================
WritePathTraceAreas
Writes deterministic per-area data for path-tracing light culling.
This is deliberately independent of authored light entities so dynamic lights
can use the same area table at runtime.
====================
*/
static void WritePathTraceAreas( uEntity_t *e ) {
procFile->WriteFloatString( "pathTraceAreas { /* numAreas = */ %i\n\n", e->numAreas );
procFile->WriteFloatString( "/* pathTraceArea format is: areaNum surfaceGroups ( bounds[6] ) neighborCount neighborArea ... lightCount \"lightName\" ... */\n" );
for ( int areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) {
uArea_t *area = &e->areas[areaNum];
idBounds bounds;
int surfaceGroups = 0;
idList<int> neighbors;
idList<mapLight_t *> lights;
bounds.Clear();
for ( optimizeGroup_t *group = area->groups ; group ; group = group->nextGroup ) {
if ( !group->triList ) {
continue;
}
surfaceGroups++;
bounds.AddPoint( group->bounds[0] );
bounds.AddPoint( group->bounds[1] );
for ( int lightNum = 0 ; lightNum < group->numGroupLights ; lightNum++ ) {
mapLight_t *light = group->groupLights[lightNum];
if ( light && lights.FindIndex( light ) < 0 ) {
lights.Append( light );
}
}
}
if ( bounds[0][0] > bounds[1][0] ) {
if ( e->tree ) {
bounds = e->tree->bounds;
} else {
bounds.Zero();
}
}
for ( int portalNum = 0 ; portalNum < numInterAreaPortals ; portalNum++ ) {
const interAreaPortal_t *iap = &interAreaPortals[portalNum];
int neighbor = -1;
if ( iap->area0 == areaNum ) {
neighbor = iap->area1;
} else if ( iap->area1 == areaNum ) {
neighbor = iap->area0;
}
if ( neighbor >= 0 && neighbors.FindIndex( neighbor ) < 0 ) {
neighbors.Append( neighbor );
}
}
float boundsVec[6] = {
bounds[0][0], bounds[0][1], bounds[0][2],
bounds[1][0], bounds[1][1], bounds[1][2]
};
procFile->WriteFloatString( "/* area %i */ %i %i ", areaNum, areaNum, surfaceGroups );
Write1DMatrix( procFile, 6, boundsVec );
procFile->WriteFloatString( "%i ", neighbors.Num() );
for ( int i = 0 ; i < neighbors.Num() ; i++ ) {
procFile->WriteFloatString( "%i ", neighbors[i] );
}
procFile->WriteFloatString( "%i ", lights.Num() );
for ( int i = 0 ; i < lights.Num() ; i++ ) {
procFile->WriteFloatString( "\"%s\" ", lights[i]->name );
}
procFile->WriteFloatString( "\n" );
}
procFile->WriteFloatString( "}\n\n" );
}
/*
====================
@@ -625,6 +846,10 @@ static void WriteOutputEntity( int entityNum ) {
// output the nodes
WriteOutputNodes( e->tree->headnode );
}
if ( entityNum == 0 ) {
WritePathTraceAreas( e );
}
}
+276 -1
View File
@@ -39,6 +39,33 @@ int numInterAreaPortals;
int c_active_portals;
int c_peak_portals;
static int c_autoAreaRefineSplits;
static int c_autoAreaRefinePortals;
static int c_autoAreaRefineFlood;
#define AUTO_AREA_REFINE_MIN_LEAFS 12
#define AUTO_AREA_REFINE_MIN_SPLIT_LEAFS 4
#define AUTO_AREA_REFINE_MAX_SPLITS 256
#define AUTO_AREA_REFINE_MIN_PORTAL_AREA 64.0f
#define AUTO_AREA_REFINE_MAX_PORTAL_AREA 65536.0f
/*
=============
ClearInterAreaPortals
=============
*/
void ClearInterAreaPortals( void ) {
for ( int i = 0 ; i < numInterAreaPortals ; i++ ) {
if ( interAreaPortals[i].generated && interAreaPortals[i].winding ) {
delete interAreaPortals[i].winding;
}
interAreaPortals[i].side = NULL;
interAreaPortals[i].winding = NULL;
interAreaPortals[i].generated = false;
}
numInterAreaPortals = 0;
}
/*
===========
AllocPortal
@@ -824,10 +851,244 @@ void ClearAreas_r( node_t *node ) {
return;
}
node->area = -1;
node->originalArea = -1;
node->areaRefineFlood = 0;
}
//=============================================================
static void MarkOriginalAreas_r( node_t *node ) {
if ( node->planenum != PLANENUM_LEAF ) {
MarkOriginalAreas_r( node->children[0] );
MarkOriginalAreas_r( node->children[1] );
return;
}
node->originalArea = node->area;
}
static void CollectAreaLeaves_r( node_t *node, int area, idList<node_t *> &leaves ) {
if ( node->planenum != PLANENUM_LEAF ) {
CollectAreaLeaves_r( node->children[0], area, leaves );
CollectAreaLeaves_r( node->children[1], area, leaves );
return;
}
if ( !node->opaque && node->area == area ) {
leaves.Append( node );
}
}
static void FloodAreaComponent_r( node_t *node, int area, const uPortal_t *blockedPortal, int floodNum, idList<node_t *> &component ) {
uPortal_t *p;
int s;
if ( node->planenum != PLANENUM_LEAF || node->opaque || node->area != area ) {
return;
}
if ( node->areaRefineFlood == floodNum ) {
return;
}
node->areaRefineFlood = floodNum;
component.Append( node );
for ( p = node->portals ; p ; p = p->next[s] ) {
s = ( p->nodes[1] == node );
if ( p == blockedPortal ) {
continue;
}
if ( !Portal_Passable( p ) ) {
continue;
}
FloodAreaComponent_r( p->nodes[!s], area, blockedPortal, floodNum, component );
}
}
static uPortal_t *FindBestAutoAreaPortal( int area, const idList<node_t *> &leaves ) {
uPortal_t *bestPortal;
float bestScore;
bestPortal = NULL;
bestScore = 0.0f;
for ( int i = 0 ; i < leaves.Num() ; i++ ) {
node_t *node = leaves[i];
int s;
for ( uPortal_t *p = node->portals ; p ; p = p->next[s] ) {
s = ( p->nodes[1] == node );
if ( p->nodes[0] != node ) {
continue; // evaluate each portal once
}
if ( !Portal_Passable( p ) ) {
continue;
}
if ( p->nodes[1]->area != area ) {
continue;
}
float portalArea = p->winding->GetArea();
if ( portalArea < AUTO_AREA_REFINE_MIN_PORTAL_AREA || portalArea > AUTO_AREA_REFINE_MAX_PORTAL_AREA ) {
continue;
}
idList<node_t *> component;
FloodAreaComponent_r( p->nodes[0], area, p, ++c_autoAreaRefineFlood, component );
int frontLeafs = component.Num();
int backLeafs = leaves.Num() - frontLeafs;
if ( frontLeafs < AUTO_AREA_REFINE_MIN_SPLIT_LEAFS || backLeafs < AUTO_AREA_REFINE_MIN_SPLIT_LEAFS ) {
continue;
}
int smaller = frontLeafs < backLeafs ? frontLeafs : backLeafs;
int larger = frontLeafs > backLeafs ? frontLeafs : backLeafs;
float balance = (float)smaller / (float)larger;
float score = balance * 100000.0f / portalArea;
if ( score > bestScore ) {
bestScore = score;
bestPortal = p;
}
}
}
return bestPortal;
}
static bool SplitAreaWithGeneratedPortal( uEntity_t *e, int area ) {
idList<node_t *> leaves;
idList<node_t *> component;
CollectAreaLeaves_r( e->tree->headnode, area, leaves );
if ( leaves.Num() < AUTO_AREA_REFINE_MIN_LEAFS ) {
return false;
}
uPortal_t *portal = FindBestAutoAreaPortal( area, leaves );
if ( !portal ) {
return false;
}
int floodNum = ++c_autoAreaRefineFlood;
FloodAreaComponent_r( portal->nodes[0], area, portal, floodNum, component );
if ( component.Num() <= 0 || component.Num() >= leaves.Num() ) {
return false;
}
int newArea = e->numAreas;
e->numAreas++;
// Keep the larger partition on the original area number so repeated
// refinement tends to continue from the broadest remaining space.
if ( component.Num() <= leaves.Num() / 2 ) {
for ( int i = 0 ; i < component.Num() ; i++ ) {
component[i]->area = newArea;
}
} else {
for ( int i = 0 ; i < leaves.Num() ; i++ ) {
if ( leaves[i]->areaRefineFlood != floodNum ) {
leaves[i]->area = newArea;
}
}
}
c_autoAreaRefineSplits++;
return true;
}
static void AutoRefineAreas( uEntity_t *e ) {
int startingAreas;
if ( !dmapGlobals.autoAreaRefine ) {
return;
}
if ( e != &dmapGlobals.uEntities[0] ) {
return;
}
startingAreas = e->numAreas;
c_autoAreaRefineSplits = 0;
c_autoAreaRefinePortals = 0;
c_autoAreaRefineFlood = 0;
for ( int area = 0 ; area < e->numAreas && c_autoAreaRefineSplits < AUTO_AREA_REFINE_MAX_SPLITS ; area++ ) {
while ( c_autoAreaRefineSplits < AUTO_AREA_REFINE_MAX_SPLITS ) {
if ( !SplitAreaWithGeneratedPortal( e, area ) ) {
break;
}
}
}
if ( c_autoAreaRefineSplits > 0 ) {
common->Printf( "%5i auto area refinement splits (%i -> %i areas)\n", c_autoAreaRefineSplits, startingAreas, e->numAreas );
} else {
common->Printf( "%5i auto area refinement splits\n", 0 );
}
}
static void AddGeneratedInterAreaPortal( uPortal_t *p ) {
if ( numInterAreaPortals >= MAX_INTER_AREA_PORTALS ) {
common->Warning( "MAX_INTER_AREA_PORTALS hit while adding generated area portals" );
return;
}
if ( !p->winding || p->winding->IsTiny() ) {
return;
}
interAreaPortal_t *iap = &interAreaPortals[numInterAreaPortals];
numInterAreaPortals++;
iap->area0 = p->nodes[0]->area;
iap->area1 = p->nodes[1]->area;
iap->side = NULL;
iap->winding = p->winding->Copy();
iap->generated = true;
c_autoAreaRefinePortals++;
}
static void FindGeneratedInterAreaPortals_r( node_t *node ) {
uPortal_t *p;
int s;
if ( node->planenum != PLANENUM_LEAF ) {
FindGeneratedInterAreaPortals_r( node->children[0] );
FindGeneratedInterAreaPortals_r( node->children[1] );
return;
}
if ( node->opaque ) {
return;
}
for ( p = node->portals ; p ; p = p->next[s] ) {
node_t *other;
s = ( p->nodes[1] == node );
other = p->nodes[!s];
if ( other->opaque ) {
continue;
}
if ( !Portal_Passable( p ) ) {
continue;
}
if ( other->area <= node->area ) {
continue;
}
if ( other->originalArea != node->originalArea ) {
continue; // authored areaportal boundary
}
if ( FindSideForPortal( p ) ) {
continue; // designer-authored portal, already emitted
}
AddGeneratedInterAreaPortal( p );
}
}
/*
=================
@@ -895,6 +1156,11 @@ static void FindInterAreaPortals_r( node_t *node ) {
continue; // already emited
}
if ( numInterAreaPortals >= MAX_INTER_AREA_PORTALS ) {
common->Warning( "MAX_INTER_AREA_PORTALS hit while adding designer area portals" );
continue;
}
iap = &interAreaPortals[numInterAreaPortals];
numInterAreaPortals++;
if ( side->planenum == p->onnode->planenum ) {
@@ -905,6 +1171,8 @@ static void FindInterAreaPortals_r( node_t *node ) {
iap->area1 = p->nodes[0]->area;
}
iap->side = side;
iap->winding = NULL;
iap->generated = false;
}
}
@@ -934,13 +1202,20 @@ void FloodAreas( uEntity_t *e ) {
common->Printf ("%5i areas\n", c_areas);
e->numAreas = c_areas;
MarkOriginalAreas_r( e->tree->headnode );
AutoRefineAreas( e );
// make sure we got all of them
CheckAreas_r( e->tree->headnode );
// identify all portals between areas if this is the world
if ( e == &dmapGlobals.uEntities[0] ) {
numInterAreaPortals = 0;
ClearInterAreaPortals();
FindInterAreaPortals_r( e->tree->headnode );
if ( dmapGlobals.autoAreaRefine ) {
FindGeneratedInterAreaPortals_r( e->tree->headnode );
common->Printf( "%5i generated inter-area portals\n", c_autoAreaRefinePortals );
}
}
}