wip(assembler): round L9 -- LOD1 skips proven-uniform blocks: 95% fewer samples, view ~2% faster
Step-2 (LOD1) tiles of native SurfaceWorld now prove 8-cell blocks uniform from exact heightfield column extrema over the padded block (cells + normal halo) and skip their density fill; features, edits, mixed strata and Unknown verdicts still fill. LOD1 samples 42,875 -> 2,063 per tile. Audit: 80,502 skipped blocks (3 seeds) + 9,216 island blocks force-filled, 0 violations. All SurfaceWorld and FloatingIslands hashes unchanged at W1/W8/W12, Clang off and GCC default. Reviewer interleaved W12, no hashing: whole view 1.439 -> 1.409 s. L8 phase probes removed; --profile-lod-work and --audit-uniform-block-proofs are opt-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
82813fb13b
commit
7650abb755
10 files changed
+439
-48
No files matched your search
@@ -121,7 +121,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
||||
std::vector<float>* OutDebugDensitySamples,
|
||||
FVoxelMarchingCubesStageTimings* OutStageTimings)
|
||||
FVoxelMarchingCubesStageTimings* OutStageTimings,
|
||||
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||
const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||
@@ -130,6 +131,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
|
||||
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
||||
if (OutStageTimings) { *OutStageTimings = FVoxelMarchingCubesStageTimings{}; }
|
||||
if (OutBlockProofAudit) { *OutBlockProofAudit = FVoxelUniformBlockProofAuditStats{}; }
|
||||
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
|
||||
if (!Generator) return MeshData;
|
||||
const bool bEmitUV = !bStandaloneSkipUvAndColors && !bStandaloneSkipUV;
|
||||
@@ -550,22 +552,19 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
|
||||
MesherSetupTimer.End();
|
||||
|
||||
// Optional 8x8x8-cell block proof. The tile classifier has already returned Mixed, but
|
||||
// that is a conservative answer over the whole tile. A tile can still be uniformly solid
|
||||
// (or uniformly air) when its smaller blocks are each provable. Do this before allocating
|
||||
// or filling the density grid: the grid is only needed when at least one block can contain
|
||||
// a surface. A whole-tile early return is allowed only when every relevant block has the
|
||||
// SAME verdict; adjacent uniform blocks with opposite signs can contain a surface between
|
||||
// them. This is a proof-only optimization: ClassifyTile is conservative and no samples or
|
||||
// resolution are removed from a tile that reaches the mesher.
|
||||
// Optional 8x8x8-cell block proof. Mixed/Unknown blocks always fall back to exact density
|
||||
// sampling. The Core SurfaceWorld column-range proof at Step 2 can also omit evaluations
|
||||
// outside active blocks and their one-sample normal halo.
|
||||
TArray<uint8> BlockSkip;
|
||||
int32 BlocksPerAxis = 0;
|
||||
// LOD2's 16-cell tile is the remaining false-Mixed empty case in the streamed game path.
|
||||
// Its step-8 lattice can use the same proof-only 8-cell block partition as LOD0. Keep the
|
||||
// gate off for steps 2/16/32: their meshing work is already smaller than repeating a full
|
||||
// tile classifier per block, and this preserves the established coarse-LOD latency profile.
|
||||
// Step 2 is enabled only for a tile wholly inside one native SurfaceWorld stratum. Steps
|
||||
// 16/32 keep the historical gate off because their smaller grids do not amortize per-block queries.
|
||||
const bool bNativeSurfaceTileProof = Step == 2 && ShapeWorldContext != nullptr
|
||||
&& ShapeWorldContext->IsNativeSurfaceBox(OriginVoxels,Step,CellsPerAxis);
|
||||
const bool bUseProofBlocks = bUseBlockEarlyOut && !OutCaptureGrid
|
||||
&& (Step == 1 || Step == 8);
|
||||
&& (Step == 1 || Step == 8 || (Step == 2
|
||||
&& (bNativeSurfaceTileProof || OutBlockProofAudit != nullptr)));
|
||||
bool bCanSkipProofBlockGrid = bNativeSurfaceTileProof;
|
||||
if (bUseProofBlocks)
|
||||
{
|
||||
constexpr int32 BlockCells = 8;
|
||||
@@ -613,18 +612,27 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
VoxelDensityProfile::RecordClassifierCall(bSampleClassifier);
|
||||
}
|
||||
EVoxelTileClass Verdict;
|
||||
bool bBlockUnknown = false;
|
||||
bool bBlockNative = false;
|
||||
if(ShapeWorldContext&&Step<=2)
|
||||
{
|
||||
const auto ShapeVerdict=ShapeWorldContext->ClassifyBox(
|
||||
BlockOrigin,Step,BlockCells);
|
||||
Verdict=ShapeVerdict==EVoxelTileClass::AllSolid
|
||||
const auto ShapeVerdict=ShapeWorldContext->ClassifyBoxDetailed(
|
||||
BlockOrigin,Step,BlockCells,Step==2);
|
||||
Verdict=ShapeVerdict.Verdict==EVoxelTileClass::AllSolid
|
||||
?EVoxelTileClass::AllSolid
|
||||
:ShapeVerdict==EVoxelTileClass::AllAir
|
||||
:ShapeVerdict.Verdict==EVoxelTileClass::AllAir
|
||||
?EVoxelTileClass::AllAir:EVoxelTileClass::Mixed;
|
||||
bBlockUnknown=ShapeVerdict.bUnknown;
|
||||
bBlockNative=ShapeVerdict.bNativeSurface;
|
||||
if(Step==2&&!ShapeVerdict.bNativeSurface)
|
||||
bCanSkipProofBlockGrid=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(Step==2)bCanSkipProofBlockGrid=false;
|
||||
Verdict=Generator->ClassifyTile(BlockOrigin,Step,BlockCells,
|
||||
bSampleClassifier ? &ClassifierStats : nullptr);
|
||||
}
|
||||
if (bSampleClassifier)
|
||||
{
|
||||
VoxelDensityProfile::AddSampledMeasurement(
|
||||
@@ -699,20 +707,70 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
ClassifierStats.RoomTailCycles);
|
||||
}
|
||||
}
|
||||
if (Verdict == EVoxelTileClass::AllSolid
|
||||
|| Verdict == EVoxelTileClass::AllAir)
|
||||
{
|
||||
if(OutBlockProofAudit&&Step==2&&bBlockNative)
|
||||
{
|
||||
++OutBlockProofAudit->CheckedBlocks;
|
||||
const int32 ProofMinX=BlockX*BlockCells-1;
|
||||
const int32 ProofMinY=BlockY*BlockCells-1;
|
||||
const int32 ProofMinZ=BlockZ*BlockCells-1;
|
||||
const int32 ProofMaxX=BlockX*BlockCells+BlockCells+1;
|
||||
const int32 ProofMaxY=BlockY*BlockCells+BlockCells+1;
|
||||
const int32 ProofMaxZ=BlockZ*BlockCells+BlockCells+1;
|
||||
bool bUniform=true;
|
||||
for(int32 Z=ProofMinZ;Z<=ProofMaxZ&&bUniform;++Z)
|
||||
for(int32 Y=ProofMinY;Y<=ProofMaxY&&bUniform;++Y)
|
||||
for(int32 X=ProofMinX;X<=ProofMaxX;++X)
|
||||
{
|
||||
++OutBlockProofAudit->CheckedSamples;
|
||||
const float Density=ShapeWorldContext
|
||||
?ShapeWorldContext->DensityAtLattice(
|
||||
OriginVoxels.X+X*Step,
|
||||
OriginVoxels.Y+Y*Step,
|
||||
OriginVoxels.Z+Z*Step)
|
||||
:Generator->GetDensityAtLattice(
|
||||
OriginVoxels.X+X*Step,
|
||||
OriginVoxels.Y+Y*Step,
|
||||
OriginVoxels.Z+Z*Step);
|
||||
if(!VoxelMath::IsFinite(Density)||Density==0.0f
|
||||
||(Verdict==EVoxelTileClass::AllSolid
|
||||
?Density>=0.0f:Density<=0.0f))
|
||||
{
|
||||
std::printf("[UniformBlockProofViolation] tile_origin=(%d,%d,%d) step=%d cells=%d block=(%d,%d,%d) sample=(%d,%d,%d) expected=%s density=%.9g native=%d unknown=%d\n",
|
||||
OriginVoxels.X,OriginVoxels.Y,OriginVoxels.Z,Step,
|
||||
CellsPerAxis,BlockX,BlockY,BlockZ,
|
||||
OriginVoxels.X+X*Step,OriginVoxels.Y+Y*Step,
|
||||
OriginVoxels.Z+Z*Step,
|
||||
Verdict==EVoxelTileClass::AllSolid?"solid":"air",
|
||||
Density,bBlockNative?1:0,bBlockUnknown?1:0);
|
||||
bUniform=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!bUniform)
|
||||
{
|
||||
++OutBlockProofAudit->Violations;
|
||||
Verdict=EVoxelTileClass::Mixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Verdict == EVoxelTileClass::Mixed)
|
||||
{
|
||||
if(OutBlockProofAudit&&Step==2)
|
||||
{
|
||||
++OutBlockProofAudit->FallbackBlocks;
|
||||
if(bBlockUnknown)++OutBlockProofAudit->UnknownBlocks;
|
||||
}
|
||||
MixedBlockIndices.Add(
|
||||
((BlockZ * BlocksPerAxis) + BlockY) * BlocksPerAxis + BlockX);
|
||||
}
|
||||
if (Verdict == EVoxelTileClass::AllSolid
|
||||
|| Verdict == EVoxelTileClass::AllAir)
|
||||
{
|
||||
// Keep the classifier result as a candidate until the exact density grid
|
||||
// used by this mesher has been filled. ClassifyTile is conservative by
|
||||
// contract, but its proof and the mesher share worker-local caches; a
|
||||
// cache replacement must never turn that contract into a cell skip by
|
||||
// itself. The post-grid sign check below is the final proof consumed by
|
||||
// the cell loop and clears any candidate whose actual MC vertices disagree.
|
||||
// In the ordinary path, candidates are checked against the filled grid.
|
||||
// The Step-2 supremum interval path is itself the proof used to omit fills.
|
||||
BlockSkip[((BlockZ * BlocksPerAxis) + BlockY) * BlocksPerAxis + BlockX] =
|
||||
Verdict == EVoxelTileClass::AllSolid ? 1 : 2;
|
||||
if (!bHaveUniformBlock)
|
||||
@@ -806,7 +864,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// ClassifyTile path already relies on this same non-Mixed proof contract. Return before
|
||||
// allocating the density grid; no samples are skipped for a tile that can contain
|
||||
// geometry, and the final geometry remains byte-identical because this path emits none.
|
||||
if (bHaveUniformBlock && bAllRelevantBlocksSame && bAllRelevantBlocksUniform)
|
||||
if (bHaveUniformBlock && bAllRelevantBlocksSame && bAllRelevantBlocksUniform
|
||||
&& !(Step == 2 && OutDebugDensitySamples != nullptr))
|
||||
{
|
||||
return MeshData;
|
||||
}
|
||||
@@ -841,6 +900,43 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
&& SharedDeltaX + GridDim < ReuseGrid->Dim
|
||||
&& SharedDeltaY + GridDim < ReuseGrid->Dim
|
||||
&& SharedDeltaZ + GridDim < ReuseGrid->Dim;
|
||||
const bool bSkipProofBlockGrid = bUseProofBlocks && bCanSkipProofBlockGrid
|
||||
&& OutBlockProofAudit == nullptr && OutPreEditDensityGrid == nullptr
|
||||
&& !bUseSharedDensityGrid;
|
||||
static thread_local TArray<uint8> RequiredGridSamples;
|
||||
if(bSkipProofBlockGrid)
|
||||
{
|
||||
RequiredGridSamples.SetNumUninitialized(MDim*MDim*MDim);
|
||||
FMemory::Memzero(RequiredGridSamples.GetData(),
|
||||
static_cast<SIZE_T>(RequiredGridSamples.Num())*sizeof(uint8));
|
||||
constexpr int32 BlockCells=8;
|
||||
for(int32 BlockZ=0;BlockZ<BlocksPerAxis;++BlockZ)
|
||||
{
|
||||
const int32 BlockMinZ=BlockZ*BlockCells;
|
||||
const int32 BlockMaxZ=FMath::Min(CellsPerAxis-1,BlockMinZ+BlockCells-1);
|
||||
if(BlockMaxZ<CzLo||BlockMinZ>CzHi)continue;
|
||||
for(int32 BlockY=0;BlockY<BlocksPerAxis;++BlockY)
|
||||
for(int32 BlockX=0;BlockX<BlocksPerAxis;++BlockX)
|
||||
{
|
||||
const int32 BlockIndex=((BlockZ*BlocksPerAxis)+BlockY)*BlocksPerAxis+BlockX;
|
||||
if(BlockSkip[BlockIndex]!=0)continue;
|
||||
const int32 CellMinX=BlockX*BlockCells;
|
||||
const int32 CellMinY=BlockY*BlockCells;
|
||||
const int32 CellMaxX=FMath::Min(CellsPerAxis-1,CellMinX+BlockCells-1);
|
||||
const int32 CellMaxY=FMath::Min(CellsPerAxis-1,CellMinY+BlockCells-1);
|
||||
const int32 SampleMinX=FMath::Max(-1,CellMinX-1);
|
||||
const int32 SampleMaxX=FMath::Min(GridDim,CellMaxX+2);
|
||||
const int32 SampleMinY=FMath::Max(-1,CellMinY-1);
|
||||
const int32 SampleMaxY=FMath::Min(GridDim,CellMaxY+2);
|
||||
const int32 SampleMinZ=FMath::Max(GzLo,FMath::Max(CzLo,BlockMinZ)-1);
|
||||
const int32 SampleMaxZ=FMath::Min(GzHi,FMath::Min(CzHi,BlockMaxZ)+2);
|
||||
for(int32 Z=SampleMinZ;Z<=SampleMaxZ;++Z)
|
||||
for(int32 Y=SampleMinY;Y<=SampleMaxY;++Y)
|
||||
for(int32 X=SampleMinX;X<=SampleMaxX;++X)
|
||||
RequiredGridSamples[((Z+1)*MDim+(Y+1))*MDim+(X+1)]=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool bCapturePreEditGrid = OutPreEditDensityGrid != nullptr
|
||||
&& !bUseSharedDensityGrid
|
||||
&& Step == 1 && CellsPerAxis == CHUNK_SIZE
|
||||
@@ -1046,6 +1142,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz)
|
||||
{
|
||||
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
|
||||
if(bSkipProofBlockGrid&&RequiredGridSamples[LocalIndex]==0)return;
|
||||
if (bUseSharedDensityGrid)
|
||||
{
|
||||
const int32 SharedX = SharedDeltaX + gx;
|
||||
@@ -1092,9 +1189,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const FStandaloneShapeWorld* ActiveShapeWorld=ShapeWorldContext;
|
||||
const bool bUseShapeWorldZBatch=ActiveShapeWorld!=nullptr
|
||||
&&ActiveShapeWorld->IsAnalyticOnly()
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid;
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid;
|
||||
const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid;
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid;
|
||||
if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
|
||||
{
|
||||
for(const std::vector<int32>& YGroup:YGroups)
|
||||
@@ -1242,6 +1339,34 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
}
|
||||
if (OutDebugDensitySamples)
|
||||
{
|
||||
// Hash/debug runs need the complete reference density lattice. Let MC exercise the
|
||||
// sparse block path, then evaluate omitted samples before exporting that lattice.
|
||||
if(bSkipProofBlockGrid)
|
||||
{
|
||||
for(int32 gz=GzLo;gz<=GzHi;++gz)
|
||||
for(int32 gy=-1;gy<=GridDim;++gy)
|
||||
for(int32 gx=-1;gx<=GridDim;++gx)
|
||||
{
|
||||
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(gx+1);
|
||||
if(RequiredGridSamples[LocalIndex]!=0)continue;
|
||||
RequiredGridSamples[LocalIndex]=1;
|
||||
const int32 ReachBlockX=FMath::Clamp(gx,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockZ=FMath::Clamp(gz,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex=
|
||||
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
|
||||
const int32 SampleX=OriginVoxels.X+gx*Step;
|
||||
const int32 SampleY=OriginVoxels.Y+gy*Step;
|
||||
const int32 SampleZ=OriginVoxels.Z+gz*Step;
|
||||
DensityGrid[LocalIndex]=ShapeWorldContext
|
||||
?ShapeWorldContext->DensityAtLattice(SampleX,SampleY,SampleZ)
|
||||
:Generator->GetDensityAtLattice(SampleX,SampleY,SampleZ);
|
||||
if(OutDensitySampleCount)++(*OutDensitySampleCount);
|
||||
}
|
||||
}
|
||||
const size_t SampleCount = static_cast<size_t>(GzHi - GzLo + 1)
|
||||
* static_cast<size_t>(MDim) * static_cast<size_t>(MDim);
|
||||
OutDebugDensitySamples->reserve(SampleCount);
|
||||
@@ -1259,7 +1384,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
FPlatformTime::Cycles64()-StageDensityGridStartCycles)
|
||||
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||
|
||||
if (bUseProofBlocks)
|
||||
if (bUseProofBlocks&&(!bSkipProofBlockGrid||OutDebugDensitySamples!=nullptr))
|
||||
{
|
||||
// Finalize every block candidate against the exact density samples that the marching-
|
||||
// cubes loop will consume. This is intentionally redundant with ClassifyTile: it is a
|
||||
|
||||
Reference in new issue
Block a user