Compare commits

..
Author SHA1 Message Date
0c55e76873
ARM NEON
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>
2026-10-06 17:13:54 +02:00
751a896d10
LF line endings
Basic, Vector, VectorF16 and VectorF32 were stored with CRLF; convert them
to LF like the rest of the tree. No other change to their content.
.gitattributes keeps text files LF from here on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 16:59:52 +02:00
8 changed files with 4838 additions and 4029 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

View file

@ -2,7 +2,7 @@
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, RVV on RISC-V, SIMD128 on WebAssembly) and falls back to scalar code elsewhere.
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
@ -10,13 +10,13 @@ The library is hardware-aware: it picks the widest SIMD path the target supports
| Partition | Contents |
| --- | --- |
| `:Basic` | `ToRadian`, plus the `VectorF16 → VectorF32` alias on x86_64 without AVX512-FP16. |
| `: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`, `v128_t`, or scalar array) used by the F16/F32 vectors. |
| `: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.
@ -52,6 +52,8 @@ This project uses [Crafter.Build](https://forgejo.catcrafts.net/Catcrafts/Crafte
- `Vector-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 (native `VectorF16`)
- `Vector-armv8-a` — AArch64 baseline NEON
Build and run tests via your usual Crafter.Build entry point; `build/` and `bin/` are git-ignored.
@ -59,6 +61,7 @@ Build and run tests via your usual Crafter.Build entry point; `build/` and `bin/
- **x86_64**: SSE / AVX / AVX-512F selected per target; AVX512-FP16 is required for native `VectorF16` (otherwise it aliases `VectorF32`). F16C is used for fp16 ↔ fp32 conversion when available.
- **RISC-V**: RVV 1.0 (the `V` extension, 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_vlen` from Clang's `-mrvv-vector-bits=`, else `__riscv_v_min_vlen` from the march's `Zvl*` suffix, else the ZVL128B baseline). `VectorF16` aliases `VectorF32` until a `Zvfh` path 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. `VectorF32` only needs the ARMv8-A baseline. ARMv8.2-FP16 (`-march=armv8.2-a+fp16`, or any `-march`/`-mcpu` that implies it) is required for native `VectorF16`; otherwise it aliases `VectorF32`, which converts fp16 ↔ fp32 on `Load`/`Store`. 32-bit ARM takes the scalar fallback.
- **WebAssembly**: `wasm_simd128.h` 128-bit path; SIMD width is capped at 16 bytes.
- **Other targets**: scalar fallback via `std::array<T, N>`.

View file

