Native
NAME
Noise::Simplex::Native โ Fast NativeCall port of Noise::Simplex.
SYNOPSIS
use Noise::Simplex::Native;
my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
my &n2d = $s.create-noise2d;
say n2d(12.3, 9.8); # โ value โ [-1, 1]
my &n3d = $s.create-noise3d;
say n3d(1.0, 2.0, 0.5);
# RAII: the native ctx is released automatically when $s and any
# closures derived from it go out of scope. No manual cleanup needed.
DESCRIPTION
Noise::Simplex::Native is a drop-in replacement for Noise::Simplex
with the per-sample hot loop implemented in C and called via
NativeCall. The public API matches the pure-Raku original exactly,
plus an additive dispose method for callers that want
deterministic teardown.
The permutation table is built in Raku using the same
Math::Random::MT seeding as the original, then handed to the C
side. This means .new(seed = N)> produces an identical permutation
on both modules.
PERFORMANCE
The pure-Raku original spends nearly all its time in the per-sample closure: array indexing, six squarings, several multiplies, a couple of permutation lookups. NativeCall removes the closure overhead and runs the math in IEEE 754 double directly. On a 65k-sample 2D fill the speedup over pure Raku is around 15-20ร on Apple Silicon; YMMV across hardware.
The per-sample closures still cross the NativeCall boundary once per
call. For dense regular-grid fills, prefer the bulk-fill methods
below โ they pay the boundary cost once and let the C compiler
inline the noise function at -O3, typically another 5-10ร on top.
BULK FILL
For dense regular-grid sampling, two methods produce all samples in a single NativeCall:
.fill-noise2d-grid(:$x0!, :$dx!, :$w!, :$y0!, :$dy!, :$h!)โCArray[num64]of length$w * $h. Index$j * $w + $iholdsnoise2d($x0 + $i * $dx, $y0 + $j * $dy)..fill-noise3d-grid(:$x0!, :$dx!, :$w!, :$y0!, :$dy!, :$h!, :$z0!, :$dz!, :$d!)โCArray[num64]of length$w * $h * $d. Index($k * $h + $j) * $w + $iholdsnoise3d($x0 + $i * $dx, $y0 + $j * $dy, $z0 + $k * $dz). Z-major iteration matches the natural per-z-slice traversal in animation code.
Output is bit-identical to calling the per-sample closure with the
same coordinates โ verified by t/08-bulk-fill.t.
my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
my $field = $s.fill-noise2d-grid(
:x0(0e0), :dx(1/64e0), :w(512),
:y0(0e0), :dy(1/64e0), :h(512),
);
# $field[$y * 512 + $x] is the noise sample at ($x/64, $y/64).
MEMORY MANAGEMENT
The native ctx is held inside the Simplex object and released by
submethod DESTROY at garbage-collection time. You do not need to
call .dispose โ RAII covers the common case.
The closures returned by create-noise2d / create-noise3d
capture self, so the Simplex instance (and its native ctx)
stays alive as long as any closure holds a reference. This is the
key safety property: closures returned to a caller are valid even
if the original Simplex binding goes out of scope.
.dispose exists for callers who want deterministic release (e.g.
to free the ~20 KB ctx eagerly in a long-running batch job). It is
idempotent. Calling any sample closure after .dispose throws.
DETERMINISM
Output relative to the pure-Raku Noise::Simplex, for the same seed:
2D โ bit-identical when inputs are exactly representable in IEEE 754 double (integers, powers-of-2 fractions, etc.). For inputs the original keeps in Rat at any intermediate step (e.g.
-3.3), the result may diverge by โค1 ULP. The native module coerces.Numat the boundary; the original computes Rat-precision sums where it can.3D โ typically within ULPs, but can diverge by O(0.001) at simplex-diagonal coordinates. The original 3D pipeline computes in arbitrary-precision Rat (its
$f3 = 1/3and$g3 = 1/6are Rat literals, and Rat propagates through floor / X0 / x0). At coordinates wherex0,y0, orz0are exactly equal in Rat (e.g. on thex = yplane), Raku's exact comparison and C's accumulated-error comparison can pick different simplex corners. The two outputs are then both valid simplex noise, but not the same sample. This is fundamental โ no compiler flag can fix it.
Across-platform determinism within this module is preserved by
compiling with -fno-fast-math and -ffp-contract=off (see
Build.rakumod), so the compiler may not reorder FP operations or
fuse multiply-adds.
ENVIRONMENT
NOISE_SIMPLEX_NATIVE_LIBโ full path to a hand-built shared library, overriding the bundled one. For custom builds, system copies, or air-gapped installs.
SEE ALSO
Noise::Simplex โ the original pure-Raku implementation.
AUTHOR
Matt Doughty
LICENSE
Artistic-2.0