perf(core): tile-drain instrumentation; parallel FI sign-edge repair, FI first tile 1.28 -> 0.85 s (L55); R24
Opt-in --instrument-tile-drain records per-tile queue/dispatch/finish timelines, stage spans and counters (proof skips, X8 lanes, GradAt/midpoint memo, skirts, FI cache stats); <2% when off. Measured: SW 13.3 and FI 50.2 worker-s across LOD0-8, tails are work-saturated. FI sign-edge repair now computes in parallel and publishes in sorted serial order (exact output): sign-edge 535 -> 47 ms, FI first tile 1.28 -> 0.85 s, near ring 1.46 -> 1.04 s, whole view 6.09 -> 5.65 s. Proof block size, lane batching, SW midpoint memo and helper cap 12 measured and left off. fast/medium/full PASS, Clang and GCC. Research R24: slab + motif generator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
e35a77f676
commit
aee1487410
13 files changed
+882
-74
No files matched your search
@@ -471,6 +471,26 @@ namespace floating_island_detail
|
||||
double DeltaX=0.0,DeltaY=0.0,Radial=0.0,Angle=0.0,LobePhase=0.0,TopSurface=0.0;
|
||||
};
|
||||
|
||||
struct FIslandColumnCacheInstrumentation
|
||||
{
|
||||
std::uint64_t localHits=0,localMisses=0,localConflictReplacements=0;
|
||||
VoxelForgeCoreShared::FColumnCacheMetrics shared;
|
||||
};
|
||||
|
||||
inline FIslandColumnCacheInstrumentation*& ActiveIslandColumnCacheInstrumentation() noexcept
|
||||
{
|
||||
static thread_local FIslandColumnCacheInstrumentation* active=nullptr;
|
||||
return active;
|
||||
}
|
||||
|
||||
inline FIslandColumnCacheInstrumentation* SetIslandColumnCacheInstrumentation(
|
||||
FIslandColumnCacheInstrumentation* value) noexcept
|
||||
{
|
||||
FIslandColumnCacheInstrumentation* previous=ActiveIslandColumnCacheInstrumentation();
|
||||
ActiveIslandColumnCacheInstrumentation()=value;
|
||||
return previous;
|
||||
}
|
||||
|
||||
using FSharedIslandColumnCache =
|
||||
VoxelForgeCoreShared::TSharedColumnValueCache<FIslandColumnCacheEntry,16384,4>;
|
||||
|
||||
@@ -501,6 +521,7 @@ namespace floating_island_detail
|
||||
IndexKey^=IndexKey>>33;
|
||||
const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways;
|
||||
FIslandColumnCacheEntry* Replacement=&cache[Base+((IndexKey>>11)&(Ways-1))];
|
||||
bool bFoundInvalid=false;
|
||||
for(std::size_t Way=0;Way<Ways;++Way)
|
||||
{
|
||||
FIslandColumnCacheEntry& Entry=cache[Base+Way];
|
||||
@@ -516,8 +537,18 @@ namespace floating_island_detail
|
||||
&&Entry.TopHalf==island.topHalfSubvoxels
|
||||
&&Entry.TopFlattenQ16==recipe.topFlattenQ16
|
||||
&&Entry.ContourWarp==recipe.contourWarpSubvoxels;
|
||||
if(bKeyMatches)return Entry;
|
||||
if(!Entry.bValid)Replacement=&Entry;
|
||||
if(bKeyMatches)
|
||||
{
|
||||
if(auto* stats=ActiveIslandColumnCacheInstrumentation())++stats->localHits;
|
||||
return Entry;
|
||||
}
|
||||
if(!Entry.bValid&&!bFoundInvalid)
|
||||
{Replacement=&Entry;bFoundInvalid=true;}
|
||||
}
|
||||
if(auto* stats=ActiveIslandColumnCacheInstrumentation())
|
||||
{
|
||||
++stats->localMisses;
|
||||
if(!bFoundInvalid)++stats->localConflictReplacements;
|
||||
}
|
||||
|
||||
const auto build=[&]()
|
||||
@@ -562,7 +593,9 @@ namespace floating_island_detail
|
||||
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),
|
||||
plan.contentHash,SampleX,SampleY,island.stableId};
|
||||
bool computed=false;
|
||||
Entry=shared->getOrCompute(key,build,computed);
|
||||
auto* stats=ActiveIslandColumnCacheInstrumentation();
|
||||
Entry=shared->getOrCompute(key,build,computed,
|
||||
stats?&stats->shared:nullptr);
|
||||
}
|
||||
else Entry=build();
|
||||
*Replacement=Entry;
|
||||
|
||||
@@ -2246,7 +2246,8 @@ public:
|
||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
|
||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr)
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr,
|
||||
bool bInParallelSignEdgeRepair=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto TotalStart=Clock::now();
|
||||
@@ -2257,6 +2258,7 @@ public:
|
||||
BindResolvedStratumFields();
|
||||
bAnalyticOnly=false;
|
||||
bProfileDetailBand=bInProfileDetailBand;
|
||||
bParallelSignEdgeRepair=bInParallelSignEdgeRepair;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
Min[A]=(&MinVoxels.X)[A]-2;
|
||||
@@ -9084,6 +9086,8 @@ private:
|
||||
|
||||
uint64 CompleteSignEdgeDistances()
|
||||
{
|
||||
if(bParallelSignEdgeRepair&&Scheduler)
|
||||
return CompleteSignEdgeDistancesParallel();
|
||||
uint64 AddedTotal=0,SignCorrections=0;
|
||||
const size_t Columns=static_cast<size_t>(Size[0])*Size[1];
|
||||
for(int32 Pass=0;Pass<4;++Pass)
|
||||
@@ -9183,6 +9187,145 @@ private:
|
||||
return AddedTotal;
|
||||
}
|
||||
|
||||
uint64 CompleteSignEdgeDistancesParallel()
|
||||
{
|
||||
uint64 AddedTotal=0,SignCorrections=0;
|
||||
const size_t Columns=static_cast<size_t>(Size[0])*Size[1];
|
||||
for(int32 Pass=0;Pass<4;++Pass)
|
||||
{
|
||||
std::vector<size_t> MissingNodes;
|
||||
const auto AddMissing=[&](int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
if(X<Min[0]||X>Max[0]||Y<Min[1]||Y>Max[1]||Z<Min[2]||Z>Max[2])return;
|
||||
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||
+static_cast<size_t>(X-Min[0]);
|
||||
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||
const size_t Word=Column*WordsPerZ+(Zi>>6);
|
||||
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||
if((CandidateWords[Word]&Mask)==0)
|
||||
MissingNodes.push_back((Zi*static_cast<size_t>(Size[1])
|
||||
+static_cast<size_t>(Y-Min[1]))*Size[0]
|
||||
+static_cast<size_t>(X-Min[0]));
|
||||
};
|
||||
|
||||
// Discover against a stable pass snapshot. Density work is then independent per
|
||||
// lattice node; packed candidates and occupancy corrections publish in node order.
|
||||
for(size_t Line=0;Line<Columns;++Line)
|
||||
{
|
||||
const int32 X=Min[0]+static_cast<int32>(Line%Size[0]);
|
||||
const int32 Y=Min[1]+static_cast<int32>(Line/Size[0]);
|
||||
const size_t Base=Line*WordsPerZ;
|
||||
for(size_t W=0;W<WordsPerZ;++W)
|
||||
{
|
||||
const size_t Word=Base+W;
|
||||
const uint64 Rock=RockWords[Word],Candidates=CandidateWords[Word];
|
||||
const uint64 PreviousRock=W?RockWords[Word-1]>>63:0;
|
||||
const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0;
|
||||
uint64 Valid=(Size[2]-W*64)>=64?~uint64{0}
|
||||
:((uint64{1}<<(Size[2]-W*64))-1);
|
||||
if(W==0)Valid&=~uint64{1};
|
||||
const uint64 ShiftedRock=(Rock<<1)|PreviousRock;
|
||||
const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates;
|
||||
uint64 Missing=((Rock^ShiftedRock)&Valid)
|
||||
&~(Candidates&ShiftedCandidates);
|
||||
while(Missing)
|
||||
{
|
||||
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||
const int32 Z=Min[2]+static_cast<int32>(W*64+Bit);
|
||||
AddMissing(X,Y,Z);AddMissing(X,Y,Z-1);
|
||||
}
|
||||
if(X<Max[0])
|
||||
{
|
||||
const size_t Other=Word+WordsPerZ;
|
||||
Missing=(Rock^RockWords[Other])&Valid
|
||||
&~(Candidates&CandidateWords[Other]);
|
||||
while(Missing)
|
||||
{
|
||||
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||
AddMissing(X,Y,Min[2]+static_cast<int32>(W*64+Bit));
|
||||
AddMissing(X+1,Y,Min[2]+static_cast<int32>(W*64+Bit));
|
||||
}
|
||||
}
|
||||
if(Y<Max[1])
|
||||
{
|
||||
const size_t Other=Word+static_cast<size_t>(Size[0])*WordsPerZ;
|
||||
Missing=(Rock^RockWords[Other])&Valid
|
||||
&~(Candidates&CandidateWords[Other]);
|
||||
while(Missing)
|
||||
{
|
||||
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||
AddMissing(X,Y,Min[2]+static_cast<int32>(W*64+Bit));
|
||||
AddMissing(X,Y+1,Min[2]+static_cast<int32>(W*64+Bit));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(MissingNodes.begin(),MissingNodes.end());
|
||||
MissingNodes.erase(std::unique(MissingNodes.begin(),MissingNodes.end()),
|
||||
MissingNodes.end());
|
||||
std::vector<float> Densities(MissingNodes.size());
|
||||
struct FParallelContext
|
||||
{
|
||||
FStandaloneShapeWorld* Shape;
|
||||
const std::vector<size_t>* Nodes;
|
||||
std::vector<float>* Values;
|
||||
int32 Minimum[3];
|
||||
int32 Extent[3];
|
||||
} Context{this,&MissingNodes,&Densities,{Min[0],Min[1],Min[2]},
|
||||
{Size[0],Size[1],Size[2]}};
|
||||
const auto EvaluateNode=[](void* Raw,size_t Index,size_t)
|
||||
{
|
||||
auto& C=*static_cast<FParallelContext*>(Raw);
|
||||
const size_t Node=(*C.Nodes)[Index];
|
||||
const int32 X=static_cast<int32>(Node%static_cast<size_t>(C.Extent[0]));
|
||||
const size_t YZ=Node/static_cast<size_t>(C.Extent[0]);
|
||||
const int32 Y=static_cast<int32>(YZ%static_cast<size_t>(C.Extent[1]));
|
||||
const int32 Z=static_cast<int32>(YZ/static_cast<size_t>(C.Extent[1]));
|
||||
(*C.Values)[Index]=C.Shape->AnalyticDensityAt(
|
||||
static_cast<float>(C.Minimum[0]+X),static_cast<float>(C.Minimum[1]+Y),
|
||||
static_cast<float>(C.Minimum[2]+Z));
|
||||
};
|
||||
Scheduler->parallel_for(MissingNodes.size(),64,
|
||||
static_cast<size_t>(std::max(1,WorkerCount)),&Context,EvaluateNode,nullptr,0);
|
||||
|
||||
uint64 AddedThisPass=0;
|
||||
bool bSignChanged=false;
|
||||
for(size_t Index=0;Index<MissingNodes.size();++Index)
|
||||
{
|
||||
const size_t Node=MissingNodes[Index];
|
||||
const int32 X=Min[0]+static_cast<int32>(Node%static_cast<size_t>(Size[0]));
|
||||
const size_t YZ=Node/static_cast<size_t>(Size[0]);
|
||||
const int32 Y=Min[1]+static_cast<int32>(YZ%static_cast<size_t>(Size[1]));
|
||||
const int32 Z=Min[2]+static_cast<int32>(YZ/static_cast<size_t>(Size[1]));
|
||||
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||
+static_cast<size_t>(X-Min[0]);
|
||||
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||
const size_t Word=Column*WordsPerZ+(Zi>>6);
|
||||
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||
if(CandidateWords[Word]&Mask)continue;
|
||||
const float Density=Densities[Index];
|
||||
StoreSurfaceDistance(Node,Density);
|
||||
CandidateWords[Word]|=Mask;
|
||||
++AddedThisPass;
|
||||
const bool bExactRock=Density<0.0f;
|
||||
const bool bPackedRock=(RockWords[Word]&Mask)!=0;
|
||||
if(bExactRock!=bPackedRock)
|
||||
{
|
||||
if(bExactRock)RockWords[Word]|=Mask;
|
||||
else RockWords[Word]&=~Mask;
|
||||
++SignCorrections;
|
||||
bSignChanged=true;
|
||||
}
|
||||
}
|
||||
AddedTotal+=AddedThisPass;
|
||||
if(!bSignChanged)break;
|
||||
}
|
||||
std::printf("[GridStairDistanceRepair] exact_endpoint_proxies=%llu occupancy_corrections=%llu mode=parallel_deterministic\n",
|
||||
static_cast<unsigned long long>(AddedTotal),
|
||||
static_cast<unsigned long long>(SignCorrections));
|
||||
return AddedTotal;
|
||||
}
|
||||
|
||||
void StoreSurfaceDistance(size_t Node,float Density)
|
||||
{
|
||||
int32 Q=static_cast<int32>(std::lround(Density*256.0f));
|
||||
@@ -10191,6 +10334,7 @@ private:
|
||||
const VoxelForgeCore::JobScheduler* Scheduler=nullptr;
|
||||
bool RoundTunnelsEnabled=false;
|
||||
bool bProfileDetailBand=false;
|
||||
bool bParallelSignEdgeRepair=false;
|
||||
int32 Min[3]{},Max[3]{},ClipMin[3]{},ClipMax[3]{},Size[3]{};
|
||||
FIntVector EditBoundsMin=FIntVector(MAX_int32,MAX_int32,MAX_int32);
|
||||
FIntVector EditBoundsMax=FIntVector(MIN_int32,MIN_int32,MIN_int32);
|
||||
|
||||
@@ -1069,6 +1069,18 @@ namespace VoxelForgeCore
|
||||
}
|
||||
};
|
||||
|
||||
struct FScopedIslandColumnCacheInstrumentation
|
||||
{
|
||||
floating_island_detail::FIslandColumnCacheInstrumentation* Previous=nullptr;
|
||||
explicit FScopedIslandColumnCacheInstrumentation(
|
||||
floating_island_detail::FIslandColumnCacheInstrumentation* Current,bool Enabled)
|
||||
:Previous(Enabled?floating_island_detail::SetIslandColumnCacheInstrumentation(Current):nullptr),
|
||||
bEnabled(Enabled){}
|
||||
~FScopedIslandColumnCacheInstrumentation()
|
||||
{if(bEnabled)floating_island_detail::SetIslandColumnCacheInstrumentation(Previous);}
|
||||
bool bEnabled=false;
|
||||
};
|
||||
|
||||
World::World(ResolvedWorldSnapshot snapshot):impl_(std::make_unique<Impl>())
|
||||
{
|
||||
if(snapshot.data_)
|
||||
@@ -1394,7 +1406,8 @@ namespace VoxelForgeCore
|
||||
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail),
|
||||
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
|
||||
request.validatePassageRoutes,request.gpuVisualDetailTiles,
|
||||
request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get());
|
||||
request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get(),
|
||||
request.parallelSignEdgeRepair);
|
||||
if(ok)
|
||||
{
|
||||
impl_->shape=std::move(shape);
|
||||
@@ -1999,6 +2012,9 @@ namespace VoxelForgeCore
|
||||
FScopedSharedColumnCaches sharedColumnCaches(options.useSharedColumnCache
|
||||
?impl_->sharedHeightfieldColumns.get():nullptr,
|
||||
options.useSharedColumnCache?impl_->sharedIslandColumns.get():nullptr);
|
||||
floating_island_detail::FIslandColumnCacheInstrumentation islandCacheInstrumentation;
|
||||
FScopedIslandColumnCacheInstrumentation islandCacheScope(
|
||||
&islandCacheInstrumentation,options.instrumentTileDrain);
|
||||
const auto& world=*impl_->owner;
|
||||
FStandaloneShapeWorld nativeOnlyShape;
|
||||
FIntVector nativeOnlyMinimum,nativeOnlyMaximum;
|
||||
@@ -2028,6 +2044,10 @@ namespace VoxelForgeCore
|
||||
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
||||
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
||||
mesher.bStandaloneEnableGradientMemoization=options.memoizeMcGradients;
|
||||
mesher.bStandaloneEnableExactMidpointDensityMemo=options.exactMidpointDensityMemo;
|
||||
mesher.bStandaloneEnableActiveLaneBatching=options.activeLaneBatching;
|
||||
mesher.bStandaloneProofBlockCells=static_cast<int32>(std::clamp<std::uint32_t>(
|
||||
options.proofBlockCells,1u,static_cast<std::uint32_t>(std::max(1,CHUNK_SIZE))));
|
||||
mesher.bGenerateSkirts=world.bGenerateSkirts;
|
||||
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
|
||||
*static_cast<float>(world.OwnerVoxelSizeMicrons)
|
||||
@@ -2138,6 +2158,21 @@ namespace VoxelForgeCore
|
||||
int64 sampleCount=0;std::vector<float> densityValues;
|
||||
FVoxelMarchingCubesStageTimings McStageTimings;
|
||||
TileMesh out;out.key=key;
|
||||
const auto CopyIslandCacheMetrics=[&]()
|
||||
{
|
||||
if(!options.instrumentTileDrain)return;
|
||||
out.diagnostics.islandLocalCacheHits=islandCacheInstrumentation.localHits;
|
||||
out.diagnostics.islandLocalCacheMisses=islandCacheInstrumentation.localMisses;
|
||||
out.diagnostics.islandLocalCacheConflictReplacements=
|
||||
islandCacheInstrumentation.localConflictReplacements;
|
||||
out.diagnostics.islandSharedCacheHits=islandCacheInstrumentation.shared.hits;
|
||||
out.diagnostics.islandSharedCacheMisses=islandCacheInstrumentation.shared.misses;
|
||||
out.diagnostics.islandSharedCacheComputes=islandCacheInstrumentation.shared.computes;
|
||||
out.diagnostics.islandSharedCacheConflictReplacements=
|
||||
islandCacheInstrumentation.shared.conflictReplacements;
|
||||
out.diagnostics.islandSharedCacheLockWaitNanoseconds=
|
||||
islandCacheInstrumentation.shared.lockWaitNanoseconds;
|
||||
};
|
||||
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
|
||||
if(options.profileMesherStages)
|
||||
{
|
||||
@@ -2291,6 +2326,7 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete;
|
||||
out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates;
|
||||
}
|
||||
CopyIslandCacheMetrics();
|
||||
out.densitySampleCount=0;
|
||||
return out;
|
||||
}
|
||||
@@ -2558,9 +2594,29 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.marchingCubesOtherMilliseconds=McStageTimings.OtherMilliseconds;
|
||||
out.diagnostics.marchingCubesGradientFallbackDensityCalls=
|
||||
McStageTimings.GradientFallbackDensityCalls;
|
||||
out.diagnostics.marchingCubesGradientCalls=McStageTimings.GradientCalls;
|
||||
out.diagnostics.marchingCubesEdgeMidpointDensityCalls=
|
||||
McStageTimings.EdgeMidpointDensityCalls;
|
||||
out.diagnostics.marchingCubesEdgeMidpointDensityMemoHits=
|
||||
McStageTimings.EdgeMidpointDensityMemoHits;
|
||||
out.diagnostics.marchingCubesGradientMemoHits=McStageTimings.GradientMemoHits;
|
||||
out.diagnostics.marchingCubesX8DensityCalls=McStageTimings.X8DensityCalls;
|
||||
out.diagnostics.marchingCubesX8ActiveLanes=McStageTimings.X8ActiveLanes;
|
||||
out.diagnostics.marchingCubesCandidateCells=McStageTimings.CandidateCells;
|
||||
out.diagnostics.marchingCubesZBandRejectedCells=McStageTimings.ZBandRejectedCells;
|
||||
out.diagnostics.marchingCubesCellClassificationCalls=
|
||||
McStageTimings.CellClassificationCalls;
|
||||
out.diagnostics.marchingCubesSurfaceCellCalls=McStageTimings.SurfaceCellCalls;
|
||||
out.diagnostics.marchingCubesProofSkippedSolidCells=
|
||||
McStageTimings.ProofSkippedSolidCells;
|
||||
out.diagnostics.marchingCubesProofSkippedAirCells=
|
||||
McStageTimings.ProofSkippedAirCells;
|
||||
out.diagnostics.marchingCubesProofSkippedDensitySamples=
|
||||
McStageTimings.ProofSkippedDensitySamples;
|
||||
out.diagnostics.marchingCubesSkirtEdgeCandidates=McStageTimings.SkirtEdgeCandidates;
|
||||
out.diagnostics.marchingCubesSkirtBoundaryCandidates=
|
||||
McStageTimings.SkirtBoundaryCandidates;
|
||||
out.diagnostics.marchingCubesSkirtDensityProbes=McStageTimings.SkirtDensityProbes;
|
||||
}
|
||||
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-MeshingStart).count();
|
||||
@@ -2822,6 +2878,7 @@ namespace VoxelForgeCore
|
||||
}
|
||||
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-MeshAssemblyStart).count();
|
||||
CopyIslandCacheMetrics();
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user