fix(mesher): stop forcing the nested block classifier on -- but I cannot reproduce the 3.5x

The code contradicted its own documentation. VoxelMarchingCubesMesher.h:150 says to leave
the optional 8^3-block refinement DISABLED in streaming, "repeating a proof on ..." --
and VoxelWorld.cpp forced `Mesher->bUseBlockEarlyOut = true` regardless.

It is now off by default, behind `voxel.UseBlockEarlyOut` (CVar + command line), so the
capability stays available for the focused diagnostics where it was measured to help.

⚠️ THE HEADLINE NUMBER DOES NOT REPRODUCE, AND I AM NOT CLAIMING IT
Sol's second pass read a 3.5x from the attribution table:
    classifier disabled    587.991 ms
    classifier enabled   2,056.596 ms
My own clean A/B on this flag, quiet machine, 3 runs each:
    nested OFF   0.597 / 0.613 / 0.607 s   mean 0.606
    nested ON    0.637 / 0.599 / 0.605 s   mean 0.614
Identical within noise.

So either the 2,056 ms control exercised more than this flag does, or the 3.5x lives on
a path the 128^3 export does not take. PERF-ATTRIBUTION.md describes that figure as "the
separate classifier control", which is not obviously the same lever. Until the game-path
A/B is run, treat the 3.5x as UNCONFIRMED.

What this commit is worth on its own: the code now does what its documentation says, the
behaviour is switchable rather than hardcoded, and geometry is unchanged --
SHA-256 b3e5f4c3..9b377 identical with the flag on, off, and against bb8ec51.

⚠️ An earlier measurement pass in this round was contaminated: a local llama-server was
using 5 GB and competing for cores. Those runs came out 0.67-0.78 s with the on/off
ranges overlapping. Discarded and re-measured on a quiet machine; only the clean numbers
above are reported. Worth recording as a reminder that the machine is part of the
instrument.

NOT DONE -- the round was OOM-killed partway (same llama-server):
- the three-way outer-classifier A/B (no outer / current+discarded validation / ⭐ outer
  with validation samples REUSED as the mesher core grid). That third mode is the
  interesting one: exact validation currently costs 33^3 = 35,937 density calls whose
  results are then DISCARDED, while the mesher then fills 35^3 = 42,875. A successful
  uniform verdict saves at most 16.18%, and a Mixed verdict saves nothing.
- game-path measurement of any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-12 13:19:42 +02:00
1 parent bb8ec51939
commit f2044135ea
4 files changed
+224 -15

No files matched your search

