M4.10: VP9 vectorised loop filter, column-parallel transforms, 16-bit 8-bit MC
The loop filter's lane masks are 0 / -1 integers with bitwise selects and min / max clipping (bool masks and clamp kept it scalar); the 2D inverse transform's column pass runs the generated butterflies on 4 / 8 columns at once; 8-bit sub-pixel filters accumulate in 16-bit lanes (every sum of the VP9 filters fits modulo 2^16). Branch-free bool decoder update. Bit-exact; tested against the per-column transform on random blocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
842801de41
commit
1827b71891
|
|
@ -63,14 +63,12 @@ impl<'a> BoolDecoder<'a> {
|
|||
}
|
||||
let split = 1 + (((self.range - 1) * p as u32) >> 8);
|
||||
let bigsplit = (split as u64) << 56;
|
||||
let bit = if self.value >= bigsplit {
|
||||
self.range -= split;
|
||||
self.value -= bigsplit;
|
||||
true
|
||||
} else {
|
||||
self.range = split;
|
||||
false
|
||||
};
|
||||
// The outcome is close to random: select instead of branching on it.
|
||||
let bit = self.value >= bigsplit;
|
||||
let m = (bit as u64).wrapping_neg();
|
||||
self.value -= bigsplit & m;
|
||||
let m32 = m as u32;
|
||||
self.range = ((self.range - split) & m32) | (split & !m32);
|
||||
let shift = self.range.leading_zeros() - 24;
|
||||
self.range <<= shift;
|
||||
self.value <<= shift;
|
||||
|
|
|
|||
|
|
@ -142,6 +142,18 @@ fn filter_h(s: &[u16], f: &[i16], max: i32, o: &mut [u16]) {
|
|||
#[inline(always)]
|
||||
fn filter_h_n<const W: usize>(s: &[u16], f: &[i16], max: i32, o: &mut [u16]) {
|
||||
let o: &mut [u16; W] = (&mut o[..W]).try_into().expect("row");
|
||||
if max == 255 {
|
||||
let mut acc = [64u16; W];
|
||||
for t in 0..8 {
|
||||
let c = f[t] as u16;
|
||||
let p: &[u16; W] = s[t..t + W].try_into().expect("row");
|
||||
for j in 0..W {
|
||||
acc[j] = acc[j].wrapping_add(c.wrapping_mul(p[j]));
|
||||
}
|
||||
}
|
||||
narrow8(&acc, o);
|
||||
return;
|
||||
}
|
||||
let mut acc = [64i32; W];
|
||||
for t in 0..8 {
|
||||
let c = f[t] as i32;
|
||||
|
|
@ -155,6 +167,18 @@ fn filter_h_n<const W: usize>(s: &[u16], f: &[i16], max: i32, o: &mut [u16]) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Round2(sum, 7) clipped to 8 bits, from 8-bit filter sums kept modulo 2^16 (8 lanes per
|
||||
/// vector instead of 4). With 8-bit samples every sum of the VP9 filters (whose positive taps
|
||||
/// add up to at most 182 and negative taps to at most -54) plus the rounding term 64 lies in
|
||||
/// [-13706, 46474]: wrapped, the non-negative ones stay below 0xC000 and the negative ones
|
||||
/// (which clip to 0) land above it.
|
||||
#[inline(always)]
|
||||
fn narrow8<const W: usize>(acc: &[u16; W], o: &mut [u16; W]) {
|
||||
for j in 0..W {
|
||||
o[j] = if acc[j] < 0xC000 { (acc[j] >> 7).min(255) } else { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical 8-tap filter of one row: `o[j]` from `s[j + t * ss]`, t = 0..8.
|
||||
#[inline(always)]
|
||||
fn filter_v(s: &[u16], ss: usize, f: &[i16], max: i32, o: &mut [u16]) {
|
||||
|
|
@ -176,6 +200,18 @@ fn filter_v(s: &[u16], ss: usize, f: &[i16], max: i32, o: &mut [u16]) {
|
|||
#[inline(always)]
|
||||
fn filter_v_n<const W: usize>(s: &[u16], ss: usize, f: &[i16], max: i32, o: &mut [u16]) {
|
||||
let o: &mut [u16; W] = (&mut o[..W]).try_into().expect("row");
|
||||
if max == 255 {
|
||||
let mut acc = [64u16; W];
|
||||
for t in 0..8 {
|
||||
let c = f[t] as u16;
|
||||
let p: &[u16; W] = s[t * ss..t * ss + W].try_into().expect("row");
|
||||
for j in 0..W {
|
||||
acc[j] = acc[j].wrapping_add(c.wrapping_mul(p[j]));
|
||||
}
|
||||
}
|
||||
narrow8(&acc, o);
|
||||
return;
|
||||
}
|
||||
let mut acc = [64i32; W];
|
||||
for t in 0..8 {
|
||||
let c = f[t] as i32;
|
||||
|
|
|
|||
|
|
@ -218,72 +218,77 @@ macro_rules! group_filter {
|
|||
let thresh_bd = thresh << shift;
|
||||
let one = 1 << shift;
|
||||
let (p3, p2, p1, p0, q0, q1, q2, q3) = (&v[4], &v[5], &v[6], &v[7], &v[8], &v[9], &v[10], &v[11]);
|
||||
// Filter mask process (8.8.5.1), with non-short-circuit `&` so that it vectorizes.
|
||||
let mut mask = [false; LANES];
|
||||
let mut hev = [false; LANES];
|
||||
let mut flat = [false; LANES];
|
||||
let mut flat2 = [false; LANES];
|
||||
let can_flat = filter_size >= TX_8X8;
|
||||
// Filter mask process (8.8.5.1). Masks are 0 / -1 per lane, selects are bitwise
|
||||
// and clipping uses min / max (`clamp` asserts its bounds), so every lane loop
|
||||
// below compiles to vector instructions.
|
||||
let le = |a: T, b: T| -((a <= b) as T);
|
||||
let sel = |m: T, a: T, b: T| (a & m) | (b & !m);
|
||||
let mut mask = [0 as T; LANES];
|
||||
let mut hev = [0 as T; LANES];
|
||||
let mut flat = [0 as T; LANES];
|
||||
let mut flat2 = [0 as T; LANES];
|
||||
let can_flat = -((filter_size >= TX_8X8) as T);
|
||||
let can_flat2 = R == 8 && filter_size >= TX_16X16;
|
||||
let apply: [T; LANES] = apply.map(|a| -(a as T));
|
||||
for l in 0..LANES {
|
||||
let d_p1p0 = (p1[l] - p0[l]).abs();
|
||||
let d_q1q0 = (q1[l] - q0[l]).abs();
|
||||
mask[l] = apply[l]
|
||||
& ((p3[l] - p2[l]).abs() <= limit_bd)
|
||||
& ((p2[l] - p1[l]).abs() <= limit_bd)
|
||||
& (d_p1p0 <= limit_bd)
|
||||
& (d_q1q0 <= limit_bd)
|
||||
& ((q2[l] - q1[l]).abs() <= limit_bd)
|
||||
& ((q3[l] - q2[l]).abs() <= limit_bd)
|
||||
& ((p0[l] - q0[l]).abs() * 2 + (p1[l] - q1[l]).abs() / 2 <= blimit_bd);
|
||||
hev[l] = (d_p1p0 > thresh_bd) | (d_q1q0 > thresh_bd);
|
||||
& le((p3[l] - p2[l]).abs(), limit_bd)
|
||||
& le((p2[l] - p1[l]).abs(), limit_bd)
|
||||
& le(d_p1p0, limit_bd)
|
||||
& le(d_q1q0, limit_bd)
|
||||
& le((q2[l] - q1[l]).abs(), limit_bd)
|
||||
& le((q3[l] - q2[l]).abs(), limit_bd)
|
||||
& le((p0[l] - q0[l]).abs() * 2 + ((p1[l] - q1[l]).abs() >> 1), blimit_bd);
|
||||
hev[l] = !(le(d_p1p0, thresh_bd) & le(d_q1q0, thresh_bd));
|
||||
flat[l] = can_flat
|
||||
& mask[l]
|
||||
& (d_p1p0 <= one)
|
||||
& (d_q1q0 <= one)
|
||||
& ((p2[l] - p0[l]).abs() <= one)
|
||||
& ((q2[l] - q0[l]).abs() <= one)
|
||||
& ((p3[l] - p0[l]).abs() <= one)
|
||||
& ((q3[l] - q0[l]).abs() <= one);
|
||||
& le(d_p1p0, one)
|
||||
& le(d_q1q0, one)
|
||||
& le((p2[l] - p0[l]).abs(), one)
|
||||
& le((q2[l] - q0[l]).abs(), one)
|
||||
& le((p3[l] - p0[l]).abs(), one)
|
||||
& le((q3[l] - q0[l]).abs(), one);
|
||||
}
|
||||
if !mask.iter().fold(false, |a, &m| a | m) {
|
||||
if mask.iter().fold(0, |a, &m| a | m) == 0 {
|
||||
return;
|
||||
}
|
||||
if can_flat2 {
|
||||
for l in 0..LANES {
|
||||
flat2[l] = flat[l]
|
||||
& ((v[0][l] - p0[l]).abs() <= one)
|
||||
& ((v[1][l] - p0[l]).abs() <= one)
|
||||
& ((v[2][l] - p0[l]).abs() <= one)
|
||||
& ((v[3][l] - p0[l]).abs() <= one)
|
||||
& ((v[12][l] - q0[l]).abs() <= one)
|
||||
& ((v[13][l] - q0[l]).abs() <= one)
|
||||
& ((v[14][l] - q0[l]).abs() <= one)
|
||||
& ((v[15][l] - q0[l]).abs() <= one);
|
||||
& le((v[0][l] - p0[l]).abs(), one)
|
||||
& le((v[1][l] - p0[l]).abs(), one)
|
||||
& le((v[2][l] - p0[l]).abs(), one)
|
||||
& le((v[3][l] - p0[l]).abs(), one)
|
||||
& le((v[12][l] - q0[l]).abs(), one)
|
||||
& le((v[13][l] - q0[l]).abs(), one)
|
||||
& le((v[14][l] - q0[l]).abs(), one)
|
||||
& le((v[15][l] - q0[l]).abs(), one);
|
||||
}
|
||||
}
|
||||
let any_flat = flat.iter().fold(false, |a, &f| a | f);
|
||||
let any_flat2 = flat2.iter().fold(false, |a, &f| a | f);
|
||||
let any_flat = flat.iter().fold(0, |a, &f| a | f) != 0;
|
||||
let any_flat2 = flat2.iter().fold(0, |a, &f| a | f) != 0;
|
||||
// Results per position across the edge; start from the unfiltered samples.
|
||||
let mut out = v;
|
||||
// Narrow filter (8.8.5.2) for lanes that are masked but not flat.
|
||||
{
|
||||
let lo = -(1 << (bit_depth - 1)) as T;
|
||||
let hi = ((1 << (bit_depth - 1)) - 1) as T;
|
||||
let c = |x: T| x.clamp(lo, hi);
|
||||
let c = |x: T| x.max(lo).min(hi);
|
||||
let off: T = 0x80 << shift;
|
||||
for l in 0..LANES {
|
||||
let (ps1, ps0, qs0, qs1) = (p1[l] - off, p0[l] - off, q0[l] - off, q1[l] - off);
|
||||
let filter = c(if hev[l] { c(ps1 - qs1) } else { 0 } + 3 * (qs0 - ps0));
|
||||
let filter = c((c(ps1 - qs1) & hev[l]) + 3 * (qs0 - ps0));
|
||||
let filter1 = c(filter + 4) >> 3;
|
||||
let filter2 = c(filter + 3) >> 3;
|
||||
let f = (filter1 + 1) >> 1;
|
||||
let narrow = mask[l] && !flat[l];
|
||||
let outer = narrow && !hev[l];
|
||||
out[8][l] = if narrow { c(qs0 - filter1) + off } else { out[8][l] };
|
||||
out[7][l] = if narrow { c(ps0 + filter2) + off } else { out[7][l] };
|
||||
out[9][l] = if outer { c(qs1 - f) + off } else { out[9][l] };
|
||||
out[6][l] = if outer { c(ps1 + f) + off } else { out[6][l] };
|
||||
let narrow = mask[l] & !flat[l];
|
||||
let outer = narrow & !hev[l];
|
||||
out[8][l] = sel(narrow, c(qs0 - filter1) + off, out[8][l]);
|
||||
out[7][l] = sel(narrow, c(ps0 + filter2) + off, out[7][l]);
|
||||
out[9][l] = sel(outer, c(qs1 - f) + off, out[9][l]);
|
||||
out[6][l] = sel(outer, c(ps1 + f) + off, out[6][l]);
|
||||
}
|
||||
}
|
||||
// Wide filter (8.8.5.3) with 8 taps for flat lanes (p2..q2) ...
|
||||
|
|
@ -291,9 +296,7 @@ macro_rules! group_filter {
|
|||
let w8 = wide_filter::<3>(&v);
|
||||
for k in 5..11 {
|
||||
for l in 0..LANES {
|
||||
if flat[l] && !flat2[l] {
|
||||
out[k][l] = w8[k][l];
|
||||
}
|
||||
out[k][l] = sel(flat[l] & !flat2[l], w8[k][l], out[k][l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,9 +305,7 @@ macro_rules! group_filter {
|
|||
let w16 = wide_filter::<4>(&v);
|
||||
for k in 1..15 {
|
||||
for l in 0..LANES {
|
||||
if flat2[l] {
|
||||
out[k][l] = w16[k][l];
|
||||
}
|
||||
out[k][l] = sel(flat2[l], w16[k][l], out[k][l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,19 +202,123 @@ fn itx_narrow<const N: usize>(coefs: &[i32], p: &TxParams, dst: &mut [u16], stri
|
|||
*v = sat(*v, lim);
|
||||
}
|
||||
}
|
||||
let mut col = [0i32; N];
|
||||
for j in 0..N {
|
||||
for i in 0..N {
|
||||
col[i] = t[i][j];
|
||||
}
|
||||
tx1d_narrow(&mut col, N, p.col_adst);
|
||||
for i in 0..N {
|
||||
let d = &mut dst[i * stride + j];
|
||||
*d = (*d as i32 + (col[i].wrapping_add(p.round) >> p.shift).clamp(-(1 << 20), 1 << 20)).clamp(0, p.max) as u16;
|
||||
// Column pass across 4 / 8 columns at once (the same butterflies on lane vectors), then
|
||||
// the rounding, residual add and clipping per row of lanes.
|
||||
if N == 4 {
|
||||
let x: [Cols<4>; N] = std::array::from_fn(|i| Cols(t[i][..4].try_into().expect("4 columns")));
|
||||
let y = col_tx4(x, p.col_adst);
|
||||
add_cols(&y, 0, p, dst, stride);
|
||||
} else {
|
||||
for c0 in (0..N).step_by(8) {
|
||||
let x: [Cols<8>; N] = std::array::from_fn(|i| Cols(t[i][c0..c0 + 8].try_into().expect("8 columns")));
|
||||
let y = col_tx8(x, p.col_adst);
|
||||
add_cols(&y, c0, p, dst, stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Round the column pass output `y` (columns c0..c0 + W of every row) and add it to `dst`.
|
||||
#[inline(always)]
|
||||
fn add_cols<const N: usize, const W: usize>(y: &[Cols<W>; N], c0: usize, p: &TxParams, dst: &mut [u16], stride: usize) {
|
||||
for (i, v) in y.iter().enumerate() {
|
||||
let d: &mut [u16; W] = (&mut dst[i * stride + c0..i * stride + c0 + W]).try_into().expect("row");
|
||||
for j in 0..W {
|
||||
let r = (v.0[j].wrapping_add(p.round) >> p.shift).clamp(-(1 << 20), 1 << 20);
|
||||
d[j] = (d[j] as i32 + r).max(0).min(p.max) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `W` columns of 32-bit values processed together: the generated butterflies
|
||||
/// (`transform_gen::cols4` / `cols8`) run on these lane vectors with the scalar arithmetic of
|
||||
/// `narrow` applied per lane, so the column pass vectorises.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Cols<const W: usize>(pub [i32; W]);
|
||||
|
||||
impl<const W: usize> From<i32> for Cols<W> {
|
||||
#[inline(always)]
|
||||
fn from(v: i32) -> Self {
|
||||
Cols([v; W])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const W: usize> Cols<W> {
|
||||
#[inline(always)]
|
||||
pub fn wrapping_add<O: Into<Self>>(self, o: O) -> Self {
|
||||
let o = o.into();
|
||||
Cols(std::array::from_fn(|i| self.0[i].wrapping_add(o.0[i])))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn wrapping_sub(self, o: Self) -> Self {
|
||||
Cols(std::array::from_fn(|i| self.0[i].wrapping_sub(o.0[i])))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn wrapping_mul(self, c: i32) -> Self {
|
||||
Cols(self.0.map(|v| v.wrapping_mul(c)))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn wrapping_neg(self) -> Self {
|
||||
Cols(self.0.map(|v| v.wrapping_neg()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const W: usize> std::ops::Shr<i32> for Cols<W> {
|
||||
type Output = Self;
|
||||
#[inline(always)]
|
||||
fn shr(self, s: i32) -> Self {
|
||||
Cols(self.0.map(|v| v >> s))
|
||||
}
|
||||
}
|
||||
|
||||
/// `x` (N rows of 4 columns) through the 1D column transform.
|
||||
#[inline(always)]
|
||||
fn col_tx4<const N: usize>(x: [Cols<4>; N], adst: bool) -> [Cols<4>; N] {
|
||||
use crate::transform_gen::cols4;
|
||||
let r = if adst { iadst4_cols(fixed(&x)) } else { cols4::idct4(fixed(&x)) };
|
||||
std::array::from_fn(|i| r[i])
|
||||
}
|
||||
|
||||
/// `x` (N rows of 8 columns) through the 1D column transform.
|
||||
#[inline(always)]
|
||||
fn col_tx8<const N: usize>(x: [Cols<8>; N], adst: bool) -> [Cols<8>; N] {
|
||||
use crate::transform_gen::cols8;
|
||||
let mut out = x;
|
||||
match (N, adst) {
|
||||
(8, false) => out.copy_from_slice(&cols8::idct8(fixed(&x))),
|
||||
(8, true) => out.copy_from_slice(&cols8::iadst8(fixed(&x))),
|
||||
(16, false) => out.copy_from_slice(&cols8::idct16(fixed(&x))),
|
||||
(16, true) => out.copy_from_slice(&cols8::iadst16(fixed(&x))),
|
||||
_ => out.copy_from_slice(&cols8::idct32(fixed(&x))),
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A slice of known length as an array.
|
||||
#[inline(always)]
|
||||
fn fixed<T: Copy, const M: usize>(x: &[T]) -> [T; M] {
|
||||
x[..M].try_into().expect("transform size")
|
||||
}
|
||||
|
||||
/// ADST4 (8.7.1.6) on 4 columns.
|
||||
#[inline(always)]
|
||||
fn iadst4_cols(x: [Cols<4>; 4]) -> [Cols<4>; 4] {
|
||||
let s0 = x[0].wrapping_mul(SINPI_1_9 as i32);
|
||||
let s1 = x[0].wrapping_mul(SINPI_2_9 as i32);
|
||||
let s2 = x[1].wrapping_mul(SINPI_3_9 as i32);
|
||||
let s3 = x[2].wrapping_mul(SINPI_4_9 as i32);
|
||||
let s4 = x[2].wrapping_mul(SINPI_1_9 as i32);
|
||||
let s5 = x[3].wrapping_mul(SINPI_2_9 as i32);
|
||||
let s6 = x[3].wrapping_mul(SINPI_4_9 as i32);
|
||||
let v = x[0].wrapping_sub(x[2]).wrapping_add(x[3]);
|
||||
let s7 = v.wrapping_mul(SINPI_3_9 as i32);
|
||||
let x0 = s0.wrapping_add(s3).wrapping_add(s5);
|
||||
let x1 = s1.wrapping_sub(s4).wrapping_sub(s6);
|
||||
let x2 = s7;
|
||||
let x3 = s2;
|
||||
let r = |v: Cols<4>| v.wrapping_add(1 << 13) >> 14;
|
||||
[r(x0.wrapping_add(x3)), r(x1.wrapping_add(x3)), r(x2), r(x0.wrapping_add(x1).wrapping_sub(x3))]
|
||||
}
|
||||
|
||||
/// 2D inverse transform of size N with 64-bit intermediates (high bit depth).
|
||||
fn itx_wide<const N: usize>(coefs: &[i32], p: &TxParams, dst: &mut [u16], stride: usize) {
|
||||
let lim = 1i64 << 40;
|
||||
|
|
@ -327,6 +431,65 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// The per-column formulation the vectorised column pass replaced.
|
||||
fn itx_narrow_ref<const N: usize>(coefs: &[i32], p: &TxParams, dst: &mut [u16], stride: usize) {
|
||||
let lim = 1 << 24;
|
||||
let mut t = [[0i32; N]; N];
|
||||
for (i, row) in t.iter_mut().enumerate().take(p.rows.min(N)) {
|
||||
for (d, s) in row.iter_mut().zip(&coefs[i * N..i * N + N]) {
|
||||
*d = sat(*s, lim);
|
||||
}
|
||||
tx1d_narrow(row, N, p.row_adst);
|
||||
for v in row.iter_mut() {
|
||||
*v = sat(*v, lim);
|
||||
}
|
||||
}
|
||||
let mut col = [0i32; N];
|
||||
for j in 0..N {
|
||||
for i in 0..N {
|
||||
col[i] = t[i][j];
|
||||
}
|
||||
tx1d_narrow(&mut col, N, p.col_adst);
|
||||
for i in 0..N {
|
||||
let d = &mut dst[i * stride + j];
|
||||
*d = (*d as i32 + (col[i].wrapping_add(p.round) >> p.shift).clamp(-(1 << 20), 1 << 20)).clamp(0, p.max) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_cols<const N: usize>(seed: &mut u32) {
|
||||
let mut rnd = || {
|
||||
*seed ^= *seed << 13;
|
||||
*seed ^= *seed >> 17;
|
||||
*seed ^= *seed << 5;
|
||||
*seed
|
||||
};
|
||||
let tx_size = N.trailing_zeros() - 2;
|
||||
let shift = (tx_size + 4).min(6);
|
||||
for case in 0..200 {
|
||||
let (row_adst, col_adst) = if N == 32 { (false, false) } else { (case & 1 == 1, case & 2 == 2) };
|
||||
let rows = 1 + rnd() as usize % N;
|
||||
let big = if case % 7 == 0 { 1 << 22 } else { 2000 };
|
||||
let coefs: Vec<i32> = (0..N * N).map(|i| if i / N < rows && rnd() % 3 == 0 { (rnd() % (2 * big)) as i32 - big as i32 } else { 0 }).collect();
|
||||
let p = TxParams { row_adst, col_adst, rows, shift, round: 1 << (shift - 1), max: 255 };
|
||||
let stride = N + 5;
|
||||
let base: Vec<u16> = (0..N * stride).map(|_| (rnd() % 256) as u16).collect();
|
||||
let (mut a, mut b) = (base.clone(), base);
|
||||
itx_narrow::<N>(&coefs, &p, &mut a, stride);
|
||||
itx_narrow_ref::<N>(&coefs, &p, &mut b, stride);
|
||||
assert_eq!(a, b, "N {N} case {case}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_pass_matches_per_column_transform() {
|
||||
let mut seed = 0x9e37_79b9u32;
|
||||
check_cols::<4>(&mut seed);
|
||||
check_cols::<8>(&mut seed);
|
||||
check_cols::<16>(&mut seed);
|
||||
check_cols::<32>(&mut seed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wht_round_trip_identity() {
|
||||
// A single DC coefficient of 4 * v spreads v / 4 ... check invertibility on a simple case:
|
||||
|
|
|
|||
|
|
@ -478,3 +478,12 @@ pub(crate) mod narrow {
|
|||
pub(crate) mod wide {
|
||||
transforms_1d!(i64);
|
||||
}
|
||||
|
||||
/// The same butterflies on 4 / 8 columns at once (column pass of the 2D transforms).
|
||||
pub(crate) mod cols4 {
|
||||
transforms_1d!(crate::transform::Cols<4>);
|
||||
}
|
||||
|
||||
pub(crate) mod cols8 {
|
||||
transforms_1d!(crate::transform::Cols<8>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,3 +247,12 @@ print()
|
|||
print("pub(crate) mod wide {")
|
||||
print(" transforms_1d!(i64);")
|
||||
print("}")
|
||||
print()
|
||||
print("/// The same butterflies on 4 / 8 columns at once (column pass of the 2D transforms).")
|
||||
print("pub(crate) mod cols4 {")
|
||||
print(" transforms_1d!(crate::transform::Cols<4>);")
|
||||
print("}")
|
||||
print()
|
||||
print("pub(crate) mod cols8 {")
|
||||
print(" transforms_1d!(crate::transform::Cols<8>);")
|
||||
print("}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue