NEON backend for VectorF32 on AArch64: one float32x4_t per vector (Len * Packing <= 4), with a vectorised sin/cos port of the SSE helpers. VectorF16 gets a native float16x8_t path when the target has ARMv8.2-FP16 arithmetic. Without it VectorF16 aliases VectorF32, which converts fp16 <-> fp32 on Load/Store. The scalar fallback gains the batched Dot that MatrixRowMajor relies on. Test tiers added for armv8-a and armv8.2-a+fp16. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4.6 KiB
Crafter.Math
A C++23 math library for the Crafter engine, distributed as a set of C++ modules. Provides generic vector and matrix types alongside SIMD-specialized fixed-size vectors for float and _Float16, plus a small set of ray intersection routines.
The library is hardware-aware: it picks the widest SIMD path the target supports (SSE / AVX / AVX-512 on x86_64, NEON on AArch64, RVV on RISC-V, SIMD128 on WebAssembly) and falls back to scalar code elsewhere.
Modules
import Crafter.Math; re-exports the following partitions:
| Partition | Contents |
|---|---|
:Basic |
ToRadian, plus the VectorF16 → VectorF32 alias on x86_64 without AVX512-FP16 and on AArch64 without ARMv8.2-FP16. |
:Vector |
Generic Vector<T, Len, Alignment> with .x/.y/.z/.w and .r/.g/.b/.a accessors, arithmetic, comparison, and conversion operators. |
:MatrixRowMajor |
MatrixRowMajor<T, ColumnSize, RowSize, Repeats> — row-major matrices with packed-batch support via Repeats. |
:VectorF32 |
VectorF32<Len, Packing> — SIMD-specialized 32-bit float vector with Load/Store, FMA, Cross, Dot, Length, Normalize, Sin/Cos, Shuffle, etc. |
:VectorF16 |
VectorF16<Len, Packing> — same surface as VectorF32 for _Float16 when the target supports it. |
:Intersection |
IntersectionTestRayTriangle, IntersectionTestRaySphere, IntersectionTestRayOrientedBox. |
:Common |
Internal SIMD type selection (__m128/256/512, __m128h/256h/512h, float32x4_t, float16x8_t, v128_t, or scalar array) used by the F16/F32 vectors. |
Len is the logical vector width (1–4 for most graphics use) and Packing is the number of vectors packed into one SIMD register — e.g. VectorF32<3, 4> holds four 3-vectors in a single 512-bit register, which is what the batched Dot, Length, and Normalize overloads operate on.
Example
import Crafter.Math;
using namespace Crafter;
// Generic 3-vector of floats.
Vector<float, 3> a(1.0f, 2.0f, 3.0f);
Vector<float, 3> b(4.0f, 5.0f, 6.0f);
auto c = a + b;
// SIMD-specialized 4-vector with one element of padding.
VectorF32<4, 1> p(1.0f);
VectorF32<4, 1> q(2.0f);
auto r = VectorF32<3, 1>::Cross(p, q);
// Ray vs. triangle.
float t = IntersectionTestRayTriangle<float>(
{0,0,0}, {1,0,0}, {0,1,0},
{0.25f, 0.25f, -1.0f}, {0, 0, 1});
Building
This project uses Crafter.Build. The build script in project.cpp declares the library as a static C++ module library and registers three test executables targeting different ISAs:
Vector-sapphirerapids— AVX-512 with FP16 (-march=sapphirerapids)Vector-x86-64-v4— AVX-512 baselineVector-x86-64-v3— AVX2 baselineVector-rv64gcv_zvl512b— RVV with VLEN≥512 (uses the 64-byte tier)Vector-rv64gcv_zvl256b— RVV with VLEN≥256 (32-byte tier)Vector-rv64gcv— RVA23 baseline ZVL128B (16-byte tier)Vector-armv8.2-a+fp16— AArch64 NEON with ARMv8.2-FP16 (nativeVectorF16)Vector-armv8-a— AArch64 baseline NEON
Build and run tests via your usual Crafter.Build entry point; build/ and bin/ are git-ignored.
Target support
- x86_64: SSE / AVX / AVX-512F selected per target; AVX512-FP16 is required for native
VectorF16(otherwise it aliasesVectorF32). F16C is used for fp16 ↔ fp32 conversion when available. - RISC-V: RVV 1.0 (the
Vextension, ratified Nov 2021, mandatory in the RVA23 profile). Storage is a 16/32/64-byte GNU vector picked at compile time from the guaranteed VLEN (__riscv_v_fixed_vlenfrom Clang's-mrvv-vector-bits=, else__riscv_v_min_vlenfrom the march'sZvl*suffix, else the ZVL128B baseline).VectorF16aliasesVectorF32until aZvfhpath lands. - AArch64: NEON (
arm_neon.h) 128-bit path; SIMD width is capped at 16 bytes, so a vector holds at most 4 floats or 8 halves.VectorF32only needs the ARMv8-A baseline. ARMv8.2-FP16 (-march=armv8.2-a+fp16, or any-march/-mcputhat implies it) is required for nativeVectorF16; otherwise it aliasesVectorF32, which converts fp16 ↔ fp32 onLoad/Store. 32-bit ARM takes the scalar fallback. - WebAssembly:
wasm_simd128.h128-bit path; SIMD width is capped at 16 bytes. - Other targets: scalar fallback via
std::array<T, N>.
License
This library is licensed under the GNU Lesser General Public License, version 3 only (LGPL-3.0-only). See LICENSE for the license text. The LGPL supplements the GNU General Public License version 3, a copy of which is included as LICENSE.GPL.
Copyright
Copyright (C) 2026 Catcrafts® catcrafts.net