measure(step2c): setup once per chunk, still 1.6 us/sample -- exact per-sample density misses 600 ms

Editor-only, default off. 259^3 ring split into 1,000 aligned 32^3 chunks; cache/index builds
bounded by chunks (948 room/SDF caches, 800 active-op lists, 8,532 index tables); 800 block setups
+ 201,243 scalar fallback points (1.16%); no per-point GetDensityAt on supported routes.
Six workers: live 7.07 s wall / 1.678 us per sample; prepared 7.92 s / 1.595 us.
Candidate visits 21.2 -> 15.8 per sample, evaluations 6.31 -> 5.75; room / tunnel / tunnel-core
evaluations unchanged (48.1 M / 5.0 M / 38.4 M for 17.4 M samples). Passage evals 18.1 M -> 8.4 M.
Exact: 0 Q16/sign disagreements, five seeds, same hash on 1 and 6 workers, reach shrink caught.
Luna's verdict: 600 ms is not reachable while every lattice point evaluates the exact current
density; 27.7 worker-CPU seconds need >= 7.7x less work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-30 04:18:44 +02:00
1 parent fd9f809c1c
commit 43c11ccaa8
9 files changed
+484 -143

No files matched your search

@@ -75,13 +75,13 @@ namespace VoxelForgeRegionEval
return static_cast<int64>(Dimension) * Dimension * Dimension;
}
struct FRegionBrickSample
struct FRegionChunkSample
{
int32 LocalIndex = 0;
int32 OutputIndex = 0;
};
struct FRegionBrick
struct FRegionChunk
{
FIntVector Origin = FIntVector::ZeroValue;
int32 SizeX = 0;
@@ -90,7 +90,7 @@ namespace VoxelForgeRegionEval
int32 LatticeX = 0;
int32 LatticeY = 0;
int32 LatticeZ = 0;
TArray<FRegionBrickSample> RequestedSamples;
TArray<FRegionChunkSample> RequestedSamples;
};
}
@@ -148,8 +148,9 @@ namespace VoxelForgeRegionEval
float ReachScale,
TArray<float>* OutDensities,
FTiming& OutTiming,
bool bBrickProgram,
int32 RequestedWorkerCount)
bool bRegionProgram,
int32 RequestedWorkerCount,
bool bRequestWholeRunCounters)
{
OutTiming = FTiming();
const int32 NumWorkers = FMath::Clamp(RequestedWorkerCount, 1, 32);
@@ -167,58 +168,70 @@ namespace VoxelForgeRegionEval
{
return false;
}
const VoxelDensityProfile::EMode PreviousProfileMode = VoxelDensityProfile::GetMode();
const uint32 PreviousProfileInterval = VoxelDensityProfile::GetSampleInterval();
if (OutDensities != nullptr && SampleCount > MAX_int32)
{
return false;
}
const bool bCollectWholeRunCounters = bRequestWholeRunCounters && bFullRing;
if (bCollectWholeRunCounters)
{
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Attribution, 16384);
}
const double StartSeconds = FPlatformTime::Seconds();
TArray<FRegionBrick> Bricks;
TArray<int32> WorkBricks;
if (bBrickProgram)
TArray<FRegionChunk> Regions;
TArray<int32> WorkRegions;
if (bRegionProgram)
{
const int32 LastX = OriginVoxels.X + Dimension - 1;
const int32 LastY = OriginVoxels.Y + Dimension - 1;
const int32 LastZ = OriginVoxels.Z + Dimension - 1;
const int32 FirstCellX = FMath::FloorToInt(static_cast<float>(OriginVoxels.X) / 8.0f);
const int32 FirstCellY = FMath::FloorToInt(static_cast<float>(OriginVoxels.Y) / 8.0f);
const int32 FirstCellZ = FMath::FloorToInt(static_cast<float>(OriginVoxels.Z) / 8.0f);
const int32 LastCellX = FMath::FloorToInt(static_cast<float>(LastX) / 8.0f);
const int32 LastCellY = FMath::FloorToInt(static_cast<float>(LastY) / 8.0f);
const int32 LastCellZ = FMath::FloorToInt(static_cast<float>(LastZ) / 8.0f);
const int32 FirstCellX = FMath::FloorToInt(static_cast<float>(OriginVoxels.X) / CHUNK_SIZE);
const int32 FirstCellY = FMath::FloorToInt(static_cast<float>(OriginVoxels.Y) / CHUNK_SIZE);
const int32 FirstCellZ = FMath::FloorToInt(static_cast<float>(OriginVoxels.Z) / CHUNK_SIZE);
const int32 LastCellX = FMath::FloorToInt(static_cast<float>(LastX) / CHUNK_SIZE);
const int32 LastCellY = FMath::FloorToInt(static_cast<float>(LastY) / CHUNK_SIZE);
const int32 LastCellZ = FMath::FloorToInt(static_cast<float>(LastZ) / CHUNK_SIZE);
const int32 CellsX = LastCellX - FirstCellX + 1;
const int32 CellsY = LastCellY - FirstCellY + 1;
const int32 CellsZ = LastCellZ - FirstCellZ + 1;
Bricks.Reserve(CellsX * CellsY * CellsZ);
Regions.Reserve(CellsX * CellsY * CellsZ);
for (int32 CellZ = FirstCellZ; CellZ <= LastCellZ; ++CellZ)
for (int32 CellY = FirstCellY; CellY <= LastCellY; ++CellY)
for (int32 CellX = FirstCellX; CellX <= LastCellX; ++CellX)
{
FRegionBrick& Brick = Bricks.AddDefaulted_GetRef();
const int32 CellMinX = CellX * 8;
const int32 CellMinY = CellY * 8;
const int32 CellMinZ = CellZ * 8;
Brick.Origin = FIntVector(
FRegionChunk& Region = Regions.AddDefaulted_GetRef();
const int32 CellMinX = CellX * CHUNK_SIZE;
const int32 CellMinY = CellY * CHUNK_SIZE;
const int32 CellMinZ = CellZ * CHUNK_SIZE;
Region.Origin = FIntVector(
FMath::Max(CellMinX, OriginVoxels.X),
FMath::Max(CellMinY, OriginVoxels.Y),
FMath::Max(CellMinZ, OriginVoxels.Z));
const int32 EndX = FMath::Min(CellMinX + 8, LastX + 1);
const int32 EndY = FMath::Min(CellMinY + 8, LastY + 1);
const int32 EndZ = FMath::Min(CellMinZ + 8, LastZ + 1);
Brick.SizeX = EndX - Brick.Origin.X;
Brick.SizeY = EndY - Brick.Origin.Y;
Brick.SizeZ = EndZ - Brick.Origin.Z;
Brick.LatticeX = Brick.Origin.X - OriginVoxels.X;
Brick.LatticeY = Brick.Origin.Y - OriginVoxels.Y;
Brick.LatticeZ = Brick.Origin.Z - OriginVoxels.Z;
const int32 EndX = FMath::Min(CellMinX + CHUNK_SIZE, LastX + 1);
const int32 EndY = FMath::Min(CellMinY + CHUNK_SIZE, LastY + 1);
const int32 EndZ = FMath::Min(CellMinZ + CHUNK_SIZE, LastZ + 1);
Region.SizeX = EndX - Region.Origin.X;
Region.SizeY = EndY - Region.Origin.Y;
Region.SizeZ = EndZ - Region.Origin.Z;
Region.LatticeX = Region.Origin.X - OriginVoxels.X;
Region.LatticeY = Region.Origin.Y - OriginVoxels.Y;
Region.LatticeZ = Region.Origin.Z - OriginVoxels.Z;
}
if (bFullRing)
{
WorkBricks.Reserve(Bricks.Num());
for (int32 BrickIndex = 0; BrickIndex < Bricks.Num(); ++BrickIndex)
WorkRegions.Reserve(Regions.Num());
for (int32 RegionIndex = 0; RegionIndex < Regions.Num(); ++RegionIndex)
{
WorkBricks.Add(BrickIndex);
WorkRegions.Add(RegionIndex);
}
}
else
{
WorkBricks.Reserve(Bricks.Num());
WorkRegions.Reserve(Regions.Num());
for (int32 OutputIndex = 0; OutputIndex < SampleIndices->Num(); ++OutputIndex)
{
const int64 LatticeIndex = (*SampleIndices)[OutputIndex];
@@ -233,25 +246,25 @@ namespace VoxelForgeRegionEval
const int32 WorldX = OriginVoxels.X + X;
const int32 WorldY = OriginVoxels.Y + Y;
const int32 WorldZ = OriginVoxels.Z + Z;
const int32 CellX = FMath::FloorToInt(static_cast<float>(WorldX) / 8.0f);
const int32 CellY = FMath::FloorToInt(static_cast<float>(WorldY) / 8.0f);
const int32 CellZ = FMath::FloorToInt(static_cast<float>(WorldZ) / 8.0f);
const int32 BrickIndex = ((CellZ - FirstCellZ) * CellsY
const int32 CellX = FMath::FloorToInt(static_cast<float>(WorldX) / CHUNK_SIZE);
const int32 CellY = FMath::FloorToInt(static_cast<float>(WorldY) / CHUNK_SIZE);
const int32 CellZ = FMath::FloorToInt(static_cast<float>(WorldZ) / CHUNK_SIZE);
const int32 RegionIndex = ((CellZ - FirstCellZ) * CellsY
+ (CellY - FirstCellY)) * CellsX + (CellX - FirstCellX);
FRegionBrick& Brick = Bricks[BrickIndex];
const int32 LocalX = WorldX - Brick.Origin.X;
const int32 LocalY = WorldY - Brick.Origin.Y;
const int32 LocalZ = WorldZ - Brick.Origin.Z;
FRegionBrickSample& BrickSample = Brick.RequestedSamples.AddDefaulted_GetRef();
BrickSample.LocalIndex =
(LocalZ * Brick.SizeY + LocalY) * Brick.SizeX + LocalX;
BrickSample.OutputIndex = OutputIndex;
FRegionChunk& Region = Regions[RegionIndex];
const int32 LocalX = WorldX - Region.Origin.X;
const int32 LocalY = WorldY - Region.Origin.Y;
const int32 LocalZ = WorldZ - Region.Origin.Z;
FRegionChunkSample& ChunkSample = Region.RequestedSamples.AddDefaulted_GetRef();
ChunkSample.LocalIndex =
(LocalZ * Region.SizeY + LocalY) * Region.SizeX + LocalX;
ChunkSample.OutputIndex = OutputIndex;
}
for (int32 BrickIndex = 0; BrickIndex < Bricks.Num(); ++BrickIndex)
for (int32 RegionIndex = 0; RegionIndex < Regions.Num(); ++RegionIndex)
{
if (!Bricks[BrickIndex].RequestedSamples.IsEmpty())
if (!Regions[RegionIndex].RequestedSamples.IsEmpty())
{
WorkBricks.Add(BrickIndex);
WorkRegions.Add(RegionIndex);
}
}
}
@@ -275,6 +288,8 @@ namespace VoxelForgeRegionEval
TArray<uint64> WorkerNonFinite;
TArray<uint64> WorkerEvaluatedSamples;
TArray<uint64> WorkerFallbackSamples;
TArray<uint64> WorkerGeneralPathEntries;
TArray<uint64> WorkerSupportedBlockSetupEntries;
TArray<uint64> WorkerEvaluationFailures;
WorkerSeconds.SetNumZeroed(NumWorkers);
WorkerCpuSeconds.SetNumZeroed(NumWorkers);
@@ -283,6 +298,8 @@ namespace VoxelForgeRegionEval
WorkerNonFinite.SetNumZeroed(NumWorkers);
WorkerEvaluatedSamples.SetNumZeroed(NumWorkers);
WorkerFallbackSamples.SetNumZeroed(NumWorkers);
WorkerGeneralPathEntries.SetNumZeroed(NumWorkers);
WorkerSupportedBlockSetupEntries.SetNumZeroed(NumWorkers);
WorkerEvaluationFailures.SetNumZeroed(NumWorkers);
bool bAllWorkerCpuFromOsThreadTimes = true;
ParallelFor(NumWorkers, [&](int32 WorkerIndex)
@@ -291,51 +308,78 @@ namespace VoxelForgeRegionEval
// inherit its task-level sampler registration. Keep this registration scoped to the
// research worker partition; with sampling disabled it is one relaxed flag load.
FScopedVoxelStackRegistration StackRegistration;
FIntVector PreviousTileOrigin = FIntVector::ZeroValue;
int32 PreviousTileStep = 1;
int32 PreviousTileCells = 0;
VoxelGenLOD::GetThreadTileCacheWindow(
PreviousTileOrigin, PreviousTileStep, PreviousTileCells);
const int32 PreviousOctaveBias = VoxelGenLOD::GetThreadOctaveBias();
VoxelCaveMorphology::SetResearchBrickBinningEnabled(bPrepared);
VoxelCaveMorphology::SetResearchBrickRoomReachScale(ReachScale);
VoxelGenLOD::SetThreadSampleStep(1);
VoxelGenLOD::SetThreadOctaveBias(0);
VoxelGenLOD::SetThreadTileContext(OriginVoxels, Dimension);
VoxelGenLOD::SetResearchRegionCacheWindowEnabled(bRegionProgram && bPrepared);
WorkerThreadIds[WorkerIndex] = FPlatformTLS::GetCurrentThreadId();
const int64 WorkCount = bBrickProgram ? WorkBricks.Num() : SampleCount;
const int64 WorkCount = bRegionProgram ? WorkRegions.Num() : SampleCount;
const int64 Begin = (WorkCount * WorkerIndex) / NumWorkers;
const int64 End = (WorkCount * (WorkerIndex + 1)) / NumWorkers;
const double WorkerStart = FPlatformTime::Seconds();
double WorkerCpuStart = 0.0;
const bool bHaveWorkerCpuStart = GetCurrentThreadCpuSeconds(WorkerCpuStart);
TArray<float> BrickDensities;
TArray<float> RegionDensities;
for (int64 OutputIndex = Begin; OutputIndex < End; ++OutputIndex)
{
if (bBrickProgram)
if (bRegionProgram)
{
const FRegionBrick& Brick = Bricks[WorkBricks[static_cast<int32>(OutputIndex)]];
int32 BrickFallbackSamples = 0;
if (!Generator.EvaluateDensityBrick(
Brick.Origin, Brick.SizeX, Brick.SizeY, Brick.SizeZ,
BrickDensities, BrickFallbackSamples)
|| BrickDensities.Num() != Brick.SizeX * Brick.SizeY * Brick.SizeZ)
const FRegionChunk& Region = Regions[WorkRegions[static_cast<int32>(OutputIndex)]];
if (bPrepared)
{
// Build/cache one bounded graph window for the current CHUNK_SIZE region.
// A ring-wide context made each worker retain a duplicate ring-sized graph
// even though its density block only consumes this one chunk.
const FIntVector CacheWindowOrigin(
FMath::FloorToInt(static_cast<float>(Region.Origin.X) / CHUNK_SIZE) * CHUNK_SIZE,
FMath::FloorToInt(static_cast<float>(Region.Origin.Y) / CHUNK_SIZE) * CHUNK_SIZE,
FMath::FloorToInt(static_cast<float>(Region.Origin.Z) / CHUNK_SIZE) * CHUNK_SIZE);
VoxelGenLOD::SetThreadTileContext(
CacheWindowOrigin, CHUNK_SIZE);
}
int32 RegionFallbackSamples = 0;
uint64 RegionGeneralPathEntries = 0;
uint64 RegionSupportedSetupEntries = 0;
if (!Generator.EvaluateDensityChunk(
Region.Origin, Region.SizeX, Region.SizeY, Region.SizeZ,
RegionDensities, RegionFallbackSamples,
RegionGeneralPathEntries, RegionSupportedSetupEntries)
|| RegionDensities.Num() != Region.SizeX * Region.SizeY * Region.SizeZ)
{
++WorkerEvaluationFailures[WorkerIndex];
continue;
}
const int32 BrickSampleCount = BrickDensities.Num();
WorkerEvaluatedSamples[WorkerIndex] += static_cast<uint64>(BrickSampleCount);
WorkerFallbackSamples[WorkerIndex] += static_cast<uint64>(BrickFallbackSamples);
const int32 RegionSampleCount = RegionDensities.Num();
WorkerEvaluatedSamples[WorkerIndex] += static_cast<uint64>(RegionSampleCount);
WorkerFallbackSamples[WorkerIndex] += static_cast<uint64>(RegionFallbackSamples);
WorkerGeneralPathEntries[WorkerIndex] += RegionGeneralPathEntries;
WorkerSupportedBlockSetupEntries[WorkerIndex] += RegionSupportedSetupEntries;
if (bFullRing && OutDensities != nullptr)
{
for (int32 Bz = 0; Bz < Brick.SizeZ; ++Bz)
for (int32 By = 0; By < Brick.SizeY; ++By)
for (int32 Bx = 0; Bx < Brick.SizeX; ++Bx)
for (int32 Bz = 0; Bz < Region.SizeZ; ++Bz)
for (int32 By = 0; By < Region.SizeY; ++By)
for (int32 Bx = 0; Bx < Region.SizeX; ++Bx)
{
const int32 LocalIndex = (Bz * Brick.SizeY + By) * Brick.SizeX + Bx;
const int32 LocalIndex = (Bz * Region.SizeY + By) * Region.SizeX + Bx;
const int64 LatticeIndex = LinearIndex(
Brick.LatticeX + Bx, Brick.LatticeY + By,
Brick.LatticeZ + Bz, Dimension);
(*OutDensities)[static_cast<int32>(LatticeIndex)] = BrickDensities[LocalIndex];
Region.LatticeX + Bx, Region.LatticeY + By,
Region.LatticeZ + Bz, Dimension);
(*OutDensities)[static_cast<int32>(LatticeIndex)] = RegionDensities[LocalIndex];
}
}
else if (OutDensities != nullptr)
{
for (const FRegionBrickSample& Sample : Brick.RequestedSamples)
for (const FRegionChunkSample& Sample : Region.RequestedSamples)
{
(*OutDensities)[Sample.OutputIndex] = BrickDensities[Sample.LocalIndex];
(*OutDensities)[Sample.OutputIndex] = RegionDensities[Sample.LocalIndex];
}
}
continue;
@@ -355,6 +399,8 @@ namespace VoxelForgeRegionEval
static_cast<float>(OriginX + X),
static_cast<float>(OriginY + Y),
static_cast<float>(OriginZ + Z));
++WorkerEvaluatedSamples[WorkerIndex];
++WorkerGeneralPathEntries[WorkerIndex];
if (OutDensities != nullptr)
{
(*OutDensities)[static_cast<int32>(OutputIndex)] = Density;
@@ -373,20 +419,34 @@ namespace VoxelForgeRegionEval
}
VoxelCaveMorphology::SetResearchBrickBinningEnabled(false);
VoxelCaveMorphology::SetResearchBrickRoomReachScale(1.0f);
VoxelGenLOD::SetResearchRegionCacheWindowEnabled(false);
VoxelGenLOD::SetThreadTileContext(PreviousTileOrigin, PreviousTileCells);
VoxelGenLOD::SetThreadSampleStep(PreviousTileStep);
VoxelGenLOD::SetThreadOctaveBias(PreviousOctaveBias);
}, EParallelForFlags::None);
VoxelDensityProfile::FSnapshot ProfileSnapshot;
if (bCollectWholeRunCounters)
{
ProfileSnapshot = VoxelDensityProfile::Snapshot();
VoxelDensityProfile::SetMode(PreviousProfileMode, PreviousProfileInterval);
VoxelDensityProfile::Reset();
}
OutTiming.WallSeconds = FPlatformTime::Seconds() - StartSeconds;
OutTiming.SampleCount = SampleCount;
OutTiming.bBrickProgram = bBrickProgram;
OutTiming.BrickCount = bBrickProgram ? WorkBricks.Num() : 0;
OutTiming.bRegionProgram = bRegionProgram;
OutTiming.bHasWholeRunCounters = bCollectWholeRunCounters;
OutTiming.RegionCount = bRegionProgram ? WorkRegions.Num() : 0;
for (int32 WorkerIndex = 0; WorkerIndex < NumWorkers; ++WorkerIndex)
{
OutTiming.WorkerCpuSeconds += WorkerCpuSeconds[WorkerIndex];
bAllWorkerCpuFromOsThreadTimes &= WorkerCpuFromOsThreadTimes[WorkerIndex] != 0;
OutTiming.NonFiniteSamples += WorkerNonFinite[WorkerIndex];
OutTiming.EvaluatedSamples += bBrickProgram
? static_cast<int64>(WorkerEvaluatedSamples[WorkerIndex]) : 0;
OutTiming.EvaluatedSamples += static_cast<int64>(WorkerEvaluatedSamples[WorkerIndex]);
OutTiming.FallbackSamples += static_cast<int64>(WorkerFallbackSamples[WorkerIndex]);
OutTiming.GeneralPathEntries += WorkerGeneralPathEntries[WorkerIndex];
OutTiming.SupportedBlockSetupEntries += WorkerSupportedBlockSetupEntries[WorkerIndex];
if (WorkerEvaluationFailures[WorkerIndex] != 0)
{
return false;
@@ -403,10 +463,75 @@ namespace VoxelForgeRegionEval
{
OutTiming.FallbackShare = static_cast<double>(OutTiming.FallbackSamples)
/ static_cast<double>(OutTiming.EvaluatedSamples);
OutTiming.GeneralPathEntriesPerSample = static_cast<double>(OutTiming.GeneralPathEntries)
/ static_cast<double>(OutTiming.EvaluatedSamples);
}
OutTiming.NanosecondsPerSample = OutTiming.SampleCount > 0
? OutTiming.WorkerCpuSeconds * 1.0e9 / static_cast<double>(OutTiming.SampleCount)
: 0.0;
if (bCollectWholeRunCounters)
{
using VoxelDensityProfile::ECounter;
using VoxelDensityProfile::EBucket;
const auto Counter = [&ProfileSnapshot](ECounter Id)
{
return ProfileSnapshot.Counters[static_cast<int32>(Id)];
};
OutTiming.SdfCacheBuilds = Counter(ECounter::SdfCacheBuild);
OutTiming.ActiveOpListBuilds = Counter(ECounter::OpBlockBuilds);
OutTiming.SdfSpatialIndexBuilds = Counter(ECounter::SdfSpatialIndexBuild);
OutTiming.SdfBrickIndexBuilds = Counter(ECounter::SdfBrickIndexBuild);
OutTiming.BroadPhaseIndexBuilds = OutTiming.SdfSpatialIndexBuilds
+ OutTiming.SdfBrickIndexBuilds;
OutTiming.RoomCandidateVisits = Counter(ECounter::CaveRoomCandidates);
OutTiming.RoomEvaluations = Counter(ECounter::CaveRoomEvaluated);
OutTiming.TunnelCandidateVisits = Counter(ECounter::CaveTunnelCandidates);
OutTiming.TunnelEvaluations = Counter(ECounter::CaveTunnelEvaluated);
OutTiming.TunnelCoreCandidateVisits = Counter(ECounter::TunnelCoreCandidates);
OutTiming.TunnelCoreEvaluations = Counter(ECounter::TunnelCoreEvaluated);
OutTiming.PassageCandidateVisits = Counter(ECounter::PassageCandidates);
OutTiming.PassageEvaluations = Counter(ECounter::PassageEvaluated);
OutTiming.TotalCandidateVisits = OutTiming.RoomCandidateVisits
+ OutTiming.TunnelCandidateVisits + OutTiming.TunnelCoreCandidateVisits
+ OutTiming.PassageCandidateVisits;
OutTiming.TotalCandidateEvaluations = OutTiming.RoomEvaluations
+ OutTiming.TunnelEvaluations + OutTiming.TunnelCoreEvaluations
+ OutTiming.PassageEvaluations;
if (OutTiming.SampleCount > 0)
{
OutTiming.CandidateVisitsPerSample = static_cast<double>(OutTiming.TotalCandidateVisits)
/ static_cast<double>(OutTiming.SampleCount);
OutTiming.CandidateEvaluationsPerSample = static_cast<double>(OutTiming.TotalCandidateEvaluations)
/ static_cast<double>(OutTiming.SampleCount);
}
const auto AttributedNsPerSample = [&ProfileSnapshot, SampleCount](EBucket Bucket)
{
return SampleCount > 0
? FPlatformTime::ToSeconds64(ProfileSnapshot.Cycles[static_cast<int32>(Bucket)])
* 1.0e9 / static_cast<double>(SampleCount)
: 0.0;
};
OutTiming.GetDensityAtNsPerSample = AttributedNsPerSample(EBucket::GetDensityAt);
OutTiming.OperatorBlockNsPerSample = AttributedNsPerSample(EBucket::OperatorBlock);
OutTiming.RoomGraphSourceNsPerSample = AttributedNsPerSample(EBucket::RoomGraphSource);
OutTiming.RoomGraphBuildNsPerSample = AttributedNsPerSample(EBucket::RoomGraphBuild);
OutTiming.RoomGraphSdfNsPerSample = AttributedNsPerSample(EBucket::RoomGraphSdf);
OutTiming.FusedEvaluatorNsPerSample = AttributedNsPerSample(EBucket::FusedEvaluator);
OutTiming.TunnelCoreSdfNsPerSample = AttributedNsPerSample(EBucket::TunnelCoreSdf);
OutTiming.TunnelCoreWorldNsPerSample = AttributedNsPerSample(EBucket::TunnelCoreWorld);
OutTiming.TunnelCorePostsNsPerSample = AttributedNsPerSample(EBucket::TunnelCorePosts);
OutTiming.PassageModifierNsPerSample = AttributedNsPerSample(EBucket::PassageModifier);
OutTiming.PassageStructuralPostsNsPerSample = AttributedNsPerSample(EBucket::PassageStructuralPosts);
OutTiming.PassageFloorNsPerSample = AttributedNsPerSample(EBucket::PassageLandingFloor);
OutTiming.StructuralPostsNsPerSample = AttributedNsPerSample(EBucket::DensityStructuralPosts);
OutTiming.DensityPrologueNsPerSample = AttributedNsPerSample(EBucket::DensityPrologue);
OutTiming.DensityCoreNsPerSample = AttributedNsPerSample(EBucket::DensityCore);
OutTiming.DisturbancesNsPerSample = AttributedNsPerSample(EBucket::DensityDisturbances);
OutTiming.DensityBoundarySealNsPerSample = AttributedNsPerSample(EBucket::DensityBoundarySeal);
OutTiming.DiffLayerNsPerSample = AttributedNsPerSample(EBucket::DensityDiffLayer);
OutTiming.DensityTailNsPerSample = AttributedNsPerSample(EBucket::DensityTail);
}
if (OutDensities != nullptr)
{
for (const float Density : *OutDensities)
@@ -452,16 +577,24 @@ namespace VoxelForgeRegionEval
}
using VoxelDensityProfile::ECounter;
Result.Room = Snapshot.Counters[static_cast<int32>(ECounter::CaveRoomCandidates)];
Result.RoomEvaluated = Snapshot.Counters[static_cast<int32>(ECounter::CaveRoomEvaluated)];
Result.Tunnel = Snapshot.Counters[static_cast<int32>(ECounter::CaveTunnelCandidates)];
Result.TunnelEvaluated = Snapshot.Counters[static_cast<int32>(ECounter::CaveTunnelEvaluated)];
Result.TunnelCore = Snapshot.Counters[static_cast<int32>(ECounter::TunnelCoreCandidates)];
Result.TunnelCoreEvaluated = Snapshot.Counters[static_cast<int32>(ECounter::TunnelCoreEvaluated)];
Result.Passage = Snapshot.Counters[static_cast<int32>(ECounter::PassageCandidates)];
Result.PassageEvaluated = Snapshot.Counters[static_cast<int32>(ECounter::PassageEvaluated)];
Result.TunnelCacheLookups = Snapshot.Counters[static_cast<int32>(ECounter::TunnelCacheLookup)];
Result.TunnelCacheHits = Snapshot.Counters[static_cast<int32>(ECounter::TunnelCacheHit)];
Result.TunnelCacheMisses = Snapshot.Counters[static_cast<int32>(ECounter::TunnelCacheMiss)];
Result.SdfCacheBuilds = Snapshot.Counters[static_cast<int32>(ECounter::SdfCacheBuild)];
Result.Total = Result.Room + Result.Tunnel + Result.TunnelCore + Result.Passage;
Result.TotalEvaluated = Result.RoomEvaluated + Result.TunnelEvaluated
+ Result.TunnelCoreEvaluated + Result.PassageEvaluated;
Result.PerSample = Result.SampleCount > 0
? static_cast<double>(Result.Total) / Result.SampleCount : 0.0;
Result.EvaluatedPerSample = Result.SampleCount > 0
? static_cast<double>(Result.TotalEvaluated) / Result.SampleCount : 0.0;
Result.AttributionSampleInterval = Snapshot.SampleInterval;
const auto AttributedNanosecondsPerSample = [&](VoxelDensityProfile::EBucket Bucket)
{