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:
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user