wip(assembler): round S12 -- short ready queue keeps workers fed between ticks: full set 6.0 s -> 4.9 s
voxel.StreamingReadyQueueDepth=8 admits one extra worker-set of prioritized tasks; voxel.StreamingBackgroundWorkerCap=1 sizes slots to the 8 TaskGraph background workers actually running tile tasks. Full set 5.96 -> 4.91 s, LOD0 1.40 -> 1.09 s, ring 0.49 -> 0.51 s, 40 m/s ring 75/75. Tick p95 +0.6 ms spawn / +0.26 ms walk, max flat. No geometry change. Report: Standalone/Research/WorldAssemblerM3/Round16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
dd9bfd3217
commit
212814a75c
8 files changed
+554
-36
No files matched your search
@@ -284,9 +284,17 @@ void RecordTile(const FTileSample& Sample)
|
||||
|
||||
GState->Samples.Add(Sample);
|
||||
++GState->TileRecordsWritten;
|
||||
const double TraceNowSeconds = FPlatformTime::Seconds() - GState->StartSeconds;
|
||||
const double SubmittedAtSeconds = Sample.RequestStartCycles != 0
|
||||
? TraceNowSeconds - Sample.RequestToApplySeconds : -1.0;
|
||||
const double WorkerStartedAtSeconds = Sample.WorkerTaskStartCycles != 0
|
||||
? SubmittedAtSeconds + Sample.WorkerQueueSeconds : -1.0;
|
||||
const double WorkerEndedAtSeconds = Sample.WorkerTaskEndCycles != 0
|
||||
? WorkerStartedAtSeconds + Sample.WorkerTaskSeconds : -1.0;
|
||||
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,\"resident_at_request\":%d,"
|
||||
"\"submitted_t_s\":%s,\"worker_start_t_s\":%s,\"worker_end_t_s\":%s,\"worker_thread_id\":%u,"
|
||||
"\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
|
||||
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,\"worker_task_s\":%s,"
|
||||
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
||||
@@ -295,6 +303,10 @@ void RecordTile(const FTileSample& Sample)
|
||||
static_cast<unsigned long long>(Sample.RequestOrdinal),
|
||||
static_cast<unsigned long long>(Sample.GenerationOrdinal),
|
||||
Sample.bResidentAtRequest ? 1 : 0,
|
||||
*Number(SubmittedAtSeconds),
|
||||
*Number(WorkerStartedAtSeconds),
|
||||
*Number(WorkerEndedAtSeconds),
|
||||
Sample.WorkerThreadId,
|
||||
Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
||||
Sample.bEmpty ? 1 : 0, Sample.Vertices, Sample.Triangles,
|
||||
static_cast<unsigned long long>(Sample.GeometryBytes),
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "Async/Fundamental/Scheduler.h"
|
||||
#include "Async/TaskGraphInterfaces.h"
|
||||
#include "Components/PointLightComponent.h"
|
||||
#include "VoxelAtmosphereManager.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
@@ -221,6 +223,18 @@ namespace
|
||||
GVoxelForgeStreamingResultBacklogCap,
|
||||
TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap."));
|
||||
|
||||
int32 GVoxelForgeStreamingReadyQueueDepth = 8;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingReadyQueueDepth(
|
||||
TEXT("voxel.StreamingReadyQueueDepth"),
|
||||
GVoxelForgeStreamingReadyQueueDepth,
|
||||
TEXT("Extra admitted tile tasks kept ready beyond the worker slots, up to one worker-set; 0 disables the refill queue."));
|
||||
|
||||
int32 GVoxelForgeStreamingBackgroundWorkerCap = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingBackgroundWorkerCap(
|
||||
TEXT("voxel.StreamingBackgroundWorkerCap"),
|
||||
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||
TEXT("Size tile admission slots to the TaskGraph background workers used by tile tasks; 0 restores the logical-core cap."));
|
||||
|
||||
int32 GVoxelForgeProfileTileGeneration = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
||||
TEXT("voxel.ProfileTileGeneration"),
|
||||
@@ -2064,6 +2078,8 @@ void AVoxelWorld::BeginPlay()
|
||||
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
|
||||
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
|
||||
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
||||
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
|
||||
int32 CommandLineStreamingBackgroundWorkerCap = GVoxelForgeStreamingBackgroundWorkerCap;
|
||||
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
||||
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
||||
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
||||
@@ -2158,6 +2174,18 @@ void AVoxelWorld::BeginPlay()
|
||||
{
|
||||
GVoxelForgeStreamingResultBacklogCap = FMath::Max(0, CommandLineStreamingResultBacklogCap);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingReadyQueueDepth="),
|
||||
CommandLineStreamingReadyQueueDepth))
|
||||
{
|
||||
GVoxelForgeStreamingReadyQueueDepth = FMath::Clamp(
|
||||
CommandLineStreamingReadyQueueDepth, 0, 32);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingBackgroundWorkerCap="),
|
||||
CommandLineStreamingBackgroundWorkerCap))
|
||||
{
|
||||
GVoxelForgeStreamingBackgroundWorkerCap =
|
||||
CommandLineStreamingBackgroundWorkerCap != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
||||
CommandLineStep1PrequeueVisibleLOD0))
|
||||
{
|
||||
@@ -2560,6 +2588,48 @@ void AVoxelWorld::BeginPlay()
|
||||
Settings->bGenerateSkirts ? 1 : 0,
|
||||
Settings->FullResClipLevels, Settings->CoarseTileCells));
|
||||
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
FTaskGraphInterface& TaskGraph = FTaskGraphInterface::Get();
|
||||
LowLevelTasks::FScheduler& TaskScheduler = LowLevelTasks::FScheduler::Get();
|
||||
const int32 QueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, GetMaxConcurrentStreamingTasks());
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("worker_backend_config"), FString::Printf(
|
||||
TEXT("\"logical_cores\":%d,\"effective_tile_slots\":%d,"
|
||||
"\"task_api\":\"UE::Tasks::Launch\",\"task_scheduler_workers\":%u,"
|
||||
"\"task_scheduler_max_workers\":%u,\"task_scheduler_worker_priority\":%d,"
|
||||
"\"task_scheduler_background_priority\":%d,"
|
||||
"\"taskgraph_worker_threads_per_priority\":%d,\"taskgraph_foreground_threads\":%d,"
|
||||
"\"taskgraph_background_threads\":%d,\"global_queued_thread_pool_threads\":%d,"
|
||||
"\"step1_cook_pool_threads\":%d,\"ready_queue_depth_cvar\":%d,"
|
||||
"\"ready_queue_depth\":%d,"
|
||||
"\"near_priority\":\"BackgroundHigh\","
|
||||
"\"horizon_priority\":\"BackgroundNormal\","
|
||||
"\"streaming_coarse_first\":%d,\"streaming_dynamic_near_reserve\":%d,"
|
||||
"\"streaming_legacy_pending_result_cap\":%d,"
|
||||
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
|
||||
"\"collision_gate\":%d,"
|
||||
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d"),
|
||||
FPlatformMisc::NumberOfCoresIncludingHyperthreads(), GetMaxConcurrentStreamingTasks(),
|
||||
TaskScheduler.GetNumWorkers(), TaskScheduler.GetMaxNumWorkers(),
|
||||
static_cast<int32>(TaskScheduler.GetWorkerPriority()),
|
||||
static_cast<int32>(TaskScheduler.GetBackgroundPriority()),
|
||||
TaskGraph.GetNumWorkerThreads(), TaskGraph.GetNumForegroundThreads(),
|
||||
TaskGraph.GetNumBackgroundThreads(), GThreadPool ? GThreadPool->GetNumThreads() : 0,
|
||||
GVoxelForgeStep1CoreCookPool.Pool ? GVoxelForgeStep1CoreCookPool.Pool->GetNumThreads() : 0,
|
||||
GVoxelForgeStreamingReadyQueueDepth, QueueDepth,
|
||||
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
|
||||
GVoxelForgeCollisionGate,
|
||||
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost));
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_worker_cap_readback"), FString::Printf(
|
||||
TEXT("\"streaming_background_worker_cap\":%d,\"background_worker_threads\":%d,"
|
||||
"\"effective_tile_slots\":%d"),
|
||||
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||
TaskGraph.GetNumBackgroundThreads(), GetMaxConcurrentStreamingTasks()));
|
||||
}
|
||||
|
||||
// Système de strates — a cooked season takes precedence; otherwise the authored pool path is
|
||||
// exactly the legacy one.
|
||||
if (!Settings->Season.IsNull() || Settings->StratePool.Num() > 0
|
||||
@@ -9779,6 +9849,10 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
Sample.WorkPriority = static_cast<int32>(Result.WorkPriority);
|
||||
Sample.RequestOrdinal = Result.RequestOrdinal;
|
||||
Sample.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Sample.RequestStartCycles = Result.RequestStartCycles;
|
||||
Sample.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||
Sample.WorkerTaskEndCycles = Result.WorkerTaskEndCycles;
|
||||
Sample.WorkerThreadId = Result.WorkerThreadId;
|
||||
Sample.bResidentAtRequest = Result.bResidentAtRequest;
|
||||
Sample.Verdict = Result.ClassifyVerdict;
|
||||
Sample.bSealedSolidProof = Result.bSealedSolidProof;
|
||||
@@ -11349,8 +11423,8 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
|
||||
int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
||||
{
|
||||
// T2.d — both sides of the cap are authored in Voxel|Streaming. BackgroundNormal priority
|
||||
// (see LoadTile) already stops gen from starving the frame; this cap stops a flat task count
|
||||
// from thrashing context switches on small CPUs where it exceeds spare parallelism.
|
||||
// (see LoadTile) yields to frame work; this cap avoids oversubscribing available workers.
|
||||
// La priorite BackgroundNormal cede la place au frame ; ce plafond evite de depasser les workers disponibles.
|
||||
const int32 Asset = Settings ? Settings->MaxConcurrentTasks : 16;
|
||||
const int32 LogicalCores = FMath::Max(
|
||||
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
||||
@@ -11369,6 +11443,17 @@ int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
||||
: NormalCap;
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetMaxConcurrentStreamingTasks() const
|
||||
{
|
||||
const int32 TaskCap = GetMaxConcurrentTasks();
|
||||
if (TaskCap <= 0 || GVoxelForgeStreamingBackgroundWorkerCap == 0)
|
||||
{
|
||||
return TaskCap;
|
||||
}
|
||||
const int32 BackgroundWorkers = FTaskGraphInterface::Get().GetNumBackgroundThreads();
|
||||
return BackgroundWorkers > 0 ? FMath::Min(TaskCap, BackgroundWorkers) : TaskCap;
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetActiveGenerationTaskCount() const
|
||||
{
|
||||
return FMath::Max(0, ActiveTaskCount.load(std::memory_order_relaxed));
|
||||
@@ -11509,7 +11594,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
? Step1HeldApplyTiles.Num() : 0;
|
||||
const int32 PendingResultsEstimate = FMath::Max(
|
||||
0, PendingTiles.Num() - ActiveTasks - HeldCompletedResults);
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||
const int32 AdmissionOccupancy = GetGenerationAdmissionCount();
|
||||
const uint64 TotalWorkerCycles = TotalGenerationWorkerTaskCycles.load(std::memory_order_relaxed);
|
||||
const uint64 WorkerCyclesDelta = TotalWorkerCycles >= LastStreamingSchedulerWorkerCycles
|
||||
@@ -11684,7 +11769,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
&& GVoxelForgeCoreRegionConcurrentAdmission == 0;
|
||||
}
|
||||
}
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||
// Keep at most one worker-set of ready work so an admission pass can feed idle slots
|
||||
// without letting far work consume the near reserve. / Garder une file d'un ensemble
|
||||
// de workers pour nourrir les slots sans consommer la reserve proche.
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||
FVoxelTileKey WalkableSupportTile;
|
||||
@@ -11741,11 +11832,18 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
return;
|
||||
}
|
||||
const bool bPawnMoving = PlayerHeading.Size() >= 50.0f;
|
||||
bStreamingPlayerMoving = bPawnMoving;
|
||||
// Match the near reserve to the added far queue so queued horizon tiles cannot spend that headroom.
|
||||
// Aligner la reserve proche sur la file lointaine pour que l'horizon ne consomme pas cette marge.
|
||||
const int32 QueueAwareNearTaskReserve = FMath::Clamp(
|
||||
FMath::Max(AuthoredNearTaskReserve, ReadyQueueDepth), 0,
|
||||
FMath::Max(0, MaxTasks - 1));
|
||||
EffectiveNearTaskReserve = GVoxelForgeStreamingDynamicNearReserve == 0
|
||||
? AuthoredNearTaskReserve
|
||||
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? AuthoredNearTaskReserve : 0);
|
||||
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? QueueAwareNearTaskReserve : 0);
|
||||
const int32 NearTaskReserve = EffectiveNearTaskReserve;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve);
|
||||
const int32 NearAdmissionCap = bPawnMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve) + ReadyQueueDepth;
|
||||
const int32 FirstCoarseLevel = FMath::Max(
|
||||
2, Settings ? Settings->FullResClipLevels : 2);
|
||||
const bool bUseCoarseFirstOrder = GVoxelForgeStreamingCoarseFirst != 0
|
||||
@@ -11765,11 +11863,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const int32 LogicalCores = FMath::Max(
|
||||
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
||||
const int32 AutomaticCap = FMath::Max(2, LogicalCores - 2);
|
||||
const int32 BackgroundWorkerThreads = FTaskGraphInterface::Get().GetNumBackgroundThreads();
|
||||
const int32 AssetCap = Settings ? Settings->MaxConcurrentTasks : 16;
|
||||
const int32 AuthoredCoreCap = Settings ? Settings->MaxGenerationWorkerCores : 0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStreamingBudget] logical_cores=%d automatic_core_cap=%d "
|
||||
"authored_core_cap=%d asset_task_cap=%d effective_generation_cap=%d "
|
||||
"taskgraph_background_workers=%d background_worker_cap=%d "
|
||||
"ready_queue_depth=%d total_admission_cap=%d "
|
||||
"step1_generation_cap=%d step1_max_mesh_applies=%d "
|
||||
"near_task_reserve=%d dynamic_reserve=%d legacy_result_cap=%d "
|
||||
"moving=%d walkable_ring_ready=%d "
|
||||
@@ -11780,6 +11881,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
AuthoredCoreCap,
|
||||
AssetCap,
|
||||
MaxTasks,
|
||||
BackgroundWorkerThreads,
|
||||
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||
ReadyQueueDepth,
|
||||
TotalAdmissionCap,
|
||||
GVoxelForgeStep1GenerationCap,
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
NearTaskReserve,
|
||||
@@ -12110,11 +12215,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
|
||||
// FLOOR RESPONSIVENESS — the support tile and collision-required 3x3 XY ring lead ordinary work.
|
||||
// PRIORITE AU SOL — la tuile de support et l'anneau XY 3x3 requis pour la collision passent avant le reste.
|
||||
// They run at BackgroundHigh so the pawn's floor gets the first available worker.
|
||||
// Ils passent en BackgroundHigh afin que le sol du pion obtienne les premiers workers disponibles.
|
||||
// They use the foreground task lane when enabled, above ordinary background streaming.
|
||||
// Ils utilisent la voie foreground lorsqu'elle est active, avant le streaming distant.
|
||||
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
||||
{
|
||||
if (GetGenerationAdmissionCount() >= MaxTasks)
|
||||
if (GetGenerationAdmissionCount() >= TotalAdmissionCap)
|
||||
{
|
||||
++SchedulerCriticalFullCapBreaks;
|
||||
break;
|
||||
@@ -12128,7 +12233,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// still in flight is KEPT queued (retried next frame) so its stale pre-carve result is corrected.
|
||||
for (auto It = DirtyRemeshQueue.CreateIterator(); It; ++It)
|
||||
{
|
||||
if (GetGenerationAdmissionCount() >= MaxTasks)
|
||||
if (GetGenerationAdmissionCount() >= TotalAdmissionCap)
|
||||
{
|
||||
++SchedulerDirtyFullCapBreaks;
|
||||
break;
|
||||
@@ -12235,12 +12340,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
const EVoxelTileWorkPriority Priority = Level == 0
|
||||
? EVoxelTileWorkPriority::LOD0 : EVoxelTileWorkPriority::Horizon;
|
||||
SubmitLevel(Level, Priority, MaxTasks, true);
|
||||
const int32 LevelAdmissionCap = Priority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : NearAdmissionCap;
|
||||
SubmitLevel(Level, Priority, LevelAdmissionCap, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, MaxTasks, false);
|
||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, NearAdmissionCap, false);
|
||||
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
||||
{
|
||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||
@@ -12283,8 +12390,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
GVoxelForgeMaxPendingTilesObserved = PendingTiles.Num();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStreamingBudget] pending_max=%d effective=%d submitted=%d"),
|
||||
GVoxelForgeMaxPendingTilesObserved, MaxTasks, Submitted);
|
||||
TEXT("[VoxelForgeStreamingBudget] pending_max=%d effective=%d ready_queue=%d submitted=%d"),
|
||||
GVoxelForgeMaxPendingTilesObserved, MaxTasks, ReadyQueueDepth, Submitted);
|
||||
}
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
&& GetGenerationAdmissionCount() >= CoarseTaskCap
|
||||
@@ -12411,8 +12518,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
for (int32 Level = 0; Level < 9; ++Level)
|
||||
{
|
||||
AdmissionCapByLevel[Level] = bUseCoarseFirstOrder
|
||||
? (Level >= FirstCoarseLevel ? CoarseTaskCap : MaxTasks)
|
||||
: (Level == 0 ? MaxTasks : CoarseTaskCap);
|
||||
? (Level >= FirstCoarseLevel || Level > 0 ? CoarseTaskCap : NearAdmissionCap)
|
||||
: (Level == 0 ? NearAdmissionCap : CoarseTaskCap);
|
||||
if (DesiredMissingByLevel[Level] <= 0) continue;
|
||||
|
||||
if (bAllChunksLoaded)
|
||||
@@ -12426,7 +12533,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
const int32 DeclinedAtCap = AdmissionCapByLevel[Level] > 0
|
||||
? AdmissionCapByLevel[Level] : SchedulerOrderStopCap;
|
||||
if (DeclinedAtCap < MaxTasks)
|
||||
if (DeclinedAtCap < TotalAdmissionCap)
|
||||
{
|
||||
DeclinedRuleByLevel[Level] = 2; // Near reserve reached during this submit pass.
|
||||
// Reserve proche atteinte pendant cette passe d'admission.
|
||||
@@ -12439,14 +12546,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
||||
}
|
||||
}
|
||||
else if (AdmissionOccupancy >= MaxTasks)
|
||||
else if (AdmissionOccupancy >= TotalAdmissionCap)
|
||||
{
|
||||
DeclinedRuleByLevel[Level] = 1; // Full task admission cap.
|
||||
// Capacite totale d'admission des taches atteinte.
|
||||
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
||||
}
|
||||
else if (AdmissionOccupancy >= AdmissionCapByLevel[Level]
|
||||
&& AdmissionCapByLevel[Level] < MaxTasks)
|
||||
&& AdmissionCapByLevel[Level] < TotalAdmissionCap)
|
||||
{
|
||||
DeclinedRuleByLevel[Level] = 2; // Authored near reserve held back this LOD.
|
||||
// La reserve proche configuree retient ce niveau de detail.
|
||||
@@ -12497,7 +12604,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"core_region_compile_pending\":%d,\"core_ready\":%d,\"coarse_first\":%d,"
|
||||
"\"dynamic_near_reserve\":%d,\"legacy_pending_result_cap\":%d,"
|
||||
"\"core_region_concurrent_admission\":%d,"
|
||||
"\"max_tasks\":%d,\"near_reserve\":%d,"
|
||||
"\"max_tasks\":%d,\"ready_queue_depth\":%d,"
|
||||
"\"total_admission_cap\":%d,\"near_admission_cap\":%d,"
|
||||
"\"near_reserve\":%d,"
|
||||
"\"coarse_cap\":%d,\"first_coarse_level\":%d,"
|
||||
"\"submission_stop_level\":%d,\"submission_stop_cap\":%d,"
|
||||
"\"pending_entry\":%d,\"pending_after\":%d,\"admission_occupancy\":%d,"
|
||||
@@ -12520,8 +12629,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
bCoreRegionCompilePending ? 1 : 0, IsVoxelForgeCoreReady() ? 1 : 0,
|
||||
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||
GVoxelForgeCoreRegionConcurrentAdmission, MaxTasks,
|
||||
NearTaskReserve, CoarseTaskCap, FirstCoarseLevel,
|
||||
GVoxelForgeCoreRegionConcurrentAdmission, MaxTasks, ReadyQueueDepth,
|
||||
TotalAdmissionCap, NearAdmissionCap, NearTaskReserve, CoarseTaskCap, FirstCoarseLevel,
|
||||
SchedulerOrderStopLevel, SchedulerOrderStopCap,
|
||||
PendingTilesAtUpdateEntry, PendingTiles.Num(), AdmissionOccupancy, ActiveTaskGuard,
|
||||
ActiveGenerationWorkerCount.load(std::memory_order_relaxed), PendingResultsEstimate,
|
||||
@@ -12572,14 +12681,19 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks(); // T2.d — core-clamped
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks(); // Core and worker lane clamped. / Core et voie worker plafonnes.
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : MaxTasks;
|
||||
? CoarseTaskCap
|
||||
: (WorkPriority == EVoxelTileWorkPriority::LOD0 ? NearAdmissionCap : TotalAdmissionCap);
|
||||
if (GetGenerationAdmissionCount() >= AdmissionCap)
|
||||
{
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
@@ -12716,14 +12830,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
ActiveTileTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// BackgroundNormal priority: coarse gen runs on background workers that YIELD to foreground
|
||||
// (game/render-thread) tasks. Without this, raising MaxConcurrentTasks past the spare
|
||||
// core count saturates the scheduler and starves the frame (the "over 12 = lag" symptom).
|
||||
// At background priority the frame keeps its cores; gen just fills in around it.
|
||||
// NEAR RESPONSIVENESS: collision support, edits, and every level-0 tile launch at
|
||||
// BackgroundHigh — still a background worker (yields to the frame, keeps the invariant) but
|
||||
// jumps AHEAD of ordinary pending streaming gen. Admission reserves slots for this class and
|
||||
// the result queues apply it in collision -> edit -> LOD0 order.
|
||||
// BackgroundNormal yields to foreground work; near tasks stay ahead at BackgroundHigh.
|
||||
// BackgroundNormal cede la priorite au premier plan ; les tuiles proches restent prioritaires en BackgroundHigh.
|
||||
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
||||
? UE::Tasks::ETaskPriority::BackgroundHigh
|
||||
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
||||
@@ -12747,6 +12855,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
||||
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
||||
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
||||
const uint32 WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
|
||||
struct FWorkerGuard
|
||||
{
|
||||
std::atomic<int32>& Counter;
|
||||
@@ -12765,6 +12874,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
|
||||
FChunkResult Result;
|
||||
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
||||
Result.WorkerThreadId = WorkerThreadId;
|
||||
Result.RequestOrdinal = RequestOrdinal;
|
||||
Result.bResidentAtRequest = bResidentAtRequest;
|
||||
Result.WorkPriority = WorkPriority;
|
||||
@@ -12786,6 +12896,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||
Canceled.WorkerTaskEndCycles = FPlatformTime::Cycles64();
|
||||
Canceled.WorkerThreadId = Result.WorkerThreadId;
|
||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||
@@ -12887,14 +12998,19 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : MaxTasks;
|
||||
? CoarseTaskCap
|
||||
: (WorkPriority == EVoxelTileWorkPriority::LOD0 ? NearAdmissionCap : TotalAdmissionCap);
|
||||
if (GetGenerationAdmissionCount() >= AdmissionCap) return;
|
||||
|
||||
PendingTiles.Add(Tile);
|
||||
@@ -12947,6 +13063,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
||||
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
||||
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
||||
const uint32 WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
|
||||
struct FWorkerGuard
|
||||
{
|
||||
std::atomic<int32>& Counter;
|
||||
@@ -12978,6 +13095,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
|
||||
FChunkResult Result;
|
||||
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
||||
Result.WorkerThreadId = WorkerThreadId;
|
||||
Result.Tile = Tile;
|
||||
Result.Epoch = TaskEpoch;
|
||||
Result.DesiredEpoch = TaskDesiredEpoch;
|
||||
@@ -13000,6 +13118,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
Canceled.RequestOrdinal = RequestOrdinal;
|
||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||
Canceled.WorkerThreadId = Result.WorkerThreadId;
|
||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||
|
||||
@@ -17,6 +17,10 @@ namespace VoxelForgeStartupTrace
|
||||
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (early path)
|
||||
uint64 RequestOrdinal = 0;
|
||||
uint64 GenerationOrdinal = 0;
|
||||
uint64 RequestStartCycles = 0;
|
||||
uint64 WorkerTaskStartCycles = 0;
|
||||
uint64 WorkerTaskEndCycles = 0;
|
||||
uint32 WorkerThreadId = 0;
|
||||
bool bResidentAtRequest = false;
|
||||
bool bSealedSolidProof = false;
|
||||
bool bEmpty = true;
|
||||
|
||||
@@ -137,6 +137,9 @@ struct FChunkResult
|
||||
// Les bornes de tâche incluent la conversion et la préparation du résultat autour du générateur.
|
||||
uint64 WorkerTaskStartCycles = 0;
|
||||
uint64 WorkerTaskEndCycles = 0;
|
||||
// Operating-system thread id identifies the physical worker slot used by this tile.
|
||||
// L'identifiant du thread systeme repere le slot worker utilise par cette tuile.
|
||||
uint32 WorkerThreadId = 0;
|
||||
// Worker timing boundaries for separating scheduler wait from generation and result/apply wait.
|
||||
// These are captured with low overhead for the clean streaming-latency summary; expensive
|
||||
// per-tile profiling remains opt-in.
|
||||
@@ -443,6 +446,7 @@ public:
|
||||
* capped to (logical cores − 2) so small CPUs don't thrash on a flat 16 (background
|
||||
* priority stops frame starvation, not the context-switch overhead). */
|
||||
int32 GetMaxConcurrentTasks() const;
|
||||
int32 GetMaxConcurrentStreamingTasks() const;
|
||||
/** Submitted generation tasks still queued or running; completed MPSC results do not occupy admission.
|
||||
* Taches de generation soumises, en file ou en cours ; les resultats MPSC termines ne bloquent pas l'admission. */
|
||||
int32 GetActiveGenerationTaskCount() const;
|
||||
@@ -1239,6 +1243,7 @@ public:
|
||||
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
|
||||
float PeakObservedPawnSpeedCmPerSecond = 0.0f;
|
||||
int32 EffectiveNearTaskReserve = -1;
|
||||
bool bStreamingPlayerMoving = false; // GT snapshot consumed by admission helpers / instantane GT pour les admissions
|
||||
uint64 NextStreamingRequestOrdinal = 0;
|
||||
std::atomic<uint64> NextStreamingGenerationOrdinal{0};
|
||||
// Cumulative worker-task wall cycles avoid aliasing the instantaneous per-frame counter.
|
||||
|
||||
Reference in new issue
Block a user