standalone: owner review fixes -- paged streaming in Core, no LOD fins, LOD0 stair patches fixed

From the owner's fly-through ("very impressive, in some cases slightly better than the original"):
- Streaming: Core compiles world-aligned fine-detail pages for tiles entering the view and releases
  them when they leave; the viewer keeps resident tiles. One chunk crossing: 144 new tiles, 1,822
  kept (was a full rebuild). A tile built as a late page hashes identically (density, mesh, payload,
  topology) to the same tile from an initial build.
- LOD>=1 fins: off-surface planar connector panels are now checked against the source surface and
  projected onto it; coarse audit 0 off-surface panels; far survival passes, bad-shell still fails.
- LOD0 stair patches: 2,630 sign-changing edges had one endpoint at the fixed +-4.125 fallback
  (carve surfaces were excluded from distance storage); distances now completed analytically, 462
  packed signs corrected; the audit now fails on any sign edge missing either endpoint (0 of 952,997).
- W1/W8 hashes equal; routes 5642/5642; look, passage and far audits pass.
Verified by me, interleaved (8 workers, cold): 1.014 s vs 1.008 s for aeeedaa, no regression.
Open: a chunk crossing still costs ~1.2 s of page compilation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-05 18:17:25 +02:00
1 parent aeeedaa73d
commit 3b37d5702e
6 files changed
+939 -25

No files matched your search

