Skip to content

feat(api): split libvmaf into libvmafx.so.1 and a compat libvmaf.so.3 on the VMAFx API (RC4 WP6) - #2303

Merged
lusoris merged 1 commit into
masterfrom
rc4/api-wp6-compat
Oct 8, 2026
Merged

lusoris merged 1 commit into
masterfrom
rc4/api-wp6-compat

Conversation

@lusoris

@lusoris lusoris commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

RC4 work package 6: the library is split into libvmafx.so.1 (engine + VMAFx API, vmafx_* exports only) and a thin libvmaf.so.3 that implements the libvmaf API on the exported vmafx_* functions alone, so a libvmaf function the new API cannot express fails the link (ADR-1852 decision D3). WP5 #2288 is on master; this PR's diff is against master. Implementation decisions are in ADR-2094 (Accepted by maintainer popup on 2026-10-06: "Forced header (Recommended)", cited in its References; it implements ADR-1852 D3 and D7 and design section 2.11).

What changes:

  • 107 of 107 libvmaf functions are [[compat]] entries in core/api/vmafx.toml: 14 generated shims, 5 generated glue, 63 hand-written (core/src/compat/libvmaf/, each declaring the vmafx_* functions it calls), 25 declared engine exceptions (the 5 CUDA and 20 SYCL functions keep the engine's definition until their WP3 lane lands; CUDA device frames are feat(api): import CUDA device frames with event fences and GL textures (RC4 WP3, ADR-2023) #2277). The 5 HIP and 9 Metal functions are compat functions in builds without those backends and engine exceptions in builds with them, until their WP3 lanes. Every exception names what ends it (until) and is exported from libvmafx.so.1 in a version node VMAF_LEGACY_<BACKEND> (core/src/vmafx_legacy_<backend>.map).
  • The library split. libvmafx links the engine with hide_unlisted = true (local: *;); libvmaf.so.3 is a separate target linked with -Wl,--no-undefined against libvmafx.so.1. Every engine translation unit compiles with the generated core/src/vmafx/engine_names_gen.h, which names the engine's own libvmaf bodies vmaf_engine_<stem>, so upstream syncs port into the engine sources unchanged. libvmafx.pc and libvmaf.pc (Requires: libvmafx) are generated; pkg-config --libs libvmaf gives -lvmaf -lvmafx.
  • VMAFx API 0.1.6 (ADR-1897 patch bump, node VMAFX_0.1): the libvmaf bridge for pictures, models and model sets, context-owned preallocated frames, perceptual side data, the context backend, frame converters, the tiny-AI functions (vmafx/dnn.h), the embedded MCP server (vmafx/mcp.h) and vmafx_backend_name().
  • Deprecation (D7). Every libvmaf declaration carries VMAF_DEPRECATED("use <successor>"), a warning only with -DVMAF_ENABLE_DEPRECATION_WARNINGS (opt-in in 1.0, default in 1.1, removal in 2.0).
  • Upstream consumers. scripts/ci/upstream-ffmpeg-compat.sh (unpatched FFmpeg FFMPEG_TAG, libvmaf filter, --cuda for libvmaf_cuda) and scripts/ci/upstream-gstreamer-compat.sh (unpatched gst-plugins-bad vmaf element, pinned GST_PLUGINS_BAD_VERSION with a Renovate git-tags manager, CI conformance: upstream GStreamer vmaf element on the libvmaf.so.3 compatibility layer #2237) build against an installed library and compare scores with the CLI and a reference library as exact text; .github/workflows/upstream-consumers.yml runs both.
  • Repository consumers follow the split: container images copy libvmafx.so*, Go builds link -lvmaf -lvmafx, the MCP CI lane runs the compat checks.

--abi-check --against-ref origin/rc4/api-generation-prototype: definition is an append-only successor of origin/rc4/api-generation-prototype (347 additions); against origin/rc4/api-wp5-provenance: 84 additions. No break of a prototype entry, so no new minor.

no ffmpeg-patches update needed: the libvmaf headers gain only VMAF_DEPRECATED markers, empty unless the consumer defines VMAF_ENABLE_DEPRECATION_WARNINGS; unpatched upstream FFmpeg n9.0.2 builds against the split library and scores identically (below).

Landing (Q-083)

Lands bottom-up per Q-083: v1.0.0-rc.3 is tagged, so RC4 lands through the merge train, one API PR at a time. Its base #2288 is on master; this PR was rebased onto master ae8ccbb23 (the base's commits dropped), retargeted to master, and #2277 (WP3 CUDA) follows once it lands. The API docs keep marking the VMAFx API as a preview (docs/api/vmafx/index.md, ABI 0.x) until the rc.4 cut. ADR-2094 is Accepted.

ABI check against master: python3 scripts/codegen/vmafx-api.py --abi-check --against-ref origin/master -> definition is an append-only successor of origin/master (86 additions). Additions only, node VMAFX_0.1, abi_version 0.1.6 (patch bump over master's).

Rebase: generated files take master's side and are regenerated once at the tip (vmafx-api.py --write, the AGENTS indexes); under render at landing (ADR-2197) this PR carries no rendered file (CHANGELOG.md, the ADR index, tag and title pages, docs/rebase-notes.md): its rebase note is a fragment under docs/rebase-notes.d/, and the citation map is derived from the tree (ADR-2200); docs/state.md by scripts/dev/resolve-state-md-conflict.py.
Two contracts master gained since this draft needed changes here, each its own commit: the e2e runtime contract test pinned the libvmaf-only SONAME find of the image builders, so it now pins both chains and both .pc files the split copies (scripts/ci/test_e2e_runtime_contract.py); and master's CI tiers (ADR-2169) route every pull-request workflow through ci-tier.yml, so upstream-consumers.yml gains the tier job and runs its 45-minute FFmpeg and GStreamer build at the full tier (master push, dispatch, fork pull request, ci: full), like the FFmpeg lanes; docs/development/upstream-consumers.md and the workflow table of docs/development/ci.md say so. The compat library also carries the two libvmaf functions newer than this draft (vmaf_set_sample_range_check_enabled(), vmaf_set_input_colorimetry(), ADR-2094 plan).

A CUDA build of the stack failed test_compat_conformance: after vmaf_read_pictures() the engine left the caller's VmafPicture structs set (its CUDA path releases host translations that are struct copies of them), while the compat library clears them (scoring scenario consumed 0 0 against consumed 1 1). vmaf_engine_read_pictures() now wraps read_pictures_owned() and clears both caller structs once the context owns the pictures, in every build; the GPU fallback source contract reads the helper's body (state row T-ENGINE-CUDA-READ-LEAVES-CALLER-PICTURES-2026-10-07, rebase note). On the previous base a CUDA build of the stack gave 0 warnings and --suite=fast 482 OK, 0 fail. tidy: cpu core/src/libvmaf.c 0 findings.

Two more fixes for the stack and master: core/src/vmafx_legacy_sycl.map named vmaf_sycl_import_d3d11_surface, which only _WIN32 builds define, so every Linux SYCL link failed (--no-undefined-version). [[compat]] gains platform = "windows": the ELF legacy map leaves the name out, its row in core/src/libvmaf_symbols.txt ends in @windows, and check_exported_symbols.py expects it only on Windows. Two generator tests fail on the previous head. And the Rust tests master gained (test_rust_twin_registry from #2086, test_predict_rust_ops and test_rust_predict from #2085, test_rust_cambi_kernels from #2090) asked for libvmaf.get_static_lib(), which the split removes, so meson setup -Denable_rust_features=true failed: they link vmaf_test_link now (grep -c get_static_lib core/test/meson.build is 0); a Rust-enabled configure succeeds and the four tests pass. The rebase note is the fragment docs/rebase-notes.d/vmafx-libvmaf-compat-split.md (ADR-2197). The pre-push assertion-density gate refused vmaf_mcp_start_sse() in core/src/compat/libvmaf/mcp.c (20 lines, no assertion): it now asserts that a successful start had a configuration and that the bound port fits the 16-bit field (clang-tidy cpu: 0 findings).

Local gate on the rebased head (CPU, -Db_lto=false, -j4, warnings as errors): build 0 warnings; --suite=fast 410 OK, 0 fail (test_gpu_picture_pool_uaf on its own with MALLOC_PERTURB_=0, as #2547 sets it: OK); codegen tests 132 passed; affected suites: tooling 2479 passed, 6 skipped, 51 shell suites ok; go vet ./... clean and go test ./... 68 packages ok against this build (CGO_LDFLAGS=-L build-cpu/src -lvmaf -lvmafx -lm, as go-ci.yml now links); make test-netflix-golden GOLDEN_NINJA_JOBS=4 280 passed, 3 skipped; preflight.sh --stage msvcism pass; Rust-enabled build (-Denable_rust_features=true) 0 warnings, --suite rust 5 OK; clang-tidy cpu lane on the 30 translation units this PR touches: 0 findings. CUDA build of the API stack through #2290 on master 4994380 (-Denable_cuda=true, warnings as errors): 0 warnings, --suite=fast 489 OK, 0 fail. New compiler-specific constructs are guarded (VMAF_DEPRECATED has an MSVC __declspec branch and an empty fallback; the trace format attribute and the C23 va_start workaround are GCC/clang-only with plain fallbacks). The merge train builds CPU and CUDA and runs its own gates (deliverables, state-md, silent revert, praetorctl audit).

Type

  • feat — new feature

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally — the commit hooks pass.
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build --suite=fast --num-processes 4 -> 377 OK, 2 expected fail (the planted conformance defects), 0 fail, 1 skipped (test_vmafx_api_abi_append_only: the merge base with origin/master has no definition yet) on a CPU build (-Denable_dnn=disabled -Db_lto=false) at f324808b4, three times for the conformance trio run in parallel; the WP6 tests also pass in a DNN build (ONNX Runtime) and an MCP build (-Denable_mcp=true, every transport); scripts/codegen/tests 110 OK; scripts/ci/tests/test_upstream_consumer_scores.py 10 passed; make docs-figures, make docs-fragments-check, the public-API Doxygen build (WARN_AS_ERROR, output identical to before the markers) clean.
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2. — not applicable: no SIMD or kernel code touched; GPU host functions stay the engine's (declared exceptions).
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below. — not applicable: no extractor touched.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header (EUPL-1.2, fork-authored).
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE: and the migration path is documented below. — not a breaking change: the libvmaf ABI is kept (same SONAME, same symbols, a binary linked against the previous libvmaf.so.3 runs unchanged); the definition is append-only.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md and the slug is appended to docs/adr/_index_fragments/_order.txt — ADR-2094, number claimed with scripts/adr/next-free.sh --claim, indexes regenerated.

tidy: cpu core/src/compat/libvmaf/{context,dnn,libvmaf_gen,model,picture,score,status_errno_gen,backend_absent_hip,backend_absent_metal}.c, core/src/vmafx/{bridge,context_frames,convert,tiny_model,mcp_server,frame_pool,model,context}.c, core/src/{libvmaf,model}.c, core/src/{dict,output}.cpp, core/src/feature/psnr.c, core/tools/vmaf.cpp, core/test/{test_compat_conformance,test_compat_conformance_api,test_compat_conformance_scoring,compat_conformance_table_gen,test_vmafx_abi_layout}.c (and through them the forced engine_names_gen.h, the conformance header and the libvmaf headers with their markers): 0 findings in WP6 files, 0 uncited NOLINT (dev container, clang-tidy 22.1.8, scripts/dev/tidy-lane.sh --only ... cpu). A first run found 21 (two pointer casts through void * between the libvmaf and VMAFx tensor records, now member-wise copies; an analyzer NULL path in vmafx_frame_convert(); the C23 va_start the VAList analyzer does not model; getenv in a test; integer-to-pointer state sentinels; out-of-range enum values the tests pass on purpose, now under NOLINTNEXTLINE(...EnumCastOutOfRange) -- ADR-1080 as core/test/dnn/test_tensor_io.c does; NULL sentinels in the generated header); all fixed. The run also reports the 7 core/include/libvmaf/picture.h findings WP5 and WP8 recorded (pre-existing on this base, from the C++ TUs; origin/master carries their ADR-1470 NOLINT blocks). GPU lanes not measured: no GPU translation unit touched; the forced engine-name header holds #defines only.

scripts/dev/preflight.sh --stage msvcism: pass. praetorctl audit --offline: governance gates passed, HISS scan clean for the touched files (the first commit attempt found 7 HISS-07 shell findings in the new consumer scripts: strict mode in the sourced library and six || true; all fixed).

Bug-status hygiene (ADR-0165)

  • docs/state.md updated — new open row T-VMAFX-COMPAT-BACKEND-EXCEPTIONS-2026-10-06: the CUDA / SYCL (and HIP / Metal when built) libvmaf functions remain engine exceptions until their WP3 lanes; the upstream FFmpeg libvmaf_cuda leg is written but unrun (no device on this host); release artifacts stage libvmaf.so* without libvmafx.so* until WP12.

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.
  • If I believe a golden value must change, I have explained why below — not applicable.

Golden gate through the compat library (core/build-golden built with gcc, GOLDEN_NINJA_JOBS=4, make VENV=<repo>/.venv test-netflix-golden; the CLI the harness runs links libvmaf.so.3 on libvmafx.so.1): 280 passed, 3 skipped at 8a18b9628 and again at f324808b4, the same as WP5.

Cross-backend numerical results

Not applicable: no extractor or kernel changed. Scores through the compat library equal the old libvmaf bodies bit for bit (test_compat_conformance, every score as %a) and the CLI's (FFmpeg and GStreamer below).

Performance (if perf or feat)

No scoring path changed. A libvmaf call costs one extra call through the exported vmafx_* function; vmaf_read_pictures() wraps the pictures as frames by reference (no copy). Generic tiny-AI runs copy up to four tensor records of each kind on the stack (more in the heap), as the engine already does.

Deep-dive deliverables (ADR-0108)

  • Research digest — no digest needed: implements Research-2158 section 2.11 and ADR-1852 D3 / D7; the implementation choices (forced engine-name header, engine exceptions, engine pool for preallocation, picture adoption, two-table conformance) are in ADR-2094.
  • Decision matrix — docs/adr/2094-libvmaf-compat-library-split.md ## Alternatives considered (renaming every body in the source, partial linking of the static engine, GPU compat functions now, frame pools for preallocation, a private copy of engine helpers in the compat library).
  • AGENTS.md invariant note — new core/src/AGENTS.d/vmafx-compat.md, updated core/src/AGENTS.d/vmafx-api.md, index entry in docs/development/rebase-sensitive-invariants.md; indexes regenerated.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/added/api-libvmaf-compat-library.md.
  • Rebase note — docs/rebase-notes.d/vmafx-libvmaf-compat-split.md (fragment, ADR-2197), "libvmaf compat library on libvmafx: engine names, split library targets".

User documentation: docs/api/vmafx/index.md "Migrating from libvmaf.h" (layout, bridge, deprecation warnings, the deliberate differences, backend exceptions); generated docs/api/vmafx/compat.md (all 107 functions, the VMAFx calls each is written on, kind, library); docs/api/index.md; docs/development/upstream-consumers.md; docs/development/api-generation.md (the [[compat]] table and outputs); generated reference pages for the new headers.

Reproducer

python3 scripts/codegen/vmafx-api.py --check
python3 scripts/codegen/vmafx-api.py --abi-check --against-ref origin/master
meson setup build core -Denable_dnn=disabled -Db_lto=false
nice -n 10 ninja -C build -j4
python3 scripts/ci/run_meson_test.py -- -C build test_compat_conformance \
    test_compat_conformance_planted_score test_compat_conformance_planted_uncovered \
    check_exported_symbols check_exported_symbols_libvmaf test_compat_library_gates \
    test_libvmaf_deprecation test_vmafx_api_generator
nm -D --defined-only build/src/libvmafx.so.1 | grep -c ' vmaf_'      # 0
nm -D --defined-only build/src/libvmaf.so.3 | grep -c ' T vmaf_'     # 74 (82 with -Denable_mcp=true)
meson setup _inst core --prefix "$PWD/_prefix" -Denable_dnn=disabled -Db_lto=false
ninja -C _inst -j4 install
scripts/ci/upstream-ffmpeg-compat.sh --prefix "$PWD/_prefix" --against-cli
scripts/ci/upstream-gstreamer-compat.sh --prefix "$PWD/_prefix" --against-cli

Tests

Test Covers
test_compat_conformance (fast) 12 scenarios (lifecycle, dictionary, models, collections, scoring over 3 frames with every score and pooled score as %a and every report format, preallocated pictures, pictures, conversion, perceptual weighting, tiny-AI with a real session on model/tiny/smoke_v0.onnx in DNN builds, HIP / Metal absent, MCP) run through the old libvmaf bodies and through libvmaf.so.3; traces must be equal and every compat function of the build must have been called (74 in a CPU build, 82 with MCP); the 25 engine exceptions are listed as such
test_compat_conformance_planted_score, _planted_uncovered (fast, should_fail) one score of the compat side one ulp off; one scenario left out
check_exported_symbols, check_exported_symbols_libvmaf (fast) libvmafx.so.1 exports exactly the vmafx_* list in its version nodes plus the built backends' legacy nodes, no vmaf_ otherwise; libvmaf.so.3 exports exactly the libvmaf functions the generated list gives this build, unversioned
test_compat_library_gates (fast) a library calling an exported vmafx_* function links against libvmafx.so.1 under --no-undefined; one reaching vmaf_engine_init or vmaf_init does not; each export check fails on its list with a row removed
test_libvmaf_deprecation (fast) every exported declaration names the successor the definition gives it; opt-in warning names vmafx_version_string; a default build and VMAF_BUILDING_LIBVMAF compile clean under -Werror; planted missing / wrong / unlisted markers found
test_vmafx_api_compat.py (15) [[compat]] rules (unknown kind, engine without backend, exception without end, unknown backend, manual source outside the compat directory, undeclared call), engine-name header, legacy maps, symbol list rows, conformance tables, manual sources call exactly their declared vmafx_* functions (planted mismatch found), checker on planted missing, extra and versioned exports
test_upstream_consumer_scores.py (10) the score comparison: identical files pass; one ulp, a trailing zero, a missing frame or metric fail; setup errors
test_gpu_public_header_docs (fast) the Doxygen attachment check accepts the deprecation marker between block and declaration and still refuses other text

Gates shown failing on planted defects (measured on this branch)

Planted defect Gate Result
compat vmaf_score_at_index one ulp off test_compat_conformance (VMAF_COMPAT_PLANT=score) scenario scoring: traces differ at line 38, exit 1
scenario pictures skipped test_compat_conformance (VMAF_COMPAT_PLANT=uncovered) no conformance case calls vmaf_picture2_alloc (and 4 more), exit 1
vmaf_init row removed from the compat list check_exported_symbols --library vmaf exit 1
vmafx_submit row removed from the VMAFx list check_exported_symbols --library vmafx exit 1
a compat source calls an engine symbol link under --no-undefined undefined reference to vmaf_engine_init
marker missing / naming another target / unlisted function test_libvmaf_deprecation the three findings named
manual source calls an undeclared vmafx_* function test_vmafx_api_compat.py a.c: calls vmafx_y, no entry declares it
definition rules (kind, backend, end, source path, unknown call) test_vmafx_api_compat.py each refused with its message
a compat return value differed (missing model path -1 vs -22, overriding an extractor the model lacks) test_compat_conformance, golden gate both found during development and fixed in core/src/compat/libvmaf/model.c
vmaf_set_sample_range_check_enabled and vmaf_set_input_colorimetry declared in the headers without entries test_libvmaf_deprecation exported but not in core/api/vmafx.toml [[compat]] for both
the three conformance tests sharing one report file (found by a parallel run: one run read another's XML as its JSON) test_compat_conformance traces differ at line 162; fixed in f324808b4 (one file per run)

Upstream consumers (this host, CPU)

  • FFmpeg n9.0.2, unpatched, libvmaf filter, 3 frames: built against the compat prefix; IDENTICAL to the same binary on the base library and to the CLI (45 values, PASS).
  • GStreamer gst-plugins-bad 1.28.7 vmaf element, unpatched: IDENTICAL to the base library and the CLI (45 values, PASS).
  • FFmpeg libvmaf_cuda: not run (no CUDA device on this host; the leg prints SKIP and never passes).

Builds measured

CPU (-Denable_dnn=disabled -Db_lto=false); DNN (-Denable_dnn=enabled, ONNX Runtime): conformance equal including real session runs; MCP (-Denable_mcp=true, every transport): 82 exports, conformance equal. GPU builds not measured here (device lanes own them); Windows .def linking and the darwin export list are WP12.

Known follow-ups

@lusoris lusoris added type:feature New feature or request rc4 RC4: the vmaf_v1.0.16_3d0h path in Rust; lands after the v1.0.0-rc.3 tag labels Oct 6, 2026
@lusoris
lusoris force-pushed the rc4/api-wp5-provenance branch from 6a3d44d to b396d21 Compare October 8, 2026 08:06
@lusoris
lusoris force-pushed the rc4/api-wp5-provenance branch from b396d21 to c3fdc20 Compare October 8, 2026 08:41
Base automatically changed from rc4/api-wp5-provenance to master October 8, 2026 08:46
@lusoris
lusoris force-pushed the rc4/api-wp6-compat branch from 24e4104 to 0c6dcd8 Compare October 8, 2026 09:29
@lusoris
lusoris marked this pull request as ready for review October 8, 2026 09:29
… on the VMAFx API (RC4 WP6) (#2303)

* feat(api): split libvmaf into libvmafx.so.1 and a compat libvmaf.so.3 on the VMAFx API (RC4 WP6, ADR-2094)

libvmafx.so.1 now holds the engine and the VMAFx API and exports vmafx_
symbols only. libvmaf.so.3 defines the libvmaf functions on exported vmafx_
symbols and links nothing else, so a libvmaf function the new API cannot
express fails the link (ADR-1852 decision D3).

- core/api/vmafx.toml lists all 107 libvmaf functions as [[compat]] entries:
  generated shims and glue, hand-written ones in core/src/compat/libvmaf/,
  and declared engine exceptions for the CUDA and SYCL functions (HIP and
  Metal while built) until their WP3 lanes land.
- The VMAFx API gains what the compat library needs (ABI 0.1.6): the libvmaf
  bridge for pictures, models and model sets, context-owned preallocated
  frames, perceptual side data, the context backend, frame converters, the
  tiny-AI and MCP functions, and vmafx_backend_name().
- Every engine translation unit compiles with the generated
  engine_names_gen.h, which names the engine's own libvmaf bodies
  vmaf_engine_<stem>; the WP2 forwarders are gone.
- test_compat_conformance runs every scenario through the old bodies and
  the compat library and requires equal traces (every score as %a) and a
  call of every compat function; planted divergence and coverage defects
  fail. check_exported_symbols checks both libraries against generated
  lists, and test_compat_library_gates shows an engine symbol does not link.

The Netflix golden gate passes through the compat library: 280 passed,
3 skipped, assertions untouched.

* feat(api): opt-in libvmaf deprecation, upstream consumer conformance and the migration guide (RC4 WP6)

Every libvmaf declaration now names its VMAFx successor, upstream FFmpeg and
GStreamer are checked against the compat library, and the split is documented
for users and packagers.

- core/include/libvmaf/*.h: each exported function carries
  VMAF_DEPRECATED("use <successor>"), a compiler warning only when the
  consumer defines VMAF_ENABLE_DEPRECATION_WARNINGS (opt-in in 1.0, ADR-1852
  decision D7). test_libvmaf_deprecation holds every marker to the target
  core/api/vmafx.toml names, shows the opt-in warning, and shows that a
  default build and the library itself compile clean under -Werror. The
  public-API Doxyfile strips the marker as a function-like macro.
- scripts/ci/upstream-ffmpeg-compat.sh and upstream-gstreamer-compat.sh build
  unpatched FFmpeg (FFMPEG_TAG) and the GStreamer vmaf element
  (GST_PLUGINS_BAD_VERSION, a new Renovate git-tags manager) against an
  installed libvmaf and compare their scores with the vmaf command line and a
  reference library as exact text. The Upstream Consumers workflow runs both
  (#2237).
- The compat tensor records of vmaf_dnn_session_run() cross as member-wise
  copies instead of a pointer cast between distinct struct types, in the
  compat library and in libvmafx. vmafx_frame_convert() refuses a NULL
  destination itself if the engine ever accepts one. The conformance test
  records every return value it used to discard, reads its plant through the
  environment snapshot (ADR-0488), and covers a real tiny-AI session.
- The MCP CI lane also runs the compat conformance and export checks, the
  only lane whose build has the vmaf_mcp_* compat functions.
- Images, Go builds and the CLI artifact take libvmafx.so* next to
  libvmaf.so*; CGO_LDFLAGS link -lvmaf -lvmafx.
- Docs: "Migrating from libvmaf.h" with the deliberate differences, the
  generated migration table docs/api/vmafx/compat.md, the upstream consumer
  guide, a changelog fragment, a rebase note and a docs/state.md row for the
  backend exceptions and the release staging WP12 owns.

* fix(api): assert the compat outputs, give each conformance run its own report, plan the two newer libvmaf functions (RC4 WP6)

The pre-push assertion-density gate and a parallel test run found two gaps
in the compat layer; this closes them and records how the two libvmaf
functions newer than the RC4 base map onto the VMAFx API.

- The generated score shims assert what a successful call guarantees
  (`post` in core/api/vmafx.toml: the record's index is the one asked for),
  and vmaf_picture_convert(), vmafx_frame_from_picture() and
  vmafx_context_acquire_frame() assert the frame and picture they hand on.
- The three conformance tests (plain and the two planted defects) ran at
  once and shared one report file, so a run could read another's XML as its
  JSON. Each run now writes its own file, named after its planted defect.
- scripts/ci/upstream_consumer_scores.py gives its dict its type arguments
  (mypy type-arg).
- ADR-2094, docs/state.md and the compat agent page record that master's
  vmaf_set_sample_range_check_enabled() (#2221) and #2300's
  vmaf_set_input_colorimetry() (ADR-2093) need compat entries when the RC4
  chain moves onto master, colorimetry on the colour VMAFx frames carry with
  a context default; test_libvmaf_deprecation refuses either one missing.

* docs(adr): accept ADR-2094 with the maintainer's answer on engine names

The maintainer accepted ADR-2094 by popup on 2026-10-06, as recommended: the
engine's libvmaf bodies get their engine names from the generated forced
header (core/src/vmafx/engine_names_gen.h), not from renaming them in the
source or from partial linking. The answer is cited in the ADR's References;
the status, the deciders and the index row move to Accepted.

* fix(api): give the compat library the two newer libvmaf functions on frame colour and a context option (RC4 WP6)

On the chain restacked onto master the vmaf command line did not link:
master's vmaf_set_sample_range_check_enabled() (#2221) and
vmaf_set_input_colorimetry() (#2300) had no compat entry, and libvmafx.so.1
hides the engine's bodies. Both are now compat functions on the VMAFx API,
as ADR-2094 planned.

- vmaf_set_sample_range_check_enabled() sets the new context option
  check_sample_range of vmafx_context_set_option() (ADR-1918).
- vmaf_set_input_colorimetry() calls the new
  vmafx_context_set_default_color() and keeps no state of its own.
- VmafxFrameDesc gains color, a VmafxColor appended at its end (ABI 0.1.6,
  the patch this pull request already raises). Host, wrapped, pool and
  preallocated frames carry the colour of their desc; a frame whose colour
  is all UNKNOWN takes the context default.
- vmafx_submit() hands each pair's colour to the engine's conversion state
  (vmaf_engine_set_pair_colorimetry()). The same colour is accepted at any
  time; another one after the first converted pair is VMAFX_E_BUSY, as
  libvmaf's -EBUSY, and the pair is not counted.
- Both functions carry their VMAF_DEPRECATED marker and a conformance
  scenario (sample_range; colorimetry with a conversion_target model).
- The Python emitter converts a record with one struct of numbers back to
  C, so FrameDesc keeps to_c() now that it holds a Color.
- The lane's regenerated binding and master's ten newer tests linked through
  vmaf_test_link are part of this commit.
- clang-tidy (cpu lane, container) on the 30 translation units this pull
  request touches: 0 findings after guarding the port write-back of
  vmaf_mcp_start_sse() against a NULL config (the analyzer's null
  dereference; a NULL config never reaches it) and splitting a test. The
  cpu baseline, the measured-source list and the ADR citation registry are
  updated with it; the shared vt_psnr_context() test helper replaces a
  second copy.

Tests: test_vmafx_frame_input (new), test_vmafx_context,
test_vmafx_python_binding, the conformance scenarios and the generator
tests fail without the change; planted defects (desc minimum of the grown
size, frames without their colour, the default overriding a frame's colour,
and in a zimg build a dropped compat colour, a default setter without the
engine's -EBUSY, an equal colour treated as a change) are each refused.
Docs: the frame colour section of the VMAFx guide, the migration note, the
sample range and pictures pages, the changelog fragment, the rebase note and
state row T-VMAFX-COMPAT-NEWER-LIBVMAF-FUNCTIONS-2026-10-07.

* ci(api): give each master push of the upstream consumer workflow its own concurrency group

Master's concurrency contract (ADR-1673) refused the Upstream Consumers
workflow this pull request adds: its group was shared between master pushes
with cancel-in-progress on, so a later push could cancel an earlier master
run. The group now names the commit on master and the ref elsewhere, as the
other workflows do; test_master_concurrency_contract passes again (it failed
on the previous group).

* test(docker): pin both SONAME chains and pkg-config files the image builders copy after the library split

WP6 builds libvmafx.so.1 next to the compat libvmaf.so.3 and the Go and node image builders copy both chains and both .pc files; the runtime contract test still pinned the libvmaf-only find command and failed on the split.

* ci(api): run the upstream consumer workflow at the full CI tier

Master's CI tiers (ADR-2169) route every pull-request workflow through ci-tier.yml; the routing contract test refused upstream-consumers.yml, which ran its 45-minute FFmpeg and GStreamer build on the release pull request too. The job now needs the tier job and runs at the full tier, like the FFmpeg lanes; the guide and the workflow table say so.

* fix(api): clear the caller's pictures once vmaf_read_pictures() owns them in a CUDA build

A CUDA build released host translations that are struct copies of the caller's pictures and left the caller's VmafPicture structs pointing at released storage; a CPU build clears them through vmaf_picture_unref(). The compat library (ADR-2094) clears them, so test_compat_conformance failed in every CUDA build. vmaf_engine_read_pictures() now clears both structs once the context owns the pictures, in every build; the body moves to read_pictures_owned().

* test(api): read the read-pictures body where it now lives in the GPU fallback contract

vmaf_engine_read_pictures() wraps read_pictures_owned() since it clears the caller's pictures; the contract that fallbacks resolve before the CUDA translation reads the helper's body.

* fix(api): keep the Windows-only SYCL importer out of the ELF legacy version script

core/src/vmafx_legacy_sycl.map named vmaf_sycl_import_d3d11_surface, which only _WIN32 builds define, so every Linux SYCL link failed (--no-undefined-version: undefined version VMAF_LEGACY_SYCL). [[compat]] gains platform = "windows": the legacy map omits the name, its compat-list row ends in @windows and check_exported_symbols.py expects it only on Windows. The generator refuses another platform; both cases are tests that fail on the previous head.

* build(test): link the Rust twin registry test through vmaf_test_link after the library split

WP6 (ADR-2094) removed libvmaf.get_static_lib(): libvmaf is the compat library, and white-box tests link the engine through vmaf_test_link. test_rust_twin_registry (framework #2086, on master) still asked for the static libvmaf, so meson setup failed with -Denable_rust_features=true. The citation map loses the compat_libvmaf_gen.c sites the split removed and gains the sites of the restack.

* docs(api): move the rebase notes to a fragment and leave the rendered files to the landing render (ADR-2197)

* build(test): link the Rust predictor and CAMBI kernel tests through vmaf_test_link

test_predict_rust_ops, test_rust_predict and test_rust_cambi_kernels came to
master with #2085 and #2090 and link libvmaf.get_static_lib(). After the
library split libvmaf.a is the compat library: a white-box test links
vmaf_test_link (libvmaf.a with libvmafx.a, or both shared libraries), as every
other white-box test of this PR does.

* ci(tidy): measure the translation units of WP6 (library split) in the cpu lane

The clang-tidy coverage rule on master requires every tracked translation
unit to be read by a lane. The new and touched units of this pull request
were measured in the dev container (scripts/dev/tidy-lane.sh --write
--only ... cpu, clang-tidy 22.1.8): 0 findings, 0 uncited NOLINT; they join
the cpu lane's measured sources.

* fix(api): state the compat SSE start's invariants as assertions

vmaf_mcp_start_sse() reached 20 lines without an assertion, which the
pre-push Power-of-10 assertion-density gate (scripts/ci/assertion-density.sh)
refuses for fork-added functions. It now asserts the two facts its tail
relies on: a successful start had a configuration (a NULL one fails with
-EINVAL before), and the bound port fits the libvmaf struct's 16-bit field.
The guard on the write stays for release builds. assertion-density: pass;
clang-tidy cpu lane on core/src/compat/libvmaf/mcp.c: 0 findings.

Signed-off-by: Lusoris <lusoris@proton.me>
@lusoris
lusoris force-pushed the rc4/api-wp6-compat branch from 0c6dcd8 to 14dc297 Compare October 8, 2026 09:54
@lusoris
lusoris merged commit 14dc297 into master Oct 8, 2026
15 of 51 checks passed
@lusoris
lusoris deleted the rc4/api-wp6-compat branch October 8, 2026 09:58
lusoris added a commit that referenced this pull request Oct 8, 2026
… ships them (#2604)

* fix(licensing): record libvmafx beside libvmaf in every artifact that ships them

After the library split (#2303) every image that ships the library failed
its licence check (ADR-1503): "no recorded licence: usr/local/lib/libvmafx.so",
libvmafx.so.1, libvmafx.so.1.0.0 and usr/local/lib/pkgconfig/libvmafx.pc.
tools/rc1-tester/image/licensing.json recorded libvmaf.so* and libvmaf.pc
only.

The vmafx-binaries components of production-cli-image,
production-server-image, production-go-server-image, production-node-image
and release-native now record libvmafx.so* (and pkgconfig/libvmafx.pc where
libvmaf.pc is recorded), under the same licences; the controller, CUDA, ROCm,
oneAPI and published-RC records inherit them.

test_every_installed_library_is_recorded_beside_its_sibling reads the
installed libraries from the pkg-config files core/src/meson.build generates
and fails on a record that names one without the other: 19 findings on the
old manifest, none now; a planted record proves the finder.

Verification on a buildx builder capped at 4 CPUs and 8 GiB: the
licence-check stages of the controller, go-server, node, CLI and server
images pass; the controller stage with the old manifest fails with the four
lines above. rc1-tester tests: 501 passed.

Signed-off-by: Lusoris <lusoris@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rc4 RC4: the vmaf_v1.0.16_3d0h path in Rust; lands after the v1.0.0-rc.3 tag type:feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant