perf(streaming): outer tile classifier off by default; delete mode 2
Owner-approved, on the A/B measured in 83a0e4d: the classifier plus exact validation cost more
than it saved on the game path. With it off, ready p95 is 182 vs 190-195 ms, generation p95 is
125-127 vs 139-141 ms, and density calls drop 18%.
- GVoxelForgeOuterClassifierMode defaults to 0. Mode 1 stays reachable via
voxel.OuterClassifierMode=1 as an A/B instrument; the clamp is now 0..1.
- Mode 2 (validation samples reused as the mesher core grid) is deleted: the reuse grid,
FVoxelMesherCoreDensityGrid, GenerateMesh's ReusableCoreGrid parameter, bValidationCoreReused,
TotalValidationCoreReuseTiles and its log field. It could not fire: it reused the grid only
when validation disproved a uniform verdict, which never happened, and Mixed tiles never
validate. The mode-1 validation loop exits early again on the first disagreeing sample.
- All three GenerateMesh call sites checked; the density-count pointer binds after BandVoxHi.
Verified by Codex, checked by me against the logs:
- build ok; the measured process loaded the staged DLL, SHA-256 1024D8CF..04D
- export OBJ SHA-256 unchanged: b3e5f4c3..9b377
- a run WITHOUT the override prints outer_classifier_mode=0 validation_density_calls=0,
proving the new default took effect
- 841/841 tiles have identical triangle counts between mode 0 and mode 1. The 394 tiles mode 1
skipped as uniform produce 0 triangles in mode 0, so the strate band cut (applied by the
mesher, not by validation) was not hiding geometry.
Docs: ARCHITECTURE 8.10, CODEMAP (GenerateTileResult, ClassifyTile) and fable-idea T1.d record
that streaming no longer classifies by default. WORK-NEXT item 1 records the owner's playtest:
he can no longer fall through the world.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
This commit is contained in:
1 parent
83a0e4d85c
commit
c65b015f01
8 files changed
+34
-87
No files matched your search
@@ -38,7 +38,6 @@ namespace
|
||||
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
|
||||
TArray<uint8>* OutCaptureGrid,
|
||||
int32 BandZMinVox, int32 BandZMaxVox,
|
||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid,
|
||||
int64* OutDensitySampleCount)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||
@@ -230,13 +229,6 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer
|
||||
const int32 GridDim = CellsPerAxis + 1;
|
||||
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
|
||||
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
||||
// Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous).
|
||||
@@ -541,7 +533,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid && !bUseReusableCoreGrid
|
||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid
|
||||
&& GVoxelForgeUseOperatorBlock != 0;
|
||||
if (bUseOperatorBlock)
|
||||
{
|
||||
@@ -576,14 +568,6 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
||||
(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
|
||||
{
|
||||
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
||||
|
||||
@@ -82,13 +82,12 @@ namespace
|
||||
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;
|
||||
// Outer classifier benchmark modes: 0 = disabled, 1 = current discarded exact validation.
|
||||
int32 GVoxelForgeOuterClassifierMode = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeOuterClassifierMode(
|
||||
TEXT("voxel.OuterClassifierMode"),
|
||||
GVoxelForgeOuterClassifierMode,
|
||||
TEXT("Outer tile classifier mode: 0 off, 1 discarded validation, 2 validation-core reuse."));
|
||||
TEXT("Outer tile classifier mode: 0 off, 1 discarded exact validation."));
|
||||
|
||||
// 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
|
||||
@@ -1058,7 +1057,6 @@ void AVoxelWorld::BeginPlay()
|
||||
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);
|
||||
@@ -1098,7 +1096,7 @@ void AVoxelWorld::BeginPlay()
|
||||
GVoxelForgeOuterClassifierMode = CommandLineOuterClassifierMode;
|
||||
}
|
||||
GVoxelForgeUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0 ? 1 : 0;
|
||||
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
|
||||
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||
|
||||
if (GVoxelForgePerfAttribution != 0)
|
||||
{
|
||||
@@ -2181,7 +2179,6 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
||||
"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"),
|
||||
@@ -2197,7 +2194,6 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
||||
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);
|
||||
@@ -3885,7 +3881,6 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
Result.NumTriangles = 0;
|
||||
Result.ValidationDensityCalls = 0;
|
||||
Result.MesherDensityCalls = 0;
|
||||
Result.bValidationCoreReused = false;
|
||||
Result.Streams.Reset();
|
||||
Result.CaptureGrid.Reset();
|
||||
Result.BandChunkLo = BandChunkLo; // strate content cut (MIN/MAX = uncut)
|
||||
@@ -3917,10 +3912,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
// 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.)
|
||||
bool bTrivialEmpty = false;
|
||||
FVoxelMesherCoreDensityGrid ValidationCoreGrid;
|
||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr;
|
||||
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
|
||||
const bool bUseValidationCoreReuse = OuterClassifierMode == 2;
|
||||
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||
if (OuterClassifierMode != 0
|
||||
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
||||
{
|
||||
@@ -3950,19 +3942,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
TGuardValue<int32> ValidationSampleStepGuard(
|
||||
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;
|
||||
for (int32 Z = 0; Z <= ValidationCells
|
||||
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Z)
|
||||
&& bExactUniform && !bValidationAborted; ++Z)
|
||||
{
|
||||
if (ShouldAbortWork(ObsoleteFlag))
|
||||
{
|
||||
@@ -3970,7 +3952,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
break;
|
||||
}
|
||||
for (int32 Y = 0; Y <= ValidationCells
|
||||
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Y)
|
||||
&& bExactUniform && !bValidationAborted; ++Y)
|
||||
{
|
||||
for (int32 X = 0; X <= ValidationCells; ++X)
|
||||
{
|
||||
@@ -3984,22 +3966,13 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
OriginVoxels.Y + Y * ValidationStep,
|
||||
OriginVoxels.Z + Z * ValidationStep);
|
||||
++Result.ValidationDensityCalls;
|
||||
if (bUseValidationCoreReuse)
|
||||
{
|
||||
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;
|
||||
if (!bUseValidationCoreReuse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4011,12 +3984,6 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
// 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.
|
||||
ValidatedVerdict = EVoxelTileClass::Mixed;
|
||||
if (bUseValidationCoreReuse && !bValidationAborted)
|
||||
{
|
||||
ReusableCoreGrid = &ValidationCoreGrid;
|
||||
Result.bValidationCoreReused = true;
|
||||
TotalValidationCoreReuseTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bValidationAborted)
|
||||
@@ -4067,7 +4034,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
HoleMinX, HoleMinY, HoleMaxX, HoleMaxY)
|
||||
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
|
||||
bWantCapture ? &Result.CaptureGrid : nullptr,
|
||||
BandVoxLo, BandVoxHi, ReusableCoreGrid,
|
||||
BandVoxLo, BandVoxHi,
|
||||
&Result.MesherDensityCalls);
|
||||
TotalMesherDensityCalls.fetch_add(
|
||||
static_cast<uint64>(Result.MesherDensityCalls), std::memory_order_relaxed);
|
||||
|
||||
@@ -38,8 +38,6 @@ struct FVoxelSharedDensityGrid
|
||||
}
|
||||
};
|
||||
|
||||
struct FVoxelMesherCoreDensityGrid;
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
|
||||
{
|
||||
@@ -72,7 +70,6 @@ public:
|
||||
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
|
||||
TArray<uint8>* OutCaptureGrid = nullptr,
|
||||
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr,
|
||||
int64* OutDensitySampleCount = nullptr);
|
||||
|
||||
/**
|
||||
@@ -166,22 +163,3 @@ public:
|
||||
// Réduction d'octaves sur les tuiles grossières ; 0 = désactivé.
|
||||
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();
|
||||
}
|
||||
};
|
||||
@@ -111,7 +111,6 @@ struct FChunkResult
|
||||
// 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 bObsolete = false; // Worker observed per-tile desired-set cancellation
|
||||
uint32 DesiredEpoch = 0; // Desired-set epoch captured at request time
|
||||
@@ -929,7 +928,6 @@ public:
|
||||
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]{};
|
||||
|
||||
|
||||
Reference in new issue
Block a user