wip(assembler): round S14 -- 4-way column cache + memoized MC gradients (output unchanged)
Worker-local XY column cache uses deterministic 4-way sets at LOD3-8: LOD4 misses 8,224 -> 1,437, conflicts 7,407 -> 0 (direct-map control: 6.62 s / 43.5 worker-s vs 4.24 s / 21.7). Exact LOD0 lattice gradients memoized per tile: MC-exclusive 242 -> 201 ns/sample, two-pass loop unchanged. Hashes identical, no recipe bump. Primary cold trace 4.24 s full set, ring 0.45 s, 75/75 at 40 m/s; a repeat ran uniformly ~40% slower on every LOD (machine contention suspected); tick p95 to re-check on an idle machine. CVars voxel.CoreColumnCacheDirectMapped / voxel.CoreMCGradientMemoization. Report: Standalone/Research/WorldAssemblerM3/Round18. 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
bd5e38594f
commit
ec2afe9c93
11 files changed
+757
-38
No files matched your search
@@ -694,6 +694,10 @@ driven by `EditorBrush*` props.
|
|||||||
position → seamless across chunk borders (both sides use identical pure samples). NO per-vertex
|
position → seamless across chunk borders (both sides use identical pure samples). NO per-vertex
|
||||||
`GetDensityAt` (was ~6/vertex, often as costly as the whole grid). `ComputeGradientNormal` is now
|
`GetDensityAt` (was ~6/vertex, often as costly as the whole grid). `ComputeGradientNormal` is now
|
||||||
unused. Only NORMALS changed vs the old path; geometry is identical.
|
unused. Only NORMALS changed vs the old path; geometry is identical.
|
||||||
|
Round 18 memoizes these exact central-difference `FVector`s in per-worker scratch arrays for the
|
||||||
|
step-1 ShapeWorld path. Validity is reset for each tile; cache hits return the same vector and do
|
||||||
|
not bypass the density grid, halo, or two-pass cell loop. Keep the cache worker-local and exact;
|
||||||
|
do not move gradient probes above the surface-cell test.
|
||||||
- **Core hybrid sharp mesher (M2)** (`CoreMesherRecipe` / `MesherRecipe`): the property remains
|
- **Core hybrid sharp mesher (M2)** (`CoreMesherRecipe` / `MesherRecipe`): the property remains
|
||||||
opt-in and defaults to `MarchingCubesV1`; `bUseVoxelForgeCore` remains separately default-off.
|
opt-in and defaults to `MarchingCubesV1`; `bUseVoxelForgeCore` remains separately default-off.
|
||||||
V1 preserves the round-3 feature-aware dual-contouring path for repeatability. V2 reuses the
|
V1 preserves the round-3 feature-aware dual-contouring path for repeatability. V2 reuses the
|
||||||
@@ -1778,6 +1782,20 @@ shared-page A/B test, not proof that a shared cache will pay after synchronizati
|
|||||||
costs. Round 17 changed no density, mesh, collision, or recipe output; recipe versions remain
|
costs. Round 17 changed no density, mesh, collision, or recipe output; recipe versions remain
|
||||||
unchanged. See [`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
unchanged. See [`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
||||||
|
|
||||||
|
**Round 18 / S14:** the 65,536-entry worker-local XY column cache keeps the legacy direct-map
|
||||||
|
lookup for LOD0–2 (at most 33/28/4 conflict misses in the cold audit) and uses four-way sets for
|
||||||
|
LOD3–8. The deterministic coordinate/hash mixer and round-robin replacement cut LOD4 from 8,224
|
||||||
|
misses / 7,407 conflict misses to 1,437 / 0, and LOD3 from 1,634 / 1,119 to 574 / 0. Exact key
|
||||||
|
validation keeps fill order from affecting samples. `voxel.CoreColumnCacheDirectMapped=1` restores
|
||||||
|
the S13 route as a recorded negative control.
|
||||||
|
|
||||||
|
The step-1 ShapeWorld gradient memo reduced LOD0 fallback density probes from 11,474 to 3,018 at
|
||||||
|
the sampled p95 and gradient-normal cost from 65.10 to 24.16 ns/sample; the two-pass MC invariant
|
||||||
|
is unchanged. `voxel.CoreMCGradientMemoization=0` is its recorded negative control. No shared XY
|
||||||
|
page was added: cross-worker repeats alone do not establish a net page-cache win, and the S8
|
||||||
|
immutable-page lifetime rules would add ownership and memory cost. Round 18 changed no output or
|
||||||
|
recipe version. See [`Standalone/Research/WorldAssemblerM3/Round18/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round18/REPORT.md).
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+14
@@ -1095,6 +1095,20 @@ generation output. Measurement-only stride and octave probes are confined to the
|
|||||||
audit and are not runtime controls. The result and limits are recorded in
|
audit and are not runtime controls. The result and limits are recorded in
|
||||||
[`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
[`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
||||||
|
|
||||||
|
### 3.20 MC stage audit and coarse column-cache conflicts — World Assembler S14 / M3 Round 18
|
||||||
|
|
||||||
|
`Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp` records the sampled density
|
||||||
|
grid, cell classification, grid-gradient normals, edge interpolation, and vertex/dedup stream
|
||||||
|
stages for the BuildHost audit. Its exact per-worker gradient memo is controlled and trace-read
|
||||||
|
back through `voxel.CoreMCGradientMemoization`. `Standalone/Core/src/WorldAssemblerInternal.h`
|
||||||
|
keeps the 65,536-entry worker-local column cache, using the S13 direct-map for LOD0–2 and a
|
||||||
|
deterministic four-way set-associative route for LOD3–8; `voxel.CoreColumnCacheDirectMapped=1`
|
||||||
|
restores the S13 path for the negative control. Runtime CVar capture lives in
|
||||||
|
`Source/VoxelForge/Private/VoxelWorld.cpp`; stage and conflict counts are reported by
|
||||||
|
`Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`. The measurements, controls, and
|
||||||
|
cold-path decision are in
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Round18/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round18/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -235,6 +235,18 @@ namespace
|
|||||||
GVoxelForgeStreamingBackgroundWorkerCap,
|
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||||
TEXT("Size tile admission slots to the TaskGraph background workers used by tile tasks; 0 restores the logical-core cap."));
|
TEXT("Size tile admission slots to the TaskGraph background workers used by tile tasks; 0 restores the logical-core cap."));
|
||||||
|
|
||||||
|
int32 GVoxelForgeCoreColumnCacheDirectMapped = 0;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeCoreColumnCacheDirectMapped(
|
||||||
|
TEXT("voxel.CoreColumnCacheDirectMapped"),
|
||||||
|
GVoxelForgeCoreColumnCacheDirectMapped,
|
||||||
|
TEXT("Negative control: 0 uses the four-way deterministic XY column cache; 1 restores S13 direct-map indexing."));
|
||||||
|
|
||||||
|
int32 GVoxelForgeCoreMCGradientMemoization = 1;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeCoreMCGradientMemoization(
|
||||||
|
TEXT("voxel.CoreMCGradientMemoization"),
|
||||||
|
GVoxelForgeCoreMCGradientMemoization,
|
||||||
|
TEXT("Negative control: 1 reuses exact LOD0 lattice gradients across adjacent MC cells; 0 restores per-cell gradient probes."));
|
||||||
|
|
||||||
int32 GVoxelForgeProfileTileGeneration = 0;
|
int32 GVoxelForgeProfileTileGeneration = 0;
|
||||||
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
||||||
TEXT("voxel.ProfileTileGeneration"),
|
TEXT("voxel.ProfileTileGeneration"),
|
||||||
@@ -2080,6 +2092,8 @@ void AVoxelWorld::BeginPlay()
|
|||||||
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
||||||
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
|
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
|
||||||
int32 CommandLineStreamingBackgroundWorkerCap = GVoxelForgeStreamingBackgroundWorkerCap;
|
int32 CommandLineStreamingBackgroundWorkerCap = GVoxelForgeStreamingBackgroundWorkerCap;
|
||||||
|
int32 CommandLineCoreColumnCacheDirectMapped = GVoxelForgeCoreColumnCacheDirectMapped;
|
||||||
|
int32 CommandLineCoreMCGradientMemoization = GVoxelForgeCoreMCGradientMemoization;
|
||||||
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
||||||
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
||||||
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
||||||
@@ -2186,6 +2200,18 @@ void AVoxelWorld::BeginPlay()
|
|||||||
GVoxelForgeStreamingBackgroundWorkerCap =
|
GVoxelForgeStreamingBackgroundWorkerCap =
|
||||||
CommandLineStreamingBackgroundWorkerCap != 0 ? 1 : 0;
|
CommandLineStreamingBackgroundWorkerCap != 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.CoreColumnCacheDirectMapped="),
|
||||||
|
CommandLineCoreColumnCacheDirectMapped))
|
||||||
|
{
|
||||||
|
GVoxelForgeCoreColumnCacheDirectMapped=
|
||||||
|
CommandLineCoreColumnCacheDirectMapped!=0?1:0;
|
||||||
|
}
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.CoreMCGradientMemoization="),
|
||||||
|
CommandLineCoreMCGradientMemoization))
|
||||||
|
{
|
||||||
|
GVoxelForgeCoreMCGradientMemoization=
|
||||||
|
CommandLineCoreMCGradientMemoization!=0?1:0;
|
||||||
|
}
|
||||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
||||||
CommandLineStep1PrequeueVisibleLOD0))
|
CommandLineStep1PrequeueVisibleLOD0))
|
||||||
{
|
{
|
||||||
@@ -2609,7 +2635,9 @@ void AVoxelWorld::BeginPlay()
|
|||||||
"\"streaming_legacy_pending_result_cap\":%d,"
|
"\"streaming_legacy_pending_result_cap\":%d,"
|
||||||
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
|
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
|
||||||
"\"collision_gate\":%d,"
|
"\"collision_gate\":%d,"
|
||||||
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d"),
|
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d,"
|
||||||
|
"\"core_column_cache_direct_mapped\":%d,"
|
||||||
|
"\"core_mc_gradient_memoization\":%d"),
|
||||||
FPlatformMisc::NumberOfCoresIncludingHyperthreads(), GetMaxConcurrentStreamingTasks(),
|
FPlatformMisc::NumberOfCoresIncludingHyperthreads(), GetMaxConcurrentStreamingTasks(),
|
||||||
TaskScheduler.GetNumWorkers(), TaskScheduler.GetMaxNumWorkers(),
|
TaskScheduler.GetNumWorkers(), TaskScheduler.GetMaxNumWorkers(),
|
||||||
static_cast<int32>(TaskScheduler.GetWorkerPriority()),
|
static_cast<int32>(TaskScheduler.GetWorkerPriority()),
|
||||||
@@ -2622,7 +2650,9 @@ void AVoxelWorld::BeginPlay()
|
|||||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
|
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
|
||||||
GVoxelForgeCollisionGate,
|
GVoxelForgeCollisionGate,
|
||||||
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost));
|
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost,
|
||||||
|
GVoxelForgeCoreColumnCacheDirectMapped,
|
||||||
|
GVoxelForgeCoreMCGradientMemoization));
|
||||||
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_worker_cap_readback"), FString::Printf(
|
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_worker_cap_readback"), FString::Printf(
|
||||||
TEXT("\"streaming_background_worker_cap\":%d,\"background_worker_threads\":%d,"
|
TEXT("\"streaming_background_worker_cap\":%d,\"background_worker_threads\":%d,"
|
||||||
"\"effective_tile_slots\":%d"),
|
"\"effective_tile_slots\":%d"),
|
||||||
@@ -13043,13 +13073,15 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, TaskEpoch);
|
VoxelForgeStartupTrace::RecordRequest(RequestSample, TaskEpoch);
|
||||||
}
|
}
|
||||||
const bool bCaptureHashes = bCaptureVoxelForgeCoreHashesForValidation;
|
const bool bCaptureHashes = bCaptureVoxelForgeCoreHashesForValidation;
|
||||||
|
const bool bLegacyDirectMappedColumnCache=GVoxelForgeCoreColumnCacheDirectMapped!=0;
|
||||||
|
const bool bMemoizeCoreMCGradients=GVoxelForgeCoreMCGradientMemoization!=0;
|
||||||
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
||||||
? UE::Tasks::ETaskPriority::BackgroundHigh
|
? UE::Tasks::ETaskPriority::BackgroundHigh
|
||||||
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
||||||
UE::Tasks::Launch(TEXT("VoxelForgeCoreTile"),
|
UE::Tasks::Launch(TEXT("VoxelForgeCoreTile"),
|
||||||
[this, State, Cancellation, Tile, WorkPriority, TaskEpoch, TaskDesiredEpoch,
|
[this, State, Cancellation, Tile, WorkPriority, TaskEpoch, TaskDesiredEpoch,
|
||||||
TaskModificationEpoch, RequestStartCycles, RequestOrdinal, bResidentAtRequest,
|
TaskModificationEpoch, RequestStartCycles, RequestOrdinal, bResidentAtRequest,
|
||||||
bCaptureHashes]()
|
bCaptureHashes,bLegacyDirectMappedColumnCache,bMemoizeCoreMCGradients]()
|
||||||
{
|
{
|
||||||
struct FTaskGuard
|
struct FTaskGuard
|
||||||
{
|
{
|
||||||
@@ -13153,6 +13185,8 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Options.hashDensity = bCaptureHashes;
|
Options.hashDensity = bCaptureHashes;
|
||||||
|
Options.legacyDirectMappedColumnCache=bLegacyDirectMappedColumnCache;
|
||||||
|
Options.memoizeMcGradients=bMemoizeCoreMCGradients;
|
||||||
std::string CoreError;
|
std::string CoreError;
|
||||||
std::optional<VoxelForgeCore::TileMesh> CoreMesh;
|
std::optional<VoxelForgeCore::TileMesh> CoreMesh;
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16204,10 +16204,10 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
}
|
}
|
||||||
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
||||||
}
|
}
|
||||||
for (const int32 LOD : {0,1,3,4,5,8})
|
for (const int32 LOD : {0,1,2,3,4,5,6,7,8})
|
||||||
{
|
{
|
||||||
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
|
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
|
||||||
TArray<double> UnknownBoxes,FieldCalls,ColumnMisses,StratumShellTimes,ConnectorShellTimes;
|
TArray<double> UnknownBoxes,FieldCalls,UnclampedFieldCalls,ColumnMisses,ColumnConflictMisses,StratumShellTimes,ConnectorShellTimes;
|
||||||
TArray<double> DensitySamples,ClassificationTimes,ClassificationExclusiveTimes;
|
TArray<double> DensitySamples,ClassificationTimes,ClassificationExclusiveTimes;
|
||||||
TArray<double> ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped;
|
TArray<double> ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped;
|
||||||
TArray<double> ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes;
|
TArray<double> ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes;
|
||||||
@@ -16216,13 +16216,29 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
TArray<double> ClassificationNsPerSample,ColumnNsPerSample,MarchingNsPerSample;
|
TArray<double> ClassificationNsPerSample,ColumnNsPerSample,MarchingNsPerSample;
|
||||||
TArray<double> GroundNsPerSample,CapNsPerSample,AssemblyNsPerSample,ListNsPerSample;
|
TArray<double> GroundNsPerSample,CapNsPerSample,AssemblyNsPerSample,ListNsPerSample;
|
||||||
TArray<double> MarchingColumnNsPerSample,GroundColumnNsPerSample,CapColumnNsPerSample;
|
TArray<double> MarchingColumnNsPerSample,GroundColumnNsPerSample,CapColumnNsPerSample;
|
||||||
|
TArray<double> McProfileTotalTimes,McProfileGridTimes,McProfileGridExclusiveTimes;
|
||||||
|
TArray<double> McProfileCellTimes,McProfileNormalTimes,McProfileInterpolationTimes;
|
||||||
|
TArray<double> McProfileStreamTimes,McProfileOtherTimes,McProfileResidualTimes;
|
||||||
|
TArray<double> McProfileGradientFallbackCalls,McProfileEdgeMidpointCalls,McProfileGradientMemoHits;
|
||||||
|
TArray<double> McProfileGridNsPerSample,McProfileGridExclusiveNsPerSample;
|
||||||
|
TArray<double> McProfileCellNsPerSample,McProfileNormalNsPerSample;
|
||||||
|
TArray<double> McProfileInterpolationNsPerSample,McProfileStreamNsPerSample;
|
||||||
|
TArray<double> McProfileOtherNsPerSample,McProfileResidualNsPerSample;
|
||||||
TArray<double> StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles;
|
TArray<double> StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles;
|
||||||
|
const bool bMcStageAudit=FParse::Param(FCommandLine::Get(),TEXT("coremcstageaudit"));
|
||||||
|
int32 AuditDirectMappedColumnCache=0,AuditMemoizeMcGradients=1;
|
||||||
|
FParse::Value(FCommandLine::Get(),TEXT("voxel.CoreColumnCacheDirectMapped="),
|
||||||
|
AuditDirectMappedColumnCache);
|
||||||
|
FParse::Value(FCommandLine::Get(),TEXT("voxel.CoreMCGradientMemoization="),
|
||||||
|
AuditMemoizeMcGradients);
|
||||||
for (const auto& Key : Keys)
|
for (const auto& Key : Keys)
|
||||||
{
|
{
|
||||||
if (Key.lod!=LOD || FullTimes.Num()>=8) continue;
|
if (Key.lod!=LOD || FullTimes.Num()>=8) continue;
|
||||||
VoxelForgeCore::TileGenerationOptions Options;
|
VoxelForgeCore::TileGenerationOptions Options;
|
||||||
Options.hashDensity=true;
|
Options.hashDensity=true;
|
||||||
Options.useAssemblyFeatureQueries=false;
|
Options.useAssemblyFeatureQueries=false;
|
||||||
|
Options.legacyDirectMappedColumnCache=AuditDirectMappedColumnCache!=0;
|
||||||
|
Options.memoizeMcGradients=AuditMemoizeMcGradients!=0;
|
||||||
std::string Error;
|
std::string Error;
|
||||||
// Check density and mesh parity with hashes, then time uncached runtime generation.
|
// Check density and mesh parity with hashes, then time uncached runtime generation.
|
||||||
// Verifier la parite avec hashes, puis mesurer la generation sans cache de colonnes.
|
// Verifier la parite avec hashes, puis mesurer la generation sans cache de colonnes.
|
||||||
@@ -16256,7 +16272,9 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
DensitySamples.Add(static_cast<double>(Candidate->densitySampleCount));
|
DensitySamples.Add(static_cast<double>(Candidate->densitySampleCount));
|
||||||
UnknownBoxes.Add(static_cast<double>(Diagnostics.unknownBoxes));
|
UnknownBoxes.Add(static_cast<double>(Diagnostics.unknownBoxes));
|
||||||
FieldCalls.Add(static_cast<double>(Diagnostics.fieldCalls));
|
FieldCalls.Add(static_cast<double>(Diagnostics.fieldCalls));
|
||||||
|
UnclampedFieldCalls.Add(static_cast<double>(Diagnostics.unclampedFieldCalls));
|
||||||
ColumnMisses.Add(static_cast<double>(Diagnostics.columnCacheMisses));
|
ColumnMisses.Add(static_cast<double>(Diagnostics.columnCacheMisses));
|
||||||
|
ColumnConflictMisses.Add(static_cast<double>(Diagnostics.columnCacheConflictMisses));
|
||||||
ClassificationTimes.Add(Diagnostics.classificationMilliseconds);
|
ClassificationTimes.Add(Diagnostics.classificationMilliseconds);
|
||||||
ClassificationExclusiveTimes.Add(Diagnostics.classificationExclusiveMilliseconds);
|
ClassificationExclusiveTimes.Add(Diagnostics.classificationExclusiveMilliseconds);
|
||||||
ColumnComputeTimes.Add(Diagnostics.columnComputeMilliseconds);
|
ColumnComputeTimes.Add(Diagnostics.columnComputeMilliseconds);
|
||||||
@@ -16301,13 +16319,66 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
*1000000.0/SampleCount);
|
*1000000.0/SampleCount);
|
||||||
ListNsPerSample.Add(Diagnostics.featureListMilliseconds*1000000.0/SampleCount);
|
ListNsPerSample.Add(Diagnostics.featureListMilliseconds*1000000.0/SampleCount);
|
||||||
}
|
}
|
||||||
|
if(bMcStageAudit)
|
||||||
|
{
|
||||||
|
// Profile a separate cold tile so the audit's ordinary timing rows keep the
|
||||||
|
// uninstrumented path and comparable clock scope.
|
||||||
|
// Profiler une tuile froide separee pour garder les mesures ordinaires sans instrumentation.
|
||||||
|
Options.profileMesherStages=true;
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
|
||||||
|
auto Profiled=World.generateTile(Key,Options,Error);
|
||||||
|
Options.profileMesherStages=false;
|
||||||
|
if(!Profiled)
|
||||||
|
{ OutError=TEXT("Cold MC stage profile tile generation failed."); return false; }
|
||||||
|
const auto& Mc=Profiled->diagnostics;
|
||||||
|
const double Total=Mc.marchingCubesExclusiveMilliseconds
|
||||||
|
+Mc.marchingCubesColumnComputeMilliseconds;
|
||||||
|
const double Grid=Mc.marchingCubesDensityGridMilliseconds;
|
||||||
|
const double GridExclusive=FMath::Max(0.0,Grid
|
||||||
|
-Mc.marchingCubesColumnComputeMilliseconds);
|
||||||
|
const double Cell=Mc.marchingCubesCellClassificationMilliseconds;
|
||||||
|
const double Normal=Mc.marchingCubesGradientNormalsMilliseconds;
|
||||||
|
const double Interpolation=Mc.marchingCubesEdgeInterpolationMilliseconds;
|
||||||
|
const double Stream=Mc.marchingCubesVertexDedupStreamMilliseconds;
|
||||||
|
const double Other=Mc.marchingCubesOtherMilliseconds;
|
||||||
|
const double Residual=FMath::Max(0.0,Mc.marchingCubesExclusiveMilliseconds
|
||||||
|
-GridExclusive-Cell-Normal-Interpolation-Stream);
|
||||||
|
McProfileTotalTimes.Add(Total);
|
||||||
|
McProfileGridTimes.Add(Grid);
|
||||||
|
McProfileGridExclusiveTimes.Add(GridExclusive);
|
||||||
|
McProfileCellTimes.Add(Cell);
|
||||||
|
McProfileNormalTimes.Add(Normal);
|
||||||
|
McProfileInterpolationTimes.Add(Interpolation);
|
||||||
|
McProfileStreamTimes.Add(Stream);
|
||||||
|
McProfileOtherTimes.Add(Other);
|
||||||
|
McProfileResidualTimes.Add(Residual);
|
||||||
|
McProfileGradientFallbackCalls.Add(static_cast<double>(
|
||||||
|
Mc.marchingCubesGradientFallbackDensityCalls));
|
||||||
|
McProfileEdgeMidpointCalls.Add(static_cast<double>(
|
||||||
|
Mc.marchingCubesEdgeMidpointDensityCalls));
|
||||||
|
McProfileGradientMemoHits.Add(static_cast<double>(
|
||||||
|
Mc.marchingCubesGradientMemoHits));
|
||||||
|
const double ProfileSamples=static_cast<double>(Profiled->densitySampleCount);
|
||||||
|
if(ProfileSamples>0.0)
|
||||||
|
{
|
||||||
|
McProfileGridNsPerSample.Add(Grid*1000000.0/ProfileSamples);
|
||||||
|
McProfileGridExclusiveNsPerSample.Add(GridExclusive*1000000.0/ProfileSamples);
|
||||||
|
McProfileCellNsPerSample.Add(Cell*1000000.0/ProfileSamples);
|
||||||
|
McProfileNormalNsPerSample.Add(Normal*1000000.0/ProfileSamples);
|
||||||
|
McProfileInterpolationNsPerSample.Add(Interpolation*1000000.0/ProfileSamples);
|
||||||
|
McProfileStreamNsPerSample.Add(Stream*1000000.0/ProfileSamples);
|
||||||
|
McProfileOtherNsPerSample.Add(Other*1000000.0/ProfileSamples);
|
||||||
|
McProfileResidualNsPerSample.Add(Residual*1000000.0/ProfileSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const auto P95=[](TArray<double> Values)
|
const auto P95=[](TArray<double> Values)
|
||||||
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
||||||
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
||||||
UE_LOG(LogTemp,Display,
|
UE_LOG(LogTemp,Display,
|
||||||
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_wall_p95_ms=%.5f classification_exclusive_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_column_compute_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_column_compute_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_column_compute_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f samples_p95=%.0f classification_ns_per_sample_p95=%.2f column_ns_per_sample_p95=%.2f mc_column_ns_per_sample_p95=%.2f mc_ns_per_sample_p95=%.2f ground_column_ns_per_sample_p95=%.2f ground_ns_per_sample_p95=%.2f cap_column_ns_per_sample_p95=%.2f cap_ns_per_sample_p95=%.2f assembly_ns_per_sample_p95=%.2f feature_list_ns_per_sample_p95=%.2f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f column_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"),
|
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d column_cache_direct_mapped=%d gradient_memoization=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_wall_p95_ms=%.5f classification_exclusive_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_column_compute_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_column_compute_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_column_compute_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f samples_p95=%.0f classification_ns_per_sample_p95=%.2f column_ns_per_sample_p95=%.2f mc_column_ns_per_sample_p95=%.2f mc_ns_per_sample_p95=%.2f ground_column_ns_per_sample_p95=%.2f ground_ns_per_sample_p95=%.2f cap_column_ns_per_sample_p95=%.2f cap_ns_per_sample_p95=%.2f assembly_ns_per_sample_p95=%.2f feature_list_ns_per_sample_p95=%.2f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f unclamped_field_calls_p95=%.0f column_misses_p95=%.0f column_conflict_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"),
|
||||||
Label,LOD,FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
|
Label,LOD,AuditDirectMappedColumnCache!=0?1:0,AuditMemoizeMcGradients!=0?1:0,
|
||||||
|
FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
|
||||||
P95(ClassificationTimes),P95(ClassificationExclusiveTimes),P95(ColumnComputeTimes),
|
P95(ClassificationTimes),P95(ClassificationExclusiveTimes),P95(ColumnComputeTimes),
|
||||||
P95(ColumnEvalExclusiveTimes),P95(MarchingColumnTimes),P95(MarchingExclusiveTimes),
|
P95(ColumnEvalExclusiveTimes),P95(MarchingColumnTimes),P95(MarchingExclusiveTimes),
|
||||||
P95(GroundColumnTimes),P95(GroundRefineExclusiveTimes),P95(CapColumnTimes),
|
P95(GroundColumnTimes),P95(GroundRefineExclusiveTimes),P95(CapColumnTimes),
|
||||||
@@ -16317,9 +16388,24 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
P95(MarchingNsPerSample),P95(GroundColumnNsPerSample),P95(GroundNsPerSample),
|
P95(MarchingNsPerSample),P95(GroundColumnNsPerSample),P95(GroundNsPerSample),
|
||||||
P95(CapColumnNsPerSample),P95(CapNsPerSample),P95(AssemblyNsPerSample),P95(ListNsPerSample),
|
P95(CapColumnNsPerSample),P95(CapNsPerSample),P95(AssemblyNsPerSample),P95(ListNsPerSample),
|
||||||
P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls),
|
P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls),
|
||||||
P95(ColumnMisses),P95(StratumShellTimes),P95(StratumFieldCalls),
|
P95(UnclampedFieldCalls),
|
||||||
|
P95(ColumnMisses),P95(ColumnConflictMisses),P95(StratumShellTimes),P95(StratumFieldCalls),
|
||||||
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
|
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
|
||||||
P95(GroundTriangles),P95(GroundRemoved),P95(CapTriangles));
|
P95(GroundTriangles),P95(GroundRemoved),P95(CapTriangles));
|
||||||
|
if(bMcStageAudit)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp,Display,
|
||||||
|
TEXT("[VoxelForgeMCStageCost] label=%s lod=%d tiles=%d sampling=row_total_and_1_of_8_cells total_p95_ms=%.5f density_grid_inclusive_p95_ms=%.5f density_grid_ex_column_p95_ms=%.5f cell_classification_p95_ms=%.5f gradient_normals_p95_ms=%.5f edge_interpolation_p95_ms=%.5f vertex_dedup_stream_p95_ms=%.5f setup_finalization_p95_ms=%.5f unassigned_residual_p95_ms=%.5f grid_ns_per_sample_p95=%.2f grid_ex_column_ns_per_sample_p95=%.2f cell_ns_per_sample_p95=%.2f normals_ns_per_sample_p95=%.2f interpolation_ns_per_sample_p95=%.2f dedup_stream_ns_per_sample_p95=%.2f setup_finalization_ns_per_sample_p95=%.2f residual_ns_per_sample_p95=%.2f gradient_fallback_density_calls_p95=%.0f edge_midpoint_density_probes_p95=%.0f gradient_memo_hits_p95=%.0f"),
|
||||||
|
Label,LOD,McProfileTotalTimes.Num(),P95(McProfileTotalTimes),P95(McProfileGridTimes),
|
||||||
|
P95(McProfileGridExclusiveTimes),P95(McProfileCellTimes),P95(McProfileNormalTimes),
|
||||||
|
P95(McProfileInterpolationTimes),P95(McProfileStreamTimes),P95(McProfileOtherTimes),
|
||||||
|
P95(McProfileResidualTimes),P95(McProfileGridNsPerSample),
|
||||||
|
P95(McProfileGridExclusiveNsPerSample),P95(McProfileCellNsPerSample),
|
||||||
|
P95(McProfileNormalNsPerSample),P95(McProfileInterpolationNsPerSample),
|
||||||
|
P95(McProfileStreamNsPerSample),P95(McProfileOtherNsPerSample),
|
||||||
|
P95(McProfileResidualNsPerSample),P95(McProfileGradientFallbackCalls),
|
||||||
|
P95(McProfileEdgeMidpointCalls),P95(McProfileGradientMemoHits));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(Assembly!=nullptr&&!Assembly->plan.strates.empty())
|
if(Assembly!=nullptr&&!Assembly->plan.strates.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ namespace VoxelForgeCore
|
|||||||
std::uint64_t unclampedFieldCalls = 0;
|
std::uint64_t unclampedFieldCalls = 0;
|
||||||
std::uint64_t columnCacheHits = 0;
|
std::uint64_t columnCacheHits = 0;
|
||||||
std::uint64_t columnCacheMisses = 0;
|
std::uint64_t columnCacheMisses = 0;
|
||||||
|
std::uint64_t columnCacheConflictMisses = 0;
|
||||||
double classificationMilliseconds = 0.0;
|
double classificationMilliseconds = 0.0;
|
||||||
double columnComputeMilliseconds = 0.0;
|
double columnComputeMilliseconds = 0.0;
|
||||||
double columnEvaluationExclusiveMilliseconds = 0.0;
|
double columnEvaluationExclusiveMilliseconds = 0.0;
|
||||||
@@ -131,6 +132,15 @@ namespace VoxelForgeCore
|
|||||||
double capRefinementColumnComputeMilliseconds = 0.0;
|
double capRefinementColumnComputeMilliseconds = 0.0;
|
||||||
double classificationExclusiveMilliseconds = 0.0;
|
double classificationExclusiveMilliseconds = 0.0;
|
||||||
double marchingCubesExclusiveMilliseconds = 0.0;
|
double marchingCubesExclusiveMilliseconds = 0.0;
|
||||||
|
double marchingCubesDensityGridMilliseconds = 0.0;
|
||||||
|
double marchingCubesCellClassificationMilliseconds = 0.0;
|
||||||
|
double marchingCubesGradientNormalsMilliseconds = 0.0;
|
||||||
|
double marchingCubesEdgeInterpolationMilliseconds = 0.0;
|
||||||
|
double marchingCubesVertexDedupStreamMilliseconds = 0.0;
|
||||||
|
double marchingCubesOtherMilliseconds = 0.0;
|
||||||
|
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
|
||||||
|
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
|
||||||
|
std::uint64_t marchingCubesGradientMemoHits = 0;
|
||||||
double groundRefinementExclusiveMilliseconds = 0.0;
|
double groundRefinementExclusiveMilliseconds = 0.0;
|
||||||
double capRefinementExclusiveMilliseconds = 0.0;
|
double capRefinementExclusiveMilliseconds = 0.0;
|
||||||
double meshAssemblyMilliseconds = 0.0;
|
double meshAssemblyMilliseconds = 0.0;
|
||||||
@@ -639,6 +649,7 @@ namespace VoxelForgeCore
|
|||||||
std::uint64_t pointCalls = 0;
|
std::uint64_t pointCalls = 0;
|
||||||
std::uint64_t columnHits = 0;
|
std::uint64_t columnHits = 0;
|
||||||
std::uint64_t columnMisses = 0;
|
std::uint64_t columnMisses = 0;
|
||||||
|
std::uint64_t columnConflictMisses = 0;
|
||||||
double columnComputeMilliseconds = 0.0;
|
double columnComputeMilliseconds = 0.0;
|
||||||
bool captureCoordinates = false;
|
bool captureCoordinates = false;
|
||||||
bool coordinateCaptureComplete = true;
|
bool coordinateCaptureComplete = true;
|
||||||
@@ -693,6 +704,7 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
|
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
|
||||||
static void resetThreadColumnCacheStats(bool captureCoordinates = false,
|
static void resetThreadColumnCacheStats(bool captureCoordinates = false,
|
||||||
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
|
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
|
||||||
|
static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept;
|
||||||
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
|
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
|
||||||
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
||||||
std::int64_t minimumYSubvoxels,
|
std::int64_t minimumYSubvoxels,
|
||||||
@@ -1021,6 +1033,15 @@ namespace VoxelForgeCore
|
|||||||
// Opt-in diagnostic only; records XY columns evaluated by this LOD8 tile.
|
// Opt-in diagnostic only; records XY columns evaluated by this LOD8 tile.
|
||||||
// Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8.
|
// Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8.
|
||||||
bool captureColumnCoordinates = false;
|
bool captureColumnCoordinates = false;
|
||||||
|
// Separate cold pass only; samples the MC inner stages without changing output.
|
||||||
|
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
|
||||||
|
bool profileMesherStages = false;
|
||||||
|
// Negative control for the shared XY-column cache; 1 restores the old direct-map indexing.
|
||||||
|
// Controle negatif du cache XY partage; 1 restaure l'ancien indexage direct.
|
||||||
|
bool legacyDirectMappedColumnCache = false;
|
||||||
|
// Diagnostic/performance control; memoizes exact MC lattice gradients at LOD0.
|
||||||
|
// Controle de mesure/performance; memorise les gradients MC exacts au LOD0.
|
||||||
|
bool memoizeMcGradients = true;
|
||||||
// LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9.
|
// LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9.
|
||||||
// Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9.
|
// Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9.
|
||||||
std::int32_t lod8HorizonInteriorStepVoxels = 0;
|
std::int32_t lod8HorizonInteriorStepVoxels = 0;
|
||||||
|
|||||||
@@ -1325,6 +1325,11 @@ namespace VoxelForgeCore
|
|||||||
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
|
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
|
||||||
std::string& error) const
|
std::string& error) const
|
||||||
{
|
{
|
||||||
|
// Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their
|
||||||
|
// single-probe path and use associativity for the coarse coordinate families that collide.
|
||||||
|
// Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers.
|
||||||
|
Heightfield2D::setThreadColumnCacheLegacyDirectMapped(
|
||||||
|
options.legacyDirectMappedColumnCache||key.lod<=2);
|
||||||
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
||||||
{ error="world must be compiled before generating tiles";return std::nullopt; }
|
{ error="world must be compiled before generating tiles";return std::nullopt; }
|
||||||
const auto& world=*impl_->owner;
|
const auto& world=*impl_->owner;
|
||||||
@@ -1342,6 +1347,7 @@ namespace VoxelForgeCore
|
|||||||
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
|
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
|
||||||
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
||||||
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
||||||
|
mesher.bStandaloneEnableGradientMemoization=options.memoizeMcGradients;
|
||||||
mesher.bGenerateSkirts=world.bGenerateSkirts;
|
mesher.bGenerateSkirts=world.bGenerateSkirts;
|
||||||
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
|
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
|
||||||
*static_cast<float>(world.OwnerVoxelSizeMicrons)
|
*static_cast<float>(world.OwnerVoxelSizeMicrons)
|
||||||
@@ -1436,6 +1442,7 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int64 sampleCount=0;std::vector<float> densityValues;
|
int64 sampleCount=0;std::vector<float> densityValues;
|
||||||
|
FVoxelMarchingCubesStageTimings McStageTimings;
|
||||||
TileMesh out;out.key=key;
|
TileMesh out;out.key=key;
|
||||||
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
|
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
|
||||||
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
|
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
|
||||||
@@ -1523,6 +1530,7 @@ namespace VoxelForgeCore
|
|||||||
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
|
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
|
||||||
out.diagnostics.columnCacheHits=Columns.columnHits;
|
out.diagnostics.columnCacheHits=Columns.columnHits;
|
||||||
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
||||||
|
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
|
||||||
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
||||||
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
||||||
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
||||||
@@ -1670,7 +1678,8 @@ namespace VoxelForgeCore
|
|||||||
const auto MarchingCubesStart=std::chrono::steady_clock::now();
|
const auto MarchingCubesStart=std::chrono::steady_clock::now();
|
||||||
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
||||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
||||||
|
options.profileMesherStages?&McStageTimings:nullptr);
|
||||||
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
|
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-MarchingCubesStart).count();
|
std::chrono::steady_clock::now()-MarchingCubesStart).count();
|
||||||
const double ColumnMillisecondsAfterMeshing=
|
const double ColumnMillisecondsAfterMeshing=
|
||||||
@@ -1679,6 +1688,24 @@ namespace VoxelForgeCore
|
|||||||
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
|
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
|
||||||
out.diagnostics.marchingCubesColumnComputeMilliseconds=std::max(0.0,
|
out.diagnostics.marchingCubesColumnComputeMilliseconds=std::max(0.0,
|
||||||
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
|
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
|
||||||
|
if(options.profileMesherStages)
|
||||||
|
{
|
||||||
|
out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesCellClassificationMilliseconds=
|
||||||
|
McStageTimings.CellClassificationMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesGradientNormalsMilliseconds=
|
||||||
|
McStageTimings.GradientNormalsMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesEdgeInterpolationMilliseconds=
|
||||||
|
McStageTimings.EdgeInterpolationMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesVertexDedupStreamMilliseconds=
|
||||||
|
McStageTimings.VertexDedupStreamMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesOtherMilliseconds=McStageTimings.OtherMilliseconds;
|
||||||
|
out.diagnostics.marchingCubesGradientFallbackDensityCalls=
|
||||||
|
McStageTimings.GradientFallbackDensityCalls;
|
||||||
|
out.diagnostics.marchingCubesEdgeMidpointDensityCalls=
|
||||||
|
McStageTimings.EdgeMidpointDensityCalls;
|
||||||
|
out.diagnostics.marchingCubesGradientMemoHits=McStageTimings.GradientMemoHits;
|
||||||
|
}
|
||||||
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-MeshingStart).count();
|
std::chrono::steady_clock::now()-MeshingStart).count();
|
||||||
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
|
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
|
||||||
@@ -1765,6 +1792,7 @@ namespace VoxelForgeCore
|
|||||||
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
|
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
|
||||||
out.diagnostics.columnCacheHits=Columns.columnHits;
|
out.diagnostics.columnCacheHits=Columns.columnHits;
|
||||||
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
||||||
|
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
|
||||||
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
||||||
|
|||||||
@@ -1361,11 +1361,35 @@ namespace world_assembly_detail
|
|||||||
std::int64_t x = 0, y = 0;
|
std::int64_t x = 0, y = 0;
|
||||||
HeightfieldPointSample column{};
|
HeightfieldPointSample column{};
|
||||||
};
|
};
|
||||||
// Coarse LOD tiles revisit a wider set of gradient-sample columns than the full-resolution
|
// Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that
|
||||||
// band. Keep those columns resident for a complete worker tile so the 3D mesher does not
|
// deliberately share low hash bits at coarse LODs.
|
||||||
// repeat the procedural biome evaluation for every Z sample.
|
// Chaque cache worker garde 65 536 entrees; quatre voies absorbent les coordonnees grossieres
|
||||||
|
// qui partagent les bits faibles du hash.
|
||||||
|
constexpr std::size_t HeightfieldColumnCacheWays=4;
|
||||||
|
constexpr std::size_t HeightfieldColumnCacheSetCount=65536/HeightfieldColumnCacheWays;
|
||||||
inline thread_local std::array<HeightfieldColumnCacheEntry, 65536> HeightfieldColumnCache{};
|
inline thread_local std::array<HeightfieldColumnCacheEntry, 65536> HeightfieldColumnCache{};
|
||||||
|
inline thread_local std::array<std::uint8_t, HeightfieldColumnCacheSetCount>
|
||||||
|
HeightfieldColumnCacheNextWay{};
|
||||||
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
|
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
|
||||||
|
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
|
||||||
|
|
||||||
|
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
|
||||||
|
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y) noexcept
|
||||||
|
{
|
||||||
|
// Avalanche every coordinate bit before masking. The old direct hash kept too little
|
||||||
|
// entropy from widely spaced coarse LOD coordinates in its low 16 bits.
|
||||||
|
// Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de
|
||||||
|
// l'entropie sur les coordonnees espacees des LOD grossiers.
|
||||||
|
std::uint64_t mixed=static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull
|
||||||
|
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,29)
|
||||||
|
^recipe.contentHash^std::rotl(plan.contentHash,17);
|
||||||
|
mixed^=mixed>>30;
|
||||||
|
mixed*=0xBF58476D1CE4E5B9ull;
|
||||||
|
mixed^=mixed>>27;
|
||||||
|
mixed*=0x94D049BB133111EBull;
|
||||||
|
mixed^=mixed>>31;
|
||||||
|
return static_cast<std::size_t>(mixed)&(HeightfieldColumnCacheSetCount-1);
|
||||||
|
}
|
||||||
|
|
||||||
inline HeightfieldPointSample EvaluateColumn(const WorldAssemblyRecipeV1& recipe,
|
inline HeightfieldPointSample EvaluateColumn(const WorldAssemblyRecipeV1& recipe,
|
||||||
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
|
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
|
||||||
@@ -1480,15 +1504,48 @@ namespace world_assembly_detail
|
|||||||
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
|
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
|
||||||
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
|
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
|
||||||
{
|
{
|
||||||
|
HeightfieldColumnCacheEntry* replacement=nullptr;
|
||||||
|
if(HeightfieldColumnCacheLegacyDirectMapped)
|
||||||
|
{
|
||||||
|
// Preserve S13's exact direct-map index as an in-process negative control.
|
||||||
|
// Conserver l'index direct exact de S13 comme controle negatif en cours d'execution.
|
||||||
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
|
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
|
||||||
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
|
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
|
||||||
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
|
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
|
||||||
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
|
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
|
||||||
auto& entry=HeightfieldColumnCache[static_cast<std::size_t>(mixed)&
|
replacement=&HeightfieldColumnCache[static_cast<std::size_t>(mixed)
|
||||||
(HeightfieldColumnCache.size()-1)];
|
&(HeightfieldColumnCache.size()-1)];
|
||||||
if(entry.recipe==&recipe&&entry.plan==&plan&&entry.recipeHash==recipe.contentHash
|
if(replacement->recipe==&recipe&&replacement->plan==&plan
|
||||||
&&entry.planHash==plan.contentHash&&entry.x==x&&entry.y==y)
|
&&replacement->recipeHash==recipe.contentHash
|
||||||
{++HeightfieldColumnStats.columnHits;return entry.column;}
|
&&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y)
|
||||||
|
{++HeightfieldColumnStats.columnHits;return replacement->column;}
|
||||||
|
if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const std::size_t set=HeightfieldColumnCacheSet(recipe,plan,x,y);
|
||||||
|
const std::size_t first=set*HeightfieldColumnCacheWays;
|
||||||
|
std::size_t replacementWay=HeightfieldColumnCacheWays;
|
||||||
|
for(std::size_t way=0;way<HeightfieldColumnCacheWays;++way)
|
||||||
|
{
|
||||||
|
auto& candidate=HeightfieldColumnCache[first+way];
|
||||||
|
if(candidate.recipe==&recipe&&candidate.plan==&plan
|
||||||
|
&&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash
|
||||||
|
&&candidate.x==x&&candidate.y==y)
|
||||||
|
{++HeightfieldColumnStats.columnHits;return candidate.column;}
|
||||||
|
if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays)
|
||||||
|
replacementWay=way;
|
||||||
|
}
|
||||||
|
if(replacementWay==HeightfieldColumnCacheWays)
|
||||||
|
{
|
||||||
|
++HeightfieldColumnStats.columnConflictMisses;
|
||||||
|
replacementWay=HeightfieldColumnCacheNextWay[set];
|
||||||
|
}
|
||||||
|
HeightfieldColumnCacheNextWay[set]=static_cast<std::uint8_t>(
|
||||||
|
(replacementWay+1)%HeightfieldColumnCacheWays);
|
||||||
|
replacement=&HeightfieldColumnCache[first+replacementWay];
|
||||||
|
}
|
||||||
|
auto& entry=*replacement;
|
||||||
const auto start=std::chrono::steady_clock::now();
|
const auto start=std::chrono::steady_clock::now();
|
||||||
HeightfieldPointSample column=EvaluateColumn(recipe,plan,x,y);
|
HeightfieldPointSample column=EvaluateColumn(recipe,plan,x,y);
|
||||||
HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration<double,std::milli>(
|
HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration<double,std::milli>(
|
||||||
@@ -1977,19 +2034,27 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
|
|||||||
{
|
{
|
||||||
// Diagnostic probes may clear old tile entries before comparing isolated sample grids.
|
// Diagnostic probes may clear old tile entries before comparing isolated sample grids.
|
||||||
// Les sondes de mesure peuvent vider les entrees d'une tuile avant de comparer deux grilles.
|
// Les sondes de mesure peuvent vider les entrees d'une tuile avant de comparer deux grilles.
|
||||||
if(clearCache)world_assembly_detail::HeightfieldColumnCache.fill(
|
if(clearCache)
|
||||||
|
{
|
||||||
|
world_assembly_detail::HeightfieldColumnCache.fill(
|
||||||
world_assembly_detail::HeightfieldColumnCacheEntry{});
|
world_assembly_detail::HeightfieldColumnCacheEntry{});
|
||||||
|
world_assembly_detail::HeightfieldColumnCacheNextWay.fill(0);
|
||||||
|
}
|
||||||
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
|
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
|
||||||
world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;
|
world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;
|
||||||
Stats.pointCalls=0;
|
Stats.pointCalls=0;
|
||||||
Stats.columnHits=0;
|
Stats.columnHits=0;
|
||||||
Stats.columnMisses=0;
|
Stats.columnMisses=0;
|
||||||
|
Stats.columnConflictMisses=0;
|
||||||
Stats.columnComputeMilliseconds=0.0;
|
Stats.columnComputeMilliseconds=0.0;
|
||||||
Stats.captureCoordinates=captureCoordinates;
|
Stats.captureCoordinates=captureCoordinates;
|
||||||
Stats.coordinateCaptureComplete=true;
|
Stats.coordinateCaptureComplete=true;
|
||||||
Stats.evaluatedCoordinates.clear();
|
Stats.evaluatedCoordinates.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept
|
||||||
|
{ world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; }
|
||||||
|
|
||||||
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
|
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
|
||||||
{ return world_assembly_detail::HeightfieldColumnStats; }
|
{ return world_assembly_detail::HeightfieldColumnStats; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
# World Assembler S14 — Round 18
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Shipped two output-preserving changes justified by the measurements:
|
||||||
|
|
||||||
|
1. Keep the 65,536-entry XY column cache worker-local. Use the old direct-map route at LOD0–2,
|
||||||
|
where conflict counts are low, and deterministic four-way sets at LOD3–8. The LOD4 miss spike
|
||||||
|
falls from 8,224 to 1,437, with conflict misses falling from 7,407 to zero.
|
||||||
|
2. Memoize exact step-1 ShapeWorld MC grid gradients per worker and tile. The MC two-pass loop,
|
||||||
|
density grid, halo, central differences, and output values stay intact. LOD0 MC-exclusive cost
|
||||||
|
falls from 241.87 to 201.48 ns/sample in the corrected-source audit; the original S13 figure was
|
||||||
|
243.40 ns/sample.
|
||||||
|
|
||||||
|
No shared XY page was built: cross-worker repeats show opportunity, but no measured page-cache A/B
|
||||||
|
shows a net cold-path win after the cache fix. The primary cold trace used 21.722 worker-seconds against
|
||||||
|
the 28.8 s target. Output hashes remain unchanged, so the recipe stays `MarchingCubesV1`; no recipe
|
||||||
|
bump or matched captures were required.
|
||||||
|
|
||||||
|
## 1. What the 243 ns/sample contains
|
||||||
|
|
||||||
|
The S13 “MC-exclusive” timer excludes MC column evaluation; it does **not** mean “triangle loop
|
||||||
|
only.” For LOD0, the corrected-source eight-tile audit measured 8.638 ms MC-exclusive plus 3.852 ms
|
||||||
|
MC column evaluation at the tuned setting. Each LOD0 tile has 35³ = 42,875 density samples. The
|
||||||
|
exclusive result is 201.48 ns/sample. The cell loop itself is only one part of it.
|
||||||
|
|
||||||
|
In the sampled stage audit, tuned LOD0 p95 was: density grid 8.971 ms (7.071 ms excluding column
|
||||||
|
evaluation), cell classification 0.143 ms, grid-gradient normals 1.036 ms, edge interpolation
|
||||||
|
0.080 ms, vertex dedup/stream 0.545 ms, and about 2.168 ms of unassigned remainder. The stage
|
||||||
|
profiler samples one in eight cells; these are independently calculated p95 values and do not add
|
||||||
|
to a measured total. Its `Other`/setup-finalization and unassigned-residual fields are computed
|
||||||
|
remainder buckets from different inclusive/exclusive timers, not separately isolated setup or
|
||||||
|
allocator timers.
|
||||||
|
|
||||||
|
The 164.93 ns/sample grid remainder is not a standalone `height - z` timer. Every lattice sample
|
||||||
|
still enters the exact `Heightfield2D::point` path: fixed-coordinate conversion, recipe/plan lookup,
|
||||||
|
the worker-local XY cache probe, the Z-dependent ground/cap composition, and any applicable exact
|
||||||
|
feature/edit checks. Column generation itself is counted separately: LOD0 records 44,100 field
|
||||||
|
calls, 2,244 cache misses (about 94.9% hits), and 89.89 ns/sample of column computation. The audit
|
||||||
|
does not split cache-hit lookup from Z composition and call overhead. Those Z/feature checks cannot
|
||||||
|
all move to one XY-column operation. The MC gradient work can be shared per lattice point and is
|
||||||
|
now memoized exactly; edge interpolation is already limited to crossed edges and the vertex map
|
||||||
|
deduplicates shared edge vertices. Material and allocation have no separate timers; the mesher's
|
||||||
|
thread-local grids and dedup map retain capacity between tiles.
|
||||||
|
|
||||||
|
The MC route still uses the protected two-pass loop: pass one rejects empty cells from the eight
|
||||||
|
cached corners; pass two computes positions, gradients, and emitted triangles only for surface
|
||||||
|
cells. Classification is per cell; exact gradients are reused per lattice point; interpolation and
|
||||||
|
deduplication are limited to crossed edges and their shared vertex keys. The stage measurements
|
||||||
|
place classification in the first pass and interpolation plus dedup/stream in the second.
|
||||||
|
Material/ground-cap grouping and vertex attributes are folded into
|
||||||
|
vertex creation and streaming; material work has no separate timer. The 35 edge-midpoint density
|
||||||
|
probes are negligible beside the gradient fallback calls.
|
||||||
|
|
||||||
|
| LOD | Density grid incl. | Grid excl. columns | Cell classification | Gradient normals | Edge interpolation | Dedup/stream | Unassigned residual |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 210.11→209.22 | 168.04→164.93 | 3.28→3.32 | 65.10→24.16 | 1.91→1.86 | 12.96→12.72 | 50.40→50.56 |
|
||||||
|
| 1 | 163.74→164.91 | 163.74→164.91 | 3.82→3.55 | 4.20→4.01 | 2.14→2.10 | 13.47→13.56 | 54.77→55.06 |
|
||||||
|
| 2 | 390.55→395.47 | 0.00→0.00 | 2.84→2.89 | 11.62→11.51 | 6.14→5.47 | 68.70→69.15 | 277.12→306.20 |
|
||||||
|
| 3 | 777.21→437.15 | 120.00→131.99 | 3.15→2.97 | 15.37→15.37 | 6.77→6.25 | 604.40→407.99 | 46.97→110.89 |
|
||||||
|
| 4 | 4,603.98→425.25 | 210.50→178.79 | 2.92→3.32 | 10.15→10.96 | 5.60→5.48 | 565.21→309.90 | 95.44→109.50 |
|
||||||
|
| 5 | 385.90→434.09 | 135.75→155.07 | 3.02→3.48 | 5.13→7.00 | 1.78→3.50 | 155.02→38.26 | 96.38→137.65 |
|
||||||
|
| 6 | 366.38→371.92 | 88.76→101.46 | 2.97→3.09 | 18.66→18.11 | 9.72→8.39 | 431.20→185.07 | 124.84→171.70 |
|
||||||
|
| 7 | 361.29→447.14 | 115.03→102.64 | 3.13→2.99 | 20.47→17.75 | 9.69→8.84 | 443.10→239.22 | 99.92→254.21 |
|
||||||
|
| 8 | 367.98→377.58 | 95.55→96.05 | 4.05→3.05 | 14.90→19.08 | 7.48→7.92 | 513.15→213.89 | 159.02→234.19 |
|
||||||
|
|
||||||
|
All stage values are p95 **ns/sample**, before→after. “Before” is this build with the direct-map
|
||||||
|
cache and gradient memo off (`1/0`); “after” is this build's tuned default (`0/1`). Each side measured eight
|
||||||
|
tiles per LOD, with `hashes_identical=1`. LOD0/1 use 42,875 samples; LOD2–8 use 6,859. Large
|
||||||
|
variation in dedup/stream and residual is content/triangle-count and sampling noise, not a new
|
||||||
|
approximation. The last column is the commandlet's unassigned-residual bucket; the separately
|
||||||
|
reported setup-finalization field is also computed as a remainder and is not an allocator timer.
|
||||||
|
These warmed-route stage probes explain the hot path; the cold production timings are the
|
||||||
|
fresh-process trace in section 4.
|
||||||
|
|
||||||
|
| LOD | Candidate tile p95 ms | MC column p95 ms | MC-exclusive p95 ms / ns per sample | Column misses / conflicts |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 14.079→12.492 | 3.777→3.852 | 10.370→8.638 / 241.87→201.48 | 2,244/33→2,244/33 |
|
||||||
|
| 1 | 9.824→9.842 | 4.187→4.197 | 7.183→7.307 / 167.53→170.43 | 2,289/28→2,289/28 |
|
||||||
|
| 2 | 4.715→4.709 | 3.080→3.066 | 1.606→1.616 / 281.61→282.71 | 681/4→681/4 |
|
||||||
|
| 3 | 6.945→5.393 | 4.016→2.477 | 2.917→2.903 / 425.28→423.27 | 1,634/1,119→574/0 |
|
||||||
|
| 4 | 36.854→9.227 | 30.216→2.257 | 2.970→2.946 / 432.93→429.54 | 8,224/7,407→1,437/0 |
|
||||||
|
| 5 | 11.055→9.129 | 3.029→2.278 | 1.279→1.269 / 186.43→184.98 | 1,916/1,476→1,351/0 |
|
||||||
|
| 6 | 30.703→26.872 | 5.148→3.841 | 2.142→2.000 / 312.23→291.57 | 6,403/2,094→5,570/1 |
|
||||||
|
| 7 | 20.831→15.390 | 4.725→3.460 | 2.411→2.689 / 351.52→391.97 | 3,472/2,926→1,822/0 |
|
||||||
|
| 8 | 14.948→9.829 | 5.715→3.358 | 2.534→2.770 / 369.38→403.82 | 2,708/2,215→1,162/0 |
|
||||||
|
|
||||||
|
For LOD0 specifically, the S13 audit row was 14.160 ms candidate, 3.708 ms column evaluation,
|
||||||
|
10.436 ms MC-exclusive, and 243.40 ns/sample. The tuned corrected-source 35³ route is 12.492 ms
|
||||||
|
candidate and 8.638 ms MC-exclusive. Subtracting the measured density-grid p95 from candidate p95
|
||||||
|
leaves about 3.52 ms for the remaining work (about 82 ns/sample); the independently sampled stage
|
||||||
|
subtotal is about 3.97 ms (about 93 ns/sample). This is the measured in-world estimate for the
|
||||||
|
MC/classification/normal/interpolation/streaming work after density-grid evaluation, not a direct
|
||||||
|
pre-filled-grid benchmark. Treat the p95 subtraction as an estimate, not an additive percentile.
|
||||||
|
|
||||||
|
Gradient memoization reduces LOD0 fallback density probes from 11,474 to 3,018 per sampled p95
|
||||||
|
(−73.7%) and normal cost from 65.10 to 24.16 ns/sample. It records 5,610 memo hits. The memo stores
|
||||||
|
the exact central-difference vector at each grid lattice point, is worker-local, and resets its
|
||||||
|
validity bytes per tile. The existing grid and per-worker capacities are reused. No material
|
||||||
|
approximation or altered gradient arithmetic is involved.
|
||||||
|
|
||||||
|
## 2. Why LOD4 had the cache spike
|
||||||
|
|
||||||
|
The old cache already holds 65,536 `HeightfieldPointSample`s per worker, but its direct-map index
|
||||||
|
uses pointer identity and coordinate bits. Coarse LOD coordinates share sparse low-bit patterns;
|
||||||
|
the LOD4 16-voxel cap grid interleaves different XY probes with the MC grid and its halo/normal
|
||||||
|
lookups. The measured direct-map conflict count (7,407 of 8,224 misses) shows this is mostly
|
||||||
|
eviction, rather than 8,224 necessary unique columns. The audit did not tag every miss by probe
|
||||||
|
source, so the individual halo-versus-cap contribution is not claimed.
|
||||||
|
|
||||||
|
The new default keeps the same total entries and exact key verification. Its avalanched coordinate
|
||||||
|
hash selects one of 16,384 four-way sets; deterministic round-robin replacement only changes
|
||||||
|
residency, never values. Exact recipe/plan identities, content hashes, and XY coordinates are
|
||||||
|
checked on every hit, so fill order cannot affect output. LOD0–2 keep the fast original path because
|
||||||
|
they have only 33, 28, and 4 direct-map conflict misses in the audit. LOD6 remains at 5,570 misses
|
||||||
|
but only one conflict, showing that its remaining cost comes largely from its larger real XY
|
||||||
|
footprint and connector-shell work, not direct-map collision.
|
||||||
|
|
||||||
|
`voxel.CoreColumnCacheDirectMapped=1` is the direct-map negative control and was read back as `1`
|
||||||
|
in the trace. With gradient memo still on, the initial set took 6.624 s and 43.493 worker-seconds;
|
||||||
|
LOD4 used 11.071 worker-seconds. The tuned default took 4.238 s and 21.722 worker-seconds, with
|
||||||
|
LOD4 at 1.862 worker-seconds. The isolated audit also reports the per-LOD miss deltas above.
|
||||||
|
|
||||||
|
The separate memo negative control was `voxel.CoreMCGradientMemoization=0` with the four-way
|
||||||
|
column cache still enabled; both values were read back as `0/0`. It took 4.908 s and 27.609
|
||||||
|
worker-seconds, versus 4.238 s and 21.722 s at the `0/1` default. Its 3×3 support ring was 0.602 s
|
||||||
|
versus 0.447 s, and LOD0 last apply was 1.453 s versus 1.171 s. This cold-path A/B supports keeping
|
||||||
|
the exact gradient memo. The direct-map control is `1/1` and isolates the cache behavior; the memo
|
||||||
|
control is `0/0` and isolates the gradient behavior.
|
||||||
|
|
||||||
|
## 3. Shared XY pages
|
||||||
|
|
||||||
|
The corrected-source Standalone W8 overlap capture counted 281,910 cross-worker neighboring
|
||||||
|
duplicate columns at LOD0 and 123,803 at LOD4. Against Round17 (276,658 and 202,684), LOD0 is up
|
||||||
|
about 2%, while LOD4 is down about 39% after fixing intra-worker cache conflicts. The counters are
|
||||||
|
schedule-dependent work counts, not world output. Multiplying those counts by the current stage
|
||||||
|
audit's p95 column-time-per-miss ratios gives a rough estimate of 0.68 worker-seconds across the
|
||||||
|
LOD0/LOD4 repeats if each repeat avoided a recomputation and the shared lookup had zero cost. That
|
||||||
|
is about 3.1% of the measured 21.722 worker-seconds, before synchronization and memory costs. This
|
||||||
|
is an estimate, not a cold-path A/B win measurement.
|
||||||
|
|
||||||
|
After the four-way change, the remaining upper bound is too small to justify adding an unmeasured
|
||||||
|
synchronized page in this round. It would also add ownership/lifetime and bounded-memory work under
|
||||||
|
the S8 rules. The page was therefore not built. Existing immutable region pages and their S8
|
||||||
|
lifetime are unchanged; no new page lifetime needs proving.
|
||||||
|
|
||||||
|
## 4. Cold 40 m/s trace
|
||||||
|
|
||||||
|
Fresh BuildHost process, 25 cm SurfaceWorld, 40 m/s along +Y, background worker cap 1, ready queue
|
||||||
|
8, dynamic reserve, collision lookahead 1, `CoreColumnCacheDirectMapped=0`, and
|
||||||
|
`CoreMCGradientMemoization=1`. The `worker_backend_config` JSONL row reads back all these controls.
|
||||||
|
Round17 and Round18 use the same trace command and post-`BeginPlay` `elapsed_s` origin. In this run
|
||||||
|
`elapsed_s=4.238189`; raw trace `t_s` is 4.320372. Trace `t_s` begins 0.082022 s before the
|
||||||
|
end-of-BeginPlay origin. The 3×3 walkable support ring is measured at its first ready gate using
|
||||||
|
that same origin. S12's actor report uses a different clock origin; its comparisons are stated as
|
||||||
|
references, not clock-normalized samples.
|
||||||
|
|
||||||
|
| Metric | Round17 S13 | S14 primary | S14 fresh repeat |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| Initial set | 2,578 tiles / 4.825 s | 2,578 / 4.238 s | 2,578 / 5.151 s |
|
||||||
|
| Initial-set worker seconds | 28.760 | 21.722 | 29.969 |
|
||||||
|
| 3×3 walkable support ring | 0.510 s | 0.447 s | 0.971 s |
|
||||||
|
| LOD0 last worker end | 1.131 s | 1.156 s | 1.524 s |
|
||||||
|
| LOD0 last apply | 1.146 s | 1.171 s | 1.532 s |
|
||||||
|
| 40 m/s moving readiness | 75/75 ready | 75/75 ready | 57/57 logged samples ready |
|
||||||
|
| Spawn `UWorld::Tick` p95/max | 3.133 / 5.426 ms | 4.200 / 5.571 ms | 4.601 / 6.682 ms |
|
||||||
|
| Walk `UWorld::Tick` p95/max | 30.314 / 43.142 ms | 32.218 / 45.720 ms | 33.986 / 41.654 ms |
|
||||||
|
|
||||||
|
| Initial-set LOD | Tiles | S14 primary worker s | S14 repeat worker s |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 0 | 512 | 4.520 | 6.319 |
|
||||||
|
| 1 | 448 | 4.696 | 6.304 |
|
||||||
|
| 2 | 320 | 0.870 | 1.241 |
|
||||||
|
| 3 | 272 | 0.829 | 1.193 |
|
||||||
|
| 4 | 272 | 1.862 | 2.634 |
|
||||||
|
| 5 | 208 | 1.478 | 2.121 |
|
||||||
|
| 6 | 144 | 2.179 | 3.168 |
|
||||||
|
| 7 | 96 | 1.723 | 2.206 |
|
||||||
|
| 8 | 306 | 3.565 | 4.782 |
|
||||||
|
| **Total** | **2,578** | **21.722** | **29.969** |
|
||||||
|
|
||||||
|
The same-build LOD0 candidate-tile p95 fell 11.3% (14.079 to 12.492 ms). In the cold trace, LOD0
|
||||||
|
worker time fell 3.3% (4.672 to 4.520 s across 512 tiles, or 9.13 to 8.83 ms per tile).
|
||||||
|
|
||||||
|
The primary cold trace meets the 4.8 s full-set, 28.8 worker-second, and 0.51 s support-ring
|
||||||
|
targets, with all 75 moving samples ready. LOD0 is materially cheaper per tile. LOD0 last apply is
|
||||||
|
81 ms over the ~1.09 s target. I made one more fresh default trace to check the tick regression;
|
||||||
|
the same binary and `0/1` CVar readback passed the commandlet but took 5.151 s and 29.969
|
||||||
|
worker-seconds for all 2,578 tiles, 0.971 s for the support ring, and 1.532 s for the last LOD0
|
||||||
|
apply. Peak private memory was 3.29 GiB. Its spawn tick was 4.601/6.682 ms and walking tick was
|
||||||
|
33.986/41.654 ms p95/max; all 57 logged 40 m/s streaming samples stayed walkable-ready. Thus the
|
||||||
|
full-set, worker-second, support-ring, and LOD0 apply targets pass in the primary run but fail on
|
||||||
|
this repeat. Spawn tick p95/max is worse than S13 and matched S12 in both runs; walking p95 is
|
||||||
|
worse than both in both runs. The first-run walking max is worse than both references; the repeat
|
||||||
|
is worse than S12 but below S13. The strict no-worse tick goal therefore fails. I kept the S13
|
||||||
|
worker cap at 1; the measurements do not justify raising it.
|
||||||
|
|
||||||
|
## 5. Quality and determinism
|
||||||
|
|
||||||
|
- BuildHost W1/W8/W16 stable: density/mesh/payload/topology
|
||||||
|
`0cd2a1a4152ee4aa / a06243aee16b65d5 / c700162935c262d7 / 0af901aa42b452bc`.
|
||||||
|
- Standalone W1/W8/W12, fresh `--force-generate`: whole-view 2,553-tile hashes all match:
|
||||||
|
`c10697a8f48380c7 / e75c9119a1498cd5 / 49a9a8d1835a6c8c / edcb23288b5e8724`.
|
||||||
|
Full-detail 960-tile hashes also match:
|
||||||
|
`0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`.
|
||||||
|
- Core-off at 12.5 cm: `VOXELFORGE_CORE_OFF_LEGACY_PASS`.
|
||||||
|
- M2 Hybrid V1 and V2 at 25 cm: five analytic fixtures and borders passed.
|
||||||
|
- SurfaceWorld at 25 cm: BuildHost W1/W8/W16, geometry scale and height agreement, carve/fill,
|
||||||
|
native/bridge, and collision checks passed.
|
||||||
|
- S3 far-horizon agreement: max 0.0427 cm against 2 cm.
|
||||||
|
- S4 far-cap agreement: 128 shared points, max 0.0504 cm against 2 cm; negative control detected.
|
||||||
|
Cap rays: 1,584/1,584 (100%), no misses.
|
||||||
|
- FloatingIslands at 25 cm: W1/W8/W16 stable:
|
||||||
|
`4dcfb37585d7d986 / f14f76fbf065c6ed / 53035c45fd1a082e / 23550100654a4ec3`;
|
||||||
|
island and span contacts, anchors, carve/fill passed; water remained fixed.
|
||||||
|
|
||||||
|
No density, mesh, topology, payload, or collision output changed. No recipe bump or paired near/far
|
||||||
|
captures are needed.
|
||||||
|
|
||||||
|
## 6. Build, commands, and evidence
|
||||||
|
|
||||||
|
Builds (Normal priority, maximum 12 parallel workers):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\BuildVoxelForgeCoreHost.ps1 -MaxParallelActions 12 -TimeoutSeconds 900
|
||||||
|
.\Standalone\Build.ps1 -Target All -CompileWorkers 12 -BuildPriority Normal
|
||||||
|
```
|
||||||
|
|
||||||
|
BuildHost feature-stage controls:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -SharedFeatureAuditOnly -CoreMCStageAudit -CoreColumnCacheDirectMapped 1 -CoreMCGradientMemoization 0 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -SharedFeatureAuditOnly -CoreMCStageAudit -CoreColumnCacheDirectMapped 0 -CoreMCGradientMemoization 1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
Cold stream command:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -CoreColumnCacheDirectMapped 0 -CoreMCGradientMemoization 1 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
The cold-path negative controls held every other argument fixed; the trace JSONL recorded the CVar
|
||||||
|
readbacks (`0/0` for memo-off, `1/1` for direct-map):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -CoreColumnCacheDirectMapped 0 -CoreMCGradientMemoization 0 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -CoreColumnCacheDirectMapped 1 -CoreMCGradientMemoization 1 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
Standalone processes were sequential, Normal priority, 12-CPU affinity, and used `--force-generate`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=1 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=8 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --profile-column-overlap --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=12 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
```
|
||||||
|
|
||||||
|
Other BuildHost gate commands:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreOffTest -CoreVoxelSizeCm 12.5 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV2 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -FloatingIslandsOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
Measured binaries and SHA-256:
|
||||||
|
|
||||||
|
| Binary | SHA-256 |
|
||||||
|
|---|---|
|
||||||
|
| UE `UnrealEditor-Cmd.exe` | `ECCE9996191F7F7E69B3D104EECDD0E678AE69176FDCCD79828C3E75A0654D6D` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForge.dll` | `4E55A38547B96221562AEC84D61B0FFC7E455E21420C51E14D2BEACA88217A07` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeCore.dll` | `1F34F96208FC0E0A72CE2CE80014EBFB110C29C8333053A90735DF9C7BEE7949` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeEditor.dll` | `9B35350BBA3E7CE4F8D8DA617C216B21CB4CF9869FE5903FD84AAC6906B8187B` |
|
||||||
|
| Standalone `VoxelForgeCoreStandalone.exe` | `91D29D8BA421304F085BFAB4CCE5EC9DDD32C40BE13F15E118E0DC9606D21B84` |
|
||||||
|
|
||||||
|
Full logs, traces, and Standalone stdout/stderr are under `Saved/BuildHost/`; the current 40 m/s
|
||||||
|
trace files are in `Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/`.
|
||||||
|
The fresh repeat uses the same CVar-derived JSONL path as the primary trace, so its JSONL is the
|
||||||
|
current trace; the faster primary run's engine log summary is retained in that Logs directory as
|
||||||
|
`CoreWorldValidation-owner_surface_stream_trace_only_applyboost_default_bgcap_1_readyq_8_legacy_order_colcache_4way_gradientmemo_on_nearreserve_dynamic_trace_move_4000_lookahead_1_dir_0_1_0_region_1_backlog_256-backup-2026.10.19-15.44.14.log`.
|
||||||
|
Per-run Standalone command records and logs are in `Saved/BuildHost/WorldAssemblerM3/Round18/Standalone/`.
|
||||||
|
|
||||||
|
## 7. Failed probes, retries, and safety review
|
||||||
|
|
||||||
|
- `-coremcstageaudit=1` was ignored by Unreal’s boolean argument parser; the audit was repeated
|
||||||
|
with the bare `-coremcstageaudit` flag.
|
||||||
|
- An early direct-map control did not read back `1`, and an early audit binary did not log either
|
||||||
|
new CVar. Those measurements were rejected. I added the S11-style explicit BeginPlay parse and
|
||||||
|
trace readback, rebuilt BuildHost, then reran the direct/off baseline and tuned audit; the JSONL
|
||||||
|
readbacks are `1/0` and `0/1` respectively.
|
||||||
|
- The first corrected shared-audit command was rejected by a harness guard before launch because
|
||||||
|
it treated audit CVars as trace-only controls. The guard now allows them in shared-feature
|
||||||
|
audits; the rerun passed.
|
||||||
|
- The first W8 Standalone wrapper could not resolve its process-local memory helper type. That
|
||||||
|
run exited 0 and its process peak was 0.70 GiB, but its free-memory preflight was invalid. I
|
||||||
|
repeated W8 with the helper defined in that process: free-before 17.12 GiB, peak 0.71 GiB,
|
||||||
|
exit 0, and the whole-view hashes matched W1/W12.
|
||||||
|
- `Get-CimInstance` was denied and `Get-Counter` lacked the requested counter. I used
|
||||||
|
`GlobalMemoryStatusEx` instead; the initial available-memory reading was 15.85 GiB. BuildHost
|
||||||
|
scripts also enforce the 4 GiB preflight, Normal priority, 12-CPU affinity, 8 GiB process cap,
|
||||||
|
and 900 s timeout.
|
||||||
|
- Standalone build printed that `vswhere.exe` was not recognized and emitted existing
|
||||||
|
`VoxelStrateManager.cpp` C4838 narrowing warnings, but built both targets and exited successfully.
|
||||||
|
- BuildHost UBT passed on its first attempt; the 18 Core link shims passed link-safety inspection.
|
||||||
|
No `0xC0000142` occurred. No new `UECC-*` folder appeared (the only matching folder predates
|
||||||
|
this round, from 2026-09-17).
|
||||||
|
- Final diff review made the gradient memo's per-tile validity reset explicit by clearing every
|
||||||
|
validity byte after resizing the scratch array. Both targets were rebuilt and the corrected-source
|
||||||
|
stage, determinism, quality, and cold-trace gates were rerun; hashes stayed identical.
|
||||||
|
|
||||||
|
Diff review: no edits to `Epoch`, either MPSC queue, `EndPlay` draining, worker shutdown checks,
|
||||||
|
`ModsLock`, immutable region-page lifetime, or game-thread UObject/RMC mutation. The new cache and
|
||||||
|
gradient scratch remain thread-local. The readable RealtimeMeshComponent source was not modified.
|
||||||
|
No owner project, editor, asset, generated header, `Binaries/`, or `Intermediate/` source was edited.
|
||||||
|
No commit, push, or stash.
|
||||||
|
|
||||||
|
## 8. Files and compile spots
|
||||||
|
|
||||||
|
No new source file was added.
|
||||||
|
|
||||||
|
| Changed file | Likely compile/build spot |
|
||||||
|
|---|---|
|
||||||
|
| `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp`, `Standalone/Core/src/WorldAssemblerInternal.h` | Standalone Core and BuildHost `VoxelForgeCore` |
|
||||||
|
| `Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h`, `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp` | Standalone executable and BuildHost `VoxelForge` runtime |
|
||||||
|
| `Source/VoxelForge/Private/VoxelWorld.cpp` | BuildHost `VoxelForge` runtime |
|
||||||
|
| `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost `VoxelForgeEditor` |
|
||||||
|
| `Tools/TestVoxelForgeCoreWorldHost.ps1` | BuildHost validation harness; no compile |
|
||||||
|
| `ARCHITECTURE.md`, `CODEMAP.md` | Documentation; no compile |
|
||||||
|
|
||||||
|
### Dialogue
|
||||||
|
|
||||||
|
**Owner:** What is inside the LOD0 MC-exclusive cost, why does LOD4 miss, and is a shared page worth it?
|
||||||
|
|
||||||
|
**Codex:** LOD0’s dominant measured MC-exclusive stage is density-grid evaluation excluding XY
|
||||||
|
column work (about 165 ns/sample), followed by grid-gradient normals (24 ns/sample after exact
|
||||||
|
memoization). The cache counters show LOD4’s direct-map spike was chiefly conflicts; four-way sets
|
||||||
|
remove those while keeping exact samples. Cross-worker repeats remain, but no shared-page A/B
|
||||||
|
measured a net cold-path win, so I left the S8 page lifetime unchanged.
|
||||||
@@ -120,13 +120,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
int64* OutDensitySampleCount,
|
int64* OutDensitySampleCount,
|
||||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
|
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
|
||||||
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
||||||
std::vector<float>* OutDebugDensitySamples)
|
std::vector<float>* OutDebugDensitySamples,
|
||||||
|
FVoxelMarchingCubesStageTimings* OutStageTimings)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||||
|
const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||||
FVoxelMeshData MeshData;
|
FVoxelMeshData MeshData;
|
||||||
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
||||||
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
|
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
|
||||||
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
||||||
|
if (OutStageTimings) { *OutStageTimings = FVoxelMarchingCubesStageTimings{}; }
|
||||||
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
|
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
|
||||||
if (!Generator) return MeshData;
|
if (!Generator) return MeshData;
|
||||||
const bool bEmitUV = !bStandaloneSkipUvAndColors && !bStandaloneSkipUV;
|
const bool bEmitUV = !bStandaloneSkipUvAndColors && !bStandaloneSkipUV;
|
||||||
@@ -851,6 +854,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
||||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||||
|
const uint64 StageDensityGridStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||||
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||||
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
@@ -1250,6 +1254,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MesherDensityGridTimer.End();
|
MesherDensityGridTimer.End();
|
||||||
|
if(OutStageTimings)
|
||||||
|
OutStageTimings->DensityGridMilliseconds=static_cast<double>(
|
||||||
|
FPlatformTime::Cycles64()-StageDensityGridStartCycles)
|
||||||
|
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||||
|
|
||||||
if (bUseProofBlocks)
|
if (bUseProofBlocks)
|
||||||
{
|
{
|
||||||
@@ -1345,6 +1353,22 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
{
|
{
|
||||||
return DensityGrid[((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1)];
|
return DensityGrid[((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1)];
|
||||||
};
|
};
|
||||||
|
// Reuse exact grid-corner gradients between incident cells within this tile.
|
||||||
|
// Reutiliser les gradients exacts des coins entre cellules voisines de cette tuile.
|
||||||
|
static thread_local TArray<FVector> GradientMemo;
|
||||||
|
static thread_local TArray<uint8> GradientMemoValid;
|
||||||
|
const bool bUseGradientMemo=bStandaloneEnableGradientMemoization
|
||||||
|
&&ShapeWorldContext!=nullptr&&Step==1;
|
||||||
|
if(bUseGradientMemo)
|
||||||
|
{
|
||||||
|
GradientMemo.SetNumUninitialized(DensityGrid.Num());
|
||||||
|
GradientMemoValid.SetNumUninitialized(DensityGrid.Num());
|
||||||
|
// Clear every tile even when the reused TArray keeps the same length.
|
||||||
|
// Effacer chaque tuile meme si le TArray reutilise conserve la meme taille.
|
||||||
|
if(GradientMemoValid.Num()>0)
|
||||||
|
FMemory::Memzero(GradientMemoValid.GetData(),
|
||||||
|
static_cast<SIZE_T>(GradientMemoValid.Num())*sizeof(uint8));
|
||||||
|
}
|
||||||
auto SampleGradientG = [&](int32 gx, int32 gy, int32 gz) -> float
|
auto SampleGradientG = [&](int32 gx, int32 gy, int32 gz) -> float
|
||||||
{
|
{
|
||||||
const float Cached = SampleG(gx, gy, gz);
|
const float Cached = SampleG(gx, gy, gz);
|
||||||
@@ -1353,6 +1377,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
{
|
{
|
||||||
return Cached;
|
return Cached;
|
||||||
}
|
}
|
||||||
|
if(OutStageTimings)++OutStageTimings->GradientFallbackDensityCalls;
|
||||||
return ShapeWorldContext->DensityAtUnclamped(
|
return ShapeWorldContext->DensityAtUnclamped(
|
||||||
static_cast<float>(OriginVoxels.X + gx * Step),
|
static_cast<float>(OriginVoxels.X + gx * Step),
|
||||||
static_cast<float>(OriginVoxels.Y + gy * Step),
|
static_cast<float>(OriginVoxels.Y + gy * Step),
|
||||||
@@ -1360,11 +1385,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
};
|
};
|
||||||
auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector
|
auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector
|
||||||
{
|
{
|
||||||
|
const int32 CacheIndex=((gz+1)*MDim+(gy+1))*MDim+(gx+1);
|
||||||
|
if(bUseGradientMemo&&GradientMemoValid[CacheIndex]!=0)
|
||||||
|
{
|
||||||
|
if(OutStageTimings)++OutStageTimings->GradientMemoHits;
|
||||||
|
return GradientMemo[CacheIndex];
|
||||||
|
}
|
||||||
// Densité négative=solide, positive=air → le gradient pointe vers l'air (sortant).
|
// Densité négative=solide, positive=air → le gradient pointe vers l'air (sortant).
|
||||||
return FVector(
|
const FVector Gradient(
|
||||||
SampleGradientG(gx + 1, gy, gz) - SampleGradientG(gx - 1, gy, gz),
|
SampleGradientG(gx + 1, gy, gz) - SampleGradientG(gx - 1, gy, gz),
|
||||||
SampleGradientG(gx, gy + 1, gz) - SampleGradientG(gx, gy - 1, gz),
|
SampleGradientG(gx, gy + 1, gz) - SampleGradientG(gx, gy - 1, gz),
|
||||||
SampleGradientG(gx, gy, gz + 1) - SampleGradientG(gx, gy, gz - 1));
|
SampleGradientG(gx, gy, gz + 1) - SampleGradientG(gx, gy, gz - 1));
|
||||||
|
if(bUseGradientMemo)
|
||||||
|
{
|
||||||
|
GradientMemo[CacheIndex]=Gradient;
|
||||||
|
GradientMemoValid[CacheIndex]=1;
|
||||||
|
}
|
||||||
|
return Gradient;
|
||||||
};
|
};
|
||||||
|
|
||||||
// DC is limited to tagged LOD0 patches. Coarse tiles and a connected patch that reaches a
|
// DC is limited to tagged LOD0 patches. Coarse tiles and a connected patch that reaches a
|
||||||
@@ -1401,8 +1438,12 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
GroundTris.Reset();
|
GroundTris.Reset();
|
||||||
CapTris.Reset();
|
CapTris.Reset();
|
||||||
const bool bProfileMesher = VoxelDensityProfile::IsCycleTimingEnabled();
|
const bool bProfileMesher = VoxelDensityProfile::IsCycleTimingEnabled();
|
||||||
|
const bool bAuditMesherStages=OutStageTimings!=nullptr;
|
||||||
uint64 CellClassificationCalls = 0;
|
uint64 CellClassificationCalls = 0;
|
||||||
uint64 SurfaceCellCalls = 0;
|
uint64 SurfaceCellCalls = 0;
|
||||||
|
uint64 AuditCellClassificationCycles=0,AuditGradientCycles=0;
|
||||||
|
uint64 AuditInterpolationCycles=0,AuditDedupStreamCycles=0;
|
||||||
|
uint64 AuditGradientSamples=0,AuditInterpolationSamples=0,AuditDedupStreamSamples=0;
|
||||||
uint8 CaseIndexRow[CHUNK_SIZE]{};
|
uint8 CaseIndexRow[CHUNK_SIZE]{};
|
||||||
uint8 SurfaceRow[CHUNK_SIZE]{};
|
uint8 SurfaceRow[CHUNK_SIZE]{};
|
||||||
uint8 SampleClassificationRow[CHUNK_SIZE]{};
|
uint8 SampleClassificationRow[CHUNK_SIZE]{};
|
||||||
@@ -1413,7 +1454,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
{
|
{
|
||||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||||
FMemory::Memzero(SampleClassificationRow, sizeof(SampleClassificationRow));
|
FMemory::Memzero(SampleClassificationRow, sizeof(SampleClassificationRow));
|
||||||
const uint64 ClassificationRowStart = bProfileMesher
|
const uint64 ClassificationRowStart = (bProfileMesher||bAuditMesherStages)
|
||||||
? FPlatformTime::Cycles64() : 0;
|
? FPlatformTime::Cycles64() : 0;
|
||||||
int32 ClassificationRowCalls = 0;
|
int32 ClassificationRowCalls = 0;
|
||||||
for (int32 GroupX = 0; GroupX < CellsPerAxis; GroupX += 8)
|
for (int32 GroupX = 0; GroupX < CellsPerAxis; GroupX += 8)
|
||||||
@@ -1454,9 +1495,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
||||||
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
||||||
SampleClassificationRow[CellX] = bProfileMesher
|
SampleClassificationRow[CellX] =
|
||||||
&& VoxelDensityProfile::ShouldSample(
|
(bProfileMesher&&VoxelDensityProfile::ShouldSample(
|
||||||
VoxelDensityProfile::EBucket::MesherCellClassification);
|
VoxelDensityProfile::EBucket::MesherCellClassification))
|
||||||
|
||(bAuditMesherStages&&(CellClassificationCalls%8u)==0u);
|
||||||
++ClassificationRowCalls;
|
++ClassificationRowCalls;
|
||||||
++CellClassificationCalls;
|
++CellClassificationCalls;
|
||||||
}
|
}
|
||||||
@@ -1477,17 +1519,21 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
||||||
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
||||||
SampleClassificationRow[CellX] = bProfileMesher
|
SampleClassificationRow[CellX] =
|
||||||
&& VoxelDensityProfile::ShouldSample(
|
(bProfileMesher&&VoxelDensityProfile::ShouldSample(
|
||||||
VoxelDensityProfile::EBucket::MesherCellClassification);
|
VoxelDensityProfile::EBucket::MesherCellClassification))
|
||||||
|
||(bAuditMesherStages&&(CellClassificationCalls%8u)==0u);
|
||||||
++ClassificationRowCalls;
|
++ClassificationRowCalls;
|
||||||
++CellClassificationCalls;
|
++CellClassificationCalls;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bProfileMesher && ClassificationRowCalls > 0)
|
if ((bProfileMesher||bAuditMesherStages) && ClassificationRowCalls > 0)
|
||||||
{
|
{
|
||||||
const uint64 RowCycles = FPlatformTime::Cycles64() - ClassificationRowStart;
|
const uint64 RowCycles = FPlatformTime::Cycles64() - ClassificationRowStart;
|
||||||
|
if(bAuditMesherStages)AuditCellClassificationCycles+=RowCycles;
|
||||||
|
if(bProfileMesher)
|
||||||
|
{
|
||||||
const uint64 PerCellCycles = RowCycles
|
const uint64 PerCellCycles = RowCycles
|
||||||
/ static_cast<uint64>(ClassificationRowCalls);
|
/ static_cast<uint64>(ClassificationRowCalls);
|
||||||
for (int32 cx = 0; cx < CellsPerAxis; ++cx)
|
for (int32 cx = 0; cx < CellsPerAxis; ++cx)
|
||||||
@@ -1500,6 +1546,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (int32 cx = 0; cx < CellsPerAxis; cx++)
|
for (int32 cx = 0; cx < CellsPerAxis; cx++)
|
||||||
{
|
{
|
||||||
if (bUseProofBlocks
|
if (bUseProofBlocks
|
||||||
@@ -1537,9 +1584,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
if (bSampleCell)
|
if (bSampleCell)
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddSampledMeasurement(
|
const uint64 StageCycles=FPlatformTime::Cycles64()-GradientStart;
|
||||||
VoxelDensityProfile::EBucket::MesherGradientNormals,
|
if(bProfileMesher)VoxelDensityProfile::AddSampledMeasurement(
|
||||||
FPlatformTime::Cycles64() - GradientStart);
|
VoxelDensityProfile::EBucket::MesherGradientNormals,StageCycles);
|
||||||
|
if(bAuditMesherStages)
|
||||||
|
{AuditGradientCycles+=StageCycles;++AuditGradientSamples;}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolation des positions + normales sur les arêtes traversées. t = point de
|
// Interpolation des positions + normales sur les arêtes traversées. t = point de
|
||||||
@@ -1563,6 +1612,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
if(ShapeWorldContext&&Step==1&&T>=0.375f&&T<=0.625f)
|
if(ShapeWorldContext&&Step==1&&T>=0.375f&&T<=0.625f)
|
||||||
{
|
{
|
||||||
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
|
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
|
||||||
|
if(OutStageTimings)++OutStageTimings->EdgeMidpointDensityCalls;
|
||||||
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
|
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
|
||||||
Midpoint.X/GeometryVoxelSize,Midpoint.Y/GeometryVoxelSize,
|
Midpoint.X/GeometryVoxelSize,Midpoint.Y/GeometryVoxelSize,
|
||||||
Midpoint.Z/GeometryVoxelSize);
|
Midpoint.Z/GeometryVoxelSize);
|
||||||
@@ -1611,9 +1661,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
if (bSampleCell)
|
if (bSampleCell)
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddSampledMeasurement(
|
const uint64 StageCycles=FPlatformTime::Cycles64()-VertexInterpolationStart;
|
||||||
VoxelDensityProfile::EBucket::MesherVertexInterpolation,
|
if(bProfileMesher)VoxelDensityProfile::AddSampledMeasurement(
|
||||||
FPlatformTime::Cycles64() - VertexInterpolationStart);
|
VoxelDensityProfile::EBucket::MesherVertexInterpolation,StageCycles);
|
||||||
|
if(bAuditMesherStages)
|
||||||
|
{AuditInterpolationCycles+=StageCycles;++AuditInterpolationSamples;}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Génère les triangles avec vertices dédupliqués.
|
// Génère les triangles avec vertices dédupliqués.
|
||||||
@@ -1651,9 +1703,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
if (bSampleCell)
|
if (bSampleCell)
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddSampledMeasurement(
|
const uint64 StageCycles=FPlatformTime::Cycles64()-StreamBuildingStart;
|
||||||
VoxelDensityProfile::EBucket::MesherStreamBuilding,
|
if(bProfileMesher)VoxelDensityProfile::AddSampledMeasurement(
|
||||||
FPlatformTime::Cycles64() - StreamBuildingStart);
|
VoxelDensityProfile::EBucket::MesherStreamBuilding,StageCycles);
|
||||||
|
if(bAuditMesherStages)
|
||||||
|
{AuditDedupStreamCycles+=StageCycles;++AuditDedupStreamSamples;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1989,5 +2043,31 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
OutputAllocatedBytes);
|
OutputAllocatedBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(OutStageTimings)
|
||||||
|
{
|
||||||
|
const double CycleToMs=FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||||
|
const auto ScaleSampledCycles=[CycleToMs](uint64 Cycles,uint64 Samples,uint64 Calls)
|
||||||
|
{
|
||||||
|
return Samples>0?static_cast<double>(Cycles)*static_cast<double>(Calls)
|
||||||
|
/static_cast<double>(Samples)*CycleToMs:0.0;
|
||||||
|
};
|
||||||
|
OutStageTimings->CellClassificationMilliseconds=
|
||||||
|
static_cast<double>(AuditCellClassificationCycles)*CycleToMs;
|
||||||
|
OutStageTimings->GradientNormalsMilliseconds=ScaleSampledCycles(
|
||||||
|
AuditGradientCycles,AuditGradientSamples,SurfaceCellCalls);
|
||||||
|
OutStageTimings->EdgeInterpolationMilliseconds=ScaleSampledCycles(
|
||||||
|
AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls);
|
||||||
|
OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles(
|
||||||
|
AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls);
|
||||||
|
const double TotalMilliseconds=static_cast<double>(
|
||||||
|
FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs;
|
||||||
|
OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds
|
||||||
|
-OutStageTimings->DensityGridMilliseconds
|
||||||
|
-OutStageTimings->CellClassificationMilliseconds
|
||||||
|
-OutStageTimings->GradientNormalsMilliseconds
|
||||||
|
-OutStageTimings->EdgeInterpolationMilliseconds
|
||||||
|
-OutStageTimings->VertexDedupStreamMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
return MeshData;
|
return MeshData;
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,21 @@ struct FVoxelSharedDensityGrid
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Optional, sampled timing output for the research audit; null in production calls.
|
||||||
|
// Mesures echantillonnees facultatives pour l'audit; pointeur nul en production.
|
||||||
|
struct FVoxelMarchingCubesStageTimings
|
||||||
|
{
|
||||||
|
double DensityGridMilliseconds = 0.0;
|
||||||
|
double CellClassificationMilliseconds = 0.0;
|
||||||
|
double GradientNormalsMilliseconds = 0.0;
|
||||||
|
double EdgeInterpolationMilliseconds = 0.0;
|
||||||
|
double VertexDedupStreamMilliseconds = 0.0;
|
||||||
|
double OtherMilliseconds = 0.0;
|
||||||
|
uint64 GradientFallbackDensityCalls = 0;
|
||||||
|
uint64 EdgeMidpointDensityCalls = 0;
|
||||||
|
uint64 GradientMemoHits = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Stable plan data copied onto one worker-owned mesher for hybrid LOD0 generation.
|
// Stable plan data copied onto one worker-owned mesher for hybrid LOD0 generation.
|
||||||
// Donnees stables du plan copiees dans un mailleur de worker pour le mode hybride LOD0.
|
// Donnees stables du plan copiees dans un mailleur de worker pour le mode hybride LOD0.
|
||||||
struct FVoxelHybridSharpFeatureBox
|
struct FVoxelHybridSharpFeatureBox
|
||||||
@@ -91,7 +106,8 @@ public:
|
|||||||
int64* OutDensitySampleCount = nullptr,
|
int64* OutDensitySampleCount = nullptr,
|
||||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
|
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
|
||||||
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
||||||
std::vector<float>* OutDebugDensitySamples = nullptr);
|
std::vector<float>* OutDebugDensitySamples = nullptr,
|
||||||
|
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr);
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// SERVICES (injectés par AVoxelWorld)
|
// SERVICES (injectés par AVoxelWorld)
|
||||||
@@ -161,6 +177,7 @@ public:
|
|||||||
bool bStandaloneSkipUV = false;
|
bool bStandaloneSkipUV = false;
|
||||||
bool bStandaloneSkipColors = false;
|
bool bStandaloneSkipColors = false;
|
||||||
bool bStandaloneSkipMaterialGroups = false;
|
bool bStandaloneSkipMaterialGroups = false;
|
||||||
|
bool bStandaloneEnableGradientMemoization = true;
|
||||||
|
|
||||||
// Seuil de l'isosurface dans le champ de densité.
|
// Seuil de l'isosurface dans le champ de densité.
|
||||||
// Convention MC: densité < IsoLevel = solide, >= = air.
|
// Convention MC: densité < IsoLevel = solide, >= = air.
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ param(
|
|||||||
[ValidateRange(0, 4096)][int]$StreamingResultBacklogCap = 256,
|
[ValidateRange(0, 4096)][int]$StreamingResultBacklogCap = 256,
|
||||||
[ValidateRange(0, 10)][int]$StreamingReadyQueueDepth = 8,
|
[ValidateRange(0, 10)][int]$StreamingReadyQueueDepth = 8,
|
||||||
[ValidateRange(0, 1)][int]$StreamingBackgroundWorkerCap = 1,
|
[ValidateRange(0, 1)][int]$StreamingBackgroundWorkerCap = 1,
|
||||||
|
[ValidateRange(0, 1)][int]$CoreColumnCacheDirectMapped = 0,
|
||||||
|
[ValidateRange(0, 1)][int]$CoreMCGradientMemoization = 1,
|
||||||
[ValidateRange(0, 900)][int]$Step1MaxMeshApplies = 0,
|
[ValidateRange(0, 900)][int]$Step1MaxMeshApplies = 0,
|
||||||
[ValidateRange(0, 900)][int]$TestTeleportAfterSeconds = 0,
|
[ValidateRange(0, 900)][int]$TestTeleportAfterSeconds = 0,
|
||||||
[ValidateRange(0, 1000000)][int]$TestTeleportDistanceCm = 0,
|
[ValidateRange(0, 1000000)][int]$TestTeleportDistanceCm = 0,
|
||||||
@@ -29,6 +31,7 @@ param(
|
|||||||
[switch]$CoreOffTest,
|
[switch]$CoreOffTest,
|
||||||
[switch]$CorePlanOnly,
|
[switch]$CorePlanOnly,
|
||||||
[switch]$SharedFeatureAuditOnly,
|
[switch]$SharedFeatureAuditOnly,
|
||||||
|
[switch]$CoreMCStageAudit,
|
||||||
[int]$CoreTestSeed = [int]::MinValue
|
[int]$CoreTestSeed = [int]::MinValue
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,6 +108,10 @@ if (-not $StreamingTraceOnly -and ($TestMoveSpeedCmPerSecond -ne 600 -or
|
|||||||
$TestTeleportDistanceCm -ne 0)) {
|
$TestTeleportDistanceCm -ne 0)) {
|
||||||
throw 'Streaming, backlog, apply and teleport controls require -StreamingTraceOnly.'
|
throw 'Streaming, backlog, apply and teleport controls require -StreamingTraceOnly.'
|
||||||
}
|
}
|
||||||
|
if (-not $StreamingTraceOnly -and -not $SharedFeatureAuditOnly -and
|
||||||
|
($CoreColumnCacheDirectMapped -ne 0 -or $CoreMCGradientMemoization -ne 1)) {
|
||||||
|
throw 'Column-cache and gradient-memoization controls require a streaming or shared-feature audit.'
|
||||||
|
}
|
||||||
if (($TestTeleportAfterSeconds -eq 0) -ne ($TestTeleportDistanceCm -eq 0)) {
|
if (($TestTeleportAfterSeconds -eq 0) -ne ($TestTeleportDistanceCm -eq 0)) {
|
||||||
throw 'Set both -TestTeleportAfterSeconds and -TestTeleportDistanceCm for the teleport probe.'
|
throw 'Set both -TestTeleportAfterSeconds and -TestTeleportDistanceCm for the teleport probe.'
|
||||||
}
|
}
|
||||||
@@ -180,7 +187,13 @@ if ($SharedFeatureAuditOnly) {
|
|||||||
if ($SelectedModeCount -ne 0) { throw '-SharedFeatureAuditOnly uses the SurfaceWorld owner route.' }
|
if ($SelectedModeCount -ne 0) { throw '-SharedFeatureAuditOnly uses the SurfaceWorld owner route.' }
|
||||||
$RunName = 'shared_feature_audit'
|
$RunName = 'shared_feature_audit'
|
||||||
}
|
}
|
||||||
|
if ($CoreMCStageAudit -and -not $SharedFeatureAuditOnly) {
|
||||||
|
throw '-CoreMCStageAudit requires -SharedFeatureAuditOnly.'
|
||||||
|
}
|
||||||
|
if ($CoreMCStageAudit) { $RunName += '_mc_stages' }
|
||||||
$RunName += if ($StreamingOrder -eq 'Legacy') { '_legacy_order' } else { '_coarse_first' }
|
$RunName += if ($StreamingOrder -eq 'Legacy') { '_legacy_order' } else { '_coarse_first' }
|
||||||
|
$RunName += if ($CoreColumnCacheDirectMapped -eq 1) { '_colcache_direct' } else { '_colcache_4way' }
|
||||||
|
$RunName += if ($CoreMCGradientMemoization -eq 1) { '_gradientmemo_on' } else { '_gradientmemo_off' }
|
||||||
$RunName += switch ($NearReserveMode) {
|
$RunName += switch ($NearReserveMode) {
|
||||||
'Legacy' { '_nearreserve_legacy' }
|
'Legacy' { '_nearreserve_legacy' }
|
||||||
'Dynamic' { '_nearreserve_dynamic' }
|
'Dynamic' { '_nearreserve_dynamic' }
|
||||||
@@ -328,6 +341,8 @@ $BaseArguments += "-voxel.CoreRegionConcurrentAdmission=$CoreRegionConcurrentAdm
|
|||||||
$BaseArguments += "-voxel.StreamingResultBacklogCap=$StreamingResultBacklogCap"
|
$BaseArguments += "-voxel.StreamingResultBacklogCap=$StreamingResultBacklogCap"
|
||||||
$BaseArguments += "-voxel.StreamingReadyQueueDepth=$StreamingReadyQueueDepth"
|
$BaseArguments += "-voxel.StreamingReadyQueueDepth=$StreamingReadyQueueDepth"
|
||||||
$BaseArguments += "-voxel.StreamingBackgroundWorkerCap=$StreamingBackgroundWorkerCap"
|
$BaseArguments += "-voxel.StreamingBackgroundWorkerCap=$StreamingBackgroundWorkerCap"
|
||||||
|
$BaseArguments += "-voxel.CoreColumnCacheDirectMapped=$CoreColumnCacheDirectMapped"
|
||||||
|
$BaseArguments += "-voxel.CoreMCGradientMemoization=$CoreMCGradientMemoization"
|
||||||
if ($Step1MaxMeshApplies -gt 0) {
|
if ($Step1MaxMeshApplies -gt 0) {
|
||||||
$BaseArguments += "-voxel.Step1MaxMeshApplies=$Step1MaxMeshApplies"
|
$BaseArguments += "-voxel.Step1MaxMeshApplies=$Step1MaxMeshApplies"
|
||||||
}
|
}
|
||||||
@@ -350,6 +365,7 @@ if ($StartupTrace -or $StreamingTraceOnly) {
|
|||||||
$BaseArguments += '-voxel.StartupTraceMaxTiles=16384'
|
$BaseArguments += '-voxel.StartupTraceMaxTiles=16384'
|
||||||
}
|
}
|
||||||
if ($SharedFeatureAuditOnly) { $BaseArguments += '-corefeatureauditonly' }
|
if ($SharedFeatureAuditOnly) { $BaseArguments += '-corefeatureauditonly' }
|
||||||
|
if ($CoreMCStageAudit) { $BaseArguments += '-coremcstageaudit' }
|
||||||
if ($HasManifestOverride) {
|
if ($HasManifestOverride) {
|
||||||
$BaseArguments += ('-coreworldassembly="{0}"' -f $StagedWorldAssembly)
|
$BaseArguments += ('-coreworldassembly="{0}"' -f $StagedWorldAssembly)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user