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:
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelMarchingCubesMesher.generated.h"
|
||||
@@ -38,6 +39,24 @@ 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;
|
||||
};
|
||||
|
||||
// Stable plan data copied onto one worker-owned mesher for hybrid LOD0 generation.
|
||||
// Donnees stables du plan copiees dans un mailleur de worker pour le mode hybride LOD0.
|
||||
struct FVoxelHybridSharpFeatureBox
|
||||
@@ -111,6 +130,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; }
|
||||
|
||||
// The caller owns this mesher instance for one tile task; no shared mutable feature state.
|
||||
// L'appelant possede cette instance pour une tuile; aucune donnee partagee modifiable.
|
||||
@@ -137,6 +158,7 @@ private:
|
||||
|
||||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||||
FVoxelGridFillExecution GridFillExecution;
|
||||
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
|
||||
bool bUseHybridSharpMesher = false;
|
||||
bool bUseHybridSharpFeatureSnap = false;
|
||||
|
||||
Reference in new issue
Block a user