@ -1,18 +1,18 @@
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Math:Basic;
import std;
import :VectorF32;
namespace Crafter {
template<typename T>
constexpr T ToRadian(T degrees) {
return degrees * (std::numbers::pi / 180);
}
#if (defined(__x86_64) && !defined(__AVX512FP16__)) || defined(__riscv_vector) || !defined(__FLT16_MAX__)
export template <std::uint32_t Len, std::uint32_t Packing>
using VectorF16 = VectorF32<Len, Packing>;
#endif
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Math:Basic;
import std;
import :VectorF32;
namespace Crafter {
template<typename T>
constexpr T ToRadian(T degrees) {
return degrees * (std::numbers::pi / 180);
}
#if (defined(__x86_64) && !defined(__AVX512FP16__)) || defined(__riscv_vector) || (defined(__aarch64__) && defined(__ARM_NEON) && !defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC)) || !defined(__FLT16_MAX__)
export template <std::uint32_t Len, std::uint32_t Packing>
using VectorF16 = VectorF32<Len, Packing>;
#endif
}

View file

@ -8,6 +8,9 @@ module;
#ifdef __wasm_simd128__
#include <wasm_simd128.h>
#endif
#if defined(__aarch64__) && defined(__ARM_NEON)
#include <arm_neon.h>
#endif
#ifdef __riscv_vector
#include <riscv_vector.h>
// Compile-time VLEN selection. RVV is VLA at the ISA level, but storage in
@ -37,9 +40,11 @@ import std;
// VectorF16 exists as a real struct when _Float16 is available AND we are not
// on x86_64 without AVX512FP16 (that path aliases VectorF16 to VectorF32 in
// Crafter.Math:Basic for performance). The same alias kicks in on RISC-V until
// a Zvfh path lands. Each translation unit that needs this distinction
// redefines the same condition since macros do not cross module boundaries.
#if defined(__FLT16_MAX__) && (!defined(__x86_64) || defined(__AVX512FP16__)) && !defined(__riscv_vector)
// a Zvfh path lands, and on AArch64 NEON without ARMv8.2-FP16 arithmetic,
// where VectorF32 converts fp16 <-> fp32 on Load/Store. Each translation unit
// that needs this distinction redefines the same condition since macros do not
// cross module boundaries.
#if defined(__FLT16_MAX__) && (!defined(__x86_64) || defined(__AVX512FP16__)) && !defined(__riscv_vector) && !(defined(__aarch64__) && defined(__ARM_NEON) && !defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC))
namespace Crafter {
export template <std::uint8_t Len, std::uint8_t Packing>
struct VectorF16;
@ -52,7 +57,7 @@ namespace Crafter {
template <std::uint8_t Len, std::uint8_t Packing, typename T>
struct VectorBase {
#if defined(__FLT16_MAX__) && (!defined(__x86_64) || defined(__AVX512FP16__)) && !defined(__riscv_vector)
#if defined(__FLT16_MAX__) && (!defined(__x86_64) || defined(__AVX512FP16__)) && !defined(__riscv_vector) && !(defined(__aarch64__) && defined(__ARM_NEON) && !defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC))
template <std::uint8_t L, std::uint8_t P>
friend struct VectorF16;
#endif
@ -89,6 +94,11 @@ namespace Crafter {
>;
#elif defined(__wasm_simd128__)
using VectorType = v128_t;
#elif defined(__aarch64__) && defined(__ARM_NEON)
// NEON registers are 128 bits wide, so every instantiation is one
// register: four floats, or eight halves for the native VectorF16
// that exists when the target has ARMv8.2-FP16 arithmetic.
using VectorType = std::conditional_t<std::is_same_v<T, float>, float32x4_t, float16x8_t>;
#elif defined(__riscv_vector)
// RVV tier mirrors the x86 selector: pick the widest register the
// toolchain guarantees, then size each instantiation down to the
@ -118,6 +128,10 @@ namespace Crafter {
// WASM SIMD only has 128-bit vectors; cap at 16 bytes so the entire
// VectorType always fits in a single v128_t.
static constexpr std::uint8_t Max = 16;
#elif defined(__aarch64__) && defined(__ARM_NEON)
// NEON only has 128-bit vectors; cap at 16 bytes so the entire
// VectorType always fits in a single register.
static constexpr std::uint8_t Max = 16;
#elif defined(__riscv_vector)
// RVV tier selected at compile time from the guaranteed VLEN. ZVL128B
// is the RVA23 baseline; ZVL256B / ZVL512B unlock wider registers
@ -698,5 +712,77 @@ namespace Crafter {
out_sin = _mm512_xor_ps(out_sin, x_sign);
}
#endif
#if defined(__aarch64__) && defined(__ARM_NEON)
// --- 128-bit (NEON) helpers, same algorithm as the SSE ones above ---
// Reduce |x| into [-pi/4, pi/4], return reduced value and quadrant
static constexpr void range_reduce_f32x4(float32x4_t ax, float32x4_t& r, float32x4_t& r2, int32x4_t& q) {
float32x4_t fq = vrndnq_f32(vmulq_f32(ax, vdupq_n_f32(two_over_pi)));
q = vcvtq_s32_f32(fq);
// ax - fq * pi/2 as an fmla with the constant negated, not as
// vfmsq_f32: that one is a wrapper function in clang's arm_neon.h,
// and calling it from two module units (here and :VectorF32) fails
// some importers with "definition with same mangled name as
// another definition".
r = vfmaq_f32(ax, fq, vdupq_n_f32(-pi_over_2_hi));
r = vfmaq_f32(r, fq, vdupq_n_f32(-pi_over_2_lo));
r2 = vmulq_f32(r, r);
}
// vfmaq_f32(a, b, c) is a + b*c, so each Horner step reads
// "next coefficient + accumulator * r2".
static constexpr void sincos_poly_f32x4(float32x4_t r, float32x4_t r2, float32x4_t& cos_r, float32x4_t& sin_r) {
cos_r = vfmaq_f32(vdupq_n_f32(c8), vdupq_n_f32(c10), r2);
cos_r = vfmaq_f32(vdupq_n_f32(c6), cos_r, r2);
cos_r = vfmaq_f32(vdupq_n_f32(c4), cos_r, r2);
cos_r = vfmaq_f32(vdupq_n_f32(c2), cos_r, r2);
cos_r = vfmaq_f32(vdupq_n_f32(c0), cos_r, r2);
sin_r = vfmaq_f32(vdupq_n_f32(s7), vdupq_n_f32(s9), r2);
sin_r = vfmaq_f32(vdupq_n_f32(s5), sin_r, r2);
sin_r = vfmaq_f32(vdupq_n_f32(s3), sin_r, r2);
sin_r = vfmaq_f32(vdupq_n_f32(s1), sin_r, r2);
sin_r = vfmaq_f32(vdupq_n_f32(1.0f), sin_r, r2);
sin_r = vmulq_f32(sin_r, r);
}
static constexpr void sincos_f32x4(float32x4_t x, float32x4_t& out_sin, float32x4_t& out_cos) {
uint32x4_t x_sign = vandq_u32(vreinterpretq_u32_f32(x), vdupq_n_u32(0x80000000u));
float32x4_t ax = vabsq_f32(x);
float32x4_t r, r2; int32x4_t q;
range_reduce_f32x4(ax, r, r2, q);
float32x4_t cos_r, sin_r;
sincos_poly_f32x4(r, r2, cos_r, sin_r);
uint32x4_t is_odd = vtstq_s32(q, vdupq_n_s32(1));
// cos: swap on odd, negate if (q+1)&2
out_cos = vbslq_f32(is_odd, sin_r, cos_r);
int32x4_t cos_neg = vandq_s32(vaddq_s32(q, vdupq_n_s32(1)), vdupq_n_s32(2));
out_cos = vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(out_cos), vshlq_n_s32(cos_neg, 30)));
// sin: swap on odd, negate if q&2, apply input sign
out_sin = vbslq_f32(is_odd, cos_r, sin_r);
int32x4_t sin_neg = vandq_s32(q, vdupq_n_s32(2));
out_sin = vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(out_sin), vshlq_n_s32(sin_neg, 30)));
out_sin = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(out_sin), x_sign));
}
// The single-result variants share sincos_f32x4; the half that is not
// returned is dead code the compiler drops after inlining.
static constexpr float32x4_t cos_f32x4(float32x4_t x) {
float32x4_t s, c;
sincos_f32x4(x, s, c);
return c;
}
static constexpr float32x4_t sin_f32x4(float32x4_t x) {
float32x4_t s, c;
sincos_f32x4(x, s, c);
return s;
}
#endif
};
}

