streaming: split dense passage-junction grid fills; heavy-first order + far-parent deferral in Unreal (L85)

Tiles with passage-overlap score >= 9 split grid fill over up to 4 helper lanes (disjoint rows,
same per-sample order, join before MC) when workers are idle. FI junction tile fill 173 -> 99 ms;
estimated serial floor ~0.55-0.60 s. Readiness essentially unchanged (FI/TN LOD0-2 ~2.15 s @W12):
the limit is now aggregate work, not single tiles.
Unreal: LOD0-2 ordered by passage overlap before travel/distance (L80 modes kept); far parents
built after LOD0-1, LOD2 gated on them; helper budget via ParallelFor. Unreal null-RHI FI
arrival: LOD0-2 resident at 3.65 s.
All tile hashes identical to L84 (four whole views). Fast tier PASS; Linux UE host builds.

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-28 02:02:32 +01:00
1 parent 0a98da6fd0
commit 06f558dc91
17 files changed
+1561 -54

No files matched your search

+1 -1
View File
@@ -2055,7 +2055,7 @@ namespace StandaloneStreamBenchmark
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
bAllowUniformSkipsWhenHashing);
bAllowUniformSkipsWhenHashing,&Scheduler);
Streamer.SetPassageHeavyPriority(ResolvedLayout);
Streamer.SetSchedulerPolicy(SchedulerPolicy);
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
+52 -4
View File
@@ -435,6 +435,29 @@ namespace StandaloneWorldMesher
}
}
// Grid fills can be nested inside the tile worker pool. Keep at most one such fill active so a
// burst of passage tiles adds only a bounded helper group instead of multiplying the worker count.
class FStandaloneGridFillScheduler final : public VoxelForgeCore::JobScheduler
{
public:
explicit FStandaloneGridFillScheduler(const VoxelForgeCore::JobScheduler* InInner)
:Inner(InInner){}
double parallel_for(std::size_t Count,std::size_t BatchSize,std::size_t WorkerLimit,
void* Context,Task Callback,double* WorkerActiveSeconds,
std::size_t WorkerActiveCapacity) const override
{
if(!Inner)return 0.0;
std::lock_guard<std::mutex> Lock(Mutex);
return Inner->parallel_for(Count,BatchSize,WorkerLimit,Context,Callback,
WorkerActiveSeconds,WorkerActiveCapacity);
}
private:
const VoxelForgeCore::JobScheduler* Inner=nullptr;
mutable std::mutex Mutex;
};
class FStandaloneStreamingGenerator
{
private:
@@ -462,10 +485,14 @@ public:
bool bInCaptureColumnCoordinates=false,
int32 InLod8HorizonInteriorStepVoxels=0,
bool bInSharedColumnCache=true,
bool bInAllowUniformSkipsWhenHashing=false)
bool bInAllowUniformSkipsWhenHashing=false,
const VoxelForgeCore::JobScheduler* InGridFillScheduler=nullptr)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
CoreWorld(InCoreWorld),
GridFillScheduler(InGridFillScheduler
?std::make_unique<FStandaloneGridFillScheduler>(InGridFillScheduler):nullptr),
bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors),
bCaptureColumnCoordinates(bInCaptureColumnCoordinates),
bSharedColumnCache(bInSharedColumnCache),
@@ -1116,6 +1143,25 @@ private:
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.useSharedColumnCache=bSharedColumnCache;
// Split only dense passage/mouth overlaps, and spend only worker lanes left idle by
// the tile pool. The grid fill runs on a second pool while this tile worker waits.
const uint32 PassageScore=PassageOverlapScore(Job.Key);
if(GridFillScheduler&&Job.Key.Level==0&&WorkerCount>1&&PassageScore>=9)
{
size_t ActiveTileCount=0;
{
std::lock_guard<std::mutex> Lock(Mutex);
ActiveTileCount=Running.size();
}
const int32 AvailableHelperLanes=std::max(0,
12-std::max(0,static_cast<int32>(ActiveTileCount)-1));
const int32 GridFillWorkers=std::min({4,WorkerCount,AvailableHelperLanes});
if(GridFillWorkers>1)
{
Options.gridFillScheduler=GridFillScheduler.get();
Options.gridFillWorkerLimit=static_cast<std::uint32_t>(GridFillWorkers);
}
}
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
Options.profilePassageComposer=bProfilePassageComposer;
@@ -1330,7 +1376,7 @@ private:
const char* TileClassName=D.tileClass==VoxelForgeCore::TileClass::allAir?"air":
D.tileClass==VoxelForgeCore::TileClass::allSolid?"solid":
D.tileClass==VoxelForgeCore::TileClass::mixed?"mixed":"unknown";
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f payload_ms=%.3f tile_classified=%d tile_class=%s tile_skip=%d density_point_calls=%llu density_float_points=%llu density_lattice_points=%llu density_unclamped_points=%llu density_x8_points=%llu field_eval_sample_ns=%llu field_eval_sample_points=%llu 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",
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f payload_ms=%.3f tile_classified=%d tile_class=%s tile_skip=%d density_point_calls=%llu density_float_points=%llu density_lattice_points=%llu density_unclamped_points=%llu density_x8_points=%llu field_eval_sample_ns=%llu field_eval_sample_points=%llu samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_grid_workers=%u 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,Event.PayloadBuildMilliseconds,
D.tileWasClassified?1:0,TileClassName,D.meshingSkipped?1:0,
@@ -1344,7 +1390,8 @@ private:
static_cast<unsigned long long>(Event.DensitySamples),D.classificationMilliseconds,
D.columnComputeMilliseconds,D.columnEvaluationExclusiveMilliseconds,
D.marchingCubesExclusiveMilliseconds+D.marchingCubesSkirtMilliseconds,
D.marchingCubesDensityGridMilliseconds,D.marchingCubesCellClassificationMilliseconds,
D.marchingCubesDensityGridMilliseconds,D.marchingCubesGridFillWorkers,
D.marchingCubesCellClassificationMilliseconds,
D.marchingCubesGradientNormalsMilliseconds,D.marchingCubesEdgeInterpolationMilliseconds,
D.marchingCubesVertexDedupStreamMilliseconds,D.marchingCubesSkirtMilliseconds,
D.marchingCubesOtherMilliseconds,D.farConnectorShellMilliseconds,
@@ -1441,6 +1488,7 @@ private:
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;
VoxelForgeCore::World* CoreWorld=nullptr;
std::unique_ptr<FStandaloneGridFillScheduler> GridFillScheduler;
bool bSkipUvAndColors=false;
bool bSkipMaterialGroups=false;
bool bSkipUV=false;
@@ -17,6 +17,9 @@
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include <atomic>
#include <array>
#include <algorithm>
#include <iterator>
#include <bit>
#include <cstdlib>
#include <immintrin.h>
@@ -24,6 +27,69 @@
namespace
{
struct FVoxelMesherThreadState
{
int32 OctaveBias=0,SampleStep=1,TileCells=0,TileReachBlocks=0,TileReachBlockIndex=INDEX_NONE;
FIntVector TileOrigin=FIntVector::ZeroValue;
uint8 TilePostReachFlags=VoxelGenLOD::AllTileReach;
bool bTilePostReachDebug=false,bTilePostReachBypass=false;
bool bTileReachDiagnosticTile=false,bTileCoreReachProofEnabled=false;
bool bTileCoreReachDecisionRecorded=false,bTileBlockReachValid=false;
float TileReachScale=1.0f;
std::array<uint8,VoxelGenLOD::TileReachMaxBlockCount> TileBlockReachFlags{};
static FVoxelMesherThreadState Capture()
{
FVoxelMesherThreadState State;
State.OctaveBias=VoxelGenLOD::OctaveBias;
State.SampleStep=VoxelGenLOD::SampleStep;
State.TileOrigin=VoxelGenLOD::TileOriginVoxels;
State.TileCells=VoxelGenLOD::TileCellsPerAxis;
State.TilePostReachFlags=VoxelGenLOD::TilePostReachFlags;
State.bTilePostReachDebug=VoxelGenLOD::bTilePostReachDebug;
State.bTilePostReachBypass=VoxelGenLOD::bTilePostReachBypass;
State.bTileReachDiagnosticTile=VoxelGenLOD::bTileReachDiagnosticTile;
State.bTileCoreReachProofEnabled=VoxelGenLOD::bTileCoreReachProofEnabled;
State.bTileCoreReachDecisionRecorded=VoxelGenLOD::bTileCoreReachDecisionRecorded;
State.bTileBlockReachValid=VoxelGenLOD::bTileBlockReachValid;
State.TileReachScale=VoxelGenLOD::TileReachScale;
State.TileReachBlocks=VoxelGenLOD::TileReachBlocksPerAxis;
State.TileReachBlockIndex=VoxelGenLOD::TileReachBlockIndex;
std::copy(std::begin(VoxelGenLOD::TileBlockReachFlags),
std::end(VoxelGenLOD::TileBlockReachFlags),State.TileBlockReachFlags.begin());
return State;
}
void Apply() const
{
VoxelGenLOD::OctaveBias=OctaveBias;
VoxelGenLOD::SampleStep=SampleStep;
VoxelGenLOD::TileOriginVoxels=TileOrigin;
VoxelGenLOD::TileCellsPerAxis=TileCells;
VoxelGenLOD::TilePostReachFlags=TilePostReachFlags;
VoxelGenLOD::bTilePostReachDebug=bTilePostReachDebug;
VoxelGenLOD::bTilePostReachBypass=bTilePostReachBypass;
VoxelGenLOD::bTileReachDiagnosticTile=bTileReachDiagnosticTile;
VoxelGenLOD::bTileCoreReachProofEnabled=bTileCoreReachProofEnabled;
VoxelGenLOD::bTileCoreReachDecisionRecorded=bTileCoreReachDecisionRecorded;
VoxelGenLOD::bTileBlockReachValid=bTileBlockReachValid;
VoxelGenLOD::TileReachScale=TileReachScale;
VoxelGenLOD::TileReachBlocksPerAxis=TileReachBlocks;
VoxelGenLOD::TileReachBlockIndex=TileReachBlockIndex;
std::copy(TileBlockReachFlags.begin(),TileBlockReachFlags.end(),
std::begin(VoxelGenLOD::TileBlockReachFlags));
}
};
struct FVoxelMesherThreadStateScope
{
explicit FVoxelMesherThreadStateScope(const FVoxelMesherThreadState& InTarget)
:Previous(FVoxelMesherThreadState::Capture())
{ InTarget.Apply(); }
~FVoxelMesherThreadStateScope(){Previous.Apply();}
FVoxelMesherThreadState Previous;
};
int32 GVoxelForgeUseOperatorBlock = 1;
FAutoConsoleVariableRef CVarVoxelForgeUseOperatorBlock(
TEXT("voxel.UseOperatorBlock"),
@@ -957,6 +1023,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
static thread_local TArray<float> DensityGrid;
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
float* const DensityGridData=DensityGrid.GetData();
const FVoxelSharedDensityGrid* ReuseGrid = InSharedDensityGrid != nullptr
? InSharedDensityGrid : SharedDensityGrid;
const int32 SharedDeltaX = ReuseGrid
@@ -1063,14 +1130,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const bool bUseOperatorBlock = !bUseSharedDensityGrid
&& !bUseParameterizedBrickEvaluator
&& GVoxelForgeUseOperatorBlock != 0;
if (bUseOperatorBlock)
{
// The grid includes one sample of halo on every side. The generator groups these exact
// lattice points by chunk key and lets the prepared op stack fill each group once.
Generator->BeginDensityBlock(
OriginVoxels + FIntVector(-Step, -Step, GzLo * Step),
Step, MDim, MDim, GzHi - GzLo + 1);
}
bool bGridFillParallel=false;
if (bUseParameterizedBrickEvaluator)
{
#if WITH_EDITOR
@@ -1238,7 +1298,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
* (VoxelGenLOD::IsUseFusedEvaluatorEnabled() ? 4 : 1);
const auto XGroups = BuildAxisGroups(OriginVoxels.X,RoomCacheRegionSize);
const auto YGroups = BuildAxisGroups(OriginVoxels.Y,RoomCacheRegionSize);
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz)
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz,
FVoxelMarchingCubesStageTimings* StageTimings,
int64* DensitySampleCount,float* GridData)
{
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
if(bSkipProofBlockGrid&&RequiredGridSamples[LocalIndex]==0)return;
@@ -1247,7 +1309,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 SharedX = SharedDeltaX + gx;
const int32 SharedY = SharedDeltaY + gy;
const int32 SharedZ = SharedDeltaZ + gz;
DensityGrid[LocalIndex] = ReuseGrid->Samples[
GridData[LocalIndex] = ReuseGrid->Samples[
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
}
else
@@ -1272,22 +1334,22 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
bBoundarySample
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
DensityGrid[LocalIndex] = ShapeWorldContext
GridData[LocalIndex] = ShapeWorldContext
?ShapeWorldContext->DensityAtLattice(SampleX,SampleY,SampleZ)
:Generator->GetDensityAtLattice(SampleX,SampleY,SampleZ,
bCapturePreEditGrid ? &PreEditDensity : nullptr);
if(OutStageTimings)
if(StageTimings)
{
if(bBoundarySample)++OutStageTimings->DensityGridHaloSamples;
else ++OutStageTimings->DensityGridInteriorSamples;
if(bBoundarySample)++StageTimings->DensityGridHaloSamples;
else ++StageTimings->DensityGridInteriorSamples;
}
if (bCapturePreEditGrid)
{
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
}
if (OutDensitySampleCount)
if (DensitySampleCount)
{
++(*OutDensitySampleCount);
++(*DensitySampleCount);
}
}
};
@@ -1308,7 +1370,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid
&&(!bSkipProofBlockGrid||bStandaloneEnableActiveLaneBatching);
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz)
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz,
FVoxelMarchingCubesStageTimings* StageTimings,int64* DensitySampleCount,
float* GridData)
{
float Densities[8]{};
const bool bBoundarySample=GX0<0||GX0+BatchCount-1>GridDim
@@ -1320,24 +1384,24 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
ActiveShapeWorld->DensityAtLatticeX8(OriginVoxels.X+GX0*Step,Step,
OriginVoxels.Y+gy*Step,OriginVoxels.Z+gz*Step,BatchCount,Densities);
if(OutStageTimings)
if(StageTimings)
{
++OutStageTimings->X8DensityCalls;
OutStageTimings->X8ActiveLanes+=static_cast<uint64>(BatchCount);
++StageTimings->X8DensityCalls;
StageTimings->X8ActiveLanes+=static_cast<uint64>(BatchCount);
for(int32 Lane=0;Lane<BatchCount;++Lane)
{
const int32 SampleGX=GX0+Lane;
if(SampleGX<0||SampleGX>GridDim||gy<0||gy>GridDim
||gz<0||gz>GridDim)
++OutStageTimings->DensityGridHaloSamples;
else ++OutStageTimings->DensityGridInteriorSamples;
++StageTimings->DensityGridHaloSamples;
else ++StageTimings->DensityGridInteriorSamples;
}
}
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];
GridData[LocalIndex]=Densities[Lane];
}
const int32 LastGX=GX0+BatchCount-1;
const int32 ReachBlockX=FMath::Clamp(LastGX,0,CellsPerAxis-1)
@@ -1348,9 +1412,202 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
if(DensitySampleCount)*DensitySampleCount+=BatchCount;
};
if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
const int32 GridZSampleCount=GzHi-GzLo+1;
const int32 GridYSampleCount=GridDim+2;
const bool bParallelizeGridRows=bUseShapeWorldZBatch
||bUseVerticalCacheTraversal||!bUseShapeWorldXBatch;
const std::size_t GridFillLaneCount=std::min<std::size_t>({
GridFillExecution.WorkerLimit,16,
static_cast<std::size_t>(FMath::Max(0,
bParallelizeGridRows?GridYSampleCount:GridZSampleCount))});
const bool bUseParallelShapeWorldGrid=GridFillExecution.ParallelFor!=nullptr
&&GridFillLaneCount>1&&GridFillExecution.BeginWorkerContext!=nullptr
&&GridFillExecution.EndWorkerContext!=nullptr
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid
&&OutCaptureGrid==nullptr;
if(bUseOperatorBlock&&!bUseParallelShapeWorldGrid)
{
// The grid includes one sample of halo on every side. The prepared operator block
// is thread-local, so a parallel fill below opens one block per worker slab.
Generator->BeginDensityBlock(
OriginVoxels+FIntVector(-Step,-Step,GzLo*Step),
Step,MDim,MDim,GzHi-GzLo+1);
}
if(bUseParallelShapeWorldGrid)
{
bGridFillParallel=true;
const FVoxelMesherThreadState TileThreadState=FVoxelMesherThreadState::Capture();
std::array<int64,16> LaneSampleCounts{};
std::array<FVoxelMarchingCubesStageTimings,16> LaneTimings{};
std::atomic<bool> bAborted{false};
auto RunLane=[&](std::size_t Lane,std::size_t Worker)
{
void* WorkerState=GridFillExecution.BeginWorkerContext(
GridFillExecution.WorkerContext,Worker);
int64& LaneSamples=LaneSampleCounts[Lane];
FVoxelMarchingCubesStageTimings& StageTimings=LaneTimings[Lane];
{
FVoxelMesherThreadStateScope ThreadStateScope(TileThreadState);
if(bUseOperatorBlock)
Generator->BeginDensityBlock(
OriginVoxels+FIntVector(-Step,-Step,GzLo*Step),Step,
MDim,MDim,GridZSampleCount);
if(bUseShapeWorldZBatch)
{
const int32 FirstGy=-1+static_cast<int32>(
(static_cast<std::size_t>(GridYSampleCount)*Lane)/GridFillLaneCount);
const int32 EndGy=-1+static_cast<int32>(
(static_cast<std::size_t>(GridYSampleCount)*(Lane+1))/GridFillLaneCount);
for(int32 gy=FirstGy;gy<EndGy;++gy)
{
if(ShouldAbortWork())
{bAborted.store(true,std::memory_order_relaxed);break;}
for(const std::vector<int32>& XGroup:XGroups)
for(const int32 gx:XGroup)
{
if(ShouldAbortWork())
{
bAborted.store(true,std::memory_order_relaxed);
break;
}
float Densities[CHUNK_SIZE+3]{};
const bool bBoundarySample=gx<0||gx>GridDim
||gy<0||gy>GridDim||GzLo<0||GzHi>GridDim;
VoxelForgeCore::Linux77Profiler::FScopedHalo l77Halo(
bBoundarySample);
VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller
islandCaller(bBoundarySample
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
ActiveShapeWorld->DensityAtLatticeZRun(
OriginVoxels.X+gx*Step,OriginVoxels.Y+gy*Step,
OriginVoxels.Z+GzLo*Step,Step,GridZSampleCount,Densities);
for(int32 ZLane=0;ZLane<GridZSampleCount;++ZLane)
{
const int32 gz=GzLo+ZLane;
const int32 LocalIndex=
((gz+1)*MDim+(gy+1))*MDim+(gx+1);
DensityGridData[LocalIndex]=Densities[ZLane];
if(gx<0||gx>GridDim||gy<0||gy>GridDim
||gz<0||gz>GridDim)
++StageTimings.DensityGridHaloSamples;
else ++StageTimings.DensityGridInteriorSamples;
}
LaneSamples+=GridZSampleCount;
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(GzHi,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)
*ReachBlocksPerAxis+ReachBlockX;
}
if(bAborted.load(std::memory_order_relaxed))break;
}
}
else if(bUseVerticalCacheTraversal||!bUseShapeWorldXBatch)
{
const int32 FirstGy=-1+static_cast<int32>(
(static_cast<std::size_t>(GridYSampleCount)*Lane)/GridFillLaneCount);
const int32 EndGy=-1+static_cast<int32>(
(static_cast<std::size_t>(GridYSampleCount)*(Lane+1))/GridFillLaneCount);
for(int32 gy=FirstGy;gy<EndGy;++gy)
{
if(ShouldAbortWork())
{bAborted.store(true,std::memory_order_relaxed);break;}
for(const std::vector<int32>& XGroup:XGroups)
for(const int32 gx:XGroup)
{
for(int32 gz=GzLo;gz<=GzHi;++gz)
{
if(ShouldAbortWork())
{bAborted.store(true,std::memory_order_relaxed);break;}
SampleGridPoint(gx,gy,gz,&StageTimings,&LaneSamples,
DensityGridData);
}
if(bAborted.load(std::memory_order_relaxed))break;
}
if(bAborted.load(std::memory_order_relaxed))break;
}
}
else
{
const int32 FirstGz=GzLo+static_cast<int32>(
(static_cast<std::size_t>(GridZSampleCount)*Lane)/GridFillLaneCount);
const int32 EndGz=GzLo+static_cast<int32>(
(static_cast<std::size_t>(GridZSampleCount)*(Lane+1))/GridFillLaneCount);
for(int32 gz=FirstGz;gz<EndGz;++gz)
{
if(ShouldAbortWork())
{bAborted.store(true,std::memory_order_relaxed);break;}
for(const std::vector<int32>& YGroup:YGroups)
for(const std::vector<int32>& XGroup:XGroups)
for(const int32 gy:YGroup)
{
if(ShouldAbortWork())
{bAborted.store(true,std::memory_order_relaxed);break;}
for(std::size_t Offset=0;Offset<XGroup.size();)
{
const int32 BatchCount=static_cast<int32>(std::min<std::size_t>(
8,XGroup.size()-Offset));
const int32 GX0=XGroup[Offset];
bool bContiguous=true;
for(int32 BatchLane=1;BatchLane<BatchCount;++BatchLane)
bContiguous&=XGroup[
Offset+static_cast<std::size_t>(BatchLane)]==GX0+BatchLane;
if(!bContiguous)
{
for(int32 BatchLane=0;BatchLane<BatchCount;++BatchLane)
SampleGridPoint(XGroup[
Offset+static_cast<std::size_t>(BatchLane)],gy,gz,
&StageTimings,&LaneSamples,DensityGridData);
}
else
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
&StageTimings,&LaneSamples,DensityGridData);
Offset+=static_cast<std::size_t>(BatchCount);
}
}
if(bAborted.load(std::memory_order_relaxed))break;
}
}
if(bUseOperatorBlock)Generator->EndDensityBlock();
}
GridFillExecution.EndWorkerContext(
GridFillExecution.WorkerContext,WorkerState,Worker);
};
struct FDispatchContext{decltype(RunLane)* Run=nullptr;};
FDispatchContext DispatchContext{&RunLane};
const auto DispatchLane=[](void* RawContext,std::size_t Lane,std::size_t Worker)
{
auto& Context=*static_cast<FDispatchContext*>(RawContext);
(*Context.Run)(Lane,Worker);
};
GridFillExecution.ParallelFor(GridFillExecution.SchedulerContext,
GridFillLaneCount,GridFillLaneCount,&DispatchContext,DispatchLane);
if(OutDensitySampleCount)
for(std::size_t Lane=0;Lane<GridFillLaneCount;++Lane)
*OutDensitySampleCount+=LaneSampleCounts[Lane];
if(OutStageTimings)
{
OutStageTimings->GridFillWorkerCount=static_cast<uint32>(GridFillLaneCount);
for(std::size_t Lane=0;Lane<GridFillLaneCount;++Lane)
{
OutStageTimings->DensityGridInteriorSamples+=
LaneTimings[Lane].DensityGridInteriorSamples;
OutStageTimings->DensityGridHaloSamples+=
LaneTimings[Lane].DensityGridHaloSamples;
OutStageTimings->X8DensityCalls+=LaneTimings[Lane].X8DensityCalls;
OutStageTimings->X8ActiveLanes+=LaneTimings[Lane].X8ActiveLanes;
}
}
if(bAborted.load(std::memory_order_relaxed))return FVoxelMeshData();
}
else if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
{
for(const std::vector<int32>& YGroup:YGroups)
for(const std::vector<int32>& XGroup:XGroups)
@@ -1419,7 +1676,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
SampleGridPoint(gx,gy,gz);
SampleGridPoint(gx,gy,gz,OutStageTimings,OutDensitySampleCount,
DensityGridData);
}
}
}
@@ -1460,8 +1718,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if(RequiredGridSamples[NextIndex]==0)break;
++BatchCount;
}
if(BatchCount<2)SampleGridPoint(GX0,gy,gz);
else SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
if(BatchCount<2)SampleGridPoint(
GX0,gy,gz,OutStageTimings,OutDensitySampleCount,
DensityGridData);
else SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
OutStageTimings,OutDensitySampleCount,DensityGridData);
Offset+=static_cast<size_t>(BatchCount);
continue;
}
@@ -1474,18 +1735,21 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if(!bContiguous)
{
for(int32 Lane=0;Lane<BatchCount;++Lane)
SampleGridPoint(XGroup[Offset+static_cast<size_t>(Lane)],gy,gz);
SampleGridPoint(XGroup[Offset+static_cast<size_t>(Lane)],gy,gz,
OutStageTimings,OutDensitySampleCount,DensityGridData);
Offset+=static_cast<size_t>(BatchCount);
continue;
}
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
OutStageTimings,OutDensitySampleCount,DensityGridData);
Offset+=static_cast<size_t>(BatchCount);
}
}
else
{
for(const int32 gx:XGroup)SampleGridPoint(gx,gy,gz);
for(const int32 gx:XGroup)SampleGridPoint(
gx,gy,gz,OutStageTimings,OutDensitySampleCount,DensityGridData);
}
}
}
@@ -1506,7 +1770,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
GridSampleCount * sizeof(float));
}
}
if (bUseOperatorBlock)
if (bUseOperatorBlock&&!bGridFillParallel)
{
Generator->EndDensityBlock();
}
@@ -13,6 +13,7 @@
#include "CoreMinimal.h"
#include <atomic>
#include <cstddef>
#include <vector>
#include "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
#include "VoxelGenerator.h"
@@ -41,10 +42,29 @@ struct FVoxelSharedDensityGrid
}
};
// Host-owned scheduling hooks for a predicted-heavy density-grid fill. Each task owns one
// deterministic Z slab and gets the source tile's worker-local sampling context.
struct FVoxelGridFillExecution
{
using FTask = void(*)(void* taskContext, std::size_t index, std::size_t workerIndex);
using FParallelFor = double(*)(void* schedulerContext, std::size_t count,
std::size_t workerLimit, void* taskContext, FTask task);
using FBeginWorkerContext = void*(*)(void* context, std::size_t workerIndex);
using FEndWorkerContext = void(*)(void* context, void* workerState,
std::size_t workerIndex);
void* SchedulerContext = nullptr;
FParallelFor ParallelFor = nullptr;
std::size_t WorkerLimit = 1;
void* WorkerContext = nullptr;
FBeginWorkerContext BeginWorkerContext = nullptr;
FEndWorkerContext EndWorkerContext = nullptr;
};
// Optional, sampled timing output for the research audit; null in production calls.
// Mesures echantillonnees facultatives pour l'audit; pointeur nul en production.
struct FVoxelMarchingCubesStageTimings
{
uint32 GridFillWorkerCount = 1;
double DensityGridMilliseconds = 0.0;
double CellClassificationMilliseconds = 0.0;
double GradientNormalsMilliseconds = 0.0;
@@ -161,6 +181,8 @@ public:
// GenerateMesh calls finish. A malformed/incomplete lattice is ignored and falls back to
// the canonical generator path rather than changing geometry.
void SetSharedDensityGrid(const FVoxelSharedDensityGrid* InGrid) { SharedDensityGrid = InGrid; }
void SetGridFillExecution(const FVoxelGridFillExecution& InExecution)
{ GridFillExecution = InExecution; }
// Standalone's interval world is an explicit immutable sampling context. Game builds do not
// set this pointer and continue through Generator.
@@ -189,6 +211,7 @@ private:
const std::atomic<bool>* ShutdownFlag = nullptr;
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
FVoxelGridFillExecution GridFillExecution;
const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
bool bUseHybridSharpMesher = false;