perf(noise): batch the three cave-warp channels through the SIMD evaluator -- exact, ~4% faster

The remove-one ablation, re-confirmed on the current build, puts the cave warp at 11.0% static /
11.8% moving of worker time: three independent Perlin3D calls per sample. VoxelNoise::Perlin3D_x3
evaluates the three warp channels together through the existing SIMD evaluator (Codex; reviewed
by me). It is wired into the generator, op-stack, classifier and room/player-fit unwarp paths.

Exact: the field is byte-identical. 0/841 tiles changed, 0 triangle delta, 0 voxels of maximum
surface displacement; canonical export 07C14005 / b3e5f4c3..9b377; capability 20,830 / 10,909;
strate-crossing and sealed-proof tests pass. The seam test (VoxelForgeMazeSeamTest, extended) has
48 neighbouring-tile and 24 cross-LOD comparisons, 0 mismatches. Before/after renders from a
camera aimed at the real arrival tunnel are byte-identical.

Cross-machine determinism: Perlin3D_x3 adds no new intrinsics, no AVX, no FMA and no runtime CPU
dispatch. It reuses the evaluator behind the compile-time gate
`#if PLATFORM_ENABLE_VECTORINTRINSICS && PLATFORM_CPU_X86_FAMILY`, so every x64 machine runs the same
instruction path; its output matches the scalar path bit for bit.

Final DEFAULT build vs the committed-tip DLL (two interleaved runs each):
  static  worker 30.83 -> 29.47 s (-4.4%)   LOD0 generation p50/p95 56.3/99.3 -> 53.3/95.3 ms
  moving  worker 67.28 -> 64.56 s (-4.0%)   LOD0 generation p50/p95 55.9/99.0 -> 53.4/96.4 ms
Ledger: every launch passed; two harness false negatives (the seam test on the game-only
validator, and a trace ended by intentional forced termination) were rerun. No crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-15 08:41:59 +02:00
1 parent ce4d8783e9
commit 1d824aaab2
5 files changed
+235 -31

No files matched your search

