Every heap ref carries its heap tag, generation checks always on, rooted async values. Splash isolates get a domain table and curated minimal preludes; system modules under `mod` expand lazily on first touch. No wall-clock script caps: isolation and force-stop instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1211 lines
57 KiB
Rust
1211 lines
57 KiB
Rust
//! Code generator quality: real kernels from Stage's plates against the
|
|
//! same maths hand-written in Rust (compiled by rustc --release), on one
|
|
//! thread, in ns per element.
|
|
//!
|
|
//! Each Rust twin computes exactly what the kernel computes: the same
|
|
//! polynomials for sin/cos/exp (the kernel's `math: fast` ones), the same
|
|
//! operation order, the same wrapped array indexing and clamped buffer
|
|
//! stores, so the comparison is honest about the safety cost. Every twin's
|
|
//! output is compared with the kernel's (bit-equal or the largest ulp
|
|
//! difference is printed).
|
|
//!
|
|
//! The plate kernels are the kernel text Motion's lower_document builds
|
|
//! for a `Kernel{}` block (the document's helpers and tables it reaches,
|
|
//! then its declarations and functions), read from `$KC1_KERNELS` or
|
|
//! `local/agent_state/edits/scratch/KC1/kernels`:
|
|
//! sk_lines.splash stack.motion.splash's sk_lines (341712 segments)
|
|
//! k_myc.splash room.motion.splash's k_myc (20007 hairlines, 8 sin per warp)
|
|
//! k_room.splash room.motion.splash's k_room (8162 elements, no Rust twin)
|
|
//! The fbm heightfield (noise) is inline.
|
|
//!
|
|
//! cargo run --release -p makepad-script-compute --example bench_codegen
|
|
//! (KC1_ASM=dir writes each kernel's scalar and NEON code as .s files.)
|
|
|
|
// The Rust twins of the plate kernels are written with NEON (aarch64).
|
|
#[cfg(target_arch = "aarch64")]
|
|
mod bench {
|
|
use makepad_script_compute_sched::kernel::KernelExt;
|
|
use makepad_script_compute::boundary::{LayoutFieldView, LayoutView};
|
|
use makepad_script_compute::kernel::{compile_with, Access, FieldTy, Kernel, KernelRef};
|
|
use makepad_script_compute::Backend;
|
|
use std::collections::HashMap;
|
|
|
|
use std::time::Instant;
|
|
|
|
fn segment() -> LayoutView<'static> {
|
|
const FIELDS: &[LayoutFieldView<'static>] = &[
|
|
LayoutFieldView { name: "a", ty: FieldTy::Vec3, offset: 0 },
|
|
LayoutFieldView { name: "b", ty: FieldTy::Vec3, offset: 3 },
|
|
LayoutFieldView { name: "color", ty: FieldTy::Vec4, offset: 6 },
|
|
LayoutFieldView { name: "width", ty: FieldTy::F32, offset: 10 },
|
|
];
|
|
LayoutView { name: "Segment", stride: 11, fields: FIELDS }
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The kernel maths in Rust (lower.rs Builder: sincos, exp, poly; min/max as
|
|
// selects; floor-to-int; Euclidean wrap)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[inline(always)]
|
|
fn mn(a: f32, b: f32) -> f32 {
|
|
if a < b {
|
|
a
|
|
} else {
|
|
b
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
fn mx(a: f32, b: f32) -> f32 {
|
|
if a > b {
|
|
a
|
|
} else {
|
|
b
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
fn poly(x: f32, c: &[f32]) -> f32 {
|
|
let mut acc = c[0];
|
|
for k in &c[1..] {
|
|
acc = acc * x + *k;
|
|
}
|
|
acc
|
|
}
|
|
#[inline(always)]
|
|
fn sincos(x: f32, cos: bool) -> f32 {
|
|
const FOPI: f32 = 1.273_239_5;
|
|
const DP1: f32 = 0.785_156_25;
|
|
const DP2: f32 = 2.418_756_5e-4;
|
|
const DP3: f32 = 3.774_895e-8;
|
|
let ax = x.abs();
|
|
let mut j = (ax * FOPI) as i32;
|
|
j = j.wrapping_add(j & 1);
|
|
let y = j as f32;
|
|
let j = j & 7;
|
|
let flip = j > 3;
|
|
let j = if flip { j - 4 } else { j };
|
|
let r = ax - y * DP1;
|
|
let r = r - y * DP2;
|
|
let r = r - y * DP3;
|
|
let z = r * r;
|
|
let sp = poly(z, &[-1.951_529_6e-4, 8.332_161e-3, -1.666_665_5e-1]) * z * r + r;
|
|
let cp = poly(z, &[2.443_315_7e-5, -1.388_731_6e-3, 4.166_664_6e-2]) * z * z - z * 0.5 + 1.0;
|
|
let mid = j == 1 || j == 2;
|
|
let (v, neg) = if cos { (if mid { sp } else { cp }, flip != (j > 1)) } else { (if mid { cp } else { sp }, flip != (x < 0.0)) };
|
|
if neg {
|
|
-v
|
|
} else {
|
|
v
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
fn sin(x: f32) -> f32 {
|
|
sincos(x, false)
|
|
}
|
|
#[inline(always)]
|
|
fn cos(x: f32) -> f32 {
|
|
sincos(x, true)
|
|
}
|
|
#[inline(always)]
|
|
fn exp(x: f32) -> f32 {
|
|
let x = mn(mx(x, -87.3), 88.0);
|
|
let fx = (x * std::f32::consts::LOG2_E + 0.5).floor();
|
|
let x = x - fx * 0.693_359_4;
|
|
let x = x - fx * -2.121_944_4e-4;
|
|
let z = x * x;
|
|
let p = poly(x, &[1.987_569_1e-4, 1.398_199_9e-3, 8.333_452e-3, 4.166_579_6e-2, 1.666_666_5e-1, 5.000_000_1e-1]);
|
|
let p = p * z + x + 1.0;
|
|
let n = (fx as i32).wrapping_add(127).wrapping_shl(23);
|
|
p * f32::from_bits(n as u32)
|
|
}
|
|
#[inline(always)]
|
|
fn fi(x: f32) -> i32 {
|
|
x.floor() as i32
|
|
}
|
|
/// A table read: Euclidean wrap into the table, as the language defines it.
|
|
#[inline(always)]
|
|
fn tab<T: Copy>(t: &[T], i: i32) -> T {
|
|
t[i.rem_euclid(t.len() as i32) as usize]
|
|
}
|
|
/// A buffer store: the word index clamped to the buffer, as every backend does.
|
|
#[inline(always)]
|
|
fn st(b: &mut [f32], at: usize, v: f32) {
|
|
let n = b.len() - 1;
|
|
b[at.min(n)] = v;
|
|
}
|
|
|
|
/// Numbers of `let NAME = [..]` in kernel text (vecN(..) flattened).
|
|
fn table(text: &str, name: &str) -> Vec<f64> {
|
|
let head = format!("let {} = [", name);
|
|
let s = text.find(&head).unwrap_or_else(|| panic!("no table {}", name)) + head.len();
|
|
let e = s + text[s..].find(']').unwrap();
|
|
text[s..e].replace("vec4(", "").replace("vec3(", "").replace("vec2(", "").replace(')', "").split(',').map(|x| x.trim().parse::<f64>().unwrap()).collect()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// sk_lines (stack)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct SkTables {
|
|
sty: Vec<[f32; 4]>,
|
|
ta: Vec<[f32; 4]>,
|
|
tb: Vec<[f32; 4]>,
|
|
la: Vec<[f32; 3]>,
|
|
lb: Vec<[f32; 3]>,
|
|
loff: Vec<i32>,
|
|
ln: Vec<i32>,
|
|
}
|
|
|
|
fn v4(t: Vec<f64>) -> Vec<[f32; 4]> {
|
|
t.chunks(4).map(|c| [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32]).collect()
|
|
}
|
|
fn v3(t: Vec<f64>) -> Vec<[f32; 3]> {
|
|
t.chunks(3).map(|c| [c[0] as f32, c[1] as f32, c[2] as f32]).collect()
|
|
}
|
|
|
|
fn sk_lines_rust(tb_: &SkTables, p: &HashMap<&str, f32>, out: &mut [f32], count: usize) {
|
|
let g = |n: &str| p[n];
|
|
let (pa_x, pa_y, pa_z, pa_w) = (g("pa_x"), g("pa_y"), g("pa_z"), g("pa_w"));
|
|
let (pb_x, pb_y, pb_z, pb_w) = (g("pb_x"), g("pb_y"), g("pb_z"), g("pb_w"));
|
|
let (pc_x, pc_y, pc_z, pc_w) = (g("pc_x"), g("pc_y"), g("pc_z"), g("pc_w"));
|
|
let (pd_x, pd_y, pd_z, pd_w) = (g("pd_x"), g("pd_y"), g("pd_z"), g("pd_w"));
|
|
let (pe_x, pe_y, pe_z) = (g("pe_x"), g("pe_y"), g("pe_z"));
|
|
for i in 0..count {
|
|
let per_copy = 28476.0f32;
|
|
let fi_ = i as i32 as f32;
|
|
let c = (fi_ / per_copy).floor();
|
|
let r = fi_ - c * per_copy;
|
|
let j = (r / 1582.0).floor();
|
|
let s = fi(r - j * 1582.0);
|
|
let k = pa_x + j;
|
|
let mut alpha = 1.0f32;
|
|
if c >= pa_z {
|
|
alpha = 0.0
|
|
}
|
|
if k > pa_y + 14.0 {
|
|
alpha = 0.0
|
|
}
|
|
let by = 0.0 - k * 7.6;
|
|
let dy = pc_y - by;
|
|
let dz = (pc_x * pc_x + dy * dy + pc_z * pc_z).sqrt();
|
|
let fog_k = exp((0.0 - mx(0.0, dz - pc_w * 0.85)) / (pc_w * 1.1 * pd_x));
|
|
if fog_k < 0.02 {
|
|
alpha = 0.0
|
|
}
|
|
let mut a = [0.0f32; 3];
|
|
let mut b = [0.0f32; 3];
|
|
let mut col = [0.0f32; 4];
|
|
let mut w = 1.0f32;
|
|
let mut hot = false;
|
|
if s < 1505 {
|
|
let ta = tab(&tb_.ta, s);
|
|
let tb = tab(&tb_.tb, s);
|
|
a = [ta[0], ta[1], ta[2]];
|
|
b = [tb[0], tb[1], tb[2]];
|
|
col = tab(&tb_.sty, fi(ta[3]));
|
|
w = tb[3];
|
|
hot = s >= 1472;
|
|
} else {
|
|
let li = s.wrapping_sub(1505);
|
|
let kk = fi(mn(k, 31.0));
|
|
if li < tab(&tb_.ln, kk) {
|
|
a = tab(&tb_.la, tab(&tb_.loff, kk).wrapping_add(li));
|
|
b = tab(&tb_.lb, tab(&tb_.loff, kk).wrapping_add(li));
|
|
col = [0.332452, 0.309469, 0.274677, 0.9];
|
|
w = 1.1;
|
|
} else {
|
|
alpha = 0.0
|
|
}
|
|
}
|
|
let is_focus = k == pa_y;
|
|
let boost = if is_focus { pb_y } else { 1.0 };
|
|
let mut rgb = [col[0] * boost, col[1] * boost, col[2] * boost];
|
|
if hot && is_focus && pb_z > 0.0 {
|
|
let ymid = (a[1] + b[1]) / 2.0;
|
|
let mut glow = 0.0f32;
|
|
if ymid < pb_w {
|
|
glow = exp(0.0 - (pb_w - ymid) * 0.5) * pb_z
|
|
}
|
|
let t = [3.0f32, 0.2226407, 0.0181465];
|
|
for q in 0..3 {
|
|
rgb[q] = rgb[q] + (t[q] - rgb[q]) * glow;
|
|
}
|
|
}
|
|
if k == 13.0 && pd_y > 0.5 {
|
|
let (cy, sy, cr, sr) = (cos(pd_z), sin(pd_z), cos(pd_w), sin(pd_w));
|
|
let ax1 = a[0] * cr - a[1] * sr;
|
|
let ay1 = a[0] * sr + a[1] * cr;
|
|
a = [ax1 * cy + a[2] * sy + pe_x, ay1 + pe_y, 0.0 - ax1 * sy + a[2] * cy + pe_z];
|
|
let bx1 = b[0] * cr - b[1] * sr;
|
|
let by1 = b[0] * sr + b[1] * cr;
|
|
b = [bx1 * cy + b[2] * sy + pe_x, by1 + pe_y, 0.0 - bx1 * sy + b[2] * cy + pe_z];
|
|
}
|
|
let mut al = col[3] * fog_k * pb_x;
|
|
let mut off = 0.0f32;
|
|
if pa_z > 1.0 {
|
|
off = pa_w * (c / (pa_z - 1.0));
|
|
al = (al / pa_z) * 1.6
|
|
}
|
|
let o = 11 * i;
|
|
let yo = by + off;
|
|
st(out, o, a[0] + 0.0);
|
|
st(out, o + 1, a[1] + yo);
|
|
st(out, o + 2, a[2] + 0.0);
|
|
st(out, o + 3, b[0] + 0.0);
|
|
st(out, o + 4, b[1] + yo);
|
|
st(out, o + 5, b[2] + 0.0);
|
|
st(out, o + 6, rgb[0]);
|
|
st(out, o + 7, rgb[1]);
|
|
st(out, o + 8, rgb[2]);
|
|
st(out, o + 9, al * alpha);
|
|
st(out, o + 10, w);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// k_myc (room): depth clip, projection, cull, 8-sine warp, four emit buffers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct Emit<'a> {
|
|
data: &'a mut [f32],
|
|
cnt: &'a mut [f32],
|
|
cap: usize,
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn emit(e: &mut Emit, n: &mut usize, elem: usize, rec: &[f32; 11], overflow: &mut bool) {
|
|
if *n < e.cap {
|
|
let at = (elem * e.cap + *n) * 11;
|
|
for (k, x) in rec.iter().enumerate() {
|
|
st(e.data, at + k, *x);
|
|
}
|
|
*n += 1;
|
|
} else {
|
|
*overflow = true;
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn k_myc_rust(myc: &[f32], p: &HashMap<&str, f32>, time: f32, bufs: [&mut Emit; 4], start: usize, count: usize) -> bool {
|
|
let g = |n: &str| p[n];
|
|
let c_p = [g("c_px"), g("c_py"), g("c_pz")];
|
|
let c_r = [g("c_rx"), g("c_ry"), g("c_rz")];
|
|
let c_u = [g("c_ux"), g("c_uy"), g("c_uz")];
|
|
let c_f = [g("c_fx"), g("c_fy"), g("c_fz")];
|
|
let (c_ff, c_wa, c_wt, b_th, b_sd, m_front) = (g("c_ff"), g("c_wa"), g("c_wt"), g("b_th"), g("b_sd"), g("m_front"));
|
|
let [ib, if_, gb, gf] = bufs;
|
|
let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
let sub = |a: [f32; 3], b: [f32; 3]| [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
let depth = |x: [f32; 3]| dot(sub(x, c_p), c_f);
|
|
let proj = |x: [f32; 3]| {
|
|
let rel = sub(x, c_p);
|
|
let z = dot(rel, c_f);
|
|
[960.0 + c_ff * dot(rel, c_r) / z, 540.0 - c_ff * dot(rel, c_u) / z]
|
|
};
|
|
let warp = |x: f32, y: f32| {
|
|
if c_wa <= 0.0 {
|
|
return [x, y];
|
|
}
|
|
let t = c_wt;
|
|
let dx = sin(y * 0.0105 + t * 3.1 + sin(x * 0.006 - t * 1.7) * 1.6) + 0.5 * sin((x + y) * 0.019 - t * 4.3);
|
|
let dy = sin(x * 0.0093 - t * 2.6 + sin(y * 0.008 + t * 1.3) * 1.4) + 0.5 * sin((x - y) * 0.017 + t * 3.7);
|
|
[x + dx * c_wa, y + dy * c_wa]
|
|
};
|
|
let front = |x: [f32; 3]| {
|
|
let n = [0.0 * b_sd, (0.0 - sin(b_th)) * b_sd, cos(b_th) * b_sd];
|
|
dot(sub(x, [0.0, 3.0, (0.0f64 - 7.55) as f32]), n) > 0.0
|
|
};
|
|
let t_land = (28.873f64 - 0.02) as f32;
|
|
let mut overflow = false;
|
|
for i in start..count {
|
|
let (mut nib, mut nif, mut ngb, mut ngf) = (0usize, 0usize, 0usize, 0usize);
|
|
let tt = time + 26.146;
|
|
let o = i as i32 as f32 * 7.0;
|
|
let bd = tab(myc, fi(o + 6.0));
|
|
if tt >= t_land && bd <= m_front {
|
|
let tip = exp((0.0 - (m_front - bd)) / 0.3);
|
|
let (c1, c2) = ([0.5300954f32, 0.5052049, 0.4575045], [0.652351f32, 0.95, 0.04292689]);
|
|
let mt = 0.15 + 0.85 * tip;
|
|
let col = [c1[0] + (c2[0] - c1[0]) * mt, c1[1] + (c2[1] - c1[1]) * mt, c1[2] + (c2[2] - c1[2]) * mt];
|
|
let gk = 0.7 * tip;
|
|
let gcol = [0.6866853 * gk, 1.0 * gk, 0.0451862 * gk];
|
|
let s_a = [tab(myc, fi(o)), tab(myc, fi(o + 1.0)), tab(myc, fi(o + 2.0))];
|
|
let s_b = [tab(myc, fi(o + 3.0)), tab(myc, fi(o + 4.0)), tab(myc, fi(o + 5.0))];
|
|
for pass in 0..2 {
|
|
if pass == 1 && !(tip > 0.25) {
|
|
break;
|
|
}
|
|
let (mut ca, mut cb) = (s_a, s_b);
|
|
let (da, db) = (depth(ca), depth(cb));
|
|
if da >= 0.1 || db >= 0.1 {
|
|
if da < 0.1 {
|
|
let u = (0.1 - da) / (db - da);
|
|
ca = [ca[0] + (cb[0] - ca[0]) * u, ca[1] + (cb[1] - ca[1]) * u, ca[2] + (cb[2] - ca[2]) * u];
|
|
} else if db < 0.1 {
|
|
let u = (0.1 - db) / (da - db);
|
|
cb = [cb[0] + (ca[0] - cb[0]) * u, cb[1] + (ca[1] - cb[1]) * u, cb[2] + (ca[2] - cb[2]) * u];
|
|
}
|
|
let pa = proj(ca);
|
|
let pb = proj(cb);
|
|
let cull = (pa[0] < -250.0 && pb[0] < -250.0) || (pa[0] > 2170.0 && pb[0] > 2170.0) || (pa[1] < -250.0 && pb[1] < -250.0) || (pa[1] > 1330.0 && pb[1] > 1330.0);
|
|
if !cull {
|
|
let wa = warp(pa[0], pa[1]);
|
|
let wb = warp(pb[0], pb[1]);
|
|
let (ww, c) = if pass == 0 { (0.9f64, col) } else { (1.8f64, gcol) };
|
|
let aa = (0.8 * (ww / 0.7).min(1.0)) as f32;
|
|
let rec = [wa[0], 0.0 - wa[1], 0.0, wb[0], 0.0 - wb[1], 0.0, c[0], c[1], c[2], aa, ww as f32];
|
|
match (pass, front(s_a)) {
|
|
(0, true) => emit(if_, &mut nif, i, &rec, &mut overflow),
|
|
(0, false) => emit(ib, &mut nib, i, &rec, &mut overflow),
|
|
(_, true) => emit(gf, &mut ngf, i, &rec, &mut overflow),
|
|
(_, false) => emit(gb, &mut ngb, i, &rec, &mut overflow),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Counts in buffer order (ib, if_, gb, gf), as the kernel stores them.
|
|
let n = ib.cnt.len() - 1;
|
|
ib.cnt[i.min(n)] = f32::from_bits(nib as u32);
|
|
if_.cnt[i.min(n)] = f32::from_bits(nif as u32);
|
|
gb.cnt[i.min(n)] = f32::from_bits(ngb as u32);
|
|
gf.cnt[i.min(n)] = f32::from_bits(ngf as u32);
|
|
}
|
|
overflow
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// fbm heightfield (noise): the prelude's hash, gradient noise and fbm2
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const HEIGHTS: &str = r#"
|
|
let W = 1024
|
|
let base = input(f32)
|
|
let pos = output(vec3)
|
|
let hgt = output(f32)
|
|
let amp = param(6.0)
|
|
fn vertex(i) {
|
|
let x = float(i % W)
|
|
let z = float(i / W)
|
|
let h = base[i] + fbm2(vec2(x, z) * 0.01, 4, 2.0, 0.5) * amp
|
|
pos[i] = vec3(x, h, z)
|
|
hgt[i] = h
|
|
}
|
|
"#;
|
|
|
|
#[inline(always)]
|
|
fn hash(x: i32) -> i32 {
|
|
let s = x.wrapping_mul(747796405).wrapping_add(0xAC564B05u32 as i32);
|
|
let w = (((s as u32) >> (((s as u32) >> 28).wrapping_add(4) & 31)) as i32 ^ s).wrapping_mul(277803737);
|
|
((w as u32) >> 22) as i32 ^ w
|
|
}
|
|
#[inline(always)]
|
|
fn hash2(a: i32, b: i32) -> i32 {
|
|
hash(a ^ hash(b.wrapping_add(0x9E3779B9u32 as i32)))
|
|
}
|
|
const GRAD2: [f32; 16] = [1.0, 0.0, 0.70710678, 0.70710678, 0.0, 1.0, -0.70710678, 0.70710678, -1.0, 0.0, -0.70710678, -0.70710678, 0.0, -1.0, 0.70710678, -0.70710678];
|
|
#[inline(always)]
|
|
fn grad2(x: i32, y: i32, fx: f32, fy: f32) -> f32 {
|
|
let h = (hash2(x, y) & 7).wrapping_mul(2);
|
|
fx * GRAD2[(h & 15) as usize] + fy * GRAD2[(h.wrapping_add(1) & 15) as usize]
|
|
}
|
|
#[inline(always)]
|
|
fn fade(t: f32) -> f32 {
|
|
t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
|
|
}
|
|
#[inline(always)]
|
|
fn gnoise2(px: f32, py: f32) -> f32 {
|
|
let (ix, iy) = (px.floor(), py.floor());
|
|
let (fx, fy) = (px - ix, py - iy);
|
|
let (x, y) = (fi(ix), fi(iy));
|
|
let (u, v) = (fade(fx), fade(fy));
|
|
let g00 = grad2(x, y, fx, fy);
|
|
let a = g00 + (grad2(x.wrapping_add(1), y, fx - 1.0, fy) - g00) * u;
|
|
let g01 = grad2(x, y.wrapping_add(1), fx, fy - 1.0);
|
|
let b = g01 + (grad2(x.wrapping_add(1), y.wrapping_add(1), fx - 1.0, fy - 1.0) - g01) * u;
|
|
(a + (b - a) * v) * 1.4142
|
|
}
|
|
fn heights_rust(base: &[f32], amp: f32, pos: &mut [f32], hgt: &mut [f32], count: usize) {
|
|
for i in 0..count {
|
|
let ii = i as i32;
|
|
let x = (ii % 1024) as f32;
|
|
let z = (ii / 1024) as f32;
|
|
let (mut qx, mut qy) = (x * 0.01, z * 0.01);
|
|
let (mut s, mut a) = (0.0f32, 0.5f32);
|
|
for _ in 0..4 {
|
|
s = s + a * gnoise2(qx, qy);
|
|
qx = qx * 2.0;
|
|
qy = qy * 2.0;
|
|
a = a * 0.5;
|
|
}
|
|
let h = base[i.min(base.len() - 1)] + s * amp;
|
|
st(pos, 3 * i, x);
|
|
st(pos, 3 * i + 1, h);
|
|
st(pos, 3 * i + 2, z);
|
|
st(hgt, i, h);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// "rustc fast" twins: the same kernels with the freedom the kernel compiler
|
|
// takes -- fused multiply-adds anywhere, four elements at a time with
|
|
// explicit NEON (LLVM does not vectorize these loops: branches, gathers),
|
|
// TBL for the noise table, invariants hoisted by hand. Not bit-equal to
|
|
// the kernels (different fusion points); compared within tolerance.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(target_arch = "aarch64")]
|
|
mod fast {
|
|
use super::{fi, mn, mx, st, tab, SkTables};
|
|
use std::arch::aarch64::*;
|
|
use std::collections::HashMap;
|
|
|
|
type F = float32x4_t;
|
|
type I = int32x4_t;
|
|
type M = uint32x4_t;
|
|
|
|
#[inline(always)]
|
|
unsafe fn s(x: f32) -> F {
|
|
vdupq_n_f32(x)
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn si(x: i32) -> I {
|
|
vdupq_n_s32(x)
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn poly(x: F, c: &[f32]) -> F {
|
|
let mut acc = s(c[0]);
|
|
for k in &c[1..] {
|
|
acc = vfmaq_f32(s(*k), acc, x);
|
|
}
|
|
acc
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn sel(m: M, a: F, b: F) -> F {
|
|
vbslq_f32(m, a, b)
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn any(m: M) -> bool {
|
|
vmaxvq_u32(m) != 0
|
|
}
|
|
#[inline(always)]
|
|
pub unsafe fn sincos(x: F, cos: bool) -> F {
|
|
let ax = vabsq_f32(x);
|
|
let mut j = vcvtq_s32_f32(vmulq_f32(ax, s(1.273_239_5)));
|
|
j = vaddq_s32(j, vandq_s32(j, si(1)));
|
|
let y = vcvtq_f32_s32(j);
|
|
let j = vandq_s32(j, si(7));
|
|
let flip = vcgtq_s32(j, si(3));
|
|
let j = vbslq_s32(flip, vsubq_s32(j, si(4)), j);
|
|
let r = vfmsq_f32(ax, y, s(0.785_156_25));
|
|
let r = vfmsq_f32(r, y, s(2.418_756_5e-4));
|
|
let r = vfmsq_f32(r, y, s(3.774_895e-8));
|
|
let z = vmulq_f32(r, r);
|
|
let sp = vfmaq_f32(r, vmulq_f32(poly(z, &[-1.951_529_6e-4, 8.332_161e-3, -1.666_665_5e-1]), z), r);
|
|
let cp = vfmaq_f32(vfmsq_f32(s(1.0), z, s(0.5)), vmulq_f32(poly(z, &[2.443_315_7e-5, -1.388_731_6e-3, 4.166_664_6e-2]), z), z);
|
|
let mid = vorrq_u32(vceqq_s32(j, si(1)), vceqq_s32(j, si(2)));
|
|
let (v, neg) = if cos {
|
|
(sel(mid, sp, cp), veorq_u32(flip, vcgtq_s32(j, si(1))))
|
|
} else {
|
|
(sel(mid, cp, sp), veorq_u32(flip, vcltq_f32(x, s(0.0))))
|
|
};
|
|
sel(neg, vnegq_f32(v), v)
|
|
}
|
|
#[inline(always)]
|
|
pub unsafe fn sin(x: F) -> F {
|
|
sincos(x, false)
|
|
}
|
|
#[inline(always)]
|
|
pub unsafe fn exp(x: F) -> F {
|
|
let x = vminq_f32(vmaxq_f32(x, s(-87.3)), s(88.0));
|
|
let fx = vrndmq_f32(vfmaq_f32(s(0.5), x, s(std::f32::consts::LOG2_E)));
|
|
let x = vfmsq_f32(x, fx, s(0.693_359_4));
|
|
let x = vfmsq_f32(x, fx, s(-2.121_944_4e-4));
|
|
let z = vmulq_f32(x, x);
|
|
let p = poly(x, &[1.987_569_1e-4, 1.398_199_9e-3, 8.333_452e-3, 4.166_579_6e-2, 1.666_666_5e-1, 5.000_000_1e-1]);
|
|
let p = vaddq_f32(vfmaq_f32(x, p, z), s(1.0));
|
|
let n = vshlq_n_s32(vaddq_s32(vcvtq_s32_f32(fx), si(127)), 23);
|
|
vmulq_f32(p, vreinterpretq_f32_s32(n))
|
|
}
|
|
unsafe fn lanes(v: F) -> [f32; 4] {
|
|
let mut a = [0.0f32; 4];
|
|
vst1q_f32(a.as_mut_ptr(), v);
|
|
a
|
|
}
|
|
unsafe fn lanes_i(v: I) -> [i32; 4] {
|
|
let mut a = [0i32; 4];
|
|
vst1q_s32(a.as_mut_ptr(), v);
|
|
a
|
|
}
|
|
unsafe fn lanes_m(v: M) -> [u32; 4] {
|
|
let mut a = [0u32; 4];
|
|
vst1q_u32(a.as_mut_ptr(), v);
|
|
a
|
|
}
|
|
unsafe fn ld(a: &[f32; 4]) -> F {
|
|
vld1q_f32(a.as_ptr())
|
|
}
|
|
|
|
// -- fbm -----------------------------------------------------------------
|
|
#[inline(always)]
|
|
unsafe fn hash(x: I) -> I {
|
|
let sv = vmlaq_s32(si(0xAC564B05u32 as i32), x, si(747796405));
|
|
let su = vreinterpretq_u32_s32(sv);
|
|
let amt = vaddq_u32(vshrq_n_u32(su, 28), vdupq_n_u32(4));
|
|
let sh = vreinterpretq_s32_u32(vshlq_u32(su, vnegq_s32(vreinterpretq_s32_u32(amt))));
|
|
let w = vmulq_s32(veorq_s32(sh, sv), si(277803737));
|
|
veorq_s32(vreinterpretq_s32_u32(vshrq_n_u32(vreinterpretq_u32_s32(w), 22)), w)
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn lookup(t: uint8x16x4_t, idx: I) -> F {
|
|
let b = vaddq_s32(vmulq_s32(idx, si(0x0404_0404)), si(0x0302_0100));
|
|
vreinterpretq_f32_u8(vqtbl4q_u8(t, vreinterpretq_u8_s32(b)))
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn grad2(t: uint8x16x4_t, x: I, y: I, fx: F, fy: F) -> F {
|
|
let h = vshlq_n_s32(vandq_s32(hash(veorq_s32(x, hash(vaddq_s32(y, si(0x9E3779B9u32 as i32))))), si(7)), 1);
|
|
vfmaq_f32(vmulq_f32(fx, lookup(t, h)), fy, lookup(t, vaddq_s32(h, si(1))))
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn fade(t: F) -> F {
|
|
vmulq_f32(vmulq_f32(vmulq_f32(t, t), t), vfmaq_f32(s(10.0), t, vfmsq_f32(vmulq_f32(t, s(6.0)), s(15.0), s(1.0))))
|
|
}
|
|
#[inline(always)]
|
|
unsafe fn gnoise2(t: uint8x16x4_t, px: F, py: F) -> F {
|
|
let (ix, iy) = (vrndmq_f32(px), vrndmq_f32(py));
|
|
let (fx, fy) = (vsubq_f32(px, ix), vsubq_f32(py, iy));
|
|
let (x, y) = (vcvtq_s32_f32(ix), vcvtq_s32_f32(iy));
|
|
let (u, v) = (fade(fx), fade(fy));
|
|
let one = si(1);
|
|
let (fx1, fy1) = (vsubq_f32(fx, s(1.0)), vsubq_f32(fy, s(1.0)));
|
|
let g00 = grad2(t, x, y, fx, fy);
|
|
let a = vfmaq_f32(g00, vsubq_f32(grad2(t, vaddq_s32(x, one), y, fx1, fy), g00), u);
|
|
let g01 = grad2(t, x, vaddq_s32(y, one), fx, fy1);
|
|
let b = vfmaq_f32(g01, vsubq_f32(grad2(t, vaddq_s32(x, one), vaddq_s32(y, one), fx1, fy1), g01), u);
|
|
vmulq_f32(vfmaq_f32(a, vsubq_f32(b, a), v), s(1.4142))
|
|
}
|
|
pub fn heights(base: &[f32], amp: f32, pos: &mut [f32], hgt: &mut [f32], count: usize) {
|
|
let n4 = count & !3;
|
|
unsafe {
|
|
let g = super::GRAD2;
|
|
let t = vld4q_u8_x(g.as_ptr() as *const u8);
|
|
let mut i = 0;
|
|
while i < n4 {
|
|
let iv = vaddq_s32(si(i as i32), vld1q_s32([0, 1, 2, 3].as_ptr()));
|
|
let x = vcvtq_f32_s32(vandq_s32(iv, si(1023)));
|
|
let z = vcvtq_f32_s32(vshrq_n_s32(iv, 10));
|
|
let (mut qx, mut qy) = (vmulq_f32(x, s(0.01)), vmulq_f32(z, s(0.01)));
|
|
let mut sum = s(0.0);
|
|
let mut a = 0.5f32;
|
|
for _ in 0..4 {
|
|
sum = vfmaq_f32(sum, s(a), gnoise2(t, qx, qy));
|
|
qx = vmulq_f32(qx, s(2.0));
|
|
qy = vmulq_f32(qy, s(2.0));
|
|
a *= 0.5;
|
|
}
|
|
// Clamped reads and writes: the last group is inside (n4 <= len).
|
|
let b = if i + 3 < base.len() { vld1q_f32(base.as_ptr().add(i)) } else { ld(&[0.0; 4]) };
|
|
let h = vfmaq_f32(b, sum, s(amp));
|
|
if 3 * i + 11 < pos.len() && i + 3 < hgt.len() {
|
|
vst3q_f32(pos.as_mut_ptr().add(3 * i), float32x4x3_t(x, h, z));
|
|
vst1q_f32(hgt.as_mut_ptr().add(i), h);
|
|
}
|
|
i += 4;
|
|
}
|
|
}
|
|
// The tail: scalar.
|
|
if n4 < count {
|
|
let mut p2 = vec![0.0f32; 3 * count];
|
|
let mut h2 = vec![0.0f32; count];
|
|
super::heights_rust(base, amp, &mut p2, &mut h2, count);
|
|
pos[3 * n4..3 * count].copy_from_slice(&p2[3 * n4..]);
|
|
hgt[n4..count].copy_from_slice(&h2[n4..]);
|
|
}
|
|
}
|
|
unsafe fn vld4q_u8_x(p: *const u8) -> uint8x16x4_t {
|
|
uint8x16x4_t(vld1q_u8(p), vld1q_u8(p.add(16)), vld1q_u8(p.add(32)), vld1q_u8(p.add(48)))
|
|
}
|
|
|
|
// -- sk_lines ------------------------------------------------------------
|
|
pub fn sk_lines(tb_: &SkTables, p: &HashMap<&str, f32>, out: &mut [f32], count: usize) {
|
|
let g = |n: &str| p[n];
|
|
let (pa_x, pa_y, pa_z, pa_w) = (g("pa_x"), g("pa_y"), g("pa_z"), g("pa_w"));
|
|
let (pb_x, pb_y, pb_z, pb_w) = (g("pb_x"), g("pb_y"), g("pb_z"), g("pb_w"));
|
|
let (pc_x, pc_y, pc_z, pc_w) = (g("pc_x"), g("pc_y"), g("pc_z"), g("pc_w"));
|
|
let (pd_x, pd_y, pd_z, pd_w) = (g("pd_x"), g("pd_y"), g("pd_z"), g("pd_w"));
|
|
let (pe_x, pe_y, pe_z) = (g("pe_x"), g("pe_y"), g("pe_z"));
|
|
// Hoisted by hand.
|
|
let (cy, sy, cr, sr) = (super::cos(pd_z), super::sin(pd_z), super::cos(pd_w), super::sin(pd_w));
|
|
let fog_den = pc_w * 1.1 * pd_x;
|
|
let pcx2z2 = pc_x * pc_x;
|
|
let n4 = count & !3;
|
|
unsafe {
|
|
let mut i = 0;
|
|
while i < n4 {
|
|
let fiv = vcvtq_f32_s32(vaddq_s32(si(i as i32), vld1q_s32([0, 1, 2, 3].as_ptr())));
|
|
let per = s(28476.0);
|
|
let c = vrndmq_f32(vdivq_f32(fiv, per));
|
|
let r = vfmsq_f32(fiv, c, per);
|
|
let j = vrndmq_f32(vdivq_f32(r, s(1582.0)));
|
|
let sv = vcvtq_s32_f32(vrndmq_f32(vfmsq_f32(r, j, s(1582.0))));
|
|
let k = vaddq_f32(s(pa_x), j);
|
|
let mut alpha = s(1.0);
|
|
alpha = sel(vcgeq_f32(c, s(pa_z)), s(0.0), alpha);
|
|
alpha = sel(vcgtq_f32(k, s(pa_y + 14.0)), s(0.0), alpha);
|
|
let by = vfmsq_f32(s(0.0), k, s(7.6));
|
|
let dy = vsubq_f32(s(pc_y), by);
|
|
let dz = vsqrtq_f32(vfmaq_f32(vfmaq_f32(s(pcx2z2), dy, dy), s(pc_z), s(pc_z)));
|
|
let fog = exp(vdivq_f32(vsubq_f32(s(0.0), vmaxq_f32(s(0.0), vfmsq_f32(dz, s(pc_w), s(0.85)))), s(fog_den)));
|
|
alpha = sel(vcltq_f32(fog, s(0.02)), s(0.0), alpha);
|
|
// Per-lane table reads.
|
|
let (sl, kl) = (lanes_i(sv), lanes(k));
|
|
let (mut a, mut b, mut col) = ([[0.0f32; 4]; 3], [[0.0f32; 4]; 3], [[0.0f32; 4]; 4]);
|
|
let (mut w, mut hot, mut am) = ([1.0f32; 4], [0u32; 4], [1.0f32; 4]);
|
|
for l in 0..4 {
|
|
let sx = sl[l];
|
|
if sx < 1505 {
|
|
let ta = tab(&tb_.ta, sx);
|
|
let tb = tab(&tb_.tb, sx);
|
|
let cl = tab(&tb_.sty, fi(ta[3]));
|
|
for q in 0..3 {
|
|
a[q][l] = ta[q];
|
|
b[q][l] = tb[q];
|
|
}
|
|
for q in 0..4 {
|
|
col[q][l] = cl[q];
|
|
}
|
|
w[l] = tb[3];
|
|
hot[l] = if sx >= 1472 { !0 } else { 0 };
|
|
} else {
|
|
let li = sx.wrapping_sub(1505);
|
|
let kk = fi(mn(kl[l], 31.0));
|
|
if li < tab(&tb_.ln, kk) {
|
|
let o = tab(&tb_.loff, kk).wrapping_add(li);
|
|
let (la, lb) = (tab(&tb_.la, o), tab(&tb_.lb, o));
|
|
for q in 0..3 {
|
|
a[q][l] = la[q];
|
|
b[q][l] = lb[q];
|
|
}
|
|
col[0][l] = 0.332452;
|
|
col[1][l] = 0.309469;
|
|
col[2][l] = 0.274677;
|
|
col[3][l] = 0.9;
|
|
w[l] = 1.1;
|
|
} else {
|
|
am[l] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
alpha = vmulq_f32(alpha, ld(&am));
|
|
let (mut ax, mut ay, mut az) = (ld(&a[0]), ld(&a[1]), ld(&a[2]));
|
|
let (mut bx, mut byy, mut bz) = (ld(&b[0]), ld(&b[1]), ld(&b[2]));
|
|
let focus = vceqq_f32(k, s(pa_y));
|
|
let boost = sel(focus, s(pb_y), s(1.0));
|
|
let mut rgb = [vmulq_f32(ld(&col[0]), boost), vmulq_f32(ld(&col[1]), boost), vmulq_f32(ld(&col[2]), boost)];
|
|
if pb_z > 0.0 {
|
|
let m = vandq_u32(vld1q_u32(hot.as_ptr()), focus);
|
|
if any(m) {
|
|
let ymid = vmulq_f32(vaddq_f32(ay, byy), s(0.5));
|
|
let glow = sel(vcltq_f32(ymid, s(pb_w)), vmulq_f32(exp(vmulq_f32(vsubq_f32(ymid, s(pb_w)), s(0.5))), s(pb_z)), s(0.0));
|
|
let glow = sel(m, glow, s(0.0));
|
|
let t = [3.0f32, 0.2226407, 0.0181465];
|
|
for q in 0..3 {
|
|
rgb[q] = vfmaq_f32(rgb[q], vsubq_f32(s(t[q]), rgb[q]), glow);
|
|
}
|
|
}
|
|
}
|
|
if pd_y > 0.5 {
|
|
let m = vceqq_f32(k, s(13.0));
|
|
if any(m) {
|
|
let ax1 = vfmsq_f32(vmulq_f32(ax, s(cr)), ay, s(sr));
|
|
let ay1 = vfmaq_f32(vmulq_f32(ax, s(sr)), ay, s(cr));
|
|
let nax = vaddq_f32(vfmaq_f32(vmulq_f32(ax1, s(cy)), az, s(sy)), s(pe_x));
|
|
let nay = vaddq_f32(ay1, s(pe_y));
|
|
let naz = vaddq_f32(vfmaq_f32(vmulq_f32(vnegq_f32(ax1), s(sy)), az, s(cy)), s(pe_z));
|
|
let bx1 = vfmsq_f32(vmulq_f32(bx, s(cr)), byy, s(sr));
|
|
let by1 = vfmaq_f32(vmulq_f32(bx, s(sr)), byy, s(cr));
|
|
let nbx = vaddq_f32(vfmaq_f32(vmulq_f32(bx1, s(cy)), bz, s(sy)), s(pe_x));
|
|
let nby = vaddq_f32(by1, s(pe_y));
|
|
let nbz = vaddq_f32(vfmaq_f32(vmulq_f32(vnegq_f32(bx1), s(sy)), bz, s(cy)), s(pe_z));
|
|
ax = sel(m, nax, ax);
|
|
ay = sel(m, nay, ay);
|
|
az = sel(m, naz, az);
|
|
bx = sel(m, nbx, bx);
|
|
byy = sel(m, nby, byy);
|
|
bz = sel(m, nbz, bz);
|
|
}
|
|
}
|
|
let mut al = vmulq_f32(vmulq_f32(ld(&col[3]), fog), s(pb_x));
|
|
let mut off = s(0.0);
|
|
if pa_z > 1.0 {
|
|
off = vmulq_f32(s(pa_w), vdivq_f32(c, s(pa_z - 1.0)));
|
|
al = vmulq_f32(vdivq_f32(al, s(pa_z)), s(1.6));
|
|
}
|
|
let yo = vaddq_f32(by, off);
|
|
let words = [ax, vaddq_f32(ay, yo), az, bx, vaddq_f32(byy, yo), bz, rgb[0], rgb[1], rgb[2], vmulq_f32(al, alpha), ld(&w)];
|
|
// One bounds check for the four records, then plain stores.
|
|
if 11 * (i + 3) + 10 < out.len() {
|
|
let o = out.as_mut_ptr().add(11 * i);
|
|
for (q, v) in words.iter().enumerate() {
|
|
vst1q_lane_f32::<0>(o.add(q), *v);
|
|
vst1q_lane_f32::<1>(o.add(11 + q), *v);
|
|
vst1q_lane_f32::<2>(o.add(22 + q), *v);
|
|
vst1q_lane_f32::<3>(o.add(33 + q), *v);
|
|
}
|
|
} else {
|
|
for (q, v) in words.iter().enumerate() {
|
|
let x = lanes(*v);
|
|
for l in 0..4 {
|
|
st(out, 11 * (i + l) + q, x[l]);
|
|
}
|
|
}
|
|
}
|
|
i += 4;
|
|
}
|
|
}
|
|
if n4 < count {
|
|
let mut o2 = vec![0.0f32; 11 * count];
|
|
super::sk_lines_rust(tb_, p, &mut o2, count);
|
|
out[11 * n4..11 * count].copy_from_slice(&o2[11 * n4..]);
|
|
}
|
|
}
|
|
|
|
// -- k_myc ---------------------------------------------------------------
|
|
pub fn k_myc(myc: &[f32], p: &HashMap<&str, f32>, time: f32, bufs: [&mut super::Emit; 4], count: usize) {
|
|
let g = |n: &str| p[n];
|
|
let c_p = [g("c_px"), g("c_py"), g("c_pz")];
|
|
let c_r = [g("c_rx"), g("c_ry"), g("c_rz")];
|
|
let c_u = [g("c_ux"), g("c_uy"), g("c_uz")];
|
|
let c_f = [g("c_fx"), g("c_fy"), g("c_fz")];
|
|
let (c_ff, c_wa, c_wt, b_th, b_sd, m_front) = (g("c_ff"), g("c_wa"), g("c_wt"), g("b_th"), g("b_sd"), g("m_front"));
|
|
let [ib, if_, gb, gf] = bufs;
|
|
// Hoisted by hand: the front plane, the warp's time terms.
|
|
let nrm = [0.0 * b_sd, (0.0 - super::sin(b_th)) * b_sd, super::cos(b_th) * b_sd];
|
|
let fo = [0.0f32, 3.0, (0.0f64 - 7.55) as f32];
|
|
let t_land = (28.873f64 - 0.02) as f32;
|
|
let tt = time + 26.146;
|
|
let (t1, t2, t3, t4, t5, t6) = (c_wt * 3.1, c_wt * 1.7, c_wt * 4.3, c_wt * 2.6, c_wt * 1.3, c_wt * 3.7);
|
|
let n4 = count & !3;
|
|
unsafe {
|
|
let dot = |x: [F; 3], c: [f32; 3]| vfmaq_f32(vfmaq_f32(vmulq_f32(x[0], s(c[0])), x[1], s(c[1])), x[2], s(c[2]));
|
|
let rel = |x: [F; 3]| [vsubq_f32(x[0], s(c_p[0])), vsubq_f32(x[1], s(c_p[1])), vsubq_f32(x[2], s(c_p[2]))];
|
|
let warp = |x: F, y: F| -> [F; 2] {
|
|
if c_wa <= 0.0 {
|
|
return [x, y];
|
|
}
|
|
let dx = vfmaq_f32(sin(vfmaq_f32(vfmaq_f32(s(t1), y, s(0.0105)), sin(vfmsq_f32(vmulq_f32(x, s(0.006)), s(t2), s(1.0))), s(1.6))), s(0.5), sin(vfmsq_f32(vmulq_f32(vaddq_f32(x, y), s(0.019)), s(t3), s(1.0))));
|
|
let dy = vfmaq_f32(sin(vfmaq_f32(vfmsq_f32(vmulq_f32(x, s(0.0093)), s(t4), s(1.0)), sin(vfmaq_f32(s(t5), y, s(0.008))), s(1.4))), s(0.5), sin(vfmaq_f32(s(t6), vsubq_f32(x, y), s(0.017))));
|
|
[vfmaq_f32(x, dx, s(c_wa)), vfmaq_f32(y, dy, s(c_wa))]
|
|
};
|
|
let mut i = 0;
|
|
let mut overflow = false;
|
|
while i < n4 {
|
|
let mut cnt = [[0usize; 4]; 4];
|
|
let mut bd = [0.0f32; 4];
|
|
let mut sa = [[0.0f32; 4]; 3];
|
|
let mut sb = [[0.0f32; 4]; 3];
|
|
for l in 0..4 {
|
|
let o = (i + l) as i32 as f32 * 7.0;
|
|
bd[l] = tab(myc, fi(o + 6.0));
|
|
for q in 0..3 {
|
|
sa[q][l] = tab(myc, fi(o + q as f32));
|
|
sb[q][l] = tab(myc, fi(o + 3.0 + q as f32));
|
|
}
|
|
}
|
|
let bdv = ld(&bd);
|
|
let active = vandq_u32(if tt >= t_land { vdupq_n_u32(!0) } else { vdupq_n_u32(0) }, vcleq_f32(bdv, s(m_front)));
|
|
if any(active) {
|
|
let tip = exp(vdivq_f32(vsubq_f32(s(0.0), vsubq_f32(s(m_front), bdv)), s(0.3)));
|
|
let mt = vfmaq_f32(s(0.15), tip, s(0.85));
|
|
let (c1, c2) = ([0.5300954f32, 0.5052049, 0.4575045], [0.652351f32, 0.95, 0.04292689]);
|
|
let col = [vfmaq_f32(s(c1[0]), s(c2[0] - c1[0]), mt), vfmaq_f32(s(c1[1]), s(c2[1] - c1[1]), mt), vfmaq_f32(s(c1[2]), s(c2[2] - c1[2]), mt)];
|
|
let gk = vmulq_f32(tip, s(0.7));
|
|
let gcol = [vmulq_f32(gk, s(0.6866853)), gk, vmulq_f32(gk, s(0.0451862))];
|
|
let sav = [ld(&sa[0]), ld(&sa[1]), ld(&sa[2])];
|
|
let sbv = [ld(&sb[0]), ld(&sb[1]), ld(&sb[2])];
|
|
let front = lanes_m(vcgtq_f32(dot([vsubq_f32(sav[0], s(fo[0])), vsubq_f32(sav[1], s(fo[1])), vsubq_f32(sav[2], s(fo[2]))], nrm), s(0.0)));
|
|
let glow_m = vandq_u32(active, vcgtq_f32(tip, s(0.25)));
|
|
for pass in 0..2 {
|
|
let pm = if pass == 0 { active } else { glow_m };
|
|
if !any(pm) {
|
|
continue;
|
|
}
|
|
let (mut ca, mut cb) = (sav, sbv);
|
|
let da = dot(rel(ca), c_f);
|
|
let db = dot(rel(cb), c_f);
|
|
let vis = vorrq_u32(vcgeq_f32(da, s(0.1)), vcgeq_f32(db, s(0.1)));
|
|
let ma = vcltq_f32(da, s(0.1));
|
|
let mb = vbicq_u32(vcltq_f32(db, s(0.1)), ma);
|
|
let ua = vdivq_f32(vsubq_f32(s(0.1), da), vsubq_f32(db, da));
|
|
let ub = vdivq_f32(vsubq_f32(s(0.1), db), vsubq_f32(da, db));
|
|
for q in 0..3 {
|
|
let na = vfmaq_f32(ca[q], vsubq_f32(cb[q], ca[q]), ua);
|
|
let nb = vfmaq_f32(cb[q], vsubq_f32(ca[q], cb[q]), ub);
|
|
ca[q] = sel(ma, na, ca[q]);
|
|
cb[q] = sel(mb, nb, cb[q]);
|
|
}
|
|
let proj = |x: [F; 3]| {
|
|
let r = rel(x);
|
|
let z = dot(r, c_f);
|
|
[vaddq_f32(s(960.0), vdivq_f32(vmulq_f32(s(c_ff), dot(r, c_r)), z)), vsubq_f32(s(540.0), vdivq_f32(vmulq_f32(s(c_ff), dot(r, c_u)), z))]
|
|
};
|
|
let pa = proj(ca);
|
|
let pb = proj(cb);
|
|
let both = |m1: M, m2: M| vandq_u32(m1, m2);
|
|
let cull = vorrq_u32(
|
|
vorrq_u32(both(vcltq_f32(pa[0], s(-250.0)), vcltq_f32(pb[0], s(-250.0))), both(vcgtq_f32(pa[0], s(2170.0)), vcgtq_f32(pb[0], s(2170.0)))),
|
|
vorrq_u32(both(vcltq_f32(pa[1], s(-250.0)), vcltq_f32(pb[1], s(-250.0))), both(vcgtq_f32(pa[1], s(1330.0)), vcgtq_f32(pb[1], s(1330.0)))),
|
|
);
|
|
let live = vbicq_u32(vandq_u32(pm, vis), cull);
|
|
if !any(live) {
|
|
continue;
|
|
}
|
|
let wa = warp(pa[0], pa[1]);
|
|
let wb = warp(pb[0], pb[1]);
|
|
let (ww, c) = if pass == 0 { (0.9f64, col) } else { (1.8f64, gcol) };
|
|
let aa = (0.8 * (ww / 0.7).min(1.0)) as f32;
|
|
let (wax, way, wbx, wby) = (lanes(wa[0]), lanes(wa[1]), lanes(wb[0]), lanes(wb[1]));
|
|
let (cr_, cg, cbl) = (lanes(c[0]), lanes(c[1]), lanes(c[2]));
|
|
let lv = lanes_m(live);
|
|
for l in 0..4 {
|
|
if lv[l] == 0 {
|
|
continue;
|
|
}
|
|
let rec = [wax[l], 0.0 - way[l], 0.0, wbx[l], 0.0 - wby[l], 0.0, cr_[l], cg[l], cbl[l], aa, ww as f32];
|
|
let (e, which): (&mut super::Emit, usize) = match (pass, front[l] != 0) {
|
|
(0, true) => (&mut *if_, 1),
|
|
(0, false) => (&mut *ib, 0),
|
|
(_, true) => (&mut *gf, 3),
|
|
(_, false) => (&mut *gb, 2),
|
|
};
|
|
super::emit(e, &mut cnt[which][l], i + l, &rec, &mut overflow);
|
|
}
|
|
}
|
|
}
|
|
for l in 0..4 {
|
|
let n = ib.cnt.len() - 1;
|
|
let at = (i + l).min(n);
|
|
ib.cnt[at] = f32::from_bits(cnt[0][l] as u32);
|
|
if_.cnt[at] = f32::from_bits(cnt[1][l] as u32);
|
|
gb.cnt[at] = f32::from_bits(cnt[2][l] as u32);
|
|
gf.cnt[at] = f32::from_bits(cnt[3][l] as u32);
|
|
}
|
|
i += 4;
|
|
}
|
|
let _ = (mx(0.0, 0.0), overflow);
|
|
}
|
|
if n4 < count {
|
|
// The tail through the scalar twin (it writes only its own elements).
|
|
super::k_myc_rust(myc, p, time, [ib, if_, gb, gf], n4, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Harness
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn best(reps: usize, mut f: impl FnMut()) -> f64 {
|
|
(0..reps)
|
|
.map(|_| {
|
|
let t0 = Instant::now();
|
|
f();
|
|
t0.elapsed().as_secs_f64()
|
|
})
|
|
.fold(f64::MAX, f64::min)
|
|
}
|
|
|
|
/// Every buffer a kernel writes, sized for `count` elements.
|
|
fn outputs(k: &Kernel, count: usize) -> Vec<(String, Vec<f32>)> {
|
|
k.buffers()
|
|
.iter()
|
|
.skip(1)
|
|
.filter_map(|b| {
|
|
let words = match b.access {
|
|
Access::Read => return None,
|
|
Access::Write => b.stride as usize * count,
|
|
Access::Emit { width, capacity } => (width * capacity) as usize * count,
|
|
Access::EmitCount => count,
|
|
};
|
|
Some((b.name.to_string(), vec![0.0f32; words.max(1)]))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum How {
|
|
Interp,
|
|
Scalar,
|
|
Neon,
|
|
}
|
|
|
|
fn run_kernel(k: &Kernel, how: How, count: usize, params: &[(&str, f32)], time: f32, inputs: &[(&str, &[f32])], outs: &mut [(String, Vec<f32>)]) {
|
|
let mut call = k.call();
|
|
for (n, v) in params {
|
|
call.set_param(n, *v);
|
|
}
|
|
call.set_time(time);
|
|
for (n, d) in inputs {
|
|
call.input(n, d).unwrap();
|
|
}
|
|
for (n, d) in outs.iter_mut() {
|
|
call.output(n, d).unwrap();
|
|
}
|
|
call.set_simd(how == How::Neon);
|
|
if how == How::Interp {
|
|
call.run_interp(count).unwrap();
|
|
} else {
|
|
call.run(count).unwrap();
|
|
}
|
|
}
|
|
|
|
/// Words that differ, and the largest difference relative to the
|
|
/// buffer's largest magnitude (fused multiply-adds round once where the
|
|
/// twin rounds twice).
|
|
fn ulps(a: &[f32], b: &[f32]) -> (usize, f64) {
|
|
let scale = b.iter().filter(|x| x.is_finite()).fold(0.0f64, |m, x| m.max(x.abs() as f64)).max(1e-30);
|
|
let mut diff = 0;
|
|
let mut worst = 0.0f64;
|
|
for (x, y) in a.iter().zip(b) {
|
|
if x.to_bits() != y.to_bits() {
|
|
diff += 1;
|
|
if !(x.is_nan() && y.is_nan()) {
|
|
worst = worst.max((*x as f64 - *y as f64).abs() / scale);
|
|
}
|
|
}
|
|
}
|
|
(diff, worst)
|
|
}
|
|
|
|
fn asm(dir: &str, name: &str, words: &[u32]) {
|
|
if words.is_empty() {
|
|
return;
|
|
}
|
|
let mut s = String::from(".text\n.globl _k\n_k:\n");
|
|
for w in words {
|
|
s += &format!(" .inst 0x{:08x}\n", w);
|
|
}
|
|
std::fs::write(format!("{}/{}.s", dir, name), s).unwrap();
|
|
}
|
|
|
|
struct Row {
|
|
name: &'static str,
|
|
count: usize,
|
|
ns: [Option<f64>; 5],
|
|
check: String,
|
|
}
|
|
|
|
type Twin<'a> = Option<&'a mut dyn FnMut() -> Vec<(String, Vec<f32>)>>;
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn bench<'a>(name: &'static str, k: &KernelRef, count: usize, params: &[(&str, f32)], time: f32, inputs: &[(&str, &[f32])], rust: Twin<'a>, fast: Twin<'a>, reps: usize) -> Row {
|
|
if let Ok(dir) = std::env::var("KC1_ASM") {
|
|
let (s, v) = k.code_words();
|
|
asm(&dir, &format!("{}_scalar", name), s);
|
|
asm(&dir, &format!("{}_neon", name), v);
|
|
std::fs::write(format!("{}/{}.air", dir, name), format!("{:#?}", k.program())).unwrap();
|
|
}
|
|
let mut ns = [None; 5];
|
|
let mut results: Vec<Vec<(String, Vec<f32>)>> = Vec::new();
|
|
for (slot, how) in [(0, How::Interp), (1, How::Scalar), (2, How::Neon)] {
|
|
if how == How::Neon && !k.simd() {
|
|
continue;
|
|
}
|
|
let n = if how == How::Interp { count.min(20_000) } else { count };
|
|
let mut outs = outputs(k, count);
|
|
let r = if how == How::Interp { 1 } else { reps };
|
|
let t = best(r, || run_kernel(k, how, n, params, time, inputs, &mut outs));
|
|
ns[slot] = Some(t * 1e9 / n as f64);
|
|
if how != How::Interp {
|
|
results.push(outs);
|
|
}
|
|
}
|
|
let mut check = String::new();
|
|
if results.len() == 2 {
|
|
let same = results[0].iter().zip(&results[1]).all(|(a, b)| a.1.iter().zip(&b.1).all(|(x, y)| x.to_bits() == y.to_bits()));
|
|
check += if same { "neon=scalar " } else { "NEON DIFFERS " };
|
|
}
|
|
for (slot, twin) in [(3usize, rust), (4, fast)] {
|
|
let Some(f) = twin else { continue };
|
|
let mut got = Vec::new();
|
|
let t = best(reps, || got = f());
|
|
ns[slot] = Some(t * 1e9 / count as f64);
|
|
if slot == 4 {
|
|
check += "| fast: ";
|
|
}
|
|
for (n, want) in &got {
|
|
let ours = &results[0].iter().find(|(m, _)| m == n).unwrap().1;
|
|
let (d, w) = ulps(ours, want);
|
|
if d == 0 {
|
|
check += &format!("{}: bit-equal ", n);
|
|
} else {
|
|
check += &format!("{}: {}/{} words differ (max {:.1e} of range) ", n, d, want.len(), w);
|
|
}
|
|
}
|
|
}
|
|
Row { name, count, ns, check }
|
|
}
|
|
|
|
pub fn main() {
|
|
let dir = std::env::var("KC1_KERNELS").unwrap_or_else(|_| concat!(env!("CARGO_MANIFEST_DIR"), "/../../../local/agent_state/edits/scratch/KC1/kernels").to_string());
|
|
let reps: usize = std::env::var("KC1_REPS").ok().and_then(|x| x.parse().ok()).unwrap_or(7);
|
|
let only = std::env::var("KC1_ONLY").ok();
|
|
let want = |n: &str| only.as_deref().is_none_or(|o| o.split(',').any(|x| x == n));
|
|
let layouts = [segment()];
|
|
let compile = |text: &str| {
|
|
let t0 = Instant::now();
|
|
let k = compile_with(text, &layouts, Backend::Native).unwrap_or_else(|e| panic!("{:?}", &e[..e.len().min(3)]));
|
|
(k, t0.elapsed().as_secs_f64() * 1e3)
|
|
};
|
|
let mut rows = Vec::new();
|
|
|
|
// -- sk_lines ------------------------------------------------------------
|
|
if want("sk_lines") {
|
|
let text = std::fs::read_to_string(format!("{}/sk_lines.splash", dir)).expect("sk_lines.splash");
|
|
let (k, ms) = compile(&text);
|
|
println!("sk_lines: compiled in {:.0} ms; simd {}; {} vals", ms, k.simd(), k.program().vals.len());
|
|
let tables = SkTables {
|
|
sty: v4(table(&text, "SK_STY")),
|
|
ta: v4(table(&text, "SK_TA")),
|
|
tb: v4(table(&text, "SK_TB")),
|
|
la: v3(table(&text, "SK_LA")),
|
|
lb: v3(table(&text, "SK_LB")),
|
|
loff: table(&text, "SK_LOFF").iter().map(|x| *x as i32).collect(),
|
|
ln: table(&text, "SK_LN").iter().map(|x| *x as i32).collect(),
|
|
};
|
|
// A frame mid-shot: blocks 0..14 from block 3, focus on 8, three
|
|
// smear copies, the disobeying block rolling.
|
|
let params: Vec<(&str, f32)> = vec![
|
|
("pa_x", 3.0), ("pa_y", 8.0), ("pa_z", 3.0), ("pa_w", 0.4),
|
|
("pb_x", 1.0), ("pb_y", 1.6), ("pb_z", 0.8), ("pb_w", -40.0),
|
|
("pc_x", 0.5), ("pc_y", -60.0), ("pc_z", 45.0), ("pc_w", 50.0),
|
|
("pd_x", 1.2), ("pd_y", 1.0), ("pd_z", 0.3), ("pd_w", 0.2),
|
|
("pe_x", 0.5), ("pe_y", 0.2), ("pe_z", -0.3), ("pe_w", 0.0),
|
|
];
|
|
let count = 3 * 28476;
|
|
let pm: HashMap<&str, f32> = params.iter().copied().collect();
|
|
let mut rust = || {
|
|
let mut out = vec![0.0f32; count * 11];
|
|
sk_lines_rust(&tables, &pm, &mut out, count);
|
|
vec![("out".to_string(), out)]
|
|
};
|
|
let mut fastf = || {
|
|
let mut out = vec![0.0f32; count * 11];
|
|
fast::sk_lines(&tables, &pm, &mut out, count);
|
|
vec![("out".to_string(), out)]
|
|
};
|
|
rows.push(bench("sk_lines", &k, count, ¶ms, 0.0, &[], Some(&mut rust), Some(&mut fastf), reps));
|
|
}
|
|
|
|
// -- k_myc ---------------------------------------------------------------
|
|
if want("k_myc") {
|
|
let text = std::fs::read_to_string(format!("{}/k_myc.splash", dir)).expect("k_myc.splash");
|
|
let (k, ms) = compile(&text);
|
|
println!("k_myc: compiled in {:.0} ms; simd {}; {} vals", ms, k.simd(), k.program().vals.len());
|
|
let myc: Vec<f32> = table(&text, "R_MYC").iter().map(|x| *x as f32).collect();
|
|
// A camera 12 units back looking down -z at the room, warp on,
|
|
// the growth front past every strand.
|
|
let params: Vec<(&str, f32)> = vec![
|
|
("c_px", 0.0), ("c_py", 1.5), ("c_pz", 6.0),
|
|
("c_rx", 1.0), ("c_ry", 0.0), ("c_rz", 0.0),
|
|
("c_ux", 0.0), ("c_uy", 1.0), ("c_uz", 0.0),
|
|
("c_fx", 0.0), ("c_fy", 0.0), ("c_fz", -1.0),
|
|
("c_ff", 1100.0), ("c_wa", 3.0), ("c_wt", 31.5), ("c_lk", 0.0),
|
|
("b_th", 0.3), ("b_sd", 1.0), ("c_am", 1.0), ("m_front", 40.0),
|
|
];
|
|
let count = 20007;
|
|
let time = 5.0;
|
|
let pm: HashMap<&str, f32> = params.iter().copied().collect();
|
|
let rust = |use_fast: bool| {
|
|
let mut bufs: Vec<(Vec<f32>, Vec<f32>)> = (0..4).map(|_| (vec![0.0f32; count * 11], vec![0.0f32; count])).collect();
|
|
{
|
|
let [a, b, c, d] = &mut bufs[..] else { unreachable!() };
|
|
let mut e = [
|
|
Emit { data: &mut a.0, cnt: &mut a.1, cap: 1 },
|
|
Emit { data: &mut b.0, cnt: &mut b.1, cap: 1 },
|
|
Emit { data: &mut c.0, cnt: &mut c.1, cap: 1 },
|
|
Emit { data: &mut d.0, cnt: &mut d.1, cap: 1 },
|
|
];
|
|
let [e0, e1, e2, e3] = &mut e;
|
|
if use_fast {
|
|
fast::k_myc(&myc, &pm, time, [e0, e1, e2, e3], count);
|
|
} else {
|
|
k_myc_rust(&myc, &pm, time, [e0, e1, e2, e3], 0, count);
|
|
}
|
|
}
|
|
let names = ["ib", "if_", "gb", "gf"];
|
|
let mut out = Vec::new();
|
|
for (n, (d, c)) in names.iter().zip(bufs) {
|
|
out.push((n.to_string(), d));
|
|
out.push((format!("{}_count", n), c));
|
|
}
|
|
out
|
|
};
|
|
let mut rust_s = || rust(false);
|
|
let mut rust_f = || rust(true);
|
|
rows.push(bench("k_myc", &k, count, ¶ms, time, &[], Some(&mut rust_s), Some(&mut rust_f), reps));
|
|
}
|
|
|
|
// -- k_room (ours only) ------------------------------------------------------
|
|
if want("k_room") {
|
|
if let Ok(text) = std::fs::read_to_string(format!("{}/k_room.splash", dir)) {
|
|
let (k, ms) = compile(&text);
|
|
println!("k_room: compiled in {:.0} ms; simd {}; {} vals", ms, k.simd(), k.program().vals.len());
|
|
let mut params: Vec<(String, f32)> = Vec::new();
|
|
for q in 0..8 {
|
|
for (f, v) in [("px", 0.0), ("py", 1.5), ("pz", 6.0), ("rx", 1.0), ("ry", 0.0), ("rz", 0.0), ("ux", 0.0), ("uy", 1.0), ("uz", 0.0), ("ff", 1100.0), ("wa", 3.0), ("wt", 31.5), ("lk", 0.0), ("am", 1.0)] {
|
|
params.push((format!("q{}_{}", q, f), v));
|
|
}
|
|
}
|
|
params.push(("b_th".into(), 0.3));
|
|
params.push(("b_sd".into(), 1.0));
|
|
let p: Vec<(&str, f32)> = params.iter().map(|(n, v)| (n.as_str(), *v)).collect();
|
|
rows.push(bench("k_room", &k, 8162, &p, 5.0, &[], None, None, reps));
|
|
}
|
|
}
|
|
|
|
// -- fbm heights ---------------------------------------------------------------
|
|
if want("fbm") {
|
|
let (k, ms) = compile(HEIGHTS);
|
|
println!("fbm: compiled in {:.0} ms; simd {}", ms, k.simd());
|
|
let n = 1024 * 256;
|
|
let base: Vec<f32> = (0..n).map(|i| ((i % 97) as f32) * 0.01).collect();
|
|
let mut rust = || {
|
|
let mut pos = vec![0.0f32; n * 3];
|
|
let mut hgt = vec![0.0f32; n];
|
|
heights_rust(&base, 6.0, &mut pos, &mut hgt, n);
|
|
vec![("pos".to_string(), pos), ("hgt".to_string(), hgt)]
|
|
};
|
|
let mut fastf = || {
|
|
let mut pos = vec![0.0f32; n * 3];
|
|
let mut hgt = vec![0.0f32; n];
|
|
fast::heights(&base, 6.0, &mut pos, &mut hgt, n);
|
|
vec![("pos".to_string(), pos), ("hgt".to_string(), hgt)]
|
|
};
|
|
rows.push(bench("fbm", &k, n, &[], 0.0, &[("base", &base)], Some(&mut rust), Some(&mut fastf), reps));
|
|
}
|
|
|
|
println!();
|
|
println!("{:<9} {:>7} {:>9} {:>9} {:>9} {:>9} {:>9} {:>8} {:>8} check", "kernel", "count", "interp", "scalar", "neon x4", "rustc", "rs fast", "vs rs", "vs fast");
|
|
for r in &rows {
|
|
let f = |x: Option<f64>| x.map_or("-".to_string(), |x| format!("{:.2}", x));
|
|
let ours = r.ns[2].or(r.ns[1]);
|
|
let gap = |t: Option<f64>| match (ours, t) {
|
|
(Some(a), Some(b)) => format!("{:.2}x", a / b),
|
|
_ => "-".into(),
|
|
};
|
|
println!("{:<9} {:>7} {:>9} {:>9} {:>9} {:>9} {:>9} {:>8} {:>8} {}", r.name, r.count, f(r.ns[0]), f(r.ns[1]), f(r.ns[2]), f(r.ns[3]), f(r.ns[4]), gap(r.ns[3]), gap(r.ns[4]), r.check);
|
|
}
|
|
println!("(ns per element, one thread, best of {})", reps);
|
|
}
|
|
}
|
|
|
|
#[cfg(target_arch = "aarch64")]
|
|
fn main() {
|
|
bench::main()
|
|
}
|
|
|
|
#[cfg(not(target_arch = "aarch64"))]
|
|
fn main() {
|
|
eprintln!("bench_codegen compares against NEON Rust twins: run it on aarch64");
|
|
}
|