zoomvtools 2.0.0

Video motion vector analysis utilities in pure Rust
Documentation
#![allow(clippy::unwrap_used, reason = "allow in test files")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "allow in test files")]
#![allow(clippy::indexing_slicing, reason = "allow in test files")]

#[cfg(all(target_arch = "x86_64", feature = "simd"))]
use std::mem::size_of;
use std::num::NonZeroUsize;

use crate::{params::Subpel, resize::SimpleResize};

fn nz(value: usize) -> NonZeroUsize {
    NonZeroUsize::new(value).expect("test value must be non-zero")
}

fn build_resizer(
    dest_width: usize,
    dest_height: usize,
    src_width: usize,
    src_height: usize,
    limit_width: usize,
    limit_height: usize,
    pel: Subpel,
) -> SimpleResize {
    SimpleResize::new(
        nz(dest_width),
        nz(dest_height),
        nz(src_width),
        nz(src_height),
        nz(limit_width),
        nz(limit_height),
        pel,
    )
}

fn build_rust_resizer(
    dest_width: usize,
    dest_height: usize,
    src_width: usize,
    src_height: usize,
    limit_width: usize,
    limit_height: usize,
    pel: Subpel,
) -> SimpleResize {
    #[allow(unused_mut, reason = "only mutated in AVX2 test builds")]
    let mut resizer = build_resizer(
        dest_width,
        dest_height,
        src_width,
        src_height,
        limit_width,
        limit_height,
        pel,
    );

    resizer
}

fn build_u8_src(width: usize, height: usize, stride: usize) -> Vec<u8> {
    let mut src = vec![0_u8; stride * height];
    for y in 0..height {
        for x in 0..width {
            src[y * stride + x] = ((x * 29 + y * 17 + (x * y) * 7 + 13) & 0xff) as u8;
        }
    }
    src
}

fn build_i16_src(width: usize, height: usize, stride: usize, pel: i16) -> Vec<i16> {
    let mut src = vec![0_i16; stride * height];
    for y in 0..height {
        for x in 0..width {
            src[y * stride + x] = (x as i16 * pel) - (y as i16 * (pel / 2 + 1));
        }
    }
    src
}

#[test]
fn resize_u8_to_vec_matches_resize_u8_and_zeroes_stride_padding() {
    let resizer = build_rust_resizer(5, 3, 4, 2, 5, 3, Subpel::Full);
    let src_stride = 6;
    let dest_stride = 8;
    let src = build_u8_src(4, 2, src_stride);
    let mut expected = vec![0xaa_u8; dest_stride * 3];

    resizer.resize_u8(&mut expected, nz(dest_stride), &src, nz(src_stride), false);
    let actual = resizer.resize_u8_to_vec(nz(dest_stride), &src, nz(src_stride), false);

    for y in 0..3 {
        let row = y * dest_stride;
        assert_eq!(&actual[row..row + 5], &expected[row..row + 5]);
        assert_eq!(&actual[row + 5..row + dest_stride], &[0, 0, 0]);
    }
}

#[test]
fn resize_i16_to_vec_matches_resize_i16_and_zeroes_stride_padding() {
    let resizer = build_rust_resizer(5, 4, 4, 3, 5, 4, Subpel::Half);
    let src_stride = 7;
    let dest_stride = 9;
    let src = build_i16_src(4, 3, src_stride, 2);
    let mut expected = vec![i16::MIN; dest_stride * 4];

    resizer.resize_i16(&mut expected, nz(dest_stride), &src, nz(src_stride), true);
    let actual = resizer.resize_i16_to_vec(nz(dest_stride), &src, nz(src_stride), true);

    for y in 0..4 {
        let row = y * dest_stride;
        assert_eq!(&actual[row..row + 5], &expected[row..row + 5]);
        assert_eq!(&actual[row + 5..row + dest_stride], &[0, 0, 0, 0]);
    }
}

#[cfg(all(target_arch = "x86_64", feature = "simd"))]
#[test]
fn build_rust_resizer_preserves_scalar_weights_after_avx2_split() {
    let rust_resizer = build_rust_resizer(12, 7, 8, 5, 11, 6, Subpel::Half);
    let resizer = build_resizer(12, 7, 8, 5, 11, 6, Subpel::Half);

    assert_eq!(
        &*rust_resizer.horizontal_weights,
        &*resizer.horizontal_weights
    );
}