@@ -162,4 +162,173 @@ bool FVoxelForgeMazeSeamTest::RunTest(const FString& Parameters)
return true; return true;
} }
// The cave-warp optimization is deliberately tile/LOD agnostic: its three channel inputs are
// world coordinates only. Exercise the production density path nevertheless, because a cache
// window or an LOD TLS leak could still make an otherwise pure warp look like a seam.
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeCaveWarpTileLodSeamTest,
"VoxelForge.Generation.CaveWarpTileLodSeam",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeCaveWarpTileLodSeamTest::RunTest(const FString& Parameters)
{
using namespace VoxelForgeTest;
FTestWorld World;
World.Build(1337, 2, true, 4);
if (!World.IsValid())
{
AddError(World.WhyInvalid());
return false;
}
int32 TunnelTopZ = 0, TunnelBottomZ = 0;
if (!World.GetSlotVoxelZRange(FTestWorld::SlotTunnelNetwork, TunnelTopZ, TunnelBottomZ))
{
AddError(TEXT("The seam fixture has no TunnelNetwork slot."));
return false;
}
const float TunnelMidZ = 0.5f * (static_cast<float>(TunnelTopZ)
+ static_cast<float>(TunnelBottomZ));
const int32 ProbeZMid = FMath::Clamp(
FMath::RoundToInt(TunnelMidZ), TunnelBottomZ + 1, TunnelTopZ - 1);
const int32 PreviousStep = VoxelGenLOD::GetThreadSampleStep();
FIntVector PreviousOrigin = FIntVector::ZeroValue;
int32 PreviousCells = 0;
int32 PreviousContextStep = PreviousStep;
VoxelGenLOD::GetThreadTileCacheWindow(
PreviousOrigin, PreviousContextStep, PreviousCells);
int32 PairMismatches = 0;
int32 LodMismatches = 0;
int32 NumPairSamples = 0;
FString FirstMismatch;
auto Compare = [&](const TCHAR* Label, const FVector& Point, float A, float B,
int32& Counter)
{
if (!BitEqual(A, B))
{
++Counter;
if (FirstMismatch.IsEmpty())
{
FirstMismatch = FString::Printf(
TEXT("%s at (%.0f, %.0f, %.0f): %.9g [0x%08X] vs %.9g [0x%08X]"),
Label, Point.X, Point.Y, Point.Z,
A, *reinterpret_cast<const uint32*>(&A),
B, *reinterpret_cast<const uint32*>(&B));
}
}
};
auto Sample = [&](int32 Step, const FIntVector& TileOrigin, const FVector& Point)
{
VoxelGenLOD::SetThreadSampleStep(Step);
VoxelGenLOD::SetThreadTileContext(TileOrigin, CHUNK_SIZE);
return World.Generator->GetDensityAt(Point.X, Point.Y, Point.Z);
};
// Common points are deliberately on the shared face of adjacent production tiles. The
// LOD1 pair is 64 voxels wide (Step=2, Cells=32); its boundary is also a LOD0 tile boundary.
const int32 CommonOffsets[] = { 4, 12, 20, 28 };
const int32 ZOffsets[] = { -8, 0, 8 };
const int32 LodSteps[] = { 1, 2 };
for (const int32 Step : LodSteps)
{
const int32 Extent = CHUNK_SIZE * Step;
const int32 ZOrigin = FMath::FloorToInt(
TunnelMidZ / static_cast<float>(Extent)) * Extent;
const int32 L0Boundary = Extent == CHUNK_SIZE ? CHUNK_SIZE : Extent;
const FIntVector XLeftOrigin(
L0Boundary - Extent, 0, ZOrigin);
const FIntVector XRightOrigin(
L0Boundary, 0, ZOrigin);
const FIntVector YLowerOrigin(
0, L0Boundary - Extent, ZOrigin);
const FIntVector YUpperOrigin(
0, L0Boundary, ZOrigin);
for (const int32 Offset : CommonOffsets)
{
for (const int32 ZOffset : ZOffsets)
{
const float ProbeZ = FMath::Clamp(
static_cast<float>(ProbeZMid + ZOffset),
static_cast<float>(TunnelBottomZ + 1),
static_cast<float>(TunnelTopZ - 1));
const FVector XPoint(static_cast<float>(L0Boundary),
static_cast<float>(Offset), ProbeZ);
const float XLeft = Sample(Step, XLeftOrigin, XPoint);
const float XRight = Sample(Step, XRightOrigin, XPoint);
Compare(Step == 1 ? TEXT("LOD0 X tile pair") : TEXT("LOD1 X tile pair"),
XPoint, XLeft, XRight, PairMismatches);
const FVector YPoint(static_cast<float>(Offset),
static_cast<float>(L0Boundary), ProbeZ);
const float YLower = Sample(Step, YLowerOrigin, YPoint);
const float YUpper = Sample(Step, YUpperOrigin, YPoint);
Compare(Step == 1 ? TEXT("LOD0 Y tile pair") : TEXT("LOD1 Y tile pair"),
YPoint, YLower, YUpper, PairMismatches);
NumPairSamples += 2;
}
}
}
// Compare the same world points through the two LOD contexts. The default LODOctaveDrop is
// zero, so LOD changes sampling density, not the value of a shared world sample.
const int32 CrossLodBoundary = CHUNK_SIZE * 2;
const int32 L0ZOrigin = FMath::FloorToInt(
TunnelMidZ / static_cast<float>(CHUNK_SIZE)) * CHUNK_SIZE;
const int32 L1ZOrigin = FMath::FloorToInt(
TunnelMidZ / static_cast<float>(CrossLodBoundary)) * CrossLodBoundary;
for (const int32 Offset : CommonOffsets)
{
for (const int32 ZOffset : ZOffsets)
{
const float ProbeZ = FMath::Clamp(
static_cast<float>(ProbeZMid + ZOffset),
static_cast<float>(TunnelBottomZ + 1),
static_cast<float>(TunnelTopZ - 1));
const FVector XPoint(static_cast<float>(CrossLodBoundary),
static_cast<float>(Offset), ProbeZ);
const float L0X = Sample(
1, FIntVector(CHUNK_SIZE, 0, L0ZOrigin), XPoint);
const float L1X = Sample(
2, FIntVector(0, 0, L1ZOrigin), XPoint);
Compare(TEXT("same X world point across LOD0/LOD1"),
XPoint, L0X, L1X, LodMismatches);
const FVector YPoint(static_cast<float>(Offset),
static_cast<float>(CrossLodBoundary), ProbeZ);
const float L0Y = Sample(
1, FIntVector(0, CHUNK_SIZE, L0ZOrigin), YPoint);
const float L1Y = Sample(
2, FIntVector(0, 0, L1ZOrigin), YPoint);
Compare(TEXT("same Y world point across LOD0/LOD1"),
YPoint, L0Y, L1Y, LodMismatches);
}
}
VoxelGenLOD::SetThreadSampleStep(PreviousContextStep);
VoxelGenLOD::SetThreadTileContext(PreviousOrigin, PreviousCells);
TestTrue(TEXT("sampled shared boundary points"), NumPairSamples > 0);
TestEqual(TEXT("LOD0 and LOD1 neighbouring tile boundaries are bit-identical"),
PairMismatches, 0);
TestEqual(TEXT("same world boundary points are bit-identical across LOD0 and LOD1"),
LodMismatches, 0);
if (!FirstMismatch.IsEmpty())
{
AddError(FirstMismatch);
}
AddInfo(FString::Printf(
TEXT("Cave warp tile/LOD seam proof: %d same-LOD pair samples, pair mismatches=%d, "
"cross-LOD mismatches=%d; shared points sampled from X and Y boundaries."),
NumPairSamples, PairMismatches, LodMismatches));
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS #endif // WITH_DEV_AUTOMATION_TESTS
@@ -3110,21 +3110,21 @@ namespace
const float Frequency = Params.CaveWarpFrequency; const float Frequency = Params.CaveWarpFrequency;
const float Strength = Params.CaveWarpStrength; const float Strength = Params.CaveWarpStrength;
Warped.X += VoxelNoise::Perlin3D( float WarpX, WarpY, WarpZ;
VoxelNoise::Perlin3D_x3(
WorldPoint.X * Frequency + VoxelHash::SeedOffset(Seed, 0.37f), WorldPoint.X * Frequency + VoxelHash::SeedOffset(Seed, 0.37f),
WorldPoint.Y * Frequency + 1.3f, WorldPoint.Y * Frequency + 1.3f,
EffectiveZ * Frequency + 5.7f) EffectiveZ * Frequency + 5.7f,
* VOXEL_NOISE_SCALE * Strength;
Warped.Y += VoxelNoise::Perlin3D(
WorldPoint.X * Frequency + 7.1f, WorldPoint.X * Frequency + 7.1f,
WorldPoint.Y * Frequency + VoxelHash::SeedOffset(Seed, 0.59f), WorldPoint.Y * Frequency + VoxelHash::SeedOffset(Seed, 0.59f),
EffectiveZ * Frequency + 2.3f) EffectiveZ * Frequency + 2.3f,
* VOXEL_NOISE_SCALE * Strength;
Warped.Z += VoxelNoise::Perlin3D(
WorldPoint.X * Frequency + 11.3f, WorldPoint.X * Frequency + 11.3f,
WorldPoint.Y * Frequency + 9.7f, WorldPoint.Y * Frequency + 9.7f,
EffectiveZ * Frequency + VoxelHash::SeedOffset(Seed, 0.41f)) EffectiveZ * Frequency + VoxelHash::SeedOffset(Seed, 0.41f),
* VOXEL_NOISE_SCALE * Strength; WarpX, WarpY, WarpZ);
Warped.X += WarpX * VOXEL_NOISE_SCALE * Strength;
Warped.Y += WarpY * VOXEL_NOISE_SCALE * Strength;
Warped.Z += WarpZ * VOXEL_NOISE_SCALE * Strength;
return Warped; return Warped;
} }
@@ -4121,18 +4121,21 @@ namespace
{ {
const float WF = P.CaveWarpFrequency; const float WF = P.CaveWarpFrequency;
const float WS = P.CaveWarpStrength; const float WS = P.CaveWarpStrength;
WarpedX += VoxelNoise::Perlin3D(FVector3f( float WarpX, WarpY, WarpZ;
VoxelNoise::Perlin3D_x3(
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f), WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
WorldY * WF + 1.3f, WorldY * WF + 1.3f,
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + 5.7f,
WarpedY += VoxelNoise::Perlin3D(FVector3f(
WorldX * WF + 7.1f, WorldX * WF + 7.1f,
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f), WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + 2.3f,
WarpedZ += VoxelNoise::Perlin3D(FVector3f(
WorldX * WF + 11.3f, WorldX * WF + 11.3f,
WorldY * WF + 9.7f, WorldY * WF + 9.7f,
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f),
WarpX, WarpY, WarpZ);
WarpedX += WarpX * VOXEL_NOISE_SCALE * WS;
WarpedY += WarpY * VOXEL_NOISE_SCALE * WS;
WarpedZ += WarpZ * VOXEL_NOISE_SCALE * WS;
} }
S.LastWarpedPosition = FVector(WarpedX, WarpedY, WarpedZ); S.LastWarpedPosition = FVector(WarpedX, WarpedY, WarpedZ);
@@ -5475,19 +5478,21 @@ namespace
if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f) if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f)
{ {
const float Frequency = P.CaveWarpFrequency; const float Frequency = P.CaveWarpFrequency;
Query.X += VoxelNoise::Perlin3D( float WarpX, WarpY, WarpZ;
VoxelNoise::Perlin3D_x3(
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 0.37f), WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 0.37f),
WorldY * Frequency + 1.3f, WorldY * Frequency + 1.3f,
EffectiveZ * Frequency + 5.7f) * WarpAmplitude; EffectiveZ * Frequency + 5.7f,
Query.Y += VoxelNoise::Perlin3D(
WorldX * Frequency + 7.1f, WorldX * Frequency + 7.1f,
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 0.59f), WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 0.59f),
EffectiveZ * Frequency + 2.3f) * WarpAmplitude; EffectiveZ * Frequency + 2.3f,
Query.Z += VoxelNoise::Perlin3D(
WorldX * Frequency + 11.3f, WorldX * Frequency + 11.3f,
WorldY * Frequency + 9.7f, WorldY * Frequency + 9.7f,
EffectiveZ * Frequency + VoxelHash::SeedOffset(SeedU, 0.41f)) EffectiveZ * Frequency + VoxelHash::SeedOffset(SeedU, 0.41f),
* WarpAmplitude; WarpX, WarpY, WarpZ);
Query.X += WarpX * WarpAmplitude;
Query.Y += WarpY * WarpAmplitude;
Query.Z += WarpZ * WarpAmplitude;
} }
return Query; return Query;
}; };
+12 -9
View File
@@ -3841,21 +3841,24 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
const float WF = Params.CaveWarpFrequency; const float WF = Params.CaveWarpFrequency;
const float WS = Params.CaveWarpStrength; const float WS = Params.CaveWarpStrength;
// Three independent Perlin fields offset by irrational-ish numbers // Three independent Perlin fields offset by irrational-ish numbers keep the X/Y/Z warp
// so the X/Y/Z warp channels don't correlate with each other. // channels decorrelated. Perlin3D_x3 evaluates these same three scalar channels in one
// Single octave to keep per-voxel cost low (3 Perlin calls total). // SIMD batch on x86; its non-SIMD fallback is the original three-call expression.
WarpedX += VoxelNoise::Perlin3D(FVector3f( float WarpX, WarpY, WarpZ;
VoxelNoise::Perlin3D_x3(
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f), WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
WorldY * WF + 1.3f, WorldY * WF + 1.3f,
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + 5.7f,
WarpedY += VoxelNoise::Perlin3D(FVector3f(
WorldX * WF + 7.1f, WorldX * WF + 7.1f,
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f), WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + 2.3f,
WarpedZ += VoxelNoise::Perlin3D(FVector3f(
WorldX * WF + 11.3f, WorldX * WF + 11.3f,
WorldY * WF + 9.7f, WorldY * WF + 9.7f,
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS; EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f),
WarpX, WarpY, WarpZ);
WarpedX += WarpX * VOXEL_NOISE_SCALE * WS;
WarpedY += WarpY * VOXEL_NOISE_SCALE * WS;
WarpedZ += WarpZ * VOXEL_NOISE_SCALE * WS;
} }
//========================================================================= //=========================================================================
+27
View File
@@ -357,6 +357,33 @@ FORCEINLINE void Perlin3D_x4(const float* Xs, const float* Ys, const float* Zs,
} }
#endif #endif
// Three independent channels are the hot cave-warp shape. On x86, evaluate them as three lanes
// of the existing four-wide implementation; the unused fourth lane is deliberate. The scalar
// fallback keeps the exact three-call behavior on platforms without the SIMD path. This helper is
// the owner of the cave-warp channel evaluation so generator, op-stack, and room-fit callers cannot
// drift into different noise formulas or channel offsets.
FORCEINLINE void Perlin3D_x3(
float X0, float Y0, float Z0,
float X1, float Y1, float Z1,
float X2, float Y2, float Z2,
float& Out0, float& Out1, float& Out2)
{
#if VF_NOISE_USE_SIMD
const float Xs[4] = { X0, X1, X2, X0 };
const float Ys[4] = { Y0, Y1, Y2, Y0 };
const float Zs[4] = { Z0, Z1, Z2, Z0 };
float Out[4];
Perlin3D_x4(Xs, Ys, Zs, Out);
Out0 = Out[0];
Out1 = Out[1];
Out2 = Out[2];
#else
Out0 = Perlin3D(X0, Y0, Z0);
Out1 = Perlin3D(X1, Y1, Z1);
Out2 = Perlin3D(X2, Y2, Z2);
#endif
}
//============================================================================= //=============================================================================
// fBm / Ridged — octaves evaluated 4 at a time through Perlin3D_x4. // fBm / Ridged — octaves evaluated 4 at a time through Perlin3D_x4.
// Accumulation stays scalar in octave order, so the result is independent of // Accumulation stays scalar in octave order, so the result is independent of