Types and Dispatch

Use Rust generics for data representation and algorithm parameters, and #[magetypes] for ISA specialization. They compose in the same function. define(f32x8) is optional shorthand for the generic vector type with the current Token; it does not replace Rust's function generics.

A generic input type and a const mode

This teaching extraction follows zenanalyze/src/tier1.rs: accumulate_row selects const modes, then incant! calls accumulate_row_simd<const BT601: bool, const FULL: bool, const SKIN: bool, R: ChunkInput>. The production kernel accumulates several image statistics. Here we retain the input-type and luma-coefficient specialization, and compute only the luma sum. The small Pixel trait replaces the production chunk-loader trait so the complete example needs no image framework.

use archmage::prelude::*;

pub trait Pixel: Copy {
    fn rgb(self) -> [f32; 3];
}
impl Pixel for [u8; 3] {
    fn rgb(self) -> [f32; 3] { self.map(f32::from) }
}
impl Pixel for [u8; 4] {
    fn rgb(self) -> [f32; 3] {
        [f32::from(self[0]), f32::from(self[1]), f32::from(self[2])]
    }
}

#[magetypes(define(f32x8), v3, neon, wasm128, scalar)]
fn luma_impl<const BT601: bool, P: Pixel>(
    token: Token, pixels: &[P], weights: [f32; 3],
) -> f64 {
    let [kr, kg, kb] = if BT601 { [0.299, 0.587, 0.114] } else { weights };
    let mut sum = 0.0_f64;
    let (chunks, tail) = pixels.as_chunks::<8>();
    for chunk in chunks {
        let rgb = chunk.map(Pixel::rgb);
        let r = f32x8::from_array_t(token, core::array::from_fn(|i| rgb[i][0]));
        let g = f32x8::from_array_t(token, core::array::from_fn(|i| rgb[i][1]));
        let b = f32x8::from_array_t(token, core::array::from_fn(|i| rgb[i][2]));
        let y = r * f32x8::splat_t(token, kr)
              + g * f32x8::splat_t(token, kg)
              + b * f32x8::splat_t(token, kb);
        sum += f64::from(y.reduce_add());
    }
    for &pixel in tail {
        let [r, g, b] = pixel.rgb();
        sum += f64::from(r * kr + g * kg + b * kb);
    }
    sum
}

pub fn luma_sum<const BT601: bool, P: Pixel>(pixels: &[P], weights: [f32; 3]) -> f64 {
    incant!(luma_impl::<BT601, P>(pixels, weights), [v3, neon, wasm128, scalar])
}

// Values are encoded RGB channel samples; this is a signal statistic,
// not a linear-light color conversion. RGBA alpha is ignored.
let rgb = [[10u8, 20, 30]; 11];
let rgba = [[10u8, 20, 30, 128]; 11];
assert_eq!(luma_sum::<false, _>(&rgb, [1.0, 0.0, 0.0]), 110.0);
assert_eq!(luma_sum::<false, _>(&rgba, [0.0, 1.0, 0.0]), 220.0);
assert!((luma_sum::<true, _>(&rgb, [0.0; 3]) - 199.65).abs() < 0.001);
assert_eq!(luma_sum::<true, [u8; 3]>(&[], [0.0; 3]), 0.0);

P is statically resolved. BT601 selects coefficients at specialization time. The ISA token is independently substituted by the macro. define(f32x8) binds the same generic vector implementation you could spell f32x8::<Token>. There is no trait-object dispatch here. The array construction is a simple teaching loader; production ChunkInput implementations specialize loading and layout too. Verify their assembly rather than assuming this loader is optimal.

Other production combinations

SourceGeneric specialization
zenavif/src/yuv_convert.rs, yuv420_strip_kernel<S: YuvSample, P: StripPixel>u8/u16 input samples and RGB/RGBA output; macro supplies ISA features for ordinary loops
zenpixels-convert/src/scan.rs, fused_cg_impl<const A: bool, const B: bool>Opacity and grayscale checks; caller passes ::<A, B> through incant!
zenpng/src/simd/scan.rs, fused_cg_impl<const A: bool, const B: bool, const C: bool>Adds binary-alpha scanning; same const-plus-ISA arrangement
zenpixels-convert/src/hdr/measure.rs, accumulate_strip_max_rgb_tier<const N: usize>RGB/RGBA stride with alpha ignored
linear-srgb/src/simd.rs → tf/srgb.rsConcrete generated slice loop calls a backend-generic vector-to-vector helper

Specialization multiplies the reachable combinations of tiers, types, and constants. Use const parameters when they remove meaningful work, not for every runtime setting. Inspect cold compile time and code size as well as runtime.

Reusing the backend-generic part

Extract an algorithm generic over T: F32x8Backend when several entries need it. Keep the feature-enabled caller and inspect inlining. The complete zenblend chain and zenfilters helper chain show this. #[magetypes] can call ordinary helpers and other per-tier helpers; it is not restricted to leaf functions.

Found an error or it needs a clarification? Open an issue on GitHub.
Substantiated corrections will be incorporated with attribution.