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

@@ -15,9 +15,73 @@
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include <atomic>
#include <array>
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"),
@@ -692,6 +756,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
@@ -739,7 +804,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const bool bUseOperatorBlock = !bUseSharedDensityGrid
&& !bUseParameterizedBrickEvaluator
&& GVoxelForgeUseOperatorBlock != 0;
if (bUseOperatorBlock)
const int32 GridZSampleCount=GzHi-GzLo+1;
const std::size_t GridFillLaneCount=std::min<std::size_t>({
GridFillExecution.WorkerLimit,16,
static_cast<std::size_t>(FMath::Max(0,GridZSampleCount))});
const bool bUseParallelDensityGrid=GridFillExecution.ParallelFor!=nullptr
&&GridFillLaneCount>1&&GridFillExecution.BeginWorkerContext!=nullptr
&&GridFillExecution.EndWorkerContext!=nullptr&&!bUseSharedDensityGrid
&&!bUseParameterizedBrickEvaluator&&OutCaptureGrid==nullptr;
if (bUseOperatorBlock&&!bUseParallelDensityGrid)
{
// 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.
@@ -889,6 +962,80 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
}
#endif
}
else if(bUseParallelDensityGrid)
{
const FVoxelMesherThreadState TileThreadState=FVoxelMesherThreadState::Capture();
std::array<int64,16> LaneSampleCounts{};
std::atomic<bool> bAborted{false};
auto RunLane=[&](std::size_t Lane,std::size_t Worker)
{
void* WorkerState=GridFillExecution.BeginWorkerContext(
GridFillExecution.WorkerContext,Worker);
int64 LaneSamples=0;
{
FVoxelMesherThreadStateScope ThreadStateScope(TileThreadState);
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);
if(bUseOperatorBlock)
{
Generator->BeginDensityBlock(
OriginVoxels+FIntVector(-Step,-Step,FirstGz*Step),Step,
MDim,MDim,EndGz-FirstGz);
}
for(int32 gz=FirstGz;gz<EndGz;++gz)
{
if(ShouldAbortWork())
{ bAborted.store(true,std::memory_order_relaxed);break; }
for(int32 gy=-1;gy<=GridDim;++gy)
{
if(ShouldAbortWork())
{ bAborted.store(true,std::memory_order_relaxed);break; }
for(int32 gx=-1;gx<=GridDim;++gx)
{
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(gx+1);
const int32 ReachBlockX=FMath::Clamp(gx,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ=FMath::Clamp(gz,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)
*ReachBlocksPerAxis+ReachBlockX;
float PreEditDensity=0.0f;
DensityGridData[LocalIndex]=Generator->GetDensityAt(
OriginVoxels.X+gx*Step,OriginVoxels.Y+gy*Step,
OriginVoxels.Z+gz*Step,
bCapturePreEditGrid?&PreEditDensity:nullptr);
if(bCapturePreEditGrid)
OutPreEditDensityGrid->Samples[LocalIndex]=PreEditDensity;
++LaneSamples;
}
}
if(bAborted.load(std::memory_order_relaxed))break;
}
if(bUseOperatorBlock)Generator->EndDensityBlock();
}
LaneSampleCounts[Lane]=LaneSamples;
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(bAborted.load(std::memory_order_relaxed))return FVoxelMeshData();
}
else for (int32 gz = GzLo; gz <= GzHi; gz++)
{
if (ShouldAbortWork())
@@ -959,7 +1106,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
GridSampleCount * sizeof(float));
}
}
if (bUseOperatorBlock)
if (bUseOperatorBlock&&!bUseParallelDensityGrid)
{
Generator->EndDensityBlock();
}
+143 -11
View File
@@ -163,6 +163,10 @@ public:
std::atomic<bool> bSnapshotReady{false};
std::atomic<bool> bReady{false};
std::atomic<bool> bInitialFailed{false};
std::atomic<bool> bFarWorldRequired{false};
std::atomic<bool> bFarWorldReady{false};
std::atomic<bool> bFarWorldBuildInFlight{false};
std::atomic<bool> bFarWorldBuildFailed{false};
std::atomic<bool> bRegionCompileInFlight{false};
std::atomic<bool> bRegionCompileFinished{false};
std::atomic<bool> bRegionCompileSucceeded{false};
@@ -7694,6 +7698,10 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
MakeShared<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe>();
State->World = std::make_unique<VoxelForgeCore::World>(std::move(*Snapshot));
State->FullResolutionLodCount = State->World->configuration().fullResolutionLodCount;
const bool bFarWorldRequired=State->World->configuration().maxLod
>=State->FullResolutionLodCount;
State->bFarWorldRequired.store(bFarWorldRequired,std::memory_order_release);
State->bFarWorldReady.store(!bFarWorldRequired,std::memory_order_release);
FVector CameraVoxels = FVector::ZeroVector;
FVector InitialPlayerPosition = FVector::ZeroVector;
@@ -7766,7 +7774,7 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
Request.clipMaxVoxel = ClipMax;
Request.workerLimit = static_cast<std::uint32_t>(FMath::Clamp(GetMaxConcurrentTasks(), 1, 16));
Request.detail = VoxelForgeCore::DetailVariant::coherent;
Request.includeFarWorld = true;
Request.includeFarWorld = false;
VoxelForgeCoreState = State;
State->bSnapshotReady.store(true, std::memory_order_release);
@@ -7802,6 +7810,9 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
if (State->World)
{
State->World->retainTileRegions(InitialDesired);
if(State->bFarWorldRequired.load(std::memory_order_acquire))
State->bFarWorldReady.store(State->World->hasFarWorld(),
std::memory_order_release);
FScopeLock RetainedLock(&State->RetainedTilesLock);
for (const VoxelForgeCore::TileKey& Key : InitialDesired)
{
@@ -12365,6 +12376,15 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
const auto SubmitTileWithTraceCount = [&](const FVoxelTileKey& Tile,
EVoxelTileWorkPriority Priority)
{
if(bUseVoxelForgeCore&&Tile.Level>=2)
{
const TSharedPtr<FVoxelForgeCoreRuntimeState,ESPMode::ThreadSafe> CoreState=
VoxelForgeCoreState;
if(CoreState.IsValid()
&&CoreState->bFarWorldRequired.load(std::memory_order_acquire)
&&!CoreState->bFarWorldReady.load(std::memory_order_acquire))
return;
}
if (Tile.Level >= 0 && Tile.Level < 9)
{
++SchedulerLoadAttemptsByLevel[Tile.Level];
@@ -12418,6 +12438,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
{
MaxDesiredLevel = FMath::Max(MaxDesiredLevel, T.Level);
}
const TSharedPtr<FVoxelForgeCoreRuntimeState,ESPMode::ThreadSafe> CoreState=
VoxelForgeCoreState;
auto SubmitLevel = [&](int32 Level, EVoxelTileWorkPriority Priority, int32 AdmissionCap,
bool bPreferTravelDirection)
@@ -12437,20 +12459,50 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
// Sort only outstanding keys; re-sorting loaded or pending tiles on every tick inflated game-thread work.
// Trier uniquement les cles restantes ; re-trier chaque tick les tuiles chargees ou en attente alourdissait le thread de jeu.
if (bPreferTravelDirection && LevelTiles.Num() > 1)
TMap<FVoxelTileKey,uint32> PassageScores;
if(Level<=2&&CoreState.IsValid()&&CoreState->bReady.load(std::memory_order_acquire))
{
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
FReadScopeLock WorldReadLock(CoreState->WorldLock);
if(CoreState->World)
{
Heading = GetActorTransform().InverseTransformVectorNoScale(
PlayerPawn->GetActorForwardVector());
PassageScores.Reserve(LevelTiles.Num());
for(const FVoxelTileKey& Key:LevelTiles)
{
const VoxelForgeCore::TileKey CoreKey{
{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
PassageScores.Add(Key,CoreState->World->passageOverlapScore(CoreKey));
}
}
Heading = Heading.GetSafeNormal();
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
* GetActiveVoxelSizeCm();
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
}
const bool bSortForPassagePriority=Level<=2&&!PassageScores.IsEmpty();
if ((bPreferTravelDirection||bSortForPassagePriority)&&LevelTiles.Num()>1)
{
FVector Heading=FVector::ZeroVector;
FVector PlayerLocalCm=FVector::ZeroVector;
double TileSizeCm=0.0;
if(bPreferTravelDirection)
{
Heading=GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
if(Heading.IsNearlyZero()&&IsValid(PlayerPawn))
{
Heading=GetActorTransform().InverseTransformVectorNoScale(
PlayerPawn->GetActorForwardVector());
}
Heading=Heading.GetSafeNormal();
PlayerLocalCm=WorldToLocalCm(CenterPosition);
TileSizeCm=static_cast<double>(VF_ClipLevelExtentVoxels(Level))
*GetActiveVoxelSizeCm();
}
std::stable_sort(LevelTiles.GetData(),LevelTiles.GetData()+LevelTiles.Num(),
[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
if(bSortForPassagePriority)
{
const uint32 ScoreA=PassageScores.FindRef(A);
const uint32 ScoreB=PassageScores.FindRef(B);
if(ScoreA!=ScoreB)return ScoreA>ScoreB;
}
if(!bPreferTravelDirection)return false;
const FVector CenterA(
(static_cast<double>(A.Coord.X) + 0.5) * TileSizeCm,
(static_cast<double>(A.Coord.Y) + 0.5) * TileSizeCm,
@@ -12509,6 +12561,64 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
return true;
};
const auto AreEarlyLODTilesReady = [this]()
{
for(const FVoxelTileKey& Tile:DesiredSorted)
{
if(Tile.Level<=1&&!LoadedTiles.Contains(Tile)
&&!CollisionNotRequiredTiles.Contains(Tile))
return false;
}
return true;
};
if(bUseVoxelForgeCore&&CoreState.IsValid()
&&CoreState->bFarWorldRequired.load(std::memory_order_acquire)
&&CoreState->bReady.load(std::memory_order_acquire)
&&!CoreState->bFarWorldReady.load(std::memory_order_acquire)
&&!CoreState->bFarWorldBuildFailed.load(std::memory_order_acquire)
&&!CoreState->bFarWorldBuildInFlight.load(std::memory_order_acquire)
&&AreEarlyLODTilesReady())
{
bool bExpected=false;
if(CoreState->bFarWorldBuildInFlight.compare_exchange_strong(
bExpected,true,std::memory_order_acq_rel))
{
ActiveTaskCount.fetch_add(1,std::memory_order_relaxed);
UE::Tasks::Launch(TEXT("VoxelForgeCoreFarWorldBuild"),[this,CoreState]()
{
struct FTaskGuard
{
std::atomic<int32>& Counter;
~FTaskGuard(){Counter.fetch_sub(1,std::memory_order_release);}
} Guard{ActiveTaskCount};
if(bShuttingDown.load(std::memory_order_acquire)
||CoreState->bShuttingDown.load(std::memory_order_acquire))
{
CoreState->bFarWorldBuildInFlight.store(false,std::memory_order_release);
return;
}
bool bBuilt=false;
{
FWriteScopeLock WorldWriteLock(CoreState->WorldLock);
if(CoreState->World)bBuilt=CoreState->World->buildFarWorld();
}
if(bBuilt)
{
CoreState->bFarWorldReady.store(true,std::memory_order_release);
CoreState->bRebuildDesired.store(true,std::memory_order_release);
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeCoreRuntime] deferred_far_world=ready"));
}
else
{
CoreState->bFarWorldBuildFailed.store(true,std::memory_order_release);
UE_LOG(LogTemp,Error,
TEXT("[VoxelForgeCoreRuntime] deferred far-world build failed"));
}
CoreState->bFarWorldBuildInFlight.store(false,std::memory_order_release);
},UE::Tasks::ETaskPriority::BackgroundNormal);
}
}
// Policy 0 reconstructs L77. Policy 3 enters that order only after sustained fast travel.
const bool bPolicy3FastTravel = SchedulerPolicy == 3 && bSpeedAwareFastMode;
@@ -13306,6 +13416,8 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
if (!State.IsValid()||State->bInitialFailed.load(std::memory_order_acquire)
||State->bRegionCompileFailed.load(std::memory_order_acquire)
||State->bShuttingDown.load(std::memory_order_acquire))return;
if(Tile.Level>=2&&State->bFarWorldRequired.load(std::memory_order_acquire)
&&!State->bFarWorldReady.load(std::memory_order_acquire))return;
const bool bShapeReady=State->bReady.load(std::memory_order_acquire);
if(!bShapeReady)
{
@@ -13502,6 +13614,26 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
Options.hashDensity = bCaptureHashes;
Options.legacyDirectMappedColumnCache=bLegacyDirectMappedColumnCache;
Options.memoizeMcGradients=bMemoizeCoreMCGradients;
if(Tile.Level==0)
{
const int32 ActiveStreamingTasks=GetActiveGenerationTaskCount();
const int32 WorkerBudget=FMath::Clamp(
GetMaxConcurrentStreamingTasks(),1,12);
const int32 AvailableGridLanes=FMath::Max(0,
WorkerBudget-FMath::Max(0,ActiveStreamingTasks-1));
const int32 GridFillWorkers=FMath::Min(4,AvailableGridLanes);
if(GridFillWorkers>1)
{
FReadScopeLock WorldReadLock(State->WorldLock);
// Only split dense junction overlaps; lower scores are too common to repay
// the helper-lane and per-thread context setup costs.
if(State->World&&State->World->passageOverlapScore(CoreKey)>=9)
{
Options.gridFillScheduler=&State->Scheduler;
Options.gridFillWorkerLimit=static_cast<std::uint32_t>(GridFillWorkers);
}
}
}
std::string CoreError;
std::optional<VoxelForgeCore::TileMesh> CoreMesh;
{