perf(generation): gate the tile cache window by LOD; LOD0 back to switch-off speed
2de603c's tile-sized room cache cut worker CPU, but LOD0 generation was ~9% slower. The per-tile
split shows the loss was evaluation (more candidates per sample in a bigger window), not cache
building. Policy, measured per path and LOD:
- fused path: tile window at LOD >= voxel.TileCacheWindowMinLOD (default 3)
- op-stack path: tile window at LOD >= voxel.TileCacheWindowOpStackMinLOD (default 4)
- lower LODs keep the 4-chunk worker window
- voxel.SpatialIndex: -1 auto (off at fused LOD0, on elsewhere), 0 or 1
Proof caveat closed: VF_ComputeTerrainOpEnvelope adds authored terrain-op extents (pits, chimneys,
detail overrides, ...) to the invariance envelope. Ops beyond the proven 256-voxel extent, or
malformed/unloaded ones, force the legacy window, so window invariance holds for every asset.
Sampler fix: the leaf symbol kept pointers into a TMap that FindOrAdd can rehash while later
frames resolve; the leaf is now re-found by program counter after insertion.
Game path, two runs each, sampler off (Codex; checked against its report):
LOD0 request p50/p95 generation p50/p95 worker s
static optimized 106.3 / 175.9 ms 76.6 / 124.3 ms 39.2
static switch-off 106.3 / 173.0 75.9 / 124.1 58.5 (-33%)
moving optimized 79.4 / 158.5 72.4 / 123.7 113.7
moving switch-off 82.8 / 160.4 73.2 / 125.4 141.9 (-20%)
LOD0 is flat within noise static and slightly better moving; worker CPU keeps most of the gain.
Field exact: 841/841 per-tile triangle identity, export OBJ SHA-256 unchanged (b3e5f4c3..9b377),
capability 20,830 / 10,909.
Caution: compare these numbers only within this round. The machine is in daytime use, so
absolute figures drift between rounds.
Current LOD0 map (sampler, moving, inclusive): tunnel SDF 12.5% + tunnel core 12.3% (the same
tunnel in two coordinate spaces); passage carving 11.1%, structural posts 11.4%, native floor
4.1%, with floor projection at 8.9% inside them; cave warp ~8% exclusive. Development-build-only
overheads: TArray RangeCheck ~6.5%, per-sample FScopedTimer ~2.6%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
2de603cd77
commit
cfad3d1c18
8 files changed
+729
-146
No files matched your search
@@ -3822,6 +3822,21 @@ namespace
|
||||
* voxel PRÈS D'UNE SURFACE, pas partout) sans que onze opérateurs la refassent chacun. */
|
||||
FStrateGenerationParams LocalParams;
|
||||
bool bLocalParamsValid = false;
|
||||
|
||||
// Window invariance is a cache-policy proof, not a sample evaluator. Keep its result
|
||||
// beside the worker-local source state so LOD0 does not pay the full reach walk once
|
||||
// per voxel.
|
||||
bool bTileCachePolicyValid = false;
|
||||
bool bTileCachePolicyResult = false;
|
||||
bool bTileCachePolicyHasContext = false;
|
||||
int32 TileCachePolicySampleStep = 0;
|
||||
FIntVector TileCachePolicyOrigin = FIntVector::ZeroValue;
|
||||
int32 TileCachePolicyStep = 0;
|
||||
int32 TileCachePolicyCells = 0;
|
||||
uint32 TileCachePolicyFingerprint = 0xFFFFFFFFu;
|
||||
uint32 TileCachePolicyLayout = 0xFFFFFFFFu;
|
||||
uint64 TileCachePolicyManagerLifetimeId = 0;
|
||||
const TArray<FStrateTerrainOpEntry>* TileCachePolicyTerrainOps = nullptr;
|
||||
};
|
||||
|
||||
FState& State() const
|
||||
@@ -3861,7 +3876,8 @@ namespace
|
||||
FTunnelSupportFloorColumn& OutColumn) const
|
||||
{
|
||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||
WorldX, WorldY, Cache, OutColumn);
|
||||
WorldX, WorldY, Cache, OutColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
}
|
||||
|
||||
const FTunnelSupportFloorColumn* GetSupportColumn(const FVector& Position) const
|
||||
@@ -3917,7 +3933,8 @@ namespace
|
||||
const FVector& Position = S.LastWorldPosition;
|
||||
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(Position);
|
||||
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
Position.X, Position.Y, Position.Z, GetCache(), SupportColumn);
|
||||
Position.X, Position.Y, Position.Z, GetCache(), SupportColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
S.LastTunnelCoreEvaluationPosition = Position;
|
||||
S.bLastTunnelCoreWorldEvaluationValid = true;
|
||||
}
|
||||
@@ -3991,7 +4008,9 @@ namespace
|
||||
*/
|
||||
float ProbeSdfUnwarped(float X, float Y, float Z) const
|
||||
{
|
||||
return VoxelCaveMorphology::EvaluateSDFCached(X, Y, Z, GetCache(), P.SDFBlendRadius);
|
||||
return VoxelCaveMorphology::EvaluateSDFCached(
|
||||
X, Y, Z, GetCache(), P.SDFBlendRadius, nullptr,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
}
|
||||
|
||||
/** Le Z « effectif » : `VerticalScale` étire le monde AVANT le bruit. Pure fonction de Z et
|
||||
@@ -4126,10 +4145,47 @@ namespace
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
int32 RequestedTileStep = SampleStep;
|
||||
int32 RequestedTileCells = 0;
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
&& VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P);
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
const int32 ChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||
const int32 ChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
|
||||
FIntVector(ChunkX, ChunkY, ChunkZ));
|
||||
if (Def) { TerrainOps = &Def->TerrainOperations; }
|
||||
}
|
||||
const bool bTileWindowEligible = VoxelGenLOD::IsTileCacheWindowEnabled(false);
|
||||
const bool bHasTileWindowContext = VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells);
|
||||
const bool bTilePolicyKeyChanged =
|
||||
!S.bTileCachePolicyValid
|
||||
|| S.bTileCachePolicyHasContext
|
||||
!= (bTileWindowEligible && bHasTileWindowContext)
|
||||
|| S.TileCachePolicySampleStep != RequestedTileStep
|
||||
|| S.TileCachePolicyOrigin != RequestedTileOrigin
|
||||
|| S.TileCachePolicyStep != RequestedTileStep
|
||||
|| S.TileCachePolicyCells != RequestedTileCells
|
||||
|| S.TileCachePolicyFingerprint != ParamsFingerprint
|
||||
|| S.TileCachePolicyLayout != LayoutVersion
|
||||
|| S.TileCachePolicyManagerLifetimeId != ManagerLifetimeId
|
||||
|| S.TileCachePolicyTerrainOps != TerrainOps;
|
||||
if (bTilePolicyKeyChanged)
|
||||
{
|
||||
S.bTileCachePolicyValid = true;
|
||||
S.bTileCachePolicyHasContext = bTileWindowEligible && bHasTileWindowContext;
|
||||
S.TileCachePolicySampleStep = RequestedTileStep;
|
||||
S.TileCachePolicyOrigin = RequestedTileOrigin;
|
||||
S.TileCachePolicyStep = RequestedTileStep;
|
||||
S.TileCachePolicyCells = RequestedTileCells;
|
||||
S.TileCachePolicyFingerprint = ParamsFingerprint;
|
||||
S.TileCachePolicyLayout = LayoutVersion;
|
||||
S.TileCachePolicyManagerLifetimeId = ManagerLifetimeId;
|
||||
S.TileCachePolicyTerrainOps = TerrainOps;
|
||||
S.bTileCachePolicyResult = S.bTileCachePolicyHasContext
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P, TerrainOps);
|
||||
}
|
||||
const bool bUseTileCacheWindow = S.bTileCachePolicyResult;
|
||||
int32 RegionSize = 0;
|
||||
int32 RegionMinX = 0;
|
||||
int32 RegionMinY = 0;
|
||||
@@ -4143,9 +4199,11 @@ namespace
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 RegionChunks = SampleStep <= 1
|
||||
? 1
|
||||
: (SampleStep >= 32 ? 4 : FMath::Max(4, (SampleStep + 3) / 4));
|
||||
// Fine requests retain the historical four-chunk worker window. The op-stack
|
||||
// tile-sized window is gated to LOD4+ (the fused path starts at LOD3), so
|
||||
// LOD0-LOD2 and the op-stack's LOD3 floor do not pay a full tile rebuild on their
|
||||
// critical path while still reusing neighbouring chunks on the same worker.
|
||||
const int32 RegionChunks = 4;
|
||||
RegionSize = CHUNK_SIZE * RegionChunks;
|
||||
const int32 RegionCellX = FMath::FloorToInt(WorldX / (float)RegionSize);
|
||||
const int32 RegionCellY = FMath::FloorToInt(WorldY / (float)RegionSize);
|
||||
@@ -4157,17 +4215,6 @@ namespace
|
||||
+ (bUseTileCacheWindow ? static_cast<float>(SampleStep) : 0.0f)
|
||||
+ 2.0f;
|
||||
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
const int32 ChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||
const int32 ChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
|
||||
FIntVector(ChunkX, ChunkY, ChunkZ));
|
||||
if (Def) { TerrainOps = &Def->TerrainOperations; }
|
||||
}
|
||||
|
||||
const FChunkSDFCache* PreviousActiveCache = S.ActiveCache;
|
||||
bool bRebuiltLocalCache = false;
|
||||
if (SampleStep > 1)
|
||||
@@ -4247,9 +4294,10 @@ namespace
|
||||
S.SupportColumns.Invalidate();
|
||||
}
|
||||
|
||||
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(false);
|
||||
float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
||||
WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius,
|
||||
&S.NearestRoom);
|
||||
&S.NearestRoom, bUseSpatialIndex);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF
|
||||
@@ -4289,7 +4337,7 @@ namespace
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
GetCache().PitSpatialIndex, GetCache().Pits.Num(),
|
||||
WorldX, WorldY, EvaluatePit);
|
||||
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
|
||||
|
||||
auto EvaluateChimney = [&](int32 ChimneyIndex)
|
||||
{
|
||||
@@ -4320,7 +4368,7 @@ namespace
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
|
||||
WorldX, WorldY, EvaluateChimney);
|
||||
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
||||
|
||||
InOut.Sdf = CaveSDF;
|
||||
|
||||
@@ -4332,7 +4380,8 @@ namespace
|
||||
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
|
||||
S.LastWorldPosition);
|
||||
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
WorldX, WorldY, WorldZ, GetCache(), SupportColumn);
|
||||
WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
|
||||
bUseSpatialIndex);
|
||||
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
|
||||
S.bLastTunnelCoreWorldEvaluationValid = true;
|
||||
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|
||||
@@ -4782,13 +4831,49 @@ namespace
|
||||
return;
|
||||
}
|
||||
|
||||
int32 StrateIdx = 0;
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
StrateIdx = LiveManager->GetStrateIndex(
|
||||
((float)CZ0 + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
|
||||
for (int32 CZ = CZ0 + 1; CZ <= CZ1; ++CZ)
|
||||
{
|
||||
if (LiveManager->GetStrateIndex(
|
||||
((float)CZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE) != StrateIdx)
|
||||
{
|
||||
Unknown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// A room operation is part of the graph cache. Every Z slice covered by this
|
||||
// proof must resolve to the same definition before one cache can represent it.
|
||||
UVoxelStrateDefinition* Def0 = nullptr;
|
||||
for (int32 CZ = CZ0; CZ <= CZ1; ++CZ)
|
||||
{
|
||||
UVoxelStrateDefinition* Def =
|
||||
LiveManager->GetStrateForChunk(FIntVector(0, 0, CZ));
|
||||
if (CZ == CZ0)
|
||||
{
|
||||
Def0 = Def;
|
||||
}
|
||||
else if (Def != Def0)
|
||||
{
|
||||
Unknown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Def0) { TerrainOps = &Def0->TerrainOperations; }
|
||||
}
|
||||
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
int32 RequestedTileStep = FMath::Max(Ctx.Step, 1);
|
||||
int32 RequestedTileCells = 0;
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled(false)
|
||||
&& VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P);
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P, TerrainOps);
|
||||
const float RequestedTileExtent = static_cast<float>(
|
||||
static_cast<int64>(RequestedTileStep)
|
||||
* static_cast<int64>(RequestedTileCells));
|
||||
@@ -4849,46 +4934,6 @@ namespace
|
||||
return;
|
||||
}
|
||||
|
||||
int32 StrateIdx = 0;
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
StrateIdx = LiveManager->GetStrateIndex(
|
||||
((float)CZ0 + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
|
||||
for (int32 CZ = CZ0 + 1; CZ <= CZ1; ++CZ)
|
||||
{
|
||||
if (LiveManager->GetStrateIndex(
|
||||
((float)CZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE) != StrateIdx)
|
||||
{
|
||||
Unknown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The room-op pool is part of the SDF: BuildChunkCache bakes pits, chimneys, and
|
||||
// columns from it. Every chunk touched by this query must therefore resolve to the
|
||||
// same definition before one cache can represent the box.
|
||||
// GetStrateForChunk is a Z-layout lookup; the manager ignores
|
||||
// X/Y. Check one representative per touched Z instead of
|
||||
// repeating the same layout search for every coarse XY key.
|
||||
UVoxelStrateDefinition* Def0 = nullptr;
|
||||
for (int32 CZ = CZ0; CZ <= CZ1; ++CZ)
|
||||
{
|
||||
UVoxelStrateDefinition* Def =
|
||||
LiveManager->GetStrateForChunk(FIntVector(0, 0, CZ));
|
||||
if (CZ == CZ0)
|
||||
{
|
||||
Def0 = Def;
|
||||
}
|
||||
else if (Def != Def0)
|
||||
{
|
||||
Unknown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Def0) { TerrainOps = &Def0->TerrainOperations; }
|
||||
}
|
||||
|
||||
const uint32 LV = Ctx.LayoutVersion;
|
||||
auto IsSharedCacheCurrent = [&]() -> bool
|
||||
{
|
||||
@@ -5085,7 +5130,8 @@ namespace
|
||||
CacheSearchMaxY,
|
||||
P, SeedU, StrateIdx, true,
|
||||
CacheSearchMinZ,
|
||||
CacheSearchMaxZ);
|
||||
CacheSearchMaxZ,
|
||||
TerrainOps);
|
||||
const int64 MaxSharedChunkSpan = Ctx.Step >= 32 ? 32 : 16;
|
||||
bool bUsedSharedCache = false;
|
||||
const bool bTileWithinSharedBudget = bUseTileCacheWindow
|
||||
@@ -6747,13 +6793,15 @@ namespace
|
||||
{
|
||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||
static_cast<float>(World.X), static_cast<float>(World.Y),
|
||||
GetBoxCache(), EmptySupportColumn);
|
||||
GetBoxCache(), EmptySupportColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
SupportColumn = &EmptySupportColumn;
|
||||
}
|
||||
const FTunnelCoreWorldEvaluation Evaluation =
|
||||
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
static_cast<float>(World.X), static_cast<float>(World.Y),
|
||||
static_cast<float>(World.Z), GetBoxCache(), SupportColumn);
|
||||
static_cast<float>(World.Z), GetBoxCache(), SupportColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
++B.ExactTailEvaluated;
|
||||
if (!FMath::IsFinite(Evaluation.SDF))
|
||||
{
|
||||
@@ -8062,7 +8110,8 @@ namespace
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
Rooms->GetCache().ColumnSpatialIndex,
|
||||
Rooms->GetCache().Columns.Num(), WorldX, WorldY, EvaluateColumn);
|
||||
Rooms->GetCache().Columns.Num(), WorldX, WorldY, EvaluateColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||
}
|
||||
|
||||
/** The cached column list is the complete source of this op's work. A valid empty list is
|
||||
|
||||
Reference in new issue
Block a user