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:
1 parent
fd9f809c1c
commit
43c11ccaa8
9 files changed
+484
-143
No files matched your search
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user