+385 -17
View File
@@ -207,6 +207,170 @@ namespace
return Result;
}
struct FCoarsePanelAudit
{
uint64 Tiles=0,PlanarOpenComponents=0,OffSurfacePanels=0,Triangles=0;
double MaximumSourceErrorVoxels=0.0;
};
FCoarsePanelAudit AuditCoarseSurfacePanels(const FStandaloneCpuTile& Tile,
const VoxelForgeCore::World& Shape,const VoxelForgeCore::WorldConfiguration& Config,
bool bPrintCandidates)
{
FCoarsePanelAudit Result;
const size_t VertexCount=Tile.Vertices.size();
const size_t TriangleCount=Tile.Indices.size()/3;
if(Tile.Key.Level<1||VertexCount==0||TriangleCount==0)return Result;
std::vector<uint32> Parent(VertexCount);
std::vector<uint8> Rank(VertexCount,0);
std::iota(Parent.begin(),Parent.end(),0u);
const auto Find=[&](uint32 Value)
{
uint32 Root=Value;
while(Parent[Root]!=Root)Root=Parent[Root];
while(Parent[Value]!=Value)
{const uint32 Next=Parent[Value];Parent[Value]=Root;Value=Next;}
return Root;
};
const auto Union=[&](uint32 A,uint32 B)
{
A=Find(A);B=Find(B);if(A==B)return;
if(Rank[A]<Rank[B])std::swap(A,B);
Parent[B]=A;if(Rank[A]==Rank[B])++Rank[A];
};
const auto EdgeKey=[](uint32 A,uint32 B)
{if(A>B)std::swap(A,B);return (static_cast<uint64>(A)<<32)|B;};
std::unordered_map<uint64,uint8> EdgeUse;
EdgeUse.reserve(TriangleCount*3/2+1);
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
Union(A,B);Union(B,C);
for(const auto [U,V]:std::array<std::pair<uint32,uint32>,3>{{{A,B},{B,C},{C,A}}})
{
uint8& Count=EdgeUse[EdgeKey(U,V)];
if(Count<255)++Count;
}
}
struct FComponent
{
uint64 Triangles=0,InteriorFreeEdges=0,SourceSamples=0,OffSourceSamples=0;
double Normal[3]={0,0,0},NormalWeight=0,MaximumSourceError=0;
double WorstPosition[3]={0,0,0};
bool bWorstTriangleCentroid=false;
};
std::vector<FComponent> Components(VertexCount);
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
const uint32 Root=Find(A);FComponent& Stats=Components[Root];++Stats.Triangles;
const auto& PA=Tile.Vertices[A].Position;const auto& PB=Tile.Vertices[B].Position;
const auto& PC=Tile.Vertices[C].Position;
const double U[3]={PB[0]-PA[0],PB[1]-PA[1],PB[2]-PA[2]};
const double V[3]={PC[0]-PA[0],PC[1]-PA[1],PC[2]-PA[2]};
double N[3]={U[1]*V[2]-U[2]*V[1],U[2]*V[0]-U[0]*V[2],U[0]*V[1]-U[1]*V[0]};
const double Area2=std::sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]);
if(Area2>1.0e-12)
{
for(int32 Axis=0;Axis<3;++Axis)Stats.Normal[Axis]+=N[Axis]/Area2*Area2;
Stats.NormalWeight+=Area2;
}
}
const FIntVector Origin=Tile.Key.OriginVoxels();
const double TileMin[3]={static_cast<double>(Origin.X),static_cast<double>(Origin.Y),static_cast<double>(Origin.Z)};
const double TileMax[3]={TileMin[0]+Tile.Key.ExtentVoxels(),TileMin[1]+Tile.Key.ExtentVoxels(),TileMin[2]+Tile.Key.ExtentVoxels()};
const auto IsTileBoundaryEdge=[&](uint32 A,uint32 B)
{
for(int32 Axis=0;Axis<3;++Axis)
{
const double PA=Tile.Vertices[A].Position[Axis]*4.0;
const double PB=Tile.Vertices[B].Position[Axis]*4.0;
if((std::abs(PA-TileMin[Axis])<0.75&&std::abs(PB-TileMin[Axis])<0.75)
||(std::abs(PA-TileMax[Axis])<0.75&&std::abs(PB-TileMax[Axis])<0.75))return true;
}
return false;
};
for(const auto& [Key,Count]:EdgeUse)
{
if(Count!=1)continue;
const uint32 A=static_cast<uint32>(Key>>32),B=static_cast<uint32>(Key);
if(A>=VertexCount||B>=VertexCount||IsTileBoundaryEdge(A,B))continue;
++Components[Find(A)].InteriorFreeEdges;
}
const int32 Extent=Tile.Key.ExtentVoxels();
const int32 Cells=Tile.Key.Level<Config.fullResolutionLodCount
?CHUNK_SIZE:std::max(1,Config.coarseTileCells);
const double Step=std::max(1,Extent/Cells);
const double ResidualLimit=std::max(2.0,Step*0.10);
std::vector<uint8> Candidate(VertexCount,0);
for(size_t Root=0;Root<VertexCount;++Root)
{
FComponent& Stats=Components[Root];
if(Stats.Triangles==0||Stats.Triangles>256||Stats.InteriorFreeEdges==0||Stats.NormalWeight<=0.0)continue;
const double NormalLength=std::sqrt(Stats.Normal[0]*Stats.Normal[0]
+Stats.Normal[1]*Stats.Normal[1]+Stats.Normal[2]*Stats.Normal[2]);
const double Coherence=NormalLength/Stats.NormalWeight;
if(Coherence<0.995)continue;
Candidate[Root]=1;++Result.PlanarOpenComponents;
}
if(Result.PlanarOpenComponents==0)return Result;
for(size_t Vertex=0;Vertex<VertexCount;++Vertex)
{
const uint32 Root=Find(static_cast<uint32>(Vertex));
if(!Candidate[Root])continue;
const auto& P=Tile.Vertices[Vertex].Position;
const double Error=std::abs(Shape.densityAt({P[0]*4.0,P[1]*4.0,P[2]*4.0}));
FComponent& Stats=Components[Root];++Stats.SourceSamples;
if(Error>Stats.MaximumSourceError)
{
Stats.MaximumSourceError=Error;
std::copy_n(P,3,Stats.WorstPosition);
Stats.bWorstTriangleCentroid=false;
}
if(Error>ResidualLimit)++Stats.OffSourceSamples;
}
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
const uint32 Root=Find(A);if(!Candidate[Root])continue;
const auto& PA=Tile.Vertices[A].Position;const auto& PB=Tile.Vertices[B].Position;
const auto& PC=Tile.Vertices[C].Position;
const double Error=std::abs(Shape.densityAt({(PA[0]+PB[0]+PC[0])*4.0/3.0,
(PA[1]+PB[1]+PC[1])*4.0/3.0,(PA[2]+PB[2]+PC[2])*4.0/3.0}));
FComponent& Stats=Components[Root];++Stats.SourceSamples;
if(Error>Stats.MaximumSourceError)
{
Stats.MaximumSourceError=Error;
Stats.WorstPosition[0]=(PA[0]+PB[0]+PC[0])/3.0;
Stats.WorstPosition[1]=(PA[1]+PB[1]+PC[1])/3.0;
Stats.WorstPosition[2]=(PA[2]+PB[2]+PC[2])/3.0;
Stats.bWorstTriangleCentroid=true;
}
if(Error>ResidualLimit)++Stats.OffSourceSamples;
}
for(size_t Root=0;Root<VertexCount;++Root)
{
const FComponent& Stats=Components[Root];if(!Candidate[Root]||Stats.SourceSamples==0)continue;
const double OffShare=static_cast<double>(Stats.OffSourceSamples)/Stats.SourceSamples;
Result.MaximumSourceErrorVoxels=std::max(Result.MaximumSourceErrorVoxels,Stats.MaximumSourceError);
if(OffShare<0.25)continue;
++Result.OffSurfacePanels;Result.Triangles+=Stats.Triangles;
if(bPrintCandidates&&Result.OffSurfacePanels<=8)
std::printf("[CoarsePanelCandidate] lod=%d tile=(%d,%d,%d) triangles=%llu interior_free_edges=%llu source_error=%.3f vox off_share=%.1f%% limit=%.3f vox worst=(%.2f,%.2f,%.2f)m sample=%s\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(Stats.Triangles),
static_cast<unsigned long long>(Stats.InteriorFreeEdges),Stats.MaximumSourceError,
OffShare*100.0,ResidualLimit,
Stats.WorstPosition[0],Stats.WorstPosition[1],Stats.WorstPosition[2],
Stats.bWorstTriangleCentroid?"centroid":"vertex");
}
Result.Tiles=1;
return Result;
}
struct M4 { float M[4][4]{}; };
M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;}
M4 Transpose(M4 A){M4 B{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)B.M[I][J]=A.M[J][I];return B;}
@@ -277,6 +441,8 @@ namespace
uint64 Tiles=0;
uint64 Samples=0;
double WorkerSeconds=0.0;
double CorePageSeconds=0.0;
uint64 PageCoveredTiles=0;
std::chrono::steady_clock::time_point StartTime{};
bool bReported=false;
};
@@ -357,6 +523,7 @@ namespace
V3 CameraPositionOverride{};
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
std::unordered_map<FVoxelTileKey,FResidentTileMesh,FStandaloneTileKeyHash> ResidentTiles;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> DeferredLeaverTiles;
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
std::unordered_set<FStandaloneBatchKey,FStandaloneBatchKeyHash> DirtyBatchSet;
@@ -441,6 +608,7 @@ namespace
}
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
DirtyBatchSet.clear();DesiredTiles.clear();CrossingWork.clear();
DeferredLeaverTiles.clear();
for(int32 I=0;I<=MaxLevels;++I)
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;
std::fill(std::begin(ProfileCyclesByLevel[I]),std::end(ProfileCyclesByLevel[I]),0);
@@ -1019,25 +1187,69 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(!World||!Streamer)return;
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
if(NewDesired.CenterChunk==BuildTargetChunk)return;
if(bShapeWorld)
if(bShapeWorld&&ShapeWorld)
{
bRebuildPending=true;
std::printf("[ShapeRegion] camera crossed its compiled full-detail bounds; rebuilding the interval region.\n");
return;
const auto CoreTiles=ShapeWorld->desiredTiles(
{CameraVoxels.X,CameraVoxels.Y,CameraVoxels.Z});
NewDesired.Tiles.clear();NewDesired.Tiles.reserve(CoreTiles.size());
for(const auto& Key:CoreTiles)
NewDesired.Tiles.emplace_back(
FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
}
if(NewDesired.CenterChunk==BuildTargetChunk)return;
const FIntVector Previous=BuildTargetChunk;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
NewSet.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)NewSet.insert(Key);
std::vector<FVoxelTileKey> Leavers;
for(const auto& Pair:ResidentTiles)if(!NewSet.contains(Pair.first))Leavers.push_back(Pair.first);
for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key);
const auto CrossingStart=std::chrono::steady_clock::now();
double PageCompileSeconds=0.0;
uint64 PageCoveredTiles=0;
if(bShapeWorld&&ShapeWorld)
{
std::vector<VoxelForgeCore::TileKey> NewCoreTiles;
NewCoreTiles.reserve(NewDesired.Tiles.size());
for(const FVoxelTileKey& Key:NewDesired.Tiles)
if(!ResidentTiles.contains(Key))
NewCoreTiles.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
std::string PageError;
size_t CompiledPageCount=0;
if(!ShapeWorld->compileTileRegion(NewCoreTiles,
static_cast<uint32>(std::min(4,WorkerCount)),CoreScheduler,PageError,
&CompiledPageCount))
{
std::fprintf(stderr,"[ShapeRegionPage] compile failed at center=(%d,%d,%d): %s\n",
NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z,
PageError.c_str());
return;
}
PageCoveredTiles=static_cast<uint64>(CompiledPageCount);
std::vector<VoxelForgeCore::TileKey> CoreDesired;
CoreDesired.reserve(NewDesired.Tiles.size());
for(const FVoxelTileKey& Key:NewDesired.Tiles)
CoreDesired.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
ShapeWorld->retainTileRegions(CoreDesired);
for(const FVoxelTileKey& Key:Leavers)DeferredLeaverTiles.insert(Key);
const double PageSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-CrossingStart).count();
PageCompileSeconds=PageSeconds;
std::printf("[ShapeRegionPage] center=(%d,%d,%d) requested_tiles=%zu page_covered_tiles=%llu worker_limit=%d seconds=%.4f retained_resident=%zu deferred_leavers=%zu\n",
NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z,
NewCoreTiles.size(),static_cast<unsigned long long>(PageCoveredTiles),
std::min(4,WorkerCount),PageSeconds,
ResidentTiles.size()-std::min(ResidentTiles.size(),Leavers.size()),Leavers.size());
}
else
{
for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key);
}
DesiredTiles=std::move(NewDesired.Tiles);
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
for(const auto& Pair:ResidentTiles)
if(NewSet.contains(Pair.first))ResidentKeys.push_back(Pair.first);
++CrossingIndex;
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex);
for(const auto& Pair:Update.CancelledByCrossing)
@@ -1048,12 +1260,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}
FCrossingWork& Work=CrossingWork[CrossingIndex];
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
Work.StartTime=std::chrono::steady_clock::now();
Work.StartTime=CrossingStart;
Work.PageCoveredTiles=PageCoveredTiles;Work.CorePageSeconds=PageCompileSeconds;
BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true;
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d dropped_resident=%zu cancelled_queued=%d full_detail=%d\n",
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d deferred_resident=%zu cancelled_queued=%d full_detail=%d\n",
static_cast<unsigned long long>(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z,
Update.CenterChunk.X-Previous.X,Update.CenterChunk.Y-Previous.Y,Update.CenterChunk.Z-Previous.Z,
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,Leavers.size(),
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,
bShapeWorld?Leavers.size():0,
Update.CancelledQueuedJobs,Update.FullResTiles);
std::fflush(stdout);
ReportCrossingWork();
@@ -1157,7 +1371,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{
if(!Streamer||World->FullResClipLevels<=0)return false;
int32 ResidentFullRes=0;
for(const auto& Pair:ResidentTiles)if(Pair.first.Level<World->FullResClipLevels)++ResidentFullRes;
for(const FVoxelTileKey& Key:DesiredTiles)
if(Key.Level<World->FullResClipLevels&&ResidentTiles.contains(Key))++ResidentFullRes;
if(ResidentFullRes!=Streamer->FullResDesiredCount())return false;
for(const FStandaloneBatchKey& Key:DirtyBatchSet)if(Key.Level<World->FullResClipLevels)return false;
return true;
@@ -1203,14 +1418,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
?Work.WorkerSeconds*1000.0/static_cast<double>(Work.FinishedJobs):0.0;
const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0
?std::chrono::duration<double>(std::chrono::steady_clock::now()-Work.StartTime).count():0.0;
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM page_covered_tiles=%llu core_page=%.4fs worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
static_cast<unsigned long long>(Pair.first),
static_cast<unsigned long long>(Work.SubmittedJobs),
static_cast<unsigned long long>(Work.FinishedJobs),
static_cast<unsigned long long>(Work.Tiles),
static_cast<unsigned long long>(Work.CancelledQueuedJobs),
static_cast<unsigned long long>(Work.StaleResults),
static_cast<double>(Work.Samples)/1.0e6,Work.WorkerSeconds,WallSeconds,AverageMs);
static_cast<double>(Work.Samples)/1.0e6,
static_cast<unsigned long long>(Work.PageCoveredTiles),Work.CorePageSeconds,
Work.WorkerSeconds,WallSeconds,AverageMs);
}
}
@@ -1240,6 +1457,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}
ReportCrossingWork();
UploadDirtyBatches();
if(!DeferredLeaverTiles.empty()&&DirtyBatchSet.empty())
{
bool bDesiredResident=true;
for(const FVoxelTileKey& Key:DesiredTiles)
if(!ResidentTiles.contains(Key)){bDesiredResident=false;break;}
if(bDesiredResident)
{
std::vector<FVoxelTileKey> ReadyToDrop;
for(const FVoxelTileKey& Key:DeferredLeaverTiles)
{
if(Streamer->IsDesired(Key))continue;
if(ResidentTiles.contains(Key))ReadyToDrop.push_back(Key);
}
for(const FVoxelTileKey& Key:ReadyToDrop)RemoveResidentTile(Key);
DeferredLeaverTiles.clear();
}
}
if(bShapePipelineActive)TryCompleteShapePipeline();
if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted
&&DirtyBatchSet.empty())
@@ -1262,17 +1496,20 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
ReportTileTimings();std::fflush(stdout);
StartFarShapeGeneration();
}
if(!bInitialClipmapReported&&ResidentTiles.size()==DesiredTiles.size()&&DirtyBatchSet.empty())
size_t DesiredResidentCount=0;
for(const FVoxelTileKey& Key:DesiredTiles)
if(ResidentTiles.contains(Key))++DesiredResidentCount;
if(!bInitialClipmapReported&&DesiredResidentCount==DesiredTiles.size()&&DirtyBatchSet.empty())
{
bInitialClipmapReported=true;
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
std::printf("[InitialClipmapReady] %.3f s after cold process start; %zu tiles, %llu vertices, %llu indices, %zu GPU batches.\n",
Elapsed,ResidentTiles.size(),static_cast<unsigned long long>(TotalVertices),
Elapsed,DesiredResidentCount,static_cast<unsigned long long>(TotalVertices),
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
ReportTileTimings();std::fflush(stdout);
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
}
bGenerationRunning=ResidentTiles.size()!=DesiredTiles.size()||!DirtyBatchSet.empty();
bGenerationRunning=DesiredResidentCount!=DesiredTiles.size()||!DirtyBatchSet.empty();
}
void CaptureRenderedFrame()
@@ -2207,6 +2444,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
double FullDetailSeconds=0.0;
const int32 DesiredTotal=Update.DesiredTiles;
FMeshSheetAudit MeshSheetStats;
FCoarsePanelAudit CoarsePanelStats;
uint64 MeshAuditedTiles=0;
while(Finished<DesiredTotal)
{
@@ -2263,6 +2501,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
TileAudit.MinimumThicknessVoxels);
++MeshAuditedTiles;
}
if(bShardAudit&&Level>=1&&CoreWorld)
{
const FCoarsePanelAudit TileAudit=AuditCoarseSurfacePanels(
Tile,*CoreWorld,CoreWorld->configuration(),CoarsePanelStats.OffSurfacePanels<8);
CoarsePanelStats.Tiles+=TileAudit.Tiles;
CoarsePanelStats.PlanarOpenComponents+=TileAudit.PlanarOpenComponents;
CoarsePanelStats.OffSurfacePanels+=TileAudit.OffSurfacePanels;
CoarsePanelStats.Triangles+=TileAudit.Triangles;
CoarsePanelStats.MaximumSourceErrorVoxels=std::max(
CoarsePanelStats.MaximumSourceErrorVoxels,TileAudit.MaximumSourceErrorVoxels);
}
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
}
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
@@ -2350,6 +2599,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
static_cast<unsigned long long>(MeshSheetStats.ThinSheets),
static_cast<unsigned long long>(MeshSheetStats.TrianglesInThinSheets),
MeshSheetStats.ThinSheets==0&&MeshSheetStats.SmallComponentsInView==0?"PASS":"FAIL");
if(bShardAudit&&CoreWorld)
std::printf("[CoarsePanelAudit] tiles=%llu planar_open_components=%llu off_surface_panels=%llu panel_triangles=%llu max_source_error=%.3f vox status=%s\n",
static_cast<unsigned long long>(CoarsePanelStats.Tiles),
static_cast<unsigned long long>(CoarsePanelStats.PlanarOpenComponents),
static_cast<unsigned long long>(CoarsePanelStats.OffSurfacePanels),
static_cast<unsigned long long>(CoarsePanelStats.Triangles),
CoarsePanelStats.MaximumSourceErrorVoxels,
CoarsePanelStats.OffSurfacePanels==0?"PASS":"FAIL");
for(int32 Level=0;Level<=MaxLevels;++Level)
{
if(TilesByLevel[Level]==0)continue;
@@ -2392,6 +2649,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
if(bShardAudit&&CoreWorld&&CoarsePanelStats.OffSurfacePanels!=0)return 13;
if(FarAudit)
{
FarAudit->Finalize();
@@ -2483,6 +2741,106 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
AffectedByLevel[Level]==FinishedByLevel[Level]?"PASS":"FAIL");
return Finished==static_cast<int32>(Affected.size())?0:10;
}
bool MakeRegionBuildRequest(std::span<const VoxelForgeCore::TileKey> tiles,
const VoxelForgeCore::WorldConfiguration& config,int32 workers,
VoxelForgeCore::DetailVariant detail,bool roundTunnels,bool enhancedWalls,
VoxelForgeCore::ShapeBuildRequest& request,std::string& error)
{
FIntVector fineMin(MAX_int32,MAX_int32,MAX_int32),fineMax(MIN_int32,MIN_int32,MIN_int32);
FIntVector clipMin(MAX_int32,MAX_int32,MAX_int32),clipMax(MIN_int32,MIN_int32,MIN_int32);
for(const auto& key:tiles)
{
if(key.lod<0||key.lod>8){error="desired tile has an invalid LOD";return false;}
const int32 extent=CHUNK_SIZE<<key.lod;
const FIntVector origin(
static_cast<int32>(static_cast<int64>(key.coordinate.x)*extent),
static_cast<int32>(static_cast<int64>(key.coordinate.y)*extent),
static_cast<int32>(static_cast<int64>(key.coordinate.z)*extent));
for(int32 axis=0;axis<3;++axis)
{
(&clipMin.X)[axis]=std::min((&clipMin.X)[axis],(&origin.X)[axis]);
(&clipMax.X)[axis]=std::max((&clipMax.X)[axis],(&origin.X)[axis]+extent);
if(key.lod<config.fullResolutionLodCount)
{
(&fineMin.X)[axis]=std::min((&fineMin.X)[axis],(&origin.X)[axis]);
(&fineMax.X)[axis]=std::max((&fineMax.X)[axis],(&origin.X)[axis]+extent);
}
}
}
if(fineMin.X==MAX_int32||clipMin.X==MAX_int32)
{error="desired region contains no fine tiles or clip bounds";return false;}
request.minVoxel={fineMin.X,fineMin.Y,fineMin.Z};
request.maxVoxel={fineMax.X,fineMax.Y,fineMax.Z};
request.clipMinVoxel={clipMin.X,clipMin.Y,clipMin.Z};
request.clipMaxVoxel={clipMax.X,clipMax.Y,clipMax.Z};
request.workerLimit=static_cast<uint32>(std::clamp(workers,1,8));
request.detail=detail;request.roundTunnels=roundTunnels;
request.enhancedTunnelWalls=enhancedWalls;request.includeFarWorld=false;
return true;
}
int RunTilePathHashAudit(VoxelForgeCore::World& base,
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,
const VoxelForgeCore::JobScheduler& scheduler,const FVector& camera,
int32 workers,VoxelForgeCore::DetailVariant detail,bool roundTunnels,
bool enhancedWalls)
{
const auto baseTiles=base.desiredTiles({camera.X,camera.Y,camera.Z});
const auto& config=base.configuration();
if(baseTiles.empty())
{std::fprintf(stderr,"[TilePathHashAudit] camera window is empty.\n");return 11;}
constexpr int32 TargetChunkOffset=40;
const int32 CameraChunkX=FMath::FloorToInt(camera.X/CHUNK_SIZE);
const int32 CameraChunkY=FMath::FloorToInt(camera.Y/CHUNK_SIZE);
const int32 CameraChunkZ=FMath::FloorToInt(camera.Z/CHUNK_SIZE);
const VoxelForgeCore::TileKey target{{CameraChunkX+TargetChunkOffset,
CameraChunkY,CameraChunkZ},0};
const FVector alternateCamera((target.coordinate.x+0.5)*CHUNK_SIZE,camera.Y,camera.Z);
const auto alternateTiles=base.desiredTiles(
{alternateCamera.X,alternateCamera.Y,alternateCamera.Z});
if(alternateTiles.empty())
{std::fprintf(stderr,"[TilePathHashAudit] target camera window is empty.\n");return 11;}
VoxelForgeCore::TileGenerationOptions options;options.hashDensity=true;
std::string error;
const std::array<VoxelForgeCore::TileKey,1> pageTiles={target};
std::size_t pageTilesA=0;
if(!base.compileTileRegion(pageTiles,static_cast<uint32>(std::clamp(workers,1,8)),
scheduler,error,&pageTilesA))
{std::fprintf(stderr,"[TilePathHashAudit] path-A page compile failed: %s\n",error.c_str());return 11;}
auto pathA=base.generateTile(target,options,error);
if(!pathA){std::fprintf(stderr,"[TilePathHashAudit] path-A generation failed: %s\n",error.c_str());return 11;}
VoxelForgeCore::World alternate(snapshot);
VoxelForgeCore::ShapeBuildRequest alternateRequest;
if(!MakeRegionBuildRequest(alternateTiles,config,workers,detail,roundTunnels,
enhancedWalls,alternateRequest,error)
||!alternate.compile(alternateRequest,scheduler,error))
{std::fprintf(stderr,"[TilePathHashAudit] alternate camera compile failed: %s\n",error.c_str());return 11;}
std::size_t pageTilesB=0;
auto pathB=alternate.generateTile(target,options,error);
if(!pathB){std::fprintf(stderr,"[TilePathHashAudit] path-B generation failed: %s\n",error.c_str());return 11;}
const auto Same=[&](const VoxelForgeCore::TileMesh& a,const VoxelForgeCore::TileMesh& b)
{
return a.densityHash==b.densityHash&&a.meshHash==b.meshHash
&&a.payloadHash==b.payloadHash&&a.topologyHash==b.topologyHash;
};
const int32 CameraDeltaX=static_cast<int32>(std::lround(alternateCamera.X-camera.X));
const bool pass=pageTilesA==1&&pageTilesB==0&&Same(*pathA,*pathB);
std::printf("[TilePathHashAudit] tile=(%d,%d,%d) lod=%d path_a=late_page path_b=initial_base camera_delta_vox=(%d,0,0) page_covered_tiles=(%zu,%zu) density=(0x%016llx,0x%016llx) mesh=(0x%016llx,0x%016llx) payload=(0x%016llx,0x%016llx) topology=(0x%016llx,0x%016llx) status=%s\n",
target.coordinate.x,target.coordinate.y,target.coordinate.z,target.lod,CameraDeltaX,
pageTilesA,pageTilesB,
static_cast<unsigned long long>(pathA->densityHash),
static_cast<unsigned long long>(pathB->densityHash),
static_cast<unsigned long long>(pathA->meshHash),
static_cast<unsigned long long>(pathB->meshHash),
static_cast<unsigned long long>(pathA->payloadHash),
static_cast<unsigned long long>(pathB->payloadHash),
static_cast<unsigned long long>(pathA->topologyHash),
static_cast<unsigned long long>(pathB->topologyHash),pass?"PASS":"FAIL");
return pass?0:12;
}
}
int main(int Argc,char** Argv)
@@ -2500,6 +2858,8 @@ int main(int Argc,char** Argv)
bool bStreamPipeline=false;
bool bProfileDetailBand=false;
bool bPassageDetailAuditOnly=false;
bool bGridStairAudit=false;
bool bTilePathHashAudit=false;
bool bShardAudit=false;
bool bFarSurvivalAudit=false;
bool bFarAuditBadShell=false;
@@ -2523,6 +2883,8 @@ int main(int Argc,char** Argv)
else if(Arg=="--density-hash")App.bDensityHash=true;
else if(Arg=="--shape-world")App.bShapeWorld=true;
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
else if(Arg=="--grid-stair-audit")bGridStairAudit=true,App.bShapeWorld=true;
else if(Arg=="--tile-path-hash-audit")bTilePathHashAudit=true,App.bShapeWorld=true;
else if(Arg=="--passage-wall-detail-audit")
{App.bShapeWorld=true;App.bShapeAuditOnly=true;bPassageDetailAuditOnly=true;}
else if(Arg=="--shape-compare")App.bShapeWorld=true,App.bShapeCompare=true;
@@ -2636,7 +2998,7 @@ int main(int Argc,char** Argv)
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
:(BenchmarkLevel>=App.World->FullResClipLevels));
if(App.bShapeWorld&&(App.bShapeAuditOnly||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
if(App.bShapeWorld&&(App.bShapeAuditOnly||bGridStairAudit||bTilePathHashAudit||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
&&!bStreamPipeline
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
@@ -2657,6 +3019,12 @@ int main(int Argc,char** Argv)
App.Yaw,App.Pitch);
return App.ShapeWorld->auditDetailPassing()&&bSurfacePassing?0:7;
}
if(bGridStairAudit)
return App.ShapeWorld->auditGridStairIntegrity()?0:9;
if(bTilePathHashAudit)
return RunTilePathHashAudit(*App.ShapeWorld,App.Snapshot,App.CoreScheduler,
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,App.ShapeDetailVariant,App.bRoundTunnels,App.bRoundTunnelEnhanced);
if(App.bShapeCompare)
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.Yaw,App.Pitch,App.WorkerCount,
ShapeCompareLevel,ShapeCompareTileLimit);