#include "VoxelForgeRegionEvalExperiment.h" #include "Async/ParallelFor.h" #include "HAL/PlatformTime.h" #include "VoxelCaveMorphology.h" #include "VoxelDensityProfile.h" #include "VoxelGenerator.h" #include "VoxelStackSampler.h" #include "HAL/PlatformTLS.h" #if PLATFORM_WINDOWS #include "Windows/AllowWindowsPlatformTypes.h" THIRD_PARTY_INCLUDES_START #include 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& Densities) { uint64 Hash = 14695981039346656037ull; for (const float Density : Densities) { uint32 Bits = 0; FMemory::Memcpy(&Bits, &Density, sizeof(Bits)); Hash = Fnv1aByte(Hash, static_cast(Bits & 0xFFu)); Hash = Fnv1aByte(Hash, static_cast((Bits >> 8) & 0xFFu)); Hash = Fnv1aByte(Hash, static_cast((Bits >> 16) & 0xFFu)); Hash = Fnv1aByte(Hash, static_cast((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(KernelTicks.QuadPart + UserTicks.QuadPart) * WindowsFileTimeSeconds; return true; #else (void)OutSeconds; return false; #endif } int64 LatticeCount(int32 Dimension) { return static_cast(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 RequestedSamples; }; } int64 LinearIndex(int32 X, int32 Y, int32 Z, int32 Dimension) { return (static_cast(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(Density) * QuantizationScale; const double Rounded = Scaled >= 0.0 ? FMath::FloorToDouble(Scaled + 0.5) : FMath::CeilToDouble(Scaled - 0.5); if (Rounded > static_cast(MAX_int16)) { bOutSaturated = true; return MAX_int16; } if (Rounded < static_cast(MIN_int16)) { bOutSaturated = true; return MIN_int16; } return static_cast(Rounded); } uint64 HashQuantized(const TArray& Densities) { uint64 Hash = 14695981039346656037ull; for (const float Density : Densities) { bool bSaturated = false; bool bFinite = false; const uint16 Q = static_cast(QuantizeDensity(Density, bSaturated, bFinite)); Hash = Fnv1aByte(Hash, static_cast(Q & 0xFFu)); Hash = Fnv1aByte(Hash, static_cast(Q >> 8)); } return Hash; } bool Evaluate( const UVoxelGenerator& Generator, FIntVector OriginVoxels, int32 Dimension, const TArray* SampleIndices, bool bFullRing, bool bPrepared, float ReachScale, TArray* 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(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 Regions; TArray 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(OriginVoxels.X) / CHUNK_SIZE); const int32 FirstCellY = FMath::FloorToInt(static_cast(OriginVoxels.Y) / CHUNK_SIZE); const int32 FirstCellZ = FMath::FloorToInt(static_cast(OriginVoxels.Z) / CHUNK_SIZE); const int32 LastCellX = FMath::FloorToInt(static_cast(LastX) / CHUNK_SIZE); const int32 LastCellY = FMath::FloorToInt(static_cast(LastY) / CHUNK_SIZE); const int32 LastCellZ = FMath::FloorToInt(static_cast(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(LatticeIndex % Dimension); const int32 Y = static_cast((LatticeIndex / Dimension) % Dimension); const int32 Z = static_cast(LatticeIndex / (static_cast(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(WorldX) / CHUNK_SIZE); const int32 CellY = FMath::FloorToInt(static_cast(WorldY) / CHUNK_SIZE); const int32 CellZ = FMath::FloorToInt(static_cast(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(SampleCount)); } const int32 OriginX = OriginVoxels.X; const int32 OriginY = OriginVoxels.Y; const int32 OriginZ = OriginVoxels.Z; TArray WorkerSeconds; TArray WorkerCpuSeconds; TArray WorkerCpuFromOsThreadTimes; TArray WorkerThreadIds; TArray WorkerNonFinite; TArray WorkerEvaluatedSamples; TArray WorkerFallbackSamples; TArray WorkerGeneralPathEntries; TArray WorkerSupportedBlockSetupEntries; TArray 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 RegionDensities; for (int64 OutputIndex = Begin; OutputIndex < End; ++OutputIndex) { if (bRegionProgram) { const FRegionChunk& Region = Regions[WorkRegions[static_cast(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(Region.Origin.X) / CHUNK_SIZE) * CHUNK_SIZE, FMath::FloorToInt(static_cast(Region.Origin.Y) / CHUNK_SIZE) * CHUNK_SIZE, FMath::FloorToInt(static_cast(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(RegionSampleCount); WorkerFallbackSamples[WorkerIndex] += static_cast(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(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(OutputIndex)]; if (LatticeIndex < 0 || LatticeIndex >= TotalLatticeSamples) { ++WorkerNonFinite[WorkerIndex]; continue; } const int32 X = static_cast(LatticeIndex % Dimension); const int32 Y = static_cast((LatticeIndex / Dimension) % Dimension); const int32 Z = static_cast(LatticeIndex / (static_cast(Dimension) * Dimension)); const float Density = Generator.GetDensityAt( static_cast(OriginX + X), static_cast(OriginY + Y), static_cast(OriginZ + Z)); ++WorkerEvaluatedSamples[WorkerIndex]; ++WorkerGeneralPathEntries[WorkerIndex]; if (OutDensities != nullptr) { (*OutDensities)[static_cast(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(WorkerEvaluatedSamples[WorkerIndex]); OutTiming.FallbackSamples += static_cast(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(OutTiming.FallbackSamples) / static_cast(OutTiming.EvaluatedSamples); OutTiming.GeneralPathEntriesPerSample = static_cast(OutTiming.GeneralPathEntries) / static_cast(OutTiming.EvaluatedSamples); } OutTiming.NanosecondsPerSample = OutTiming.SampleCount > 0 ? OutTiming.WorkerCpuSeconds * 1.0e9 / static_cast(OutTiming.SampleCount) : 0.0; if (bCollectWholeRunCounters) { using VoxelDensityProfile::ECounter; using VoxelDensityProfile::EBucket; const auto Counter = [&ProfileSnapshot](ECounter Id) { return ProfileSnapshot.Counters[static_cast(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(OutTiming.TotalCandidateVisits) / static_cast(OutTiming.SampleCount); OutTiming.CandidateEvaluationsPerSample = static_cast(OutTiming.TotalCandidateEvaluations) / static_cast(OutTiming.SampleCount); } const auto AttributedNsPerSample = [&ProfileSnapshot, SampleCount](EBucket Bucket) { return SampleCount > 0 ? FPlatformTime::ToSeconds64(ProfileSnapshot.Cycles[static_cast(Bucket)]) * 1.0e9 / static_cast(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& 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(ECounter::CaveRoomCandidates)]; Result.RoomEvaluated = Snapshot.Counters[static_cast(ECounter::CaveRoomEvaluated)]; Result.Tunnel = Snapshot.Counters[static_cast(ECounter::CaveTunnelCandidates)]; Result.TunnelEvaluated = Snapshot.Counters[static_cast(ECounter::CaveTunnelEvaluated)]; Result.TunnelCore = Snapshot.Counters[static_cast(ECounter::TunnelCoreCandidates)]; Result.TunnelCoreEvaluated = Snapshot.Counters[static_cast(ECounter::TunnelCoreEvaluated)]; Result.Passage = Snapshot.Counters[static_cast(ECounter::PassageCandidates)]; Result.PassageEvaluated = Snapshot.Counters[static_cast(ECounter::PassageEvaluated)]; Result.TunnelCacheLookups = Snapshot.Counters[static_cast(ECounter::TunnelCacheLookup)]; Result.TunnelCacheHits = Snapshot.Counters[static_cast(ECounter::TunnelCacheHit)]; Result.TunnelCacheMisses = Snapshot.Counters[static_cast(ECounter::TunnelCacheMiss)]; Result.SdfCacheBuilds = Snapshot.Counters[static_cast(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(Result.Total) / Result.SampleCount : 0.0; Result.EvaluatedPerSample = Result.SampleCount > 0 ? static_cast(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(Bucket)]) * 1.0e9 / static_cast(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; } }