@@ -37,12 +37,15 @@ namespace
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis, FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
TArray<uint8>* OutCaptureGrid, TArray<uint8>* OutCaptureGrid,
int32 BandZMinVox, int32 BandZMaxVox) int32 BandZMinVox, int32 BandZMaxVox,
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid,
int64* OutDensitySampleCount)
{ {
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh); TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
VF_ParseOperatorBlockSwitch(); VF_ParseOperatorBlockSwitch();
FVoxelMeshData MeshData; FVoxelMeshData MeshData;
if (OutCaptureGrid) { OutCaptureGrid->Reset(); } if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
if (!Generator) return MeshData; if (!Generator) return MeshData;
VoxelDensityProfile::FScopedTimer MesherProfileTimer( VoxelDensityProfile::FScopedTimer MesherProfileTimer(
VoxelDensityProfile::EBucket::MesherGenerateMesh); VoxelDensityProfile::EBucket::MesherGenerateMesh);
@@ -227,6 +230,12 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer
const int32 GridDim = CellsPerAxis + 1; const int32 GridDim = CellsPerAxis + 1;
const int32 MDim = GridDim + 2; // +1 marge de chaque côté const int32 MDim = GridDim + 2; // +1 marge de chaque côté
const bool bUseReusableCoreGrid = !OutCaptureGrid
&& ReusableCoreGrid
&& ReusableCoreGrid->OriginVoxels == OriginVoxels
&& ReusableCoreGrid->Step == Step
&& ReusableCoreGrid->CellsPerAxis == CellsPerAxis
&& ReusableCoreGrid->Samples.Num() == GridDim * GridDim * GridDim;
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules // COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs). // dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
@@ -532,7 +541,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// 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.
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer( VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
VoxelDensityProfile::EBucket::MesherDensityGrid); VoxelDensityProfile::EBucket::MesherDensityGrid);
const bool bUseOperatorBlock = !bUseSharedDensityGrid const bool bUseOperatorBlock = !bUseSharedDensityGrid && !bUseReusableCoreGrid
&& GVoxelForgeUseOperatorBlock != 0; && GVoxelForgeUseOperatorBlock != 0;
if (bUseOperatorBlock) if (bUseOperatorBlock)
{ {
@@ -567,6 +576,14 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
DensityGrid[LocalIndex] = ReuseGrid->Samples[ DensityGrid[LocalIndex] = ReuseGrid->Samples[
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX]; (SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
} }
else if (bUseReusableCoreGrid
&& gx >= 0 && gx < GridDim
&& gy >= 0 && gy < GridDim
&& gz >= 0 && gz < GridDim)
{
DensityGrid[LocalIndex] = ReusableCoreGrid->Samples[
(gz * GridDim + gy) * GridDim + gx];
}
else else
{ {
// World voxel = tile origin + grid offset scaled by the cell size (Step). // World voxel = tile origin + grid offset scaled by the cell size (Step).
@@ -574,6 +591,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
OriginVoxels.X + gx * Step, OriginVoxels.X + gx * Step,
OriginVoxels.Y + gy * Step, OriginVoxels.Y + gy * Step,
OriginVoxels.Z + gz * Step); OriginVoxels.Z + gz * Step);
if (OutDensitySampleCount)
{
++(*OutDensitySampleCount);
}
} }
} }
} }
+159 -12
View File
@@ -74,6 +74,22 @@ namespace
GVoxelForgePerfAttribution, GVoxelForgePerfAttribution,
TEXT("Collect low-overhead component attribution for the game path.")); TEXT("Collect low-overhead component attribution for the game path."));
// The nested 8^3 proof is useful for focused/offline measurements, but it is opt-in for
// streaming because every qualifying mixed tile would pay the recursive proof again.
int32 GVoxelForgeUseBlockEarlyOut = 0;
FAutoConsoleVariableRef CVarVoxelForgeUseBlockEarlyOut(
TEXT("voxel.UseBlockEarlyOut"),
GVoxelForgeUseBlockEarlyOut,
TEXT("Use the optional nested 8x8x8 mesher classifier in streaming."));
// Outer classifier benchmark modes: 0 = disabled, 1 = current discarded exact validation,
// 2 = exact validation core reused by the mesher when the candidate fails validation.
int32 GVoxelForgeOuterClassifierMode = 1;
FAutoConsoleVariableRef CVarVoxelForgeOuterClassifierMode(
TEXT("voxel.OuterClassifierMode"),
GVoxelForgeOuterClassifierMode,
TEXT("Outer tile classifier mode: 0 off, 1 discarded validation, 2 validation-core reuse."));
// Startup command-line cvars can arrive before the game world has spawned AVoxelWorld (or // Startup command-line cvars can arrive before the game world has spawned AVoxelWorld (or
// before its first tiles have become visible). Keep the diagnostic edit pending until Tick has // before its first tiles have become visible). Keep the diagnostic edit pending until Tick has
// a loaded tile; otherwise a startup measurement silently becomes a no-op or edits the wrong // a loaded tile; otherwise a startup measurement silently becomes a no-op or edits the wrong
@@ -1036,6 +1052,21 @@ void AVoxelWorld::BeginPlay()
LOD0ReadySamples.Reset(); LOD0ReadySamples.Reset();
ObsoleteTileAbortCount = 0; ObsoleteTileAbortCount = 0;
PeakObservedPawnSpeedCmPerSecond = 0.0f; PeakObservedPawnSpeedCmPerSecond = 0.0f;
TotalWorkerGenerationCycles.store(0, std::memory_order_relaxed);
TotalWorkerGenerationTasks.store(0, std::memory_order_relaxed);
TotalObsoleteWorkerCycles.store(0, std::memory_order_relaxed);
TotalObsoleteWorkerTasks.store(0, std::memory_order_relaxed);
TotalValidationDensityCalls.store(0, std::memory_order_relaxed);
TotalMesherDensityCalls.store(0, std::memory_order_relaxed);
TotalValidationCoreReuseTiles.store(0, std::memory_order_relaxed);
for (int32 LOD = 0; LOD < TrackedClassifierLODCount; ++LOD)
{
OuterClassifierCallsByLOD[LOD].store(0, std::memory_order_relaxed);
for (int32 Verdict = 0; Verdict < 3; ++Verdict)
{
OuterClassifierVerdictsByLOD[LOD][Verdict].store(0, std::memory_order_relaxed);
}
}
ConfigureHeadlessStreamingTest(); ConfigureHeadlessStreamingTest();
// Some headless harnesses load module CVars after the engine's startup-CVar pass. Read the // Some headless harnesses load module CVars after the engine's startup-CVar pass. Read the
@@ -1044,6 +1075,8 @@ void AVoxelWorld::BeginPlay()
int32 CommandLineProfile = GVoxelForgeProfileDensity; int32 CommandLineProfile = GVoxelForgeProfileDensity;
int32 CommandLineProfileFull = GVoxelForgeProfileDensityFull; int32 CommandLineProfileFull = GVoxelForgeProfileDensityFull;
int32 CommandLinePerfAttribution = GVoxelForgePerfAttribution; int32 CommandLinePerfAttribution = GVoxelForgePerfAttribution;
int32 CommandLineUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut;
int32 CommandLineOuterClassifierMode = GVoxelForgeOuterClassifierMode;
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ProfileDensity="), CommandLineProfile)) if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ProfileDensity="), CommandLineProfile))
{ {
GVoxelForgeProfileDensity = CommandLineProfile; GVoxelForgeProfileDensity = CommandLineProfile;
@@ -1056,6 +1089,16 @@ void AVoxelWorld::BeginPlay()
{ {
GVoxelForgePerfAttribution = CommandLinePerfAttribution; GVoxelForgePerfAttribution = CommandLinePerfAttribution;
} }
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.UseBlockEarlyOut="), CommandLineUseBlockEarlyOut))
{
GVoxelForgeUseBlockEarlyOut = CommandLineUseBlockEarlyOut;
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.OuterClassifierMode="), CommandLineOuterClassifierMode))
{
GVoxelForgeOuterClassifierMode = CommandLineOuterClassifierMode;
}
GVoxelForgeUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0 ? 1 : 0;
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
if (GVoxelForgePerfAttribution != 0) if (GVoxelForgePerfAttribution != 0)
{ {
@@ -1159,7 +1202,11 @@ void AVoxelWorld::BeginPlay()
Mesher->bGenerateSkirts = Settings->bGenerateSkirts; Mesher->bGenerateSkirts = Settings->bGenerateSkirts;
Mesher->SkirtCells = Settings->SkirtCells; Mesher->SkirtCells = Settings->SkirtCells;
Mesher->LODOctaveDrop = Settings->LODOctaveDrop; // T2.b — 0 = off Mesher->LODOctaveDrop = Settings->LODOctaveDrop; // T2.b — 0 = off
Mesher->bUseBlockEarlyOut = true; Mesher->bUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeClassifierConfig] outer_mode=%d nested_block_early_out=%d"),
GVoxelForgeOuterClassifierMode, Mesher->bUseBlockEarlyOut ? 1 : 0);
VoxelForgeStartupTrace::RecordEvent(TEXT("generator_mesher_constructed"), FString::Printf( VoxelForgeStartupTrace::RecordEvent(TEXT("generator_mesher_constructed"), FString::Printf(
TEXT("\"generator\":1,\"mesher\":1,\"generate_skirts\":%d,\"full_res_clip_levels\":%d,\"coarse_tile_cells\":%d"), TEXT("\"generator\":1,\"mesher\":1,\"generate_skirts\":%d,\"full_res_clip_levels\":%d,\"coarse_tile_cells\":%d"),
@@ -2102,6 +2149,25 @@ void AVoxelWorld::LogStreamingLatencySummary() const
const double RequestP95 = Percentile(RequestToReady, 0.95); const double RequestP95 = Percentile(RequestToReady, 0.95);
const double TravelP95Metres = RequestP95 const double TravelP95Metres = RequestP95
* static_cast<double>(PeakObservedPawnSpeedCmPerSecond) / 100.0; * static_cast<double>(PeakObservedPawnSpeedCmPerSecond) / 100.0;
FString ClassifierVerdictSummary;
for (int32 LOD = 0; LOD < TrackedClassifierLODCount; ++LOD)
{
if (LOD > 0)
{
ClassifierVerdictSummary += TEXT(";");
}
ClassifierVerdictSummary += FString::Printf(
TEXT("l%d[c=%llu,m=%llu,s=%llu,a=%llu]"),
LOD,
static_cast<unsigned long long>(OuterClassifierCallsByLOD[LOD].load(std::memory_order_relaxed)),
static_cast<unsigned long long>(OuterClassifierVerdictsByLOD[LOD][0].load(std::memory_order_relaxed)),
static_cast<unsigned long long>(OuterClassifierVerdictsByLOD[LOD][1].load(std::memory_order_relaxed)),
static_cast<unsigned long long>(OuterClassifierVerdictsByLOD[LOD][2].load(std::memory_order_relaxed)));
}
const uint64 WorkerGenerationCycles =
TotalWorkerGenerationCycles.load(std::memory_order_relaxed);
const uint64 ObsoleteWorkerCycles =
TotalObsoleteWorkerCycles.load(std::memory_order_relaxed);
const bool bDiagnosticsEnabled = GVoxelForgeProfileTileGeneration != 0 const bool bDiagnosticsEnabled = GVoxelForgeProfileTileGeneration != 0
|| VoxelForgeStartupTrace::IsActive() || VoxelForgeStartupTrace::IsActive()
|| VoxelDensityProfile::GetMode() != VoxelDensityProfile::EMode::Disabled; || VoxelDensityProfile::GetMode() != VoxelDensityProfile::EMode::Disabled;
@@ -2111,7 +2177,13 @@ void AVoxelWorld::LogStreamingLatencySummary() const
"queue_wait_s[p50=%.6f p95=%.6f max=%.6f] " "queue_wait_s[p50=%.6f p95=%.6f max=%.6f] "
"generation_s[p50=%.6f p95=%.6f max=%.6f] " "generation_s[p50=%.6f p95=%.6f max=%.6f] "
"collision_cook_s[p50=%.6f p95=%.6f max=%.6f] " "collision_cook_s[p50=%.6f p95=%.6f max=%.6f] "
"peak_speed_cm_s=%.3f p95_travel_m=%.6f obsolete_tile_aborts=%d"), "peak_speed_cm_s=%.3f p95_travel_m=%.6f obsolete_tile_aborts=%d "
"outer_classifier_mode=%d nested_block_early_out=%d "
"worker_generation_tasks=%llu worker_generation_sum_s=%.6f "
"validation_density_calls=%llu mesher_density_calls=%llu "
"validation_core_reuse_tiles=%llu "
"obsolete_worker_tasks=%llu obsolete_worker_sum_s=%.6f "
"outer_classifier_verdicts=%s"),
bDiagnosticsEnabled ? TEXT("profiled") : TEXT("clean"), bDiagnosticsEnabled ? TEXT("profiled") : TEXT("clean"),
bDiagnosticsEnabled ? TEXT("on") : TEXT("off"), bDiagnosticsEnabled ? TEXT("on") : TEXT("off"),
LOD0ReadySamples.Num(), LOD0ReadySamples.Num(),
@@ -2119,7 +2191,16 @@ void AVoxelWorld::LogStreamingLatencySummary() const
Percentile(QueueWait, 0.50), Percentile(QueueWait, 0.95), Maximum(QueueWait), Percentile(QueueWait, 0.50), Percentile(QueueWait, 0.95), Maximum(QueueWait),
Percentile(Generation, 0.50), Percentile(Generation, 0.95), Maximum(Generation), Percentile(Generation, 0.50), Percentile(Generation, 0.95), Maximum(Generation),
Percentile(CollisionCook, 0.50), Percentile(CollisionCook, 0.95), Maximum(CollisionCook), Percentile(CollisionCook, 0.50), Percentile(CollisionCook, 0.95), Maximum(CollisionCook),
PeakObservedPawnSpeedCmPerSecond, TravelP95Metres, ObsoleteTileAbortCount); PeakObservedPawnSpeedCmPerSecond, TravelP95Metres, ObsoleteTileAbortCount,
GVoxelForgeOuterClassifierMode, GVoxelForgeUseBlockEarlyOut,
static_cast<unsigned long long>(TotalWorkerGenerationTasks.load(std::memory_order_relaxed)),
FPlatformTime::ToSeconds64(WorkerGenerationCycles),
static_cast<unsigned long long>(TotalValidationDensityCalls.load(std::memory_order_relaxed)),
static_cast<unsigned long long>(TotalMesherDensityCalls.load(std::memory_order_relaxed)),
static_cast<unsigned long long>(TotalValidationCoreReuseTiles.load(std::memory_order_relaxed)),
static_cast<unsigned long long>(TotalObsoleteWorkerTasks.load(std::memory_order_relaxed)),
FPlatformTime::ToSeconds64(ObsoleteWorkerCycles),
*ClassifierVerdictSummary);
} }
void AVoxelWorld::HandleTileCollisionCookComplete( void AVoxelWorld::HandleTileCollisionCookComplete(
@@ -3644,7 +3725,19 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, bool bHighPriority)
? FPlatformTime::Cycles64() : 0; ? FPlatformTime::Cycles64() : 0;
Result.RequestStartCycles = RequestStartCycles; Result.RequestStartCycles = RequestStartCycles;
if (Cancellation->bObsolete.load(std::memory_order_relaxed) || Result.bAborted) const uint64 WorkerGenerationCycles = Result.GenerationEndCycles >= GenerationStartCycles
? Result.GenerationEndCycles - GenerationStartCycles : 0;
TotalWorkerGenerationTasks.fetch_add(1, std::memory_order_relaxed);
TotalWorkerGenerationCycles.fetch_add(WorkerGenerationCycles, std::memory_order_relaxed);
const bool bObsoleteWork = Cancellation->bObsolete.load(std::memory_order_relaxed)
|| Result.bObsolete;
if (bObsoleteWork)
{
TotalObsoleteWorkerTasks.fetch_add(1, std::memory_order_relaxed);
TotalObsoleteWorkerCycles.fetch_add(WorkerGenerationCycles, std::memory_order_relaxed);
}
if (bObsoleteWork || Result.bAborted)
{ {
if (!ShouldAbortWork()) if (!ShouldAbortWork())
{ {
@@ -3790,6 +3883,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
Result.MeshSeconds = 0.0; Result.MeshSeconds = 0.0;
Result.StreamSeconds = 0.0; Result.StreamSeconds = 0.0;
Result.NumTriangles = 0; Result.NumTriangles = 0;
Result.ValidationDensityCalls = 0;
Result.MesherDensityCalls = 0;
Result.bValidationCoreReused = false;
Result.Streams.Reset(); Result.Streams.Reset();
Result.CaptureGrid.Reset(); Result.CaptureGrid.Reset();
Result.BandChunkLo = BandChunkLo; // strate content cut (MIN/MAX = uncut) Result.BandChunkLo = BandChunkLo; // strate content cut (MIN/MAX = uncut)
@@ -3821,7 +3917,12 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
// même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre). // même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre).
// (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.) // (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.)
bool bTrivialEmpty = false; bool bTrivialEmpty = false;
if (!bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f) FVoxelMesherCoreDensityGrid ValidationCoreGrid;
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr;
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
const bool bUseValidationCoreReuse = OuterClassifierMode == 2;
if (OuterClassifierMode != 0
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
{ {
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile); TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile);
INC_DWORD_STAT(STAT_VoxelForgeTilesClassified); INC_DWORD_STAT(STAT_VoxelForgeTilesClassified);
@@ -3849,37 +3950,73 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
TGuardValue<int32> ValidationSampleStepGuard( TGuardValue<int32> ValidationSampleStepGuard(
VoxelGenLOD::SampleStep, ValidationStep); VoxelGenLOD::SampleStep, ValidationStep);
if (bUseValidationCoreReuse)
{
ValidationCoreGrid.OriginVoxels = OriginVoxels;
ValidationCoreGrid.Step = ValidationStep;
ValidationCoreGrid.CellsPerAxis = ValidationCells;
const int32 ValidationDim = ValidationCells + 1;
ValidationCoreGrid.Samples.SetNumUninitialized(
ValidationDim * ValidationDim * ValidationDim);
}
bool bExactUniform = true; bool bExactUniform = true;
for (int32 Z = 0; Z <= ValidationCells && bExactUniform && !bValidationAborted; ++Z) for (int32 Z = 0; Z <= ValidationCells
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Z)
{ {
if (ShouldAbortWork(ObsoleteFlag)) if (ShouldAbortWork(ObsoleteFlag))
{ {
bValidationAborted = true; bValidationAborted = true;
break; break;
} }
for (int32 Y = 0; Y <= ValidationCells && bExactUniform && !bValidationAborted; ++Y) for (int32 Y = 0; Y <= ValidationCells
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Y)
{ {
for (int32 X = 0; X <= ValidationCells; ++X) for (int32 X = 0; X <= ValidationCells; ++X)
{ {
if (ShouldAbortWork(ObsoleteFlag))
{
bValidationAborted = true;
break;
}
const float Density = Generator->GetDensityAt( const float Density = Generator->GetDensityAt(
OriginVoxels.X + X * ValidationStep, OriginVoxels.X + X * ValidationStep,
OriginVoxels.Y + Y * ValidationStep, OriginVoxels.Y + Y * ValidationStep,
OriginVoxels.Z + Z * ValidationStep); OriginVoxels.Z + Z * ValidationStep);
if (!FMath::IsFinite(Density) || Density == 0.0f ++Result.ValidationDensityCalls;
|| (Verdict == EVoxelTileClass::AllSolid && Density >= 0.0f) if (bUseValidationCoreReuse)
|| (Verdict == EVoxelTileClass::AllAir && Density <= 0.0f)) {
const int32 ValidationDim = ValidationCells + 1;
ValidationCoreGrid.Samples[
(Z * ValidationDim + Y) * ValidationDim + X] = Density;
}
const bool bSampleAgrees = FMath::IsFinite(Density) && Density != 0.0f
&& (Verdict != EVoxelTileClass::AllSolid || Density < 0.0f)
&& (Verdict != EVoxelTileClass::AllAir || Density > 0.0f);
if (!bSampleAgrees)
{ {
bExactUniform = false; bExactUniform = false;
break; if (!bUseValidationCoreReuse)
{
break;
}
} }
} }
} }
} }
TotalValidationDensityCalls.fetch_add(
static_cast<uint64>(Result.ValidationDensityCalls), std::memory_order_relaxed);
if (!bExactUniform) if (!bExactUniform)
{ {
// A false Mixed costs meshing; a false uniform verdict removes both render and // A false Mixed costs meshing; a false uniform verdict removes both render and
// collision geometry. Fall back to the safe path whenever one exact vertex disagrees. // collision geometry. Fall back to the safe path whenever one exact vertex disagrees.
ValidatedVerdict = EVoxelTileClass::Mixed; ValidatedVerdict = EVoxelTileClass::Mixed;
if (bUseValidationCoreReuse && !bValidationAborted)
{
ReusableCoreGrid = &ValidationCoreGrid;
Result.bValidationCoreReused = true;
TotalValidationCoreReuseTiles.fetch_add(1, std::memory_order_relaxed);
}
} }
} }
if (bValidationAborted) if (bValidationAborted)
@@ -3891,6 +4028,13 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
ClassifySeconds = bMeasureTile ? FPlatformTime::Seconds() - ClassifyStartSeconds : 0.0; ClassifySeconds = bMeasureTile ? FPlatformTime::Seconds() - ClassifyStartSeconds : 0.0;
ClassifyVerdict = static_cast<int32>(ValidatedVerdict); ClassifyVerdict = static_cast<int32>(ValidatedVerdict);
Result.ClassifyVerdict = ClassifyVerdict; Result.ClassifyVerdict = ClassifyVerdict;
const int32 TrackedLOD = FMath::Clamp(Tile.Level, 0, TrackedClassifierLODCount - 1);
OuterClassifierCallsByLOD[TrackedLOD].fetch_add(1, std::memory_order_relaxed);
if (ClassifyVerdict >= 0 && ClassifyVerdict < 3)
{
OuterClassifierVerdictsByLOD[TrackedLOD][ClassifyVerdict].fetch_add(
1, std::memory_order_relaxed);
}
if (ShouldAbortWork(ObsoleteFlag)) if (ShouldAbortWork(ObsoleteFlag))
{ {
AbortResult(); AbortResult();
@@ -3923,7 +4067,10 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
HoleMinX, HoleMinY, HoleMaxX, HoleMaxY) HoleMinX, HoleMinY, HoleMaxX, HoleMaxY)
: Mesher->GenerateMesh(OriginVoxels, Step, Cells, : Mesher->GenerateMesh(OriginVoxels, Step, Cells,
bWantCapture ? &Result.CaptureGrid : nullptr, bWantCapture ? &Result.CaptureGrid : nullptr,
BandVoxLo, BandVoxHi); BandVoxLo, BandVoxHi, ReusableCoreGrid,
&Result.MesherDensityCalls);
TotalMesherDensityCalls.fetch_add(
static_cast<uint64>(Result.MesherDensityCalls), std::memory_order_relaxed);
MeshSeconds = bMeasureTile ? FPlatformTime::Seconds() - MeshStartSeconds : 0.0; MeshSeconds = bMeasureTile ? FPlatformTime::Seconds() - MeshStartSeconds : 0.0;
INC_DWORD_STAT(STAT_VoxelForgeTilesMeshed); INC_DWORD_STAT(STAT_VoxelForgeTilesMeshed);
if (ShouldAbortWork(ObsoleteFlag)) if (ShouldAbortWork(ObsoleteFlag))
@@ -38,6 +38,8 @@ struct FVoxelSharedDensityGrid
} }
}; };
struct FVoxelMesherCoreDensityGrid;
UCLASS(BlueprintType) UCLASS(BlueprintType)
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
{ {
@@ -69,7 +71,9 @@ public:
*/ */
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE, FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
TArray<uint8>* OutCaptureGrid = nullptr, TArray<uint8>* OutCaptureGrid = nullptr,
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX); int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr,
int64* OutDensitySampleCount = nullptr);
/** /**
* F18 — FEUILLE de champ lointain (anneau render-distance, cf. UVoxelSettings::bFarSheetRing). * F18 — FEUILLE de champ lointain (anneau render-distance, cf. UVoxelSettings::bFarSheetRing).
@@ -162,3 +166,22 @@ public:
// Réduction d'octaves sur les tuiles grossières ; 0 = désactivé. // Réduction d'octaves sur les tuiles grossières ; 0 = désactivé.
int32 LODOctaveDrop = 0; int32 LODOctaveDrop = 0;
}; };
// A per-tile core lattice produced by the outer classifier's exact validation. Unlike
// FVoxelSharedDensityGrid this is intentionally local to one GenerateMesh call: validation can
// run concurrently on several streaming workers without mutating shared mesher state.
struct FVoxelMesherCoreDensityGrid
{
FIntVector OriginVoxels = FIntVector::ZeroValue;
int32 Step = 1;
int32 CellsPerAxis = 0;
TArray<float> Samples;
void Reset()
{
OriginVoxels = FIntVector::ZeroValue;
Step = 1;
CellsPerAxis = 0;
Samples.Reset();
}
};
+18
View File
@@ -107,6 +107,11 @@ struct FChunkResult
uint64 GenerationEndCycles = 0; uint64 GenerationEndCycles = 0;
uint64 ApplyStartCycles = 0; uint64 ApplyStartCycles = 0;
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0; int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0;
// Clean, low-overhead work counters. These are carried with the result so the worker can
// count exact validation and mesher density calls locally, then publish one aggregate update.
int64 ValidationDensityCalls = 0;
int64 MesherDensityCalls = 0;
bool bValidationCoreReused = false;
bool bAborted = false; // Worker observed shutdown or cancellation; never mark this tile loaded bool bAborted = false; // Worker observed shutdown or cancellation; never mark this tile loaded
bool bObsolete = false; // Worker observed per-tile desired-set cancellation bool bObsolete = false; // Worker observed per-tile desired-set cancellation
uint32 DesiredEpoch = 0; // Desired-set epoch captured at request time uint32 DesiredEpoch = 0; // Desired-set epoch captured at request time
@@ -915,6 +920,19 @@ public:
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
float PeakObservedPawnSpeedCmPerSecond = 0.0f; float PeakObservedPawnSpeedCmPerSecond = 0.0f;
// Clean performance accounting. LOD 0..8 covers every marching-cubes clip level; sheet tiles
// above the configured MC range never enter the outer classifier.
static constexpr int32 TrackedClassifierLODCount = 9;
std::atomic<uint64> TotalWorkerGenerationCycles{0};
std::atomic<uint64> TotalWorkerGenerationTasks{0};
std::atomic<uint64> TotalObsoleteWorkerCycles{0};
std::atomic<uint64> TotalObsoleteWorkerTasks{0};
std::atomic<uint64> TotalValidationDensityCalls{0};
std::atomic<uint64> TotalMesherDensityCalls{0};
std::atomic<uint64> TotalValidationCoreReuseTiles{0};
std::atomic<uint64> OuterClassifierCallsByLOD[TrackedClassifierLODCount]{};
std::atomic<uint64> OuterClassifierVerdictsByLOD[TrackedClassifierLODCount][3]{};
struct FStreamingLatencySample struct FStreamingLatencySample
{ {
double RequestToReadySeconds = 0.0; double RequestToReadySeconds = 0.0;