Skip to content

feature/cambi: store the integer options in int fields - #1658

Open
lusoris wants to merge 1 commit into
Netflix:masterfrom
VMAFx:fix/cambi-option-int-fields
Open

lusoris wants to merge 1 commit into
Netflix:masterfrom
VMAFx:fix/cambi-option-int-fields

Conversation

@lusoris

@lusoris lusoris commented Oct 2, 2026 •

Copy link
Copy Markdown

CambiState declares window_size and max_log_contrast as uint16_t, but both options are registered as VMAF_OPT_TYPE_INT. vmaf_option_set() writes them through an int * (opt.c, set_option_int()) and vmaf_feature_name_from_options() reads them through an int * (feature_name.c, option_is_default()). This declares both fields as int, so the option code accesses what the struct holds.

What breaks today

Master 9e48141b, GCC 16.2.1, -Db_sanitize=address,undefined -Db_lto=false, any run that creates the cambi extractor:

vmaf -r src01_hrc00_576x324.yuv -d src01_hrc01_576x324.yuv -w 576 -h 324 -p 420 -b 8 -m version=vmaf_v1.0.16_3d0h -q -o /dev/null
libvmaf/src/opt.c:40:10: runtime error: store to misaligned address 0x...902 for type 'int', which requires 4 byte alignment
libvmaf/src/feature/feature_name.c:99:38: runtime error: load of misaligned address 0x...902 for type 'int', which requires 4 byte alignment

--feature cambi alone gives the same two reports; with this change both runs print none. The store is max_log_contrast, option offset 258 in CambiState (read from gdb: window_size 212, src_window_size 214, vlt_luma 256, max_log_contrast 258, heatmaps_path 264).

What I measured and what I infer:

  • Measured on x86-64: the values are right today. The int store at 258 covers the field and two bytes of padding before heatmaps_path; the store at 212 is aligned but covers src_window_size too, which cambi.c assigns later (s->src_window_size = s->window_size). The three Netflix pairs plus cambi=window_size=31:max_log_contrast=4:full_ref=true score identically on master and on this branch (.engagement/golden_compare.py, default dispatch and cpumask=-1: ALL IDENTICAL, e.g. src01 vmaf_mean 76.667831).
  • Inference, not run (no big-endian target here): on a big-endian target the 16-bit field is the upper half of the int and reads back 0 for both options; a target that traps on a misaligned store would fault at offset 258.

Fix

window_size, src_window_size and max_log_contrast become int, and adjust_window_size() takes an int *. Defaults, ranges and values do not change; the one place a 16-bit wrap was possible (window_size * (width + height) / 375 >> 4 above 65535, which needs width + height over about 3.1 million) now reaches the existing max_window * max_window >= CAMBI_RECIPROCAL_LUT_SIZE check instead of wrapping (from reading the code; no input that large was run).

I checked every VMAF_OPT_TYPE_* option table in libvmaf/src against the declared type of its field (a script over .offset = offsetof(S, f), .type = T). The only other mismatch is float_adm.c adm_adm3_apply_hm, a VMAF_OPT_TYPE_BOOL option stored in an int; it is aligned and sized so it does not trip the sanitizer, and this change leaves it alone.

Test

test_integer_options_fill_int_fields in libvmaf/test/test_cambi.c: on a zeroed CambiState it sets every VMAF_OPT_TYPE_INT option to its default through vmaf_option_set() and asserts that each offset is a multiple of _Alignof(int), that window_size and max_log_contrast are int sized, that the defaults arrive, and that src_window_size and vlt_luma keep their values. On master it fails at the first assertion ("integer option offset is a multiple of alignof(int)"), in a release and in an ASan/UBSan build; with the change it passes. test_adjust_window_size uses int now.

Validation

x86-64 Linux, GCC 16.2.1, master 9e48141b. Rebased on master 9e48141b (2026-10-02); the counts below and the repro were re-run on it.

  • Release meson test with -Denable_float=true -Denable_checkasm=true: 25/25 on master and here.
  • -Db_sanitize=address,undefined -Db_lto=false, same options: 22 pass and 3 fail of 25 on both; the failures are test_predict and test_pic_preallocation (SIGABRT, LeakSanitizer) and checkasm (heap-buffer-overflow in adm_dwt2_16, integer_adm.c:2603), all unchanged by this change.
  • Repro command above on the ASan/UBSan binary: 2 reports on master, 0 here.
  • .engagement/golden_compare.py, master binary vs this branch: ALL IDENTICAL.

Relation to other changes

#1571 moves CambiState into cambi.h and changes test_cambi.c; the two conflict textually (struct fields, meson.build, test file). The resolution is the same three field types in cambi.h.

window_size and max_log_contrast are registered as VMAF_OPT_TYPE_INT,
which vmaf_option_set() writes and option_is_default() reads through an
int *, but CambiState declared both as uint16_t. max_log_contrast sits
at offset 258, so UBSan reports a store and a load of a misaligned int
in opt.c and feature_name.c for any model that creates cambi. The
window_size store at offset 212 is aligned but covers the neighbouring
src_window_size as well, and on a big-endian target either 16-bit field
would read back the upper half of the int, which is zero.

Declare window_size, src_window_size and max_log_contrast as int and let
adjust_window_size() take an int *. The values and the defaults do not
change.

The new test sets every integer option of the extractor to its default
on a zeroed CambiState and checks that each offset is a multiple of
alignof(int), that the two fields are int sized, and that the
neighbouring fields are left alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant