kahlo 0.0.3

Optimized software rendering library.
Documentation
//! SIMD implementations for compositing on x86_64.

use crate::{
    colour::BlendMode,
    formats::Rgba32,
    math::*,
    op::{avx::*, binary_map_pixel_data_with, common_x86_64::*},
    prelude::*,
};

/// AVX entry point for RGBA-RGBA composition.
pub fn avx_rgba32_rgba32(
    write_data: &mut dyn BitmapRawDataMut,
    read_data: &dyn BitmapRawData,
    params: &(PixelBox, PixelPoint, BlendMode),
) {
    match params.2 {
        BlendMode::SourceCopy => binary_map_pixel_data_with::<0, Rgba32, Rgba32>(
            write_data,
            read_data,
            params.0.size(),
            params.1,
            params.0.min,
            <[u8]>::copy_from_slice,
        ),
        BlendMode::Simple => binary_map_pixel_data_with::<2, Rgba32, Rgba32>(
            write_data,
            read_data,
            params.0.size(),
            params.1,
            params.0.min,
            |w, r| unsafe {
                (rgba32_rgba32_simple)(
                    &mut *(w.as_mut_ptr() as *mut [u8; 8]),
                    &*(r.as_ptr() as *const [u8; 8]),
                )
            },
        ),
        BlendMode::SourceAlpha => binary_map_pixel_data_with::<4, Rgba32, Rgba32>(
            write_data,
            read_data,
            params.0.size(),
            params.1,
            params.0.min,
            |w, r| unsafe {
                (rgba32_rgba32_source)(
                    &mut *(w.as_mut_ptr() as *mut [u8; 16]),
                    &*(r.as_ptr() as *const [u8; 16]),
                )
            },
        ),
        BlendMode::BackdropAlpha => binary_map_pixel_data_with::<4, Rgba32, Rgba32>(
            write_data,
            read_data,
            params.0.size(),
            params.1,
            params.0.min,
            |w, r| unsafe {
                (rgba32_rgba32_backdrop)(
                    &mut *(w.as_mut_ptr() as *mut [u8; 16]),
                    &*(r.as_ptr() as *const [u8; 16]),
                )
            },
        ),
        BlendMode::Multiply => binary_map_pixel_data_with::<4, Rgba32, Rgba32>(
            write_data,
            read_data,
            params.0.size(),
            params.1,
            params.0.min,
            |w, r| unsafe {
                (rgba32_rgba32_multiply)(
                    &mut *(w.as_mut_ptr() as *mut [u8; 16]),
                    &*(r.as_ptr() as *const [u8; 16]),
                )
            },
        ),
    }
}

#[inline]
#[target_feature(enable = "fma")]
#[target_feature(enable = "avx2")]
fn rgba32_rgba32_simple(e: &mut [u8; 8], s: &[u8; 8]) {
    let bvec = Rf32x8::load8_and_expand(e);
    let svec = Rf32x8::load8_and_expand(s);

    rgba_simple_blend!(bvec, svec).shrink_and_store8(e)
}

/// computes:
/// - out.rgb = ((x.rgb * x.a) + (255-x.a)*y.rgb)/255
/// - out.a = ((255 * x.a) + (255-x.a)*y.a)/255
#[inline]
#[target_feature(enable = "avx2")]
fn rgba32_rgba32_source(e: &mut [u8; 16], s: &[u8; 16]) {
    let bvec = Ri16x16::load8_and_expand(e);
    let svec = Ri16x16::load8_and_expand(s);

    rgba_source_blend!(bvec, svec).shrink_and_store8(e);
}

/// computes:
/// - out.rgb = ((x.rgb * y.a) + (255-y.a)*y.rgb)/255
/// - out.a = ((255 * y.a) + (255-y.a)*x.a)/255
#[inline]
#[target_feature(enable = "avx2")]
fn rgba32_rgba32_backdrop(e: &mut [u8; 16], s: &[u8; 16]) {
    let bvec = Ri16x16::load8_and_expand(e);
    let svec = Ri16x16::load8_and_expand(s);

    rgba_backdrop_blend!(bvec, svec).shrink_and_store8(e);
}

