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 + $i holds noise2d($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 + $i holds noise3d($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 .Num at 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/3 and $g3 = 1/6 are Rat literals, and Rat propagates through floor / X0 / x0). At coordinates where x0, y0, or z0 are exactly equal in Rat (e.g. on the x = y plane), 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

Noise::Simplex::Native v0.1.1

Fast NativeCall port of Noise::Simplex (2D/3D simplex noise)

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

NativeCallMath::Random:ver<0.1.3+>:auth<zef:raku-community-modules>

Test Dependencies

Provides

  • Noise::Simplex::Native
  • Noise::Simplex::Native::FFI

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite โ€” the markup and publishing tools behind this site.