streaming: incremental clipmap desired-set delta for adjacent crossings (L165)

BuildDelta emits entering/leaving slabs per LOD (incl. coarser-ring coverage changes); cached Core key set updated
from the delta at one-tile crossings, full rebuild for larger jumps or LOD>=4 changes. Equality audit vs full rebuild
(45/45 crossings, 20/20 sampled; injected mismatch asserts) + 630 automation cases. 30 m/s UE, 5 pairs:
BuildDesiredTiles p95 7.80 -> 4.08 ms, p99 11.46 -> 7.97 ms, worst tick 13.73 -> 12.11 ms (all pairs), near and
forward readiness and arrival better in all pairs; far holes better in 4/5 (pair 5 +0.29 pp). Geometry unchanged.
Source/: VoxelClipmapDesiredTiles.h, VoxelWorld.cpp, VoxelWorld.h, Tests/VoxelForgeClipmapNoOverlapTest.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-11-02 12:01:25 +01:00
1 parent aa458d3607
commit 9c70053543
6 files changed
+1314 -14

No files matched your search

@@ -322,6 +322,108 @@ bool FVoxelForgeClipmapNoOverlapTest::RunTest(const FString& Parameters)
FirstFailure.IsEmpty() ? TEXT("none") : *FirstFailure));
TestEqual(TEXT("all swept desired sets have zero overlapping pairs"), OverlapCases, 0);
TestEqual(TEXT("all swept desired sets cover the legacy footprint"), CoverageFailures, 0);
static const FIntVector DeltaCenters[] = {
FIntVector(-5, -2, -1), FIntVector(-3, 1, 2), FIntVector(-1, -3, 0),
FIntVector(0, 0, 0), FIntVector(1, 3, -2), FIntVector(3, -1, 1),
FIntVector(5, 2, -3),
};
static const FIntVector DeltaSteps[] = {
FIntVector(-1, 0, 0), FIntVector(1, 0, 0),
FIntVector(0, -1, 0), FIntVector(0, 1, 0),
FIntVector(0, 0, -1), FIntVector(0, 0, 1),
};
int32 DeltaCases = 0;
int32 DeltaFailures = 0;
FString FirstDeltaFailure;
for (const FIntVector& Center : DeltaCenters)
for (const FSweepSettings& Sweep : SweepSettings)
for (int32 ClampCase = 0; ClampCase < 3; ++ClampCase)
{
FParameters P;
P.ClipRadius = Sweep.Radius;
P.MaxClipLevel = Sweep.MaxLevel;
P.RenderDistanceChunks = Sweep.RenderDistance;
const auto GetVerticalBounds = [&](int32 CenterZ, int32& OutMinZ, int32& OutMaxZ)
{
OutMinZ = MIN_int32;
OutMaxZ = MAX_int32;
if (ClampCase == 1)
{
OutMinZ = CenterZ - 2;
OutMaxZ = CenterZ + 3;
}
else if (ClampCase == 2)
{
const int32 Band = VoxelClipmapDesiredTiles::FloorDiv(
CenterZ, 1 << Sweep.MaxLevel);
const int64 BandStart = static_cast<int64>(Band)
* (int64{1} << static_cast<uint32>(Sweep.MaxLevel));
OutMinZ = static_cast<int32>(BandStart - 1);
OutMaxZ = OutMinZ + 6;
}
};
int32 PreviousMinZ = 0;
int32 PreviousMaxZ = 0;
GetVerticalBounds(Center.Z, PreviousMinZ, PreviousMaxZ);
TArray<FVoxelTileKey> PreviousTiles;
VoxelClipmapDesiredTiles::Build(Center, PreviousMinZ, PreviousMaxZ, P, PreviousTiles);
for (const FIntVector& Step : DeltaSteps)
{
const FIntVector NextCenter = Center + Step;
int32 NextMinZ = 0;
int32 NextMaxZ = 0;
GetVerticalBounds(NextCenter.Z, NextMinZ, NextMaxZ);
TArray<FVoxelTileKey> Entering;
TArray<FVoxelTileKey> Leaving;
VoxelClipmapDesiredTiles::BuildDelta(
Center, PreviousMinZ, PreviousMaxZ,
NextCenter, NextMinZ, NextMaxZ, P, Entering, Leaving);
TSet<FVoxelTileKey> Updated;
for (const FVoxelTileKey& Tile : PreviousTiles) Updated.Add(Tile);
for (const FVoxelTileKey& Tile : Leaving) Updated.Remove(Tile);
for (const FVoxelTileKey& Tile : Entering) Updated.Add(Tile);
TArray<FVoxelTileKey> FullNextTiles;
VoxelClipmapDesiredTiles::Build(
NextCenter, NextMinZ, NextMaxZ, P, FullNextTiles);
TSet<FVoxelTileKey> FullNextSet;
for (const FVoxelTileKey& Tile : FullNextTiles) FullNextSet.Add(Tile);
bool bMatches = Updated.Num() == FullNextSet.Num();
if (bMatches)
{
for (const FVoxelTileKey& Tile : Updated)
{
if (!FullNextSet.Contains(Tile))
{
bMatches = false;
break;
}
}
}
if (!bMatches)
{
++DeltaFailures;
if (FirstDeltaFailure.IsEmpty())
{
FirstDeltaFailure = FString::Printf(
TEXT("center=(%d,%d,%d) step=(%d,%d,%d) radius=%d level=%d clamp=%d entering=%d leaving=%d full=%d updated=%d"),
Center.X, Center.Y, Center.Z, Step.X, Step.Y, Step.Z,
Sweep.Radius, Sweep.MaxLevel, ClampCase,
Entering.Num(), Leaving.Num(), FullNextSet.Num(), Updated.Num());
}
}
++DeltaCases;
}
}
AddInfo(FString::Printf(
TEXT("clipmap delta cases=%d failures=%d first=%s"), DeltaCases, DeltaFailures,
FirstDeltaFailure.IsEmpty() ? TEXT("none") : *FirstDeltaFailure));
TestEqual(TEXT("clipmap delta update equals a full rebuild on every tested crossing"),
DeltaFailures, 0);
return true;
}
@@ -182,4 +182,218 @@ namespace VoxelClipmapDesiredTiles
PreviousRingMax = RingMax;
}
}
// Compute the exact key difference between two adjacent clipmap centers without visiting
// unchanged ring cells. A coarser ring can change membership when its finer child ring moves,
// even when the coarser ring bounds themselves stay fixed; the cover-box delta below includes
// those boundary keys as well.
FORCEINLINE void BuildDelta(
const FIntVector& PreviousCenter, int32 PreviousMinChunkZ, int32 PreviousMaxChunkZ,
const FIntVector& Center, int32 MinChunkZ, int32 MaxChunkZ,
const FParameters& Parameters,
TArray<FVoxelTileKey>& OutEntering, TArray<FVoxelTileKey>& OutLeaving)
{
struct FBox
{
FIntVector Min = FIntVector::ZeroValue;
FIntVector Max = FIntVector::ZeroValue;
bool bValid = false;
};
OutEntering.Reset();
OutLeaving.Reset();
const int32 Radius = FMath::Max(1, Parameters.ClipRadius);
const int32 MaxLevel = FMath::Clamp(Parameters.MaxClipLevel, 0, 8);
const int32 OuterRadius = OuterShellRadius(Parameters, Radius, MaxLevel);
FBox PreviousValid[9];
FBox CurrentValid[9];
FBox PreviousCovered[9];
FBox CurrentCovered[9];
const auto BuildRings = [&](const FIntVector& RingCenter, int32 RingMinChunkZ,
int32 RingMaxChunkZ, FBox* OutValid, FBox* OutCovered)
{
FIntVector ChildRingMin = FIntVector::ZeroValue;
FIntVector ChildRingMax = FIntVector::ZeroValue;
for (int32 Level = 0; Level <= MaxLevel; ++Level)
{
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(RingCenter, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
const int32 RingAlignment = Level < MaxLevel ? 2 : 1;
FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
FBox Valid;
Valid.Min = RingMin;
Valid.Max = RingMax;
Valid.Min.Z = FMath::Max(Valid.Min.Z, FloorDiv(RingMinChunkZ, Pow));
Valid.Max.Z = FMath::Min(Valid.Max.Z, FloorDiv(RingMaxChunkZ, Pow));
Valid.bValid = Valid.Min.X <= Valid.Max.X
&& Valid.Min.Y <= Valid.Max.Y && Valid.Min.Z <= Valid.Max.Z;
OutValid[Level] = Valid;
FBox Covered;
if (Level > 0)
{
Covered.Min = FIntVector(
CeilDiv(ChildRingMin.X, 2), CeilDiv(ChildRingMin.Y, 2),
CeilDiv(ChildRingMin.Z, 2));
Covered.Max = FIntVector(
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.X) - 1, 2)),
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Y) - 1, 2)),
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Z) - 1, 2)));
Covered.bValid = Covered.Min.X <= Covered.Max.X
&& Covered.Min.Y <= Covered.Max.Y && Covered.Min.Z <= Covered.Max.Z;
}
OutCovered[Level] = Covered;
ChildRingMin = RingMin;
ChildRingMax = RingMax;
}
};
BuildRings(PreviousCenter, PreviousMinChunkZ, PreviousMaxChunkZ,
PreviousValid, PreviousCovered);
BuildRings(Center, MinChunkZ, MaxChunkZ, CurrentValid, CurrentCovered);
const auto Intersect = [](const FBox& A, const FBox& B)
{
FBox Result;
if (!A.bValid || !B.bValid) return Result;
Result.Min = FIntVector(
FMath::Max(A.Min.X, B.Min.X), FMath::Max(A.Min.Y, B.Min.Y),
FMath::Max(A.Min.Z, B.Min.Z));
Result.Max = FIntVector(
FMath::Min(A.Max.X, B.Max.X), FMath::Min(A.Max.Y, B.Max.Y),
FMath::Min(A.Max.Z, B.Max.Z));
Result.bValid = Result.Min.X <= Result.Max.X
&& Result.Min.Y <= Result.Max.Y && Result.Min.Z <= Result.Max.Z;
return Result;
};
const auto AppendDifference = [&Intersect](const FBox& A, const FBox& B, auto& OutBoxes)
{
if (!A.bValid) return;
const FBox Common = Intersect(A, B);
if (!Common.bValid)
{
OutBoxes.Add(A);
return;
}
// Six disjoint slabs cover A minus Common. The later slabs are narrowed to the
// common extent on each already-partitioned axis, so no key is emitted twice.
FBox Slab = A;
if (A.Min.X < Common.Min.X)
{
Slab.Max.X = Common.Min.X - 1;
Slab.bValid = Slab.Min.X <= Slab.Max.X;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
if (Common.Max.X < A.Max.X)
{
Slab.Min.X = Common.Max.X + 1;
Slab.bValid = Slab.Min.X <= Slab.Max.X;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
if (A.Min.Y < Common.Min.Y)
{
Slab.Max.Y = Common.Min.Y - 1;
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
if (Common.Max.Y < A.Max.Y)
{
Slab.Min.Y = Common.Max.Y + 1;
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
Slab.Min.Y = Common.Min.Y;
Slab.Max.Y = Common.Max.Y;
if (A.Min.Z < Common.Min.Z)
{
Slab.Max.Z = Common.Min.Z - 1;
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
Slab.Min.Y = Common.Min.Y;
Slab.Max.Y = Common.Max.Y;
if (Common.Max.Z < A.Max.Z)
{
Slab.Min.Z = Common.Max.Z + 1;
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
if (Slab.bValid) OutBoxes.Add(Slab);
}
};
const auto Contains = [](const FBox& Box, const FIntVector& Point)
{
return Box.bValid && Point.X >= Box.Min.X && Point.X <= Box.Max.X
&& Point.Y >= Box.Min.Y && Point.Y <= Box.Max.Y
&& Point.Z >= Box.Min.Z && Point.Z <= Box.Max.Z;
};
const auto ForEachBox = [](const FBox& Box, const auto& Callback)
{
if (!Box.bValid) return;
for (int64 Z = Box.Min.Z; Z <= Box.Max.Z; ++Z)
for (int64 Y = Box.Min.Y; Y <= Box.Max.Y; ++Y)
for (int64 X = Box.Min.X; X <= Box.Max.X; ++X)
{
Callback(FIntVector(static_cast<int32>(X), static_cast<int32>(Y),
static_cast<int32>(Z)));
}
};
for (int32 Level = 0; Level <= MaxLevel; ++Level)
{
TArray<FBox, TInlineAllocator<16>> ChangedBoxes;
AppendDifference(CurrentValid[Level], PreviousValid[Level], ChangedBoxes);
AppendDifference(PreviousValid[Level], CurrentValid[Level], ChangedBoxes);
if (Level > 0)
{
const FBox CommonValid = Intersect(CurrentValid[Level], PreviousValid[Level]);
if (CommonValid.bValid)
{
TArray<FBox, TInlineAllocator<16>> CoverDifferences;
AppendDifference(CurrentCovered[Level], PreviousCovered[Level], CoverDifferences);
AppendDifference(PreviousCovered[Level], CurrentCovered[Level], CoverDifferences);
for (const FBox& CoverDifference : CoverDifferences)
{
const FBox ChangedCover = Intersect(CoverDifference, CommonValid);
if (ChangedCover.bValid) ChangedBoxes.Add(ChangedCover);
}
}
}
for (const FBox& ChangedBox : ChangedBoxes)
{
ForEachBox(ChangedBox, [&](const FIntVector& Coord)
{
const bool bCurrentMember = Contains(CurrentValid[Level], Coord)
&& !Contains(CurrentCovered[Level], Coord);
const bool bPreviousMember = Contains(PreviousValid[Level], Coord)
&& !Contains(PreviousCovered[Level], Coord);
if (bCurrentMember && !bPreviousMember)
{
OutEntering.Emplace(Coord, Level);
}
else if (bPreviousMember && !bCurrentMember)
{
OutLeaving.Emplace(Coord, Level);
}
});
}
}
}
}
+332 -14
View File
@@ -384,6 +384,24 @@ namespace
TEXT("voxel.StreamingCoreDesiredCacheByCenter"),
GVoxelForgeStreamingCoreDesiredCacheByCenter,
TEXT("Reuse Core clipmap desired keys when a same-center overlay rebuild occurs; 0 queries Core each time."));
int32 GVoxelForgeStreamingIncrementalDesiredSet = 0;
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredSet(
TEXT("voxel.StreamingIncrementalDesiredSet"),
GVoxelForgeStreamingIncrementalDesiredSet,
TEXT("Apply exact per-LOD clipmap key deltas and reuse same-center Core desired keys; 0 uses full Core queries."));
int32 GVoxelForgeStreamingIncrementalDesiredAuditEvery = 0;
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredAuditEvery(
TEXT("voxel.StreamingIncrementalDesiredAuditEvery"),
GVoxelForgeStreamingIncrementalDesiredAuditEvery,
TEXT("Compare the incremental desired set with a full rebuild every N center crossings; 0 disables the debug audit."));
int32 GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch = 0;
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch(
TEXT("voxel.StreamingIncrementalDesiredAuditInjectMismatch"),
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch,
TEXT("Negative control: add one key to the full desired audit reference so the next audit must fail."));
constexpr int32 StreamingStartupMaxMeshApplies = 24;
constexpr float StreamingStartupApplyBudgetMilliseconds = 14.0f;
constexpr double StreamingStartupApplyMaxSeconds = 30.0;
@@ -1161,6 +1179,11 @@ void AVoxelWorld::RegenerateAllChunks()
DesiredTransitionDescendants.Reset();
DesiredTransitionMaxLevel = 0;
DesiredStamped.Reset();
CachedCoreDesiredTiles.Reset();
CachedCoreDesiredTileSet.Reset();
CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
bHasCachedCoreDesiredTiles = false;
IncrementalDesiredCenterCrossings = 0;
++DesiredEpoch;
TransitionHold.Reset();
TransitionHoldQueue.Reset();
@@ -2316,6 +2339,25 @@ void AVoxelWorld::BeginPlay()
GVoxelForgeStreamingCoreDesiredCacheByCenter =
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0;
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingIncrementalDesiredSet="),
GVoxelForgeStreamingIncrementalDesiredSet))
{
GVoxelForgeStreamingIncrementalDesiredSet =
GVoxelForgeStreamingIncrementalDesiredSet != 0 ? 1 : 0;
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingIncrementalDesiredAuditEvery="),
GVoxelForgeStreamingIncrementalDesiredAuditEvery))
{
GVoxelForgeStreamingIncrementalDesiredAuditEvery = FMath::Clamp(
GVoxelForgeStreamingIncrementalDesiredAuditEvery, 0, 1000000);
}
if (FParse::Value(FCommandLine::Get(),
TEXT("voxel.StreamingIncrementalDesiredAuditInjectMismatch="),
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch))
{
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch =
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch != 0 ? 1 : 0;
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingResultBacklogCap="),
CommandLineStreamingResultBacklogCap))
{
@@ -8118,8 +8160,10 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
VoxelForgeCoreState = State;
CachedCoreDesiredTiles.Reset();
CachedCoreDesiredTileSet.Reset();
bHasCachedCoreDesiredTiles = false;
CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
IncrementalDesiredCenterCrossings = 0;
State->bSnapshotReady.store(true, std::memory_order_release);
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
const double CoreCompileLaunchSeconds = bTraceCoreStartup
@@ -11081,6 +11125,8 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
SupportProbeTile, &ResolvedSupportTile, &SupportUnavailableReason)
&& SupportUnavailableReason == nullptr;
const uint32 PreviousDesiredStamp = DesiredStamp;
const bool bHadPreviousDesiredSet = !DesiredStamped.IsEmpty();
DesiredSorted.Reset();
CriticalDesiredTiles.Reset();
OutLeavers.Reset();
@@ -11096,15 +11142,137 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
std::vector<VoxelForgeCore::TileKey> UncachedCoreDesiredTiles;
const TArray<FVoxelTileKey>* CoreDesiredTiles = nullptr;
TArray<FVoxelTileKey> IncrementalCoreEntering;
TArray<FVoxelTileKey> IncrementalCoreLeaving;
double CoreDesiredQueryMilliseconds = 0.0;
bool bCoreDesiredCacheHit = false;
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0
bool bCoreDesiredIncrementalDelta = false;
bool bCoreDesiredFullFallback = false;
const bool bIncrementalDesiredSet = GVoxelForgeStreamingIncrementalDesiredSet != 0;
const bool bIncrementalCenterCrossing = bIncrementalDesiredSet
&& bHasCachedCoreDesiredTiles && CachedCoreDesiredCenter != Center;
if (bIncrementalCenterCrossing)
{
++IncrementalDesiredCenterCrossings;
}
const int32 DesiredAuditInterval = FMath::Max(
0, GVoxelForgeStreamingIncrementalDesiredAuditEvery);
const bool bShouldAuditIncrementalDesired = bIncrementalCenterCrossing
&& DesiredAuditInterval > 0
&& (IncrementalDesiredCenterCrossings % DesiredAuditInterval) == 0;
if (bIncrementalDesiredSet && bHasCachedCoreDesiredTiles
&& CachedCoreDesiredCenter == Center)
{
CoreDesiredTiles = &CachedCoreDesiredTiles;
bCoreDesiredCacheHit = true;
}
else if (bIncrementalDesiredSet && bHasCachedCoreDesiredTiles
&& bIncrementalCenterCrossing)
{
const int64 CenterDelta = FMath::Abs(static_cast<int64>(Center.X) - CachedCoreDesiredCenter.X)
+ FMath::Abs(static_cast<int64>(Center.Y) - CachedCoreDesiredCenter.Y)
+ FMath::Abs(static_cast<int64>(Center.Z) - CachedCoreDesiredCenter.Z);
if (CenterDelta == 1)
{
TArray<FVoxelTileKey> RingEntering;
TArray<FVoxelTileKey> RingLeaving;
{
FReadScopeLock Lock(State->WorldLock);
if (!State->World) return;
const VoxelForgeCore::WorldConfiguration& Configuration =
State->World->configuration();
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.ClipRadius = Configuration.clipRadius;
Parameters.MaxClipLevel = Configuration.maxLod;
Parameters.RenderDistanceChunks = Configuration.renderDistanceChunks;
const auto GetVerticalBounds = [&](int32 CenterChunkZ,
int32& OutMinChunkZ, int32& OutMaxChunkZ)
{
bool bHasStrate = false;
int32 StrateTopChunkZ = 0;
int32 StrateBottomChunkZ = 0;
if (Configuration.clampViewToStrate && StrateManager)
{
bHasStrate = StrateManager->GetStrateChunkZBounds(
CenterChunkZ, StrateTopChunkZ, StrateBottomChunkZ);
}
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
CenterChunkZ, Configuration.viewDistanceUp,
Configuration.viewDistanceDown, Configuration.strateViewMarginChunks,
Configuration.clampViewToStrate, bHasStrate,
StrateTopChunkZ, StrateBottomChunkZ, OutMinChunkZ, OutMaxChunkZ);
};
int32 PreviousMinChunkZ = 0;
int32 PreviousMaxChunkZ = 0;
int32 CurrentMinChunkZ = 0;
int32 CurrentMaxChunkZ = 0;
GetVerticalBounds(CachedCoreDesiredCenter.Z,
PreviousMinChunkZ, PreviousMaxChunkZ);
GetVerticalBounds(Center.Z, CurrentMinChunkZ, CurrentMaxChunkZ);
VoxelClipmapDesiredTiles::BuildDelta(
CachedCoreDesiredCenter, PreviousMinChunkZ, PreviousMaxChunkZ,
Center, CurrentMinChunkZ, CurrentMaxChunkZ, Parameters,
RingEntering, RingLeaving);
}
bool bTouchesCoreSurfaceOverlay = false;
for (const FVoxelTileKey& Key : RingEntering)
{
bTouchesCoreSurfaceOverlay |= Key.Level >= 4;
}
for (const FVoxelTileKey& Key : RingLeaving)
{
bTouchesCoreSurfaceOverlay |= Key.Level >= 4;
}
if (!bTouchesCoreSurfaceOverlay)
{
TSet<FVoxelTileKey> LeavingSet;
LeavingSet.Reserve(RingLeaving.Num());
for (const FVoxelTileKey& Key : RingLeaving)
{
LeavingSet.Add(Key);
CachedCoreDesiredTileSet.Remove(Key);
}
CachedCoreDesiredTiles.RemoveAll([&LeavingSet](const FVoxelTileKey& Key)
{
return LeavingSet.Contains(Key);
});
for (const FVoxelTileKey& Key : RingEntering)
{
if (!CachedCoreDesiredTileSet.Contains(Key))
{
CachedCoreDesiredTileSet.Add(Key);
CachedCoreDesiredTiles.Add(Key);
}
}
IncrementalCoreEntering = MoveTemp(RingEntering);
IncrementalCoreLeaving = MoveTemp(RingLeaving);
CachedCoreDesiredCenter = Center;
CoreDesiredTiles = &CachedCoreDesiredTiles;
bCoreDesiredCacheHit = true;
bCoreDesiredIncrementalDelta = true;
}
else
{
bCoreDesiredFullFallback = true;
}
}
else
{
bCoreDesiredFullFallback = true;
}
}
if (CoreDesiredTiles == nullptr
&& GVoxelForgeStreamingCoreDesiredCacheByCenter != 0
&& bHasCachedCoreDesiredTiles && CachedCoreDesiredCenter == Center)
{
CoreDesiredTiles = &CachedCoreDesiredTiles;
bCoreDesiredCacheHit = true;
}
else
if (CoreDesiredTiles == nullptr)
{
const uint64 QueryStartCycles = VoxelForgeStartupTrace::IsActive()
? FPlatformTime::Cycles64() : 0;
@@ -11115,14 +11283,18 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
UncachedCoreDesiredTiles = State->World->desiredTiles(
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
}
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0)
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 || bIncrementalDesiredSet)
{
CachedCoreDesiredTiles.Reset();
CachedCoreDesiredTileSet.Reset();
CachedCoreDesiredTiles.Reserve(static_cast<int32>(UncachedCoreDesiredTiles.size()));
CachedCoreDesiredTileSet.Reserve(static_cast<int32>(UncachedCoreDesiredTiles.size()));
for (const VoxelForgeCore::TileKey& Key : UncachedCoreDesiredTiles)
{
CachedCoreDesiredTiles.Emplace(
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
CachedCoreDesiredTiles.Add(RuntimeKey);
CachedCoreDesiredTileSet.Add(RuntimeKey);
}
CachedCoreDesiredCenter = Center;
bHasCachedCoreDesiredTiles = true;
@@ -11137,11 +11309,17 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("core_desired_cache"), FString::Printf(
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"hit\":%d,\"keys\":%d,\"query_ms\":%s"),
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"hit\":%d,"
"\"incremental_delta\":%d,\"full_fallback\":%d,"
"\"crossing_count\":%d,\"audit_due\":%d,\"keys\":%d,"
"\"delta_entering\":%d,\"delta_leaving\":%d,\"query_ms\":%s"),
static_cast<unsigned long long>(GFrameCounter), Center.X, Center.Y, Center.Z,
bCoreDesiredCacheHit ? 1 : 0,
bCoreDesiredIncrementalDelta ? 1 : 0, bCoreDesiredFullFallback ? 1 : 0,
IncrementalDesiredCenterCrossings, bShouldAuditIncrementalDesired ? 1 : 0,
CoreDesiredTiles ? CoreDesiredTiles->Num()
: static_cast<int32>(UncachedCoreDesiredTiles.size()),
IncrementalCoreEntering.Num(), IncrementalCoreLeaving.Num(),
*FString::SanitizeFloat(static_cast<float>(CoreDesiredQueryMilliseconds))));
}
@@ -11156,19 +11334,31 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
TSet<FVoxelTileKey> BaseVisualTiles;
TSet<FVoxelTileKey> FullVisualAnchorTiles;
TSet<FVoxelTileKey> CollisionAnchorTiles;
auto AddCandidate = [&](const FVoxelTileKey& Key)
TSet<FVoxelTileKey> OverlayDesiredTiles;
TArray<FVoxelTileKey> DesiredEnteringTiles;
auto AddCandidate = [&](const FVoxelTileKey& Key, bool bOverlay)
{
if (bOverlay)
{
OverlayDesiredTiles.Add(Key);
}
if (!CandidateSet.Contains(Key))
{
CandidateSet.Add(Key);
CandidateKeys.Add(Key);
const uint32* PreviousStamp = DesiredStamped.Find(Key);
if (bIncrementalDesiredSet
&& (PreviousStamp == nullptr || *PreviousStamp != PreviousDesiredStamp))
{
DesiredEnteringTiles.Add(Key);
}
}
DesiredStamped.FindOrAdd(Key) = DesiredStamp;
};
const auto AddCoreDesiredTile = [&](const FVoxelTileKey& RuntimeKey)
{
AddCandidate(RuntimeKey);
AddCandidate(RuntimeKey, false);
BaseVisualTiles.Add(RuntimeKey);
};
if (CoreDesiredTiles)
@@ -11213,7 +11403,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
for (int32 Dx = -XYRadius; Dx <= XYRadius; ++Dx)
{
const FVoxelTileKey Key(AnchorCenter + FIntVector(Dx, Dy, Dz), 0);
AddCandidate(Key);
AddCandidate(Key, true);
if (Anchor.Policy == EVoxelAnchorPolicy::FullVisual)
{
FullVisualAnchorTiles.Add(Key);
@@ -11237,14 +11427,14 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
It.RemoveCurrent();
continue;
}
AddCandidate(Key);
AddCandidate(Key, true);
CollisionDesiredTiles.Add(Key);
CollisionAnchorTiles.Add(Key);
}
if (bRetainResolvedSupport)
{
AddCandidate(ResolvedSupportTile);
AddCandidate(ResolvedSupportTile, true);
CollisionDesiredTiles.Add(ResolvedSupportTile);
if (!BaseVisualTiles.Contains(ResolvedSupportTile)
&& !FullVisualAnchorTiles.Contains(ResolvedSupportTile))
@@ -11304,6 +11494,95 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
}
}
if (bShouldAuditIncrementalDesired)
{
std::vector<VoxelForgeCore::TileKey> FullReferenceCoreTiles;
const FVector CameraVoxels = WorldToLocalCm(PlayerPosition) / GetActiveVoxelSizeCm();
{
FReadScopeLock Lock(State->WorldLock);
if (!State->World) return;
FullReferenceCoreTiles = State->World->desiredTiles(
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
}
TSet<FVoxelTileKey> FullReferenceCoreSet;
FullReferenceCoreSet.Reserve(static_cast<int32>(FullReferenceCoreTiles.size()));
TSet<FVoxelTileKey> FullReferenceDesiredSet;
FullReferenceDesiredSet.Reserve(
static_cast<int32>(FullReferenceCoreTiles.size()) + OverlayDesiredTiles.Num());
for (const VoxelForgeCore::TileKey& Key : FullReferenceCoreTiles)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FullReferenceCoreSet.Add(RuntimeKey);
FullReferenceDesiredSet.Add(RuntimeKey);
}
for (const FVoxelTileKey& Key : OverlayDesiredTiles)
{
FullReferenceDesiredSet.Add(Key);
}
int32 MissingFromIncrementalDesired = 0;
int32 ExtraInIncrementalDesired = 0;
for (const FVoxelTileKey& Key : FullReferenceDesiredSet)
{
if (!CandidateSet.Contains(Key)) ++MissingFromIncrementalDesired;
}
for (const FVoxelTileKey& Key : CandidateSet)
{
if (!FullReferenceDesiredSet.Contains(Key)) ++ExtraInIncrementalDesired;
}
TSet<FVoxelTileKey> FullReferenceCollisionOnly;
for (const FVoxelTileKey& Key : CollisionAnchorTiles)
{
if (!FullReferenceCoreSet.Contains(Key) && !FullVisualAnchorTiles.Contains(Key))
{
FullReferenceCollisionOnly.Add(Key);
}
}
int32 MissingCollisionOnly = 0;
int32 ExtraCollisionOnly = 0;
for (const FVoxelTileKey& Key : FullReferenceCollisionOnly)
{
if (!CollisionOnlyTiles.Contains(Key)) ++MissingCollisionOnly;
}
for (const FVoxelTileKey& Key : CollisionOnlyTiles)
{
if (!FullReferenceCollisionOnly.Contains(Key)) ++ExtraCollisionOnly;
}
if (GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch != 0)
{
FullReferenceDesiredSet.Add(
FVoxelTileKey(FIntVector(MAX_int32, MAX_int32, MAX_int32), 8));
++MissingFromIncrementalDesired;
}
const bool bDesiredSetsMatch = MissingFromIncrementalDesired == 0
&& ExtraInIncrementalDesired == 0
&& MissingCollisionOnly == 0 && ExtraCollisionOnly == 0;
VoxelForgeStartupTrace::RecordEvent(TEXT("incremental_desired_audit"), FString::Printf(
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"crossing\":%d,"
"\"core_keys\":%d,\"reference_keys\":%d,\"candidate_keys\":%d,"
"\"missing\":%d,\"extra\":%d,\"collision_only_missing\":%d,"
"\"collision_only_extra\":%d,\"pass\":%d"),
static_cast<unsigned long long>(GFrameCounter), Center.X, Center.Y, Center.Z,
IncrementalDesiredCenterCrossings, static_cast<int32>(FullReferenceCoreTiles.size()),
FullReferenceDesiredSet.Num(), CandidateSet.Num(), MissingFromIncrementalDesired,
ExtraInIncrementalDesired, MissingCollisionOnly, ExtraCollisionOnly,
bDesiredSetsMatch ? 1 : 0));
if (!bDesiredSetsMatch)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeIncrementalDesiredAudit] mismatch crossing=%d missing=%d extra=%d collision_only_missing=%d collision_only_extra=%d"),
IncrementalDesiredCenterCrossings, MissingFromIncrementalDesired,
ExtraInIncrementalDesired, MissingCollisionOnly, ExtraCollisionOnly);
FPlatformMisc::RequestExitWithStatus(
false, 1, TEXT("VoxelForge incremental desired-set equality failure"));
checkf(false, TEXT("VoxelForge incremental desired-set equality failure"));
}
}
std::vector<VoxelForgeCore::TileKey> PriorityInput;
PriorityInput.reserve(static_cast<std::size_t>(CandidateKeys.Num()));
for (const FVoxelTileKey& Key : CandidateKeys)
@@ -11384,17 +11663,50 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
DesiredSorted.Insert(Key, 0);
}
}
BuildDesiredTransitionIndex();
if (bIncrementalDesiredSet && bHadPreviousDesiredSet)
{
constexpr int32 MaxSupportedTileLevel = 8;
const int32 TransitionIndexMaxLevel = FMath::Max(
DesiredTransitionMaxLevel, MaxSupportedTileLevel);
const auto ApplyTransitionDelta = [this, TransitionIndexMaxLevel](
const FVoxelTileKey& Key, bool bAdd)
{
for (int32 Level = Key.Level + 1; Level <= TransitionIndexMaxLevel; ++Level)
{
TArray<FVoxelTileKey, TInlineAllocator<8>> TouchingAncestors;
VF_AppendTouchingAncestors(Key, Level, TouchingAncestors);
for (const FVoxelTileKey& Ancestor : TouchingAncestors)
{
if (bAdd)
{
TArray<FVoxelTileKey>& Descendants =
DesiredTransitionDescendants.FindOrAdd(Ancestor);
if (!Descendants.Contains(Key)) Descendants.Add(Key);
}
else if (TArray<FVoxelTileKey>* Descendants =
DesiredTransitionDescendants.Find(Ancestor))
{
Descendants->RemoveSingleSwap(Key, EAllowShrinking::No);
if (Descendants->IsEmpty()) DesiredTransitionDescendants.Remove(Ancestor);
}
}
}
};
for (const FVoxelTileKey& Key : OutLeavers) ApplyTransitionDelta(Key, false);
for (const FVoxelTileKey& Key : DesiredEnteringTiles) ApplyTransitionDelta(Key, true);
}
else
{
BuildDesiredTransitionIndex();
}
bool bRetainedSetChanged = false;
int32 RetainedTileCount = 0;
TSet<FVoxelTileKey> DesiredSet;
for (const FVoxelTileKey& Key : DesiredSorted) DesiredSet.Add(Key);
TArray<FVoxelTileKey> MissingRetainedFineTiles;
{
FScopeLock Lock(&State->RetainedTilesLock);
RetainedTileCount = static_cast<int32>(State->RetainedTiles.size());
bRetainedSetChanged = DesiredSet.Num() != RetainedTileCount;
bRetainedSetChanged = DesiredSorted.Num() != RetainedTileCount;
for (const FVoxelTileKey& Key : DesiredSorted)
{
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
@@ -14039,6 +14351,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
"\"near_reserve\":%d,\"scheduler_policy\":%d,"
"\"forward_first\":%d,\"rebuild_on_core_region_completion\":%d,"
"\"core_desired_cache_by_center\":%d,"
"\"incremental_desired_set\":%d,"
"\"incremental_desired_audit_every\":%d,"
"\"incremental_desired_audit_inject_mismatch\":%d,"
"\"camera_forward_valid\":%d,"
"\"near_missing_by_direction\":%s,\"near_submitted_by_direction\":%s,"
"\"near_admission_order_first\":%s,"
@@ -14074,6 +14389,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
GVoxelForgeStreamingFastTravelForwardFirst != 0 ? 1 : 0,
GVoxelForgeStreamingRebuildOnCoreRegionCompletion != 0 ? 1 : 0,
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0,
GVoxelForgeStreamingIncrementalDesiredSet != 0 ? 1 : 0,
GVoxelForgeStreamingIncrementalDesiredAuditEvery,
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch,
bCameraForwardValid ? 1 : 0,
*IntArray4Json(DesiredMissingByDirection),
*IntArray4Json(SchedulerSubmittedByDirection),
+2
View File
@@ -1255,8 +1255,10 @@ public:
bool bSpeedAwareTravelMode = false;
bool bSpeedAwareFastMode = false;
TArray<FVoxelTileKey> CachedCoreDesiredTiles;
TSet<FVoxelTileKey> CachedCoreDesiredTileSet;
FIntVector CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
bool bHasCachedCoreDesiredTiles = false;
int32 IncrementalDesiredCenterCrossings = 0;
TArray<FVoxelTileKey> DesiredSorted; // critical floor prefix, then true-pawn distance-first
TArray<FVoxelTileKey> CriticalDesiredTiles; // occupied/support tile, then heading tile
// Ancestor -> desired descendants, rebuilt with DesiredSorted. Reconciliation uses this