#[cfg(all(target_arch = "x86_64", feature = "simd"))]
#[test]
fn simple_resize_u8_avx2_matches_rust_varied_shapes() {
    let cases = [
        (8, 4, 4, 3, false),
        (12, 7, 8, 5, false),
        (17, 9, 8, 6, false),
        (19, 11, 9, 5, true),
        (31, 13, 16, 9, true),
    ];

    for &(dest_width, dest_height, src_width, src_height, horizontal_vectors) in &cases {
        for pel in [Subpel::Full, Subpel::Half, Subpel::Quarter] {
            let src_stride = src_width + 5;
            let dest_stride = dest_width + 7;
            let rust_resizer = build_rust_resizer(
                dest_width,
                dest_height,
                src_width,
                src_height,
                dest_width.saturating_sub(1).max(1),
                dest_height.saturating_sub(1).max(1),
                pel,
            );
            let avx_resizer = build_resizer(
                dest_width,
                dest_height,
                src_width,
                src_height,
                dest_width.saturating_sub(1).max(1),
                dest_height.saturating_sub(1).max(1),
                pel,
            );
            let src = build_u8_src(src_width, src_height, src_stride);
            let mut rust_dest = vec![0_u8; dest_stride * dest_height];
            let mut avx_dest = rust_dest.clone();

            unsafe {
                super::rust::simple_resize::<u8>(
                    &rust_resizer,
                    rust_dest.as_mut_ptr().cast(),
                    nz(dest_stride),
                    src.as_ptr().cast(),
                    nz(src_stride),
                    horizontal_vectors,
                );
                super::avx2::simple_resize_u8(
                    &avx_resizer,
                    avx_dest.as_mut_ptr().cast(),
                    nz(dest_stride),
                    src.as_ptr().cast(),
                    nz(src_stride),
                    horizontal_vectors,
                );
            }

            assert_eq!(
                avx_dest, rust_dest,
                "u8 mismatch dest={dest_width}x{dest_height} src={src_width}x{src_height} pel={pel:?} horizontal_vectors={horizontal_vectors}"
            );
        }
    }
}

#[cfg(all(target_arch = "x86_64", feature = "simd"))]
#[test]
fn simple_resize_i16_avx2_matches_rust_varied_shapes() {
    let cases = [
        (8, 4, 8, 3, 5, 4, false),
        (9, 6, 8, 4, 7, 5, false),
        (11, 8, 8, 4, 5, 6, true),
        (17, 9, 8, 6, 12, 7, true),
        (23, 10, 11, 7, 9, 8, true),
    ];

    for &(
        dest_width,
        dest_height,
        src_width,
        src_height,
        limit_width,
        limit_height,
        horizontal_vectors,
    ) in &cases
    {
        for pel in [Subpel::Full, Subpel::Half, Subpel::Quarter] {
            let src_stride = src_width + 6;
            let dest_stride = dest_width + 5;
            let pel_value = i16::from(u8::from(pel));
            let rust_resizer = build_rust_resizer(
                dest_width,
                dest_height,
                src_width,
                src_height,
                limit_width,
                limit_height,
                pel,
            );
            let avx_resizer = build_resizer(
                dest_width,
                dest_height,
                src_width,
                src_height,
                limit_width,
                limit_height,
                pel,
            );
            let src = build_i16_src(src_width, src_height, src_stride, pel_value);
            let mut rust_dest = vec![i16::MIN; dest_stride * dest_height];
            let mut avx_dest = rust_dest.clone();

            unsafe {
                super::rust::simple_resize::<i16>(
                    &rust_resizer,
                    rust_dest.as_mut_ptr().cast(),
                    nz(dest_stride * size_of::<i16>()),
                    src.as_ptr().cast(),
                    nz(src_stride * size_of::<i16>()),
                    horizontal_vectors,
                );
                super::avx2::simple_resize_i16(
                    &avx_resizer,
                    avx_dest.as_mut_ptr().cast(),
                    nz(dest_stride * size_of::<i16>()),
                    src.as_ptr().cast(),
                    nz(src_stride * size_of::<i16>()),
                    horizontal_vectors,
                );
            }

            assert_eq!(
                avx_dest, rust_dest,
                "i16 mismatch dest={dest_width}x{dest_height} src={src_width}x{src_height} limit={limit_width}x{limit_height} pel={pel:?} horizontal_vectors={horizontal_vectors}"
            );
        }
    }
}