View file

@ -1,469 +1,469 @@
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Math:Vector;
import std;
namespace Crafter {
template <typename T, std::uint32_t Len, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase {
T v[Aligment];
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 1, Aligment> {
union {
T v[Aligment];
T x, r;
};
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 2, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y;
};
struct __attribute__((packed)) {
T r, g;
};
};
constexpr VectorBase<T, 2, Aligment>(T x, T y): x(x), y(y) {
}
constexpr VectorBase<T, 2, Aligment>() = default;
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 3, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y, z;
};
struct __attribute__((packed)) {
T r, g, b;
};
};
constexpr VectorBase<T, 3, Aligment>(T x, T y, T z): x(x), y(y), z(z) {
}
constexpr VectorBase<T, 3, Aligment>() = default;
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 4, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y, z, w;
};
struct __attribute__((packed)) {
T r, g, b, a;
};
};
constexpr VectorBase<T, 4, Aligment>(T x, T y, T z, T w): x(x), y(y), z(z), w(w) {
}
constexpr VectorBase<T, 4, Aligment>() = default;
};
template <typename T, std::uint32_t Len>
struct VectorBase<T, Len, 0> {
T v[Len];
};
template <typename T>
struct VectorBase<T, 1, 0> {
union {
T v[1];
T x, r;
};
};
template <typename T>
struct VectorBase<T, 2, 0> {
union {
T v[2];
struct {
T x, y;
};
struct {
T r, g;
};
};
constexpr VectorBase<T, 2, 0>(T x, T y): x(x), y(y) {
}
constexpr VectorBase<T, 2, 0>() = default;
};
template <typename T>
struct VectorBase<T, 3, 0> {
union {
T v[3];
struct {
T x, y, z;
};
struct {
T r, g, b;
};
};
constexpr VectorBase<T, 3, 0>(T x, T y, T z): x(x), y(y), z(z) {
}
constexpr VectorBase<T, 3, 0>() = default;
};
template <typename T>
struct VectorBase<T, 4, 0> {
union {
T v[4];
struct {
T x, y, z, w;
};
struct {
T r, g, b, a;
};
};
constexpr VectorBase<T, 4, 0>(T x, T y, T z, T w): x(x), y(y), z(z), w(w) {
}
constexpr VectorBase<T, 4, 0>() = default;
};
export template <typename T, std::uint32_t Len, std::uint32_t Aligment = 0>
class Vector : public VectorBase<T, Len, Aligment> {
public:
constexpr Vector(T x, T y, T z, T w ) requires(std::same_as<T, T> && Len == 4) : VectorBase<T, Len, Aligment>(x, y, z, w) {
}
constexpr Vector(T x, T y, T z) requires(std::same_as<T, T> && Len == 3) : VectorBase<T, Len, Aligment>(x, y, z) {
}
constexpr Vector(T x, T y) requires(std::same_as<T, T> && Len == 2) : VectorBase<T, Len, Aligment>(x, y) {
}
constexpr Vector() = default;
template <std::uint32_t BAlignment>
constexpr operator Vector<T, Len, BAlignment>() const {
Vector<T, Len, BAlignment> returnVector;
for(std::uint32_t i = 0; i < Len; i++) {
returnVector.v[i] = this->v[i];
}
return returnVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator+(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]+b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator-(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]-b.v[i];
}
return resultVector;
}
constexpr Vector<T, Len, Aligment> operator-(){
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = -this->v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator*(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]*b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator/(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]/b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator+=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]+=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator-=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]-=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator*=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]*=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator/=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]/=b.v[i];
}
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator+(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] + b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator-(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] - b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator*(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] * b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator/(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] / b;
}
return resultVector;
}
template <typename BT>
constexpr void operator+=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] += b;
}
}
template <typename BT>
constexpr void operator-=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] -= b;
}
}
template <typename BT>
constexpr void operator*=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] *= b;
}
}
template <typename BT>
constexpr void operator/=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] /= b;
}
}
template <typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr bool operator==(Vector<BT, Blen, BAlignment> b) const {
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
if(this->v[i] != b.v[i]) {
return false;
}
}
return true;
}
template <typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr bool operator!=(Vector<BT, Blen, BAlignment> b) const {
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
if(this->v[i] != b.v[i]) {
return true;
}
}
return false;
}
constexpr void Normalize() {
T fLength = Length();
fLength = 1.0f / fLength;
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] *= fLength;
}
}
constexpr void Invert() {
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] = -this->v[i];
}
}
constexpr T Length() const {
T Result = LengthSq();
return std::sqrtf(Result);
}
constexpr T LengthSq() const {
return Dot(*this, *this);
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment, typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr static Vector<T, Len, Aligment> Cross(Vector<AT, Alen, AAlignment> a, Vector<BT, Blen, BAlignment> b) requires(Len == 3 && Alen >= 3 && Blen >= 3) {
return Vector<T, Len, Aligment>(
(a.v[1] * b.v[2]) - (a.v[2] * b.v[1]),
(a.v[2] * b.v[0]) - (a.v[0] * b.v[2]),
(a.v[0] * b.v[1]) - (a.v[1] * b.v[0])
);
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment>
constexpr static Vector<T, Len, Aligment> Normalize(Vector<AT, Alen, AAlignment> a) requires(Len == Alen) {
Vector<T, 3, 0> returned;
T fLength = a.Length();
if (fLength > 0) {
fLength = 1.0f / fLength;
}
for(std::uint32_t i = 0; i < Len; i++) {
returned.v[i] = a.v[i] * fLength;
}
return returned;
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment, typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr static T Dot(Vector<AT, Alen, AAlignment> a, Vector<BT, Blen, BAlignment> b) requires(Alen >= Len && Blen >= Len) {
T accumulate = a.v[0] * b.v[0];
for(std::uint32_t i = 1; i < Len; i++) {
accumulate += a.v[i] * b.v[i];
}
return accumulate;
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment>
constexpr static Vector<T, 3, Aligment> Rotate(Vector<AT, 3, AAlignment> v, Vector<BT, 4, BAlignment> q) requires(Len == 3) {
Vector<T, 3, 0> qv(q.x, q.y, q.z);
Vector<T, 3, 0> t = Vector<T, 3, Aligment>::Cross(qv, v) * T(2);
return v + t * q.w + Vector<T, 3, Aligment>::Cross(qv, t);
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment, typename PT, std::uint32_t PAlignment>
constexpr static Vector<T, 3, Aligment> RotatePivot(Vector<AT, 3, AAlignment> v, Vector<BT, 4, BAlignment> q, Vector<PT, 3, PAlignment> pivot) requires(Len == 3) {
Vector<T, 3, 0> translated = v - pivot;
Vector<T, 3, 0> qv(q.x, q.y, q.z);
Vector<T, 3, 0> t = Cross(qv, translated) * T(2);
Vector<T, 3, 0> rotated = translated + t * q.w +Cross(qv, t);
return rotated + pivot;
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment, typename CT, std::uint32_t CAlignment>
constexpr static Vector<T, 4, Aligment> QuanternionFromBasis(Vector<AT, 3, AAlignment> right, Vector<BT, 3, BAlignment> up, Vector<CT, 3, CAlignment> forward) requires(Len == 4) {
T m00 = right.x;
T m01 = up.x;
T m02 = forward.x;
T m10 = right.y;
T m11 = up.y;
T m12 = forward.y;
T m20 = right.z;
T m21 = up.z;
T m22 = forward.z;
T trace = m00 + m11 + m22;
Vector<T, 4, Aligment> q;
if (trace > std::numeric_limits<T>::epsilon()) {
T s = std::sqrt(trace + T(1)) * T(2);
q.w = T(0.25) * s;
q.x = (m21 - m12) / s;
q.y = (m02 - m20) / s;
q.z = (m10 - m01) / s;
}
else if ((m00 > m11) && (m00 > m22)) {
T s = std::sqrt(T(1) + m00 - m11 - m22) * T(2);
q.w = (m21 - m12) / s;
q.x = T(0.25) * s;
q.y = (m01 + m10) / s;
q.z = (m02 + m20) / s;
}
else if (m11 > m22) {
T s = std::sqrt(T(1) + m11 - m00 - m22) * T(2);
q.w = (m02 - m20) / s;
q.x = (m01 + m10) / s;
q.y = T(0.25) * s;
q.z = (m12 + m21) / s;
}
else {
T s = std::sqrt(T(1) + m22 - m00 - m11) * T(2);
q.w = (m10 - m01) / s;
q.x = (m02 + m20) / s;
q.y = (m12 + m21) / s;
q.z = T(0.25) * s;
}
q.Normalize();
return q;
}
constexpr static Vector<T, 4, Aligment> QuanternionFromEuler(T rollHalf, T pitchHalf, T yawHalf) {
T cr = std::cos(rollHalf);
T sr = std::sin(rollHalf);
T cp = std::cos(pitchHalf);
T sp = std::sin(pitchHalf);
T cy = std::cos(yawHalf);
T sy = std::sin(yawHalf);
return Vector<T, 4, Aligment>(
sr * cp * cy - cr * sp * sy,
cr * sp * cy + sr * cp * sy,
cr * cp * sy - sr * sp * cy,
cr * cp * cy + sr * sp * sy
);
}
};
}
template <typename T, std::uint32_t Len, std::uint32_t Aligment>
struct std::formatter<Crafter::Vector<T, Len, Aligment>> : std::formatter<std::string> {
auto format(const Crafter::Vector<T, Len, Aligment>& vec, format_context& ctx) const {
std::string out = "{";
for(std::uint32_t i2 = 0; i2 < Len; i2++) {
#ifdef __x86_64
if constexpr(std::same_as<T, _Float16>) {
out += std::format("{}", static_cast<float>(vec.v[i2]));
} else
#endif
{
out += std::format("{}", vec.v[i2]);
}
if (i2 + 1 < Len) out += ",";
}
out += "}";
return std::formatter<std::string>::format(out, ctx);
}
};
template <typename T, std::uint32_t Len, std::uint32_t Aligment, typename BT>
constexpr Crafter::Vector<T, Len, Aligment> operator*(BT b, const Crafter::Vector<T, Len, Aligment>& v) requires std::is_arithmetic_v<BT> {
return v * b;
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Math:Vector;
import std;
namespace Crafter {
template <typename T, std::uint32_t Len, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase {
T v[Aligment];
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 1, Aligment> {
union {
T v[Aligment];
T x, r;
};
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 2, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y;
};
struct __attribute__((packed)) {
T r, g;
};
};
constexpr VectorBase<T, 2, Aligment>(T x, T y): x(x), y(y) {
}
constexpr VectorBase<T, 2, Aligment>() = default;
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 3, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y, z;
};
struct __attribute__((packed)) {
T r, g, b;
};
};
constexpr VectorBase<T, 3, Aligment>(T x, T y, T z): x(x), y(y), z(z) {
}
constexpr VectorBase<T, 3, Aligment>() = default;
};
template <typename T, std::uint32_t Aligment>
struct __attribute__((packed)) VectorBase<T, 4, Aligment> {
union {
T v[Aligment];
struct __attribute__((packed)) {
T x, y, z, w;
};
struct __attribute__((packed)) {
T r, g, b, a;
};
};
constexpr VectorBase<T, 4, Aligment>(T x, T y, T z, T w): x(x), y(y), z(z), w(w) {
}
constexpr VectorBase<T, 4, Aligment>() = default;
};
template <typename T, std::uint32_t Len>
struct VectorBase<T, Len, 0> {
T v[Len];
};
template <typename T>
struct VectorBase<T, 1, 0> {
union {
T v[1];
T x, r;
};
};
template <typename T>
struct VectorBase<T, 2, 0> {
union {
T v[2];
struct {
T x, y;
};
struct {
T r, g;
};
};
constexpr VectorBase<T, 2, 0>(T x, T y): x(x), y(y) {
}
constexpr VectorBase<T, 2, 0>() = default;
};
template <typename T>
struct VectorBase<T, 3, 0> {
union {
T v[3];
struct {
T x, y, z;
};
struct {
T r, g, b;
};
};
constexpr VectorBase<T, 3, 0>(T x, T y, T z): x(x), y(y), z(z) {
}
constexpr VectorBase<T, 3, 0>() = default;
};
template <typename T>
struct VectorBase<T, 4, 0> {
union {
T v[4];
struct {
T x, y, z, w;
};
struct {
T r, g, b, a;
};
};
constexpr VectorBase<T, 4, 0>(T x, T y, T z, T w): x(x), y(y), z(z), w(w) {
}
constexpr VectorBase<T, 4, 0>() = default;
};
export template <typename T, std::uint32_t Len, std::uint32_t Aligment = 0>
class Vector : public VectorBase<T, Len, Aligment> {
public:
constexpr Vector(T x, T y, T z, T w ) requires(std::same_as<T, T> && Len == 4) : VectorBase<T, Len, Aligment>(x, y, z, w) {
}
constexpr Vector(T x, T y, T z) requires(std::same_as<T, T> && Len == 3) : VectorBase<T, Len, Aligment>(x, y, z) {
}
constexpr Vector(T x, T y) requires(std::same_as<T, T> && Len == 2) : VectorBase<T, Len, Aligment>(x, y) {
}
constexpr Vector() = default;
template <std::uint32_t BAlignment>
constexpr operator Vector<T, Len, BAlignment>() const {
Vector<T, Len, BAlignment> returnVector;
for(std::uint32_t i = 0; i < Len; i++) {
returnVector.v[i] = this->v[i];
}
return returnVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator+(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]+b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator-(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]-b.v[i];
}
return resultVector;
}
constexpr Vector<T, Len, Aligment> operator-(){
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = -this->v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator*(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]*b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr Vector<T, Len, Aligment> operator/(Vector<T, Blen, BAlignment> b) const {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
resultVector.v[i] = this->v[i]/b.v[i];
}
return resultVector;
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator+=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]+=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator-=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]-=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator*=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]*=b.v[i];
}
}
template <std::uint32_t Blen, std::uint32_t BAlignment>
constexpr void operator/=(Vector<T, Blen, BAlignment> b){
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
this->v[i]/=b.v[i];
}
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator+(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] + b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator-(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] - b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator*(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] * b;
}
return resultVector;
}
template <typename BT>
constexpr Vector<T, Len, Aligment> operator/(BT b) const requires std::is_arithmetic_v<BT> {
Vector<T, Len, Aligment> resultVector;
for(std::uint32_t i = 0; i < Len; i++) {
resultVector.v[i] = this->v[i] / b;
}
return resultVector;
}
template <typename BT>
constexpr void operator+=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] += b;
}
}
template <typename BT>
constexpr void operator-=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] -= b;
}
}
template <typename BT>
constexpr void operator*=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] *= b;
}
}
template <typename BT>
constexpr void operator/=(BT b) requires std::is_arithmetic_v<BT>{
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] /= b;
}
}
template <typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr bool operator==(Vector<BT, Blen, BAlignment> b) const {
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
if(this->v[i] != b.v[i]) {
return false;
}
}
return true;
}
template <typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr bool operator!=(Vector<BT, Blen, BAlignment> b) const {
for(std::uint32_t i = 0; i < std::min(Len, Blen); i++) {
if(this->v[i] != b.v[i]) {
return true;
}
}
return false;
}
constexpr void Normalize() {
T fLength = Length();
fLength = 1.0f / fLength;
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] *= fLength;
}
}
constexpr void Invert() {
for(std::uint32_t i = 0; i < Len; i++) {
this->v[i] = -this->v[i];
}
}
constexpr T Length() const {
T Result = LengthSq();
return std::sqrtf(Result);
}
constexpr T LengthSq() const {
return Dot(*this, *this);
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment, typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr static Vector<T, Len, Aligment> Cross(Vector<AT, Alen, AAlignment> a, Vector<BT, Blen, BAlignment> b) requires(Len == 3 && Alen >= 3 && Blen >= 3) {
return Vector<T, Len, Aligment>(
(a.v[1] * b.v[2]) - (a.v[2] * b.v[1]),
(a.v[2] * b.v[0]) - (a.v[0] * b.v[2]),
(a.v[0] * b.v[1]) - (a.v[1] * b.v[0])
);
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment>
constexpr static Vector<T, Len, Aligment> Normalize(Vector<AT, Alen, AAlignment> a) requires(Len == Alen) {
Vector<T, 3, 0> returned;
T fLength = a.Length();
if (fLength > 0) {
fLength = 1.0f / fLength;
}
for(std::uint32_t i = 0; i < Len; i++) {
returned.v[i] = a.v[i] * fLength;
}
return returned;
}
template <typename AT, std::uint32_t Alen, std::uint32_t AAlignment, typename BT, std::uint32_t Blen, std::uint32_t BAlignment>
constexpr static T Dot(Vector<AT, Alen, AAlignment> a, Vector<BT, Blen, BAlignment> b) requires(Alen >= Len && Blen >= Len) {
T accumulate = a.v[0] * b.v[0];
for(std::uint32_t i = 1; i < Len; i++) {
accumulate += a.v[i] * b.v[i];
}
return accumulate;
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment>
constexpr static Vector<T, 3, Aligment> Rotate(Vector<AT, 3, AAlignment> v, Vector<BT, 4, BAlignment> q) requires(Len == 3) {
Vector<T, 3, 0> qv(q.x, q.y, q.z);
Vector<T, 3, 0> t = Vector<T, 3, Aligment>::Cross(qv, v) * T(2);
return v + t * q.w + Vector<T, 3, Aligment>::Cross(qv, t);
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment, typename PT, std::uint32_t PAlignment>
constexpr static Vector<T, 3, Aligment> RotatePivot(Vector<AT, 3, AAlignment> v, Vector<BT, 4, BAlignment> q, Vector<PT, 3, PAlignment> pivot) requires(Len == 3) {
Vector<T, 3, 0> translated = v - pivot;
Vector<T, 3, 0> qv(q.x, q.y, q.z);
Vector<T, 3, 0> t = Cross(qv, translated) * T(2);
Vector<T, 3, 0> rotated = translated + t * q.w +Cross(qv, t);
return rotated + pivot;
}
template <typename AT, std::uint32_t AAlignment, typename BT, std::uint32_t BAlignment, typename CT, std::uint32_t CAlignment>
constexpr static Vector<T, 4, Aligment> QuanternionFromBasis(Vector<AT, 3, AAlignment> right, Vector<BT, 3, BAlignment> up, Vector<CT, 3, CAlignment> forward) requires(Len == 4) {
T m00 = right.x;
T m01 = up.x;
T m02 = forward.x;
T m10 = right.y;
T m11 = up.y;
T m12 = forward.y;
T m20 = right.z;
T m21 = up.z;
T m22 = forward.z;
T trace = m00 + m11 + m22;
Vector<T, 4, Aligment> q;
if (trace > std::numeric_limits<T>::epsilon()) {
T s = std::sqrt(trace + T(1)) * T(2);
q.w = T(0.25) * s;
q.x = (m21 - m12) / s;
q.y = (m02 - m20) / s;
q.z = (m10 - m01) / s;
}
else if ((m00 > m11) && (m00 > m22)) {
T s = std::sqrt(T(1) + m00 - m11 - m22) * T(2);
q.w = (m21 - m12) / s;
q.x = T(0.25) * s;
q.y = (m01 + m10) / s;
q.z = (m02 + m20) / s;
}
else if (m11 > m22) {
T s = std::sqrt(T(1) + m11 - m00 - m22) * T(2);
q.w = (m02 - m20) / s;
q.x = (m01 + m10) / s;
q.y = T(0.25) * s;
q.z = (m12 + m21) / s;
}
else {
T s = std::sqrt(T(1) + m22 - m00 - m11) * T(2);
q.w = (m10 - m01) / s;
q.x = (m02 + m20) / s;
q.y = (m12 + m21) / s;
q.z = T(0.25) * s;
}
q.Normalize();
return q;
}
constexpr static Vector<T, 4, Aligment> QuanternionFromEuler(T rollHalf, T pitchHalf, T yawHalf) {
T cr = std::cos(rollHalf);
T sr = std::sin(rollHalf);
T cp = std::cos(pitchHalf);
T sp = std::sin(pitchHalf);
T cy = std::cos(yawHalf);
T sy = std::sin(yawHalf);
return Vector<T, 4, Aligment>(
sr * cp * cy - cr * sp * sy,
cr * sp * cy + sr * cp * sy,
cr * cp * sy - sr * sp * cy,
cr * cp * cy + sr * sp * sy
);
}
};
}
template <typename T, std::uint32_t Len, std::uint32_t Aligment>
struct std::formatter<Crafter::Vector<T, Len, Aligment>> : std::formatter<std::string> {
auto format(const Crafter::Vector<T, Len, Aligment>& vec, format_context& ctx) const {
std::string out = "{";
for(std::uint32_t i2 = 0; i2 < Len; i2++) {
#ifdef __x86_64
if constexpr(std::same_as<T, _Float16>) {
out += std::format("{}", static_cast<float>(vec.v[i2]));
} else
#endif
{
out += std::format("{}", vec.v[i2]);
}
if (i2 + 1 < Len) out += ",";
}
out += "}";
return std::formatter<std::string>::format(out, ctx);
}
};
template <typename T, std::uint32_t Len, std::uint32_t Aligment, typename BT>
constexpr Crafter::Vector<T, Len, Aligment> operator*(BT b, const Crafter::Vector<T, Len, Aligment>& v) requires std::is_arithmetic_v<BT> {
return v * b;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -46,12 +46,22 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
{"rv64gcv", "generic"},
}};
// AArch64 has a single NEON register width (128-bit), so the tiers
// differ in VectorF16 only: ARMv8.2-FP16 gives it a native half-precision
// path, the baseline aliases it to VectorF32.
std::array<MarchTier, 2> armTiers = {{
{"armv8.2-a+fp16", "generic"},
{"armv8-a", "generic"},
}};
const std::string_view target = cfg.target;
const bool isX86 = target.starts_with("x86_64") || target.starts_with("i686");
const bool isRiscv = target.starts_with("riscv");
const bool isArm = target.starts_with("aarch64");
for (std::string_view name : { "Vector", "Intersection", "Matrix" }) {
if (isX86) cfg.AddMarchVariants(name, mathInterfaces, x86Tiers);
if (isRiscv) cfg.AddMarchVariants(name, mathInterfaces, riscvTiers);
if (isArm) cfg.AddMarchVariants(name, mathInterfaces, armTiers);
}
return cfg;