wip(step1): collision/upload clocks behind default-off switches -- partial, stopped on low memory
Compiled and run (cap 14 / 12 / 8), then stopped: another app held 12.5 GB, free memory fell under the 6 GB launch floor and processes failed to start (STATUS_DLL_INIT_FAILED). Measured so far (owner world, 120 s windows): - 343 L0 tiles, request->collision-ready p50/p95: cap 14 = 262/440 ms, cap 8 = 196/238 ms; scheduler queue wait p95 141 ms -> 0.9 ms. Initial support tile at cap 14: RMC submit->body 7.47 s, first check->ready 9.73 s; at cap 12: 0.33 s / 2.54 s; at cap 8 a 3,949-tri tile reached body update 2.8 ms after RMC submission. Waiting, not cooking, dominates. - NOT confirmed: a capsule sweep hitting the exact target body (the 0.75 ms hit was another component); the 10 ms core-cook and 150 ms/27-tile thresholds are unscored. - CMC cannot take a same-frame density floor: FindFloor/ComputeFloorDist trace the collision scene. Underfoot digs (100-dig test), upload throughput (render-enabled caps 4/8/16) and the 8x per-sample question are not run. Per-sample profiling was fully disabled in these runs, so it does not explain the 8x. Switches (all default off): voxel.Step1GenerationCap, Step1PauseGenerationAfterSeconds, Step1MaxMeshApplies, Step1PrequeueVisibleLOD0, Step1UploadDisableCollision, Step1CollisionProbe, Step1CoreCookBenchmark; harness -EnableRender. Also: plan step 3b, AVX2 need not match scalar. 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
6d2a068308
commit
97a1874498
5 files changed
+613
-19
No files matched your search
@@ -36,6 +36,11 @@
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Misc/IQueuedWork.h"
|
||||
#include "Misc/QueuedThreadPool.h"
|
||||
#include "RealtimeMeshCollisionLibrary.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "Async/Async.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
@@ -90,6 +95,140 @@ namespace
|
||||
GVoxelForgeProfileCollisionCookStages,
|
||||
TEXT("Log per-tile collision queue/apply/RMC/future timing and cooked geometry counts."));
|
||||
|
||||
int32 GVoxelForgeStep1GenerationCap = -1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1GenerationCap(
|
||||
TEXT("voxel.Step1GenerationCap"),
|
||||
GVoxelForgeStep1GenerationCap,
|
||||
TEXT("Step 1 measurement override: -1 uses the normal cap, 0 pauses generation, positive values cap worker tasks."));
|
||||
|
||||
float GVoxelForgeStep1PauseGenerationAfterSeconds = 0.0f;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1PauseGenerationAfterSeconds(
|
||||
TEXT("voxel.Step1PauseGenerationAfterSeconds"),
|
||||
GVoxelForgeStep1PauseGenerationAfterSeconds,
|
||||
TEXT("Step 1 measurement: switch the generation cap to paused after this many run seconds; 0 disables the timed pause."));
|
||||
double GVoxelForgeStep1StartSeconds = 0.0;
|
||||
bool bVoxelForgeStep1GenerationPauseApplied = false;
|
||||
|
||||
int32 GVoxelForgeStep1MaxMeshApplies = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1MaxMeshApplies(
|
||||
TEXT("voxel.Step1MaxMeshApplies"),
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
TEXT("Step 1 measurement override: 0 uses the asset MaxMeshAppliesPerFrame, otherwise caps visible applies per frame."));
|
||||
|
||||
int32 GVoxelForgeStep1PrequeueVisibleLOD0 = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1PrequeueVisibleLOD0(
|
||||
TEXT("voxel.Step1PrequeueVisibleLOD0"),
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0,
|
||||
TEXT("Step 1 measurement: hold generated results until this many non-empty level-0 meshes are ready, then release them for upload."));
|
||||
|
||||
int32 GVoxelForgeStep1UploadDisableCollision = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1UploadDisableCollision(
|
||||
TEXT("voxel.Step1UploadDisableCollision"),
|
||||
GVoxelForgeStep1UploadDisableCollision,
|
||||
TEXT("Step 1 upload isolation: suppress terrain collision requests while measuring prequeued render uploads."));
|
||||
|
||||
int32 GVoxelForgeStep1CollisionProbe = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1CollisionProbe(
|
||||
TEXT("voxel.Step1CollisionProbe"),
|
||||
GVoxelForgeStep1CollisionProbe,
|
||||
TEXT("Step 1 measurement: observe physics-state readiness and the first support capsule sweep after RMC body install."));
|
||||
|
||||
int32 GVoxelForgeStep1CoreCookBenchmark = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1CoreCookBenchmark(
|
||||
TEXT("voxel.Step1CoreCookBenchmark"),
|
||||
GVoxelForgeStep1CoreCookBenchmark,
|
||||
TEXT("Step 1 measurement: directly extract and CookComplexMesh for critical L0 support tiles on a dedicated one-thread pool; does not install this sidecar body."));
|
||||
|
||||
struct FStep1CoreCookPoolOwner
|
||||
{
|
||||
FCriticalSection Guard;
|
||||
FQueuedThreadPool* Pool = nullptr;
|
||||
|
||||
FQueuedThreadPool* GetOrCreate()
|
||||
{
|
||||
FScopeLock Lock(&Guard);
|
||||
if (Pool == nullptr)
|
||||
{
|
||||
Pool = FQueuedThreadPool::Allocate();
|
||||
if (Pool == nullptr
|
||||
|| !Pool->Create(1, 32 * 1024, TPri_AboveNormal,
|
||||
TEXT("VoxelForgeStep1Cook")))
|
||||
{
|
||||
if (Pool != nullptr)
|
||||
{
|
||||
delete Pool;
|
||||
Pool = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pool;
|
||||
}
|
||||
|
||||
~FStep1CoreCookPoolOwner()
|
||||
{
|
||||
if (Pool != nullptr)
|
||||
{
|
||||
Pool->Destroy();
|
||||
delete Pool;
|
||||
Pool = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FStep1CoreCookPoolOwner GVoxelForgeStep1CoreCookPool;
|
||||
|
||||
class FVoxelStep1CoreCookWork final : public IQueuedWork
|
||||
{
|
||||
public:
|
||||
FVoxelStep1CoreCookWork(
|
||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> InStreams,
|
||||
FVoxelTileKey InTile, int32 InTriangleCount, uint64 InQueueCycles)
|
||||
: Streams(MoveTemp(InStreams)), Tile(InTile), TriangleCount(InTriangleCount),
|
||||
QueueCycles(InQueueCycles)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void DoThreadedWork() override
|
||||
{
|
||||
const uint64 WorkerStartCycles = FPlatformTime::Cycles64();
|
||||
FRealtimeMeshCollisionMesh CollisionMesh;
|
||||
const uint64 ExtractionStartCycles = FPlatformTime::Cycles64();
|
||||
const bool bExtracted = Streams.IsValid()
|
||||
&& URealtimeMeshCollisionTools::AppendStreamsToCollisionMesh(
|
||||
CollisionMesh, *Streams, 0);
|
||||
const uint64 ExtractionEndCycles = FPlatformTime::Cycles64();
|
||||
const uint64 CookStartCycles = FPlatformTime::Cycles64();
|
||||
if (bExtracted)
|
||||
{
|
||||
URealtimeMeshCollisionTools::CookComplexMesh(CollisionMesh);
|
||||
}
|
||||
const uint64 CookEndCycles = FPlatformTime::Cycles64();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1CoreCook] tile=(%d,%d,%d) level=%d extracted=%d "
|
||||
"triangles=%d collision_vertices=%d queue_to_worker_ms=%.3f "
|
||||
"stream_extract_ms=%.3f cook_ms=%.3f total_ms=%.3f thread_id=%u"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
bExtracted ? 1 : 0, TriangleCount, CollisionMesh.GetVertices().Num(),
|
||||
FPlatformTime::ToSeconds64(WorkerStartCycles - QueueCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(ExtractionEndCycles - ExtractionStartCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(CookEndCycles - CookStartCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(CookEndCycles - WorkerStartCycles) * 1000.0,
|
||||
FPlatformTLS::GetCurrentThreadId());
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual void Abandon() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> Streams;
|
||||
FVoxelTileKey Tile;
|
||||
int32 TriangleCount = 0;
|
||||
uint64 QueueCycles = 0;
|
||||
};
|
||||
|
||||
int32 GVoxelForgeTestFullResClipLevels = -1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeTestFullResClipLevels(
|
||||
TEXT("voxel.TestFullResClipLevels"),
|
||||
@@ -1638,6 +1777,14 @@ void AVoxelWorld::BeginPlay()
|
||||
int32 CommandLineSealedSolidProof = GVoxelForgeSealedSolidProof;
|
||||
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
|
||||
int32 CommandLineCollisionGate = GVoxelForgeCollisionGate;
|
||||
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
||||
float CommandLineStep1PauseGenerationAfterSeconds =
|
||||
GVoxelForgeStep1PauseGenerationAfterSeconds;
|
||||
int32 CommandLineStep1MaxMeshApplies = GVoxelForgeStep1MaxMeshApplies;
|
||||
int32 CommandLineStep1PrequeueVisibleLOD0 = GVoxelForgeStep1PrequeueVisibleLOD0;
|
||||
int32 CommandLineStep1UploadDisableCollision = GVoxelForgeStep1UploadDisableCollision;
|
||||
int32 CommandLineStep1CollisionProbe = GVoxelForgeStep1CollisionProbe;
|
||||
int32 CommandLineStep1CoreCookBenchmark = GVoxelForgeStep1CoreCookBenchmark;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ProfileDensity="), CommandLineProfile))
|
||||
{
|
||||
GVoxelForgeProfileDensity = CommandLineProfile;
|
||||
@@ -1664,6 +1811,49 @@ void AVoxelWorld::BeginPlay()
|
||||
{
|
||||
GVoxelForgeProfileCollisionCookStages = CommandLineProfileCollisionCookStages;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1GenerationCap="),
|
||||
CommandLineStep1GenerationCap))
|
||||
{
|
||||
GVoxelForgeStep1GenerationCap = CommandLineStep1GenerationCap;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PauseGenerationAfterSeconds="),
|
||||
CommandLineStep1PauseGenerationAfterSeconds))
|
||||
{
|
||||
GVoxelForgeStep1PauseGenerationAfterSeconds = FMath::Max(
|
||||
0.0f, CommandLineStep1PauseGenerationAfterSeconds);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1MaxMeshApplies="),
|
||||
CommandLineStep1MaxMeshApplies))
|
||||
{
|
||||
GVoxelForgeStep1MaxMeshApplies = CommandLineStep1MaxMeshApplies;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
||||
CommandLineStep1PrequeueVisibleLOD0))
|
||||
{
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0 = FMath::Max(0, CommandLineStep1PrequeueVisibleLOD0);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1UploadDisableCollision="),
|
||||
CommandLineStep1UploadDisableCollision))
|
||||
{
|
||||
GVoxelForgeStep1UploadDisableCollision = CommandLineStep1UploadDisableCollision;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1CollisionProbe="),
|
||||
CommandLineStep1CollisionProbe))
|
||||
{
|
||||
GVoxelForgeStep1CollisionProbe = CommandLineStep1CollisionProbe;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1CoreCookBenchmark="),
|
||||
CommandLineStep1CoreCookBenchmark))
|
||||
{
|
||||
GVoxelForgeStep1CoreCookBenchmark = CommandLineStep1CoreCookBenchmark;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1] gen_cap=%d pause_after_s=%.3f max_applies=%d prequeue_l0=%d "
|
||||
"disable_collision=%d collision_probe=%d core_cook_benchmark=%d"),
|
||||
GVoxelForgeStep1GenerationCap, GVoxelForgeStep1PauseGenerationAfterSeconds,
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0, GVoxelForgeStep1UploadDisableCollision,
|
||||
GVoxelForgeStep1CollisionProbe, GVoxelForgeStep1CoreCookBenchmark);
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(),
|
||||
TEXT("voxel.TestFullResClipLevels="),
|
||||
@@ -5726,6 +5916,26 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
APawn* PlayerPawn = nullptr;
|
||||
const bool bHasPlayer = TryGetPlayerPosition(PlayerLastPos, &PlayerPawn);
|
||||
|
||||
if (GVoxelForgeStep1PauseGenerationAfterSeconds > 0.0f
|
||||
&& !bVoxelForgeStep1GenerationPauseApplied)
|
||||
{
|
||||
const double NowSeconds = FPlatformTime::Seconds();
|
||||
if (GVoxelForgeStep1StartSeconds <= 0.0)
|
||||
{
|
||||
GVoxelForgeStep1StartSeconds = NowSeconds;
|
||||
}
|
||||
else if (NowSeconds - GVoxelForgeStep1StartSeconds
|
||||
>= static_cast<double>(GVoxelForgeStep1PauseGenerationAfterSeconds))
|
||||
{
|
||||
GVoxelForgeStep1GenerationCap = 0;
|
||||
bVoxelForgeStep1GenerationPauseApplied = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1] generation_paused=1 elapsed_s=%.3f pending=%d held_results=%d"),
|
||||
NowSeconds - GVoxelForgeStep1StartSeconds, PendingTiles.Num(),
|
||||
Step1HeldApplyResults.Num());
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasPlayer) {
|
||||
// Capture velocity before the collision gate can deliberately zero it while the support
|
||||
// tile is cooking. The scheduler uses this as the movement heading for its next-tile
|
||||
@@ -5823,7 +6033,9 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
}
|
||||
}
|
||||
ProcessPendingChunks();
|
||||
PollStep1SupportSweepProbes();
|
||||
ReconcileReadyTransitionVisibility();
|
||||
PollStep1VisibleApplyProbes();
|
||||
// Near-field evidence is independent of the full horizon settle point. Record one completion
|
||||
// for each desired-set epoch after this frame's result drain, including moving crossings.
|
||||
RecordLOD0RingReadyIfComplete();
|
||||
@@ -6978,11 +7190,215 @@ void AVoxelWorld::HandleRealtimeMeshCollisionBodyUpdated(URealtimeMesh* Mesh, UB
|
||||
Pending.bBodyUpdatedEventSeen = true;
|
||||
Pending.BodyUpdatedEventCycles = FPlatformTime::Cycles64();
|
||||
Pending.BodyUpdatedEventFrame = GFrameCounter;
|
||||
if (GVoxelForgeStep1CollisionProbe != 0)
|
||||
{
|
||||
APawn* Pawn = nullptr;
|
||||
FVector PlayerPosition = FVector::ZeroVector;
|
||||
FVoxelTileKey CurrentSupportTile;
|
||||
if (TryGetPlayerPosition(PlayerPosition, &Pawn)
|
||||
&& GetPlayerSupportTile(Pawn, PlayerPosition, CurrentSupportTile)
|
||||
&& CurrentSupportTile == Pair.Key)
|
||||
{
|
||||
const URealtimeMeshComponent* Component = TileComponents.FindRef(Pair.Key);
|
||||
Pending.bPhysicsStateReadyAtBodyUpdate = IsValid(Component)
|
||||
&& Component->IsPhysicsStateCreated();
|
||||
if (Pending.bPhysicsStateReadyAtBodyUpdate)
|
||||
{
|
||||
Pending.PhysicsStateReadyCycles = FPlatformTime::Cycles64();
|
||||
}
|
||||
FStep1SupportSweepProbe& Probe = Step1SupportSweepProbes.Add(Pair.Key);
|
||||
Probe.CollisionRequestCycles = Pending.CollisionSubmittedCycles;
|
||||
Probe.BodyUpdatedCycles = Pending.BodyUpdatedEventCycles;
|
||||
Probe.PhysicsStateReadyCycles = Pending.PhysicsStateReadyCycles;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=body_updated tile=(%d,%d,%d) "
|
||||
"request_to_body_ms=%.3f physics_ready=%d body_to_physics_ms=%.3f"),
|
||||
Pair.Key.Coord.X, Pair.Key.Coord.Y, Pair.Key.Coord.Z,
|
||||
FPlatformTime::ToSeconds64(
|
||||
Probe.BodyUpdatedCycles - Probe.CollisionRequestCycles) * 1000.0,
|
||||
Pending.bPhysicsStateReadyAtBodyUpdate ? 1 : 0,
|
||||
Pending.bPhysicsStateReadyAtBodyUpdate
|
||||
? FPlatformTime::ToSeconds64(
|
||||
Probe.PhysicsStateReadyCycles - Probe.BodyUpdatedCycles) * 1000.0
|
||||
: -1.0);
|
||||
PollStep1SupportSweepProbes();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelWorld::PollStep1SupportSweepProbes()
|
||||
{
|
||||
if (GVoxelForgeStep1CollisionProbe == 0 || Step1SupportSweepProbes.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
APawn* Pawn = nullptr;
|
||||
FVector PlayerPosition = FVector::ZeroVector;
|
||||
FVoxelTileKey CurrentSupportTile;
|
||||
if (!TryGetPlayerPosition(PlayerPosition, &Pawn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const bool bHaveCurrentSupportTile = GetPlayerSupportTile(
|
||||
Pawn, PlayerPosition, CurrentSupportTile);
|
||||
|
||||
const uint64 NowCycles = FPlatformTime::Cycles64();
|
||||
for (auto It = Step1SupportSweepProbes.CreateIterator(); It; ++It)
|
||||
{
|
||||
FStep1SupportSweepProbe& Probe = It.Value();
|
||||
URealtimeMeshComponent* Component = TileComponents.FindRef(It.Key());
|
||||
if (!IsValid(Component))
|
||||
{
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Component->IsPhysicsStateCreated() && Probe.PhysicsStateReadyCycles == 0)
|
||||
{
|
||||
Probe.PhysicsStateReadyCycles = NowCycles;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=physics_ready tile=(%d,%d,%d) "
|
||||
"request_to_physics_ms=%.3f body_to_physics_ms=%.3f"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
FPlatformTime::ToSeconds64(
|
||||
Probe.PhysicsStateReadyCycles - Probe.CollisionRequestCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(
|
||||
Probe.PhysicsStateReadyCycles - Probe.BodyUpdatedCycles) * 1000.0);
|
||||
}
|
||||
|
||||
ACharacter* Character = Cast<ACharacter>(Pawn);
|
||||
UCapsuleComponent* Capsule = Character ? Character->GetCapsuleComponent() : nullptr;
|
||||
UWorld* World = GetWorld();
|
||||
if (Probe.PhysicsStateReadyCycles != 0 && IsValid(Capsule) && World != nullptr)
|
||||
{
|
||||
const float Radius = Capsule->GetScaledCapsuleRadius();
|
||||
const float HalfHeight = Capsule->GetScaledCapsuleHalfHeight();
|
||||
const FVector Start = Pawn->GetActorLocation() + FVector(0.0, 0.0, 5.0);
|
||||
const FVector End = Start - FVector(0.0, 0.0, 200.0);
|
||||
const FCollisionShape CapsuleShape = FCollisionShape::MakeCapsule(
|
||||
Radius, FMath::Max(Radius, HalfHeight - 2.0f));
|
||||
FCollisionQueryParams QueryParams(
|
||||
SCENE_QUERY_STAT(VoxelForgeStep1SupportSweep), false, Pawn);
|
||||
FHitResult Hit;
|
||||
const bool bHit = World->SweepSingleByChannel(
|
||||
Hit, Start, End, FQuat::Identity, ECC_Pawn, CapsuleShape, QueryParams);
|
||||
FHitResult ObjectHit;
|
||||
FCollisionObjectQueryParams ObjectQueryParams;
|
||||
ObjectQueryParams.AddObjectTypesToQuery(Component->GetCollisionObjectType());
|
||||
const bool bObjectHit = World->SweepSingleByObjectType(
|
||||
ObjectHit, Start, End, FQuat::Identity, ObjectQueryParams,
|
||||
CapsuleShape, QueryParams);
|
||||
if (!Probe.bFirstSweepAttemptLogged)
|
||||
{
|
||||
const UPrimitiveComponent* HitComponent = Hit.GetComponent();
|
||||
const UPrimitiveComponent* ObjectHitComponent = ObjectHit.GetComponent();
|
||||
const FBoxSphereBounds ComponentBounds = Component->Bounds;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=first_sweep_attempt tile=(%d,%d,%d) "
|
||||
"pawn_channel_hit=%d pawn_channel_target=%d pawn_hit_component=%s "
|
||||
"object_query_hit=%d object_query_target=%d object_hit_component=%s "
|
||||
"collision_enabled=%d pawn_response=%d object_type=%d physics_state=%d "
|
||||
"pawn=(%.1f,%.1f,%.1f) sweep_start_z=%.1f sweep_end_z=%.1f "
|
||||
"component_bounds=(%.1f,%.1f,%.1f;%.1f,%.1f,%.1f) "
|
||||
"current_support_match=%d"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
bHit ? 1 : 0, (bHit && HitComponent == Component) ? 1 : 0,
|
||||
HitComponent ? *HitComponent->GetName() : TEXT("none"),
|
||||
bObjectHit ? 1 : 0,
|
||||
(bObjectHit && ObjectHitComponent == Component) ? 1 : 0,
|
||||
ObjectHitComponent ? *ObjectHitComponent->GetName() : TEXT("none"),
|
||||
static_cast<int32>(Component->GetCollisionEnabled()),
|
||||
static_cast<int32>(Component->GetCollisionResponseToChannel(ECC_Pawn)),
|
||||
static_cast<int32>(Component->GetCollisionObjectType()),
|
||||
Component->IsPhysicsStateCreated() ? 1 : 0,
|
||||
PlayerPosition.X, PlayerPosition.Y, PlayerPosition.Z, Start.Z, End.Z,
|
||||
ComponentBounds.Origin.X, ComponentBounds.Origin.Y, ComponentBounds.Origin.Z,
|
||||
ComponentBounds.BoxExtent.X, ComponentBounds.BoxExtent.Y,
|
||||
ComponentBounds.BoxExtent.Z,
|
||||
bHaveCurrentSupportTile && CurrentSupportTile == It.Key() ? 1 : 0);
|
||||
Probe.bFirstSweepAttemptLogged = true;
|
||||
}
|
||||
if (bHit && Hit.GetComponent() == Component)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=first_support_capsule_sweep tile=(%d,%d,%d) "
|
||||
"request_to_sweep_ms=%.3f body_to_sweep_ms=%.3f hit_z=%.3f"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
FPlatformTime::ToSeconds64(NowCycles - Probe.CollisionRequestCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) * 1000.0,
|
||||
Hit.ImpactPoint.Z);
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Probe.BodyUpdatedCycles != 0 && NowCycles >= Probe.BodyUpdatedCycles
|
||||
&& FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) > 10.0)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=sweep_timeout tile=(%d,%d,%d) "
|
||||
"physics_ready=%d support_match=%d body_to_timeout_ms=%.3f"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
Component->IsPhysicsStateCreated() ? 1 : 0,
|
||||
bHaveCurrentSupportTile && CurrentSupportTile == It.Key() ? 1 : 0,
|
||||
FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) * 1000.0);
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelWorld::PollStep1VisibleApplyProbes()
|
||||
{
|
||||
if (GVoxelForgeStep1PrequeueVisibleLOD0 <= 0 || Step1PendingVisibleApplyProbes.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64 NowCycles = FPlatformTime::Cycles64();
|
||||
for (auto It = Step1PendingVisibleApplyProbes.CreateIterator(); It; ++It)
|
||||
{
|
||||
URealtimeMeshComponent* Component = TileComponents.FindRef(It.Key());
|
||||
if (!IsValid(Component))
|
||||
{
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
|
||||
const float CurrentLastRenderTime = Component->GetLastRenderTime();
|
||||
if (Component->IsVisible()
|
||||
&& CurrentLastRenderTime > It.Value().LastRenderTimeBeforeApply + SMALL_NUMBER)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeFirstVisible] tile=(%d,%d,%d) level=0 "
|
||||
"applied_frame=%llu visible_frame=%llu frame_delta=%llu apply_to_visible_ms=%.3f"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
static_cast<unsigned long long>(It.Value().AppliedFrame),
|
||||
static_cast<unsigned long long>(GFrameCounter),
|
||||
static_cast<unsigned long long>(GFrameCounter - It.Value().AppliedFrame),
|
||||
FPlatformTime::ToSeconds64(NowCycles - It.Value().AppliedCycles) * 1000.0);
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NowCycles >= It.Value().AppliedCycles
|
||||
&& FPlatformTime::ToSeconds64(NowCycles - It.Value().AppliedCycles) > 10.0)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("[VoxelForgeFirstVisible] tile=(%d,%d,%d) level=0 state=not_seen "
|
||||
"applied_frame=%llu waited_ms=%.3f visible=%d"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
static_cast<unsigned long long>(It.Value().AppliedFrame),
|
||||
FPlatformTime::ToSeconds64(NowCycles - It.Value().AppliedCycles) * 1000.0,
|
||||
Component->IsVisible() ? 1 : 0);
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelWorld::RecordLOD0ReadySample(
|
||||
uint64 RequestStartCycles, uint64 GenerationStartCycles, uint64 GenerationEndCycles,
|
||||
uint64 ApplyStartCycles, uint64 ReadyCycles, uint64 CollisionSubmittedCycles)
|
||||
@@ -7232,6 +7648,8 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
const bool bBodyUpdatedEventSeen = Pending->bBodyUpdatedEventSeen;
|
||||
const uint64 BodyUpdatedEventCycles = Pending->BodyUpdatedEventCycles;
|
||||
const uint64 BodyUpdatedEventFrame = Pending->BodyUpdatedEventFrame;
|
||||
const bool bPhysicsStateReadyAtBodyUpdate = Pending->bPhysicsStateReadyAtBodyUpdate;
|
||||
const uint64 PhysicsStateReadyCycles = Pending->PhysicsStateReadyCycles;
|
||||
const uint64 MeasuredCollisionSubmittedFrame = Pending->CollisionSubmittedFrame;
|
||||
const uint64 MeasuredRequestStartCycles = Pending->RequestStartCycles;
|
||||
const uint64 MeasuredGenerationStartCycles = Pending->GenerationStartCycles;
|
||||
@@ -7325,6 +7743,9 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
const double BodyEventToFutureSeconds = bBodyUpdatedEventSeen
|
||||
&& ReadyCycles >= BodyUpdatedEventCycles
|
||||
? FPlatformTime::ToSeconds64(ReadyCycles - BodyUpdatedEventCycles) : -1.0;
|
||||
const double BodyEventToPhysicsReadySeconds = bPhysicsStateReadyAtBodyUpdate
|
||||
&& PhysicsStateReadyCycles >= BodyUpdatedEventCycles
|
||||
? FPlatformTime::ToSeconds64(PhysicsStateReadyCycles - BodyUpdatedEventCycles) : -1.0;
|
||||
const auto SecondsBetween = [](uint64 EndCycles, uint64 StartCycles) -> double
|
||||
{
|
||||
return StartCycles != 0 && EndCycles >= StartCycles
|
||||
@@ -7371,6 +7792,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
"merge=%d request_to_worker_s=%.6f worker_gen_s=%.6f worker_to_apply_s=%.6f "
|
||||
"apply_to_mesh_submit_s=%.6f mesh_to_collision_submit_s=%.6f "
|
||||
"rmc_submit_to_body_update_s=%.6f submit_to_body_frames=%lld "
|
||||
"physics_ready_at_body=%d body_update_to_physics_ready_s=%.6f "
|
||||
"body_update_to_future_callback_s=%.6f future_callback_to_gt_s=%.6f "
|
||||
"suppressed_to_ready_s=%.6f mesh_submit_to_ready=%.6f "
|
||||
"collision_request_to_ready=%.6f body_event_to_future=%.6f"),
|
||||
@@ -7386,6 +7808,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
RequestToWorkerStartSeconds, WorkerGenerationSeconds, WorkerResultToApplySeconds,
|
||||
ApplyToMeshSubmitSeconds, MeshSubmitToCollisionSubmitSeconds,
|
||||
RMCSubmitToBodyUpdateSeconds, FramesToBodyUpdate,
|
||||
bPhysicsStateReadyAtBodyUpdate ? 1 : 0, BodyEventToPhysicsReadySeconds,
|
||||
BodyUpdateToFutureCallbackSeconds, FutureCallbackToGameThreadSeconds,
|
||||
SuppressedToReadySeconds, MeshToCookSeconds,
|
||||
CollisionRequestToCookSeconds, BodyEventToFutureSeconds);
|
||||
@@ -7459,9 +7882,11 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
// Budget limits how many VISIBLE mesh applies we do per frame (GPU upload cost).
|
||||
// Empty meshes and stale results are free to drain — don't count them.
|
||||
const bool bProfileApply = GVoxelForgeProfileTileGeneration != 0;
|
||||
const uint64 DrainStartCycles = FPlatformTime::Cycles64();
|
||||
const int32 MaxApplies = FMath::Max(
|
||||
uint64 DrainStartCycles = FPlatformTime::Cycles64();
|
||||
const int32 ConfiguredMaxApplies = FMath::Max(
|
||||
Settings ? Settings->MaxMeshAppliesPerFrame : 4, 1);
|
||||
const int32 MaxApplies = GVoxelForgeStep1MaxMeshApplies > 0
|
||||
? GVoxelForgeStep1MaxMeshApplies : ConfiguredMaxApplies;
|
||||
const float ApplyBudgetMilliseconds = Settings
|
||||
? FMath::Max(Settings->MaxMeshApplyMilliseconds, 0.0f)
|
||||
: 2.0f;
|
||||
@@ -7473,6 +7898,18 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
FChunkResult DequeuedChunk;
|
||||
const auto DequeueNextResult = [&]() -> bool
|
||||
{
|
||||
if (bStep1UploadCorpusReleased
|
||||
&& Step1HeldApplyCursor < Step1HeldApplyResults.Num())
|
||||
{
|
||||
DequeuedChunk = MoveTemp(Step1HeldApplyResults[Step1HeldApplyCursor++]);
|
||||
Step1HeldApplyTiles.Remove(DequeuedChunk.Tile);
|
||||
return true;
|
||||
}
|
||||
if (bStep1UploadCorpusReleased && Step1HeldApplyResults.Num() > 0)
|
||||
{
|
||||
Step1HeldApplyResults.Empty();
|
||||
Step1HeldApplyCursor = 0;
|
||||
}
|
||||
// Mirror the admission order. A horizon result that completed earlier must not consume
|
||||
// the game-thread apply budget ahead of collision support, an edit, or ordinary LOD0.
|
||||
return CriticalProcessQueue.Dequeue(DequeuedChunk)
|
||||
@@ -7480,6 +7917,71 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
|| NearProcessQueue.Dequeue(DequeuedChunk)
|
||||
|| ProcessQueue.Dequeue(DequeuedChunk);
|
||||
};
|
||||
|
||||
if (GVoxelForgeStep1PrequeueVisibleLOD0 > 0 && !bStep1UploadCorpusReleased)
|
||||
{
|
||||
while (DequeueNextResult())
|
||||
{
|
||||
const TSharedPtr<FVoxelTileCancellationState, ESPMode::ThreadSafe> Cancellation =
|
||||
PendingTileCancellation.FindRef(DequeuedChunk.Tile);
|
||||
const bool bRequestObsolete = DequeuedChunk.bObsolete
|
||||
|| (Cancellation.IsValid()
|
||||
&& Cancellation->bObsolete.load(std::memory_order_acquire));
|
||||
if (DequeuedChunk.bAborted || bRequestObsolete
|
||||
|| DequeuedChunk.Epoch != GenerationEpoch || !IsDesired(DequeuedChunk.Tile))
|
||||
{
|
||||
PendingTileCancellation.Remove(DequeuedChunk.Tile);
|
||||
PendingTiles.Remove(DequeuedChunk.Tile);
|
||||
if (bRequestObsolete)
|
||||
{
|
||||
++ObsoleteTileAbortCount;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Step1HeldApplyTiles.Contains(DequeuedChunk.Tile))
|
||||
{
|
||||
Step1HeldApplyTiles.Add(DequeuedChunk.Tile);
|
||||
if (DequeuedChunk.Tile.Level == 0 && !DequeuedChunk.bEmpty
|
||||
&& DequeuedChunk.Streams.IsValid() && DequeuedChunk.NumTriangles > 0)
|
||||
{
|
||||
++Step1HeldVisibleLOD0Count;
|
||||
}
|
||||
Step1HeldApplyResults.Add(MoveTemp(DequeuedChunk));
|
||||
if (Step1HeldVisibleLOD0Count >= GVoxelForgeStep1PrequeueVisibleLOD0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Step1HeldVisibleLOD0Count >= GVoxelForgeStep1PrequeueVisibleLOD0)
|
||||
{
|
||||
bStep1UploadCorpusReleased = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeUploadPrequeue] state=released target_visible_l0=%d "
|
||||
"held_visible_l0=%d held_results=%d active_generation=%d pending=%d frame=%llu"),
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0, Step1HeldVisibleLOD0Count,
|
||||
Step1HeldApplyResults.Num(), GetActiveGenerationTaskCount(), PendingTiles.Num(),
|
||||
static_cast<unsigned long long>(GFrameCounter));
|
||||
DrainStartCycles = FPlatformTime::Cycles64();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Step1LastLoggedPrequeueCount < 0
|
||||
|| Step1HeldVisibleLOD0Count >= Step1LastLoggedPrequeueCount + 50)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeUploadPrequeue] state=collecting target_visible_l0=%d "
|
||||
"held_visible_l0=%d held_results=%d active_generation=%d pending=%d"),
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0, Step1HeldVisibleLOD0Count,
|
||||
Step1HeldApplyResults.Num(), GetActiveGenerationTaskCount(), PendingTiles.Num());
|
||||
Step1LastLoggedPrequeueCount = Step1HeldVisibleLOD0Count;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (DequeueNextResult())
|
||||
{
|
||||
++ResultsDrained;
|
||||
@@ -7769,6 +8271,23 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
LogTileApplied();
|
||||
return false;
|
||||
}
|
||||
if (GVoxelForgeStep1PrequeueVisibleLOD0 > 0 && Result.Tile.Level == 0)
|
||||
{
|
||||
if (URealtimeMeshComponent* Component = TileComponents.FindRef(Result.Tile))
|
||||
{
|
||||
FStep1VisibleApplyProbe& Probe = Step1PendingVisibleApplyProbes.Add(Result.Tile);
|
||||
Probe.AppliedCycles = FPlatformTime::Cycles64();
|
||||
Probe.AppliedFrame = GFrameCounter;
|
||||
Probe.LastRenderTimeBeforeApply = Component->GetLastRenderTime();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeUploadApplied] tile=(%d,%d,%d) triangles=%d frame=%llu "
|
||||
"apply_begin_to_submit_ms=%.3f"),
|
||||
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
|
||||
Result.NumTriangles,
|
||||
static_cast<unsigned long long>(Probe.AppliedFrame),
|
||||
FPlatformTime::ToSeconds64(Probe.AppliedCycles - ApplyStartCycles) * 1000.0);
|
||||
}
|
||||
}
|
||||
LoadedTiles.Add(Result.Tile);
|
||||
AppliedModificationEpoch.Add(Result.Tile, Result.ModificationEpoch);
|
||||
LoadedTileGeometry.Add(Result.Tile, FLoadedTileGeometryStats{
|
||||
@@ -8589,7 +9108,21 @@ int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
||||
const int32 CoreCap = AuthoredCoreCap > 0
|
||||
? FMath::Clamp(AuthoredCoreCap, 1, LogicalCores)
|
||||
: AutomaticCap;
|
||||
return FMath::Clamp(Asset, 1, CoreCap);
|
||||
const int32 NormalCap = FMath::Clamp(Asset, 1, CoreCap);
|
||||
if (GVoxelForgeStep1GenerationCap == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return GVoxelForgeStep1GenerationCap > 0
|
||||
? FMath::Min(NormalCap, GVoxelForgeStep1GenerationCap)
|
||||
: NormalCap;
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetActiveGenerationTaskCount() const
|
||||
{
|
||||
const int32 HeldCompletedResults = GVoxelForgeStep1PrequeueVisibleLOD0 > 0
|
||||
? Step1HeldApplyTiles.Num() : 0;
|
||||
return FMath::Max(0, PendingTiles.Num() - HeldCompletedResults);
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetNearTaskReserve(int32 MaxTasks) const
|
||||
@@ -8618,6 +9151,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
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 "
|
||||
"step1_generation_cap=%d step1_max_mesh_applies=%d "
|
||||
"near_task_reserve=%d coarse_task_cap=%d "
|
||||
"max_mesh_applies=%d max_apply_ms=%.3f max_unloads=%d"),
|
||||
LogicalCores,
|
||||
@@ -8625,6 +9159,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
AuthoredCoreCap,
|
||||
AssetCap,
|
||||
MaxTasks,
|
||||
GVoxelForgeStep1GenerationCap,
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
NearTaskReserve,
|
||||
CoarseTaskCap,
|
||||
Settings ? Settings->MaxMeshAppliesPerFrame : 4,
|
||||
@@ -8920,7 +9456,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// They run at BackgroundHigh so the pawn's floor gets the first available worker.
|
||||
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
||||
{
|
||||
if (PendingTiles.Num() >= MaxTasks) break;
|
||||
if (GetActiveGenerationTaskCount() >= MaxTasks) break;
|
||||
if (PendingTiles.Contains(T) || LoadedTiles.Contains(T)) continue;
|
||||
LoadTile(T, EVoxelTileWorkPriority::CollisionCritical);
|
||||
++Submitted;
|
||||
@@ -8930,7 +9466,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 (PendingTiles.Num() >= MaxTasks) break;
|
||||
if (GetActiveGenerationTaskCount() >= MaxTasks) break;
|
||||
const FVoxelTileKey T = *It;
|
||||
if (!IsDesired(T)) { It.RemoveCurrent(); continue; } // obsolete — drop
|
||||
if (PendingTiles.Contains(T)) { continue; } // in flight — retry after it lands
|
||||
@@ -8945,7 +9481,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
for (const FVoxelTileKey& T : DesiredSorted)
|
||||
{
|
||||
if (T.Level != 0) continue;
|
||||
if (PendingTiles.Num() >= MaxTasks) break;
|
||||
if (GetActiveGenerationTaskCount() >= MaxTasks) break;
|
||||
if (PendingTiles.Contains(T)) continue; // in flight
|
||||
if (LoadedTiles.Contains(T)) continue; // already loaded (level is in the key — no LOD remesh)
|
||||
LoadTile(T, EVoxelTileWorkPriority::LOD0);
|
||||
@@ -8967,13 +9503,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
for (const FVoxelTileKey& T : DesiredSorted)
|
||||
{
|
||||
if (T.Level != Level) continue;
|
||||
if (PendingTiles.Num() >= CoarseTaskCap) break;
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
if (PendingTiles.Contains(T)) continue; // in flight
|
||||
if (LoadedTiles.Contains(T)) continue; // already loaded
|
||||
LoadTile(T);
|
||||
++Submitted;
|
||||
}
|
||||
if (PendingTiles.Num() >= CoarseTaskCap) break;
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
}
|
||||
if (bMeasureReadyTransitionVisibility)
|
||||
{
|
||||
@@ -8987,7 +9523,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// mesh reste visible jusqu'au résultat — même schéma que RemeshDirtyChunks).
|
||||
for (auto It = BandRemeshQueue.CreateIterator(); It; ++It)
|
||||
{
|
||||
if (PendingTiles.Num() >= CoarseTaskCap) break;
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
const FVoxelTileKey T = *It;
|
||||
It.RemoveCurrent();
|
||||
if (!IsDesired(T) || PendingTiles.Contains(T) || !LoadedTiles.Contains(T)) continue;
|
||||
@@ -9004,7 +9540,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
GVoxelForgeMaxPendingTilesObserved, MaxTasks, Submitted);
|
||||
}
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
&& PendingTiles.Num() >= CoarseTaskCap
|
||||
&& GetActiveGenerationTaskCount() >= CoarseTaskCap
|
||||
&& !GVoxelForgeGenerationCapHitReported)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
@@ -9032,6 +9568,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
|
||||
void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority WorkPriority)
|
||||
{
|
||||
if (GVoxelForgeStep1GenerationCap == 0) return;
|
||||
if (PendingTiles.Contains(Tile)) return;
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks(); // T2.d — core-clamped
|
||||
@@ -9039,7 +9576,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - GetNearTaskReserve(MaxTasks));
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : MaxTasks;
|
||||
if (PendingTiles.Num() >= AdmissionCap)
|
||||
if (GetActiveGenerationTaskCount() >= AdmissionCap)
|
||||
{
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
&& !GVoxelForgeGenerationCapHitReported)
|
||||
@@ -9695,6 +10232,26 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
Result.NumSkirtTriangles = MeshData.NumSkirtTriangles;
|
||||
Result.bHasCeilingTris = MeshData.NumCeilingTriangles > 0;
|
||||
Result.bHasGroundTris = NumTris > MeshData.NumCeilingTriangles;
|
||||
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Result.Tile.Level == 0
|
||||
&& Result.WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
|
||||
{
|
||||
if (FQueuedThreadPool* CookPool = GVoxelForgeStep1CoreCookPool.GetOrCreate())
|
||||
{
|
||||
CookPool->AddQueuedWork(
|
||||
new FVoxelStep1CoreCookWork(
|
||||
MakeShared<RealtimeMesh::FRealtimeMeshStreamSet>(*Result.Streams),
|
||||
Result.Tile, Result.NumTriangles,
|
||||
FPlatformTime::Cycles64()),
|
||||
EQueuedWorkPriority::High);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelForgeStep1CoreCook] dedicated_pool_create_failed=1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parity instrumentation observes the same FVoxelMeshData that the runtime stream builder
|
||||
@@ -9850,7 +10407,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
|
||||
const bool bHasGroundTris = Result.bHasGroundTris;
|
||||
const bool bHasCeilingTris = Result.bHasCeilingTris;
|
||||
const bool bLevel0 = (Tile.Level == 0);
|
||||
const bool bWantsCollision = bLevel0 && IsTileCollisionDesired(Tile);
|
||||
const bool bWantsCollision = bLevel0 && IsTileCollisionDesired(Tile)
|
||||
&& GVoxelForgeStep1UploadDisableCollision == 0;
|
||||
const bool bCollisionSuppressed = CollisionSuppressedUntilCook.Contains(Tile);
|
||||
|
||||
// RMC keeps its installed BodySetup active while the replacement collision cooks. Preserve
|
||||
|
||||
@@ -294,6 +294,8 @@ public:
|
||||
bool bBodyUpdatedEventSeen = false;
|
||||
uint64 BodyUpdatedEventCycles = 0;
|
||||
uint64 BodyUpdatedEventFrame = 0;
|
||||
bool bPhysicsStateReadyAtBodyUpdate = false;
|
||||
uint64 PhysicsStateReadyCycles = 0;
|
||||
uint64 FutureCallbackCycles = 0;
|
||||
uint64 CompletionGameThreadCycles = 0;
|
||||
// Game-thread-only timing copied from the generation result for the clean LOD0 summary.
|
||||
@@ -320,6 +322,29 @@ public:
|
||||
uint64 NextCollisionSubmissionId = 0;
|
||||
TMap<URealtimeMesh*, FDelegateHandle> CollisionBodyUpdatedHandles;
|
||||
|
||||
struct FStep1SupportSweepProbe
|
||||
{
|
||||
uint64 CollisionRequestCycles = 0;
|
||||
uint64 BodyUpdatedCycles = 0;
|
||||
uint64 PhysicsStateReadyCycles = 0;
|
||||
bool bFirstSweepAttemptLogged = false;
|
||||
};
|
||||
TMap<FVoxelTileKey, FStep1SupportSweepProbe> Step1SupportSweepProbes;
|
||||
|
||||
struct FStep1VisibleApplyProbe
|
||||
{
|
||||
uint64 AppliedCycles = 0;
|
||||
uint64 AppliedFrame = 0;
|
||||
float LastRenderTimeBeforeApply = 0.0f;
|
||||
};
|
||||
TMap<FVoxelTileKey, FStep1VisibleApplyProbe> Step1PendingVisibleApplyProbes;
|
||||
TArray<FChunkResult> Step1HeldApplyResults;
|
||||
TSet<FVoxelTileKey> Step1HeldApplyTiles;
|
||||
int32 Step1HeldVisibleLOD0Count = 0;
|
||||
int32 Step1LastLoggedPrequeueCount = -1;
|
||||
int32 Step1HeldApplyCursor = 0;
|
||||
bool bStep1UploadCorpusReleased = false;
|
||||
|
||||
/** Render component per NON-empty loaded tile (GC-safe via actor ownership; not UPROPERTY
|
||||
* because FVoxelTileKey isn't a USTRUCT key). */
|
||||
TMap<FVoxelTileKey, URealtimeMeshComponent*> TileComponents;
|
||||
@@ -347,6 +372,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 GetActiveGenerationTaskCount() const;
|
||||
/** Reserve task slots for collision-critical/edit/LOD0 work; far work uses the remainder. */
|
||||
int32 GetNearTaskReserve(int32 MaxTasks) const;
|
||||
|
||||
@@ -920,6 +946,8 @@ public:
|
||||
void BindRealtimeMeshCollisionEvent(URealtimeMesh* Mesh);
|
||||
void UnbindRealtimeMeshCollisionEvent(URealtimeMesh* Mesh);
|
||||
void HandleRealtimeMeshCollisionBodyUpdated(URealtimeMesh* Mesh, UBodySetup* BodySetup);
|
||||
void PollStep1SupportSweepProbes();
|
||||
void PollStep1VisibleApplyProbes();
|
||||
void RecordLOD0ReadySample(uint64 RequestStartCycles, uint64 GenerationStartCycles,
|
||||
uint64 GenerationEndCycles, uint64 ApplyStartCycles,
|
||||
uint64 ReadyCycles, uint64 CollisionSubmittedCycles);
|
||||
|
||||
Reference in new issue
Block a user