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

+4 -4
View File
@@ -117,10 +117,10 @@ remains `0`, and lateral regions remain gated off.
|------|------|
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor brick program on the 259³ lattice and five-seed probe set; `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set, reporting whole-ring setup/candidate counters; `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; owns bounded render/`VF_` walk/OBJ export orchestration and is not linked by Game/Shipping. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. `-regioneval=1` runs the default-off step-2 research harness: full-ring and five-seed Q16 comparisons, repeat hashes, shrunk-reach falsification, cold timings, counted live fallbacks, and separate before/after stack captures when `-SampleDensityStacks` is set. `-RegionHashProbe` runs one full-ring brick evaluation and compares its raw float hash with a reference from another worker count. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Lattice driver with six workers by default for the commandlet-only region experiment. The live path calls `GetDensityAt` per point; the brick path groups the lattice into aligned 8³ regions and calls the editor-only `EvaluateDensityBrick` once per region, counts scalar fallback points, and compares the exact final field. The operator block uses prepared SDF candidate bins and retains canonical per-point post-stack writer order. The full-ring hash probe supports 1–32 fixed partitions to check determinism across worker counts. |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; owns bounded render/`VF_` walk/OBJ export orchestration and is not linked by Game/Shipping. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. `-regioneval=1` runs the default-off step-2 research harness: full-ring and five-seed Q16 comparisons, repeat hashes, shrunk-reach falsification, cold timings, full-ring setup/candidate counters, counted live fallbacks, and separate before/after stack captures when `-SampleDensityStacks` is set. `-RegionHashProbe` runs one full-ring chunk evaluation and compares its raw float hash with a reference from another worker count. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Lattice driver with six workers by default for the commandlet-only region experiment. The live path calls `GetDensityAt` per point; the prepared path assigns each 32³ chunk once and calls editor-only `EvaluateDensityChunk`, which seeds one block evaluation and runs canonical final-field writers across that chunk. The prepared room/SDF cache window is aligned to that chunk; 8-voxel SDF candidate bins stay editor-only and default off. Unsupported routes use counted scalar fallbacks. Full-ring output reports room/SDF cache, active-op-list, and broad-phase/index builds; general-path entries and fallbacks; candidate visits/evaluations per point; and fresh sampled attribution. The full-ring hash probe supports 1–32 fixed partitions to check determinism across worker counts. |
| `Public/VoxelGeometryHash.h` | Header-only geometry digest shared by commandlet export and runtime tile-hash dump; hashes mesh counts and the vertex/normal/UV/color/triangle arrays with the historical CRC order. |
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. Each `Cave Bail *` counter names one guard; `Cave Bail No Stack` counts slots with no buildable stack (no definition, or a degenerate strate). |
@@ -363,7 +363,7 @@ This is **where terrain shape lives.**
| `ApplyBoundarySeal` (static) | 996 | Solidifies strate top/bottom shells. |
| `InitializeSettings` | 2691 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
| **`GetDensityAt`** | 2712 | **Entry point.** Picks strate + generator type and, on the per-chunk refetch, builds the chunk's operator stack (editor composer → cooked-season recipe → native archetype stack); evaluates it (TunnelNetwork/Underwater through the fused `GetDensityWithParams`), then adds disturbances and structural posts. Optional `OutPreEditDensity` receives the exact final procedural field immediately before the player DiffLayer tail, allowing the near-player dig cache to replay edits without regenerating the operator stack. Its ordinary one-argument behavior is unchanged. A degenerate strate has no stack and is air; `CP_*` state remains keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`. |
| **`EvaluateDensityBrick`** | 2728 | **Editor-only, default-off research API.** Accepts a lattice region contained by one 8-voxel bin and one 32-voxel chunk. Routes once, reuses `FVoxelDensityBlockSession::FillChunk` / `EvalBlock` for supported TunnelNetwork and Underwater operator stacks, and applies the existing post-stack writers across all brick points before returning. Unsupported routes use scalar `GetDensityAt` and return their fallback count. The API is absent from non-editor builds; streaming and gameplay continue through the original scalar API. |
| **`EvaluateDensityChunk`** | 2728 | **Editor-only, default-off research API.** Accepts a lattice region contained by one 32-voxel chunk. Routes once, reuses `FVoxelDensityBlockSession::FillChunk` / `EvalBlock` for supported TunnelNetwork and Underwater operator stacks, and applies the existing post-stack writers across all chunk points before returning. Unsupported routes use scalar `GetDensityAt` and return both fallback and general-path entry counts. The region evaluator prepares bounded cache/index state once per region or chunk. The API is absent from non-editor builds; streaming and gameplay continue through the original scalar API. |
| **`GetDensityWithParams`** | 3749 | TunnelNetwork pipeline (~1000 lines) — the fused evaluator of the TunnelNetwork/Underwater stack in `GetDensityAt`. See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path (default 0) uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
| `ComputeSurfaceTerrainZ` / `ComputeSurfaceColumn` | — | SurfaceWorld heightfield → terrain Z (+ sky-cap ceiling and overhang gate per column); biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in the stack's `FOverhangShelfMod`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
@@ -1416,6 +1416,11 @@ namespace
static void VF_BuildSpatialIndex(
FChunkSDFSpatialIndex& OutIndex, int32 ItemCount, FGetBounds&& GetBounds)
{
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::SdfSpatialIndexBuild);
}
OutIndex.Reset();
if (ItemCount <= 0)
{
@@ -1811,6 +1816,11 @@ namespace
static void VF_BuildBrickIndex(
FChunkSDFBrickIndex& OutIndex, int32 ItemCount, FGetBounds&& GetBounds)
{
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::SdfBrickIndexBuild);
}
OutIndex.Reset();
if (ItemCount <= 0)
{
@@ -640,6 +640,8 @@ namespace VoxelDensityProfile
case ECounter::TunnelCacheHit: return TEXT("TunnelCacheHit");
case ECounter::TunnelCacheMiss: return TEXT("TunnelCacheMiss");
case ECounter::SdfCacheBuild: return TEXT("SdfCacheBuild");
case ECounter::SdfSpatialIndexBuild: return TEXT("SdfSpatialIndexBuild");
case ECounter::SdfBrickIndexBuild: return TEXT("SdfBrickIndexBuild");
case ECounter::RoomGraphBuildGeneratorTile: return TEXT("RoomGraphBuildGeneratorTile");
case ECounter::RoomGraphBuildGeneratorTunnelCore: return TEXT("RoomGraphBuildGeneratorTunnelCore");
case ECounter::RoomGraphBuildOpShared: return TEXT("RoomGraphBuildOpShared");
+28 -8
View File
@@ -38,6 +38,9 @@ namespace VoxelMath
namespace
{
#if WITH_EDITOR
thread_local bool GVoxelResearchRegionCacheWindowEnabled = false;
#endif
// The default-on branch uses VoxelMath::IsFiniteFast, while 0 retains the original
// FMath::IsFinite call for a real within-round A/B and sampler comparison.
FAutoConsoleVariableRef CVarVoxelForgeFastIsFinite(
@@ -884,6 +887,12 @@ bool VoxelGenLOD::IsTileCacheWindowEnabled(bool bFusedPath)
{
return false;
}
#if WITH_EDITOR
if (GVoxelResearchRegionCacheWindowEnabled)
{
return true;
}
#endif
const int32 RequiredMinLOD = bFusedPath
? GVoxelForgeTileCacheWindowMinLOD
: GVoxelForgeTileCacheWindowOpStackMinLOD;
@@ -891,6 +900,13 @@ bool VoxelGenLOD::IsTileCacheWindowEnabled(bool bFusedPath)
return FMath::Max(SampleStep, 1) >= RequiredStep;
}
#if WITH_EDITOR
void VoxelGenLOD::SetResearchRegionCacheWindowEnabled(bool bEnabled)
{
GVoxelResearchRegionCacheWindowEnabled = bEnabled;
}
#endif
bool VoxelGenLOD::GetThreadTileCacheWindow(
FIntVector& OutOriginVoxels, int32& OutStep, int32& OutCellsPerAxis)
{
@@ -2722,15 +2738,19 @@ void UVoxelGenerator::EndDensityBlock() const
}
#if WITH_EDITOR
bool UVoxelGenerator::EvaluateDensityBrick(
bool UVoxelGenerator::EvaluateDensityChunk(
FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
TArray<float>& OutDensities, int32& OutFallbackSamples) const
TArray<float>& OutDensities, int32& OutFallbackSamples,
uint64& OutGeneralPathEntries, uint64& OutSupportedBlockSetupEntries) const
{
OutDensities.Reset();
OutFallbackSamples = 0;
OutGeneralPathEntries = 0;
OutSupportedBlockSetupEntries = 0;
const int64 SampleCount64 = static_cast<int64>(SizeX) * SizeY * SizeZ;
if (SizeX <= 0 || SizeY <= 0 || SizeZ <= 0
|| SizeX > 8 || SizeY > 8 || SizeZ > 8 || SampleCount64 > 512)
|| SizeX > CHUNK_SIZE || SizeY > CHUNK_SIZE || SizeZ > CHUNK_SIZE
|| SampleCount64 > static_cast<int64>(CHUNK_SIZE) * CHUNK_SIZE * CHUNK_SIZE)
{
return false;
}
@@ -2741,10 +2761,7 @@ bool UVoxelGenerator::EvaluateDensityBrick(
return FMath::FloorToInt(static_cast<float>(A) / static_cast<float>(CellSize))
== FMath::FloorToInt(static_cast<float>(B) / static_cast<float>(CellSize));
};
if (!SameCell(OriginVoxels.X, Last.X, 8)
|| !SameCell(OriginVoxels.Y, Last.Y, 8)
|| !SameCell(OriginVoxels.Z, Last.Z, 8)
|| !SameCell(OriginVoxels.X, Last.X, CHUNK_SIZE)
if (!SameCell(OriginVoxels.X, Last.X, CHUNK_SIZE)
|| !SameCell(OriginVoxels.Y, Last.Y, CHUNK_SIZE)
|| !SameCell(OriginVoxels.Z, Last.Z, CHUNK_SIZE))
{
@@ -2761,17 +2778,19 @@ bool UVoxelGenerator::EvaluateDensityBrick(
static_cast<float>(OriginVoxels.X),
static_cast<float>(OriginVoxels.Y),
static_cast<float>(OriginVoxels.Z));
OutGeneralPathEntries = 1;
if (GVoxelDensityBlockSession.bBatchSucceeded)
{
OutDensities.SetNumUninitialized(SampleCount);
FMemory::Memcpy(
OutDensities.GetData(), GVoxelDensityBlockSession.BatchResults.GetData(),
static_cast<SIZE_T>(SampleCount) * sizeof(float));
OutSupportedBlockSetupEntries = 1;
EndDensityBlock();
return OutDensities.Num() == SampleCount;
}
// Recipes without the editor brick port retain the canonical point evaluator. The first
// Routes without the editor block program retain the canonical point evaluator. The first
// call already produced point zero; the remaining samples are explicit counted fallbacks.
GVoxelDensityBlockSession.bBatchRequested = false;
OutDensities.SetNumUninitialized(SampleCount);
@@ -2787,6 +2806,7 @@ bool UVoxelGenerator::EvaluateDensityBrick(
static_cast<float>(OriginVoxels.Z + Z));
}
OutFallbackSamples = SampleCount;
OutGeneralPathEntries = static_cast<uint64>(SampleCount);
EndDensityBlock();
return true;
}
@@ -106,6 +106,8 @@ namespace VoxelDensityProfile
TunnelCacheHit,
TunnelCacheMiss,
SdfCacheBuild,
SdfSpatialIndexBuild,
SdfBrickIndexBuild,
RoomGraphBuildGeneratorTile,
RoomGraphBuildGeneratorTunnelCore,
RoomGraphBuildOpShared,
+9 -4
View File
@@ -77,6 +77,10 @@ namespace VoxelGenLOD
// Called by the module's game-thread startup before any worker can enter GetDensityAt.
VOXELFORGE_API void InitializeConsoleSwitches();
VOXELFORGE_API bool IsTileCacheWindowEnabled(bool bFusedPath = true);
#if WITH_EDITOR
/** Research-only override for the bounded editor region evaluator; default false per thread. */
VOXELFORGE_API void SetResearchRegionCacheWindowEnabled(bool bEnabled);
#endif
VOXELFORGE_API bool GetThreadTileCacheWindow(
FIntVector& OutOriginVoxels, int32& OutStep, int32& OutCellsPerAxis);
VOXELFORGE_API void SetThreadTileContext(
@@ -181,10 +185,11 @@ public:
void EndDensityBlock() const;
#if WITH_EDITOR
/** Editor-only region-program entry point; unsupported routes report live point fallbacks. */
bool EvaluateDensityBrick(FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
TArray<float>& OutDensities,
int32& OutFallbackSamples) const;
/** Editor-only chunk program; supported routes enter GetDensityAt once to seed a whole block. */
bool EvaluateDensityChunk(FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
TArray<float>& OutDensities, int32& OutFallbackSamples,
uint64& OutGeneralPathEntries,
uint64& OutSupportedBlockSetupEntries) const;
#endif
/**
@@ -1758,7 +1758,7 @@ bool RunRegionEvalPass(
const FExploreArguments& BaseArguments,
int32 Seed,
bool bPrepared,
bool bBrickProgram,
bool bRegionProgram,
float ReachScale,
bool bFullRing,
const TArray<int64>* RequestedIndices,
@@ -1767,7 +1767,8 @@ bool RunRegionEvalPass(
bool bKeepDensities,
FRegionEvalPass& OutPass,
FString& OutError,
int32 RequestedWorkerCount = VoxelForgeRegionEval::WorkerCount)
int32 RequestedWorkerCount = VoxelForgeRegionEval::WorkerCount,
bool bCollectWholeRunCounters = false)
{
OutPass = FRegionEvalPass();
FExploreWorld World;
@@ -1809,7 +1810,7 @@ bool RunRegionEvalPass(
bFullRing ? nullptr : &OutPass.SampleIndices,
bFullRing, bPrepared, ReachScale,
bKeepDensities ? &OutPass.Densities : nullptr,
OutPass.Timing, bBrickProgram, RequestedWorkerCount);
OutPass.Timing, bRegionProgram, RequestedWorkerCount, bCollectWholeRunCounters);
OutPass.EndedUtc = FDateTime::UtcNow().ToIso8601();
if (!bEvaluated)
{
@@ -1835,18 +1836,102 @@ bool RunRegionEvalPass(
TSharedRef<FJsonObject> RegionEvalTimingJson(const FRegionEvalPass& Pass, bool bPrepared)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetStringField(TEXT("path"), Pass.Timing.bBrickProgram
? TEXT("editor_region_brick_program")
Json->SetStringField(TEXT("path"), Pass.Timing.bRegionProgram
? TEXT("editor_region_chunk_program")
: (bPrepared ? TEXT("prepared_8cube_bins") : TEXT("live_GetDensityAt")));
Json->SetStringField(TEXT("started_utc"), Pass.StartedUtc);
Json->SetStringField(TEXT("ended_utc"), Pass.EndedUtc);
Json->SetNumberField(TEXT("worker_count"), Pass.Timing.Workers);
Json->SetNumberField(TEXT("observed_worker_threads"), Pass.Timing.ObservedWorkerThreads);
Json->SetNumberField(TEXT("samples"), static_cast<double>(Pass.Timing.SampleCount));
Json->SetNumberField(TEXT("evaluated_brick_points"), static_cast<double>(Pass.Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("brick_count"), static_cast<double>(Pass.Timing.BrickCount));
Json->SetNumberField(TEXT("evaluated_points"), static_cast<double>(Pass.Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("region_count"), static_cast<double>(Pass.Timing.RegionCount));
Json->SetNumberField(TEXT("fallback_points"), static_cast<double>(Pass.Timing.FallbackSamples));
Json->SetNumberField(TEXT("fallback_share"), Pass.Timing.FallbackShare);
Json->SetNumberField(TEXT("general_path_entries"), static_cast<double>(Pass.Timing.GeneralPathEntries));
Json->SetNumberField(TEXT("general_path_entries_per_evaluated_sample"),
Pass.Timing.GeneralPathEntriesPerSample);
Json->SetNumberField(TEXT("supported_block_setup_entries"),
static_cast<double>(Pass.Timing.SupportedBlockSetupEntries));
Json->SetBoolField(TEXT("whole_run_counters_present"), Pass.Timing.bHasWholeRunCounters);
if (Pass.Timing.bHasWholeRunCounters)
{
Json->SetNumberField(TEXT("room_graph_sdf_cache_builds"),
static_cast<double>(Pass.Timing.SdfCacheBuilds));
Json->SetNumberField(TEXT("active_op_list_builds"),
static_cast<double>(Pass.Timing.ActiveOpListBuilds));
Json->SetNumberField(TEXT("broad_phase_index_builds"),
static_cast<double>(Pass.Timing.BroadPhaseIndexBuilds));
Json->SetNumberField(TEXT("sdf_spatial_index_builds"),
static_cast<double>(Pass.Timing.SdfSpatialIndexBuilds));
Json->SetNumberField(TEXT("sdf_3d_brick_index_builds"),
static_cast<double>(Pass.Timing.SdfBrickIndexBuilds));
Json->SetNumberField(TEXT("candidate_visits_total"),
static_cast<double>(Pass.Timing.TotalCandidateVisits));
Json->SetNumberField(TEXT("candidate_visits_per_sample"),
Pass.Timing.CandidateVisitsPerSample);
Json->SetNumberField(TEXT("candidate_evaluations_total"),
static_cast<double>(Pass.Timing.TotalCandidateEvaluations));
Json->SetNumberField(TEXT("candidate_evaluations_per_sample"),
Pass.Timing.CandidateEvaluationsPerSample);
Json->SetNumberField(TEXT("room_candidate_visits"),
static_cast<double>(Pass.Timing.RoomCandidateVisits));
Json->SetNumberField(TEXT("room_evaluations"),
static_cast<double>(Pass.Timing.RoomEvaluations));
Json->SetNumberField(TEXT("tunnel_candidate_visits"),
static_cast<double>(Pass.Timing.TunnelCandidateVisits));
Json->SetNumberField(TEXT("tunnel_evaluations"),
static_cast<double>(Pass.Timing.TunnelEvaluations));
Json->SetNumberField(TEXT("tunnel_core_candidate_visits"),
static_cast<double>(Pass.Timing.TunnelCoreCandidateVisits));
Json->SetNumberField(TEXT("tunnel_core_evaluations"),
static_cast<double>(Pass.Timing.TunnelCoreEvaluations));
Json->SetNumberField(TEXT("passage_candidate_visits"),
static_cast<double>(Pass.Timing.PassageCandidateVisits));
Json->SetNumberField(TEXT("passage_evaluations"),
static_cast<double>(Pass.Timing.PassageEvaluations));
TSharedRef<FJsonObject> Attribution = MakeShared<FJsonObject>();
Attribution->SetNumberField(TEXT("get_density_at_ns_per_sample"),
Pass.Timing.GetDensityAtNsPerSample);
Attribution->SetNumberField(TEXT("operator_block_ns_per_sample"),
Pass.Timing.OperatorBlockNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_source_ns_per_sample"),
Pass.Timing.RoomGraphSourceNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_build_ns_per_sample"),
Pass.Timing.RoomGraphBuildNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_sdf_ns_per_sample"),
Pass.Timing.RoomGraphSdfNsPerSample);
Attribution->SetNumberField(TEXT("fused_evaluator_ns_per_sample"),
Pass.Timing.FusedEvaluatorNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_sdf_ns_per_sample"),
Pass.Timing.TunnelCoreSdfNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_world_ns_per_sample"),
Pass.Timing.TunnelCoreWorldNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_posts_ns_per_sample"),
Pass.Timing.TunnelCorePostsNsPerSample);
Attribution->SetNumberField(TEXT("passage_modifier_ns_per_sample"),
Pass.Timing.PassageModifierNsPerSample);
Attribution->SetNumberField(TEXT("passage_structural_posts_ns_per_sample"),
Pass.Timing.PassageStructuralPostsNsPerSample);
Attribution->SetNumberField(TEXT("passage_floor_ns_per_sample"),
Pass.Timing.PassageFloorNsPerSample);
Attribution->SetNumberField(TEXT("structural_posts_ns_per_sample"),
Pass.Timing.StructuralPostsNsPerSample);
Attribution->SetNumberField(TEXT("density_prologue_ns_per_sample"),
Pass.Timing.DensityPrologueNsPerSample);
Attribution->SetNumberField(TEXT("density_core_ns_per_sample"),
Pass.Timing.DensityCoreNsPerSample);
Attribution->SetNumberField(TEXT("disturbances_ns_per_sample"),
Pass.Timing.DisturbancesNsPerSample);
Attribution->SetNumberField(TEXT("boundary_seal_ns_per_sample"),
Pass.Timing.DensityBoundarySealNsPerSample);
Attribution->SetNumberField(TEXT("diff_layer_ns_per_sample"),
Pass.Timing.DiffLayerNsPerSample);
Attribution->SetNumberField(TEXT("density_tail_ns_per_sample"),
Pass.Timing.DensityTailNsPerSample);
Json->SetObjectField(TEXT("fresh_attribution"), Attribution);
}
Json->SetNumberField(TEXT("world_setup_seconds_before_density_ring"), Pass.WorldPrepareSeconds);
Json->SetNumberField(TEXT("wall_seconds"), Pass.Timing.WallSeconds);
Json->SetNumberField(TEXT("worker_cpu_seconds"), Pass.Timing.WorkerCpuSeconds);
@@ -1864,26 +1949,31 @@ TSharedRef<FJsonObject> RegionEvalTimingJson(const FRegionEvalPass& Pass, bool b
Json->SetNumberField(TEXT("non_finite_samples"), static_cast<double>(Pass.Timing.NonFiniteSamples));
if (Pass.Visits.SampleCount > 0)
{
Json->SetNumberField(TEXT("candidate_visits_per_sample"), Pass.Visits.PerSample);
Json->SetNumberField(TEXT("room_candidate_visits"), static_cast<double>(Pass.Visits.Room));
Json->SetNumberField(TEXT("tunnel_candidate_visits"), static_cast<double>(Pass.Visits.Tunnel));
Json->SetNumberField(TEXT("tunnel_core_candidate_visits"), static_cast<double>(Pass.Visits.TunnelCore));
Json->SetNumberField(TEXT("passage_candidate_visits"), static_cast<double>(Pass.Visits.Passage));
Json->SetNumberField(TEXT("tunnel_cache_lookups"), static_cast<double>(Pass.Visits.TunnelCacheLookups));
Json->SetNumberField(TEXT("tunnel_cache_hits"), static_cast<double>(Pass.Visits.TunnelCacheHits));
Json->SetNumberField(TEXT("tunnel_cache_misses"), static_cast<double>(Pass.Visits.TunnelCacheMisses));
Json->SetNumberField(TEXT("sdf_cache_builds"), static_cast<double>(Pass.Visits.SdfCacheBuilds));
Json->SetNumberField(TEXT("candidate_counter_samples"), static_cast<double>(Pass.Visits.SampleCount));
Json->SetNumberField(TEXT("attribution_sample_interval"), Pass.Visits.AttributionSampleInterval);
Json->SetStringField(TEXT("attribution_basis"), TEXT("sampled VoxelDensityProfile phases; diagnostic only, excluded from clean timing"));
Json->SetNumberField(TEXT("get_density_at_attributed_ns_per_sample"), Pass.Visits.GetDensityAtAttributedNsPerSample);
Json->SetNumberField(TEXT("density_prologue_attributed_ns_per_sample"), Pass.Visits.DensityPrologueAttributedNsPerSample);
Json->SetNumberField(TEXT("density_core_attributed_ns_per_sample"), Pass.Visits.DensityCoreAttributedNsPerSample);
Json->SetNumberField(TEXT("density_disturbances_attributed_ns_per_sample"), Pass.Visits.DensityDisturbancesAttributedNsPerSample);
Json->SetNumberField(TEXT("density_structural_posts_attributed_ns_per_sample"), Pass.Visits.DensityStructuralPostsAttributedNsPerSample);
Json->SetNumberField(TEXT("density_boundary_seal_attributed_ns_per_sample"), Pass.Visits.DensityBoundarySealAttributedNsPerSample);
Json->SetNumberField(TEXT("density_diff_layer_attributed_ns_per_sample"), Pass.Visits.DensityDiffLayerAttributedNsPerSample);
Json->SetNumberField(TEXT("density_tail_attributed_ns_per_sample"), Pass.Visits.DensityTailAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_candidate_visits_per_sample"), Pass.Visits.PerSample);
Json->SetNumberField(TEXT("probe_candidate_evaluations_per_sample"), Pass.Visits.EvaluatedPerSample);
Json->SetNumberField(TEXT("probe_room_candidate_visits"), static_cast<double>(Pass.Visits.Room));
Json->SetNumberField(TEXT("probe_room_candidate_evaluations"), static_cast<double>(Pass.Visits.RoomEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_candidate_visits"), static_cast<double>(Pass.Visits.Tunnel));
Json->SetNumberField(TEXT("probe_tunnel_candidate_evaluations"), static_cast<double>(Pass.Visits.TunnelEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_core_candidate_visits"), static_cast<double>(Pass.Visits.TunnelCore));
Json->SetNumberField(TEXT("probe_tunnel_core_candidate_evaluations"), static_cast<double>(Pass.Visits.TunnelCoreEvaluated));
Json->SetNumberField(TEXT("probe_passage_candidate_visits"), static_cast<double>(Pass.Visits.Passage));
Json->SetNumberField(TEXT("probe_passage_candidate_evaluations"), static_cast<double>(Pass.Visits.PassageEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_cache_lookups"), static_cast<double>(Pass.Visits.TunnelCacheLookups));
Json->SetNumberField(TEXT("probe_tunnel_cache_hits"), static_cast<double>(Pass.Visits.TunnelCacheHits));
Json->SetNumberField(TEXT("probe_tunnel_cache_misses"), static_cast<double>(Pass.Visits.TunnelCacheMisses));
Json->SetNumberField(TEXT("probe_sdf_cache_builds"), static_cast<double>(Pass.Visits.SdfCacheBuilds));
Json->SetNumberField(TEXT("probe_candidate_counter_samples"), static_cast<double>(Pass.Visits.SampleCount));
Json->SetNumberField(TEXT("probe_attribution_sample_interval"), Pass.Visits.AttributionSampleInterval);
Json->SetStringField(TEXT("probe_attribution_basis"), TEXT("sampled VoxelDensityProfile phases; diagnostic only, excluded from clean timing"));
Json->SetNumberField(TEXT("probe_get_density_at_attributed_ns_per_sample"), Pass.Visits.GetDensityAtAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_prologue_attributed_ns_per_sample"), Pass.Visits.DensityPrologueAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_core_attributed_ns_per_sample"), Pass.Visits.DensityCoreAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_disturbances_attributed_ns_per_sample"), Pass.Visits.DensityDisturbancesAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_structural_posts_attributed_ns_per_sample"), Pass.Visits.DensityStructuralPostsAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_boundary_seal_attributed_ns_per_sample"), Pass.Visits.DensityBoundarySealAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_diff_layer_attributed_ns_per_sample"), Pass.Visits.DensityDiffLayerAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_tail_attributed_ns_per_sample"), Pass.Visits.DensityTailAttributedNsPerSample);
}
return Json;
}
@@ -2018,7 +2108,7 @@ int32 RunRegionEval(const FString& Params)
ProbeReport->SetNumberField(TEXT("observed_worker_threads"),
WorkerHashPass.Timing.ObservedWorkerThreads);
ProbeReport->SetNumberField(TEXT("samples"), WorkerHashPass.Timing.SampleCount);
ProbeReport->SetNumberField(TEXT("brick_count"), WorkerHashPass.Timing.BrickCount);
ProbeReport->SetNumberField(TEXT("region_count"), WorkerHashPass.Timing.RegionCount);
ProbeReport->SetNumberField(TEXT("fallback_points"), WorkerHashPass.Timing.FallbackSamples);
ProbeReport->SetNumberField(TEXT("fallback_share"), WorkerHashPass.Timing.FallbackShare);
ProbeReport->SetNumberField(TEXT("wall_seconds"), WorkerHashPass.Timing.WallSeconds);
@@ -2064,6 +2154,8 @@ int32 RunRegionEval(const FString& Params)
TArray<TSharedPtr<FJsonValue>> SeedReports;
TArray<TSharedPtr<FJsonValue>> OldTimingReports;
TArray<TSharedPtr<FJsonValue>> PreparedTimingReports;
TSharedPtr<FJsonObject> LiveFullRingDiagnostics;
TSharedPtr<FJsonObject> PreparedFullRingDiagnostics;
TArray<float> OldFullDensities;
TArray<float> PreparedFullDensities;
TArray<int64> PrimarySampleIndices;
@@ -2164,42 +2256,69 @@ int32 RunRegionEval(const FString& Params)
&& OldFullSaturatedSamples == 0
&& PreparedFullSaturatedSamples == 0;
// Exact whole-ring counters and sampled phase attribution are collected in separate cold
// passes. Their diagnostic hooks stay out of the two wall-time trials used for the 600 ms bar.
FRegionEvalPass LiveDiagnosticPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], false, false, 1.0f, true, nullptr,
RequestedSamples, false, false, LiveDiagnosticPass, Error,
VoxelForgeRegionEval::WorkerCount, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] live full-ring diagnostic pass failed: %s"), *Error);
return 2;
}
LiveFullRingDiagnostics = RegionEvalTimingJson(LiveDiagnosticPass, false);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FRegionEvalPass PreparedDiagnosticPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], true, true, 1.0f, true, nullptr,
RequestedSamples, false, false, PreparedDiagnosticPass, Error,
VoxelForgeRegionEval::WorkerCount, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] prepared full-ring diagnostic pass failed: %s"), *Error);
return 2;
}
PreparedFullRingDiagnostics = RegionEvalTimingJson(PreparedDiagnosticPass, true);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
// Statistical fixed-lattice comparisons across four additional independent seeds. The sample
// set is a deterministic affine permutation of the entire 259^3 ring plus dense strata,
// passage-floor, landing, carve, and fill probes.
if (Arguments.bSampleStacks)
{
FRegionEvalStackSamplerSession BrickStackSamplerSession;
if (!BrickStackSamplerSession.Start(
FRegionEvalStackSamplerSession RegionStackSamplerSession;
if (!RegionStackSamplerSession.Start(
Arguments.SampleStacksIntervalUs, Arguments.OutDirectory,
TEXT("region_eval_brick_ring")))
TEXT("region_eval_chunk_ring")))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] brick stack sampler could not be started."));
TEXT("[VoxelForgeRegionEval] chunk stack sampler could not be started."));
return 2;
}
FRegionEvalPass ProfiledBrickPass;
FRegionEvalPass ProfiledRegionPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], true, true, 1.0f, true, nullptr,
RequestedSamples, false, false, ProfiledBrickPass, Error))
RequestedSamples, false, false, ProfiledRegionPass, Error))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] brick attribution run failed: %s"), *Error);
TEXT("[VoxelForgeRegionEval] chunk attribution run failed: %s"), *Error);
return 2;
}
FVoxelStackSampler::FSummary BrickStackSummary;
if (!BrickStackSamplerSession.Stop(BrickStackSummary))
FVoxelStackSampler::FSummary RegionStackSummary;
if (!RegionStackSamplerSession.Stop(RegionStackSummary))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] brick stack capture retained no worker samples."));
TEXT("[VoxelForgeRegionEval] chunk stack capture retained no worker samples."));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] brick stack capture registered=%llu retained=%llu "
TEXT("[VoxelForgeRegionEval] chunk stack capture registered=%llu retained=%llu "
"raw=%s summary=%s"),
static_cast<unsigned long long>(BrickStackSummary.RegisteredThreadSamples),
static_cast<unsigned long long>(BrickStackSummary.RetainedSamples),
*BrickStackSummary.RawSamplesPath, *BrickStackSummary.SummaryPath);
static_cast<unsigned long long>(RegionStackSummary.RegisteredThreadSamples),
static_cast<unsigned long long>(RegionStackSummary.RetainedSamples),
*RegionStackSummary.RawSamplesPath, *RegionStackSummary.SummaryPath);
}
for (int32 SeedIndex = 1; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex)
@@ -2307,9 +2426,11 @@ int32 RunRegionEval(const FString& Params)
const bool bSpeedPass = PreparedMedianWall <= 0.600 && bSixWorkersObserved;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetNumberField(TEXT("schema_version"), 1);
Report->SetNumberField(TEXT("schema_version"), 2);
Report->SetStringField(TEXT("route"), TEXT("editor-only explore commandlet; required Unreal composer, manager, and generator types"));
Report->SetStringField(TEXT("evaluator"), TEXT("editor-only 8^3 region brick program; block-evaluated composer operator core and canonical per-point final-field writers; unsupported routes use counted live fallbacks"));
Report->SetStringField(TEXT("evaluator"), TEXT("editor-only chunk program; region-scoped room/SDF caches, one operator block per chunk, canonical post-stack writers, and counted live point fallbacks"));
Report->SetStringField(TEXT("timing_basis"), TEXT("two cold wall-time trials per path with counters disabled; exact whole-ring counters and sampled phase attribution are separate diagnostic passes"));
Report->SetBoolField(TEXT("diagnostic_passes_excluded_from_speed_verdict"), true);
Report->SetStringField(TEXT("comparison_quantization"), TEXT("signed int16; round ties away from zero; multiply density by 16; saturate outside [-2048, 2047.9375] density voxels"));
Report->SetNumberField(TEXT("worker_count"), VoxelForgeRegionEval::WorkerCount);
Report->SetNumberField(TEXT("ring_dimension"), VoxelForgeRegionEval::RingDimension);
@@ -2325,6 +2446,8 @@ int32 RunRegionEval(const FString& Params)
Report->SetObjectField(TEXT("full_ring_comparison"), RegionEvalComparisonJson(FullComparison));
Report->SetArrayField(TEXT("live_full_ring_trials"), OldTimingReports);
Report->SetArrayField(TEXT("prepared_full_ring_trials"), PreparedTimingReports);
Report->SetObjectField(TEXT("live_full_ring_diagnostics"), LiveFullRingDiagnostics.ToSharedRef());
Report->SetObjectField(TEXT("prepared_full_ring_diagnostics"), PreparedFullRingDiagnostics.ToSharedRef());
Report->SetArrayField(TEXT("additional_seed_comparisons"), SeedReports);
TSharedRef<FJsonObject> Coverage = MakeShared<FJsonObject>();
@@ -2342,7 +2465,7 @@ int32 RunRegionEval(const FString& Params)
Coverage->SetStringField(TEXT("tunnel_bin_reach"), TEXT("union of finalized warped-SDF and world-chain centerline bounds expanded by their influence radii, plus both conservative bounding spheres"));
Coverage->SetStringField(TEXT("warp_coordinates"), TEXT("room bins are queried after the exact live cave warp in the same coordinate domain; tunnel bins union warped-SDF and unwarped world-chain reach"));
Coverage->SetStringField(TEXT("tunnel_bin_unit"), TEXT("whole cached tunnel chain; per-chain segment loops remain in the exact evaluator"));
Coverage->SetStringField(TEXT("unbinned_writers"), TEXT("passage/landing posts, disturbances, and player diff edits stay in canonical GetDensityAt tail; this is the candidate-bin ablation, not the full region compiler"));
Coverage->SetStringField(TEXT("unbinned_writers"), TEXT("passage/landing posts, disturbances, and player diff edits retain their canonical per-sample order inside the chunk block writer pass"));
Coverage->SetBoolField(TEXT("route_landing_floor_probes"), PrimaryPassageCount > 0);
Coverage->SetBoolField(TEXT("strata_top_and_bottom_boundary_sweep"), true);
Coverage->SetBoolField(TEXT("player_carve_and_fill_edits"), true);
@@ -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)
{
@@ -18,33 +18,79 @@ namespace VoxelForgeRegionEval
int32 ObservedWorkerThreads = 0;
int64 SampleCount = 0;
int64 EvaluatedSamples = 0;
int64 BrickCount = 0;
int64 RegionCount = 0;
int64 FallbackSamples = 0;
uint64 GeneralPathEntries = 0;
uint64 SupportedBlockSetupEntries = 0;
double FallbackShare = 0.0;
double GeneralPathEntriesPerSample = 0.0;
double WallSeconds = 0.0;
double WorkerCpuSeconds = 0.0;
double NanosecondsPerSample = 0.0;
bool bWorkerCpuFromOsThreadTimes = false;
bool bBrickProgram = false;
bool bRegionProgram = false;
bool bHasWholeRunCounters = false;
uint64 QuantizedHash = 0;
uint64 RawFloatHash = 0;
uint64 SaturatedSamples = 0;
uint64 NonFiniteSamples = 0;
uint64 SdfCacheBuilds = 0;
uint64 ActiveOpListBuilds = 0;
uint64 BroadPhaseIndexBuilds = 0;
uint64 SdfSpatialIndexBuilds = 0;
uint64 SdfBrickIndexBuilds = 0;
uint64 RoomCandidateVisits = 0;
uint64 RoomEvaluations = 0;
uint64 TunnelCandidateVisits = 0;
uint64 TunnelEvaluations = 0;
uint64 TunnelCoreCandidateVisits = 0;
uint64 TunnelCoreEvaluations = 0;
uint64 PassageCandidateVisits = 0;
uint64 PassageEvaluations = 0;
uint64 TotalCandidateVisits = 0;
uint64 TotalCandidateEvaluations = 0;
double CandidateVisitsPerSample = 0.0;
double CandidateEvaluationsPerSample = 0.0;
double GetDensityAtNsPerSample = 0.0;
double OperatorBlockNsPerSample = 0.0;
double RoomGraphSourceNsPerSample = 0.0;
double RoomGraphBuildNsPerSample = 0.0;
double RoomGraphSdfNsPerSample = 0.0;
double FusedEvaluatorNsPerSample = 0.0;
double TunnelCoreSdfNsPerSample = 0.0;
double TunnelCoreWorldNsPerSample = 0.0;
double TunnelCorePostsNsPerSample = 0.0;
double PassageModifierNsPerSample = 0.0;
double PassageStructuralPostsNsPerSample = 0.0;
double PassageFloorNsPerSample = 0.0;
double StructuralPostsNsPerSample = 0.0;
double DensityPrologueNsPerSample = 0.0;
double DensityCoreNsPerSample = 0.0;
double DisturbancesNsPerSample = 0.0;
double DensityBoundarySealNsPerSample = 0.0;
double DiffLayerNsPerSample = 0.0;
double DensityTailNsPerSample = 0.0;
};
struct FCandidateVisits
{
int64 SampleCount = 0;
uint64 Room = 0;
uint64 RoomEvaluated = 0;
uint64 Tunnel = 0;
uint64 TunnelEvaluated = 0;
uint64 TunnelCore = 0;
uint64 TunnelCoreEvaluated = 0;
uint64 Passage = 0;
uint64 PassageEvaluated = 0;
uint64 TunnelCacheLookups = 0;
uint64 TunnelCacheHits = 0;
uint64 TunnelCacheMisses = 0;
uint64 SdfCacheBuilds = 0;
uint64 Total = 0;
uint64 TotalEvaluated = 0;
double PerSample = 0.0;
double EvaluatedPerSample = 0.0;
uint32 AttributionSampleInterval = 0;
double GetDensityAtAttributedNsPerSample = 0.0;
double DensityPrologueAttributedNsPerSample = 0.0;
@@ -57,11 +103,10 @@ namespace VoxelForgeRegionEval
};
/**
* Evaluate a deterministic lattice through the live final-field API. The prepared mode only
* enables the commandlet-only 8^3 SDF candidate bins; all field writers and their order remain
* the live GetDensityAt implementation. FullRing uses brick-grouped output ownership and
* writes every lattice density exactly once. SampleIndices are sorted lattice offsets for the
* statistical correctness / candidate-counter sweeps.
* Evaluate a deterministic lattice through the live final-field API. The prepared mode uses
* the commandlet-only 8^3 SDF candidate bins and chunk-sized operator blocks. FullRing assigns
* each chunk to one worker and writes every lattice density exactly once. SampleIndices are
* sorted lattice offsets for the statistical correctness / candidate-counter sweeps.
*/
bool Evaluate(
const UVoxelGenerator& Generator,
@@ -73,8 +118,9 @@ namespace VoxelForgeRegionEval
float ReachScale,
TArray<float>* OutDensities,
FTiming& OutTiming,
bool bBrickProgram = false,
int32 RequestedWorkerCount = WorkerCount);
bool bRegionProgram = false,
int32 RequestedWorkerCount = WorkerCount,
bool bCollectWholeRunCounters = false);
/** Count exact room/tunnel/core/passage candidate visits with instrumentation enabled. */
FCandidateVisits CountCandidates(