Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,32 @@ jobs:
- name: Build and test
run: cargo test --target x86_64-pc-windows-gnu --verbose

build-windows-msvc:
name: Windows x86_64 (MSVC)
runs-on: windows-latest

steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: '3.x'

- name: Install Meson and Ninja
run: python -m pip install meson ninja

- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: x86_64-pc-windows-msvc

- name: Build and test
run: cargo test --target x86_64-pc-windows-msvc --verbose

build-ios:
name: iOS compile and link
runs-on: macos-latest
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ This repository publishes `vmaf-head-sys`, raw Rust FFI bindings to a statically
## Generated And Vendored Files

- Do not edit `src/bindings.rs` by hand. Change `vendor_vmaf.py`, then run `python3 vendor_vmaf.py --bindings-only`.
- Update `vendored/vmaf` only through `python3 vendor_vmaf.py`. Preserve upstream source, licenses, formatting, and model data byte-for-byte.
- Update `vendored/vmaf` only through `python3 vendor_vmaf.py`. Keep local source changes in ordered `patches/*.patch` files; the script applies them after copying upstream. Never edit the vendored tree by hand.
- Keep patches focused, preserve untouched upstream source and model data byte-for-byte, and fail clearly when a patch no longer applies.
- `vendored/VMAF_VERSION` must identify the exact upstream commit. Verify it with `python3 check_vmaf_version.py`.
- Keep bindgen output target-independent. In particular, retain `--no-layout-tests` so committed bindings compile on 32-bit targets.

## Native Build Invariants

- `build.rs` owns Meson configuration, static linking, platform runtime libraries, and generated iOS/Android cross files.
- Meson and Ninja are required for native builds. `xxd` is required for built-in models.
- x86 and x86_64 builds always compile NASM and AVX2 paths, including with `--no-default-features`; runtime CPUID dispatch must remain enabled.
- x86 and x86_64 builds compile NASM and AVX2 paths, including with `--no-default-features`; runtime CPUID dispatch must remain enabled. MSVC builds are the exception and use scalar kernels.
- MSVC builds use private pthread and POSIX translation headers added by the vendor patch under `vendored/vmaf/libvmaf/src/compat/msvc`.
- AArch64 builds use VMAF's NEON paths when the `asm` feature is enabled.
- Preserve compile-and-link support for every target in `.github/workflows/ci.yml`, including iOS device/simulators and all four Android ABIs.
- Android uses NDK libc++ (`c++_shared`), not GNU `stdc++`. NDK r29 x86 links require Clang builtins through `cargo-ndk --link-builtins`.
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Building requires:
- Meson and Ninja
- A C and C++ compiler
- `xxd` when `built-in-models` is enabled
- NASM on x86 and x86_64 platforms
- NASM on x86 and x86_64 platforms except MSVC

CUDA is intentionally disabled. This crate binds the portable CPU API and does not expose `libvmaf_cuda.h`.

Expand All @@ -32,6 +32,8 @@ CUDA is intentionally disabled. This crate binds the portable CPU API and does n

On x86 and x86_64, NASM support and AVX2 kernels are always built, including with `--no-default-features`. VMAF still uses CPUID runtime dispatch, so AVX2 instructions execute only when both the CPU and operating system support them. This avoids illegal-instruction crashes on older x86 systems.

MSVC builds use a private Windows-native pthread translation layer and scalar C kernels. Windows GNU builds retain the assembly and AVX2 kernels.

## Usage

```toml
Expand All @@ -57,6 +59,8 @@ CI compiles and links these mobile targets:
- `x86_64-linux-android`
- `i686-linux-android` (`x86`)

CI also builds and runs the test suite for both `x86_64-pc-windows-gnu` and `x86_64-pc-windows-msvc`.

For Android final binaries, `c++_shared` must be packaged with the application. With `cargo-ndk` and NDK r29, use:

```bash
Expand All @@ -72,6 +76,8 @@ The vendored VMAF source is tracked in `vendored/VMAF_VERSION`. Run the update s
python vendor_vmaf.py
```

The update script applies the ordered patches under `patches/` after copying upstream VMAF. Patch failures stop the update so upstream changes cannot silently drop local platform fixes.

Regenerate bindings without downloading VMAF:

```bash
Expand Down
25 changes: 21 additions & 4 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ fn build_vmaf() -> Result<(), Box<dyn std::error::Error>> {
let target = env::var("TARGET")?;
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let target_os = env::var("CARGO_CFG_TARGET_OS")?;
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let is_x86 = matches!(target_arch.as_str(), "x86" | "x86_64");
let is_msvc = target_os == "windows" && target_env == "msvc";
if env::var_os("DOCS_RS").is_some() || target_arch.starts_with("wasm") {
return Ok(());
}
Expand Down Expand Up @@ -60,11 +62,11 @@ fn build_vmaf() -> Result<(), Box<dyn std::error::Error>> {
let ninja = env::var_os("NINJA").unwrap_or_else(|| "ninja".into());
require_build_tool(&meson, "Meson", "MESON")?;
require_build_tool(&ninja, "Ninja", "NINJA")?;
if is_x86 {
if is_x86 && !is_msvc {
require_nasm()?;
}

let asm_enabled = is_x86 || env::var_os("CARGO_FEATURE_ASM").is_some();
let asm_enabled = !is_msvc && (is_x86 || env::var_os("CARGO_FEATURE_ASM").is_some());

let mut setup = Command::new(&meson);
setup
Expand All @@ -84,6 +86,12 @@ fn build_vmaf() -> Result<(), Box<dyn std::error::Error>> {
.arg(format!("-Denable_asm={asm_enabled}"))
.arg(feature_option("float", "enable_float"));

if is_msvc {
setup
.arg("--native-file")
.arg(generate_msvc_native_file(&out_dir)?);
}

let cross_file = match env::var_os("VMAF_MESON_CROSS_FILE") {
Some(path) => Some(PathBuf::from(path)),
None => generate_mobile_cross_file(&target, &target_arch, &target_os, &out_dir)?,
Expand Down Expand Up @@ -121,7 +129,6 @@ fn build_vmaf() -> Result<(), Box<dyn std::error::Error>> {
);
println!("cargo:rustc-link-lib=static=vmaf");

let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
match (target_os.as_str(), target_env.as_str()) {
("macos" | "ios", _) => println!("cargo:rustc-link-lib=c++"),
("android", _) => {
Expand All @@ -133,7 +140,7 @@ fn build_vmaf() -> Result<(), Box<dyn std::error::Error>> {
}
println!("cargo:rustc-link-lib=c++_shared");
}
("windows", "msvc") => println!("cargo:rustc-link-lib=msvcp140"),
("windows", "msvc") => {}
("windows", _) => println!("cargo:rustc-link-lib=stdc++"),
_ => println!("cargo:rustc-link-lib=stdc++"),
}
Expand All @@ -159,6 +166,16 @@ fn feature_option(feature: &str, option: &str) -> String {
format!("-D{option}={value}")
}

fn generate_msvc_native_file(out_dir: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
let contents = format!(
"[built-in options]\nc_args = [{}]\n",
meson_string("/D_USE_MATH_DEFINES"),
);
let path = out_dir.join("meson-msvc.ini");
fs::write(&path, contents)?;
Ok(path)
}

fn generate_mobile_cross_file(
target: &str,
target_arch: &str,
Expand Down
Loading
Loading