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
@@ -39,6 +39,8 @@ namespace
|
||||
bool ForceGenerate=false;
|
||||
bool MeshCoverage=false;
|
||||
bool ScreenSpaceGate=false;
|
||||
bool AuditUniformBlockProofs=false;
|
||||
bool ProfileLodWork=false;
|
||||
bool HasSeedOverride=false;
|
||||
int SeedOverride=0;
|
||||
bool HasRenderDistanceOverride=false;
|
||||
@@ -111,6 +113,8 @@ namespace
|
||||
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||
" --screen-space-gate (measure display error and historical negative controls)\n"
|
||||
" --audit-uniform-block-proofs (fill and check every interval-certified block)\n"
|
||||
" --profile-lod-work (report per-LOD density sample counts)\n"
|
||||
" --render-distance-chunks=N (0 disables the extra far ring)\n"
|
||||
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
|
||||
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
|
||||
@@ -126,6 +130,8 @@ namespace
|
||||
else if(arg=="--force-generate")options.ForceGenerate=true;
|
||||
else if(arg=="--mesh-coverage")options.MeshCoverage=true;
|
||||
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
|
||||
else if(arg=="--audit-uniform-block-proofs")options.AuditUniformBlockProofs=true;
|
||||
else if(arg=="--profile-lod-work")options.ProfileLodWork=true;
|
||||
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
|
||||
else if(arg.starts_with("--recipe="))
|
||||
{
|
||||
@@ -348,5 +354,5 @@ int main(int argc,char** argv)
|
||||
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
|
||||
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
|
||||
options.CameraPitchDegrees,options.ScreenSpaceGate,options.UseSharedColumnCache,
|
||||
cameraStepVoxels);
|
||||
cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork);
|
||||
}
|
||||
@@ -138,7 +138,8 @@ namespace StandaloneStreamBenchmark
|
||||
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false,
|
||||
const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f,
|
||||
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false,
|
||||
bool bUseSharedColumnCache=true,FVector CameraStepVoxels=FVector::ZeroVector)
|
||||
bool bUseSharedColumnCache=true,FVector CameraStepVoxels=FVector::ZeroVector,
|
||||
bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -264,6 +265,7 @@ namespace StandaloneStreamBenchmark
|
||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true);
|
||||
const auto StreamStart=Clock::now();
|
||||
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
||||
@@ -330,6 +332,7 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
||||
std::array<double,9> MeshAssemblyMsByLOD{};
|
||||
std::array<double,9> SkirtMsByLOD{};
|
||||
std::array<std::uint64_t,9> DensitySamplesByLOD{};
|
||||
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
|
||||
std::array<std::uint64_t,9> TileCountByLOD{};
|
||||
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||
@@ -354,6 +357,8 @@ namespace StandaloneStreamBenchmark
|
||||
GateSampleDistances.fill(std::numeric_limits<double>::infinity());
|
||||
Clock::time_point NeighborhoodReady{},FullDetailReady{},WholeViewReady{};
|
||||
bool bNeighborhoodReported=false,bFullDetailReported=false;
|
||||
uint64 BlockProofBlocksChecked=0,BlockProofSamplesChecked=0;
|
||||
uint64 BlockProofFallbackBlocks=0,BlockProofUnknownBlocks=0,BlockProofViolations=0;
|
||||
while(Finished<Update.DesiredTiles)
|
||||
{
|
||||
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
|
||||
@@ -379,7 +384,16 @@ namespace StandaloneStreamBenchmark
|
||||
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
||||
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
||||
SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
|
||||
if(bReportLodWorkProfile)DensitySamplesByLOD[LOD]+=Tile.DensitySamples;
|
||||
SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
|
||||
if(bAuditUniformBlockProofs)
|
||||
{
|
||||
BlockProofBlocksChecked+=Tile.CoreDiagnostics.uniformBlockProofsChecked;
|
||||
BlockProofSamplesChecked+=Tile.CoreDiagnostics.uniformBlockProofSamplesChecked;
|
||||
BlockProofFallbackBlocks+=Tile.CoreDiagnostics.uniformBlockProofFallbackBlocks;
|
||||
BlockProofUnknownBlocks+=Tile.CoreDiagnostics.uniformBlockProofUnknownBlocks;
|
||||
BlockProofViolations+=Tile.CoreDiagnostics.uniformBlockProofViolations;
|
||||
}
|
||||
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
|
||||
&&!Tile.Vertices.empty()
|
||||
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
|
||||
@@ -600,6 +614,13 @@ namespace StandaloneStreamBenchmark
|
||||
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
|
||||
SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
|
||||
MeshAssemblyMsByLOD[LOD]);
|
||||
if(bReportLodWorkProfile)
|
||||
{
|
||||
const double SamplesPerTile=TileCountByLOD[LOD]
|
||||
?static_cast<double>(DensitySamplesByLOD[LOD])/TileCountByLOD[LOD]:0.0;
|
||||
std::printf("[WorldAssemblerLODSamples] lod=%zu samples=%llu samples_per_tile=%.1f\n",
|
||||
LOD,static_cast<unsigned long long>(DensitySamplesByLOD[LOD]),SamplesPerTile);
|
||||
}
|
||||
}
|
||||
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
|
||||
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
|
||||
@@ -625,6 +646,13 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(NativeColumnMisses),
|
||||
NativeColumnComputeMilliseconds,NativeClassificationMilliseconds,
|
||||
NativeMeshingMilliseconds);
|
||||
if(bAuditUniformBlockProofs)
|
||||
std::printf("[UniformBlockProofAudit] checked_blocks=%llu checked_samples=%llu fallback_blocks=%llu unknown_blocks=%llu violations=%llu\n",
|
||||
static_cast<unsigned long long>(BlockProofBlocksChecked),
|
||||
static_cast<unsigned long long>(BlockProofSamplesChecked),
|
||||
static_cast<unsigned long long>(BlockProofFallbackBlocks),
|
||||
static_cast<unsigned long long>(BlockProofUnknownBlocks),
|
||||
static_cast<unsigned long long>(BlockProofViolations));
|
||||
|
||||
bool bHashesPassing=true;
|
||||
if(bHashDensity)
|
||||
@@ -701,6 +729,7 @@ namespace StandaloneStreamBenchmark
|
||||
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
|
||||
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
|
||||
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)
|
||||
&&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing?0:6;
|
||||
&&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing
|
||||
&&(!bAuditUniformBlockProofs||BlockProofViolations==0)?0:6;
|
||||
}
|
||||
} // namespace StandaloneStreamBenchmark
|
||||
@@ -650,6 +650,8 @@ public:
|
||||
|
||||
void SetMesherStageProfilingEnabled(bool Enabled)
|
||||
{bProfileMesherStages=Enabled;}
|
||||
void SetUniformBlockProofAuditEnabled(bool Enabled)
|
||||
{bAuditUniformBlockProofs=Enabled;}
|
||||
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
||||
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
|
||||
bool ForceGenerate=false)
|
||||
@@ -821,6 +823,7 @@ private:
|
||||
Options.useSharedColumnCache=bSharedColumnCache;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
|
||||
Options.auditUniformBlockProofs=bAuditUniformBlockProofs&&!ForceGenerate;
|
||||
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
|
||||
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
Options.diagnosticColumnInterpolationStride=
|
||||
@@ -999,6 +1002,7 @@ private:
|
||||
bool bSharedColumnCache=true;
|
||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||
bool bProfileMesherStages=false;
|
||||
bool bAuditUniformBlockProofs=false;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
std::condition_variable Wake;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,6 +57,17 @@ struct FVoxelMarchingCubesStageTimings
|
||||
uint64 GradientMemoHits = 0;
|
||||
};
|
||||
|
||||
// Opt-in soundness audit for density blocks which the Core mesher would skip.
|
||||
// Audit de justesse facultatif pour les blocs de densite que Core sauterait.
|
||||
struct FVoxelUniformBlockProofAuditStats
|
||||
{
|
||||
uint64 CheckedBlocks = 0;
|
||||
uint64 CheckedSamples = 0;
|
||||
uint64 FallbackBlocks = 0;
|
||||
uint64 UnknownBlocks = 0;
|
||||
uint64 Violations = 0;
|
||||
};
|
||||
|
||||
// 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.
|
||||
struct FVoxelHybridSharpFeatureBox
|
||||
@@ -108,7 +119,8 @@ public:
|
||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
||||
std::vector<float>* OutDebugDensitySamples = nullptr,
|
||||
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr);
|
||||
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr,
|
||||
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr);
|
||||
|
||||
//=========================================================================
|
||||
// SERVICES (injectés par AVoxelWorld)
|
||||
|
||||
Reference in new issue
Block a user