/// computes:
/// - out.rgba = (x.rgba * y.rgba)/255
#[inline]
#[target_feature(enable = "avx2")]
fn rgba32_rgba32_multiply(e: &mut [u8; 16], s: &[u8; 16]) {
    let bvec = Ri16x16::load8_and_expand(e);
    let svec = Ri16x16::load8_and_expand(s);

    rgba_multiply_blend!(bvec, svec).shrink_and_store8(e);
}

#[cfg(test)]
mod test {
    use super::{
        rgba32_rgba32_backdrop, rgba32_rgba32_multiply, rgba32_rgba32_simple, rgba32_rgba32_source,
    };
    use crate::colour::{test::*, Colour};
    use crate::palette::css;
    use rstest::rstest;
    use rstest_reuse::apply;

    fn col_to_arr_int(col: Colour) -> [u8; 16] {
        unsafe { std::mem::transmute([[col.r, col.g, col.b, col.a]; 4]) }
    }
    fn col_to_arr_fp(col: Colour) -> [u8; 8] {
        unsafe { std::mem::transmute([[col.r, col.g, col.b, col.a]; 2]) }
    }

    fn arr_to_col(data: &[u8]) -> Colour {
        Colour::new(data[0], data[1], data[2], data[3])
    }

    #[apply(blend_template)]
    fn simple_blend_mode_fp(tc: BlendTestCase) {
        if !std::arch::is_x86_feature_detected!("avx2") {
            println!("no avx2 detected, skipping test");
            return;
        }
        println!("blending case: {}", tc.desc);
        let r = tc
            .simple_result
            .expect("there is no expected simple result for this case");
        println!("blending {:?} and {:?}", tc.source, tc.backdrop);
        println!("expecting {r:?}");

        let mut backdrop = col_to_arr_fp(tc.backdrop);
        let source = col_to_arr_fp(tc.source);

        unsafe { rgba32_rgba32_simple(&mut backdrop, &source) }

        assert_eq!(r, arr_to_col(&backdrop))
    }

    #[apply(blend_template)]
    fn source_alpha_blend_mode_int(tc: BlendTestCase) {
        if !std::arch::is_x86_feature_detected!("avx2") {
            println!("no avx2 detected, skipping test");
            return;
        }
        println!("blending case: {}", tc.desc);
        let r = tc
            .source_result
            .expect("there is no expected source result for this case");
        println!("blending {:?} and {:?}", tc.source, tc.backdrop);
        println!("expecting {r:?}");

        let mut backdrop = col_to_arr_int(tc.backdrop);
        let source = col_to_arr_int(tc.source);

        unsafe { rgba32_rgba32_source(&mut backdrop, &source) }

        assert_eq!(r, arr_to_col(&backdrop))
    }

    #[apply(blend_template)]
    fn backdrop_alpha_blend_mode_int(tc: BlendTestCase) {
        if !std::arch::is_x86_feature_detected!("avx2") {
            println!("no avx2 detected, skipping test");
            return;
        }
        println!("blending case: {}", tc.desc);
        let r = tc
            .backdrop_result
            .expect("there is no expected backdrop result for this case");
        println!("blending {:?} and {:?}", tc.source, tc.backdrop);
        println!("expecting {r:?}");

        let mut backdrop = col_to_arr_int(tc.backdrop);
        let source = col_to_arr_int(tc.source);

        unsafe { rgba32_rgba32_backdrop(&mut backdrop, &source) }

        assert_eq!(r, arr_to_col(&backdrop))
    }

    #[apply(blend_template)]
    fn multiply_blend_mode_int(tc: BlendTestCase) {
        if !std::arch::is_x86_feature_detected!("avx2") {
            println!("no avx2 detected, skipping test");
            return;
        }
        println!("blending case: {}", tc.desc);
        let r = tc
            .multiply_result
            .expect("there is no expected multiply result for this case");
        println!("blending {:?} and {:?}", tc.source, tc.backdrop);
        println!("expecting {r:?}");

        let mut backdrop = col_to_arr_int(tc.backdrop);
        let source = col_to_arr_int(tc.source);

        unsafe { rgba32_rgba32_multiply(&mut backdrop, &source) }

        assert_eq!(r, arr_to_col(&backdrop))
    }
}