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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-25 06:16:07 +01:00
1 parent e35a77f676
commit aee1487410
13 files changed
+882 -74

No files matched your search

+7 -1
View File
@@ -14,13 +14,16 @@
class FStandaloneCoreScheduler final : public VoxelForgeCore::JobScheduler
{
public:
explicit FStandaloneCoreScheduler(std::size_t helperLimit=12)
:HelperLimit(std::clamp<std::size_t>(helperLimit,1,12)){}
double parallel_for(std::size_t count,std::size_t batchSize,std::size_t workerLimit,
void* context,Task task,double* workerActiveSeconds,
std::size_t workerActiveCapacity) const override
{
if(count==0)return 0.0;
const std::size_t workers=std::clamp<std::size_t>(workerLimit,1,
std::min<std::size_t>(8,std::max<std::size_t>(1,count)));
std::min<std::size_t>(HelperLimit,std::max<std::size_t>(1,count)));
const std::size_t grain=std::max<std::size_t>(1,batchSize);
std::atomic<std::size_t> next{0};
std::vector<double> active(workers,0.0);
@@ -50,4 +53,7 @@ public:
}
return total;
}
private:
std::size_t HelperLimit=8;
};
+36 -2
View File
@@ -46,6 +46,13 @@ namespace
bool ScreenSpaceGate=false;
bool AuditUniformBlockProofs=false;
bool ProfileLodWork=false;
bool InstrumentTileDrain=false;
bool ExactMidpointDensityMemo=false;
bool ActiveLaneBatching=false;
bool ParallelSignEdgeRepair=false;
bool HasSignEdgeRepairOverride=false;
int ProofBlockCells=8;
int ShapeHelperLimit=8;
bool AuditBasePageEquivalence=false;
bool AuditRouteParity=false;
bool RegionCrossing=false;
@@ -1143,6 +1150,11 @@ namespace
" --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"
" --instrument-tile-drain (opt-in CpuTileTimeline and per-tile stages/counters)\n"
" --exact-midpoint-density-memo (exact-input midpoint query memo A/B)\n"
" --active-lane-batching --proof-block-cells=N (proof-masked density fill A/B)\n"
" --parallel-sign-edge-repair --serial-sign-edge-repair\n"
" --shape-helper-limit=8|12 (default 8; shape build A/B)\n"
" --audit-base-page-equivalence (hash one tile through both shape routes)\n"
" --audit-route-parity (compare page, native-only, exact-query and mesh routes)\n"
" --audit-tile-shape-readiness (run a deliberately mis-marked tile against the native-only guard)\n"
@@ -1190,6 +1202,25 @@ namespace
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=="--instrument-tile-drain")options.InstrumentTileDrain=true;
else if(arg=="--exact-midpoint-density-memo")options.ExactMidpointDensityMemo=true;
else if(arg=="--active-lane-batching")options.ActiveLaneBatching=true;
else if(arg=="--parallel-sign-edge-repair")
{options.ParallelSignEdgeRepair=true;options.HasSignEdgeRepairOverride=true;}
else if(arg=="--serial-sign-edge-repair")
{options.ParallelSignEdgeRepair=false;options.HasSignEdgeRepairOverride=true;}
else if(arg.starts_with("--proof-block-cells="))
{
if(!readInt("--proof-block-cells=",options.ProofBlockCells)
||options.ProofBlockCells<1||options.ProofBlockCells>32)
{std::fprintf(stderr,"--proof-block-cells must be in 1..32\n");return false;}
}
else if(arg.starts_with("--shape-helper-limit="))
{
if(!readInt("--shape-helper-limit=",options.ShapeHelperLimit)
||(options.ShapeHelperLimit!=8&&options.ShapeHelperLimit!=12))
{std::fprintf(stderr,"--shape-helper-limit must be 8 or 12\n");return false;}
}
else if(arg=="--audit-base-page-equivalence")options.AuditBasePageEquivalence=true;
else if(arg=="--audit-route-parity")options.AuditRouteParity=true;
else if(arg=="--region-crossing")options.RegionCrossing=true;
@@ -1751,7 +1782,7 @@ int main(int argc,char** argv)
if(!snapshot)
{std::fprintf(stderr,"Shape interval build failed: %s\n",error.c_str());return 2;}
FStandaloneCoreScheduler scheduler;
FStandaloneCoreScheduler scheduler(static_cast<std::size_t>(options.ShapeHelperLimit));
const FVector cameraStepVoxels=options.HasCameraStep
?FVector(options.CameraStepMeters.X*1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons),
options.CameraStepMeters.Y*1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons),
@@ -1768,5 +1799,8 @@ int main(int argc,char** argv)
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
options.AuditResolvedLayoutPassages,options.AuditStrateTransitions,
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability,
options.AuditOreVeinYield,options.AuditNativeCaves);
options.AuditOreVeinYield,options.AuditNativeCaves,options.InstrumentTileDrain,
options.ExactMidpointDensityMemo,options.ActiveLaneBatching,
options.ProofBlockCells,options.HasSignEdgeRepairOverride
?options.ParallelSignEdgeRepair:options.FloatingIslandsRecipe);
}
+20 -2
View File
@@ -1422,7 +1422,10 @@ namespace StandaloneStreamBenchmark
bool bAuditStrateTransitions=false,
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr,
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false,
bool bAuditOreVeinYield=false,bool bAuditNativeCaves=false)
bool bAuditOreVeinYield=false,bool bAuditNativeCaves=false,
bool bInstrumentTileDrain=false,bool bExactMidpointDensityMemo=false,
bool bActiveLaneBatching=false,int32 ProofBlockCells=8,
bool bParallelSignEdgeRepair=false)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -1575,6 +1578,7 @@ namespace StandaloneStreamBenchmark
ShapeRequest.clipMinVoxel={ClipMin.X,ClipMin.Y,ClipMin.Z};
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,16));
ShapeRequest.parallelSignEdgeRepair=bParallelSignEdgeRepair;
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
ShapeRequest.roundTunnels=bRoundTunnels;
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
@@ -1582,7 +1586,20 @@ namespace StandaloneStreamBenchmark
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true);
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
Streamer.SetExactMidpointDensityMemoEnabled(bExactMidpointDensityMemo);
Streamer.SetActiveLaneBatchingEnabled(bActiveLaneBatching);
Streamer.SetProofBlockCells(ProofBlockCells);
if(bScreenSpaceGate||bReportLodWorkProfile||bInstrumentTileDrain)
Streamer.SetMesherStageProfilingEnabled(true);
if(bInstrumentTileDrain)
{
Streamer.EnableColdTimelineTrace(ColdStart,nullptr);
std::printf("[CpuTileInstrumentation] enabled=true labeled=true flush=after_run proof_block_cells=%d active_lane_batching=%s midpoint_density_memo=%s parallel_sign_edge_repair=%s\n",
ProofBlockCells,bActiveLaneBatching?"on":"off",
bExactMidpointDensityMemo?"on":"off",
bParallelSignEdgeRepair?"on":"off");
}
const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate EarlyUpdate=Streamer.StartWithDesired(ShapeFreeDesired);
if(EarlyUpdate.DesiredTiles!=static_cast<int32>(ShapeFreeDesired.Tiles.size()))
@@ -4830,6 +4847,7 @@ namespace StandaloneStreamBenchmark
}
}
Streamer.Stop();
Streamer.DumpColdTimelineTrace();
bool bScreenSpaceGatePassing=true;
if(bScreenSpaceGate)
{
+151 -17
View File
@@ -41,6 +41,7 @@ struct FStandaloneDesiredTileSet
struct FStandaloneCpuTile
{
FVoxelTileKey Key;
int32 WorkerId=-1;
uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
@@ -358,6 +359,25 @@ struct FStandaloneTileJob
std::chrono::steady_clock::time_point StartedAt{};
};
struct FStandaloneCpuTileTimelineEvent
{
enum class EType : uint8 { Queued, Dispatch, Finish };
EType Type=EType::Queued;
FVoxelTileKey Key;
int32 WorkerId=-1;
size_t QueueDepth=0;
size_t ActiveWorkers=0;
size_t PriorityRank=0;
std::chrono::steady_clock::time_point At{};
std::chrono::steady_clock::time_point QueuedAt{};
std::chrono::steady_clock::time_point StartedAt{};
std::chrono::steady_clock::time_point FinishedAt{};
double WorkerMilliseconds=-1.0;
uint64 DensitySamples=0;
uint32 SkirtTriangles=0;
VoxelForgeCore::TileGenerationStats Diagnostics{};
};
struct FStandaloneTileJobHeapOrder
{
bool operator()(const FStandaloneTileJob& A,const FStandaloneTileJob& B)const
@@ -453,9 +473,45 @@ public:
{
TimelineOrigin=Origin;
TimelineFrameOrdinal=FrameOrdinal;
TimelineQueueEvents.clear();
TimelineWorkerEvents.clear();
TimelineWorkerEvents.resize(static_cast<size_t>(WorkerCount));
bColdTimelineTrace=true;
}
void DumpColdTimelineTrace() const
{
if(!bColdTimelineTrace)return;
std::vector<FStandaloneCpuTileTimelineEvent> Events=TimelineQueueEvents;
size_t Total=Events.size();
for(const auto& WorkerEvents:TimelineWorkerEvents)Total+=WorkerEvents.size();
Events.reserve(Total);
for(const auto& WorkerEvents:TimelineWorkerEvents)
Events.insert(Events.end(),WorkerEvents.begin(),WorkerEvents.end());
std::stable_sort(Events.begin(),Events.end(),[](const auto& A,const auto& B)
{return A.At<B.At;});
std::printf("[CpuTileTimeline] mode=instrumented events=%zu workers=%d flush=after_run\n",
Events.size(),WorkerCount);
for(const auto& Event:Events)
{
const char* Type=Event.Type==FStandaloneCpuTileTimelineEvent::EType::Queued
?"queued":Event.Type==FStandaloneCpuTileTimelineEvent::EType::Dispatch
?"dispatch":"finish";
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu worker_id=%d queue_depth=%zu active_workers=%zu priority_rank=%zu queued_ms=%.3f dispatch_ms=%.3f finish_ms=%.3f service_ms=%.3f worker_ms=%.3f\n",
Type,Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
TimelineMilliseconds(Event.At),static_cast<unsigned long long>(TimelineFrame()),
Event.WorkerId,Event.QueueDepth,Event.ActiveWorkers,Event.PriorityRank,
TimelineMilliseconds(Event.QueuedAt),TimelineMilliseconds(Event.StartedAt),
TimelineMilliseconds(Event.FinishedAt),
Event.StartedAt.time_since_epoch().count()!=0
?std::chrono::duration<double,std::milli>(Event.At-Event.StartedAt).count():-1.0,
Event.WorkerMilliseconds);
if(Event.Type==FStandaloneCpuTileTimelineEvent::EType::Finish)
PrintTileStageEvent(Event);
}
std::fflush(stdout);
}
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
const std::vector<FVoxelTileKey>& ResidentKeys={},
uint64 CrossingIndex=0)
@@ -465,7 +521,7 @@ public:
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
}
@@ -479,7 +535,7 @@ public:
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
return UpdateDesiredSet(InitialDesired,ResidentKeys,CrossingIndex);
}
@@ -619,7 +675,6 @@ public:
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
@@ -653,6 +708,14 @@ public:
void SetMesherStageProfilingEnabled(bool Enabled)
{bProfileMesherStages=Enabled;}
void SetTileDrainInstrumentationEnabled(bool Enabled)
{bInstrumentTileDrain=Enabled;}
void SetExactMidpointDensityMemoEnabled(bool Enabled)
{bExactMidpointDensityMemo=Enabled;}
void SetActiveLaneBatchingEnabled(bool Enabled)
{bActiveLaneBatching=Enabled;}
void SetProofBlockCells(int32 Cells)
{ProofBlockCells=std::clamp(Cells,1,32);}
void SetUniformBlockProofAuditEnabled(bool Enabled)
{bAuditUniformBlockProofs=Enabled;}
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
@@ -693,7 +756,6 @@ private:
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
@@ -770,7 +832,9 @@ private:
Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key);
Job.QueuedAt=FClock::now();
Queue.push_back(Job);
if(bColdTimelineTrace)TraceTileEvent("queued",Key,Job.QueuedAt);
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,Key,-1,
Queue.size(),Running.size(),Job.PriorityRank,Job.QueuedAt,Job.QueuedAt);
Outstanding.insert(Key);++Stats.NewJobs;
}
CurrentDesired=std::move(NewDesired);
@@ -825,7 +889,11 @@ private:
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.useSharedColumnCache=bSharedColumnCache;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
Options.exactMidpointDensityMemo=bExactMidpointDensityMemo&&!ForceGenerate;
Options.activeLaneBatching=bActiveLaneBatching&&!ForceGenerate;
Options.proofBlockCells=static_cast<std::uint32_t>(ProofBlockCells);
Options.auditUniformBlockProofs=bAuditUniformBlockProofs&&!ForceGenerate;
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
@@ -928,7 +996,7 @@ private:
return Out;
}
void WorkerLoop()
void WorkerLoop(int32 WorkerId)
{
constexpr uint32 PriorityTaskBurst=3;
uint32 ConsecutivePriorityTasks=0;
@@ -936,6 +1004,7 @@ private:
{
FStandaloneTileJob Job;
std::function<void()> PriorityTask;
size_t DispatchQueueDepth=0,DispatchActiveWorkers=0;
{
std::unique_lock<std::mutex> Lock(Mutex);
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()||!Queue.empty();});
@@ -952,6 +1021,7 @@ private:
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
Running.insert(Job.Key);ConsecutivePriorityTasks=0;
DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size();
}
}
if(PriorityTask)
@@ -959,14 +1029,21 @@ private:
PriorityTask();
continue;
}
if(bColdTimelineTrace)TraceTileEvent("started",Job.Key,Job.StartedAt,Job.QueuedAt,Job.StartedAt);
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Dispatch,Job.Key,
WorkerId,DispatchQueueDepth,DispatchActiveWorkers,Job.PriorityRank,
Job.StartedAt,Job.QueuedAt,Job.StartedAt);
FStandaloneCpuTile Tile=Generate(Job);
if(bColdTimelineTrace)TraceTileEvent("finished",Tile.Key,Tile.CompletedAt,
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0);
Tile.WorkerId=WorkerId;
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Running.erase(Tile.Key);
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Finish,Tile.Key,
WorkerId,Queue.size(),Running.size(),Job.PriorityRank,Tile.CompletedAt,
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0,
&Tile.CoreDiagnostics,Tile.DensitySamples,Tile.SkirtTriangleCount);
Ready.push_back(std::move(Tile));
}
}
@@ -982,14 +1059,65 @@ private:
{
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
}
void TraceTileEvent(const char* Event,const FVoxelTileKey& Key,FClock::time_point At,
FClock::time_point Queued={},FClock::time_point Started={},FClock::time_point Finished={},
double WorkerMs=-1.0)const
static void PrintTileStageEvent(const FStandaloneCpuTileTimelineEvent& Event)
{
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu queued_ms=%.3f started_ms=%.3f finished_ms=%.3f worker_ms=%.3f\n",
Event,Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,TimelineMilliseconds(At),
static_cast<unsigned long long>(TimelineFrame()),TimelineMilliseconds(Queued),
TimelineMilliseconds(Started),TimelineMilliseconds(Finished),WorkerMs);
const auto& D=Event.Diagnostics;
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_cell_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_skirt_ms=%.3f mc_other_ms=%.3f connector_ms=%.3f ground_ms=%.3f cap_ms=%.3f horizon_ms=%.3f mesh_assembly_ms=%.3f candidate_cells=%llu classified_cells=%llu surface_cells=%llu proof_solid_cells=%llu proof_air_cells=%llu proof_density_samples=%llu zband_reject_cells=%llu x8_calls=%llu x8_active_lanes=%llu grad_calls=%llu grad_memo_hits=%llu grad_fallback_calls=%llu midpoint_attempts=%llu midpoint_memo_hits=%llu skirt_edges=%llu skirt_boundary_edges=%llu skirt_density_probes=%llu skirt_triangles=%u fi_local_hit=%llu fi_local_miss=%llu fi_local_conflicts=%llu fi_shared_hit=%llu fi_shared_miss=%llu fi_shared_computes=%llu fi_shared_conflicts=%llu fi_lock_wait_us=%.3f\n",
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
Event.WorkerId,Event.WorkerMilliseconds,
static_cast<unsigned long long>(Event.DensitySamples),D.classificationMilliseconds,
D.columnComputeMilliseconds,D.columnEvaluationExclusiveMilliseconds,
D.marchingCubesExclusiveMilliseconds+D.marchingCubesSkirtMilliseconds,
D.marchingCubesDensityGridMilliseconds,D.marchingCubesCellClassificationMilliseconds,
D.marchingCubesGradientNormalsMilliseconds,D.marchingCubesEdgeInterpolationMilliseconds,
D.marchingCubesVertexDedupStreamMilliseconds,D.marchingCubesSkirtMilliseconds,
D.marchingCubesOtherMilliseconds,D.farConnectorShellMilliseconds,
D.groundRefinementExclusiveMilliseconds,D.capRefinementExclusiveMilliseconds,
D.farHorizonMilliseconds,D.meshAssemblyMilliseconds,
static_cast<unsigned long long>(D.marchingCubesCandidateCells),
static_cast<unsigned long long>(D.marchingCubesCellClassificationCalls),
static_cast<unsigned long long>(D.marchingCubesSurfaceCellCalls),
static_cast<unsigned long long>(D.marchingCubesProofSkippedSolidCells),
static_cast<unsigned long long>(D.marchingCubesProofSkippedAirCells),
static_cast<unsigned long long>(D.marchingCubesProofSkippedDensitySamples),
static_cast<unsigned long long>(D.marchingCubesZBandRejectedCells),
static_cast<unsigned long long>(D.marchingCubesX8DensityCalls),
static_cast<unsigned long long>(D.marchingCubesX8ActiveLanes),
static_cast<unsigned long long>(D.marchingCubesGradientCalls),
static_cast<unsigned long long>(D.marchingCubesGradientMemoHits),
static_cast<unsigned long long>(D.marchingCubesGradientFallbackDensityCalls),
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityCalls),
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityMemoHits),
static_cast<unsigned long long>(D.marchingCubesSkirtEdgeCandidates),
static_cast<unsigned long long>(D.marchingCubesSkirtBoundaryCandidates),
static_cast<unsigned long long>(D.marchingCubesSkirtDensityProbes),
Event.SkirtTriangles,
static_cast<unsigned long long>(D.islandLocalCacheHits),
static_cast<unsigned long long>(D.islandLocalCacheMisses),
static_cast<unsigned long long>(D.islandLocalCacheConflictReplacements),
static_cast<unsigned long long>(D.islandSharedCacheHits),
static_cast<unsigned long long>(D.islandSharedCacheMisses),
static_cast<unsigned long long>(D.islandSharedCacheComputes),
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
}
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
size_t PriorityRank,FClock::time_point At,FClock::time_point Queued={},
FClock::time_point Started={},FClock::time_point Finished={},double WorkerMs=-1.0,
const VoxelForgeCore::TileGenerationStats* Diagnostics=nullptr,
uint64 DensitySamples=0,uint32 SkirtTriangles=0)
{
FStandaloneCpuTileTimelineEvent Event;
Event.Type=Type;Event.Key=Key;Event.WorkerId=WorkerId;
Event.QueueDepth=QueueDepth;Event.ActiveWorkers=ActiveWorkers;
Event.PriorityRank=PriorityRank;Event.At=At;Event.QueuedAt=Queued;
Event.StartedAt=Started;Event.FinishedAt=Finished;Event.WorkerMilliseconds=WorkerMs;
Event.DensitySamples=DensitySamples;Event.SkirtTriangles=SkirtTriangles;
if(Diagnostics)Event.Diagnostics=*Diagnostics;
if(WorkerId>=0&&static_cast<size_t>(WorkerId)<TimelineWorkerEvents.size())
TimelineWorkerEvents[static_cast<size_t>(WorkerId)].push_back(Event);
else TimelineQueueEvents.push_back(Event);
}
FStandaloneOwnerWorld* World=nullptr;
inline static std::atomic<bool> bCoarseFirstSchedulingEnabled{true};
@@ -1009,6 +1137,10 @@ private:
bool bSharedColumnCache=true;
int32 Lod8HorizonInteriorStepVoxels=0;
bool bProfileMesherStages=false;
bool bInstrumentTileDrain=false;
bool bExactMidpointDensityMemo=false;
bool bActiveLaneBatching=false;
int32 ProofBlockCells=8;
bool bAuditUniformBlockProofs=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
@@ -1028,4 +1160,6 @@ private:
bool bColdTimelineTrace=false;
FClock::time_point TimelineOrigin{};
const std::atomic<uint64>* TimelineFrameOrdinal=nullptr;
std::vector<FStandaloneCpuTileTimelineEvent> TimelineQueueEvents;
std::vector<std::vector<FStandaloneCpuTileTimelineEvent>> TimelineWorkerEvents;
};
@@ -16,7 +16,9 @@
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include <atomic>
#include <bit>
#include <immintrin.h>
#include <unordered_map>
namespace
{
@@ -546,7 +548,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// la borne (un contact purement tangent est exclu — sa traversée serait hors bande).
CzLo = FMath::Max(CzLo, FloorDivI((int64)BandZMinVox - OriginVoxels.Z, Step));
CzHi = FMath::Min(CzHi, FloorDivI((int64)BandZMaxVox - OriginVoxels.Z, Step));
if (CzHi < CzLo) { return MeshData; } // tuile entièrement hors bande → vide
if (CzHi < CzLo)
{
if(OutStageTimings)
{
const uint64 Candidate=static_cast<uint64>(CellsPerAxis)*CellsPerAxis*CellsPerAxis;
OutStageTimings->CandidateCells=Candidate;
OutStageTimings->ZBandRejectedCells=Candidate;
}
return MeshData;
} // tuile entièrement hors bande → vide
}
const int32 GzLo = CzLo - 1; // coins CzLo..CzHi+1, gradients ±1
const int32 GzHi = CzHi + 2; // (== -1..GridDim sans bande)
@@ -568,9 +579,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
&& (bNativeSurfaceTileProof || OutBlockProofAudit != nullptr
|| bStandaloneShapeProof)));
bool bCanSkipProofBlockGrid = Step <= 2 && bStandaloneShapeProof;
const int32 ProofBlockCells=std::clamp(bStandaloneProofBlockCells,1,CellsPerAxis);
if (bUseProofBlocks)
{
constexpr int32 BlockCells = 8;
const int32 BlockCells = ProofBlockCells;
BlocksPerAxis = (CellsPerAxis + BlockCells - 1) / BlockCells;
BlockSkip.Init(0, BlocksPerAxis * BlocksPerAxis * BlocksPerAxis);
bool bHaveUniformBlock = false;
@@ -917,7 +929,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
RequiredGridSamples.SetNumUninitialized(MDim*MDim*MDim);
FMemory::Memzero(RequiredGridSamples.GetData(),
static_cast<SIZE_T>(RequiredGridSamples.Num())*sizeof(uint8));
constexpr int32 BlockCells=8;
const int32 BlockCells=ProofBlockCells;
for(int32 BlockZ=0;BlockZ<BlocksPerAxis;++BlockZ)
{
const int32 BlockMinZ=BlockZ*BlockCells;
@@ -945,6 +957,25 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
}
}
}
if(OutStageTimings)
{
const uint64 PlaneCells=static_cast<uint64>(CellsPerAxis)*CellsPerAxis;
const int32 ActiveZ=FMath::Max(0,CzHi-CzLo+1);
OutStageTimings->CandidateCells=PlaneCells*static_cast<uint64>(CellsPerAxis);
OutStageTimings->ZBandRejectedCells=PlaneCells
*static_cast<uint64>(CellsPerAxis-ActiveZ);
if(bSkipProofBlockGrid)
{
for(int32 Z=GzLo;Z<=GzHi;++Z)
for(int32 Y=-1;Y<=GridDim;++Y)
for(int32 X=-1;X<=GridDim;++X)
{
const int32 Index=((Z+1)*MDim+(Y+1))*MDim+(X+1);
if(RequiredGridSamples[Index]==0)
++OutStageTimings->ProofSkippedDensitySamples;
}
}
}
const bool bCapturePreEditGrid = OutPreEditDensityGrid != nullptr
&& !bUseSharedDensityGrid
&& Step == 1 && CellsPerAxis == CHUNK_SIZE
@@ -1199,7 +1230,35 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
&&ActiveShapeWorld->IsAnalyticOnly()
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid;
const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid;
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid
&&(!bSkipProofBlockGrid||bStandaloneEnableActiveLaneBatching);
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz)
{
float Densities[8]{};
ActiveShapeWorld->DensityAtLatticeX8(OriginVoxels.X+GX0*Step,Step,
OriginVoxels.Y+gy*Step,OriginVoxels.Z+gz*Step,BatchCount,Densities);
if(OutStageTimings)
{
++OutStageTimings->X8DensityCalls;
OutStageTimings->X8ActiveLanes+=static_cast<uint64>(BatchCount);
}
for(int32 Lane=0;Lane<BatchCount;++Lane)
{
const int32 GX=GX0+Lane;
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(GX+1);
DensityGrid[LocalIndex]=Densities[Lane];
}
const int32 LastGX=GX0+BatchCount-1;
const int32 ReachBlockX=FMath::Clamp(LastGX,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;
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
};
if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
{
for(const std::vector<int32>& YGroup:YGroups)
@@ -1277,6 +1336,26 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
for(size_t Offset=0;Offset<XGroup.size();)
{
if(bSkipProofBlockGrid)
{
const int32 GX0=XGroup[Offset];
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(GX0+1);
if(RequiredGridSamples[LocalIndex]==0)
{++Offset;continue;}
int32 BatchCount=1;
while(BatchCount<8&&Offset+static_cast<size_t>(BatchCount)<XGroup.size()
&&XGroup[Offset+static_cast<size_t>(BatchCount)]==GX0+BatchCount)
{
const int32 NextGX=XGroup[Offset+static_cast<size_t>(BatchCount)];
const int32 NextIndex=((gz+1)*MDim+(gy+1))*MDim+(NextGX+1);
if(RequiredGridSamples[NextIndex]==0)break;
++BatchCount;
}
if(BatchCount<2)SampleGridPoint(GX0,gy,gz);
else SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
Offset+=static_cast<size_t>(BatchCount);
continue;
}
const int32 BatchCount=static_cast<int32>(std::min<size_t>(
8,XGroup.size()-Offset));
const int32 GX0=XGroup[Offset];
@@ -1291,30 +1370,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
continue;
}
float Densities[8]{};
ActiveShapeWorld->DensityAtLatticeX8(
OriginVoxels.X+GX0*Step,Step,
OriginVoxels.Y+gy*Step,OriginVoxels.Z+gz*Step,
BatchCount,Densities);
for(int32 Lane=0;Lane<BatchCount;++Lane)
{
const int32 GX=GX0+Lane;
const int32 LocalIndex=
((gz+1)*MDim+(gy+1))*MDim+(GX+1);
DensityGrid[LocalIndex]=Densities[Lane];
}
const int32 LastGX=GX0+BatchCount-1;
const int32 ReachBlockX=FMath::Clamp(LastGX,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;
if(OutDensitySampleCount)
*OutDensitySampleCount+=BatchCount;
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
Offset+=static_cast<size_t>(BatchCount);
}
}
@@ -1398,7 +1454,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// cubes loop will consume. This is intentionally redundant with ClassifyTile: it is a
// cheap soundness fence around the proof/cache hand-off. Unknown, zero, or opposite-sign
// vertices clear the candidate, so an unproven block pays the ordinary cell path.
constexpr int32 BlockCells = 8;
const int32 BlockCells = ProofBlockCells;
for (int32 BlockZ = 0; BlockZ < BlocksPerAxis; ++BlockZ)
{
const int32 BlockMinZ = BlockZ * BlockCells;
@@ -1454,6 +1510,31 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
}
}
}
if(OutStageTimings&&bUseProofBlocks)
{
for(int32 BlockZ=0;BlockZ<BlocksPerAxis;++BlockZ)
{
const int32 CellMinZ=BlockZ*ProofBlockCells;
const int32 CellMaxZ=FMath::Min(CellsPerAxis-1,CellMinZ+ProofBlockCells-1);
const int32 ZCount=FMath::Max(0,FMath::Min(CzHi,CellMaxZ)
-FMath::Max(CzLo,CellMinZ)+1);
if(ZCount==0)continue;
for(int32 BlockY=0;BlockY<BlocksPerAxis;++BlockY)
for(int32 BlockX=0;BlockX<BlocksPerAxis;++BlockX)
{
const int32 Index=(BlockZ*BlocksPerAxis+BlockY)*BlocksPerAxis+BlockX;
const uint8 Reason=BlockSkip[Index];
if(Reason==0)continue;
const int32 XCount=FMath::Min(CellsPerAxis-1,
(BlockX+1)*ProofBlockCells-1)-BlockX*ProofBlockCells+1;
const int32 YCount=FMath::Min(CellsPerAxis-1,
(BlockY+1)*ProofBlockCells-1)-BlockY*ProofBlockCells+1;
const uint64 Skipped=static_cast<uint64>(XCount)*YCount*ZCount;
if(Reason==1)OutStageTimings->ProofSkippedSolidCells+=Skipped;
else if(Reason==2)OutStageTimings->ProofSkippedAirCells+=Skipped;
}
}
}
// ── CAPTURE-DURING-MESHING ──
// Si demandé et que la tuile est pleine résolution (CellsPerAxis==CHUNK_SIZE ⇒ Step==1<<Level,
@@ -1518,6 +1599,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
};
auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector
{
if(OutStageTimings)++OutStageTimings->GradientCalls;
const int32 CacheIndex=((gz+1)*MDim+(gy+1))*MDim+(gx+1);
if(bUseGradientMemo&&GradientMemoValid[CacheIndex]!=0)
{
@@ -1577,6 +1659,25 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
uint64 AuditCellClassificationCycles=0,AuditGradientCycles=0;
uint64 AuditInterpolationCycles=0,AuditDedupStreamCycles=0;
uint64 AuditGradientSamples=0,AuditInterpolationSamples=0,AuditDedupStreamSamples=0;
struct FMidpointDensityKey
{
uint64 X=0,Y=0,Z=0;
bool operator==(const FMidpointDensityKey&)const=default;
};
struct FMidpointDensityKeyHash
{
size_t operator()(const FMidpointDensityKey& Key)const noexcept
{
uint64 Hash=Key.X^std::rotl(Key.Y,21)^std::rotl(Key.Z,42);
Hash^=Hash>>30;Hash*=0xbf58476d1ce4e5b9ull;
Hash^=Hash>>27;Hash*=0x94d049bb133111ebull;
Hash^=Hash>>31;
return static_cast<size_t>(Hash);
}
};
std::unordered_map<FMidpointDensityKey,float,FMidpointDensityKeyHash> MidpointDensityMemo;
if(bStandaloneEnableExactMidpointDensityMemo&&ShapeWorldContext&&Step==1)
MidpointDensityMemo.reserve(1024);
uint8 CaseIndexRow[CHUNK_SIZE]{};
uint8 SurfaceRow[CHUNK_SIZE]{};
uint8 SampleClassificationRow[CHUNK_SIZE]{};
@@ -1593,14 +1694,21 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
for (int32 GroupX = 0; GroupX < CellsPerAxis; GroupX += 8)
{
const int32 GroupCount = FMath::Min(8, CellsPerAxis - GroupX);
if (bUseProofBlocks
&& BlockSkip[((cz / 8) * BlocksPerAxis + (cy / 8)) * BlocksPerAxis
+ (GroupX / 8)] != 0)
bool bAnySkipped=false,bAllSkipped=bUseProofBlocks;
if(bUseProofBlocks)
{
continue;
for(int32 Lane=0;Lane<GroupCount;++Lane)
{
const int32 CellX=GroupX+Lane;
const int32 BlockIndex=((cz/ProofBlockCells)*BlocksPerAxis
+(cy/ProofBlockCells))*BlocksPerAxis+(CellX/ProofBlockCells);
const bool bSkipped=BlockSkip[BlockIndex]!=0;
bAnySkipped|=bSkipped;bAllSkipped&=bSkipped;
}
}
if(bAllSkipped)continue;
if (GroupCount == 8)
if (GroupCount == 8&&!bAnySkipped)
{
const float* Row00 = DensityGrid.GetData()
+ ((cz + 1) * MDim + (cy + 1)) * MDim + GroupX + 1;
@@ -1641,6 +1749,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
for (int32 Lane = 0; Lane < GroupCount; ++Lane)
{
const int32 CellX = GroupX + Lane;
if(bUseProofBlocks
&&BlockSkip[((cz/ProofBlockCells)*BlocksPerAxis
+(cy/ProofBlockCells))*BlocksPerAxis
+(CellX/ProofBlockCells)]!=0)continue;
int32 CaseIndex = 0;
for (int32 Corner = 0; Corner < 8; ++Corner)
{
@@ -1683,7 +1795,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
for (int32 cx = 0; cx < CellsPerAxis; cx++)
{
if (bUseProofBlocks
&& BlockSkip[((cz / 8) * BlocksPerAxis + (cy / 8)) * BlocksPerAxis + (cx / 8)] != 0)
&& BlockSkip[((cz / ProofBlockCells) * BlocksPerAxis
+ (cy / ProofBlockCells)) * BlocksPerAxis
+ (cx / ProofBlockCells)] != 0)
{
continue;
}
@@ -1746,9 +1860,28 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
if(OutStageTimings)++OutStageTimings->EdgeMidpointDensityCalls;
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
Midpoint.X/GeometryVoxelSize,Midpoint.Y/GeometryVoxelSize,
Midpoint.Z/GeometryVoxelSize);
const double MidX=Midpoint.X/GeometryVoxelSize;
const double MidY=Midpoint.Y/GeometryVoxelSize;
const double MidZ=Midpoint.Z/GeometryVoxelSize;
float MidDensity=0.0f;
if(bStandaloneEnableExactMidpointDensityMemo)
{
const FMidpointDensityKey Key{std::bit_cast<uint64>(MidX),
std::bit_cast<uint64>(MidY),std::bit_cast<uint64>(MidZ)};
const auto Found=MidpointDensityMemo.find(Key);
if(Found!=MidpointDensityMemo.end())
{
MidDensity=Found->second;
if(OutStageTimings)
++OutStageTimings->EdgeMidpointDensityMemoHits;
}
else
{
MidDensity=ShapeWorldContext->DensityAtUnclamped(MidX,MidY,MidZ);
MidpointDensityMemo.emplace(Key,MidDensity);
}
}
else MidDensity=ShapeWorldContext->DensityAtUnclamped(MidX,MidY,MidZ);
if(FMath::Abs(MidDensity-IsoLevel)<1.0e-6f)
T=0.5f;
else if((MidDensity<IsoLevel)==(D1<IsoLevel))
@@ -1938,6 +2071,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
};
std::vector<float> SurfaceVertexDensitySamples;
const auto CountSkirtDensityProbe=[&]()
{if(OutStageTimings)++OutStageTimings->SkirtDensityProbes;};
if(ShapeWorldContext)
SurfaceVertexDensitySamples.assign(static_cast<size_t>(MeshData.Vertices.Num()),
std::numeric_limits<float>::quiet_NaN());
@@ -1945,12 +2080,18 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
if(!ShapeWorldContext)return 0.0f;
if(OriginalIndex<0||static_cast<size_t>(OriginalIndex)>=SurfaceVertexDensitySamples.size())
{
CountSkirtDensityProbe();
return ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
}
float& Density=SurfaceVertexDensitySamples[static_cast<size_t>(OriginalIndex)];
if(std::isnan(Density))
{
CountSkirtDensityProbe();
Density=ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
}
return Density;
};
@@ -1974,8 +2115,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
FSkirtVertex Vertex{Position,Normal,Color,0.0f,OriginalIndex};
if(!bOnSurface&&ShapeWorldContext)
{
CountSkirtDensityProbe();
Vertex.Density=ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
}
return Vertex;
};
const auto InterpolateVertex=[&](const FSkirtVertex& A,const FSkirtVertex& B,float T)
@@ -1990,10 +2134,13 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.G),static_cast<float>(B.Color.G),T)),0,255)),
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.B),static_cast<float>(B.Color.B),T)),0,255)),
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.A),static_cast<float>(B.Color.A),T)),0,255)));
Out.Density=ShapeWorldContext
?ShapeWorldContext->DensityAt(Out.Position.X/GeometryVoxelSize,
Out.Position.Y/GeometryVoxelSize,Out.Position.Z/GeometryVoxelSize)
:FMath::Lerp(A.Density,B.Density,T);
if(ShapeWorldContext)
{
CountSkirtDensityProbe();
Out.Density=ShapeWorldContext->DensityAt(Out.Position.X/GeometryVoxelSize,
Out.Position.Y/GeometryVoxelSize,Out.Position.Z/GeometryVoxelSize);
}
else Out.Density=FMath::Lerp(A.Density,B.Density,T);
Out.OriginalIndex=INDEX_NONE;
return Out;
};
@@ -2052,6 +2199,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const FVector Position=P0.Position*static_cast<float>(WA)
+P1.Position*static_cast<float>(WB)
+P2.Position*static_cast<float>(WC);
CountSkirtDensityProbe();
return ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
};
@@ -2101,6 +2249,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
const FVector Center=(P0.Position+P1.Position+P2.Position)
/(3.0f*GeometryVoxelSize);
CountSkirtDensityProbe();
if(ShapeWorldContext->DensityAt(Center.X,Center.Y,Center.Z)>0.0f)continue;
}
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan]);Tris.Add(PolygonIndices[Fan+1]);
@@ -2113,12 +2262,14 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 Tri[3] = { Tris[t], Tris[t + 1], Tris[t + 2] };
for (int32 e = 0; e < 3; ++e)
{
if(OutStageTimings)++OutStageTimings->SkirtEdgeCandidates;
const int32 iA = Tri[e], iB = Tri[(e + 1) % 3];
// COPIES par valeur — AddSkirtVert réalloue Vertices/Normals (invaliderait des refs).
const FVector PA = MeshData.Vertices[iA];
const FVector PB = MeshData.Vertices[iB];
const uint8 FaceMask=BoundaryFaceMask(PA,PB);
if (!FaceMask) continue;
if(OutStageTimings)++OutStageTimings->SkirtBoundaryCandidates;
const FVector NA = MeshData.Normals[iA];
const FVector NB = MeshData.Normals[iB];
@@ -2183,6 +2334,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if(OutStageTimings)
{
OutStageTimings->CellClassificationCalls=CellClassificationCalls;
OutStageTimings->SurfaceCellCalls=SurfaceCellCalls;
const double CycleToMs=FPlatformTime::GetSecondsPerCycle64()*1000.0;
const auto ScaleSampledCycles=[CycleToMs](uint64 Cycles,uint64 Samples,uint64 Calls)
{
@@ -53,8 +53,22 @@ struct FVoxelMarchingCubesStageTimings
double SkirtMilliseconds = 0.0;
double OtherMilliseconds = 0.0;
uint64 GradientFallbackDensityCalls = 0;
uint64 GradientCalls = 0;
uint64 EdgeMidpointDensityCalls = 0;
uint64 EdgeMidpointDensityMemoHits = 0;
uint64 GradientMemoHits = 0;
uint64 X8DensityCalls = 0;
uint64 X8ActiveLanes = 0;
uint64 CandidateCells = 0;
uint64 ZBandRejectedCells = 0;
uint64 CellClassificationCalls = 0;
uint64 SurfaceCellCalls = 0;
uint64 ProofSkippedSolidCells = 0;
uint64 ProofSkippedAirCells = 0;
uint64 ProofSkippedDensitySamples = 0;
uint64 SkirtEdgeCandidates = 0;
uint64 SkirtBoundaryCandidates = 0;
uint64 SkirtDensityProbes = 0;
};
// Opt-in soundness audit for density blocks which the Core mesher would skip.
@@ -192,6 +206,9 @@ public:
bool bStandaloneSkipColors = false;
bool bStandaloneSkipMaterialGroups = false;
bool bStandaloneEnableGradientMemoization = true;
bool bStandaloneEnableExactMidpointDensityMemo = false;
bool bStandaloneEnableActiveLaneBatching = false;
int32 bStandaloneProofBlockCells = 8;
// Seuil de l'isosurface dans le champ de densité.
// Convention MC: densité < IsoLevel = solide, >= = air.