Editor-only, default off (voxel.ResearchRedefinedDensityField). The redefined field is a seeded, periodic cross-corridor pattern per strate plus the authored passages, landings and a shaft tree, with four value-noise detail bands at every near-surface 25 cm sample and sound 8^3 brick proofs. 259^3 ring, six workers: 308 ms median (467 ms without skips) vs 8.9 s for the live field. Detail, boundary-artefact, determinism (1 vs 6 workers, two runs) and bound-audit checks pass. Why it is not a pass: the slices show a mostly-solid field with a few straight corridors, where the owner's world is mostly open space; rooms, the tunnel network, composer recipes and archetypes are gone. Much of the speed comes from 60% of bricks being proven solid in that simpler world. What it does show: detail at full resolution plus brick skipping costs ~80-130 ns/sample; the cost of the live field is how rooms/tunnels/passages are represented, not the detail or the lattice. The two walkability tests were redirected to the research field in the working tree; that change is NOT committed (saved as scratchpad step2d-test-redirect.patch) so they keep testing the live world. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
875 lines
44 KiB
C++
875 lines
44 KiB
C++
#include "VoxelForgeRegionEvalExperiment.h"
|
|
|
|
#include "Async/ParallelFor.h"
|
|
#include "HAL/PlatformTime.h"
|
|
#include "VoxelCaveMorphology.h"
|
|
#include "VoxelDensityProfile.h"
|
|
#include "VoxelGenerator.h"
|
|
#include "VoxelRedefinedDensityField.h"
|
|
#include "VoxelStackSampler.h"
|
|
#include "HAL/PlatformTLS.h"
|
|
|
|
#include <atomic>
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
THIRD_PARTY_INCLUDES_START
|
|
#include <Windows.h>
|
|
THIRD_PARTY_INCLUDES_END
|
|
#include "Windows/HideWindowsPlatformTypes.h"
|
|
#endif
|
|
|
|
namespace VoxelForgeRegionEval
|
|
{
|
|
namespace
|
|
{
|
|
constexpr double WindowsFileTimeSeconds = 1.0e-7;
|
|
|
|
uint64 Fnv1aByte(uint64 Hash, uint8 Byte)
|
|
{
|
|
Hash ^= Byte;
|
|
return Hash * 1099511628211ull;
|
|
}
|
|
|
|
uint64 HashRawFloats(const TArray<float>& Densities)
|
|
{
|
|
uint64 Hash = 14695981039346656037ull;
|
|
for (const float Density : Densities)
|
|
{
|
|
uint32 Bits = 0;
|
|
FMemory::Memcpy(&Bits, &Density, sizeof(Bits));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>(Bits & 0xFFu));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>((Bits >> 8) & 0xFFu));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>((Bits >> 16) & 0xFFu));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>((Bits >> 24) & 0xFFu));
|
|
}
|
|
return Hash;
|
|
}
|
|
|
|
bool GetCurrentThreadCpuSeconds(double& OutSeconds)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
FILETIME CreationTime = {};
|
|
FILETIME ExitTime = {};
|
|
FILETIME KernelTime = {};
|
|
FILETIME UserTime = {};
|
|
if (GetThreadTimes(
|
|
GetCurrentThread(), &CreationTime, &ExitTime, &KernelTime, &UserTime) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
ULARGE_INTEGER KernelTicks = {};
|
|
ULARGE_INTEGER UserTicks = {};
|
|
KernelTicks.LowPart = KernelTime.dwLowDateTime;
|
|
KernelTicks.HighPart = KernelTime.dwHighDateTime;
|
|
UserTicks.LowPart = UserTime.dwLowDateTime;
|
|
UserTicks.HighPart = UserTime.dwHighDateTime;
|
|
OutSeconds = static_cast<double>(KernelTicks.QuadPart + UserTicks.QuadPart)
|
|
* WindowsFileTimeSeconds;
|
|
return true;
|
|
#else
|
|
(void)OutSeconds;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
int64 LatticeCount(int32 Dimension)
|
|
{
|
|
return static_cast<int64>(Dimension) * Dimension * Dimension;
|
|
}
|
|
|
|
struct FRegionChunkSample
|
|
{
|
|
int32 LocalIndex = 0;
|
|
int32 OutputIndex = 0;
|
|
};
|
|
|
|
struct FRegionChunk
|
|
{
|
|
FIntVector Origin = FIntVector::ZeroValue;
|
|
int32 SizeX = 0;
|
|
int32 SizeY = 0;
|
|
int32 SizeZ = 0;
|
|
int32 LatticeX = 0;
|
|
int32 LatticeY = 0;
|
|
int32 LatticeZ = 0;
|
|
TArray<FRegionChunkSample> RequestedSamples;
|
|
};
|
|
|
|
struct FResearchBrick
|
|
{
|
|
FIntVector Origin = FIntVector::ZeroValue;
|
|
FIntVector Size = FIntVector::ZeroValue;
|
|
FIntVector Lattice = FIntVector::ZeroValue;
|
|
int32 SampleCount = 0;
|
|
};
|
|
}
|
|
|
|
int64 LinearIndex(int32 X, int32 Y, int32 Z, int32 Dimension)
|
|
{
|
|
return (static_cast<int64>(Z) * Dimension + Y) * Dimension + X;
|
|
}
|
|
|
|
int16 QuantizeDensity(float Density, bool& bOutSaturated, bool& bOutFinite)
|
|
{
|
|
bOutFinite = FMath::IsFinite(Density);
|
|
bOutSaturated = false;
|
|
if (!bOutFinite)
|
|
{
|
|
return 0;
|
|
}
|
|
const double Scaled = static_cast<double>(Density) * QuantizationScale;
|
|
const double Rounded = Scaled >= 0.0
|
|
? FMath::FloorToDouble(Scaled + 0.5)
|
|
: FMath::CeilToDouble(Scaled - 0.5);
|
|
if (Rounded > static_cast<double>(MAX_int16))
|
|
{
|
|
bOutSaturated = true;
|
|
return MAX_int16;
|
|
}
|
|
if (Rounded < static_cast<double>(MIN_int16))
|
|
{
|
|
bOutSaturated = true;
|
|
return MIN_int16;
|
|
}
|
|
return static_cast<int16>(Rounded);
|
|
}
|
|
|
|
uint64 HashQuantized(const TArray<float>& Densities)
|
|
{
|
|
uint64 Hash = 14695981039346656037ull;
|
|
for (const float Density : Densities)
|
|
{
|
|
bool bSaturated = false;
|
|
bool bFinite = false;
|
|
const uint16 Q = static_cast<uint16>(QuantizeDensity(Density, bSaturated, bFinite));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>(Q & 0xFFu));
|
|
Hash = Fnv1aByte(Hash, static_cast<uint8>(Q >> 8));
|
|
}
|
|
return Hash;
|
|
}
|
|
|
|
bool EvaluateRedefined(
|
|
const UVoxelGenerator& Generator,
|
|
FIntVector OriginVoxels,
|
|
int32 Dimension,
|
|
int32 RequestedWorkerCount,
|
|
float DetailScale,
|
|
TArray<float>& OutDensities,
|
|
FTiming& OutTiming,
|
|
bool bEnableBrickSkips)
|
|
{
|
|
OutTiming = FTiming();
|
|
OutDensities.Reset();
|
|
if (Dimension <= 0 || Dimension > 1024 || !FMath::IsFinite(DetailScale))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int32 NumWorkers = FMath::Clamp(RequestedWorkerCount, 1, 32);
|
|
const int64 TotalSamples = LatticeCount(Dimension);
|
|
if (TotalSamples > MAX_int32)
|
|
{
|
|
return false;
|
|
}
|
|
const double StartSeconds = FPlatformTime::Seconds();
|
|
OutTiming.Workers = NumWorkers;
|
|
OutTiming.SampleCount = TotalSamples;
|
|
OutDensities.SetNumUninitialized(static_cast<int32>(TotalSamples));
|
|
|
|
TArray<FResearchBrick> Bricks;
|
|
const FIntVector Last = OriginVoxels
|
|
+ FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
|
|
const FIntVector FirstBrick(
|
|
FMath::FloorToInt(static_cast<float>(OriginVoxels.X) / 8.0f),
|
|
FMath::FloorToInt(static_cast<float>(OriginVoxels.Y) / 8.0f),
|
|
FMath::FloorToInt(static_cast<float>(OriginVoxels.Z) / 8.0f));
|
|
const FIntVector LastBrick(
|
|
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
|
|
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
|
|
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
|
|
Bricks.Reserve((LastBrick.X - FirstBrick.X + 1)
|
|
* (LastBrick.Y - FirstBrick.Y + 1) * (LastBrick.Z - FirstBrick.Z + 1));
|
|
for (int32 BZ = FirstBrick.Z; BZ <= LastBrick.Z; ++BZ)
|
|
for (int32 BY = FirstBrick.Y; BY <= LastBrick.Y; ++BY)
|
|
for (int32 BX = FirstBrick.X; BX <= LastBrick.X; ++BX)
|
|
{
|
|
const FIntVector BrickWorldMin(BX * 8, BY * 8, BZ * 8);
|
|
FResearchBrick& Brick = Bricks.AddDefaulted_GetRef();
|
|
Brick.Origin = FIntVector(
|
|
FMath::Max(OriginVoxels.X, BrickWorldMin.X),
|
|
FMath::Max(OriginVoxels.Y, BrickWorldMin.Y),
|
|
FMath::Max(OriginVoxels.Z, BrickWorldMin.Z));
|
|
const FIntVector BrickEnd = BrickWorldMin + FIntVector(8, 8, 8);
|
|
const FIntVector RingEnd = OriginVoxels
|
|
+ FIntVector(Dimension, Dimension, Dimension);
|
|
const FIntVector End(
|
|
FMath::Min(BrickEnd.X, RingEnd.X),
|
|
FMath::Min(BrickEnd.Y, RingEnd.Y),
|
|
FMath::Min(BrickEnd.Z, RingEnd.Z));
|
|
Brick.Size = End - Brick.Origin;
|
|
Brick.Lattice = Brick.Origin - OriginVoxels;
|
|
Brick.SampleCount = Brick.Size.X * Brick.Size.Y * Brick.Size.Z;
|
|
}
|
|
|
|
TArray<double> WorkerWall;
|
|
TArray<double> WorkerCpu;
|
|
TArray<double> WorkerClassify;
|
|
TArray<double> WorkerEvaluate;
|
|
TArray<double> WorkerFill;
|
|
TArray<uint32> WorkerThreadIds;
|
|
TArray<uint64> WorkerEvaluated;
|
|
TArray<uint64> WorkerSkippedBricks;
|
|
TArray<uint64> WorkerSkippedSamples;
|
|
TArray<uint64> WorkerMacroChecks;
|
|
TArray<uint64> WorkerMacroCentreEvaluations;
|
|
TArray<uint64> WorkerFallbackReasons;
|
|
WorkerWall.SetNumZeroed(NumWorkers);
|
|
WorkerCpu.SetNumZeroed(NumWorkers);
|
|
WorkerClassify.SetNumZeroed(NumWorkers);
|
|
WorkerEvaluate.SetNumZeroed(NumWorkers);
|
|
WorkerFill.SetNumZeroed(NumWorkers);
|
|
WorkerThreadIds.SetNumZeroed(NumWorkers);
|
|
WorkerEvaluated.SetNumZeroed(NumWorkers);
|
|
WorkerSkippedBricks.SetNumZeroed(NumWorkers);
|
|
WorkerSkippedSamples.SetNumZeroed(NumWorkers);
|
|
WorkerMacroChecks.SetNumZeroed(NumWorkers);
|
|
WorkerMacroCentreEvaluations.SetNumZeroed(NumWorkers);
|
|
WorkerFallbackReasons.SetNumZeroed(NumWorkers * 5);
|
|
|
|
// Surface bricks carry more work than uniform interior bricks. Equal brick counts do
|
|
// not balance that work, so claim small contiguous batches dynamically. Each brick
|
|
// writes a disjoint output range and still evaluates the same exact field.
|
|
std::atomic<int32> NextBrickIndex{0};
|
|
constexpr int32 BrickBatchSize = 16;
|
|
ParallelFor(NumWorkers, [&](int32 WorkerIndex)
|
|
{
|
|
WorkerThreadIds[WorkerIndex] = FPlatformTLS::GetCurrentThreadId();
|
|
const double WorkerStart = FPlatformTime::Seconds();
|
|
double CpuStart = 0.0;
|
|
const bool bHaveCpuStart = GetCurrentThreadCpuSeconds(CpuStart);
|
|
TArray<float> BrickDetail;
|
|
VoxelForgeRedefinedDensityField::FBrickEvaluationContext BrickContext;
|
|
for (;;)
|
|
{
|
|
const int32 BatchBegin = NextBrickIndex.fetch_add(
|
|
BrickBatchSize, std::memory_order_relaxed);
|
|
if (BatchBegin >= Bricks.Num())
|
|
{
|
|
break;
|
|
}
|
|
const int32 BatchEnd = FMath::Min(BatchBegin + BrickBatchSize, Bricks.Num());
|
|
for (int32 BrickIndex = BatchBegin; BrickIndex < BatchEnd; ++BrickIndex)
|
|
{
|
|
const FResearchBrick& Brick = Bricks[BrickIndex];
|
|
bool bUniformAir = false;
|
|
bool bEvaluatedMacroCentre = false;
|
|
VoxelForgeRedefinedDensityField::EBrickFallbackReason FallbackReason =
|
|
VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState;
|
|
++WorkerMacroChecks[WorkerIndex];
|
|
const double ClassifyStart = FPlatformTime::Seconds();
|
|
const bool bCanSkip = bEnableBrickSkips
|
|
&& VoxelForgeRedefinedDensityField::TryClassifyBrick(
|
|
Generator, Brick.Origin, Brick.Size, bUniformAir, DetailScale,
|
|
&bEvaluatedMacroCentre, &FallbackReason);
|
|
WorkerClassify[WorkerIndex] += FPlatformTime::Seconds() - ClassifyStart;
|
|
WorkerMacroCentreEvaluations[WorkerIndex] += bEvaluatedMacroCentre ? 1u : 0u;
|
|
if (bEnableBrickSkips && !bCanSkip)
|
|
{
|
|
const int32 ReasonIndex = static_cast<uint8>(FallbackReason);
|
|
if (ReasonIndex < 5)
|
|
{
|
|
++WorkerFallbackReasons[WorkerIndex * 5 + ReasonIndex];
|
|
}
|
|
}
|
|
if (bCanSkip)
|
|
{
|
|
++WorkerSkippedBricks[WorkerIndex];
|
|
WorkerSkippedSamples[WorkerIndex] += static_cast<uint64>(Brick.SampleCount);
|
|
const float UniformDensity = bUniformAir ? 64.0f : -64.0f;
|
|
const double FillStart = FPlatformTime::Seconds();
|
|
for (int32 Z = 0; Z < Brick.Size.Z; ++Z)
|
|
for (int32 Y = 0; Y < Brick.Size.Y; ++Y)
|
|
for (int32 X = 0; X < Brick.Size.X; ++X)
|
|
{
|
|
const int64 LatticeIndex = LinearIndex(
|
|
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
|
|
Brick.Lattice.Z + Z, Dimension);
|
|
OutDensities[static_cast<int32>(LatticeIndex)] = UniformDensity;
|
|
}
|
|
WorkerFill[WorkerIndex] += FPlatformTime::Seconds() - FillStart;
|
|
continue;
|
|
}
|
|
|
|
const double EvaluationStart = FPlatformTime::Seconds();
|
|
VoxelForgeRedefinedDensityField::BuildDetailBrick(
|
|
Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail,
|
|
&BrickContext);
|
|
for (int32 Z = 0; Z < Brick.Size.Z; ++Z)
|
|
for (int32 Y = 0; Y < Brick.Size.Y; ++Y)
|
|
for (int32 X = 0; X < Brick.Size.X; ++X)
|
|
{
|
|
const int32 WorldX = Brick.Origin.X + X;
|
|
const int32 WorldY = Brick.Origin.Y + Y;
|
|
const int32 WorldZ = Brick.Origin.Z + Z;
|
|
const int64 LatticeIndex = LinearIndex(
|
|
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
|
|
Brick.Lattice.Z + Z, Dimension);
|
|
OutDensities[static_cast<int32>(LatticeIndex)] =
|
|
VoxelForgeRedefinedDensityField::EvaluateWithDetailValue(
|
|
Generator, static_cast<float>(WorldX), static_cast<float>(WorldY),
|
|
static_cast<float>(WorldZ),
|
|
BrickDetail[(Z * Brick.Size.Y + Y) * Brick.Size.X + X],
|
|
nullptr, DetailScale, &BrickContext);
|
|
++WorkerEvaluated[WorkerIndex];
|
|
}
|
|
WorkerEvaluate[WorkerIndex] += FPlatformTime::Seconds() - EvaluationStart;
|
|
}
|
|
}
|
|
WorkerWall[WorkerIndex] = FPlatformTime::Seconds() - WorkerStart;
|
|
double CpuEnd = 0.0;
|
|
if (bHaveCpuStart && GetCurrentThreadCpuSeconds(CpuEnd))
|
|
{
|
|
WorkerCpu[WorkerIndex] = FMath::Max(0.0, CpuEnd - CpuStart);
|
|
}
|
|
else
|
|
{
|
|
WorkerCpu[WorkerIndex] = WorkerWall[WorkerIndex];
|
|
}
|
|
}, EParallelForFlags::None);
|
|
|
|
OutTiming.WallSeconds = FPlatformTime::Seconds() - StartSeconds;
|
|
OutTiming.bRegionProgram = true;
|
|
OutTiming.BrickCount = static_cast<uint64>(Bricks.Num());
|
|
OutTiming.ObservedWorkerThreads = 0;
|
|
for (int32 Worker = 0; Worker < NumWorkers; ++Worker)
|
|
{
|
|
OutTiming.WorkerCpuSeconds += WorkerCpu[Worker];
|
|
OutTiming.BrickClassificationSeconds += WorkerClassify[Worker];
|
|
OutTiming.DensityEvaluationSeconds += WorkerEvaluate[Worker];
|
|
OutTiming.UniformFillSeconds += WorkerFill[Worker];
|
|
OutTiming.LongestWorkerWallSeconds = FMath::Max(
|
|
OutTiming.LongestWorkerWallSeconds, WorkerWall[Worker]);
|
|
OutTiming.EvaluatedSamples += static_cast<int64>(WorkerEvaluated[Worker]);
|
|
OutTiming.SkippedBricks += WorkerSkippedBricks[Worker];
|
|
OutTiming.SkippedSamples += WorkerSkippedSamples[Worker];
|
|
OutTiming.MacroBrickChecks += WorkerMacroChecks[Worker];
|
|
OutTiming.MacroCentreEvaluations += WorkerMacroCentreEvaluations[Worker];
|
|
OutTiming.UnsupportedBrickProofs += WorkerFallbackReasons[Worker * 5
|
|
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState)];
|
|
OutTiming.ChunkBoundaryBrickProofs += WorkerFallbackReasons[Worker * 5
|
|
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::ChunkOrStrateBoundary)];
|
|
OutTiming.PassageBoundBrickProofs += WorkerFallbackReasons[Worker * 5
|
|
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::PassageBounds)];
|
|
OutTiming.MixedMacroBrickProofs += WorkerFallbackReasons[Worker * 5
|
|
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::MixedMacroInterval)];
|
|
bool bPreviouslyObserved = false;
|
|
for (int32 Previous = 0; Previous < Worker; ++Previous)
|
|
{
|
|
bPreviouslyObserved |= WorkerThreadIds[Previous] == WorkerThreadIds[Worker];
|
|
}
|
|
OutTiming.ObservedWorkerThreads += bPreviouslyObserved ? 0 : 1;
|
|
}
|
|
OutTiming.NanosecondsPerSample = TotalSamples > 0
|
|
? OutTiming.WorkerCpuSeconds * 1.0e9 / static_cast<double>(TotalSamples)
|
|
: 0.0;
|
|
OutTiming.CandidateEvaluationsPerSample = TotalSamples > 0
|
|
? static_cast<double>(OutTiming.EvaluatedSamples
|
|
+ static_cast<int64>(OutTiming.MacroCentreEvaluations))
|
|
/ static_cast<double>(TotalSamples)
|
|
: 0.0;
|
|
for (const float Density : OutDensities)
|
|
{
|
|
OutTiming.NonFiniteSamples += FMath::IsFinite(Density) ? 0u : 1u;
|
|
}
|
|
OutTiming.QuantizedHash = HashQuantized(OutDensities);
|
|
OutTiming.RawFloatHash = HashRawFloats(OutDensities);
|
|
return OutTiming.EvaluatedSamples + static_cast<int64>(OutTiming.SkippedSamples)
|
|
== TotalSamples && OutTiming.NonFiniteSamples == 0;
|
|
}
|
|
|
|
bool Evaluate(
|
|
const UVoxelGenerator& Generator,
|
|
FIntVector OriginVoxels,
|
|
int32 Dimension,
|
|
const TArray<int64>* SampleIndices,
|
|
bool bFullRing,
|
|
bool bPrepared,
|
|
float ReachScale,
|
|
TArray<float>* OutDensities,
|
|
FTiming& OutTiming,
|
|
bool bRegionProgram,
|
|
int32 RequestedWorkerCount,
|
|
bool bRequestWholeRunCounters)
|
|
{
|
|
OutTiming = FTiming();
|
|
const int32 NumWorkers = FMath::Clamp(RequestedWorkerCount, 1, 32);
|
|
OutTiming.Workers = NumWorkers;
|
|
if (Dimension <= 0 || Dimension > 4096
|
|
|| (bFullRing && SampleIndices != nullptr)
|
|
|| (!bFullRing && SampleIndices == nullptr))
|
|
{
|
|
return false;
|
|
}
|
|
const int64 TotalLatticeSamples = LatticeCount(Dimension);
|
|
const int64 SampleCount = bFullRing
|
|
? TotalLatticeSamples : static_cast<int64>(SampleIndices->Num());
|
|
if (SampleCount <= 0 || SampleCount > TotalLatticeSamples)
|
|
{
|
|
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<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) / 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;
|
|
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)
|
|
{
|
|
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 + 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)
|
|
{
|
|
WorkRegions.Reserve(Regions.Num());
|
|
for (int32 RegionIndex = 0; RegionIndex < Regions.Num(); ++RegionIndex)
|
|
{
|
|
WorkRegions.Add(RegionIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WorkRegions.Reserve(Regions.Num());
|
|
for (int32 OutputIndex = 0; OutputIndex < SampleIndices->Num(); ++OutputIndex)
|
|
{
|
|
const int64 LatticeIndex = (*SampleIndices)[OutputIndex];
|
|
if (LatticeIndex < 0 || LatticeIndex >= TotalLatticeSamples)
|
|
{
|
|
return false;
|
|
}
|
|
const int32 X = static_cast<int32>(LatticeIndex % Dimension);
|
|
const int32 Y = static_cast<int32>((LatticeIndex / Dimension) % Dimension);
|
|
const int32 Z = static_cast<int32>(LatticeIndex
|
|
/ (static_cast<int64>(Dimension) * Dimension));
|
|
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) / 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);
|
|
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 RegionIndex = 0; RegionIndex < Regions.Num(); ++RegionIndex)
|
|
{
|
|
if (!Regions[RegionIndex].RequestedSamples.IsEmpty())
|
|
{
|
|
WorkRegions.Add(RegionIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (OutDensities != nullptr)
|
|
{
|
|
if (SampleCount > MAX_int32)
|
|
{
|
|
return false;
|
|
}
|
|
OutDensities->SetNumUninitialized(static_cast<int32>(SampleCount));
|
|
}
|
|
|
|
const int32 OriginX = OriginVoxels.X;
|
|
const int32 OriginY = OriginVoxels.Y;
|
|
const int32 OriginZ = OriginVoxels.Z;
|
|
TArray<double> WorkerSeconds;
|
|
TArray<double> WorkerCpuSeconds;
|
|
TArray<uint8> WorkerCpuFromOsThreadTimes;
|
|
TArray<uint32> WorkerThreadIds;
|
|
TArray<uint64> WorkerNonFinite;
|
|
TArray<uint64> WorkerEvaluatedSamples;
|
|
TArray<uint64> WorkerFallbackSamples;
|
|
TArray<uint64> WorkerGeneralPathEntries;
|
|
TArray<uint64> WorkerSupportedBlockSetupEntries;
|
|
TArray<uint64> WorkerEvaluationFailures;
|
|
WorkerSeconds.SetNumZeroed(NumWorkers);
|
|
WorkerCpuSeconds.SetNumZeroed(NumWorkers);
|
|
WorkerCpuFromOsThreadTimes.SetNumZeroed(NumWorkers);
|
|
WorkerThreadIds.SetNumZeroed(NumWorkers);
|
|
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)
|
|
{
|
|
// Region-eval workers are not launched through AVoxelWorld::LoadTile, so they do not
|
|
// 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 = 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> RegionDensities;
|
|
for (int64 OutputIndex = Begin; OutputIndex < End; ++OutputIndex)
|
|
{
|
|
if (bRegionProgram)
|
|
{
|
|
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 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 < Region.SizeZ; ++Bz)
|
|
for (int32 By = 0; By < Region.SizeY; ++By)
|
|
for (int32 Bx = 0; Bx < Region.SizeX; ++Bx)
|
|
{
|
|
const int32 LocalIndex = (Bz * Region.SizeY + By) * Region.SizeX + Bx;
|
|
const int64 LatticeIndex = LinearIndex(
|
|
Region.LatticeX + Bx, Region.LatticeY + By,
|
|
Region.LatticeZ + Bz, Dimension);
|
|
(*OutDensities)[static_cast<int32>(LatticeIndex)] = RegionDensities[LocalIndex];
|
|
}
|
|
}
|
|
else if (OutDensities != nullptr)
|
|
{
|
|
for (const FRegionChunkSample& Sample : Region.RequestedSamples)
|
|
{
|
|
(*OutDensities)[Sample.OutputIndex] = RegionDensities[Sample.LocalIndex];
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
const int64 LatticeIndex = bFullRing
|
|
? OutputIndex : (*SampleIndices)[static_cast<int32>(OutputIndex)];
|
|
if (LatticeIndex < 0 || LatticeIndex >= TotalLatticeSamples)
|
|
{
|
|
++WorkerNonFinite[WorkerIndex];
|
|
continue;
|
|
}
|
|
const int32 X = static_cast<int32>(LatticeIndex % Dimension);
|
|
const int32 Y = static_cast<int32>((LatticeIndex / Dimension) % Dimension);
|
|
const int32 Z = static_cast<int32>(LatticeIndex
|
|
/ (static_cast<int64>(Dimension) * Dimension));
|
|
const float Density = Generator.GetDensityAt(
|
|
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;
|
|
}
|
|
}
|
|
WorkerSeconds[WorkerIndex] = FPlatformTime::Seconds() - WorkerStart;
|
|
double WorkerCpuEnd = 0.0;
|
|
if (bHaveWorkerCpuStart && GetCurrentThreadCpuSeconds(WorkerCpuEnd))
|
|
{
|
|
WorkerCpuSeconds[WorkerIndex] = FMath::Max(0.0, WorkerCpuEnd - WorkerCpuStart);
|
|
WorkerCpuFromOsThreadTimes[WorkerIndex] = true;
|
|
}
|
|
else
|
|
{
|
|
WorkerCpuSeconds[WorkerIndex] = WorkerSeconds[WorkerIndex];
|
|
}
|
|
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.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 += 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;
|
|
}
|
|
bool bAlreadyCounted = false;
|
|
for (int32 PriorWorker = 0; PriorWorker < WorkerIndex; ++PriorWorker)
|
|
{
|
|
bAlreadyCounted |= WorkerThreadIds[PriorWorker] == WorkerThreadIds[WorkerIndex];
|
|
}
|
|
OutTiming.ObservedWorkerThreads += bAlreadyCounted ? 0 : 1;
|
|
}
|
|
OutTiming.bWorkerCpuFromOsThreadTimes = bAllWorkerCpuFromOsThreadTimes;
|
|
if (OutTiming.EvaluatedSamples > 0)
|
|
{
|
|
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)
|
|
{
|
|
bool bSaturated = false;
|
|
bool bFinite = false;
|
|
QuantizeDensity(Density, bSaturated, bFinite);
|
|
OutTiming.SaturatedSamples += bSaturated ? 1u : 0u;
|
|
OutTiming.NonFiniteSamples += bFinite ? 0u : 1u;
|
|
}
|
|
OutTiming.QuantizedHash = HashQuantized(*OutDensities);
|
|
OutTiming.RawFloatHash = HashRawFloats(*OutDensities);
|
|
}
|
|
return OutTiming.NonFiniteSamples == 0;
|
|
}
|
|
|
|
FCandidateVisits CountCandidates(
|
|
const UVoxelGenerator& Generator,
|
|
FIntVector OriginVoxels,
|
|
int32 Dimension,
|
|
const TArray<int64>& SampleIndices,
|
|
bool bPrepared,
|
|
float ReachScale)
|
|
{
|
|
FCandidateVisits Result;
|
|
Result.SampleCount = SampleIndices.Num();
|
|
VoxelDensityProfile::Reset();
|
|
VoxelDensityProfile::SetMode(
|
|
VoxelDensityProfile::EMode::Attribution,
|
|
VoxelDensityProfile::DefaultSampleInterval);
|
|
FTiming IgnoredTiming;
|
|
const bool bEvaluated = Evaluate(
|
|
Generator, OriginVoxels, Dimension, &SampleIndices, false,
|
|
bPrepared, ReachScale, nullptr, IgnoredTiming);
|
|
const VoxelDensityProfile::FSnapshot Snapshot = VoxelDensityProfile::Snapshot();
|
|
VoxelDensityProfile::SetMode(
|
|
VoxelDensityProfile::EMode::Disabled,
|
|
VoxelDensityProfile::DefaultSampleInterval);
|
|
VoxelDensityProfile::Reset();
|
|
if (!bEvaluated)
|
|
{
|
|
return Result;
|
|
}
|
|
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)
|
|
{
|
|
return Result.SampleCount > 0
|
|
? FPlatformTime::ToSeconds64(Snapshot.Cycles[static_cast<int32>(Bucket)])
|
|
* 1.0e9 / static_cast<double>(Result.SampleCount)
|
|
: 0.0;
|
|
};
|
|
Result.GetDensityAtAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::GetDensityAt);
|
|
Result.DensityPrologueAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityPrologue);
|
|
Result.DensityCoreAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityCore);
|
|
Result.DensityDisturbancesAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityDisturbances);
|
|
Result.DensityStructuralPostsAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityStructuralPosts);
|
|
Result.DensityBoundarySealAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityBoundarySeal);
|
|
Result.DensityDiffLayerAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityDiffLayer);
|
|
Result.DensityTailAttributedNsPerSample = AttributedNanosecondsPerSample(
|
|
VoxelDensityProfile::EBucket::DensityTail);
|
|
return Result;
|
|
}
|
|
}
|