diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f7b4ec70..bc6c4f2f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ env: # If the compilation fails, then the version specified here needs to be bumped up to reality. # Be sure to also update the rust-version property in the workspace Cargo.toml file, # plus all the README.md files of the affected packages. - RUST_MIN_VER: "1.88" + RUST_MIN_VER: "1.89" # List of packages that will be checked with the minimum supported Rust version. # This should be limited to packages that are intended for publishing. RUST_MIN_VER_PKGS: "-p vello -p vello_encoding -p vello_shaders -p vello_common -p vello_cpu -p vello_hybrid -p glifo" @@ -346,6 +346,56 @@ jobs: - name: cargo test --doc run: cargo test --doc --workspace --locked --all-features --no-fail-fast + test-avx512: + # Since GitHub runners don't support AVX-512, we need to run the tests with Intel SDE. This + # setup mirrors https://github.com/linebender/fearless_simd/blob/30c7f38cd545b20ec98624bbf62d1357cd8814e3/.github/workflows/ci.yml#L222 + name: vello_sparse_tests (AVX-512) + needs: prime-lfs-cache + runs-on: ubuntu-latest + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: "true" + CARGO_TEST_ARGS: -p vello_sparse_tests --locked --release --test tests + SDE_PKG: sde-external-10.8.0-2026-03-15 + SDE_SHA256: 50b320cd226acef7a491f5b321fc1be3c3c7984f9e27a456e64894b5b0979dd3 + VELLO_TEST_AVX512: "true" + steps: + - uses: actions/checkout@v6 + + - name: Restore lfs cache + uses: actions/cache/restore@v4 + with: + path: .git/lfs + key: vello-lfs-${{ needs.prime-lfs-cache.outputs.lfs-hash }} + + - name: Checkout LFS files + run: git lfs checkout '${{ join(fromJson(env.LFS_FILES), ''' ''') }}' + + - name: install stable toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_STABLE_VER }} + + - name: restore cache + uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.event_name != 'merge_group' }} + + - name: install Intel Software Development Emulator + run: | + curl -fsSL -o "${SDE_PKG}-lin.tar.xz" "https://github.com/Shnatsel/intel-sde-mirror/releases/download/sde-${SDE_PKG#sde-external-}/${SDE_PKG}-lin.tar.xz" + if ! echo "${SDE_SHA256} ${SDE_PKG}-lin.tar.xz" | sha256sum -c -; then + echo "SDE archive checksum verification failed" >&2 + exit 1 + fi + tar -Jxf "${SDE_PKG}-lin.tar.xz" + + # Building under SDE would be very slow. + - name: build tests + run: cargo test --no-run $CARGO_TEST_ARGS + + - name: run vello_sparse_tests with AVX-512 + run: ${SDE_PKG}-lin/sde64 -icl -- cargo test $CARGO_TEST_ARGS avx512 + test-stable-wasm: name: cargo test (wasm32, ${{ matrix.name }}) needs: prime-lfs-cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4f08d18..4caab2082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] -This release has an [MSRV][] of 1.88. +This release has an [MSRV][] of 1.89. ### Added diff --git a/Cargo.lock b/Cargo.lock index 9f5a88214..112613796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,9 +1084,9 @@ dependencies = [ [[package]] name = "fearless_simd" -version = "0.4.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76258897e51fd156ee03b6246ea53f3e0eb395d0b327e9961c4fc4c8b2fa151a" +checksum = "f4beca3cb2444e3304ac30843cc091f44ed58932353cd492ce740067bfce6b12" dependencies = [ "libm", ] @@ -4497,6 +4497,7 @@ version = "0.2.0" dependencies = [ "bytemuck", "crossbeam-channel", + "fearless_simd", "glifo", "hashbrown 0.17.1", "ordered-channel", diff --git a/Cargo.toml b/Cargo.toml index fa4e18980..995abdb2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ version = "0.10.0" edition = "2024" # Keep in sync with RUST_MIN_VER in .github/workflows/ci.yml, with the relevant README.md files # and with the MSRV in the `Unreleased` section of CHANGELOG.md. -rust-version = "1.88" +rust-version = "1.89" license = "Apache-2.0 OR MIT" repository = "https://github.com/linebender/vello" @@ -118,7 +118,7 @@ rayon = { version = "1.12.0" } thread_local = "1.1.9" crossbeam-channel = "0.5.15" ordered-channel = { version = "1.2.0", features = ["crossbeam-channel"] } -fearless_simd = { version = "0.4.0", default-features = false } +fearless_simd = { version = "0.7.0", default-features = false } # Unlike the other crates, please do not update these before a release # unless absolutely necessary. diff --git a/README.md b/README.md index 7500b2ea7..099594f1c 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ VELLO_STATIC_LOG="vello=trace" VELLO_STATIC_ARGS="--test-scenes" cargo apk run - ## Minimum supported Rust Version (MSRV) -This version of Vello has been verified to compile with **Rust 1.88** and later. +This version of Vello has been verified to compile with **Rust 1.89** and later. Future versions of Vello might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/glifo/CHANGELOG.md b/glifo/CHANGELOG.md index 1c34a02d4..dc813f90e 100644 --- a/glifo/CHANGELOG.md +++ b/glifo/CHANGELOG.md @@ -10,7 +10,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] -This release has an [MSRV][] of 1.88. +This release has an [MSRV][] of 1.89. ## [0.3.0][] - 2026-08-07 diff --git a/glifo/README.md b/glifo/README.md index e69f5c85e..0d862fc2f 100644 --- a/glifo/README.md +++ b/glifo/README.md @@ -48,7 +48,7 @@ At least one of `std` and `libm` is required. ## Minimum supported Rust Version (MSRV) -This version of Glifo has been verified to compile with **Rust 1.88** and later. +This version of Glifo has been verified to compile with **Rust 1.89** and later. Future versions of Glifo might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/sparse_strips/vello_common/CHANGELOG.md b/sparse_strips/vello_common/CHANGELOG.md index 1b83ceebb..5ef28da1f 100644 --- a/sparse_strips/vello_common/CHANGELOG.md +++ b/sparse_strips/vello_common/CHANGELOG.md @@ -10,7 +10,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] -This release has an [MSRV][] of 1.88. +This release has an [MSRV][] of 1.89. ## [0.2.0][] - 2026-08-07 diff --git a/sparse_strips/vello_common/README.md b/sparse_strips/vello_common/README.md index b2475d23e..cf35a1cb9 100644 --- a/sparse_strips/vello_common/README.md +++ b/sparse_strips/vello_common/README.md @@ -62,7 +62,7 @@ This crate acts as a foundation for `vello_cpu` and `vello_hybrid`, providing es ## Minimum supported Rust Version (MSRV) -This version of Vello Common has been verified to compile with **Rust 1.88** and later. +This version of Vello Common has been verified to compile with **Rust 1.89** and later. Future versions of Vello Common might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/sparse_strips/vello_common/src/clip.rs b/sparse_strips/vello_common/src/clip.rs index c30506333..4d14ee940 100644 --- a/sparse_strips/vello_common/src/clip.rs +++ b/sparse_strips/vello_common/src/clip.rs @@ -8,11 +8,11 @@ use crate::kurbo::{Affine, BezPath, PathEl}; use crate::strip::Strip; use crate::strip_generator::{GenerationMode, StripGenerator, StripStorage}; use crate::tile::Tile; -use crate::util::{Clear, Pool, normalized_mul_u8x16, strip_bbox}; +use crate::util::{Clear, Pool, narrow, normalized_mul_u8, strip_bbox}; use alloc::vec; use alloc::vec::Vec; use core::ops::Range; -use fearless_simd::{Level, Simd, SimdBase, dispatch, u8x16}; +use fearless_simd::{Level, dispatch, prelude::*, u8x16}; use peniko::Fill; #[derive(Debug)] @@ -461,7 +461,7 @@ fn intersect_impl( let s2 = u8x16::from_slice(simd, s2_alpha); // Combine them. - let res = simd.narrow_u16x16(normalized_mul_u8x16(s1, s2)); + let res = narrow(normalized_mul_u8(s1, s2)); target.alphas.extend(res.as_slice()); } } diff --git a/sparse_strips/vello_common/src/pixmap.rs b/sparse_strips/vello_common/src/pixmap.rs index b7204d4be..b698b7598 100644 --- a/sparse_strips/vello_common/src/pixmap.rs +++ b/sparse_strips/vello_common/src/pixmap.rs @@ -8,12 +8,12 @@ use alloc::vec::Vec; #[cfg(feature = "png")] use std::io::{BufRead, Seek}; -use crate::fearless_simd::{Level, Simd, SimdBase, SimdInt, SimdMask, dispatch, mask8x16, u16x16}; +use crate::fearless_simd::{Level, dispatch, mask8x16, prelude::*, u8x16, u16x16}; use crate::peniko::{ ImageAlphaType, color::{PremulRgba8, Rgba8}, }; -use crate::util::{Div255Ext, unpremultiply}; +use crate::util::{Div255Ext, narrow, unpremultiply, widen}; #[cfg(feature = "png")] extern crate std; @@ -462,17 +462,13 @@ fn unpremultiply_rgba8_impl(simd: S, data: &mut [u8]) { let (body, tail) = data.as_chunks_mut::<64>(); for chunk in body { - let rgba = simd.load_interleaved_128_u8x64(chunk); - let (rg, ba) = simd.split_u8x64(rgba); - let (r, g) = simd.split_u8x32(rg); - let (b, a) = simd.split_u8x32(ba); + let [r, g, b, a] = simd.load_four_interleaved_u8x16(chunk); let reciprocal = u16x16::from_fn(simd, |lane| unpremultiply::reciprocal(a[lane])); let r = unpremultiply::simd(simd, r, reciprocal); let g = unpremultiply::simd(simd, g, reciprocal); let b = unpremultiply::simd(simd, b, reciprocal); - let rgba = simd.combine_u8x32(simd.combine_u8x16(r, g), simd.combine_u8x16(b, a)); - simd.store_interleaved_128_u8x64(rgba, chunk); + simd.store_four_interleaved_u8x16([r, g, b, a], chunk); } for pixel in tail.chunks_exact_mut(4) { @@ -486,27 +482,20 @@ fn unpremultiply_rgba8_impl(simd: S, data: &mut [u8]) { #[inline(always)] fn premultiply_rgba8_impl(simd: S, data: &mut [u8]) -> bool { let (body, tail) = data.as_chunks_mut::<64>(); - let mut transparency = mask8x16::splat(simd, 0); + let mut transparency = mask8x16::splat(simd, false); for chunk in body { - let rgba = simd.load_interleaved_128_u8x64(chunk); - let (rg, ba) = simd.split_u8x64(rgba); - let (r, g) = simd.split_u8x32(rg); - let (b, a) = simd.split_u8x32(ba); + let [r, g, b, a] = simd.load_four_interleaved_u8x16(chunk); transparency |= !a.simd_eq(255); let premultiply = { #[inline(always)] - |component| { - let product = simd.widen_u8x16(component) * simd.widen_u8x16(a); - simd.narrow_u16x16(product.div_255()) - } + |component: u8x16| narrow((widen(component) * widen(a)).div_255()) }; - let premultiplied = simd.combine_u8x32( - simd.combine_u8x16(premultiply(r), premultiply(g)), - simd.combine_u8x16(premultiply(b), a), + simd.store_four_interleaved_u8x16( + [premultiply(r), premultiply(g), premultiply(b), a], + chunk, ); - simd.store_interleaved_128_u8x64(premultiplied, chunk); } let mut may_have_transparency = transparency.any_true(); diff --git a/sparse_strips/vello_common/src/tile.rs b/sparse_strips/vello_common/src/tile.rs index b0d09df48..5f90503e4 100644 --- a/sparse_strips/vello_common/src/tile.rs +++ b/sparse_strips/vello_common/src/tile.rs @@ -478,7 +478,7 @@ impl Tiles { pub fn sort_tiles(&mut self) { self.sorted = true; // To enable auto-vectorization. - self.level.dispatch(|_| self.tile_buf.sort_unstable()); + dispatch!(self.level, _ => self.tile_buf.sort_unstable()); } /// Get the tile at a certain index. @@ -636,11 +636,8 @@ impl Tiles { let current = f32x4::from_slice(s, target_row); // See comment below on the double counting risk! - let double_count = if at_top_of_tile { - f_dir_v - } else { - f32x4::splat(s, 0.0) - }; + let double_count = + mask32x4::splat(s, at_top_of_tile).select(f_dir_v, f32x4::splat(s, 0.0)); let next = fractional_coverage.mul_add(f_dir_v, current - double_count); next.store_slice(target_row); } @@ -789,11 +786,8 @@ impl Tiles { let target_row = &mut self.windings.partial[y_idx as usize]; let current = f32x4::from_slice(s, target_row); - let double_count = if crosses_top { - f_dir_v - } else { - f32x4::splat(s, 0.0) - }; + let double_count = mask32x4::splat(s, crosses_top) + .select(f_dir_v, f32x4::splat(s, 0.0)); let next = fractional_coverage .mul_add(f_dir_v, current - double_count); next.store_slice(target_row); diff --git a/sparse_strips/vello_common/src/util.rs b/sparse_strips/vello_common/src/util.rs index 8d0462377..c502929c6 100644 --- a/sparse_strips/vello_common/src/util.rs +++ b/sparse_strips/vello_common/src/util.rs @@ -9,9 +9,7 @@ use crate::strip::{Strip, visit_strip_fill_segments}; use crate::tile::Tile; use alloc::vec::Vec; use core::ops::{Index, IndexMut}; -use fearless_simd::{ - Bytes, Simd, SimdBase, SimdFloat, f32x16, u8x16, u8x32, u16x16, u16x32, u32x16, -}; +use fearless_simd::{f32x16, prelude::*, u8x16, u32x16}; #[cfg(not(feature = "std"))] use peniko::kurbo::common::FloatFuncs as _; use peniko::kurbo::{Affine, Rect}; @@ -51,39 +49,79 @@ pub fn f32_to_u8(val: f32x16) -> u8x16 { } /// A trait for implementing a fast approximal division by 255 for integers. -pub trait Div255Ext { +pub trait Div255Ext: private::Sealed { /// Divide by 255. fn div_255(self) -> Self; } -impl Div255Ext for u16x32 { - #[inline(always)] - fn div_255(self) -> Self { - let p1 = Self::splat(self.simd, 255); - let p2 = self + p1; - p2 >> 8 - } +mod private { + use fearless_simd::{Simd, u16x16, u16x32}; + + #[expect(unnameable_types, reason = "Sealed trait pattern.")] + pub trait Sealed {} + + impl Sealed for u16x16 {} + impl Sealed for u16x32 {} } -impl Div255Ext for u16x16 { - #[inline(always)] - fn div_255(self) -> Self { - let p1 = Self::splat(self.simd, 255); - let p2 = self + p1; - p2 >> 8 - } +/// Widen a SIMD vector and combine the two widened halves into one vector. +#[inline(always)] +pub fn widen(value: V) -> >::Combined +where + S: Simd, + V: SimdWiden, + V::Widened: SimdCombine, +{ + let (low, high) = value.widen(); + low.combine(high) +} + +/// Split a SIMD vector and narrow its two halves into one vector. +#[inline(always)] +pub fn narrow(value: V) -> >::Narrowed +where + S: Simd, + V: SimdSplit, + V::Split: SimdNarrow, +{ + let (low, high) = value.split(); + low.narrow(high) } -/// Perform a normalized multiplication for u8x32. +/// Split a SIMD vector and narrow its two halves with saturation. #[inline(always)] -pub fn normalized_mul_u8x32(a: u8x32, b: u8x32) -> u16x32 { - (S::widen_u8x32(a.simd, a) * S::widen_u8x32(b.simd, b)).div_255() +pub fn saturating_narrow(value: V) -> >::Narrowed +where + S: Simd, + V: SimdSplit, + V::Split: SimdNarrow, +{ + let (low, high) = value.split(); + low.saturating_narrow(high) } -/// Perform a normalized multiplication for u8x16. +impl Div255Ext for T +where + T: private::Sealed + core::ops::Add + core::ops::Shr, +{ + #[inline(always)] + fn div_255(self) -> Self { + (self + 255_u16) >> 8_u32 + } +} + +/// Perform a normalized multiplication for a SIMD vector of `u8` values. #[inline(always)] -pub fn normalized_mul_u8x16(a: u8x16, b: u8x16) -> u16x16 { - (S::widen_u8x16(a.simd, a) * S::widen_u8x16(b.simd, b)).div_255() +pub fn normalized_mul_u8(a: V, b: V) -> >::Combined +where + S: Simd, + V: SimdWiden, + V::Widened: SimdCombine, + >::Combined: Div255Ext, +{ + let a = widen(a); + let b = widen(b); + (a * b).div_255() } /// Check if an affine transform is a pure integer translation. @@ -384,7 +422,7 @@ pub fn strip_bbox(strips: &[Strip]) -> Option { } pub(crate) mod unpremultiply { - use fearless_simd::{Simd, SimdBase, u8x16, u16x16}; + use fearless_simd::{prelude::*, u8x16, u16x16}; trait Div256Ext { /// Divide by 256, rounding to the nearest integer. @@ -393,17 +431,13 @@ pub(crate) mod unpremultiply { fn div_256(self) -> Self; } - impl Div256Ext for u16 { - #[inline(always)] - fn div_256(self) -> Self { - (self + 128) >> 8 - } - } - - impl Div256Ext for u16x16 { + impl Div256Ext for T + where + T: core::ops::Add + core::ops::Shr, + { #[inline(always)] fn div_256(self) -> Self { - (self + Self::splat(self.simd, 128)) >> 8 + (self + 128_u16) >> 8_u32 } } @@ -451,9 +485,10 @@ pub(crate) mod unpremultiply { } #[inline(always)] - pub(crate) fn simd(simd: S, component: u8x16, reciprocal: u16x16) -> u8x16 { - let product = simd.widen_u8x16(component) * reciprocal; - simd.narrow_u16x16(product.div_256()) + pub(crate) fn simd(_simd: S, component: u8x16, reciprocal: u16x16) -> u8x16 { + let component = super::widen(component); + let product = (component * reciprocal).div_256(); + super::narrow(product) } #[cfg(test)] diff --git a/sparse_strips/vello_cpu/CHANGELOG.md b/sparse_strips/vello_cpu/CHANGELOG.md index de996e828..f7a34d4b0 100644 --- a/sparse_strips/vello_cpu/CHANGELOG.md +++ b/sparse_strips/vello_cpu/CHANGELOG.md @@ -10,7 +10,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] -This release has an [MSRV][] of 1.88. +This release has an [MSRV][] of 1.89. ## [0.2.0][] - 2026-08-07 diff --git a/sparse_strips/vello_cpu/Cargo.toml b/sparse_strips/vello_cpu/Cargo.toml index 2c9ad5588..e12597297 100644 --- a/sparse_strips/vello_cpu/Cargo.toml +++ b/sparse_strips/vello_cpu/Cargo.toml @@ -26,6 +26,9 @@ ordered-channel = { workspace = true, optional = true, features = ["crossbeam-ch rayon = { workspace = true, optional = true } thread_local = { workspace = true, optional = true } +[dev-dependencies] +fearless_simd = { workspace = true, features = ["force_support_fallback"] } + [features] # If adding new features, also add to `src/lib.rs` default = ["std", "png", "text", "u8_pipeline"] diff --git a/sparse_strips/vello_cpu/README.md b/sparse_strips/vello_cpu/README.md index a32a07f9a..2454ae158 100644 --- a/sparse_strips/vello_cpu/README.md +++ b/sparse_strips/vello_cpu/README.md @@ -157,7 +157,7 @@ overview nevertheless. ## Minimum supported Rust Version (MSRV) -This version of Vello CPU has been verified to compile with **Rust 1.88** and later. +This version of Vello CPU has been verified to compile with **Rust 1.89** and later. Future versions of Vello CPU might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs b/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs index 9089d1d88..5a66904ce 100644 --- a/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs +++ b/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs @@ -94,11 +94,8 @@ impl crate::fine::Painter for BlurredRoundedRectFiller { let b = u8x16::from_f32(simd, simd.combine_f32x8(first.b, second.b)); let a = u8x16::from_f32(simd, simd.combine_f32x8(first.a, second.a)); - let combined = - simd.combine_u8x32(simd.combine_u8x16(r, g), simd.combine_u8x16(b, a)); - - simd.store_interleaved_128_u8x64( - combined, + simd.store_four_interleaved_u8x16( + [r, g, b, a], (&mut chunk[..]).try_into().unwrap(), ); } @@ -111,11 +108,13 @@ impl crate::fine::Painter for BlurredRoundedRectFiller { #[inline(always)] || { for chunk in buf.chunks_exact_mut(32) { - let (c1, c2) = self.next().unwrap().get(); - c1.simd - .store_interleaved_128_f32x16(c1, (&mut chunk[..16]).try_into().unwrap()); - c2.simd - .store_interleaved_128_f32x16(c2, (&mut chunk[16..]).try_into().unwrap()); + let [c1, c2] = self.next().unwrap().get(); + c1[0] + .simd + .store_four_interleaved_f32x4(c1, (&mut chunk[..16]).try_into().unwrap()); + c2[0] + .simd + .store_four_interleaved_f32x4(c2, (&mut chunk[16..]).try_into().unwrap()); } }, ); diff --git a/sparse_strips/vello_cpu/src/fine/highp/blend.rs b/sparse_strips/vello_cpu/src/fine/highp/blend.rs index 93b1f0107..16c712afa 100644 --- a/sparse_strips/vello_cpu/src/fine/highp/blend.rs +++ b/sparse_strips/vello_cpu/src/fine/highp/blend.rs @@ -51,25 +51,16 @@ fn mix_inner(src_c: f32x16, bg: f32x16, blend_mode: BlendMode) -> res_bg.g = apply_alpha(bg_a, src_a, unpremultiplied_src.g, mix_src.g); res_bg.b = apply_alpha(bg_a, src_a, unpremultiplied_src.b, mix_src.b); - let combined = simd.combine_f32x8( - simd.combine_f32x4(res_bg.r, res_bg.g), - simd.combine_f32x4(res_bg.b, src_a), - ); - let mut storage = [0.0; 16]; - simd.store_interleaved_128_f32x16(combined, &mut storage); + simd.store_four_interleaved_f32x4([res_bg.r, res_bg.g, res_bg.b, src_a], &mut storage); f32x16::from_slice(simd, &storage) } #[inline(always)] fn split(simd: S, input: f32x16) -> (Channels, f32x4) { let mut storage = [0.0; 16]; - simd.store_interleaved_128_f32x16(input, &mut storage); - let input_v = f32x16::from_slice(simd, &storage); - - let p1 = simd.split_f32x16(input_v); - let (r, g) = simd.split_f32x8(p1.0); - let (b, a) = simd.split_f32x8(p1.1); + input.store_slice(&mut storage); + let [r, g, b, a] = simd.load_four_interleaved_f32x4(&storage); (Channels { r, g, b }, a) } diff --git a/sparse_strips/vello_cpu/src/fine/lowp/blend.rs b/sparse_strips/vello_cpu/src/fine/lowp/blend.rs index aa7dbdc9b..336f84d97 100644 --- a/sparse_strips/vello_cpu/src/fine/lowp/blend.rs +++ b/sparse_strips/vello_cpu/src/fine/lowp/blend.rs @@ -4,7 +4,7 @@ use crate::fine::{Splat4thExt, highp, u8_to_f32}; use crate::peniko::{BlendMode, Mix}; use vello_common::fearless_simd::*; -use vello_common::util::{Div255Ext, f32_to_u8, normalized_mul_u8x32}; +use vello_common::util::{Div255Ext, f32_to_u8, normalized_mul_u8, saturating_narrow, widen}; pub(crate) fn mix(src_c: u8x32, bg_c: u8x32, blend_mode: BlendMode) -> u8x32 { src_c.simd.vectorize( @@ -79,12 +79,6 @@ fn try_u8_mix(blend_mode: BlendMode, src_c: u8x32, bg_c: u8x32) - }) } -#[inline(always)] -fn narrow_saturating_u16x32(simd: S, val: u16x32) -> u8x32 { - // In case we had an overflow, make sure to clamp back to `u8::MAX`. - simd.narrow_u16x32(val.min(u16x32::splat(simd, 255))) -} - macro_rules! u8_mix { ($name:ident, $calc:expr) => { struct $name; @@ -115,12 +109,11 @@ macro_rules! u8_mix { // M = S * (1 - Ab) + As * Ab * Cb * Cs // = S * (1 - Ab) + S * D u8_mix!(Multiply, |src_c: u8x32, bg_c: u8x32| { - let simd = src_c.simd; let one_minus_bg_a = 255 - bg_c.splat_4th(); - let p1 = normalized_mul_u8x32(src_c, one_minus_bg_a); - let p2 = normalized_mul_u8x32(src_c, bg_c); + let p1 = normalized_mul_u8(src_c, one_minus_bg_a); + let p2 = normalized_mul_u8(src_c, bg_c); - narrow_saturating_u16x32(simd, p1 + p2) + saturating_narrow(p1 + p2) }); // Screen: @@ -128,12 +121,11 @@ u8_mix!(Multiply, |src_c: u8x32, bg_c: u8x32| { // M = S * (1 - Ab) + As * D + S * Ab - S * D // = S + As * D - S * D u8_mix!(Screen, |src_c: u8x32, bg_c: u8x32| { - let simd = src_c.simd; - let p1 = normalized_mul_u8x32(src_c.splat_4th(), bg_c); - let p2 = normalized_mul_u8x32(src_c, bg_c); - let res = simd.widen_u8x32(src_c) + p1 - p2; + let p1 = normalized_mul_u8(src_c.splat_4th(), bg_c); + let p2 = normalized_mul_u8(src_c, bg_c); + let res = widen(src_c) + p1 - p2; - narrow_saturating_u16x32(simd, res) + saturating_narrow(res) }); // Overlay is hard-light with source and backdrop swapped. @@ -145,26 +137,24 @@ u8_mix!(Overlay, |src_c: u8x32, bg_c: u8x32| { // B(Cb, Cs) = min(Cb, Cs) // M = S * (1 - Ab) + min(S * Ab, D * As) u8_mix!(Darken, |src_c: u8x32, bg_c: u8x32| { - let simd = src_c.simd; let src_a = src_c.splat_4th(); let bg_a = bg_c.splat_4th(); - let p1 = normalized_mul_u8x32(src_c, 255 - bg_a); - let p2 = normalized_mul_u8x32(src_c, bg_a).min(normalized_mul_u8x32(bg_c, src_a)); + let p1 = normalized_mul_u8(src_c, 255 - bg_a); + let p2 = normalized_mul_u8(src_c, bg_a).min(normalized_mul_u8(bg_c, src_a)); - narrow_saturating_u16x32(simd, p1 + p2) + saturating_narrow(p1 + p2) }); // Lighten: // B(Cb, Cs) = max(Cb, Cs) // M = S * (1 - Ab) + max(S * Ab, D * As) u8_mix!(Lighten, |src_c: u8x32, bg_c: u8x32| { - let simd = src_c.simd; let src_a = src_c.splat_4th(); let bg_a = bg_c.splat_4th(); - let p1 = normalized_mul_u8x32(src_c, 255 - bg_a); - let p2 = normalized_mul_u8x32(src_c, bg_a).max(normalized_mul_u8x32(bg_c, src_a)); + let p1 = normalized_mul_u8(src_c, 255 - bg_a); + let p2 = normalized_mul_u8(src_c, bg_a).max(normalized_mul_u8(bg_c, src_a)); - narrow_saturating_u16x32(simd, p1 + p2) + saturating_narrow(p1 + p2) }); // Hard-light: @@ -178,15 +168,14 @@ u8_mix!(HardLight, |src_c: u8x32, bg_c: u8x32| { // B(Cb, Cs) = abs(Cb - Cs) // M = S * (1 - Ab) + abs(S * Ab - D * As) u8_mix!(Difference, |src_c: u8x32, bg_c: u8x32| { - let simd = src_c.simd; let src_a = src_c.splat_4th(); let bg_a = bg_c.splat_4th(); - let p1 = normalized_mul_u8x32(src_c, 255 - bg_a); - let p2 = normalized_mul_u8x32(src_c, bg_a); - let p3 = normalized_mul_u8x32(bg_c, src_a); + let p1 = normalized_mul_u8(src_c, 255 - bg_a); + let p2 = normalized_mul_u8(src_c, bg_a); + let p3 = normalized_mul_u8(bg_c, src_a); let diff = p2.max(p3) - p2.min(p3); - narrow_saturating_u16x32(simd, p1 + diff) + saturating_narrow(p1 + diff) }); // Exclusion: @@ -195,24 +184,24 @@ u8_mix!(Difference, |src_c: u8x32, bg_c: u8x32| { // = S + As * D - 2 * S * D u8_mix!(Exclusion, |src_c: u8x32, bg_c: u8x32| { let simd = src_c.simd; - let p1 = normalized_mul_u8x32(src_c.splat_4th(), bg_c); - let p2 = normalized_mul_u8x32(src_c, bg_c); - let res = simd.widen_u8x32(src_c) + p1; + let p1 = normalized_mul_u8(src_c.splat_4th(), bg_c); + let p2 = normalized_mul_u8(src_c, bg_c); + let res = widen(src_c) + p1; let sub = p2 + p2; let res = simd.select_u16x32(res.simd_ge(sub), res - sub, u16x32::splat(simd, 0)); - narrow_saturating_u16x32(simd, res) + saturating_narrow(res) }); #[inline(always)] fn hard_light_inner(src_c: u8x32, bg_c: u8x32, condition: u8x32) -> u8x32 { let simd = src_c.simd; - let src = simd.widen_u8x32(src_c); - let bg = simd.widen_u8x32(bg_c); - let src_a = simd.widen_u8x32(src_c.splat_4th()); - let bg_a = simd.widen_u8x32(bg_c.splat_4th()); - let condition_a = simd.widen_u8x32(condition.splat_4th()); - let condition = simd.widen_u8x32(condition); + let src = widen(src_c); + let bg = widen(bg_c); + let src_a = widen(src_c.splat_4th()); + let bg_a = widen(bg_c.splat_4th()); + let condition_a = widen(condition.splat_4th()); + let condition = widen(condition); let base = src * (255 - bg_a); // Multiply branch: As * Ab * 2 * Cb * Cs = 2 * S * D. @@ -230,7 +219,7 @@ fn hard_light_inner(src_c: u8x32, bg_c: u8x32, condition: u8x32( @@ -60,9 +60,9 @@ fn compose_inner( if let Some(alpha_mask) = alpha_mask { let alpha_mask_inv = 255 - alpha_mask; - let p1 = simd.widen_u8x32(alpha_mask) * simd.widen_u8x32(res); - let p2 = simd.widen_u8x32(alpha_mask_inv) * simd.widen_u8x32(bg_c); - res = simd.narrow_u16x32((p1 + p2).div_255()); + let p1 = widen(alpha_mask) * widen(res); + let p2 = widen(alpha_mask_inv) * widen(bg_c); + res = narrow((p1 + p2).div_255()); } res @@ -82,11 +82,10 @@ macro_rules! compose { let fb = $fb(simd, al_s, al_b); if $sat { - simd.narrow_u16x32( - (simd.widen_u8x32(src_c.normalized_mul(fa)) - + simd.widen_u8x32(fb.normalized_mul(bg_c))) - .min(u16x32::splat(simd, 255)) - .max(u16x32::splat(simd, 0)), + narrow( + (widen(src_c.normalized_mul(fa)) + widen(fb.normalized_mul(bg_c))) + .min(u16x32::splat(simd, 255)) + .max(u16x32::splat(simd, 0)), ) } else { src_c.normalized_mul(fa) + fb.normalized_mul(bg_c) diff --git a/sparse_strips/vello_cpu/src/fine/lowp/image.rs b/sparse_strips/vello_cpu/src/fine/lowp/image.rs index c5bbfc734..96984dd7e 100644 --- a/sparse_strips/vello_cpu/src/fine/lowp/image.rs +++ b/sparse_strips/vello_cpu/src/fine/lowp/image.rs @@ -5,10 +5,10 @@ use crate::fine::PosExt; use crate::fine::common::image::{ImagePainterData, extend, fract_floor, sample}; use crate::fine::macros::u8x16_painter; use vello_common::encode::EncodedImage; -use vello_common::fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, u8x16, u16x16}; +use vello_common::fearless_simd::{f32x4, prelude::*, u8x16, u16x16}; use vello_common::pixmap::Pixmap; use vello_common::simd::element_wise_splat; -use vello_common::util::{Div255Ext, f32_to_u8}; +use vello_common::util::{Div255Ext, f32_to_u8, narrow, widen}; /// A faster bilinear image renderer for the u8 pipeline. #[derive(Debug)] @@ -79,8 +79,8 @@ impl Iterator for BilinearImagePainter<'_, S> { fract_floor(y_positions + 0.5).mul_add(255.0, 0.5), )); - let fx = self.simd.widen_u8x16(fx); - let fy = self.simd.widen_u8x16(fy); + let fx = widen(fx); + let fy = widen(fy); let fx_inv = u16x16::splat(self.simd, 255) - fx; let fy_inv = u16x16::splat(self.simd, 255) - fy; @@ -89,22 +89,14 @@ impl Iterator for BilinearImagePainter<'_, S> { let y_pos1 = extend_y(y_positions - 0.5); let y_pos2 = extend_y(y_positions + 0.5); - let p00 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos1)); - let p10 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos1)); - let p01 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos1, y_pos2)); - let p11 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos2, y_pos2)); + let p00 = widen(sample(self.simd, &self.data, x_pos1, y_pos1)); + let p10 = widen(sample(self.simd, &self.data, x_pos2, y_pos1)); + let p01 = widen(sample(self.simd, &self.data, x_pos1, y_pos2)); + let p11 = widen(sample(self.simd, &self.data, x_pos2, y_pos2)); let ip1 = (p00 * fx_inv + p10 * fx).div_255(); let ip2 = (p01 * fx_inv + p11 * fx).div_255(); - let res = self.simd.narrow_u16x16((ip1 * fy_inv + ip2 * fy).div_255()); + let res = narrow((ip1 * fy_inv + ip2 * fy).div_255()); self.data.cur_pos += self.data.image.x_advance; @@ -178,7 +170,7 @@ impl<'a, S: Simd> PlainBilinearImagePainter<'a, S> { simd, fract_floor(y_positions + 0.5).mul_add(255.0, 0.5), )); - let fy = simd.widen_u8x16(fy); + let fy = widen(fy); let fy_inv = u16x16::splat(simd, 255) - fy; let cur_x_pos = f32x4::splat_pos( @@ -232,29 +224,19 @@ impl Iterator for PlainBilinearImagePainter<'_, S> { self.simd, fract_floor(x_plus_half).mul_add(255.0, 0.5), )); - let fx = self.simd.widen_u8x16(fx); + let fx = widen(fx); let fx_inv = u16x16::splat(self.simd, 255) - fx; // Sample the 4 corners using pre-computed y positions - let p00 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos1, self.y_pos1)); - let p10 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos2, self.y_pos1)); - let p01 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos1, self.y_pos2)); - let p11 = self - .simd - .widen_u8x16(sample(self.simd, &self.data, x_pos2, self.y_pos2)); + let p00 = widen(sample(self.simd, &self.data, x_pos1, self.y_pos1)); + let p10 = widen(sample(self.simd, &self.data, x_pos2, self.y_pos1)); + let p01 = widen(sample(self.simd, &self.data, x_pos1, self.y_pos2)); + let p11 = widen(sample(self.simd, &self.data, x_pos2, self.y_pos2)); // Bilinear interpolation let ip1 = (p00 * fx_inv + p10 * fx).div_255(); let ip2 = (p01 * fx_inv + p11 * fx).div_255(); - let res = self - .simd - .narrow_u16x16((ip1 * self.fy_inv + ip2 * self.fy).div_255()); + let res = narrow((ip1 * self.fy_inv + ip2 * self.fy).div_255()); self.cur_x_pos += self.advance; diff --git a/sparse_strips/vello_cpu/src/fine/lowp/mod.rs b/sparse_strips/vello_cpu/src/fine/lowp/mod.rs index f8a7f5e02..5717e22f2 100644 --- a/sparse_strips/vello_cpu/src/fine/lowp/mod.rs +++ b/sparse_strips/vello_cpu/src/fine/lowp/mod.rs @@ -31,7 +31,7 @@ use vello_common::mask::Mask; use vello_common::paint::{PremulColor, Tint, TintMode}; use vello_common::pixmap::Pixmap; use vello_common::tile::Tile; -use vello_common::util::Div255Ext; +use vello_common::util::{narrow, normalized_mul_u8}; /// The kernel for doing rendering using u8/u16. #[derive(Clone, Copy, Debug)] @@ -138,10 +138,7 @@ impl FineKernel for U8Kernel { || { for el in dest.chunks_exact_mut(16) { let loaded = u8x16::from_slice(simd, el); - let mulled = simd.narrow_u16x16( - (simd.widen_u8x16(loaded) * simd.widen_u8x16(src.next().unwrap())) - .div_255(), - ); + let mulled = narrow(normalized_mul_u8(loaded, src.next().unwrap())); mulled.store_slice(el); } }, @@ -360,10 +357,9 @@ fn pack_block(simd: S, scratch: &[u8], width: usize, region: &mut Regio for col in scratch[..width * TILE_HEIGHT_COMPONENTS].chunks_exact(CHUNK_LENGTH) { let casted: &[u32; 16] = cast_slice::(col).try_into().unwrap(); - let loaded = simd.load_interleaved_128_u32x16(casted).to_bytes(); - let (loaded_lo, loaded_hi) = simd.split_u8x64(loaded); - let (loaded_1, loaded_2) = simd.split_u8x32(loaded_lo); - let (loaded_3, loaded_4) = simd.split_u8x32(loaded_hi); + let [loaded_1, loaded_2, loaded_3, loaded_4] = simd + .load_four_interleaved_u32x4(casted) + .map(u32x4::to_bytes); let (dest0, rest0) = row0.split_at_mut(Tile::WIDTH as usize * COLOR_COMPONENTS); let (dest1, rest1) = row1.split_at_mut(Tile::WIDTH as usize * COLOR_COMPONENTS); @@ -440,9 +436,7 @@ fn unpack_block(simd: S, region: &mut Region<'_>, width: usize, scratch let r1 = f32x4::from_bytes(u8x16::from_slice(simd, src1)); let r2 = f32x4::from_bytes(u8x16::from_slice(simd, src2)); let r3 = f32x4::from_bytes(u8x16::from_slice(simd, src3)); - let combined = simd.combine_f32x8(simd.combine_f32x4(r0, r1), simd.combine_f32x4(r2, r3)); - - simd.store_interleaved_128_f32x16(combined, col); + simd.store_four_interleaved_f32x4([r0, r1, r2, r3], col); row0 = rest0; row1 = rest1; @@ -473,7 +467,7 @@ mod fill { use crate::fine::lowp::compose::ComposeExt; use crate::peniko::{BlendMode, Mix}; use vello_common::fearless_simd::*; - use vello_common::util::normalized_mul_u8x32; + use vello_common::util::{narrow, normalized_mul_u8}; /// Applies blend mode compositing to a buffer without per-pixel masks. pub(super) fn blend>>( @@ -551,12 +545,12 @@ mod fill { /// This implements the Porter-Duff "source over" operator. #[inline(always)] fn alpha_composite_inner( - s: S, + _s: S, bg: u8x32, src: u8x32, one_minus_alpha: u8x32, ) -> u8x32 { - s.narrow_u16x32(normalized_mul_u8x32(bg, one_minus_alpha)) + src + narrow(normalized_mul_u8(bg, one_minus_alpha)) + src } } @@ -571,7 +565,7 @@ mod alpha_fill { use crate::fine::lowp::{blend, extract_masks}; use crate::peniko::{BlendMode, Mix}; use vello_common::fearless_simd::*; - use vello_common::util::{Div255Ext, normalized_mul_u8x32}; + use vello_common::util::{Div255Ext, narrow, normalized_mul_u8, widen}; /// Applies blend mode compositing with per-pixel alpha masks. pub(super) fn blend>>( @@ -672,11 +666,14 @@ mod alpha_fill { let bg_v = u8x32::from_slice(s, dest); let mask_v = extract_masks(s, masks); - let inv_src_a_mask_a = one - s.narrow_u16x32(normalized_mul_u8x32(src_a, mask_v)); - - let p1 = s.widen_u8x32(bg_v) * s.widen_u8x32(inv_src_a_mask_a); - let p2 = s.widen_u8x32(src_c) * s.widen_u8x32(mask_v); - let res = s.narrow_u16x32((p1 + p2).div_255()); + let inv_src_a_mask_a = one - narrow(normalized_mul_u8(src_a, mask_v)); + + let bg = widen(bg_v); + let inv = widen(inv_src_a_mask_a); + let src = widen(src_c); + let mask = widen(mask_v); + let result = (bg * inv + src * mask).div_255(); + let res = narrow(result); res.store_slice(dest); }, diff --git a/sparse_strips/vello_cpu/src/fine/mod.rs b/sparse_strips/vello_cpu/src/fine/mod.rs index fdfdb7cab..923f5c7d0 100644 --- a/sparse_strips/vello_cpu/src/fine/mod.rs +++ b/sparse_strips/vello_cpu/src/fine/mod.rs @@ -1176,26 +1176,15 @@ pub(crate) struct ShaderResultF32 { impl ShaderResultF32 { /// Convert from planar format to interleaved RGBA format. /// - /// Returns two f32x16 vectors containing 8 pixels (4 RGBA components each) - /// with channels interleaved in the standard RGBA order. + /// Returns two sets of four f32x4 vectors containing 8 pixels (4 RGBA components each), #[inline(always)] - pub(crate) fn get(&self) -> (f32x16, f32x16) { + pub(crate) fn get(&self) -> [[f32x4; 4]; 2] { let (r_1, r_2) = self.r.simd.split_f32x8(self.r); let (g_1, g_2) = self.g.simd.split_f32x8(self.g); let (b_1, b_2) = self.b.simd.split_f32x8(self.b); let (a_1, a_2) = self.a.simd.split_f32x8(self.a); - let first = self.r.simd.combine_f32x8( - self.r.simd.combine_f32x4(r_1, g_1), - self.r.simd.combine_f32x4(b_1, a_1), - ); - - let second = self.r.simd.combine_f32x8( - self.r.simd.combine_f32x4(r_2, g_2), - self.r.simd.combine_f32x4(b_2, a_2), - ); - - (first, second) + [[r_1, g_1, b_1, a_1], [r_2, g_2, b_2, a_2]] } } diff --git a/sparse_strips/vello_cpu/src/lib.rs b/sparse_strips/vello_cpu/src/lib.rs index a0cd312d8..630bedce6 100644 --- a/sparse_strips/vello_cpu/src/lib.rs +++ b/sparse_strips/vello_cpu/src/lib.rs @@ -158,7 +158,7 @@ mod filter; mod record; #[cfg(feature = "text")] mod text; -#[cfg(all(feature = "text", feature = "std", debug_assertions))] +#[cfg(all(feature = "text", feature = "std", debug_assertions, not(doc)))] mod text_debug; mod util; diff --git a/sparse_strips/vello_cpu/src/util.rs b/sparse_strips/vello_cpu/src/util.rs index 97d992c27..aa11c5c16 100644 --- a/sparse_strips/vello_cpu/src/util.rs +++ b/sparse_strips/vello_cpu/src/util.rs @@ -3,10 +3,10 @@ use crate::peniko::ImageQuality; use vello_common::encode::EncodedImage; -use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x32}; +use vello_common::fearless_simd::{f32x4, prelude::*, u8x32}; use vello_common::math::FloatExt; use vello_common::tile::Tile; -use vello_common::util::Div255Ext; +use vello_common::util::{narrow, normalized_mul_u8}; pub(crate) mod scalar { /// Perform an approximate division by 255. @@ -71,8 +71,7 @@ pub(crate) trait NormalizedMulExt { impl NormalizedMulExt for u8x32 { #[inline(always)] fn normalized_mul(self, other: Self) -> Self { - let divided = (self.simd.widen_u8x32(self) * other.simd.widen_u8x32(other)).div_255(); - self.simd.narrow_u16x32(divided) + narrow(normalized_mul_u8(self, other)) } } diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs index a35c53ae4..f021a630a 100644 --- a/sparse_strips/vello_dev_macros/src/test.rs +++ b/sparse_strips/vello_dev_macros/src/test.rs @@ -95,6 +95,14 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr &format!("{input_fn_name}_cpu_f32_neon"), input_fn_name.span(), ); + let u8_fn_name_sse2 = Ident::new( + &format!("{input_fn_name}_cpu_u8_sse2"), + input_fn_name.span(), + ); + let f32_fn_name_sse2 = Ident::new( + &format!("{input_fn_name}_cpu_f32_sse2"), + input_fn_name.span(), + ); let u8_fn_name_sse42 = Ident::new( &format!("{input_fn_name}_cpu_u8_sse42"), input_fn_name.span(), @@ -111,6 +119,14 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr &format!("{input_fn_name}_cpu_f32_avx2"), input_fn_name.span(), ); + let u8_fn_name_avx512 = Ident::new( + &format!("{input_fn_name}_cpu_u8_avx512"), + input_fn_name.span(), + ); + let f32_fn_name_avx512 = Ident::new( + &format!("{input_fn_name}_cpu_f32_avx512"), + input_fn_name.span(), + ); let u8_fn_name_wasm = Ident::new( &format!("{input_fn_name}_cpu_u8_wasm"), input_fn_name.span(), @@ -146,10 +162,14 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr let f32_fn_name_str_scalar = f32_fn_name_scalar.to_string(); let u8_fn_name_str_neon = u8_fn_name_neon.to_string(); let f32_fn_name_str_neon = f32_fn_name_neon.to_string(); + let u8_fn_name_str_sse2 = u8_fn_name_sse2.to_string(); + let f32_fn_name_str_sse2 = f32_fn_name_sse2.to_string(); let u8_fn_name_str_sse42 = u8_fn_name_sse42.to_string(); let f32_fn_name_str_sse42 = f32_fn_name_sse42.to_string(); let u8_fn_name_str_avx2 = u8_fn_name_avx2.to_string(); let f32_fn_name_str_avx2 = f32_fn_name_avx2.to_string(); + let u8_fn_name_str_avx512 = u8_fn_name_avx512.to_string(); + let f32_fn_name_str_avx512 = f32_fn_name_avx512.to_string(); let u8_fn_name_wasm_str = u8_fn_name_wasm.to_string(); let f32_fn_name_wasm_str = f32_fn_name_wasm.to_string(); let multithreaded_fn_name_str = multithreaded_fn_name.to_string(); @@ -310,6 +330,12 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr #[cfg(not(target_arch = "aarch64"))] let has_neon = false; + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] + let has_sse2 = false; + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + let has_sse2 = + std::arch::is_x86_feature_detected!("sse2") && std::arch::is_x86_feature_detected!("fxsr"); + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] let has_sse42 = false; #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] @@ -321,6 +347,12 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr let has_avx2 = std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma"); + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] + let has_avx512 = false; + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + let has_avx512 = std::arch::is_x86_feature_detected!("avx512f") + || std::env::var_os("VELLO_TEST_AVX512").is_some(); + let wasm_simd_level = quote! {if cfg!(target_feature = "simd128") { "wasm_simd128" } else { @@ -415,6 +447,31 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr invoke_test.clone(), ); + let sse2_u8_snippet = cpu_snippet( + u8_fn_name_sse2, + u8_fn_name_str_sse2, + input_fn_name_str.clone(), + cpu_u8_tolerance_simd, + false, + 0, + quote! {"sse2"}, + skip_cpu | !has_sse2, + quote! { RenderMode::OptimizeSpeed }, + invoke_test.clone(), + ); + let sse2_f32_snippet = cpu_snippet( + f32_fn_name_sse2, + f32_fn_name_str_sse2, + input_fn_name_str.clone(), + cpu_f32_tolerance_simd, + false, + 0, + quote! {"sse2"}, + skip_cpu | !has_sse2, + quote! { RenderMode::OptimizeQuality }, + invoke_test.clone(), + ); + let sse42_u8_snippet = cpu_snippet( u8_fn_name_sse42, u8_fn_name_str_sse42, @@ -467,6 +524,31 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr invoke_test.clone(), ); + let avx512_u8_snippet = cpu_snippet( + u8_fn_name_avx512, + u8_fn_name_str_avx512, + input_fn_name_str.clone(), + cpu_u8_tolerance_simd, + false, + 0, + quote! {"avx512"}, + skip_cpu | !has_avx512, + quote! { RenderMode::OptimizeSpeed }, + invoke_test.clone(), + ); + let avx512_f32_snippet = cpu_snippet( + f32_fn_name_avx512, + f32_fn_name_str_avx512, + input_fn_name_str.clone(), + cpu_f32_tolerance_simd, + false, + 0, + quote! {"avx512"}, + skip_cpu | !has_avx512, + quote! { RenderMode::OptimizeQuality }, + invoke_test.clone(), + ); + let cached_cpu_f32_fn_name = Ident::new( &format!("{input_fn_name}_cpu_f32_scalar_cached"), input_fn_name.span(), @@ -594,20 +676,28 @@ pub(crate) fn vello_test_inner(attr: TokenStream, item: TokenStream) -> TokenStr #neon_u8_snippet + #sse2_u8_snippet + #sse42_u8_snippet #avx2_u8_snippet + #avx512_u8_snippet + #u8_snippet_wasm #f32_snippet #neon_f32_snippet + #sse2_f32_snippet + #sse42_f32_snippet #avx2_f32_snippet + #avx512_f32_snippet + #f32_snippet_wasm #multi_threaded_snippet diff --git a/sparse_strips/vello_hybrid/CHANGELOG.md b/sparse_strips/vello_hybrid/CHANGELOG.md index 625fd1b5d..d32e78717 100644 --- a/sparse_strips/vello_hybrid/CHANGELOG.md +++ b/sparse_strips/vello_hybrid/CHANGELOG.md @@ -10,7 +10,7 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe ## [Unreleased] -This release has an [MSRV][] of 1.88. +This release has an [MSRV][] of 1.89. ## [0.2.0][] - 2026-08-07 diff --git a/sparse_strips/vello_hybrid/README.md b/sparse_strips/vello_hybrid/README.md index b9a29f6b5..ddb20beb5 100644 --- a/sparse_strips/vello_hybrid/README.md +++ b/sparse_strips/vello_hybrid/README.md @@ -90,7 +90,7 @@ these and other limitations in future releases. ## Minimum supported Rust Version (MSRV) -This version of Vello Hybrid has been verified to compile with **Rust 1.88** and later. +This version of Vello Hybrid has been verified to compile with **Rust 1.89** and later. Future versions of Vello Hybrid might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/sparse_strips/vello_sparse_shaders/README.md b/sparse_strips/vello_sparse_shaders/README.md index 64a211478..646cdf6e3 100644 --- a/sparse_strips/vello_sparse_shaders/README.md +++ b/sparse_strips/vello_sparse_shaders/README.md @@ -44,7 +44,7 @@ cargo run -p vello_sparse_shaders --features glsl,unminified ## Minimum supported Rust Version (MSRV) -This version of Vello Hybrid Shaders has been verified to compile with **Rust 1.88** and later. +This version of Vello Hybrid Shaders has been verified to compile with **Rust 1.89** and later. Future versions of Vello Hybrid might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases. diff --git a/sparse_strips/vello_sparse_tests/tests/util.rs b/sparse_strips/vello_sparse_tests/tests/util.rs index dd8702efa..1e467dad1 100644 --- a/sparse_strips/vello_sparse_tests/tests/util.rs +++ b/sparse_strips/vello_sparse_tests/tests/util.rs @@ -144,9 +144,15 @@ pub(crate) fn get_ctx_with_depth_buffer( .expect("wasm simd128 should be available"), ), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + "sse2" => Level::Sse2( + Level::try_detect() + .and_then(Level::as_sse2) + .expect("SSE2 should be available"), + ), + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] "sse42" => { if std::arch::is_x86_feature_detected!("sse4.2") { - Level::Sse4_2(unsafe { fearless_simd::Sse4_2::new_unchecked() }) + Level::Sse4_2(unsafe { fearless_simd::Sse4_2::assume_supported() }) } else { panic!("sse4.2 feature not detected"); } @@ -156,11 +162,17 @@ pub(crate) fn get_ctx_with_depth_buffer( if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma") { - Level::Avx2(unsafe { fearless_simd::Avx2::new_unchecked() }) + Level::Avx2(unsafe { fearless_simd::Avx2::assume_supported() }) } else { panic!("avx2 or fma feature not detected"); } } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + "avx512" => Level::Avx512( + Level::try_detect() + .and_then(Level::as_avx512) + .expect("Ice Lake AVX-512 should be available"), + ), "fallback" => Level::fallback(), _ => panic!("unknown level: {level}"), };