README

NAME

Noise::Simplex::Native — Fast NativeCall port of Noise::Simplex (2-D & 3-D Simplex-noise generator for Raku)

SYNOPSIS

use Noise::Simplex::Native;

# 2-D field
my $s   = Noise::Simplex::Native::Simplex.new(seed => 42);
my &n2d = $s.create-noise2d;
say n2d(12.3, 9.8);          # → value ā‰ˆ [-1, 1]

# 3-D slice
my &n3d = $s.create-noise3d;
say n3d(1.0, 2.0, 0.5);

# RAII: native ctx is released when $s and any closures go out of scope.

DESCRIPTION

Drop-in replacement for Noise::Simplex with the per-sample hot loop implemented in C and called via NativeCall. Public API is byte-for-byte the same as the original, plus an additive dispose method for deterministic teardown.

The permutation table is built in Raku using the same Math::Random::MT seeding as the original module, then handed to the C side as a 256-byte array. Same seed → identical permutation table on both modules (verified by test).

CLASS

Simplex

  • .new(:$seed!)

Create a generator initialised with an integer seed. Different seeds produce independent noise fields.

  • .create-noise2d → Callable

Returns a two-argument callable sub ($x, $y -- Num)>. Inputs may be Int, Rat, or Num — they are coerced to Num at the boundary.

  • .create-noise3d → Callable

Returns a three-argument callable sub ($x, $y, $z -- Num)>.

  • .build-permutation-table → Array[Int]

Returns the 512-element permutation table. Useful for verification; not normally called directly by users.

  • .fill-noise2d-grid(:$x0!, :$dx!, :$w!, :$y0!, :$dy!, :$h!) → CArray[num64]

Bulk-fill a 2D regular grid in a single NativeCall. Returns a CArray[num64] of length $w * $h where index $j * $w + $i holds noise2d($x0 + $i * $dx, $y0 + $j * $dy). Bit-identical to the per-sample closure.

  • .fill-noise3d-grid(:$x0!, :$dx!, :$w!, :$y0!, :$dy!, :$h!, :$z0!, :$dz!, :$d!) → CArray[num64]

Bulk-fill a 3D regular volume. Returns a CArray[num64] of length $w * $h * $d with z-major iteration: index ($k * $h + $j) * $w + $i holds the sample at (x0 + i*dx, y0 + j*dy, z0 + k*dz). Bit-identical to the per-sample closure (modulo the same simplex-diagonal caveat that applies to all 3D output, see DETERMINISM below).

  • .dispose → Nil

Idempotent. Releases the native ctx eagerly. After .dispose, any sample call throws "used after dispose".

  • submethod DESTROY

Runs .dispose at GC time. Users should not call this directly — it exists to make RAII work.

RANGE

Outputs are centred on zero and scaled to roughly [-1, 1] using factors 70 (2-D) and 32 (3-D), same as the original.

PERFORMANCE

Pure-Raku Noise::Simplex spends nearly all its time in the per-sample closure: array indexing, six squarings, several multiplies, and 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 per-sample speedup over pure Raku is around 15-20Ɨ on Apple Silicon; YMMV across hardware.

For dense regular-grid sampling, prefer the .fill-noise{2,3}d-grid methods. They sample the whole grid in a single NativeCall — boundary cost paid once instead of N times, and the C compiler inlines the inner noise function into the loop at -O3. Typical extra speedup over the per-sample closures is 5-10Ɨ on a 256x256 grid, so the end-to-end win over pure Raku approaches two orders of magnitude when the bulk path is used.

MEMORY MANAGEMENT

The native ctx (~20 KB) is held inside the Simplex instance and released by submethod DESTROY at garbage-collection time. Users do not need to call .dispose.

The closures returned by create-noise2d / create-noise3d capture self strongly, 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 remain valid even if the original Simplex binding goes out of scope.

.dispose exists for callers that want deterministic release — for example, freeing the ctx eagerly in a long-running batch job between many seeds. It is idempotent. Calling any sample closure after .dispose throws.

DETERMINISM

Output relative to the pure-Raku Noise::Simplex, for the same seed:

  • 2-D — bit-identical when inputs are exactly representable in IEEE 754 double (integers, powers-of-2 fractions). For inputs the original keeps in Rat for any intermediate step (e.g. -3.3), the result may diverge by ≤1 ULP because the native module coerces .Num at the boundary while the original computes Rat-precision sums where it can.

  • 3-D — typically within ULPs, but may 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 the whole floor → X0 → x0 derivation). At coordinates where x0, y0, or z0 happen to be exactly equal in Rat (e.g. on the x = y plane), Raku's exact comparison and C's accumulated-rounding 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 close the gap.

In practice, on a 1024-sample 3D grid the median absolute difference is ULP-small (<1e-15) and the worst-case is bounded under 0.05.

The native code is compiled with -fno-fast-math and -ffp-contract=off so the compiler may not reorder FP operations or fuse multiply-adds — across-build determinism within this module is preserved.

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.0

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

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

NativeCallMath::Random

Test Dependencies

Provides

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

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