const LANES: usize = 8;
pub(super) fn dot(window: &[f32], row: &[f32]) -> f32 {
fold(lanes(window, row))
}
pub(super) fn dot_between(window: &[f32], row0: &[f32], row1: &[f32], frac: f32) -> f32 {
let a = lanes(window, row0);
let b = lanes(window, row1);
let mut mixed = [0.0f32; LANES];
for ((m, &a), &b) in mixed.iter_mut().zip(&a).zip(&b) {
*m = (b - a).mul_add(frac, a);
}
fold(mixed)
}
fn lanes(window: &[f32], row: &[f32]) -> [f32; LANES] {
let mut acc = [0.0f32; LANES];
let mut x = window.chunks(LANES);
let mut h = row.chunks(LANES);
while let (Some(x), Some(h)) = (x.next(), h.next()) {
for ((a, &x), &h) in acc.iter_mut().zip(x).zip(h) {
*a = x.mul_add(h, *a);
}
}
acc
}
fn fold(a: [f32; LANES]) -> f32 {
let b = [a[0] + a[4], a[1] + a[5], a[2] + a[6], a[3] + a[7]];
let c = [b[0] + b[2], b[1] + b[3]];
c[0] + c[1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dot_sums_every_tap() {
let x: Vec<f32> = (1..=20).map(|v| v as f32).collect();
let h = vec![1.0f32; 20];
assert_eq!(dot(&x, &h), 210.0);
}
#[test]
fn a_short_row_stops_the_sum() {
let x = [1.0f32, 2.0, 3.0, 4.0];
assert_eq!(dot(&x, &[1.0, 1.0]), 3.0);
}
#[test]
fn lanes_fold_in_ffmpeg_order() {
let tiny = 2.0f32.powi(-24);
let mut x = [0.0f32; 8];
x[0] = 1.0;
x[1] = tiny;
x[5] = tiny;
assert_eq!(dot(&x, &[1.0; 8]), 1.0 + 2.0 * tiny);
}
#[test]
fn between_rows_interpolates() {
let x = [2.0f32; 8];
let lo = [0.0f32; 8];
let hi = [1.0f32; 8];
assert_eq!(dot_between(&x, &lo, &hi, 0.25), 4.0);
assert_eq!(dot_between(&x, &lo, &hi, 0.0), 0.0);
}
}