Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -346,6 +346,56 @@ jobs:
- name: cargo test --doc
run: cargo test --doc --workspace --locked --all-features --no-fail-fast

test-avx512:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m concerned this adds more CI time by rerunning nearly the entire test suite for relatively uncommon AVX-512 hardware. Could we keep SSE2 coverage but run AVX-512 only when SIMD-related code changes, or in a nightly/weekly scheduled workflow instead of on every PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not nearly as bad as it appears. CI time for this job is under 3 minutes; it does not make CI take longer overall.

And it's not as uncommon as you might expect. Steam hardware survey puts AVX-512 availability at 23%, which is far ahead of both SSE4.2-only (2%) and SSE2-only (1%) systems in their data.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with @Shnatsel here, it definitely doesn't impact CI run times since it's not even close to being the bottleneck, and AVX-512 is not that uncommon anymore, especially on server hardware, which is also relevant for our internal use cases. The only downside is that it makes the CI file uglier. So unless you are strongly opposed, I think it would be better to keep this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond runner time, this brings the workflow to 21 jobs while Linebender’s Free plan allows 20 concurrent GitHub-hosted jobs. The AVX-512 job took ~4 minutes and finished well before the slowest job, so it likely won’t materially increase total CI duration, but it can still occupy a scarce slot and cause queueing when the organization is busy.

Anyway, I’m not blocking this — just expressing concern about the gradual increase in CI infra usage, which I think we’ll need to address in the near future.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSE4.2-only (2%) and SSE2-only (1%)

Just to clarify, what do you mean by “SSE4.2-only (2%) and SSE2-only (1%)” here? The Steam survey seems to report support for each instruction set independently rather than mutually exclusive “only” buckets.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I subtracted the AVX2 percentage from the SSE4.2 to get "SSE4.2 only". Same for "SSE2 only": SSE2 - SSE4.2.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, didn't know about the 20 concurrent jobs limit, thanks for pointing it out!

# 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
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add browser wasm support to the benchmark harness? The web is an important target for us, and this would give us much better insight into its performance; once supported, it would also be useful to rerun the benchmarks for this fearless_simd bump to identify any improvements/regressions.


# Unlike the other crates, please do not update these before a release
# unless absolutely necessary.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion glifo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion glifo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion sparse_strips/vello_common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion sparse_strips/vello_common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions sparse_strips/vello_common/src/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -461,7 +461,7 @@ fn intersect_impl<S: Simd>(
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());
}
}
Expand Down
31 changes: 10 additions & 21 deletions sparse_strips/vello_common/src/pixmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -462,17 +462,13 @@ fn unpremultiply_rgba8_impl<S: Simd>(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) {
Expand All @@ -486,27 +482,20 @@ fn unpremultiply_rgba8_impl<S: Simd>(simd: S, data: &mut [u8]) {
#[inline(always)]
fn premultiply_rgba8_impl<S: Simd>(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<S>| 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();
Expand Down
16 changes: 5 additions & 11 deletions sparse_strips/vello_common/src/tile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
Comment on lines 639 to +790

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On AVX2 wit the old code I got a regression of around 25% with new fearless_simd, because the branchless selects were turned into branches. These two changes fix this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use std's select_unpredictable here to keep the logic in the scalar domain. I don't know if that will give you any performance benefits or not.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think I'll keep it for now since I've already benchmarked this, but thanks for pointing it out!

let next = fractional_coverage
.mul_add(f_dir_v, current - double_count);
next.store_slice(target_row);
Expand Down
Loading
Loading