From 1169263f63ec515c4880a1caceb776872aa55b95 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 10 Jun 2026 20:46:35 +0200 Subject: [PATCH 01/40] =?UTF-8?q?chore(deps):=20upgrade=20criterion=200.5.?= =?UTF-8?q?1=20=E2=86=92=200.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #174. criterion 0.5.1 (May 2023) is ~3 years stale; bump to the current stable line (resolves to 0.8.2 today). The only API break in the 0.5 → 0.8 span affecting our benches is the removal of `criterion::black_box` (deprecated in 0.6, gone in 0.7+); switch the three bench files to `std::hint::black_box` and keep the rest of the imports unchanged. - crates/core/Cargo.toml: bump dev-dep to `criterion = "0.8"` - crates/core/benches/{marker,feature_map,simd}_bench.rs: import `std::hint::black_box`; drop `criterion::black_box` - docs/design/issue-174-criterion-upgrade.md: design doc from the `/brainstorming` session that scoped this change Verified: `cargo build --benches --all-features`, `cargo fmt`, CI's `cargo clippy --workspace -- -D warnings`, and `cargo test --all-features` (463 tests) all green. All three benches produce output under criterion 0.8. Co-Authored-By: Claude Opus 4.7 --- crates/core/Cargo.toml | 2 +- crates/core/benches/feature_map_bench.rs | 3 +- crates/core/benches/marker_bench.rs | 3 +- crates/core/benches/simd_bench.rs | 3 +- docs/design/issue-174-criterion-upgrade.md | 162 +++++++++++++++++++++ 5 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 docs/design/issue-174-criterion-upgrade.md diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index cc31add..980ced0 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -80,7 +80,7 @@ log-helpers = ["dep:env_logger", "dep:console_log"] [dev-dependencies] chrono = "0.4" env_logger = "0.11" -criterion = "0.5.1" +criterion = "0.8" clap = { version = "4.5", features = ["derive"] } tempfile = "3" # Used by the absolute corner-error integration test (#166) to parse diff --git a/crates/core/benches/feature_map_bench.rs b/crates/core/benches/feature_map_bench.rs index d19afd6..f6edc85 100644 --- a/crates/core/benches/feature_map_bench.rs +++ b/crates/core/benches/feature_map_bench.rs @@ -43,7 +43,8 @@ //! would make each iteration multi-minute. We target a realistic-but-bounded //! working size that still stresses the scoring loop. -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; use std::path::PathBuf; use webarkitlib_rs::ar2::{ar2_gen_feature_map, ar2_gen_image_set}; use webarkitlib_rs::arlog_e; diff --git a/crates/core/benches/marker_bench.rs b/crates/core/benches/marker_bench.rs index 3b2c3eb..133f63d 100644 --- a/crates/core/benches/marker_bench.rs +++ b/crates/core/benches/marker_bench.rs @@ -34,8 +34,9 @@ * */ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, Criterion}; use std::fs::File; +use std::hint::black_box; use std::io::Read; use webarkitlib_rs::{ marker::ar_detect_marker, diff --git a/crates/core/benches/simd_bench.rs b/crates/core/benches/simd_bench.rs index 193d564..e8d94a9 100644 --- a/crates/core/benches/simd_bench.rs +++ b/crates/core/benches/simd_bench.rs @@ -34,7 +34,8 @@ * */ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; use webarkitlib_rs::image_proc::{box_filter_h_scalar, box_filter_v_scalar, rgba_to_gray_scalar}; use webarkitlib_rs::pattern::dot_product_scalar; diff --git a/docs/design/issue-174-criterion-upgrade.md b/docs/design/issue-174-criterion-upgrade.md new file mode 100644 index 0000000..d1231f3 --- /dev/null +++ b/docs/design/issue-174-criterion-upgrade.md @@ -0,0 +1,162 @@ +# Issue #174 — Upgrade criterion 0.5.1 → 0.8.x + +Design document for the dependency bump described in +[issue #174](https://github.com/webarkit/WebARKitLib-rs/issues/174). +Produced via the `/brainstorming` workflow on 2026-06-10. + +--- + +## 1. Understanding Lock (confirmed) + +**What is being built** +A dependency-only upgrade of `criterion` from `0.5.1` → `0.8` in +`crates/core/Cargo.toml`, plus the minimal source fixes in the three +bench files to match criterion 0.8's API. + +**Why it exists** +`criterion 0.5.1` (May 2023) is ~3 years stale; `0.8.2` (Feb 2026) is +the current stable. Surfaced as a follow-up to M9-3 (#142), intentionally +separated per CLAUDE.md's "fresh branch per sub-issue" rule — the +upgrade is unrelated to the "remove C++ FFI as default" theme of #142, +and the 0.5 → 0.8 span has API breaks that deserve their own focused +review. + +**Who it is for** +WebARKitLib-rs maintainers running local benchmarks, and CI's +`benchmarks` job. + +**Key constraints** +- No API changes outside the three bench files. +- Pin style: `criterion = "0.8"` (matches the existing dev-dep style). +- Single bench matrix for `BENCHMARKS.md`: `--features simd`. +- `BENCHMARKS.md` refresh is **scope-driven by the bench run output** + (see Decision #4). +- All four CLAUDE.md §5 pre-commit checks must pass. + +**Explicit non-goals** +- No new benchmarks (KPM/NFT-specific gaps tracked separately under #142). +- No migration to `divan` / `iai`. +- No bumps to other dev-deps. +- No changes to library code. + +**Assumptions** +1. The 0.5 → 0.8 break surface in these benches is limited to + `criterion::black_box` → `std::hint::black_box`. Verified by + compiling. +2. `Criterion::default()`, `criterion_group!`, `criterion_main!`, and + `group.sample_size(N)` are stable across 0.5 → 0.8. (Spot-checked + criterion 0.8 changelog.) +3. `BENCHMARKS.md` edits will be scope-driven by the bench run output: + minimal numeric refresh by default; add sections only if the run + reveals something that needs explanation. +4. CI's `benchmarks` job uses the same `cargo bench` invocation and + doesn't pin criterion separately. + +--- + +## 2. Decision Log + +| # | Decision | Alternatives | Why | +|----|--------------------------------------------------------------------------------------------|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| 1 | Pin as `criterion = "0.8"` | `"0.8.2"`, `"=0.8.2"` | Matches existing dev-dep style (`clap = "4.5"`); allows patch updates. User-confirmed. | +| 2 | Single PR, two commits (deps+code, then doc) | One commit / per-file commits | Clean blame across deps vs doc; one logical change → one PR. | +| 3 | Bench matrix: `--features simd` only | + scalar / + ffi-backend / + dual-mode | Matches issue acceptance; minimizes scope creep. User-confirmed. | +| 4 | `BENCHMARKS.md`: scope-driven refresh | Full rewrite / skip-numbers | "Full rewrite" overstates the work for a dep bump; decide minimal-edit vs added section after seeing run output. | +| 5 | `black_box` migration: `criterion::black_box` → `std::hint::black_box` | Keep `criterion::black_box` (removed in 0.7+) | Required by 0.7+. No call-site changes, just the import line. | +| 6 | Verify MSRV before pushing | Bump MSRV silently | criterion 0.8 needs Rust 1.74+; if workspace MSRV is lower, surface as its own decision before pushing. | + +--- + +## 3. File Changes + +### 3.1 `crates/core/Cargo.toml` + +```toml +# was +criterion = "0.5.1" +# after +criterion = "0.8" +``` + +### 3.2 `crates/core/benches/{marker_bench,feature_map_bench,simd_bench}.rs` + +```rust +// before +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +// after +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; +``` + +No other call-site changes expected. + +### 3.3 `crates/core/benches/BENCHMARKS.md` + +Scope-driven after the bench run: + +- **Default path (minimal edit)**: replace numbers table, bump the + "Tooling" line to `criterion 0.8`, refresh the timestamp. +- **If the run reveals a shape change** (new metric reported, restructured + output): add a small section explaining the delta. +- **If a bench had to be restructured to compile**: document the + restructuring in a new subsection. + +--- + +## 4. Execution Sequence + +1. Branch off `dev` → `chore/criterion-0.8-upgrade`. +2. Bump `criterion = "0.8"` in `crates/core/Cargo.toml`. +3. Fix `black_box` imports in the 3 bench files. +4. **Compile gate**: `cargo build --benches --all-features`. If it fails + outside the `black_box` line, stop and re-evaluate (assumption #1 + broken). +5. **Pre-commit gates** (CLAUDE.md §5): + - `cargo fmt --all` + - `cargo build --all-features` + - `cargo clippy --all-targets --all-features -- --deny warnings` + - `cargo test --all-features` +6. Commit 1: `chore(deps): upgrade criterion 0.5.1 → 0.8`. +7. Bench run: `cargo bench -p webarkitlib-rs --features simd`. Capture + output. +8. Diff old vs new numbers in `BENCHMARKS.md`. Apply scope-driven edit. +9. Commit 2: `doc(benches): refresh BENCHMARKS.md for criterion 0.8`. +10. Push branch, open PR against `dev` titled + `chore(deps): upgrade criterion 0.5.1 → 0.8.x`. + +--- + +## 5. Risks and Rollback + +- **Risk**: a transitive of criterion 0.8 conflicts with another + dev-dep. *Mitigation*: `cargo tree -p webarkitlib-rs --target all` + if `cargo build` errors. +- **Risk**: workspace MSRV is below criterion 0.8's requirement + (Rust 1.74+). *Mitigation*: check `rust-version` before pushing; + surface a separate decision if a bump is needed. +- **Rollback**: revert the two commits. No library-code or public API + changes, so no downstream breakage is possible. + +--- + +## 6. Testing Strategy + +- `cargo build --benches` is the real correctness gate — benches don't + have unit tests. +- `cargo bench --features simd` must produce numbers for every existing + bench (issue acceptance criterion). +- `cargo test --all-features` must stay green (sanity check; should be + unaffected by a dev-dep bump). + +--- + +## 7. Acceptance (mirrors issue #174) + +- [ ] `cargo bench -p webarkitlib-rs --features simd` builds and runs + cleanly with criterion 0.8. +- [ ] All existing benches produce output (no API-mismatch panics). +- [ ] `BENCHMARKS.md` refreshed with new numbers. +- [ ] `cargo fmt --all -- --check` and + `cargo clippy --all-targets --all-features -- --deny warnings` + green. +- [ ] CI's `benchmarks` job passes on the upgrade PR. From a30b0334c47473146ec195abf67d9960c2811766 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 10 Jun 2026 20:47:44 +0200 Subject: [PATCH 02/40] doc(benches): refresh BENCHMARKS.md for criterion 0.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #174. Re-runs the SIMD benchmark suite under criterion 0.8 on the same hardware (Windows x86_64) and updates the medians + speedup column. Scalar medians compare cleanly against the pre-upgrade table (<15% machine variance), confirming no measurement-methodology drift from the 0.5 → 0.8 jump. - Refresh "SIMD Performance" table with new medians (rgba_to_gray 2.64x, dot_product 5.42x, box_filter_v 3.03x, box_filter_h 1.07x) - Pin "Tooling" line to `criterion 0.8` - Add toolchain note and the `RUSTFLAGS="-C target-feature=+sse4.1"` reminder so future readers can reproduce the SIMD numbers without hunting for the gate Co-Authored-By: Claude Opus 4.7 --- crates/core/benches/BENCHMARKS.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/core/benches/BENCHMARKS.md b/crates/core/benches/BENCHMARKS.md index fdf0883..d57f275 100644 --- a/crates/core/benches/BENCHMARKS.md +++ b/crates/core/benches/BENCHMARKS.md @@ -1,6 +1,6 @@ # WebARKitLib.rs Core Benchmarks -**Last Updated**: 2026-06-04 (M9-3 — #142) +**Last Updated**: 2026-06-10 (chore: criterion 0.5.1 → 0.8 — #174) This document tracks the performance of critical image processing and pattern matching functions in the `webarkitlib_rs` core crate. @@ -8,16 +8,16 @@ This document tracks the performance of critical image processing and pattern ma The following benchmarks were conducted on an x86_64 system with SSE4.1 support enabled via the `simd` feature. -| Function | Implementation | Average Time | Speedup | +| Function | Implementation | Median Time | Speedup | | :--- | :--- | :--- | :--- | -| `rgba_to_gray` | Scalar | 550.68 µs | - | -| | SIMD (SSE4.1) | 232.72 µs | **2.37x** | -| `dot_product` | Scalar | 347.85 ns | - | -| | SIMD (SSE4.1) | 54.56 ns | **6.44x** | -| `box_filter_h` | Scalar | 1.731 ms | - | -| | SIMD (SSE4.1) | 1.734 ms | 1.00x | -| `box_filter_v` | Scalar | 2.590 ms | - | -| | SIMD (SSE4.1) | 886.84 µs | **2.92x** | +| `rgba_to_gray` | Scalar | 629.57 µs | - | +| | SIMD (SSE4.1) | 238.42 µs | **2.64x** | +| `dot_product` | Scalar | 326.45 ns | - | +| | SIMD (SSE4.1) | 60.24 ns | **5.42x** | +| `box_filter_h` | Scalar | 1.8386 ms | - | +| | SIMD (SSE4.1) | 1.7245 ms | 1.07x | +| `box_filter_v` | Scalar | 2.3240 ms | - | +| | SIMD (SSE4.1) | 765.77 µs | **3.03x** | ### Analysis @@ -41,10 +41,12 @@ cargo bench --bench simd_bench ## Setup Details -- **Tooling**: [Criterion.rs](https://github.com/bheisler/criterion.rs) +- **Tooling**: [Criterion.rs](https://github.com/bheisler/criterion.rs) `0.8` - **Target OS**: Windows - **Target Architecture**: x86_64 (SSE4.1) - **Frame Size**: 640x480 (typical AR video resolution) +- **Toolchain**: `rustc 1.94.0 (stable)` +- **SIMD activation**: SSE4.1 intrinsics are gated by `#[cfg(target_feature = "sse4.1")]` — set `RUSTFLAGS="-C target-feature=+sse4.1"` (or `target-cpu=native`) when running `simd_bench` to exercise the SIMD paths. ## KPM / NFT performance (M9-3 status) From 1b3a3e4d35136f3a2605c5cf0aa327b145370888 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 10 Jun 2026 23:22:46 +0200 Subject: [PATCH 03/40] fix(core): clean up trivial clippy lints under --all-targets --all-features (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 1 of 5 in the #180 series to close the gap between CI's clippy gate and `cargo clippy --all-targets --all-features -- -D warnings`. Local baseline drops from 60 errors to 43; remaining lints partition cleanly into the PR 2-5 backlog (FFI shims, field-reassign-with-default, SIMD too_many_arguments, CI tightening). Trivial mechanical fixes only — zero behavior change: - ar/image_proc.rs: silence unused-vars in luma_hist_and_box_filter_with_bias (variables used only under specific target_feature gates); convert manual range check to (100..200).contains(&thresh). - ar/math.rs, ar/pattern.rs: move items above the test module so clippy::items_after_test_module is satisfied. - ar/matrix.rs, ar/pattern.rs, ar2/feature_map.rs, ar2/image_set.rs: convert index-only loops to iterator / enumerate forms (clippy::needless_range_loop). - ar/bch.rs: drop no-op `as i32` cast. - arlog.rs: drop needless `fn main` in doctest. - version.rs: use char-array split pattern. - kpm/freak/homography.rs: drop needless `*pt` deref. - kpm/freak/math.rs: convert manual inclusive range to (0.0..=360.0).contains. Adds .claude/issue-180-plan.md with the brainstormed 5-PR plan, Decision Log, assumptions, and risks for the remaining series. --- .claude/issue-180-plan.md | 147 +++++++++++++++ crates/core/src/ar/bch.rs | 2 +- crates/core/src/ar/image_proc.rs | 9 +- crates/core/src/ar/math.rs | 52 +++--- crates/core/src/ar/matrix.rs | 4 +- crates/core/src/ar/pattern.rs | 238 ++++++++++++------------ crates/core/src/ar2/feature_map.rs | 8 +- crates/core/src/ar2/image_set.rs | 8 +- crates/core/src/arlog.rs | 6 +- crates/core/src/kpm/freak/homography.rs | 2 +- crates/core/src/kpm/freak/math.rs | 2 +- crates/core/src/version.rs | 2 +- 12 files changed, 314 insertions(+), 166 deletions(-) create mode 100644 .claude/issue-180-plan.md diff --git a/.claude/issue-180-plan.md b/.claude/issue-180-plan.md new file mode 100644 index 0000000..c9c069c --- /dev/null +++ b/.claude/issue-180-plan.md @@ -0,0 +1,147 @@ +# Issue #180 — Tighten CI Clippy Gate to `--all-targets --all-features` + +**Status**: Planning complete, ready for implementation. +**Branch base**: `dev` (HEAD `a30b033` at planning time, post-PR #183 criterion 0.8 merge). +**Tracking issue**: [#180](https://github.com/webarkit/WebARKitLib-rs/issues/180). + +--- + +## Understanding Summary + +- **What**: Close the gap between CI's clippy gate (`--workspace -- -D warnings`, default features, lib targets only) and the strict local check (`--all-targets --all-features -- -D warnings`) — a ~70-lint delta on `dev` HEAD. +- **Why**: Contributor onboarding (new contributors run the strict check and assume the codebase is broken); the strict gate catches real lints in `ffi-backend` / `dual-mode` / `simd-*` paths CI never sees today. +- **Who**: WebARKitLib-rs contributors; no end-user behavior change. +- **How**: Execute issue #180's 5-PR sequence as the spec — trivial wins → FFI shim annotations → `ar/marker.rs` field-reassign cleanup → SIMD `#[allow]` with rationale → tighten CI in `.github/workflows/ci.yml`. +- **Non-goals**: + - No `clippy.toml` file (deferred to M10+ if SIMD surface grows). + - No `FeatureMapConfig` / struct refactor (SIMD signatures are locked by runtime-dispatch contract). + - No deletion of `webarkit_cpp_*` FFI shims unless truly orphaned. + - No `CHANGELOG.md` edits (project rule). + - Not blocking v0.7.0 (already released). + +--- + +## Decision Log + +| # | Decision | Alternatives considered | Why this choice | +|---|---|---|---| +| 1 | Follow #180's 5-PR sequence verbatim | Refine ordering; merge PRs; re-baseline lint counts first | Issue is well-scoped; counts may drift but plan structure is sound | +| 2 | PR 2 default posture: `#[allow(dead_code)]` + `// rationale:` on extern blocks; delete only the truly orphaned ones | Wire up callers (scope creep); bulk delete (irreversible); case-by-case triage (review burden) | Lowest risk; zero behavior change; honest about why shims exist (dual-mode parity) | +| 3 | PR 4: inline `#[allow(clippy::too_many_arguments)]` + rationale on `get_similarity_sse41` / `get_similarity_avx2` | `clippy.toml` threshold raise; bundle args into struct | SIMD signatures locked by runtime-dispatch contract; inline is scoped, threshold raise loses signal everywhere | +| 4 | Defer `clippy.toml` | Add now with `too-many-arguments-threshold = 9` | Only 2 sites today; revisit when SIMD surface grows in M10+ | +| 5 | Keep CI on `dtolnay/rust-toolchain@stable` after PR 5 | Pin to a specific rustc version; document known-good rustc | Accept the churn; v0.7.0 is shipped, future rustc lint additions fix in follow-up PRs | +| 6 | Branch off `dev`, one PR per branch | Stacked PRs; single mega-PR | Project convention (CLAUDE.md §4); keeps reviews focused | + +--- + +## Assumptions + +- **A1**: CI keeps `dtolnay/rust-toolchain@stable` after PR 5 tightens the gate. When a new rustc adds lints, fix in a follow-up PR. No rustc pin. +- **A2**: PR 4 stays its own PR (not folded into PR 1) for review clarity, even though it's only 2 `#[allow]` lines. Revisit at execution time if it feels artificial. +- **A3**: Branch naming follows `chore/clippy-180-pr-` — branched off `dev`, one PR per branch, conventional commits per CLAUDE.md §6. +- **A4**: Re-baseline the lint inventory at the start of PR 1, not now. Counts may differ from #180's ~70 due to rustc drift since #180 was filed. + +--- + +## Open Risks + +- **R1** — *Rustc drift since #180*: lints may have been promoted/demoted between #180's filing and today. PR 1's scope could expand or contract. **Mitigation**: re-baseline before opening PR 1; update #180 with actual counts. +- **R2** — *FFI shim mislabeled*: a `webarkit_cpp_*` extern annotated as "kept for dual-mode parity" might actually be orphaned. **Mitigation**: run `cargo test --features dual-mode` and `cargo test --features ffi-backend` after PR 2; any annotated shim that has no caller under any feature combo gets deleted in the same PR. +- **R3** — *`ar/marker.rs` init-order dependency*: 44 mechanical `field-reassign-with-default` fixes touch many lines. Struct-init syntax could surface a subtle init-order dependency in `ARHandle` construction. **Mitigation**: rely on existing tests; if any fail, revert that specific site to the default+reassign pattern with an `#[allow]` + rationale. +- **R4** — *CI tightening fails after merge*: PR 5 flips the gate, then unrelated work introduces new lints under `--all-features` that the contributor didn't catch locally. **Mitigation**: PR 5 description includes a one-liner of the exact local command (`cargo clippy --all-targets --all-features -- -D warnings`); update CLAUDE.md §5 to match (already aligned) and add a §6 checklist note. + +--- + +## Implementation Plan — 5 PRs + +All PRs: branched off `dev`, opened against `dev`, conventional commits, pre-commit checklist (CLAUDE.md §5) green before push. + +### PR 1 — Trivial wins (~10 lints) + +**Branch**: `chore/clippy-180-pr1-trivial-wins` +**Scope**: +- Unused variables: `crates/core/src/ar/image_proc.rs:217` (`image_temp_u16`), `:218` (`image2`) — prefix `_` or remove. +- `items_after_test_module`: move items above `#[cfg(test)] mod tests` in `crates/core/src/ar/math.rs:831` and `ar/pattern.rs:1050`. +- `needless_deref`: `crates/core/src/kpm/freak/homography.rs:2645`. +- `needless_range_loop` (4 sites): convert to `iter().enumerate()` in `ar2/feature_map.rs`, `ar2/image_set.rs`, `ar/pattern.rs`. + +**Verification**: +```bash +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings # the new lints here pass +cargo test --all-features +``` + +**Commit**: `fix(core): clean up trivial clippy lints under --all-targets --all-features (#180)` + +--- + +### PR 2 — Dead FFI shim hygiene (~14 lints) + +**Branch**: `chore/clippy-180-pr2-ffi-shims` +**Scope**: `crates/core/src/kpm/freak/{clustering,homography,math,hough,matcher}.rs` — for each dead `extern "C" { fn webarkit_cpp_*(...) }` block: + +1. Run `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` to find which shims actually have callers. +2. **If a caller exists** (under any feature combo): add `#[allow(dead_code)]` to the extern block (or to the specific item) with `// rationale: dual-mode parity shim; called from :: under --features dual-mode`. +3. **If no caller exists anywhere**: delete the extern declaration. Note the deletion in the PR body for reviewer scrutiny. + +**Verification**: same triad as PR 1, plus `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` green. + +**Commit**: `chore(kpm): document or remove dead webarkit_cpp_* FFI shims (#180)` + +--- + +### PR 3 — `ar/marker.rs` field-reassign-with-default (44 lints) + +**Branch**: `chore/clippy-180-pr3-ar-marker-init` +**Scope**: `crates/core/src/ar/marker.rs` — mechanical conversion of `let mut h = ARHandle::default(); h.field = x; h.other = y;` to struct-init syntax `let h = ARHandle { field: x, other: y, ..Default::default() };`. + +**Process**: +- One commit per logical group (e.g. one per call site / constructor) to make review tractable. +- If any site fails tests after conversion, revert that specific site with `#[allow(clippy::field_reassign_with_default)]` + `// rationale: init order matters here because `. + +**Verification**: same triad as PR 1, with extra attention to `cargo test --all-features` since `ARHandle` is core path. + +**Commit**: `refactor(ar): convert ARHandle construction to struct-init syntax (#180)` + +--- + +### PR 4 — SIMD `too_many_arguments` `#[allow]` (2 lints) + +**Branch**: `chore/clippy-180-pr4-simd-too-many-args` +**Scope**: `crates/core/src/ar2/feature_map.rs:275` and `:387` — add to each: + +```rust +#[allow(clippy::too_many_arguments)] +// rationale: SIMD variant of get_similarity; signature locked to match +// scalar fallback and sibling SIMD impl for runtime dispatch via +// is_x86_feature_detected!. +``` + +**Verification**: same triad as PR 1. + +**Commit**: `chore(ar2): allow too_many_arguments on SIMD get_similarity variants (#180)` + +--- + +### PR 5 — Tighten CI + +**Branch**: `chore/clippy-180-pr5-ci-tighten` +**Pre-req**: PRs 1–4 merged; `cargo clippy --all-targets --all-features -- -D warnings` clean on `dev`. + +**Scope**: `.github/workflows/ci.yml` +- `build-and-test` job: replace `cargo clippy --workspace -- -D warnings` with `cargo clippy --workspace --all-targets --all-features -- -D warnings`. +- `pure-rust-build` job: keep `cargo clippy -p webarkitlib-rs -- -D warnings` (this job intentionally verifies the no-features path stays clean — that's a different invariant). + +**Verification**: open PR, watch CI go green. If a lint slips in between PR 4 merge and PR 5 (rustc drift, A1), fix in this PR. + +**Commit**: `ci: tighten clippy gate to --all-targets --all-features (#180)` + +--- + +## Closure + +When PR 5 merges: +- Close issue #180. +- Note in the closing comment whether actual lint count matched #180's ~70 estimate (data point for future audits). +- Confirm CLAUDE.md §5 is still accurate (it already prescribes the strict command — no update needed). diff --git a/crates/core/src/ar/bch.rs b/crates/core/src/ar/bch.rs index 44799d8..53362f4 100644 --- a/crates/core/src/ar/bch.rs +++ b/crates/core/src/ar/bch.rs @@ -484,7 +484,7 @@ pub(crate) mod test_helpers { // elements stored as i32 (0 represents zero element). let mut m_coeffs: Vec = vec![1]; // M(x) = 1 in GF(2^7) for &i in &coset { - let alpha_i = BCH_127_ALPHA_TO[i] as i32; + let alpha_i = BCH_127_ALPHA_TO[i]; // Multiply m_coeffs by (x + α^i) (subtraction == addition in GF(2)). let mut new_m = vec![0i32; m_coeffs.len() + 1]; for (idx, &c) in m_coeffs.iter().enumerate() { diff --git a/crates/core/src/ar/image_proc.rs b/crates/core/src/ar/image_proc.rs index 28e33b0..55a4251 100644 --- a/crates/core/src/ar/image_proc.rs +++ b/crates/core/src/ar/image_proc.rs @@ -196,6 +196,13 @@ impl ARImageProcInfo { } /// Calculate image histogram, and box filter image + // rationale: `bias`, `kernel_size_half`, `image_temp_u16`, `image2` are + // used only inside cfg blocks gated on `target_feature = "sse4.1"` / + // `not(target_arch = "x86_64")`. On x86_64 without the compile-time + // sse4.1 gate, no inner branch matches — the variables are genuinely + // unused. The deeper cfg-fallback gap is tracked separately; this + // allow keeps PR scope to the strict clippy gate (#180). + #[allow(unused_variables)] pub fn luma_hist_and_box_filter_with_bias( &mut self, data: &[u8], @@ -731,6 +738,6 @@ mod tests { let thresh = ipi.luma_hist_and_otsu(&img).unwrap(); // Since peaks are 100 and 200, threshold should be around 100. - assert!(thresh >= 100 && thresh < 200); + assert!((100..200).contains(&thresh)); } } diff --git a/crates/core/src/ar/math.rs b/crates/core/src/ar/math.rs index 5e02bc0..74a7398 100644 --- a/crates/core/src/ar/math.rs +++ b/crates/core/src/ar/math.rs @@ -827,6 +827,32 @@ pub fn ar_util_quat_slerp( q[3] = qy[3] * k0 + qz2[3] * k1; } +/// Multiply a 3x4 double matrix by a 3x4 float matrix, outputting a 3x4 float matrix. +/// Port of arUtilMatMuldff. +pub fn mat_mul_dff(a: &[[f64; 4]; 3], b: &[[f32; 4]; 3], dest: &mut [[f32; 4]; 3]) { + for i in 0..3 { + for j in 0..3 { + dest[i][j] = (a[i][0] * b[0][j] as f64 + + a[i][1] * b[1][j] as f64 + + a[i][2] * b[2][j] as f64) as f32; + } + dest[i][3] = (a[i][0] * b[0][3] as f64 + + a[i][1] * b[1][3] as f64 + + a[i][2] * b[2][3] as f64 + + a[i][3]) as f32; + } +} + +/// Multiply two 3x4 float matrices, outputting a 3x4 float matrix. +pub fn mat_mul_fff(a: &[[f32; 4]; 3], b: &[[f32; 4]; 3], dest: &mut [[f32; 4]; 3]) { + for i in 0..3 { + for j in 0..3 { + dest[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; + } + dest[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3]; + } +} + #[cfg(test)] mod tests { use super::*; @@ -978,29 +1004,3 @@ mod tests { assert!((qr[3]).abs() < 1e-10); } } - -/// Multiply a 3x4 double matrix by a 3x4 float matrix, outputting a 3x4 float matrix. -/// Port of arUtilMatMuldff. -pub fn mat_mul_dff(a: &[[f64; 4]; 3], b: &[[f32; 4]; 3], dest: &mut [[f32; 4]; 3]) { - for i in 0..3 { - for j in 0..3 { - dest[i][j] = (a[i][0] * b[0][j] as f64 - + a[i][1] * b[1][j] as f64 - + a[i][2] * b[2][j] as f64) as f32; - } - dest[i][3] = (a[i][0] * b[0][3] as f64 - + a[i][1] * b[1][3] as f64 - + a[i][2] * b[2][3] as f64 - + a[i][3]) as f32; - } -} - -/// Multiply two 3x4 float matrices, outputting a 3x4 float matrix. -pub fn mat_mul_fff(a: &[[f32; 4]; 3], b: &[[f32; 4]; 3], dest: &mut [[f32; 4]; 3]) { - for i in 0..3 { - for j in 0..3 { - dest[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; - } - dest[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3]; - } -} diff --git a/crates/core/src/ar/matrix.rs b/crates/core/src/ar/matrix.rs index 600619d..de4e95b 100644 --- a/crates/core/src/ar/matrix.rs +++ b/crates/core/src/ar/matrix.rs @@ -940,9 +940,9 @@ mod tests { // Build a known 120-bit pattern and confirm each rotation of the // input grid yields the same `recd[0..120]` (different dir values). let mut bits = [0u8; 120]; - for i in 0..120 { + for (i, b) in bits.iter_mut().enumerate() { // Pseudo-random pattern: prime-indexed positions are 1. - bits[i] = if matches!( + *b = if matches!( i, 2 | 3 | 5 diff --git a/crates/core/src/ar/pattern.rs b/crates/core/src/ar/pattern.rs index dc90a09..a50e02f 100644 --- a/crates/core/src/ar/pattern.rs +++ b/crates/core/src/ar/pattern.rs @@ -1046,127 +1046,6 @@ pub fn ar_patt_get_id( pattern_match(patt_handle, patt_detect_mode, data, patt_size) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pattern_load_and_match() { - let mut handle = ARPattHandle::new(AR_PATT_SIZE1, AR_PATT_NUM_MAX); - - let mut mock_patt = String::new(); - // 4 orientations * size * size * 3 colors - for _ in 0..(4 * AR_PATT_SIZE1 * AR_PATT_SIZE1 * 3) { - mock_patt.push_str("128 "); - } - - let patno = ar_patt_load_from_buffer(&mut handle, &mock_patt).unwrap(); - assert_eq!(patno, 0); - assert_eq!(handle.patt_num, 1); - - // Mock extracted pattern with high contrast - let mut mock_data = vec![0; (AR_PATT_SIZE1 * AR_PATT_SIZE1 * 3) as usize]; - for i in 0..mock_data.len() { - if i % 2 == 0 { - mock_data[i] = 10; - } else { - mock_data[i] = 240; - } - } - - let result = pattern_match( - &handle, - AR_TEMPLATE_MATCHING_COLOR, - &mock_data, - AR_PATT_SIZE1, - ); - assert!(result.is_ok()); - let ok = result.unwrap(); - assert!(ok.cf >= 0.0); - assert_eq!(ok.error_corrected, 0); - } - - #[test] - fn test_ar_patt_save_and_load() { - use image::ImageReader; - use std::fs; - let filename = "test_pattern_save.patt"; - let patt_size_i32: i32 = 16; - let patt_size: usize = 16; - - // Load the image from examples/Data relative to this crate (CARGO_MANIFEST_DIR) - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let img_path = std::path::Path::new(manifest_dir) - .join("examples") - .join("Data") - .join("HIRO-test.jpg"); - let img = ImageReader::open(img_path).unwrap().decode().unwrap(); - // Use actual image dimensions to avoid out-of-bounds when sampling - let xsize_i32 = img.width() as i32; - let ysize_i32 = img.height() as i32; - let xsize = img.width() as usize; - let ysize = img.height() as usize; - let image_buf = img.to_rgb8(); - // consume image buffer to obtain owned Vec for API which expects &[u8] - let image = image_buf.into_raw(); - - // Define a simple square quad (in image coordinates) - let vertex = [ - [100.0, 100.0], - [200.0, 100.0], - [200.0, 200.0], - [100.0, 200.0], - ]; - - // Build a simple ARParamLTf lookup table (identity mapping) to satisfy ar_patt_get_image2 - let param_ltf = ARParamLTf::new_basic(xsize_i32, ysize_i32); - - // Build a simple marker_info with the quad we defined - let mut marker = ARMarkerInfo::default(); - marker.vertex = vertex; - - // Save using the image-first signature of ar_patt_save - let filename_path = std::path::Path::new(filename); - let res = ar_patt_save( - &image, - xsize, - ysize, - ARPixelFormat::RGB, - ¶m_ltf, - AR_IMAGE_PROC_FRAME_IMAGE, - &marker, - 0.5, // patt_ratio - patt_size, - filename_path, - ); - - assert!(res.is_ok(), "ar_patt_save failed: {:?}", res.err()); - assert!( - fs::metadata(filename).is_ok(), - "Pattern file was not created" - ); - - // Test loading it back using the buffer version since load_from_file is not available - let content = fs::read_to_string(filename).expect("Failed to read saved pattern file"); - let mut handle = ARPattHandle::new(patt_size_i32, 1); - let load_res = ar_patt_load_from_buffer(&mut handle, &content); - - assert!( - load_res.is_ok(), - "ar_patt_load_from_buffer failed: {:?}", - load_res.err() - ); - - // Compare with crates/core/examples/Data/patt.hiro (left commented for now) - let _reference_content = fs::read_to_string("../core/examples/Data/patt.hiro") - .expect("Failed to read reference pattern file"); - // assert_eq!(content, _reference_content, "Saved pattern does not match the reference pattern"); - - // Cleanup - let _ = fs::remove_file(filename); - } -} - #[inline] fn dot_product(a: &[i16], b: &[i16]) -> i64 { #[cfg(feature = "simd-pattern")] @@ -1283,3 +1162,120 @@ pub unsafe fn dot_product_simd_x86(a: &[i16], b: &[i16]) -> i64 { total } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pattern_load_and_match() { + let mut handle = ARPattHandle::new(AR_PATT_SIZE1, AR_PATT_NUM_MAX); + + let mut mock_patt = String::new(); + // 4 orientations * size * size * 3 colors + for _ in 0..(4 * AR_PATT_SIZE1 * AR_PATT_SIZE1 * 3) { + mock_patt.push_str("128 "); + } + + let patno = ar_patt_load_from_buffer(&mut handle, &mock_patt).unwrap(); + assert_eq!(patno, 0); + assert_eq!(handle.patt_num, 1); + + // Mock extracted pattern with high contrast + let mut mock_data = vec![0; (AR_PATT_SIZE1 * AR_PATT_SIZE1 * 3) as usize]; + for (i, px) in mock_data.iter_mut().enumerate() { + *px = if i % 2 == 0 { 10 } else { 240 }; + } + + let result = pattern_match( + &handle, + AR_TEMPLATE_MATCHING_COLOR, + &mock_data, + AR_PATT_SIZE1, + ); + assert!(result.is_ok()); + let ok = result.unwrap(); + assert!(ok.cf >= 0.0); + assert_eq!(ok.error_corrected, 0); + } + + #[test] + fn test_ar_patt_save_and_load() { + use image::ImageReader; + use std::fs; + let filename = "test_pattern_save.patt"; + let patt_size_i32: i32 = 16; + let patt_size: usize = 16; + + // Load the image from examples/Data relative to this crate (CARGO_MANIFEST_DIR) + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let img_path = std::path::Path::new(manifest_dir) + .join("examples") + .join("Data") + .join("HIRO-test.jpg"); + let img = ImageReader::open(img_path).unwrap().decode().unwrap(); + // Use actual image dimensions to avoid out-of-bounds when sampling + let xsize_i32 = img.width() as i32; + let ysize_i32 = img.height() as i32; + let xsize = img.width() as usize; + let ysize = img.height() as usize; + let image_buf = img.to_rgb8(); + // consume image buffer to obtain owned Vec for API which expects &[u8] + let image = image_buf.into_raw(); + + // Define a simple square quad (in image coordinates) + let vertex = [ + [100.0, 100.0], + [200.0, 100.0], + [200.0, 200.0], + [100.0, 200.0], + ]; + + // Build a simple ARParamLTf lookup table (identity mapping) to satisfy ar_patt_get_image2 + let param_ltf = ARParamLTf::new_basic(xsize_i32, ysize_i32); + + // Build a simple marker_info with the quad we defined + let mut marker = ARMarkerInfo::default(); + marker.vertex = vertex; + + // Save using the image-first signature of ar_patt_save + let filename_path = std::path::Path::new(filename); + let res = ar_patt_save( + &image, + xsize, + ysize, + ARPixelFormat::RGB, + ¶m_ltf, + AR_IMAGE_PROC_FRAME_IMAGE, + &marker, + 0.5, // patt_ratio + patt_size, + filename_path, + ); + + assert!(res.is_ok(), "ar_patt_save failed: {:?}", res.err()); + assert!( + fs::metadata(filename).is_ok(), + "Pattern file was not created" + ); + + // Test loading it back using the buffer version since load_from_file is not available + let content = fs::read_to_string(filename).expect("Failed to read saved pattern file"); + let mut handle = ARPattHandle::new(patt_size_i32, 1); + let load_res = ar_patt_load_from_buffer(&mut handle, &content); + + assert!( + load_res.is_ok(), + "ar_patt_load_from_buffer failed: {:?}", + load_res.err() + ); + + // Compare with crates/core/examples/Data/patt.hiro (left commented for now) + let _reference_content = fs::read_to_string("../core/examples/Data/patt.hiro") + .expect("Failed to read reference pattern file"); + // assert_eq!(content, _reference_content, "Saved pattern does not match the reference pattern"); + + // Cleanup + let _ = fs::remove_file(filename); + } +} diff --git a/crates/core/src/ar2/feature_map.rs b/crates/core/src/ar2/feature_map.rs index 9ac287c..4879caa 100644 --- a/crates/core/src/ar2/feature_map.rs +++ b/crates/core/src/ar2/feature_map.rs @@ -326,8 +326,8 @@ unsafe fn get_similarity_sse41( i += 16; } // Tail for sx/sxx (scalar) - for ii in i..patch_size { - let p = img_row[ii] as u64; + for &b in &img_row[i..patch_size] { + let p = b as u64; sx_i += p; sxx_i += p * p; } @@ -432,8 +432,8 @@ unsafe fn get_similarity_avx2( sxx_i += _mm_cvtsi128_si32(sum1) as u32 as u64; i += 16; } - for ii in i..patch_size { - let p = img_row[ii] as u64; + for &b in &img_row[i..patch_size] { + let p = b as u64; sx_i += p; sxx_i += p * p; } diff --git a/crates/core/src/ar2/image_set.rs b/crates/core/src/ar2/image_set.rs index b2eeed1..2a71dd8 100644 --- a/crates/core/src/ar2/image_set.rs +++ b/crates/core/src/ar2/image_set.rs @@ -967,8 +967,8 @@ mod tests { // Level 1: 4×4 at 100 DPI — gradient. let mut pixels_1 = vec![0u8; 16]; - for i in 0..16 { - pixels_1[i] = (i as u8) * 16; + for (i, px) in pixels_1.iter_mut().enumerate() { + *px = (i as u8) * 16; } let original = AR2ImageSetT { @@ -1010,8 +1010,8 @@ mod tests { fn test_image_set_save_jpeg_load_roundtrip() { // 16×16 gradient at 150 DPI — single scale for simplicity. let mut pixels = vec![0u8; 256]; - for i in 0..256 { - pixels[i] = i as u8; + for (i, px) in pixels.iter_mut().enumerate() { + *px = i as u8; } let original = AR2ImageSetT { diff --git a/crates/core/src/arlog.rs b/crates/core/src/arlog.rs index 446ad67..d4a8842 100644 --- a/crates/core/src/arlog.rs +++ b/crates/core/src/arlog.rs @@ -296,10 +296,8 @@ fn init_env_logger(verbose: bool) { /// Call once in your binary's `main()` — never from library code. /// /// ```no_run -/// fn main() { -/// webarkitlib_rs::arlog::ar_log_init_default(); -/// // ... rest of your application -/// } +/// webarkitlib_rs::arlog::ar_log_init_default(); +/// // ... rest of your application /// ``` #[cfg(all(feature = "log-helpers", not(target_arch = "wasm32")))] pub fn ar_log_init_default() { diff --git a/crates/core/src/kpm/freak/homography.rs b/crates/core/src/kpm/freak/homography.rs index c5e9184..83cbeed 100644 --- a/crates/core/src/kpm/freak/homography.rs +++ b/crates/core/src/kpm/freak/homography.rs @@ -2642,7 +2642,7 @@ mod tests { // Verify: each point projects to itself (after dividing by w) for pt in &[&p1, &p2, &p3, &p4] { let mut out = [0.0_f32; 2]; - multiply_point_homography_inhomogenous(&mut out, &h, *pt); + multiply_point_homography_inhomogenous(&mut out, &h, pt); assert!( approx_eq(out[0], pt[0], 1e-3) && approx_eq(out[1], pt[1], 1e-3), "identity DLT failed for pt={:?}, got out={:?}", diff --git a/crates/core/src/kpm/freak/math.rs b/crates/core/src/kpm/freak/math.rs index 10aa581..cf1b476 100644 --- a/crates/core/src/kpm/freak/math.rs +++ b/crates/core/src/kpm/freak/math.rs @@ -1651,7 +1651,7 @@ mod tests { #[test] fn test_fast_atan2_360() { let result = fast_atan2_360(0.0, 1.0); - assert!(result >= 0.0 && result <= 360.0); + assert!((0.0..=360.0).contains(&result)); } #[test] diff --git a/crates/core/src/version.rs b/crates/core/src/version.rs index ee10128..905be4d 100644 --- a/crates/core/src/version.rs +++ b/crates/core/src/version.rs @@ -83,7 +83,7 @@ mod tests { let v = get_version(); // Only validate the numeric core (major.minor[.patch]) before any pre-release/build suffix. - let core = v.split(|c| c == '-' || c == '+').next().unwrap_or(v); + let core = v.split(['-', '+']).next().unwrap_or(v); let parts: Vec<&str> = core.split('.').collect(); assert!( From 224d76697b8ce39412783360b72957d3e0e639b6 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 10 Jun 2026 23:45:30 +0200 Subject: [PATCH 04/40] test(core): add unit tests for relocated math/pattern helpers (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving `mat_mul_dff`, `mat_mul_fff` (math.rs) and the `dot_product*` family (pattern.rs) above their respective test modules in the previous commit tripped codecov/patch (0% diff coverage, target 54.63%) — the code is byte-identical, but the new line positions registered as additions, exposing a pre-existing gap that none of these helpers had direct unit tests. Add minimal scalar tests: - mat_mul_dff: identity case + known-values case (translation column). - mat_mul_fff: identity case + composed-translation case. - dot_product_scalar: known values, all-zero input, negative values. - dot_product (dispatcher): asserts SIMD/scalar paths agree at runtime. SIMD variants (dot_product_simd_wasm, dot_product_simd_x86) remain covered only via the dispatcher equivalence test and the existing pattern-matching integration tests; direct cfg-gated SIMD unit tests are out of scope for #180 PR 1 and tracked separately. --- crates/core/src/ar/math.rs | 84 +++++++++++++++++++++++++++++++++++ crates/core/src/ar/pattern.rs | 33 ++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/crates/core/src/ar/math.rs b/crates/core/src/ar/math.rs index 74a7398..6154a1d 100644 --- a/crates/core/src/ar/math.rs +++ b/crates/core/src/ar/math.rs @@ -1003,4 +1003,88 @@ mod tests { assert!((qr[2]).abs() < 1e-10); assert!((qr[3]).abs() < 1e-10); } + + #[test] + fn test_mat_mul_dff_identity() { + // I_dbl * B_f32 == B_f32 (columns 0..3); column 3 picks up the + // a[i][3] translation term. + let a: [[f64; 4]; 3] = [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + ]; + let b: [[f32; 4]; 3] = [ + [1.0, 2.0, 3.0, 4.0], + [5.0, 6.0, 7.0, 8.0], + [9.0, 10.0, 11.0, 12.0], + ]; + let mut dest = [[0.0f32; 4]; 3]; + mat_mul_dff(&a, &b, &mut dest); + assert_eq!(dest, b); + } + + #[test] + fn test_mat_mul_dff_known_values() { + // a row 0: [2, 0, 0, 1] picks 2*b[0][j] + a[0][3] for column 3. + let a: [[f64; 4]; 3] = [ + [2.0, 0.0, 0.0, 1.0], + [0.0, 3.0, 0.0, -1.0], + [0.0, 0.0, 4.0, 0.5], + ]; + let b: [[f32; 4]; 3] = [ + [1.0, 1.0, 1.0, 10.0], + [2.0, 2.0, 2.0, 20.0], + [3.0, 3.0, 3.0, 30.0], + ]; + let mut dest = [[0.0f32; 4]; 3]; + mat_mul_dff(&a, &b, &mut dest); + assert!((dest[0][0] - 2.0).abs() < 1e-5); + assert!((dest[0][3] - (20.0 + 1.0)).abs() < 1e-5); + assert!((dest[1][1] - 6.0).abs() < 1e-5); + assert!((dest[1][3] - (60.0 - 1.0)).abs() < 1e-5); + assert!((dest[2][2] - 12.0).abs() < 1e-5); + assert!((dest[2][3] - (120.0 + 0.5)).abs() < 1e-5); + } + + #[test] + fn test_mat_mul_fff_identity() { + let a: [[f32; 4]; 3] = [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + ]; + let b: [[f32; 4]; 3] = [ + [1.0, 2.0, 3.0, 4.0], + [5.0, 6.0, 7.0, 8.0], + [9.0, 10.0, 11.0, 12.0], + ]; + let mut dest = [[0.0f32; 4]; 3]; + mat_mul_fff(&a, &b, &mut dest); + assert_eq!(dest, b); + } + + #[test] + fn test_mat_mul_fff_composes_translation() { + // Pure translation composed with pure translation == sum of + // translations; rotation block (cols 0..3) stays identity. + let a: [[f32; 4]; 3] = [ + [1.0, 0.0, 0.0, 1.0], + [0.0, 1.0, 0.0, 2.0], + [0.0, 0.0, 1.0, 3.0], + ]; + let b: [[f32; 4]; 3] = [ + [1.0, 0.0, 0.0, 10.0], + [0.0, 1.0, 0.0, 20.0], + [0.0, 0.0, 1.0, 30.0], + ]; + let mut dest = [[0.0f32; 4]; 3]; + mat_mul_fff(&a, &b, &mut dest); + assert!((dest[0][3] - 11.0).abs() < 1e-5); + assert!((dest[1][3] - 22.0).abs() < 1e-5); + assert!((dest[2][3] - 33.0).abs() < 1e-5); + // Diagonal stays 1. + assert!((dest[0][0] - 1.0).abs() < 1e-5); + assert!((dest[1][1] - 1.0).abs() < 1e-5); + assert!((dest[2][2] - 1.0).abs() < 1e-5); + } } diff --git a/crates/core/src/ar/pattern.rs b/crates/core/src/ar/pattern.rs index a50e02f..f674efa 100644 --- a/crates/core/src/ar/pattern.rs +++ b/crates/core/src/ar/pattern.rs @@ -1278,4 +1278,37 @@ mod tests { // Cleanup let _ = fs::remove_file(filename); } + + #[test] + fn test_dot_product_scalar_known_values() { + let a: [i16; 4] = [1, 2, 3, 4]; + let b: [i16; 4] = [5, 6, 7, 8]; + // 1*5 + 2*6 + 3*7 + 4*8 = 5 + 12 + 21 + 32 = 70 + assert_eq!(dot_product_scalar(&a, &b), 70); + } + + #[test] + fn test_dot_product_scalar_zero_inputs() { + let a = vec![0i16; 32]; + let b = vec![123i16; 32]; + assert_eq!(dot_product_scalar(&a, &b), 0); + } + + #[test] + fn test_dot_product_scalar_negative() { + let a: [i16; 3] = [-1, -2, -3]; + let b: [i16; 3] = [4, 5, 6]; + // -4 + -10 + -18 = -32 + assert_eq!(dot_product_scalar(&a, &b), -32); + } + + #[test] + fn test_dot_product_dispatcher_matches_scalar() { + // dot_product chooses SIMD when the runtime feature is detected + // and the cfg gate matches, else falls back to the scalar path. + // Both paths must produce the same integer-exact result. + let a: Vec = (0..23).map(|i| i as i16 - 11).collect(); + let b: Vec = (0..23).map(|i| 2 * i as i16 + 1).collect(); + assert_eq!(dot_product(&a, &b), dot_product_scalar(&a, &b)); + } } From f8521ebc85c7399c90b855ee1a05446ecf70adfc Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 15:13:14 +0200 Subject: [PATCH 05/40] fix(kpm): tighten dual-mode FFI extern cfg to match caller cfg (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 2 of 5 in the #180 series. Clears all 17 dead `webarkit_cpp_*` extern declarations flagged by `cargo clippy --all-targets --all-features -- -D warnings` (baseline 43 → 24 errors). Root cause: each extern block was gated `#[cfg(feature = "dual-mode")]` but its only callers live in `#[cfg(all(test, feature = "dual-mode"))]` modules in the same file. When clippy compiles the lib target with `--all-features`, dual-mode is on but `test` is not — so the extern is present, callers are gone, and the dead-code lint fires legitimately. Fix: tighten each block to `#[cfg(all(test, feature = "dual-mode"))]`, matching its caller exactly. The extern now exists only when its caller does, the lint disappears, and dual-mode test binaries are unchanged. Files: - kpm/freak/clustering.rs — fast_random/array_shuffle/bhc_build_and_query - kpm/freak/homography.rs — mat3_exp_pade_via_eigen/preemptive_/robust_ - kpm/freak/hough.rs — auto_adjust_xy_num_bins - kpm/freak/math.rs — fast_atan2/sqrt1/exp6 + solve_* + partial_sort - kpm/freak/matcher.rs — match_features_brute/indexed/guided Plan doc: Decision Log #2 updated to record the switch from the originally-planned `#[allow(dead_code)]` + rationale approach to the cfg-tightening approach, with the diagnostic evidence that drove it. Verified: - cargo clippy --all-targets --all-features -- -D warnings: 17 shim lints clear; remaining 24 errors all in PR 3-4 scope. - cargo test --all-features: 471 passed, 8 ignored. - cargo test -p webarkitlib-rs --lib --features dual-mode: 445 passed. - cargo clippy --workspace -- -D warnings (existing CI gate): clean. - cargo clippy -p webarkitlib-rs -- -D warnings (pure-Rust gate): clean. --- .claude/issue-180-plan.md | 6 ++---- crates/core/src/kpm/freak/clustering.rs | 5 ++++- crates/core/src/kpm/freak/homography.rs | 5 ++++- crates/core/src/kpm/freak/hough.rs | 5 ++++- crates/core/src/kpm/freak/matcher.rs | 5 ++++- crates/core/src/kpm/freak/math.rs | 5 ++++- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.claude/issue-180-plan.md b/.claude/issue-180-plan.md index c9c069c..154e686 100644 --- a/.claude/issue-180-plan.md +++ b/.claude/issue-180-plan.md @@ -26,7 +26,7 @@ | # | Decision | Alternatives considered | Why this choice | |---|---|---|---| | 1 | Follow #180's 5-PR sequence verbatim | Refine ordering; merge PRs; re-baseline lint counts first | Issue is well-scoped; counts may drift but plan structure is sound | -| 2 | PR 2 default posture: `#[allow(dead_code)]` + `// rationale:` on extern blocks; delete only the truly orphaned ones | Wire up callers (scope creep); bulk delete (irreversible); case-by-case triage (review burden) | Lowest risk; zero behavior change; honest about why shims exist (dual-mode parity) | +| 2 | **(Updated during PR 2)** Tighten each extern block's cfg from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` — matches caller cfg, lint disappears because the extern only exists when callers do. Originally planned `#[allow(dead_code)]` + rationale. | Original plan (allow + rationale); wire up callers (scope creep); bulk delete (irreversible) | Evidence at PR 2 time: all 17 "dead" shims have callers, but the callers are gated `cfg(all(test, feature = "dual-mode"))` while the extern blocks were gated only on `cfg(feature = "dual-mode")`. The cfg mismatch was the real root cause; tightening fixes it honestly. Same effort per block (one line), zero behavior change. | | 3 | PR 4: inline `#[allow(clippy::too_many_arguments)]` + rationale on `get_similarity_sse41` / `get_similarity_avx2` | `clippy.toml` threshold raise; bundle args into struct | SIMD signatures locked by runtime-dispatch contract; inline is scoped, threshold raise loses signal everywhere | | 4 | Defer `clippy.toml` | Add now with `too-many-arguments-threshold = 9` | Only 2 sites today; revisit when SIMD surface grows in M10+ | | 5 | Keep CI on `dtolnay/rust-toolchain@stable` after PR 5 | Pin to a specific rustc version; document known-good rustc | Accept the churn; v0.7.0 is shipped, future rustc lint additions fix in follow-up PRs | @@ -81,9 +81,7 @@ cargo test --all-features **Branch**: `chore/clippy-180-pr2-ffi-shims` **Scope**: `crates/core/src/kpm/freak/{clustering,homography,math,hough,matcher}.rs` — for each dead `extern "C" { fn webarkit_cpp_*(...) }` block: -1. Run `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` to find which shims actually have callers. -2. **If a caller exists** (under any feature combo): add `#[allow(dead_code)]` to the extern block (or to the specific item) with `// rationale: dual-mode parity shim; called from :: under --features dual-mode`. -3. **If no caller exists anywhere**: delete the extern declaration. Note the deletion in the PR body for reviewer scrutiny. +**Implemented (Decision #2 updated)**: change each block's cfg gate from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` so the extern only exists when its caller (also `cfg(all(test, feature = "dual-mode"))`) does. Add a one-line comment above each block referencing #180 for traceability. Verified all 17 callers still resolve and dual-mode tests still pass. **Verification**: same triad as PR 1, plus `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` green. diff --git a/crates/core/src/kpm/freak/clustering.rs b/crates/core/src/kpm/freak/clustering.rs index cdcb4f1..174043e 100644 --- a/crates/core/src/kpm/freak/clustering.rs +++ b/crates/core/src/kpm/freak/clustering.rs @@ -1047,7 +1047,10 @@ mod tests { // from math/rand.h). This is what enables the BHC-indexed match dual-mode // test in matcher.rs to assert pair equality with C++ rather than count-only. -#[cfg(feature = "dual-mode")] +// Only ever called from `#[cfg(all(test, feature = "dual-mode"))]` +// modules below; gate the extern declaration the same way so the lib +// target under --all-features doesn't see a phantom dead symbol (#180). +#[cfg(all(test, feature = "dual-mode"))] extern "C" { fn webarkit_cpp_fast_random(seed: *mut i32) -> i32; fn webarkit_cpp_array_shuffle(v: *mut i32, pop_size: i32, sample_size: i32, seed: *mut i32); diff --git a/crates/core/src/kpm/freak/homography.rs b/crates/core/src/kpm/freak/homography.rs index 83cbeed..e1e26c0 100644 --- a/crates/core/src/kpm/freak/homography.rs +++ b/crates/core/src/kpm/freak/homography.rs @@ -2813,7 +2813,10 @@ mod tests { // and the final homography agrees element-wise within accumulated float // rounding (~1e-5). -#[cfg(feature = "dual-mode")] +// Only ever called from `#[cfg(all(test, feature = "dual-mode"))]` +// modules below; gate the extern declaration the same way so the lib +// target under --all-features doesn't see a phantom dead symbol (#180). +#[cfg(all(test, feature = "dual-mode"))] extern "C" { fn webarkit_cpp_mat3_exp_pade_via_eigen(out: *mut f32, input: *const f32); diff --git a/crates/core/src/kpm/freak/hough.rs b/crates/core/src/kpm/freak/hough.rs index e03629c..0ee4b32 100644 --- a/crates/core/src/kpm/freak/hough.rs +++ b/crates/core/src/kpm/freak/hough.rs @@ -1195,7 +1195,10 @@ mod tests { // the algorithm level, separately from the end-to-end VisualDatabase // parity gate in `visual_database.rs`. -#[cfg(feature = "dual-mode")] +// Only ever called from `#[cfg(all(test, feature = "dual-mode"))]` +// modules below; gate the extern declaration the same way so the lib +// target under --all-features doesn't see a phantom dead symbol (#180). +#[cfg(all(test, feature = "dual-mode"))] extern "C" { /// M9 #150: reimplementation of `HoughSimilarityVoting::autoAdjustXYNumBins` /// in `kpm_c_api.cpp` (the C++ method is private, so the shim ports the diff --git a/crates/core/src/kpm/freak/matcher.rs b/crates/core/src/kpm/freak/matcher.rs index e730f4d..80820b7 100644 --- a/crates/core/src/kpm/freak/matcher.rs +++ b/crates/core/src/kpm/freak/matcher.rs @@ -805,7 +805,10 @@ mod tests { // assert that the match counts agree within a small tolerance (BHC RNG // differences and minor floating-point order can cause small variation). -#[cfg(feature = "dual-mode")] +// Only ever called from `#[cfg(all(test, feature = "dual-mode"))]` +// modules below; gate the extern declaration the same way so the lib +// target under --all-features doesn't see a phantom dead symbol (#180). +#[cfg(all(test, feature = "dual-mode"))] extern "C" { fn webarkit_cpp_match_features_brute( query_descs: *const u8, diff --git a/crates/core/src/kpm/freak/math.rs b/crates/core/src/kpm/freak/math.rs index cf1b476..59800f9 100644 --- a/crates/core/src/kpm/freak/math.rs +++ b/crates/core/src/kpm/freak/math.rs @@ -2106,7 +2106,10 @@ mod tests { // the input domain and assert that the pure-Rust ports produce results within // a small tolerance of the C++ baseline. -#[cfg(feature = "dual-mode")] +// Only ever called from `#[cfg(all(test, feature = "dual-mode"))]` +// modules below; gate the extern declaration the same way so the lib +// target under --all-features doesn't see a phantom dead symbol (#180). +#[cfg(all(test, feature = "dual-mode"))] extern "C" { // M6-1 (#63) — math_utils.h fn webarkit_cpp_fast_atan2(y: f32, x: f32) -> f32; From a0e43887886b854242c5be0ca4f7feba9ce84925 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 16:53:17 +0200 Subject: [PATCH 06/40] refactor(ar): convert Default::default() + field reassign to struct-init (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 3 of 5 in the #180 series. Clears all 22 `field_reassign_with_default` lints flagged by `cargo clippy --all-targets --all-features -- -D warnings` (baseline 24 → 2; the remaining 2 are the SIMD `too_many_arguments` pair earmarked for PR 4). Mechanical conversion across 4 files: - ar/marker.rs — 17 sites (ARMarkerInfo: 6 sites, ARHandle: 11 sites). Tests for finalize_marker_id_cf_dir and history-merge/resurrection pipelines. - ar/param.rs — 3 sites (ARParam in ar_param_change_size tests). - ar/param_gl.rs — 1 site (ARParam in make_param helper). - ar/pattern.rs — 1 site (ARMarkerInfo in ar_patt_save test). All sites are test-only code; #180 noted the lint as pre-M9 churn, nothing pre-existing breaks. Where the original code interleaved non-field reassigns (e.g. `src.mat[0] = [...]` array indexing) the let-binding stays `mut` so subsequent index assignments still compile. Verified: - cargo clippy --all-targets --all-features -- -D warnings: 22 lints clear; remaining 2 errors are the documented PR 4 SIMD sites. - cargo test --all-features: 471 passed, 8 ignored. - cargo clippy --workspace -- -D warnings (existing CI gate): clean. - cargo clippy -p webarkitlib-rs -- -D warnings (pure-Rust gate): clean. --- crates/core/src/ar/marker.rs | 167 ++++++++++++++++++++------------- crates/core/src/ar/param.rs | 24 +++-- crates/core/src/ar/param_gl.rs | 8 +- crates/core/src/ar/pattern.rs | 6 +- 4 files changed, 124 insertions(+), 81 deletions(-) diff --git a/crates/core/src/ar/marker.rs b/crates/core/src/ar/marker.rs index e5e4e00..9beb731 100644 --- a/crates/core/src/ar/marker.rs +++ b/crates/core/src/ar/marker.rs @@ -1475,13 +1475,15 @@ mod tests { /// `arGetMarkerInfo.c` lines 92-100. #[test] fn test_finalize_marker_id_cf_dir_template_color() { - let mut m = ARMarkerInfo::default(); - m.id_patt = 7; - m.dir_patt = 2; - m.cf_patt = 0.85; - m.id_matrix = 99; // should be ignored - m.dir_matrix = 3; - m.cf_matrix = 0.5; + let mut m = ARMarkerInfo { + id_patt: 7, + dir_patt: 2, + cf_patt: 0.85, + id_matrix: 99, // should be ignored + dir_matrix: 3, + cf_matrix: 0.5, + ..Default::default() + }; finalize_marker_id_cf_dir(&mut m, crate::pattern::AR_TEMPLATE_MATCHING_COLOR); @@ -1492,10 +1494,12 @@ mod tests { #[test] fn test_finalize_marker_id_cf_dir_template_mono() { - let mut m = ARMarkerInfo::default(); - m.id_patt = 4; - m.dir_patt = 1; - m.cf_patt = 0.7; + let mut m = ARMarkerInfo { + id_patt: 4, + dir_patt: 1, + cf_patt: 0.7, + ..Default::default() + }; finalize_marker_id_cf_dir(&mut m, crate::pattern::AR_TEMPLATE_MATCHING_MONO); @@ -1506,13 +1510,15 @@ mod tests { #[test] fn test_finalize_marker_id_cf_dir_matrix_only() { - let mut m = ARMarkerInfo::default(); - m.id_patt = 99; // should be ignored - m.dir_patt = 2; - m.cf_patt = 0.5; - m.id_matrix = 12; - m.dir_matrix = 0; - m.cf_matrix = 0.92; + let mut m = ARMarkerInfo { + id_patt: 99, // should be ignored + dir_patt: 2, + cf_patt: 0.5, + id_matrix: 12, + dir_matrix: 0, + cf_matrix: 0.92, + ..Default::default() + }; finalize_marker_id_cf_dir(&mut m, crate::types::AR_MATRIX_CODE_DETECTION); @@ -1523,17 +1529,19 @@ mod tests { #[test] fn test_finalize_marker_id_cf_dir_mixed_color_matrix_leaves_finals_alone() { - let mut m = ARMarkerInfo::default(); // Pre-set finals to sentinel values; verify they're not overwritten. - m.id = -42; - m.dir = -42; - m.cf = -42.0; - m.id_patt = 1; - m.dir_patt = 1; - m.cf_patt = 0.1; - m.id_matrix = 2; - m.dir_matrix = 2; - m.cf_matrix = 0.2; + let mut m = ARMarkerInfo { + id: -42, + dir: -42, + cf: -42.0, + id_patt: 1, + dir_patt: 1, + cf_patt: 0.1, + id_matrix: 2, + dir_matrix: 2, + cf_matrix: 0.2, + ..Default::default() + }; finalize_marker_id_cf_dir( &mut m, @@ -1547,10 +1555,12 @@ mod tests { #[test] fn test_finalize_marker_id_cf_dir_mixed_mono_matrix_leaves_finals_alone() { - let mut m = ARMarkerInfo::default(); - m.id = -42; - m.dir = -42; - m.cf = -42.0; + let mut m = ARMarkerInfo { + id: -42, + dir: -42, + cf: -42.0, + ..Default::default() + }; finalize_marker_id_cf_dir( &mut m, @@ -1920,16 +1930,18 @@ mod tests { /// leaves finals untouched. #[test] fn test_finalize_marker_id_cf_dir_mono_matrix_leaves_finals_alone() { - let mut m = ARMarkerInfo::default(); - m.id = -42; - m.dir = -42; - m.cf = -42.0; - m.id_patt = 1; - m.dir_patt = 1; - m.cf_patt = 0.6; - m.id_matrix = 2; - m.dir_matrix = 2; - m.cf_matrix = 0.7; + let mut m = ARMarkerInfo { + id: -42, + dir: -42, + cf: -42.0, + id_patt: 1, + dir_patt: 1, + cf_patt: 0.6, + id_matrix: 2, + dir_matrix: 2, + cf_matrix: 0.7, + ..Default::default() + }; finalize_marker_id_cf_dir( &mut m, @@ -1996,8 +2008,10 @@ mod tests { #[test] fn test_cutoff_phase_set_on_barcode_edc_fail() { use crate::types::{ARMarkerInfoCutoffPhase, MatchError}; - let mut marker = ARMarkerInfo::default(); - marker.cutoff_phase = MatchError::BarcodeEdcFail.into(); + let marker = ARMarkerInfo { + cutoff_phase: MatchError::BarcodeEdcFail.into(), + ..Default::default() + }; assert_eq!( marker.cutoff_phase, ARMarkerInfoCutoffPhase::MatchBarcodeEdcFail @@ -2008,8 +2022,10 @@ mod tests { #[test] fn test_cutoff_phase_set_on_match_generic() { use crate::types::{ARMarkerInfoCutoffPhase, MatchError}; - let mut marker = ARMarkerInfo::default(); - marker.cutoff_phase = MatchError::Generic.into(); + let marker = ARMarkerInfo { + cutoff_phase: MatchError::Generic.into(), + ..Default::default() + }; assert_eq!(marker.cutoff_phase, ARMarkerInfoCutoffPhase::MatchGeneric); } @@ -2025,9 +2041,11 @@ mod tests { #[test] fn test_history_disabled_skips_merge() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_marker_extraction_mode = crate::types::AR_NOUSE_TRACKING_HISTORY; - handle.ar_pattern_detection_mode = crate::pattern::AR_TEMPLATE_MATCHING_MONO; + let mut handle = ARHandle { + ar_marker_extraction_mode: crate::types::AR_NOUSE_TRACKING_HISTORY, + ar_pattern_detection_mode: crate::pattern::AR_TEMPLATE_MATCHING_MONO, + ..Default::default() + }; // Strong history record. handle.history[0].marker.id = 5; @@ -2065,8 +2083,10 @@ mod tests { #[test] fn test_history_merge_strengthens_weak_marker_single_mode() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_pattern_detection_mode = crate::pattern::AR_TEMPLATE_MATCHING_MONO; + let mut handle = ARHandle { + ar_pattern_detection_mode: crate::pattern::AR_TEMPLATE_MATCHING_MONO, + ..Default::default() + }; // Current marker: weak. handle.marker_info[0].area = 10000; @@ -2109,8 +2129,10 @@ mod tests { #[test] fn test_history_merge_does_nothing_when_current_cf_higher() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_pattern_detection_mode = crate::pattern::AR_TEMPLATE_MATCHING_MONO; + let mut handle = ARHandle { + ar_pattern_detection_mode: crate::pattern::AR_TEMPLATE_MATCHING_MONO, + ..Default::default() + }; handle.marker_info[0].area = 10000; handle.marker_info[0].pos = [100.0, 100.0]; @@ -2141,8 +2163,10 @@ mod tests { #[test] fn test_history_merge_skips_when_area_ratio_out_of_range() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_pattern_detection_mode = crate::pattern::AR_TEMPLATE_MATCHING_MONO; + let mut handle = ARHandle { + ar_pattern_detection_mode: crate::pattern::AR_TEMPLATE_MATCHING_MONO, + ..Default::default() + }; handle.marker_info[0].area = 10000; handle.marker_info[0].pos = [100.0, 100.0]; @@ -2175,9 +2199,11 @@ mod tests { #[test] fn test_history_merge_combined_mode_updates_both_cfs() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_pattern_detection_mode = - crate::types::AR_TEMPLATE_MATCHING_MONO_AND_MATRIX_CODE_DETECTION; + let mut handle = ARHandle { + ar_pattern_detection_mode: + crate::types::AR_TEMPLATE_MATCHING_MONO_AND_MATRIX_CODE_DETECTION, + ..Default::default() + }; handle.marker_info[0].area = 10000; handle.marker_info[0].pos = [100.0, 100.0]; @@ -2271,9 +2297,10 @@ mod tests { #[test] fn test_save_creates_new_record_for_new_id() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - - handle.history_num = 0; + let mut handle = ARHandle { + history_num: 0, + ..Default::default() + }; handle.marker_info[0].id = 42; handle.marker_info[0].area = 7777; @@ -2323,8 +2350,10 @@ mod tests { #[test] fn test_v2_mode_skips_resurrection() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_marker_extraction_mode = crate::types::AR_USE_TRACKING_HISTORY_V2; + let mut handle = ARHandle { + ar_marker_extraction_mode: crate::types::AR_USE_TRACKING_HISTORY_V2, + ..Default::default() + }; handle.history[0].marker.id = 9; handle.history[0].marker.area = 5000; @@ -2347,8 +2376,10 @@ mod tests { #[test] fn test_resurrection_reinserts_missing_history_marker() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_marker_extraction_mode = crate::types::AR_USE_TRACKING_HISTORY; + let mut handle = ARHandle { + ar_marker_extraction_mode: crate::types::AR_USE_TRACKING_HISTORY, + ..Default::default() + }; handle.history[0].marker.id = 9; handle.history[0].marker.area = 5000; @@ -2370,8 +2401,10 @@ mod tests { #[test] fn test_resurrection_does_not_duplicate_existing_marker() { use crate::types::ARHandle; - let mut handle = ARHandle::default(); - handle.ar_marker_extraction_mode = crate::types::AR_USE_TRACKING_HISTORY; + let mut handle = ARHandle { + ar_marker_extraction_mode: crate::types::AR_USE_TRACKING_HISTORY, + ..Default::default() + }; handle.history[0].marker.area = 5000; handle.history[0].marker.pos = [50.0, 50.0]; diff --git a/crates/core/src/ar/param.rs b/crates/core/src/ar/param.rs index 17839fd..23f3f45 100644 --- a/crates/core/src/ar/param.rs +++ b/crates/core/src/ar/param.rs @@ -246,9 +246,11 @@ mod tests { #[test] fn test_ar_param_change_size_doubles_resolution() { - let mut src = ARParam::default(); - src.xsize = 640; - src.ysize = 480; + let mut src = ARParam { + xsize: 640, + ysize: 480, + ..Default::default() + }; // fx, skew=0, cx, tx src.mat[0] = [700.0, 0.0, 320.0, 0.0]; // 0, fy, cy, ty @@ -277,9 +279,11 @@ mod tests { #[test] fn test_ar_param_change_size_halves_resolution() { - let mut src = ARParam::default(); - src.xsize = 1280; - src.ysize = 960; + let mut src = ARParam { + xsize: 1280, + ysize: 960, + ..Default::default() + }; src.mat[0] = [1400.0, 0.0, 640.0, 0.0]; src.mat[1] = [0.0, 1400.0, 480.0, 0.0]; src.mat[2] = [0.0, 0.0, 1.0, 0.0]; @@ -294,9 +298,11 @@ mod tests { #[test] fn test_ar_param_change_size_identity() { - let mut src = ARParam::default(); - src.xsize = 640; - src.ysize = 480; + let mut src = ARParam { + xsize: 640, + ysize: 480, + ..Default::default() + }; src.mat[0] = [700.0, 0.0, 320.0, 0.0]; src.mat[1] = [0.0, 700.0, 240.0, 0.0]; src.mat[2] = [0.0, 0.0, 1.0, 0.0]; diff --git a/crates/core/src/ar/param_gl.rs b/crates/core/src/ar/param_gl.rs index d8588b8..e17c661 100644 --- a/crates/core/src/ar/param_gl.rs +++ b/crates/core/src/ar/param_gl.rs @@ -486,9 +486,11 @@ mod tests { use crate::types::ARParam; fn make_param(xsize: i32, ysize: i32, fx: f64, fy: f64, cx: f64, cy: f64) -> ARParam { - let mut p = ARParam::default(); - p.xsize = xsize; - p.ysize = ysize; + let mut p = ARParam { + xsize, + ysize, + ..Default::default() + }; p.mat[0] = [fx, 0.0, cx, 0.0]; p.mat[1] = [0.0, fy, cy, 0.0]; p.mat[2] = [0.0, 0.0, 1.0, 0.0]; diff --git a/crates/core/src/ar/pattern.rs b/crates/core/src/ar/pattern.rs index f674efa..d502942 100644 --- a/crates/core/src/ar/pattern.rs +++ b/crates/core/src/ar/pattern.rs @@ -1235,8 +1235,10 @@ mod tests { let param_ltf = ARParamLTf::new_basic(xsize_i32, ysize_i32); // Build a simple marker_info with the quad we defined - let mut marker = ARMarkerInfo::default(); - marker.vertex = vertex; + let marker = ARMarkerInfo { + vertex, + ..Default::default() + }; // Save using the image-first signature of ar_patt_save let filename_path = std::path::Path::new(filename); From e1b036c93b284cb37a1eee99ae8243e62161f368 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 17:17:38 +0200 Subject: [PATCH 07/40] chore(ar2): allow too_many_arguments on SIMD get_similarity variants (#180) PR 4 of 5 in the #180 series. Adds `#[allow(clippy::too_many_arguments)]` with rationale on the two SIMD `get_similarity_*` impls in `crates/core/src/ar2/feature_map.rs` (lines 275 and 387), clearing the final two lib lints flagged by `cargo clippy --all-targets --all-features -- -D warnings`. These functions are runtime-dispatched SIMD variants of the scalar `get_similarity`. Their 9-argument signatures are locked: they must match the scalar fallback and each sibling SIMD impl so the dispatcher can swap between them via `is_x86_feature_detected!`. Bundling args into a struct would force every caller to materialize it on a hot path with no design win. The plan (.claude/issue-180-plan.md Decision #3) called for an inline `#[allow]` over a `clippy.toml` threshold raise to keep the exception scoped to these two sites. Library is now fully strict-clean: - `cargo clippy -p webarkitlib-rs --lib --all-features -- -D warnings`: clean - `cargo clippy --workspace -- -D warnings` (existing CI gate): clean Surfaced during verification: clearing the lib unmasked 24 pre-existing lints in examples/tests/benches that had been hidden because cargo halts on lib errors before checking those targets. They are out of scope for PR 4 and will be addressed in a follow-up cleanup before PR 5 tightens CI to `--all-targets --all-features`. --- crates/core/src/ar2/feature_map.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/core/src/ar2/feature_map.rs b/crates/core/src/ar2/feature_map.rs index 4879caa..06c6686 100644 --- a/crates/core/src/ar2/feature_map.rs +++ b/crates/core/src/ar2/feature_map.rs @@ -272,6 +272,11 @@ fn get_similarity_scalar( /// after `is_x86_feature_detected!`. #[cfg(all(feature = "simd-x86-sse41", target_arch = "x86_64"))] #[target_feature(enable = "sse4.1")] +// rationale: SIMD variant of get_similarity; signature locked to match +// the scalar fallback and sibling SIMD impl for runtime dispatch via +// is_x86_feature_detected! — bundling args into a struct would force +// every caller to materialize it on a hot path with no design win. +#[allow(clippy::too_many_arguments)] unsafe fn get_similarity_sse41( image: &[u8], xsize: i32, @@ -384,6 +389,11 @@ unsafe fn get_similarity_sse41( /// [`get_similarity`] after `is_x86_feature_detected!`. #[cfg(all(feature = "simd-x86-avx2", target_arch = "x86_64"))] #[target_feature(enable = "avx2,fma")] +// rationale: SIMD variant of get_similarity; signature locked to match +// the scalar fallback and sibling SIMD impl for runtime dispatch via +// is_x86_feature_detected! — bundling args into a struct would force +// every caller to materialize it on a hot path with no design win. +#[allow(clippy::too_many_arguments)] unsafe fn get_similarity_avx2( image: &[u8], xsize: i32, From b14df4cab77929e69518ed496ad802621d0bee71 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 20:38:02 +0200 Subject: [PATCH 08/40] chore: clear strict clippy lints in examples/tests/benches (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 4.5 of the #180 series. Clears all clippy lints in non-library targets that were previously masked by lib compilation halting cargo on errors. After PR 4 made the library strict-clean, these surfaced under `cargo clippy --all-targets --all-features -- -D warnings`. Workspace is now fully strict-clean — PR 5 can safely flip CI to the `--all-targets --all-features -- -D warnings` invocation. ## Categories cleared | Category | Count | Files | |---|---:|---| | `excessive_precision` (file-scoped allow + rationale) | 26 | tests/kpm_regression.rs | | `field_reassign_with_default` | 10 | examples/{barcode,dump_patt,generate_patt,simple}.rs + benches/marker_bench.rs | | `unnecessary_cast` (usize→usize) | 7 | examples/generate_patt.rs | | `or_fun_call` (call inside expect) | 4 | examples/{debug_labeling,simple}.rs | | `needless_range_loop` | 5 | examples/{dump_patt,simple_nft,simple_nft_dual}.rs | | `ptr_arg` (&Vec → &[_]) | 3 | examples/generate_patt.rs | | `redundant_closure` | 3 | examples/generate_patt.rs | | `unnecessary_lazy_evaluations` | 1 | examples/simple_nft_dual.rs (.then → .then_some) | | `manual_is_multiple_of` | 1 | examples/generate_patt.rs | | `manual_range_contains` | 1 | tests/nft_pipeline.rs | | `let_unit_value` | 1 | benches/marker_bench.rs (let _ = .unwrap()) | | `redundant_pattern_matching` | 1 | examples/barcode.rs (if let Ok(_) → .is_ok()) | | `type_complexity` | 1 | examples/generate_patt.rs (RunReport type alias) | ## Notes - **tests/kpm_regression.rs**: file-scoped `#![allow(clippy::excessive_precision)]` with rationale rather than truncating each of 26 baseline floats. The literals were captured verbatim from C++ `kpm_dump_fixtures`; truncating would round-trip to the same f32 but lose source-traceability to the baseline. - **examples**: `ARHandle`/`ARParamLTf`/`ARPattHandle` struct-init conversions follow the PR 3 pattern. Method-call mutations (`set_pixel_format`, etc.) stay outside the initializer. - **examples/simple_nft\*.rs**: `for r in 0..3 { cam_pose[r][i] }` → `for row in cam_pose { row[i] }`. - **examples/generate_patt.rs**: introduced `type RunReport = (...)` alias to clear `type_complexity` on `run_once` signature. ## Verified - cargo fmt --all -- --check: clean. - cargo clippy --all-targets --all-features -- -D warnings: No issues found. - cargo clippy --workspace -- -D warnings (existing CI gate): clean. - cargo clippy -p webarkitlib-rs -- -D warnings (pure-Rust gate): clean. - cargo test --all-features: 471 passed, 8 ignored. - Regression smoke tests on marker pipelines: - examples/simple: Hiro template match, CF=0.8925, ICP=0.0841 (identical to PR 3). - examples/barcode: matrix code ID=5, CF=1.0 (identical to PR 3). --- crates/core/benches/marker_bench.rs | 14 +++--- crates/core/examples/barcode.rs | 26 +++++----- crates/core/examples/debug_labeling.rs | 2 +- crates/core/examples/dump_patt.rs | 53 +++++++++++--------- crates/core/examples/generate_patt.rs | 54 ++++++++++---------- crates/core/examples/simple.rs | 66 +++++++++++++------------ crates/core/examples/simple_nft.rs | 20 ++++---- crates/core/examples/simple_nft_dual.rs | 22 ++++----- crates/core/tests/kpm_regression.rs | 7 +++ crates/core/tests/nft_pipeline.rs | 2 +- 10 files changed, 145 insertions(+), 121 deletions(-) diff --git a/crates/core/benches/marker_bench.rs b/crates/core/benches/marker_bench.rs index 133f63d..4c0f07c 100644 --- a/crates/core/benches/marker_bench.rs +++ b/crates/core/benches/marker_bench.rs @@ -71,12 +71,14 @@ fn marker_detection_benchmark(c: &mut Criterion) { param_ltf, }); - let mut ar_handle = ARHandle::default(); - ar_handle.xsize = width; - ar_handle.ysize = height; + let mut ar_handle = ARHandle { + xsize: width, + ysize: height, + ar_labeling_thresh: 100, // Standard threshold + ar_param_lt: &mut *param_lt, + ..Default::default() + }; ar_handle.set_pixel_format(ARPixelFormat::MONO); - ar_handle.ar_labeling_thresh = 100; // Standard threshold - ar_handle.ar_param_lt = &mut *param_lt; let mut patt_handle = webarkitlib_rs::types::ARPattHandle::new(16, 50); ar_patt_load(&mut patt_handle, patt_path).expect("Failed to load pattern"); @@ -97,7 +99,7 @@ fn marker_detection_benchmark(c: &mut Criterion) { c.bench_function("ar_full_pipeline_429x317", |b| { b.iter(|| { - let _ = ar_detect_marker(black_box(&mut ar_handle), black_box(&frame)).unwrap(); + ar_detect_marker(black_box(&mut ar_handle), black_box(&frame)).unwrap(); if ar_handle.marker_num > 0 { let marker_info = &ar_handle.marker_info[0]; let mut trans = [[0.0; 4]; 3]; diff --git a/crates/core/examples/barcode.rs b/crates/core/examples/barcode.rs index ae7733e..8f1f1a9 100644 --- a/crates/core/examples/barcode.rs +++ b/crates/core/examples/barcode.rs @@ -132,20 +132,24 @@ fn main() -> Result<(), Box> { for thresh in thresholds { arlog_i!("--- Testing BlackRegion, Threshold: {} ---", thresh); - let mut ar_handle = ARHandle::default(); - ar_handle.xsize = width; - ar_handle.ysize = height; - ar_handle.ar_debug = 1; + let mut ar_handle = ARHandle { + xsize: width, + ysize: height, + ar_debug: 1, + ar_labeling_thresh: thresh, + ar_labeling_mode: 0, // BlackRegion + ..Default::default() + }; ar_handle.set_pixel_format(ARPixelFormat::RGB); - ar_handle.ar_labeling_thresh = thresh; - ar_handle.ar_labeling_mode = 0; // BlackRegion ar_handle.set_pattern_detection_mode(AR_MATRIX_CODE_DETECTION); ar_handle.set_matrix_code_type(code_type); - let mut param_ltf = ARParamLTf::default(); - param_ltf.xsize = width; - param_ltf.ysize = height; - param_ltf.o2i = vec![0.0; (width * height * 2) as usize]; + let mut param_ltf = ARParamLTf { + xsize: width, + ysize: height, + o2i: vec![0.0; (width * height * 2) as usize], + ..Default::default() + }; for y in 0..height { for x in 0..width { let idx = ((y * width + x) * 2) as usize; @@ -168,7 +172,7 @@ fn main() -> Result<(), Box> { time: AR2VideoTimestampT { sec: 1, usec: 0 }, }; - if let Ok(_) = ar_detect_marker(&mut ar_handle, &frame) { + if ar_detect_marker(&mut ar_handle, &frame).is_ok() { arlog_i!("Found {} square candidates.", ar_handle.marker2_num); arlog_i!("Found {} valid barcode markers.", ar_handle.marker_num); let mut found_valid = false; diff --git a/crates/core/examples/debug_labeling.rs b/crates/core/examples/debug_labeling.rs index 9dbd343..6de1d75 100644 --- a/crates/core/examples/debug_labeling.rs +++ b/crates/core/examples/debug_labeling.rs @@ -32,7 +32,7 @@ fn main() -> Result<(), Box> { arlog_i!("Loading image {}...", final_image_path.display()); let full_img = ImageReader::open(&final_image_path) - .expect(&format!("Failed to open {}", final_image_path.display())) + .unwrap_or_else(|_| panic!("Failed to open {}", final_image_path.display())) .decode() .expect("Failed to decode image"); let luma_img = full_img.to_luma8(); diff --git a/crates/core/examples/dump_patt.rs b/crates/core/examples/dump_patt.rs index 1cf74d2..71bc971 100644 --- a/crates/core/examples/dump_patt.rs +++ b/crates/core/examples/dump_patt.rs @@ -112,10 +112,12 @@ fn main() { arlog_i!("Otsu threshold: {}", otsu_thresh); // Identity LT (skip lens distortion correction for the diagnostic). - let mut param_ltf = ARParamLTf::default(); - param_ltf.xsize = width; - param_ltf.ysize = height; - param_ltf.o2i = vec![0.0; (width * height * 2) as usize]; + let mut param_ltf = ARParamLTf { + xsize: width, + ysize: height, + o2i: vec![0.0; (width * height * 2) as usize], + ..Default::default() + }; for y in 0..height { for x in 0..width { let idx = ((y * width + x) * 2) as usize; @@ -125,26 +127,30 @@ fn main() { } let mut param_lt = Box::new(ARParamLT { param, param_ltf }); - let mut ar_handle = ARHandle::default(); - ar_handle.xsize = width; - ar_handle.ysize = height; + let mut ar_handle = ARHandle { + xsize: width, + ysize: height, + ar_image_proc_mode: 0, // FrameImage + ar_pattern_detection_mode: AR_TEMPLATE_MATCHING_COLOR, + ar_labeling_mode: 0, + ar_labeling_thresh: otsu_thresh as i32, + patt_ratio: 0.5, + matrix_code_type: ARMatrixCodeType::Code3x3, + ar_param_lt: &mut *param_lt, + ..Default::default() + }; ar_handle.set_pixel_format(ARPixelFormat::MONO); - ar_handle.ar_image_proc_mode = 0; // FrameImage - ar_handle.ar_pattern_detection_mode = AR_TEMPLATE_MATCHING_COLOR; - ar_handle.ar_labeling_mode = 0; - ar_handle.ar_labeling_thresh = otsu_thresh as i32; - ar_handle.patt_ratio = 0.5; - ar_handle.matrix_code_type = ARMatrixCodeType::Code3x3; - ar_handle.ar_param_lt = &mut *param_lt; - let mut patt_handle = webarkitlib_rs::types::ARPattHandle::default(); - patt_handle.patt_num_max = 50; - patt_handle.patt_size = 16; - patt_handle.pattf = vec![0; 50]; - patt_handle.patt = vec![vec![0; 16 * 16 * 3 * 4]; 50]; - patt_handle.pattpow = vec![0.0; 50 * 4]; - patt_handle.patt_bw = vec![vec![0; 16 * 16 * 4]; 50]; - patt_handle.pattpow_bw = vec![0.0; 50 * 4]; + let mut patt_handle = webarkitlib_rs::types::ARPattHandle { + patt_num_max: 50, + patt_size: 16, + pattf: vec![0; 50], + patt: vec![vec![0; 16 * 16 * 3 * 4]; 50], + pattpow: vec![0.0; 50 * 4], + patt_bw: vec![vec![0; 16 * 16 * 4]; 50], + pattpow_bw: vec![0.0; 50 * 4], + ..Default::default() + }; arlog_i!("Loading hiro pattern from {}", patt_path); if let Err(e) = ar_patt_load(&mut patt_handle, &patt_path) { @@ -244,8 +250,7 @@ fn main() { "selected = {} (of {} markers)\n", sel, ar_handle.marker_num )); - for i in 0..4 { - let idx = vertex_indices[i]; + for (i, &idx) in vertex_indices.iter().enumerate() { meta.push_str(&format!( "vertex[{}] = ({}, {})\n", i, info2.x_coord[idx], info2.y_coord[idx] diff --git a/crates/core/examples/generate_patt.rs b/crates/core/examples/generate_patt.rs index a107993..93d02be 100644 --- a/crates/core/examples/generate_patt.rs +++ b/crates/core/examples/generate_patt.rs @@ -128,10 +128,11 @@ fn default_paths() -> (String, String) { ) } -fn run_once( - cfg: &Config, - suffix: &str, -) -> Result<(usize, u8, u8, f64, Option, Option, Option), String> { +/// Per-run report: `(num_detected, otsu_thresh, otsu_thresh_inv, +/// mean_abs_diff, mean_abs_diff_inv, vert_flip_idx, vert_flip_used)`. +type RunReport = (usize, u8, u8, f64, Option, Option, Option); + +fn run_once(cfg: &Config, suffix: &str) -> Result { // Load image let image = ImageReader::open(&cfg.input) .map_err(|e| format!("open image: {}", e))? @@ -339,13 +340,15 @@ fn run_once( ); } - let mut marker = ARMarkerInfo::default(); - marker.vertex = [ - [minx as f64, miny as f64], - [maxx as f64, miny as f64], - [maxx as f64, maxy as f64], - [minx as f64, maxy as f64], - ]; + let marker = ARMarkerInfo { + vertex: [ + [minx as f64, miny as f64], + [maxx as f64, miny as f64], + [maxx as f64, maxy as f64], + [minx as f64, maxy as f64], + ], + ..Default::default() + }; if cfg.debug { arlog_i!("Debug: marker vertices: {:?}", marker.vertex); } @@ -373,15 +376,15 @@ fn run_once( let _ = overlay_img.save(&overlay_path); // extract pattern image - let mut ext_patt = vec![0u8; (cfg.patt_size * cfg.patt_size * 3) as usize]; + let mut ext_patt = vec![0u8; cfg.patt_size * cfg.patt_size * 3]; if cfg.debug { arlog_i!("Debug: calling ar_patt_get_image2 with patt_size={} sample_size={} xsize={} ysize={} pixel_format={:?}", cfg.patt_size, cfg.patt_size * cfg.sample_factor, xsize, ysize, pixel_format); } if let Err(e) = ar_patt_get_image2( 0, ARPixelFormat::RGB as i32, - cfg.patt_size as usize, - (cfg.patt_size * cfg.sample_factor) as usize, + cfg.patt_size, + cfg.patt_size * cfg.sample_factor, &img, xsize, ysize, @@ -402,9 +405,9 @@ fn run_once( // save extracted PNG let mut out_img = image::RgbImage::new(cfg.patt_size as u32, cfg.patt_size as u32); - for y in 0..cfg.patt_size as usize { - for x in 0..cfg.patt_size as usize { - let idx = (y * cfg.patt_size as usize + x) * 3; + for y in 0..cfg.patt_size { + for x in 0..cfg.patt_size { + let idx = (y * cfg.patt_size + x) * 3; let b = ext_patt[idx]; let g = ext_patt[idx + 1]; let r = ext_patt[idx + 2]; @@ -433,7 +436,7 @@ fn run_once( 0, &marker, patt_ratio, - cfg.patt_size as usize, + cfg.patt_size, &patt_path, ) .map_err(|e| format!("ar_patt_save failed: {}", e))?; @@ -530,7 +533,7 @@ fn parse_patt_blocks(content: &str, patt_size: usize) -> Result>, St if tokens.len() == block_len { return Ok(vec![tokens]); } - if tokens.len() % block_len != 0 { + if !tokens.len().is_multiple_of(block_len) { return Err(format!( "unexpected token count {} (not multiple of block_len {})", tokens.len(), @@ -541,7 +544,7 @@ fn parse_patt_blocks(content: &str, patt_size: usize) -> Result>, St Ok(blocks) } -fn mean_abs_diff_u8(a: &Vec, b: &Vec) -> f64 { +fn mean_abs_diff_u8(a: &[u8], b: &[u8]) -> f64 { if a.len() != b.len() { return f64::INFINITY; } @@ -552,7 +555,7 @@ fn mean_abs_diff_u8(a: &Vec, b: &Vec) -> f64 { s / (a.len() as f64) } -fn flip_vert_u8(buf: &Vec, patt_size: usize) -> Vec { +fn flip_vert_u8(buf: &[u8], patt_size: usize) -> Vec { let mut out = vec![0u8; buf.len()]; let row_bytes = patt_size * 3; for y in 0..patt_size { @@ -670,10 +673,9 @@ fn main() { let patt = format!("generated{}.patt", suffix); let ref_mean_str: String = ref_mean .map(|v| format!("{:.2}", v)) - .unwrap_or_else(|| String::new()); - let ref_idx_str: String = ref_idx - .map(|i| i.to_string()) - .unwrap_or_else(|| String::new()); + .unwrap_or_else(String::new); + let ref_idx_str: String = + ref_idx.map(|i| i.to_string()).unwrap_or_else(String::new); let ref_flip_str: String = ref_flip .map(|b| { if b { @@ -682,7 +684,7 @@ fn main() { String::from("0") } }) - .unwrap_or_else(|| String::new()); + .unwrap_or_else(String::new); writeln!( csv, "{},{},{},{},{},{},{},{},{:.2},{},{},{},{},{}", diff --git a/crates/core/examples/simple.rs b/crates/core/examples/simple.rs index 1d65d0d..c3e01cf 100644 --- a/crates/core/examples/simple.rs +++ b/crates/core/examples/simple.rs @@ -104,16 +104,14 @@ fn main() { // Load ARParam arlog_i!("Loading camera parameters from {}...", cparam_path); - let param_file = File::open(cparam_path).expect(&format!( - "Failed to open camera parameters: {}", - cparam_path - )); + let param_file = File::open(cparam_path) + .unwrap_or_else(|_| panic!("Failed to open camera parameters: {}", cparam_path)); let param = ARParam::load(param_file).expect("Failed to read camera parameters"); // Load image arlog_i!("Loading image {}...", img_path); let img = ImageReader::open(img_path) - .expect(&format!("Failed to open image: {}", img_path)) + .unwrap_or_else(|_| panic!("Failed to open image: {}", img_path)) .decode() .expect("Failed to decode image"); let width = img.width() as i32; @@ -128,7 +126,7 @@ fn main() { use std::io::Write; arlog_i!("Exporting {} for C benchmark...", hiro_raw_path.display()); let mut f = File::create(&hiro_raw_path) - .expect(&format!("Failed to create {}", hiro_raw_path.display())); + .unwrap_or_else(|_| panic!("Failed to create {}", hiro_raw_path.display())); f.write_all(luma_img.as_raw()) .expect("Failed to write luma raw data"); } @@ -157,12 +155,14 @@ fn main() { arlog_i!("Saved thresholded image to {}", thresh_path.display()); // We mock an identity lookup table for the image size to avoid distortion failure - let mut param_ltf = ARParamLTf::default(); - param_ltf.xsize = width; - param_ltf.ysize = height; - param_ltf.x_off = 0; - param_ltf.y_off = 0; - param_ltf.o2i = vec![0.0; (width * height * 2) as usize]; + let mut param_ltf = ARParamLTf { + xsize: width, + ysize: height, + x_off: 0, + y_off: 0, + o2i: vec![0.0; (width * height * 2) as usize], + ..Default::default() + }; for y in 0..height { for x in 0..width { let idx = ((y * width + x) * 2) as usize; @@ -180,28 +180,32 @@ fn main() { let mut ar3d_handle_ptr = ar_3d_create_handle(¶m).expect("Failed to create AR3DHandle"); // Initialize the main tracking handle - let mut ar_handle = ARHandle::default(); - ar_handle.ar_debug = 1; - ar_handle.xsize = width; - ar_handle.ysize = height; + let mut ar_handle = ARHandle { + ar_debug: 1, + xsize: width, + ysize: height, + ar_image_proc_mode: 0, // FrameImage + ar_pattern_detection_mode: 0, // Template matching color/mono + ar_labeling_mode: 0, // Black region + ar_labeling_thresh: otsu_thresh as i32, + patt_ratio: 0.5, + matrix_code_type: ARMatrixCodeType::Code3x3, + ar_param_lt: &mut *param_lt, + ..Default::default() + }; ar_handle.set_pixel_format(ARPixelFormat::MONO); // raw luma input - ar_handle.ar_image_proc_mode = 0; // FrameImage - ar_handle.ar_pattern_detection_mode = 0; // Template matching color/mono - ar_handle.ar_labeling_mode = 0; // Black region - ar_handle.ar_labeling_thresh = otsu_thresh as i32; - ar_handle.patt_ratio = 0.5; - ar_handle.matrix_code_type = ARMatrixCodeType::Code3x3; - ar_handle.ar_param_lt = &mut *param_lt; // Allocate pattern handle and load real pattern - let mut patt_handle = webarkitlib_rs::types::ARPattHandle::default(); - patt_handle.patt_num_max = 50; - patt_handle.patt_size = 16; - patt_handle.pattf = vec![0; 50]; - patt_handle.patt = vec![vec![0; 16 * 16 * 3 * 4]; 50]; - patt_handle.pattpow = vec![0.0; 50 * 4]; - patt_handle.patt_bw = vec![vec![0; 16 * 16 * 4]; 50]; - patt_handle.pattpow_bw = vec![0.0; 50 * 4]; + let mut patt_handle = webarkitlib_rs::types::ARPattHandle { + patt_num_max: 50, + patt_size: 16, + pattf: vec![0; 50], + patt: vec![vec![0; 16 * 16 * 3 * 4]; 50], + pattpow: vec![0.0; 50 * 4], + patt_bw: vec![vec![0; 16 * 16 * 4]; 50], + pattpow_bw: vec![0.0; 50 * 4], + ..Default::default() + }; arlog_i!("Loading hiro pattern from {}...", patt_path); match ar_patt_load(&mut patt_handle, patt_path) { diff --git a/crates/core/examples/simple_nft.rs b/crates/core/examples/simple_nft.rs index a2189d2..bd2645d 100644 --- a/crates/core/examples/simple_nft.rs +++ b/crates/core/examples/simple_nft.rs @@ -224,13 +224,13 @@ fn main() { Some((cam_pose, page_no, error)) => { arlog_i!(" ✓ KPM match found! Page={}, error={:.4}", page_no, error); arlog_i!(" Initial 3×4 pose matrix:"); - for r in 0..3 { + for row in cam_pose { arlog_i!( " [{:>10.4} {:>10.4} {:>10.4} {:>10.4}]", - cam_pose[r][0], - cam_pose[r][1], - cam_pose[r][2], - cam_pose[r][3] + row[0], + row[1], + row[2], + row[3] ); } @@ -273,13 +273,13 @@ fn main() { Ok(()) => { arlog_i!(" ✓ AR2 tracking succeeded! Error={:.4}", tracking_err); arlog_i!(" Refined 3×4 pose matrix:"); - for r in 0..3 { + for row in &refined_pose { arlog_i!( " [{:>10.4} {:>10.4} {:>10.4} {:>10.4}]", - refined_pose[r][0], - refined_pose[r][1], - refined_pose[r][2], - refined_pose[r][3] + row[0], + row[1], + row[2], + row[3] ); } } diff --git a/crates/core/examples/simple_nft_dual.rs b/crates/core/examples/simple_nft_dual.rs index 7fee0db..e19a136 100644 --- a/crates/core/examples/simple_nft_dual.rs +++ b/crates/core/examples/simple_nft_dual.rs @@ -462,7 +462,7 @@ fn main() { // tier-2 check exactly. let matched = dual_result.matched_id; match (matched >= 0) - .then(|| matched as usize) + .then_some(matched as usize) .and_then(|i| ref_dims.get(i)) { Some(&(ref_w, ref_h)) => { @@ -607,13 +607,13 @@ fn main() { arlog_i!(" KPM match: page={}, error={:.4}", page_no, error); arlog_i!(" Initial 3x4 pose matrix:"); - for r in 0..3 { + for row in cam_pose { arlog_i!( " [{:>10.4} {:>10.4} {:>10.4} {:>10.4}]", - cam_pose[r][0], - cam_pose[r][1], - cam_pose[r][2], - cam_pose[r][3] + row[0], + row[1], + row[2], + row[3] ); } @@ -646,13 +646,13 @@ fn main() { Ok(()) => { arlog_i!(" AR2 tracking succeeded! Error={:.4}", tracking_err); arlog_i!(" Refined 3x4 pose matrix:"); - for r in 0..3 { + for row in &refined_pose { arlog_i!( " [{:>10.4} {:>10.4} {:>10.4} {:>10.4}]", - refined_pose[r][0], - refined_pose[r][1], - refined_pose[r][2], - refined_pose[r][3] + row[0], + row[1], + row[2], + row[3] ); } } diff --git a/crates/core/tests/kpm_regression.rs b/crates/core/tests/kpm_regression.rs index e8ec598..36db519 100644 --- a/crates/core/tests/kpm_regression.rs +++ b/crates/core/tests/kpm_regression.rs @@ -52,6 +52,13 @@ //! | `test_luminance_conversion` | JPEG→luma conversion consistency | none | #![allow(dead_code)] +// rationale: the SCREEN/WORLD/pose constants below were generated by the +// C++ kpm_dump_fixtures tool. The extra decimal digits document the +// captured C++ output verbatim; truncating to f32-exact representations +// would round-trip to the same bit pattern but lose source-traceability +// to the C++ baseline. Tracked under #180 — file-scoped allow rather +// than per-literal truncation. +#![allow(clippy::excessive_precision)] use std::path::Path; diff --git a/crates/core/tests/nft_pipeline.rs b/crates/core/tests/nft_pipeline.rs index 65699b4..f2503ca 100644 --- a/crates/core/tests/nft_pipeline.rs +++ b/crates/core/tests/nft_pipeline.rs @@ -196,7 +196,7 @@ fn test_compare_with_c_generated_pinball_marker() { // Allow ±10%: float rounding differs across compilers and platforms. let ratio = rust_total as f64 / c_total as f64; assert!( - ratio >= 0.90 && ratio <= 1.10, + (0.90..=1.10).contains(&ratio), "feature count diverges: Rust {} vs C {} ({:.1}% — expected within ±10%)", rust_total, c_total, From 09e5904ddea5d669caf710e2b088b383b0d979fe Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 20:51:28 +0200 Subject: [PATCH 09/40] chore(codecov): exclude examples/benches from coverage targets (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The codecov/patch gate fails on PRs touching examples/ or benches/ because cargo-llvm-cov (and tarpaulin) don't instrument example binaries or bench harnesses by default. When a cleanup PR touches those paths (#180 PR 4.5: 137 lines in examples/, 8 in benches/, 8 in tests/), the diff shows ~0% coverage and the patch threshold trips even though the library and integration tests are unaffected. Add a project codecov.yml that: - Ignores crates/core/examples/**, crates/core/benches/**, crates/wasm/**, and benchmarks/**. These are demonstration, perf-measurement, or build-system targets — not the library under test. - Sets project + patch targets to `auto` with a 1% slack threshold, so genuine regressions in src/ or tests/ still fail the gate but PRs don't fail on float-noise coverage swings. The library (crates/core/src/) and integration tests (crates/core/tests/) remain instrumented. A real regression there will still fail codecov, which is what we want. --- codecov.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..ed0e892 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,30 @@ +# Codecov configuration for WebARKitLib-rs. +# +# Coverage scope: the pure-Rust library (`crates/core/src/`) and the +# integration tests under `crates/core/tests/`. Examples and benches +# are not instrumented for line coverage — they exist to demonstrate +# usage and measure performance respectively, not to be exercised by +# the coverage runner. Including them in the diff target produces +# spurious "0% of diff hit" failures on PRs that touch them. +# +# Surfaced during #180 PR 4.5: a cleanup PR touching examples/ and +# benches/ failed `codecov/patch` despite the lib + tests being fully +# strict-clean. The exclusions below are scoped narrowly so any real +# regression in src/ or tests/ still fails the gate. + +coverage: + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: auto + threshold: 1% + +ignore: + - "crates/core/examples/**" + - "crates/core/benches/**" + - "crates/wasm/**" + - "benchmarks/**" From 53aeb4ae3ca589656e617413a03ad342a617b843 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 21:05:04 +0200 Subject: [PATCH 10/40] ci(coverage): fix tarpaulin tests/ glob and mirror in codecov (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coordinated fixes for the codecov/patch failure on PRs that touch files the coverage runner doesn't instrument: 1. .github/workflows/coverage.yml: the existing `--exclude-files "tests/*"` glob is workspace-root relative and never matched our tests at crates/core/tests/*.rs. tarpaulin was silently instrumenting them, then reporting 0% on lines that the coverage workflow's default-features test run never executes (e.g. tests gated on --features ffi-backend). Change to `**/tests/*` so the exclusion actually fires. 2. codecov.yml: mirror tarpaulin's exclusion by adding crates/core/tests/** to the ignore list. Without this, codecov still sees the tests/ paths in the diff, finds no coverage data, and counts every added line as missed in the patch report. (Codecov treats "file in diff with no report data" as 0% covered unless the file is on the ignore list.) Also revert the patch threshold-100% workaround from the previous commit — with the two fixes above, the patch gate stays meaningful (target: auto, threshold: 1%) and only counts real src/ changes. Surfaced during #180 PR 4.5. --- .github/workflows/coverage.yml | 2 +- codecov.yml | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ad750a0..f4436a7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -32,7 +32,7 @@ jobs: --out Xml \ --output-dir coverage \ --timeout 300 \ - --exclude-files "tests/*" \ + --exclude-files "**/tests/*" \ --ignore-panics - name: Upload coverage reports to Codecov diff --git a/codecov.yml b/codecov.yml index ed0e892..9150d44 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,16 +1,18 @@ # Codecov configuration for WebARKitLib-rs. # -# Coverage scope: the pure-Rust library (`crates/core/src/`) and the -# integration tests under `crates/core/tests/`. Examples and benches -# are not instrumented for line coverage — they exist to demonstrate -# usage and measure performance respectively, not to be exercised by -# the coverage runner. Including them in the diff target produces -# spurious "0% of diff hit" failures on PRs that touch them. +# Coverage scope: the pure-Rust library (`crates/core/src/`). Examples, +# benches, and integration tests are NOT instrumented by the coverage +# runner (tarpaulin uses `--exclude-files "**/tests/*"` in +# .github/workflows/coverage.yml; examples/benches are excluded by +# convention — they exist to demonstrate usage and measure performance, +# not to be exercised for line coverage). # -# Surfaced during #180 PR 4.5: a cleanup PR touching examples/ and -# benches/ failed `codecov/patch` despite the lib + tests being fully -# strict-clean. The exclusions below are scoped narrowly so any real -# regression in src/ or tests/ still fails the gate. +# The `ignore` list below mirrors tarpaulin's exclusions on the codecov +# side so PRs that happen to land entirely in uninstrumented code don't +# fail `codecov/patch` at 0% diff hit. +# +# Surfaced during #180 PR 4.5: a cleanup PR touching examples/, benches/, +# and tests/ failed codecov/patch even though the library was unchanged. coverage: status: @@ -26,5 +28,6 @@ coverage: ignore: - "crates/core/examples/**" - "crates/core/benches/**" + - "crates/core/tests/**" - "crates/wasm/**" - "benchmarks/**" From a5e185a306a07ed133895af11098b00d1b348858 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 23:25:59 +0200 Subject: [PATCH 11/40] ci: tighten clippy gate to --all-targets --all-features (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 5 of 5 — finale of the #180 series. Locks in everything PRs 1-4.5 cleaned up by flipping the `build-and-test` job's clippy step from `cargo clippy --workspace -- -D warnings` to the strict variant: cargo clippy --workspace --all-targets --all-features -- -D warnings What this catches that the old gate didn't: - Lints in examples/, tests/, and benches/ (--all-targets). - Lints in ffi-backend, dual-mode, and simd-* code paths (--all-features) — the original ~70-lint gap on dev that #180 was opened to close. The `pure-rust-build` job keeps its current `cargo clippy -p webarkitlib-rs -- -D warnings` invocation deliberately. That job exists to prove the no-features build stays clean (no accidental dependency on ffi-backend / dual-mode leaks into the pure-Rust path) — a different invariant from the strict gate, so it shouldn't be conflated. Also updates .claude/issue-180-plan.md to document PR 4.5 (the unplanned cleanup PR that cleared 26 examples/tests/benches lints unmasked once the lib went strict-clean in PR 4). Series totals (after this lands): 60 lib lints + 26 non-lib lints cleared across 5 PRs, plus a coverage workflow bug fix unrelated to clippy. CLAUDE.md §5's strict command is now enforced by CI. --- .claude/issue-180-plan.md | 27 ++++++++++++++++++++++++++- .github/workflows/ci.yml | 5 ++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.claude/issue-180-plan.md b/.claude/issue-180-plan.md index 154e686..3581a5e 100644 --- a/.claude/issue-180-plan.md +++ b/.claude/issue-180-plan.md @@ -122,10 +122,35 @@ cargo test --all-features --- +### PR 4.5 — Examples/tests/benches cleanup (unplanned, discovered during PR 4) + +**Branch**: `chore/clippy-180-pr4-5-examples-tests-benches` + +**Surfaced**: PR 4 cleared the final two **library** lints. Cargo had been halting on lib errors before checking `--all-targets`, so 26 examples/tests/benches lints were hidden until the lib went strict-clean. PR 5 couldn't tighten CI safely until these were also cleared. + +**Categories**: +- 26 `excessive_precision` in `tests/kpm_regression.rs` — handled via file-scoped `#![allow(clippy::excessive_precision)]` with rationale (C++ baseline fixtures, extra digits document source-traceability). +- 9 `field_reassign_with_default` in examples + benches — same struct-init conversion as PR 3. +- 7 `unnecessary_cast usize→usize` in `generate_patt.rs` — drop redundant `as usize`. +- 3 `or_fun_call inside expect` — `unwrap_or_else(|_| panic!(...))`. +- 3 `ptr_arg &Vec` → `&[u8]`. +- 3 `redundant_closure` (`|| String::new()` → `String::new`). +- 5 `needless_range_loop` — iterator forms. +- 5 singletons (`type_complexity` via `type RunReport = ...`, `redundant_pattern_matching` (`if let Ok(_)` → `.is_ok()`), `manual_range_contains`, `manual_is_multiple_of`, `let_unit_value`). + +**Coverage workflow tangent**: PR 4.5 also fixed an unrelated bug in `.github/workflows/coverage.yml` — tarpaulin's `--exclude-files "tests/*"` glob was workspace-root-relative and never matched our nested `crates/core/tests/`. Fixed to `**/tests/*`, and added a project `codecov.yml` mirroring the exclusions so the patch gate doesn't fail on PRs that land entirely in uninstrumented paths. + +**Commits**: +- `chore: clear strict clippy lints in examples/tests/benches (#180)` +- `chore(codecov): exclude examples/benches from coverage targets (#180)` +- `ci(coverage): fix tarpaulin tests/ glob and mirror in codecov (#180)` + +--- + ### PR 5 — Tighten CI **Branch**: `chore/clippy-180-pr5-ci-tighten` -**Pre-req**: PRs 1–4 merged; `cargo clippy --all-targets --all-features -- -D warnings` clean on `dev`. +**Pre-req**: PRs 1–4.5 merged; `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean on `dev`. **Scope**: `.github/workflows/ci.yml` - `build-and-test` job: replace `cargo clippy --workspace -- -D warnings` with `cargo clippy --workspace --all-targets --all-features -- -D warnings`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7ed67d..c361227 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,10 @@ jobs: run: cargo fmt --all -- --check - name: Run cargo clippy - run: cargo clippy --workspace -- -D warnings + # Strict gate (#180): --all-targets covers examples/tests/benches; + # --all-features exercises ffi-backend, dual-mode, simd-* paths + # that the default-features check skipped. + run: cargo clippy --workspace --all-targets --all-features -- -D warnings - name: Run cargo check run: cargo check --workspace From 2cf9467f3ec34a7c6d024a71ae58f08e4d7142aa Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 11 Jun 2026 23:38:18 +0200 Subject: [PATCH 12/40] ci: relocate strict clippy gate to kpm-build (ubuntu) job (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First push of PR 5 added the strict gate to `build-and-test`, which failed because that job's checkout deliberately omits git submodules (it's the pure-Rust path verification). `--all-features` enables `ffi-backend`, which makes `build.rs` panic looking for the WebARKitLib C++ sources at crates/core/third_party/WebARKitLib/lib/SRC/KPM/FreakMatcher. Move the strict clippy step to `kpm-build (ubuntu-latest)` where the prerequisites (submodules + libclang-dev) already exist for the ffi-backend build / dual-mode tests. The step is Linux-only since lints are OS-agnostic — running it once is sufficient. `build-and-test` keeps its `cargo clippy --workspace -- -D warnings` step. The three clippy invocations in CI now correspond to three distinct invariants: - build-and-test (no submodules, default features, all crates) — proves the workspace stays clean against the lints any contributor sees by default. - pure-rust-build (single-crate, no features) — proves the pure-Rust path never accidentally depends on ffi-backend. - kpm-build ubuntu (submodules, --all-targets --all-features) — proves examples/tests/benches AND ffi-backend/dual-mode/simd-* paths all stay strict-clean. This is the #180 gate. Plan doc updated to record the relocation and the diagnostic that drove it. --- .claude/issue-180-plan.md | 7 +++++-- .github/workflows/ci.yml | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.claude/issue-180-plan.md b/.claude/issue-180-plan.md index 3581a5e..eaed82d 100644 --- a/.claude/issue-180-plan.md +++ b/.claude/issue-180-plan.md @@ -153,8 +153,11 @@ cargo test --all-features **Pre-req**: PRs 1–4.5 merged; `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean on `dev`. **Scope**: `.github/workflows/ci.yml` -- `build-and-test` job: replace `cargo clippy --workspace -- -D warnings` with `cargo clippy --workspace --all-targets --all-features -- -D warnings`. -- `pure-rust-build` job: keep `cargo clippy -p webarkitlib-rs -- -D warnings` (this job intentionally verifies the no-features path stays clean — that's a different invariant). +- `kpm-build (ubuntu-latest)` job: add a new step `cargo clippy --workspace --all-targets --all-features -- -D warnings` (Linux-only). This job already has submodules + libclang, which `--all-features` requires (`ffi-backend` triggers C++ compilation in `build.rs`). +- `build-and-test` job: keep `cargo clippy --workspace -- -D warnings` (default features, no submodules). This job's checkout intentionally skips submodules to verify the pure-Rust path doesn't accidentally depend on them — adding `--all-features` here would conflict with that invariant. +- `pure-rust-build` job: keep `cargo clippy -p webarkitlib-rs -- -D warnings` (no-features path stays clean — a third invariant). + +**Discovery**: the original plan had the strict gate going into `build-and-test`. First push of PR 5 failed because that job's checkout doesn't include git submodules, so `--all-features` enabled `ffi-backend` and `build.rs` panicked looking for the C++ sources. Moved to `kpm-build (ubuntu-latest)` where the prerequisites already exist. Lints are OS-agnostic, so Linux-only is sufficient. **Verification**: open PR, watch CI go green. If a lint slips in between PR 4 merge and PR 5 (rustc drift, A1), fix in this PR. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c361227..fd19744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,7 @@ jobs: run: cargo fmt --all -- --check - name: Run cargo clippy - # Strict gate (#180): --all-targets covers examples/tests/benches; - # --all-features exercises ffi-backend, dual-mode, simd-* paths - # that the default-features check skipped. - run: cargo clippy --workspace --all-targets --all-features -- -D warnings + run: cargo clippy --workspace -- -D warnings - name: Run cargo check run: cargo check --workspace @@ -99,6 +96,15 @@ jobs: - name: Rust Cache uses: Swatinem/rust-cache@v2 + # Strict clippy gate (#180): --all-targets covers examples/tests/benches; + # --all-features exercises ffi-backend, dual-mode, simd-* paths the + # default-features check skipped. Ubuntu-only because this is the job + # that already has submodules + libclang; lints are OS-agnostic so + # running it on one platform is sufficient. + - name: Run cargo clippy (strict, --all-targets --all-features) + if: runner.os == 'Linux' + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: Build kpm backend (FFI backend) run: cargo build -p webarkitlib-rs --features ffi-backend From 5d244aa1278f3f8aacdb09c6e8f95792d5aff00d Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 15:50:06 +0200 Subject: [PATCH 13/40] docs(agents): codify #180 clippy conventions and un-ignore .agents/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #180 cleanup series surfaced patterns worth codifying for both contributors and AI assistants: - CLAUDE.md gains §7 "Codebase clippy conventions (#180 lessons)" with six patterns (struct-init, FFI shim cfg alignment, items-above-tests, SIMD allow + rationale, fixture allow, re-run after clean), each linked to the PR that established it. - .agents/instructions.md gains Phase 4 covering the same patterns briefly, referencing CLAUDE.md §7 for the full text. Also un-ignore .agents/ from .gitignore and commit the rules / skills tracked under it, so the Antigravity agent rules and the license-header-adder skill are shareable. roadmap.md and gemini_context.md stay untracked pending a content review (they were marked obsolete). These conventions weren't taught by clippy's lint messages alone — they came out of the diagnostic work in PRs #184-189. Codifying them prevents the next contributor (human or AI) from recreating the lints we just cleared. --- .agents/instructions.md | 60 +++++++++ .agents/rules/rules.md | 21 ++++ .agents/skills/license-header-adder/SKILL.md | 37 ++++++ .../license-header-adder/resources/HEADER.txt | 36 ++++++ .gitignore | 3 - CLAUDE.md | 117 ++++++++++++++++++ 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 .agents/instructions.md create mode 100644 .agents/rules/rules.md create mode 100644 .agents/skills/license-header-adder/SKILL.md create mode 100644 .agents/skills/license-header-adder/resources/HEADER.txt diff --git a/.agents/instructions.md b/.agents/instructions.md new file mode 100644 index 0000000..0935e13 --- /dev/null +++ b/.agents/instructions.md @@ -0,0 +1,60 @@ +# Agent Instructions: Porting Core Data Structures (`include/AR/*.h`) + +## Phase 1: Header Triage & Analysis +1. Analyze in the https://github.com/webarkit/WebARKitLib the `include/AR/` directory. Consider code from lib/src/AR, lib/SRC/AR2, lib/SRC/ARUtils +2. **IGNORE** the following headers: `video.h`, `gsub.h`, `gsub_lite.h`, `arMulti.h`, `arGL.h`, `arOSG.h`. +3. Focus strictly on: `ar.h`, `param.h`, `matrix.h`, `arFilterTransMat.h`. + +## Phase 2: Struct Extraction & Translation +1. Identify ALL core data structures in `ar.h` and related math headers. This includes, but is not limited to: + - Calibration: `ARParam`, `ARParamLT` + - Tracking & State: `ARHandle`, `AR3DHandle`, `ARMarkerInfo` + - Math: `ARMat`, `ARVec` +2. Create the corresponding Rust modules (e.g., `crates/core/src/types.rs`, `crates/core/src/math.rs`). +3. Translate the C `struct`s into Rust. Ensure correct memory alignment and types. +4. Add `#[derive(Debug, Default, Clone)]` where applicable. + +## Phase 3: Validation +1. Write unit tests for every function and struct created to ensure their default instantiation behaves as expected and mathematical equivalence is maintained. +2. Ensure all generated documentation and inline comments are in English. + +## Phase 4: Clippy hygiene (post-#180) + +The strict clippy gate (`cargo clippy --workspace --all-targets --all-features -- -D warnings`) +is enforced in CI by the `kpm-build (ubuntu-latest)` job. When writing +or modifying any code in this repo, follow the conventions below to +avoid recreating lints cleared during the #180 series. Full reference +in [CLAUDE.md §7](../CLAUDE.md). + +1. **Struct-init over `Default::default()` + field reassign.** + `let x = T { f: ..., ..Default::default() };`, not + `let mut x = T::default(); x.f = ...;`. Refs: PR #186, #188. + +2. **FFI shim cfg must match its caller's cfg.** When declaring + `extern "C" { fn webarkit_cpp_*(...) }` only for dual-mode parity + tests, gate the block with `#[cfg(all(test, feature = "dual-mode"))]` + — same as the caller module. A `#[cfg(feature = "dual-mode")]` + alone leaks the extern into the non-test lib build, where it has + no caller and trips `dead_code`. Ref: PR #185. + +3. **Place new items above `#[cfg(test)] mod tests`.** Clippy's + `items_after_test_module` fires otherwise. Ref: PR #184. + +4. **SIMD runtime-dispatch variants get inline + `#[allow(clippy::too_many_arguments)]` + `// rationale:`** — not + a `clippy.toml` threshold raise. Their signatures are locked by + the dispatcher contract. Ref: PR #187. + +5. **Auto-generated test fixtures from external sources** (e.g. + `tests/kpm_regression.rs` constants captured from C++) get + file-scoped `#![allow(clippy::excessive_precision)]` + rationale + to preserve source-traceability. Ref: PR #188. + +6. **Re-run strict clippy after every clean.** Cargo halts on lib + errors before checking `--all-targets`. Lints in + examples/tests/benches stay masked until the lib is clean. Refs: + PR #187 → #188 (26 lints surfaced this way). + +When you must add a lint suppression, write it as `#[allow(...)]` with +a `// rationale:` comment explaining *why* — not `#[expect(...)]` or a +bare allow. The rationale is the contract for future contributors. \ No newline at end of file diff --git a/.agents/rules/rules.md b/.agents/rules/rules.md new file mode 100644 index 0000000..90229b3 --- /dev/null +++ b/.agents/rules/rules.md @@ -0,0 +1,21 @@ +--- +trigger: always_on +--- + +# Antigravity Agent Rules for WebARKitLib.rs + +## 1. Role & Identity +You are an expert systems programmer porting WebARKitLib from C/C++ to Rust. The ultimate target is a pure, side-effect-free WASM module and native library. + +## 2. Strict Exclusions (Out of Scope) +- **NO Video Handling**: Completely ignore all files and functions related to video capture (e.g., V4L2, DirectShow, QuickTime, `video.h`, `arVideo.h`). Video buffering will be handled externally (e.g., via JavaScript `Uint8Array` passed to WASM). +- **NO Rendering/OpenGL**: Completely ignore all files and functions related to OpenGL, GLUT, or 3D rendering (e.g., `gsub.h`, `gsub_lite.h`, `arGL.h`). +- **NO arMulti**: Skip all code related to multi-marker tracking (`arMulti.h`, `arMulti*.c`) for now. + +## 3. Code Standards & Language +- **Language**: ALL code comments, explanations, docstrings, and commit messages MUST be written in English. +- **Safety**: Prefer safe Rust. Use `unsafe` only for specific WASM SIMD intrinsics (`std::arch::wasm32`). + +## 4. C to Rust Translation Guidelines +- Translate C structs from headers into idiomatic Rust. Use exact primitive types (e.g., `f64` for `ARdouble` if configured so). +- Replace C arrays inside structs with Rust arrays or `Vec` depending on whether the size is known at compile time. \ No newline at end of file diff --git a/.agents/skills/license-header-adder/SKILL.md b/.agents/skills/license-header-adder/SKILL.md new file mode 100644 index 0000000..16f0133 --- /dev/null +++ b/.agents/skills/license-header-adder/SKILL.md @@ -0,0 +1,37 @@ +--- +name: license-header-adder +description: Automatically adds a license header to source files with a filename placeholder. +--- + +# License Header Adder Skill + +This skill allows you to add a consistent LGPLv3 license header to all source files in the project. + +## Instructions + +1. **Read the Template**: Locate the license template in `resources/HEADER.txt`. +2. **Placeholder Replacement**: Before applying the header to a file, replace the `{{FILENAME}}` placeholder with the basename of the target file. +3. **Supported Files**: apply this header to: + - `.rs` files + - `.c` and `.h` files + - `.js` and `.ts` files +4. **Application Logic**: + - If the file already has a license header, skip it. + - **Exclusions**: Never apply this to files in `benchmarks/c_benchmark/src/WebARKitLib` or third-party vendor directories. + - Insert the header at the very top of the file. + - Ensure there is a blank line after the header before the original content starts. + +## Example Usage + +When applying to `core/src/lib.rs`: +```rust +/* + * lib.rs + * + * WebARKitLib.rs - High-performance AR tracking library + ... + */ + +use wasm_bindgen::prelude::*; +... +``` diff --git a/.agents/skills/license-header-adder/resources/HEADER.txt b/.agents/skills/license-header-adder/resources/HEADER.txt new file mode 100644 index 0000000..8d9ef2c --- /dev/null +++ b/.agents/skills/license-header-adder/resources/HEADER.txt @@ -0,0 +1,36 @@ +/* + * {{FILENAME}} + * WebARKitLib-rs + * + * This file is part of WebARKitLib-rs - WebARKit. + * + * WebARKitLib-rs is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WebARKitLib-rs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with WebARKitLib-rs. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2026 WebARKit. + * + * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt + * + */ + diff --git a/.gitignore b/.gitignore index b9d78af..ed53543 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# Google Antigravity local agent state (optional, but usually good to keep out of repos) -.agents/ - # VSCode files .vscode/ diff --git a/CLAUDE.md b/CLAUDE.md index 149095d..e2567a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -196,6 +196,123 @@ cargo test --all-features - [ ] CHANGELOG.md **not** touched - [ ] Branch is off current `dev` +## 7. Codebase clippy conventions (#180 lessons) + +The strict clippy gate (`--all-targets --all-features -- -D warnings`) +is enforced in CI by the `kpm-build (ubuntu-latest)` job. The patterns +below are non-obvious — they were discovered during the #180 cleanup +series and aren't taught by clippy's lint messages alone. + +### 7.1 Struct-init over `Default::default()` + field reassign + +Clippy's `field_reassign_with_default` fires on the C-style pattern. + +```rust +// ❌ Don't +let mut h = ARHandle::default(); +h.xsize = w; +h.ysize = h; + +// ✅ Do +let mut h = ARHandle { + xsize: w, + ysize: h, + ..Default::default() +}; +``` + +If subsequent code mutates other fields (e.g. `h.o2i[i] = ...`), keep +the binding `mut`. Method calls (`h.set_pixel_format(...)`) stay +after the initializer — they're not field reassigns. + +Reference: PR #186, PR #188. + +### 7.2 FFI shim cfg must match its caller's cfg + +Dual-mode parity tests live under `#[cfg(all(test, feature = "dual-mode"))]`. +The `extern "C"` block declaring the `webarkit_cpp_*` shims it calls +must use the **same** cfg — not just `#[cfg(feature = "dual-mode")]` — +otherwise the lib target sees an extern with no caller and clippy +fires `dead_code` under `--all-features`. + +```rust +// ❌ Don't (extern declared in non-test builds where it has no caller) +#[cfg(feature = "dual-mode")] +extern "C" { + fn webarkit_cpp_foo(...) -> i32; +} + +#[cfg(all(test, feature = "dual-mode"))] +mod dual_mode_tests { + use super::*; + #[test] fn foo_matches_cpp() { unsafe { webarkit_cpp_foo(...) }; } +} + +// ✅ Do (extern and caller share the same gate) +#[cfg(all(test, feature = "dual-mode"))] +extern "C" { + fn webarkit_cpp_foo(...) -> i32; +} +``` + +Reference: PR #185. + +### 7.3 Items must come **above** `#[cfg(test)] mod tests` + +Clippy's `items_after_test_module` fires when `pub fn ...` or any item +appears after the test module. When adding helpers to a file that +already has a `mod tests`, insert them above the `#[cfg(test)]` line. + +Reference: PR #184 (`mat_mul_dff`, `dot_product*`). + +### 7.4 SIMD runtime-dispatch variants: inline `#[allow]` + rationale + +Functions like `get_similarity_sse41` / `get_similarity_avx2` are +runtime-dispatched alternatives to a scalar fallback. Their +signatures are **locked** to match each other and the scalar version. +Prefer inline `#[allow(clippy::too_many_arguments)]` with a +`// rationale:` line over raising the threshold in `clippy.toml` — the +allow scopes to exactly the sites that need it. + +```rust +#[cfg(all(feature = "simd-x86-sse41", target_arch = "x86_64"))] +#[target_feature(enable = "sse4.1")] +// rationale: SIMD variant of get_similarity; signature locked to match +// the scalar fallback and sibling SIMD impl for runtime dispatch via +// is_x86_feature_detected!. +#[allow(clippy::too_many_arguments)] +unsafe fn get_similarity_sse41(...) -> Option { ... } +``` + +Reference: PR #187. + +### 7.5 Auto-generated fixtures: file-scoped `#![allow]` + rationale + +For test files containing constants captured verbatim from external +sources (e.g. C++ baseline output dumped by `kpm_dump_fixtures`), +preserve source-traceability with a file-scoped allow rather than +truncating the literals. + +```rust +//! KPM regression tests — numerical validation against C++ baseline. + +#![allow(dead_code)] +// rationale: SCREEN/WORLD/pose constants below were generated by the +// C++ kpm_dump_fixtures tool. Extra decimal digits document the +// captured C++ output verbatim; truncating to f32-exact would +// round-trip to the same bit pattern but lose traceability (#180). +#![allow(clippy::excessive_precision)] +``` + +Reference: PR #188 (`tests/kpm_regression.rs`). + +### 7.6 Re-run strict clippy after the lib goes clean + +`cargo clippy --all-targets` halts on lib errors before checking +examples/tests/benches. After clearing the last lib lint, **re-run** +the strict command — previously masked lints in non-lib targets will +surface. PR #188 cleaned up 26 such lints unmasked by PR #187. + ## 📝 Documentation & Style - All comments, docstrings, and commit messages must be in **English**. - Use `Result` for error handling instead of `panic!`. From b94a720837b1ab4f18be12ebb396859ee102b7d1 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 17:31:24 +0200 Subject: [PATCH 14/40] ci(miri): add Miri UB validation for pure-Rust code paths (#182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new `miri` CI job that runs `cargo +nightly miri test -p webarkitlib-rs --lib` to catch undefined behavior in the freshly- ported KPM/FREAK/AR/AR2 code: use-after-free, OOB reads, invalid `unsafe` invariants, uninitialized-memory reads, and reference-aliasing violations that regular tests miss. Scope decisions (full rationale in docs/design/issue-182-miri-plan.md): - `continue-on-error: true` initially; ratchet to required after UB fix PRs land. Mirrors the #180 strict-clippy ratcheting pattern. - PR-scoped via job-level `if:` guard (dev/main only) — Miri is 5-30x slower than `cargo test`, so push-to-branch runs add cost without value. - Pinned nightly date inline (`MIRI_NIGHTLY`) so a bad nightly can't red the job for reasons unrelated to our code. - Excludes `ffi-backend` (Miri can't execute C++) and `simd-x86-*` (Miri's x86 SIMD support is incomplete). Scalar fallbacks ARE validated; SIMD parity is covered by existing scalar/SIMD tests. - `-Zmiri-strict-provenance` enabled to catch pointer-provenance bugs loose provenance silently allows. Adds docs/miri.md covering scope, exclusions, local-run instructions, failure investigation, the nightly-pin bump procedure, and the continue-on-error -> required gate ratchet plan. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 42 +++++++++ docs/design/issue-182-miri-plan.md | 143 +++++++++++++++++++++++++++++ docs/miri.md | 112 ++++++++++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 docs/design/issue-182-miri-plan.md create mode 100644 docs/miri.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd19744..1963b8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,48 @@ jobs: --test absolute_corner_error \ --features dual-mode -- --nocapture + # Miri UB validation (#182): runs the pure-Rust lib under the MIR + # interpreter to catch use-after-free, OOB reads, invalid `unsafe` + # invariants, uninitialized-memory reads, and reference-aliasing + # violations that escape regular tests. Excludes `ffi-backend` + # (Miri can't execute C++) and `simd-x86-*` (Miri's x86 SIMD support + # is incomplete). Scalar fallbacks ARE validated. + # + # `continue-on-error: true` initially — will be ratcheted to required + # once initial UB findings are resolved via follow-up PRs tracked + # under #182. See docs/miri.md for scope, exclusions, local-run + # instructions, and the nightly-pin bump procedure. + miri: + name: miri (pure-Rust UB validation) + runs-on: ubuntu-latest + # Restrict to PRs against dev/main only — Miri is 5–30x slower than + # cargo test and the pre-merge gate is where it earns its keep. + if: github.event_name == 'pull_request' && (github.base_ref == 'dev' || github.base_ref == 'main') + continue-on-error: true + env: + # Pinned nightly — bump manually if Miri breaks on this date or + # every ~3 months for freshness. See docs/miri.md. + MIRI_NIGHTLY: nightly-2026-06-01 + steps: + - uses: actions/checkout@v5 + + - name: Install pinned nightly + Miri + run: | + rustup toolchain install $MIRI_NIGHTLY --component miri --profile minimal + rustup default $MIRI_NIGHTLY + cargo miri setup + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: miri-${{ env.MIRI_NIGHTLY }} + shared-key: miri + + - name: Run Miri (pure-Rust, lib targets only) + run: cargo miri test -p webarkitlib-rs --lib + env: + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-strict-provenance + submodule-drift-check: runs-on: ubuntu-latest steps: diff --git a/docs/design/issue-182-miri-plan.md b/docs/design/issue-182-miri-plan.md new file mode 100644 index 0000000..1058612 --- /dev/null +++ b/docs/design/issue-182-miri-plan.md @@ -0,0 +1,143 @@ +# Issue #182 — Miri UB Validation for Pure-Rust Code Paths + +Design plan produced via `/brainstorming` skill. +Source issue: https://github.com/webarkit/WebARKitLib-rs/issues/182 +Branch: `ci/miri-validation-182` (off `dev`). + +--- + +## Understanding Summary + +- **What**: Add a Miri-based undefined-behavior validation CI job + (`cargo +nightly miri test -p webarkitlib-rs --lib`) and accompanying + `docs/miri.md`. +- **Why**: Catch use-after-free, OOB reads, invalid `unsafe` invariants, + uninitialized-memory reads, and reference-aliasing violations in the + freshly-ported KPM/FREAK/AR/AR2 code before contributors build on top + of it. Sister to #180 (clippy strictness) as the second half of the + post-M9 safety net. +- **Who**: Maintainers and contributors touching `unsafe`, FFI + boundaries, or hot loops with unchecked slicing. +- **Scope**: Pure-Rust default features, lib targets only. Excludes + `ffi-backend` (Miri can't execute C++), `simd-x86-*` (Miri x86 SIMD + support is incomplete), and FFI-dependent integration tests under + `tests/`. +- **Non-goals**: Fixing whatever UB Miri surfaces in this PR — each + finding gets its own follow-up PR, mirroring the #180 cleanup series + pattern. + +## Modules under Miri validation + +Explicit list (per user correction during brainstorming — issue draft +only highlighted KPM/FREAK): + +- `crates/core/src/ar/` — labeling, pattern matching, marker decoding, + image processing (C ARToolKit port). +- `crates/core/src/ar2/` — image_set, feature_map, byteorder I/O. +- `crates/core/src/kpm/` — KPM pipeline. +- `crates/core/src/kpm/freak/` — math, homography, clustering, matcher, + visual_database (M6→M9 fresh port). + +## Assumptions + +- `kpm-build (ubuntu-latest)` is the right neighbor for the Miri job in + `ci.yml` — both are pure-Rust correctness gates. +- No `rust-toolchain.toml` change needed; nightly stays scoped to the + Miri job. +- Conventional Commits: PR is + `ci(miri): add Miri UB validation for pure-Rust code paths (#182)`. +- The repo-wide `on:` block (`push: branches: ['**']` + `pull_request:`) + requires a job-level `if:` guard to restrict to PRs against + `dev`/`main` (per Q2 decision). + +## Non-Functional Requirements + +- **CI cost**: Single Ubuntu runner, no matrix. Expected 10–30 min on + warm cache. Acceptable per issue. +- **Reliability**: `continue-on-error: true` initially; transient + nightly breaks → manual rerun. +- **Maintenance**: Nightly pin bumped manually when Miri breaks or + every ~3 months. No automation. +- **Security**: None — checkout + rustup + cache action; no secrets. +- **Scope discipline**: This PR adds *only* CI infra + docs. Any UB + Miri surfaces → separate fix PRs. + +## Decision Log + +| # | Decision | Alternatives | Rationale | +|---|---|---|---| +| 1 | `continue-on-error: true`, ratchet later | Block PR until clean; bundle fixes here | Matches #180 ratcheting precedent; decouples infra from unknown-size UB cleanup | +| 2 | Trigger on PRs to `dev`/`main` only | Every push; nightly schedule; both | Pre-merge gate is the actual value; nightly schedule = YAGNI | +| 3 | `Swatinem/rust-cache@v2`, Miri-keyed | No cache; explicit sysroot cache | Standard; handles Miri sysroot when keyed on toolchain | +| 4 | Pin nightly date inline in workflow | Floating nightly; `rust-toolchain.toml` | Local to Miri job; survives nightly regressions; doesn't bleed into stable jobs | +| 5 | `docs/miri.md` (new file) | Section in `CONTRIBUTING.md`; both | Single canonical URL for CI annotations & follow-up PRs | +| 6 | Standalone top-level `miri:` job | Step in existing job; reusable workflow | Matches repo pattern; trivial ratchet to required; no nightly contamination of stable jobs | +| 7 | Scope description names `ar/`, `ar2/`, `kpm/`, `kpm/freak/` explicitly | KPM/FREAK only (per issue draft) | User correction — `ar/` modules contain the C ARToolKit port surface and matter equally | +| 8 | `-Zmiri-strict-provenance` enabled in CI | Default provenance | Catches pointer-provenance bugs that loose-provenance hides; appropriate for a careful port | +| 9 | Job-level `if:` guard for PR-to-dev/main scoping | Workflow-level `on:` override (not possible per-job); separate workflow file | Cleanest; one line; doesn't touch other jobs' triggers | + +## Final Design + +### Workflow job (added to `.github/workflows/ci.yml`) + +```yaml +miri: + name: miri (pure-Rust UB validation) + runs-on: ubuntu-latest + # Per Q2: restrict to PRs against dev/main only. + if: github.event_name == 'pull_request' && + (github.base_ref == 'dev' || github.base_ref == 'main') + continue-on-error: true # ratchet to false after UB fix PRs land (#182) + env: + MIRI_NIGHTLY: nightly-2026-06-01 + steps: + - uses: actions/checkout@v5 + - name: Install pinned nightly + Miri + run: | + rustup toolchain install $MIRI_NIGHTLY --component miri --profile minimal + rustup default $MIRI_NIGHTLY + cargo miri setup + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: miri-${{ env.MIRI_NIGHTLY }} + shared-key: miri + - name: Run Miri (pure-Rust, lib targets only) + run: cargo miri test -p webarkitlib-rs --lib + env: + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-strict-provenance +``` + +### `docs/miri.md` skeleton + +Sections: +1. What Miri validates here (module list). +2. What Miri does NOT validate (`ffi-backend`, SIMD, FFI integration tests). +3. Running locally (commands). +4. Investigating failures (`MIRIFLAGS` knobs). +5. Bumping the nightly pin (policy). +6. CI gate status (`continue-on-error` → required ratchet). + +## Risks + +| Risk | Mitigation | +|---|---| +| Nightly Miri broken on the pinned date | Pick known-good date during PR; document bump procedure | +| First-run UB findings overwhelming | `continue-on-error: true`; separate follow-up PRs | +| CI minute cost on PRs | PR-scoped via `if:` guard; rust-cache keeps warm runs cheap | +| Cache pollution between nightly bumps | Cache key includes `MIRI_NIGHTLY` → automatic invalidation | + +## Exit Criteria — all met + +- [x] Understanding Lock confirmed +- [x] Design approach A (standalone top-level job) accepted +- [x] Assumptions documented +- [x] Risks acknowledged +- [x] Decision Log complete + +## Out of Scope + +- Fixing any UB findings surfaced by the first Miri run. +- Adding Miri to `simd-*` or `ffi-backend` paths. +- Multi-OS Miri matrix (Linux-only is sufficient — Miri's findings are + not OS-specific for pure-Rust code). diff --git a/docs/miri.md b/docs/miri.md new file mode 100644 index 0000000..77a54d1 --- /dev/null +++ b/docs/miri.md @@ -0,0 +1,112 @@ +# Miri — Undefined Behavior Validation + +[Miri](https://github.com/rust-lang/miri) is the Rust MIR interpreter. +It detects undefined behavior that escapes regular tests: use-after-free, +out-of-bounds reads, invalid `unsafe` invariants, uninitialized-memory +reads, and reference-aliasing violations. + +WebARKitLib-rs runs Miri in CI as the second half of the post-M9 safety +net (sister to #180 clippy strictness). + +--- + +## What Miri validates here + +CI runs: + +```bash +cargo +nightly miri test -p webarkitlib-rs --lib +``` + +That exercises the pure-Rust lib of the `webarkitlib-rs` crate, which +covers the modules where manual buffer math and `unsafe` live: + +- `crates/core/src/ar/` — labeling, pattern matching, marker decoding, + image processing (C ARToolKit port). +- `crates/core/src/ar2/` — `image_set`, `feature_map`, `byteorder` I/O + on disk buffers. +- `crates/core/src/kpm/` — KPM pipeline. +- `crates/core/src/kpm/freak/` — `math`, `homography`, `clustering`, + `matcher`, `visual_database` (the freshly-ported M6→M9 surface). + +## What Miri does NOT validate + +- **`--features ffi-backend`** — Miri cannot execute foreign C/C++ + functions, so all FFI shims would fail under it. The `webarkit_cpp_*` + externs are intentionally outside Miri's scope. +- **`--features simd-x86-sse41` / `simd-x86-avx2`** — Miri's x86 SIMD + intrinsic support is incomplete. Scalar fallbacks ARE exercised under + Miri; SIMD parity is covered by the scalar/SIMD parity tests in + CI separately. +- **Integration tests under `tests/`** that depend on FFI (e.g. + `kpm_regression`, `cross_stack_parity`, `absolute_corner_error` in + `dual-mode`) — these need the C++ baseline to run. + +## Running Miri locally + +```bash +# One-time setup (matches the CI pin — update if CI pin changes) +rustup toolchain install nightly-2026-06-01 --component miri +cargo +nightly-2026-06-01 miri setup + +# Run the full Miri suite (matches CI) +cargo +nightly-2026-06-01 miri test -p webarkitlib-rs --lib + +# Run a single test +cargo +nightly-2026-06-01 miri test -p webarkitlib-rs --lib +``` + +Expect the run to be **5–30× slower** than `cargo test`. This is normal +— Miri interprets MIR rather than executing native code. + +## Investigating failures + +When Miri reports UB, useful environment knobs: + +```bash +# Full backtrace on the offending operation +MIRIFLAGS="-Zmiri-backtrace=full" cargo +nightly miri test -p webarkitlib-rs --lib + +# Strict provenance (already set in CI — catches pointer-provenance bugs +# that loose provenance silently allows) +MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test -p webarkitlib-rs --lib + +# Disable isolation if a test needs filesystem access Miri rejects +MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -p webarkitlib-rs --lib +``` + +If a finding is intentional (e.g. a known-safe `transmute` pattern that +Miri can't model), mark the test `#[ignore]` under Miri and open a +tracking issue. **Do not** add `unsafe` to silence Miri without +understanding the report. + +## Bumping the nightly pin + +The pin lives in `.github/workflows/ci.yml` as the `MIRI_NIGHTLY` job +env var. Bump when: + +- CI hits a known nightly Miri regression (link the upstream issue in + the PR description). +- Every ~3 months for general freshness. + +Procedure: + +1. Pick a recent known-good nightly from + (filter on + `miri`). +2. Update `MIRI_NIGHTLY` in `ci.yml` AND the "Running locally" snippet + in this file. +3. Open a PR; CI will validate the new pin. + +The `Swatinem/rust-cache@v2` key includes `MIRI_NIGHTLY`, so the cache +invalidates automatically on bump. + +## CI gate status + +The Miri job currently runs with `continue-on-error: true` (advisory). +It will be ratcheted to a required gate once initial UB findings +surfaced by the first runs are resolved via follow-up PRs tracked under +[#182](https://github.com/webarkit/WebARKitLib-rs/issues/182). + +Each finding gets its own fix PR — never a mega-PR — mirroring the #180 +cleanup series pattern. From 0e2eba925b1a53351502cdf2464ea46526e1e9dc Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 17:45:38 +0200 Subject: [PATCH 15/40] ci(miri): drop -Zmiri-strict-provenance (trips on third-party deps) (#182) First CI run surfaced that strict provenance fires inside crossbeam-epoch (a transitive dep of rayon) when ar2::feature_map's par_chunks_mut spins up the thread pool. The dep uses integer-to-pointer casts internally and hasn't migrated to the Strict Provenance APIs. Strict provenance against unmigrated third-party deps produces failures we cannot fix here. Keep regular Miri (the core UB net) and revisit strict provenance once the ecosystem catches up. Developers can still spot-check their own unsafe code locally with the flag. docs/miri.md updated with the rationale. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 6 +++++- docs/miri.md | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1963b8a..0d676a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,10 +199,14 @@ jobs: key: miri-${{ env.MIRI_NIGHTLY }} shared-key: miri + # NOTE: -Zmiri-strict-provenance is intentionally NOT enabled. + # It trips inside transitive deps (e.g. crossbeam-epoch via rayon) + # that haven't migrated to Strict Provenance APIs yet. Re-evaluate + # once the ecosystem catches up. See docs/miri.md. - name: Run Miri (pure-Rust, lib targets only) run: cargo miri test -p webarkitlib-rs --lib env: - MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-strict-provenance + MIRIFLAGS: -Zmiri-backtrace=full submodule-drift-check: runs-on: ubuntu-latest diff --git a/docs/miri.md b/docs/miri.md index 77a54d1..d8af1fc 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -67,8 +67,8 @@ When Miri reports UB, useful environment knobs: # Full backtrace on the offending operation MIRIFLAGS="-Zmiri-backtrace=full" cargo +nightly miri test -p webarkitlib-rs --lib -# Strict provenance (already set in CI — catches pointer-provenance bugs -# that loose provenance silently allows) +# Strict provenance (NOT enabled in CI — see note below; useful locally +# for spot-checking our own code) MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test -p webarkitlib-rs --lib # Disable isolation if a test needs filesystem access Miri rejects @@ -101,6 +101,19 @@ Procedure: The `Swatinem/rust-cache@v2` key includes `MIRI_NIGHTLY`, so the cache invalidates automatically on bump. +## Why `-Zmiri-strict-provenance` is NOT enabled in CI + +The first CI run with strict provenance enabled tripped inside +`crossbeam-epoch` (a transitive dep of `rayon`), called from +`ar2::feature_map`'s `par_chunks_mut`. That dep predates the Strict +Provenance APIs and still uses integer-to-pointer casts internally. + +Running CI with strict provenance against unmigrated third-party deps +produces failures we cannot fix in this repo. We keep regular Miri (the +core UB net) on by default and revisit strict provenance once the +ecosystem catches up. Developers are encouraged to spot-check their own +new `unsafe` code locally with `MIRIFLAGS="-Zmiri-strict-provenance"`. + ## CI gate status The Miri job currently runs with `continue-on-error: true` (advisory). From b9a49cec18eab6e19116d2b109d66ed898fba9db Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 17:53:16 +0200 Subject: [PATCH 16/40] ci(miri): switch to Tree Borrows to avoid crossbeam-epoch false positive (#182) Second CI run surfaced a Stacked Borrows aliasing error entirely inside crossbeam-epoch -> rayon (called from ar2::feature_map par_chunks_mut). The backtrace never enters our code; the violation is a known incompatibility between Miri's default Stacked Borrows model and crossbeam's internal patterns (crossbeam-rs/crossbeam#545 family). Switch CI to the newer Tree Borrows aliasing model (-Zmiri-tree-borrows), which accepts crossbeam's patterns while still catching real UB in our own unsafe code. docs/miri.md updated to explain the choice and how to spot-check our own code under Stacked Borrows locally if desired. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 16 +++++++++++----- docs/miri.md | 35 +++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d676a0..b34b226 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,14 +199,20 @@ jobs: key: miri-${{ env.MIRI_NIGHTLY }} shared-key: miri - # NOTE: -Zmiri-strict-provenance is intentionally NOT enabled. - # It trips inside transitive deps (e.g. crossbeam-epoch via rayon) - # that haven't migrated to Strict Provenance APIs yet. Re-evaluate - # once the ecosystem catches up. See docs/miri.md. + # NOTES on MIRIFLAGS: + # * -Zmiri-tree-borrows: use the Tree Borrows aliasing model instead + # of the default Stacked Borrows. Stacked Borrows trips inside + # crossbeam-epoch (a transitive dep of rayon, reached from our + # ar2::feature_map par_chunks_mut) on patterns that are sound in + # practice. Tree Borrows accepts them while still catching real + # UB in our own code. + # * -Zmiri-strict-provenance is intentionally NOT enabled — it also + # trips inside crossbeam-epoch (integer-to-pointer casts). Re- + # evaluate once the ecosystem migrates. See docs/miri.md. - name: Run Miri (pure-Rust, lib targets only) run: cargo miri test -p webarkitlib-rs --lib env: - MIRIFLAGS: -Zmiri-backtrace=full + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows submodule-drift-check: runs-on: ubuntu-latest diff --git a/docs/miri.md b/docs/miri.md index d8af1fc..8c8aba9 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -101,18 +101,33 @@ Procedure: The `Swatinem/rust-cache@v2` key includes `MIRI_NIGHTLY`, so the cache invalidates automatically on bump. -## Why `-Zmiri-strict-provenance` is NOT enabled in CI +## Aliasing model: Tree Borrows (not Stacked Borrows) + +CI sets `MIRIFLAGS=-Zmiri-tree-borrows`. We use the **Tree Borrows** +aliasing model instead of Miri's default **Stacked Borrows** because +Stacked Borrows trips inside `crossbeam-epoch` (a transitive dep of +`rayon`, reached from `ar2::feature_map`'s `par_chunks_mut`) on +patterns that are sound in practice. Tree Borrows is a newer +experimental aliasing model that accepts those patterns while still +catching real UB in our code. -The first CI run with strict provenance enabled tripped inside -`crossbeam-epoch` (a transitive dep of `rayon`), called from -`ar2::feature_map`'s `par_chunks_mut`. That dep predates the Strict -Provenance APIs and still uses integer-to-pointer casts internally. +If you want to spot-check our own `unsafe` under the stricter Stacked +Borrows locally, just drop the flag: + +```bash +MIRIFLAGS="-Zmiri-backtrace=full" cargo +nightly miri test \ + -p webarkitlib-rs --lib +``` + +## Why `-Zmiri-strict-provenance` is NOT enabled in CI -Running CI with strict provenance against unmigrated third-party deps -produces failures we cannot fix in this repo. We keep regular Miri (the -core UB net) on by default and revisit strict provenance once the -ecosystem catches up. Developers are encouraged to spot-check their own -new `unsafe` code locally with `MIRIFLAGS="-Zmiri-strict-provenance"`. +Strict provenance also trips inside `crossbeam-epoch` (it predates the +Strict Provenance APIs and still uses integer-to-pointer casts +internally). Running CI with strict provenance against unmigrated +third-party deps produces failures we cannot fix in this repo. We +revisit once the ecosystem catches up. Developers are encouraged to +spot-check their own new `unsafe` code locally with +`MIRIFLAGS="-Zmiri-strict-provenance"`. ## CI gate status From 8185703b8e2a15673ade80b089fa9160e579bebf Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 17:58:58 +0200 Subject: [PATCH 17/40] ci(miri): enable -Zmiri-disable-isolation for tempfile-using tests (#182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third CI run surfaced an isolation error: ar2::feature_set roundtrip uses tempfile, which calls `open()` — blocked by Miri's default isolation. Add -Zmiri-disable-isolation so disk I/O passes through to the host. This is the standard Miri configuration for test suites that include filesystem-touching tests; it does not weaken the UB checks. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 4 +++- docs/miri.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b34b226..3f07631 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -212,7 +212,9 @@ jobs: - name: Run Miri (pure-Rust, lib targets only) run: cargo miri test -p webarkitlib-rs --lib env: - MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows + # -Zmiri-disable-isolation: allow filesystem ops so tests that + # use `tempfile` (e.g. ar2::feature_set roundtrip) can run. + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation submodule-drift-check: runs-on: ubuntu-latest diff --git a/docs/miri.md b/docs/miri.md index 8c8aba9..5664000 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -71,7 +71,8 @@ MIRIFLAGS="-Zmiri-backtrace=full" cargo +nightly miri test -p webarkitlib-rs --l # for spot-checking our own code) MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test -p webarkitlib-rs --lib -# Disable isolation if a test needs filesystem access Miri rejects +# -Zmiri-disable-isolation (already set in CI — lets tests using +# tempfile / disk I/O run; e.g. ar2::feature_set roundtrip) MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -p webarkitlib-rs --lib ``` From 6e064d3d61bd3661c85aa815cf294e92ff56908c Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 20:38:53 +0200 Subject: [PATCH 18/40] fix(kpm): replace unaligned transmute in hamming_distance_96 (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Miri (CI job from #182) flagged `hamming_distance_96` for UB: transmuting `&[u8; 96]` to `&[u32; 24]` requires 4-byte alignment on the input, which `&[u8; 96]` does not guarantee — the byte array only needs 1-byte alignment, and Miri produces a case where it isn't. Replace the transmute with `u32::from_ne_bytes` on 4-byte chunks. This removes the `unsafe` entirely while producing identical numerical output (the same 32-bit popcount summed over the same 24 words). On aligned inputs the codegen is equivalent to the transmute version; on unaligned inputs it is now correct instead of UB. Verified locally: the full library test suite (`cargo test -p webarkitlib-rs --lib`) is green (419 passed, 2 ignored). The C++ parity is preserved because the byte order, word count, and hamming_distance_32 reduction are unchanged. Audit: grep across the whole `crates/` tree confirms this was the only `transmute::<&[u8; ...], &[u32; ...]>` site — no further unaligned-transmute fixes needed in `kpm/freak/{math,matcher, visual_database}.rs`. Co-Authored-By: Claude Opus 4.7 --- crates/core/src/kpm/freak/clustering.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/core/src/kpm/freak/clustering.rs b/crates/core/src/kpm/freak/clustering.rs index 174043e..6a3e735 100644 --- a/crates/core/src/kpm/freak/clustering.rs +++ b/crates/core/src/kpm/freak/clustering.rs @@ -63,16 +63,17 @@ fn hamming_distance_32(a: u32, b: u32) -> u32 { /// Computes Hamming distance between two 96-byte (768-bit) FREAK descriptors. /// C equivalent: HammingDistance768 pub fn hamming_distance_96(a: &[u8; 96], b: &[u8; 96]) -> u32 { - // SAFETY: Transmuting &[u8; 96] to &[u32; 24] is safe because: - // 1. Byte arrays are correctly sized (96 bytes = 24 × 4 bytes) - // 2. u32 alignment is guaranteed on all supported targets - // 3. The transmute creates a properly-aligned view of the same data - let a32 = unsafe { std::mem::transmute::<&[u8; 96], &[u32; 24]>(a) }; - let b32 = unsafe { std::mem::transmute::<&[u8; 96], &[u32; 24]>(b) }; - - a32.iter() - .zip(b32.iter()) - .map(|(x, y)| hamming_distance_32(*x, *y)) + // Reassemble each 4-byte chunk via `u32::from_ne_bytes` rather than + // transmuting `&[u8; 96]` to `&[u32; 24]`. The transmute requires + // 4-byte alignment on the input, which `&[u8; 96]` does not + // guarantee — Miri flagged it as UB (#192). + (0..24) + .map(|i| { + let off = i * 4; + let ax = u32::from_ne_bytes([a[off], a[off + 1], a[off + 2], a[off + 3]]); + let bx = u32::from_ne_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]]); + hamming_distance_32(ax, bx) + }) .sum() } From 5952fd6bd47e5501fe554704dfd88568864ae810 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 12 Jun 2026 23:47:08 +0200 Subject: [PATCH 19/40] perf(ci): scope Miri job to unsafe boundaries via cfg_attr(miri, ignore) (#194) The Miri CI job added in #191 was hitting 1-2 hour runtimes on PRs. Root cause: ~14 unit tests run *full pipelines* (BHC tree construction over hundreds of descriptors, DoG keypoint detection on real benchmark images, full ar2_gen_feature_map runs). Native `cargo test` finishes them in milliseconds via compiled code + rayon parallelism; Miri interprets MIR single-threaded, so each test takes 10-40 minutes under interpretation -- and none of them exercise `unsafe` boundaries that targeted unit tests don't already cover. Annotate them with `#[cfg_attr(miri, ignore)]`. The tests still run under normal `cargo test`; they are skipped only under `cargo miri test`. Targeted unit tests on the actual `unsafe` surface (hamming_distance_*, descriptor pack/unpack, byteorder reads, image_proc indexing) stay enabled -- those are what Miri is meant to validate. Tests annotated: * clustering.rs: test_bhc_max_nodes_to_pop_widens_candidate_set (200 descriptors x2 builds; the test that hung CI on #191), test_bhc_max_nodes_to_pop_zero_is_tied_min_only, test_bhc_set_num_centers_preserves_num_hypotheses, test_bhc_set_num_hypotheses_then_build (80 descriptors each). * detector.rs: test_dog_detector_finds_keypoints_on_real_image, test_dog_detector_keypoints_within_image_bounds. * keyframe.rs: test_find_features_populates_keyframe, test_keyframe_build_index_populates_index, test_keyframe_build_index_is_idempotent. * visual_database.rs: test_visual_database_add_image_builds_index, test_visual_database_add_keyframe_builds_index_when_absent, test_visual_database_add_keyframe_preserves_caller_built_index. * feature_map.rs: test_feature_map_small_synthetic, test_feature_map_mindpi_maxdpi. (test_feature_map_produces_points already has #[ignore], skipped.) docs/miri.md updated: new section explaining the convention and how to run a Miri-ignored test locally for investigation. Verified locally: cargo test -p webarkitlib-rs --lib still passes all 419 tests (cfg_attr only affects Miri runs). Expected effect: Miri CI runtime drops from 1-2 hours to ~10 minutes, restoring per-PR Miri as a practical safety net. Co-Authored-By: Claude Opus 4.7 --- crates/core/src/ar2/feature_map.rs | 2 ++ crates/core/src/kpm/freak/clustering.rs | 4 +++ crates/core/src/kpm/freak/detector.rs | 2 ++ crates/core/src/kpm/freak/keyframe.rs | 3 ++ crates/core/src/kpm/freak/visual_database.rs | 3 ++ docs/miri.md | 32 ++++++++++++++++++++ 6 files changed, 46 insertions(+) diff --git a/crates/core/src/ar2/feature_map.rs b/crates/core/src/ar2/feature_map.rs index 06c6686..26bb537 100644 --- a/crates/core/src/ar2/feature_map.rs +++ b/crates/core/src/ar2/feature_map.rs @@ -942,6 +942,7 @@ mod tests { /// Lightweight version using a small synthetic image. #[test] + #[cfg_attr(miri, ignore)] // #194: full ar2_gen_feature_map pipeline (64×64) — too slow under Miri fn test_feature_map_small_synthetic() { let w: i32 = 64; let h: i32 = 64; @@ -969,6 +970,7 @@ mod tests { /// Verify mindpi/maxdpi are computed correctly for a 3-level pyramid. #[test] + #[cfg_attr(miri, ignore)] // #194: full ar2_gen_feature_map pipeline (32×32 × 3 scales) — too slow under Miri fn test_feature_map_mindpi_maxdpi() { // Build a 3-scale set manually: 72, 57, 45 dpi let make = |dpi: f32| AR2ImageT { diff --git a/crates/core/src/kpm/freak/clustering.rs b/crates/core/src/kpm/freak/clustering.rs index 6a3e735..50d4bf4 100644 --- a/crates/core/src/kpm/freak/clustering.rs +++ b/crates/core/src/kpm/freak/clustering.rs @@ -954,6 +954,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: BHC build over 80 descriptors — too slow under Miri fn test_bhc_set_num_hypotheses_then_build() { // Setting num_hypotheses to a non-default value must not break build/query. let descs = make_synth_descriptors(80); @@ -977,6 +978,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: BHC build over 80 descriptors — too slow under Miri fn test_bhc_set_num_centers_preserves_num_hypotheses() { // Regression test for setter ordering: set_num_centers must not // silently reset num_hypotheses back to 1 (it did before #146). @@ -996,6 +998,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: BHC build over 80 descriptors — too slow under Miri fn test_bhc_max_nodes_to_pop_zero_is_tied_min_only() { // With max_nodes_to_pop=0 (default), query should produce exactly // the same result set as the pre-#146 implementation: only @@ -1013,6 +1016,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: BHC build over 200 descriptors ×2 — by far the slowest test under Miri fn test_bhc_max_nodes_to_pop_widens_candidate_set() { // Monotonicity check: query(max_nodes_to_pop=N) should return at // least as many candidates as query(max_nodes_to_pop=0) on the same diff --git a/crates/core/src/kpm/freak/detector.rs b/crates/core/src/kpm/freak/detector.rs index 1ba0b5d..dfd0162 100644 --- a/crates/core/src/kpm/freak/detector.rs +++ b/crates/core/src/kpm/freak/detector.rs @@ -1116,6 +1116,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image DoG detection — too slow under Miri fn test_dog_detector_finds_keypoints_on_real_image() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_test_pyramid(&img, 3); @@ -1125,6 +1126,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image DoG detection — too slow under Miri fn test_dog_detector_keypoints_within_image_bounds() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_test_pyramid(&img, 3); diff --git a/crates/core/src/kpm/freak/keyframe.rs b/crates/core/src/kpm/freak/keyframe.rs index 9a6e1b0..6217b5c 100644 --- a/crates/core/src/kpm/freak/keyframe.rs +++ b/crates/core/src/kpm/freak/keyframe.rs @@ -171,6 +171,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pyramid + DoG pipeline on real image — too slow under Miri fn test_find_features_populates_keyframe() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut pyr = GaussianScaleSpacePyramid::new(3); @@ -197,6 +198,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pyramid + DoG + BHC pipeline on real image — too slow under Miri fn test_keyframe_build_index_populates_index() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut pyr = GaussianScaleSpacePyramid::new(3); @@ -215,6 +217,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pipeline run twice — too slow under Miri fn test_keyframe_build_index_is_idempotent() { // Calling build_index twice should not error; the second call replaces // the first cleanly (mirrors C++ Keyframe::buildIndex which always diff --git a/crates/core/src/kpm/freak/visual_database.rs b/crates/core/src/kpm/freak/visual_database.rs index 32db360..a7a4414 100644 --- a/crates/core/src/kpm/freak/visual_database.rs +++ b/crates/core/src/kpm/freak/visual_database.rs @@ -954,6 +954,7 @@ mod tests { // ----------------------------------------------------------------- #[test] + #[cfg_attr(miri, ignore)] // #194: full pyramid + DoG + BHC pipeline on real image — too slow under Miri fn test_visual_database_add_image_builds_index() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut db = VisualDatabase::new().expect("new"); @@ -965,6 +966,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pyramid + DoG + BHC pipeline on real image — too slow under Miri fn test_visual_database_add_keyframe_builds_index_when_absent() { // Build a keyframe externally without calling build_index, hand it to // add_keyframe, assert add_keyframe built the index for us. @@ -989,6 +991,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pyramid + DoG + BHC pipeline on real image — too slow under Miri fn test_visual_database_add_keyframe_preserves_caller_built_index() { // If the caller pre-built the index, add_keyframe must not rebuild. // Verified by the fact that after a successful pre-build the keyframe diff --git a/docs/miri.md b/docs/miri.md index 5664000..a1c8450 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -41,6 +41,38 @@ covers the modules where manual buffer math and `unsafe` live: - **Integration tests under `tests/`** that depend on FFI (e.g. `kpm_regression`, `cross_stack_parity`, `absolute_corner_error` in `dual-mode`) — these need the C++ baseline to run. +- **Heavy algorithmic / pipeline tests** annotated with + `#[cfg_attr(miri, ignore)]` (see next section). + +## Tests skipped under Miri (`#[cfg_attr(miri, ignore)]`) + +Some unit tests run full pipelines — BHC tree construction over hundreds +of descriptors, DoG keypoint detection on real benchmark images, full +`ar2_gen_feature_map` runs. Native `cargo test` finishes them in +milliseconds because the code is compiled and parallelized. Miri +interprets MIR single-threaded, so the same tests can take 30+ minutes +each — and they exercise no `unsafe` boundary that targeted unit tests +don't already cover (#194). + +We annotate these with `#[cfg_attr(miri, ignore)]`: + +```rust +#[test] +#[cfg_attr(miri, ignore)] // #194: full pipeline — too slow under Miri +fn test_heavy_pipeline() { ... } +``` + +Effect: the test still runs under regular `cargo test`; it's skipped +only under `cargo miri test`. Targeted unit tests on `unsafe` +boundaries (`hamming_distance_*`, descriptor pack/unpack, byteorder +reads, image_proc indexing) stay enabled — those are the ones Miri +actually validates. + +To run a Miri-ignored test locally for investigation: + +```bash +cargo +nightly miri test -p webarkitlib-rs --lib -- --ignored +``` ## Running Miri locally From 55e0f464e49aaac22ccf102c3b8d7eecba742c01 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sat, 13 Jun 2026 10:49:56 +0200 Subject: [PATCH 20/40] perf(ci): annotate remaining real-image tests (#194 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First Miri run on PR #195 still hit GitHub Actions' 6-hour ceiling. Timestamps in the log show clustering tests finished in 15 seconds (annotations worked), but kpm/freak/descriptor.rs tests took ~2.5h each — they load found.jpg and run FREAK extraction. Original audit missed the `load_grayscale()` pattern. This commit annotates every remaining test that touches found.jpg / img.jpg: * kpm/freak/descriptor.rs: 5 tests (test_freak_descriptor_length_one_keypoint and 4 siblings) * kpm/freak/visual_database.rs: 4 tests (test_visual_database_add_and_query_same_image, query_different_image_returns_no_match, add_same_id_returns_err, erase_removes_keyframe) * kpm/rust_backend.rs: 3 tests (test_rust_freak_matcher_implements_backend, extract_features, add_freak_features) Audit method this time: `grep -rn load_grayscale` across the whole crate to enumerate every real-image test before annotating, so we catch them all in one pass. test_dual_mode_no_divergence_on_pinball stays unannotated — already cfg-gated to `dual-mode` which Miri CI does not enable. Verified locally: cargo test -p webarkitlib-rs --lib still passes all 419 tests. Co-Authored-By: Claude Opus 4.7 --- crates/core/src/kpm/freak/descriptor.rs | 5 +++++ crates/core/src/kpm/freak/visual_database.rs | 4 ++++ crates/core/src/kpm/rust_backend.rs | 3 +++ 3 files changed, 12 insertions(+) diff --git a/crates/core/src/kpm/freak/descriptor.rs b/crates/core/src/kpm/freak/descriptor.rs index b2b6e0c..794d8cd 100644 --- a/crates/core/src/kpm/freak/descriptor.rs +++ b/crates/core/src/kpm/freak/descriptor.rs @@ -337,6 +337,7 @@ mod tests { // ── Length / shape ──────────────────────────────────────────────── #[test] + #[cfg_attr(miri, ignore)] // #194: real-image FREAK extraction — too slow under Miri fn test_freak_descriptor_length_one_keypoint() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_pyramid(&img); @@ -348,6 +349,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image FREAK extraction — too slow under Miri fn test_freak_descriptor_length_multiple_keypoints() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_pyramid(&img); @@ -361,6 +363,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image FREAK extraction — too slow under Miri fn test_freak_descriptor_padding_bytes_are_zero() { // Bytes 84..96 of each descriptor must be zero (matches C++ store layout). let img = load_grayscale("../../benchmarks/data/found.jpg"); @@ -382,6 +385,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image FREAK extraction — too slow under Miri fn test_freak_descriptor_empty_input() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_pyramid(&img); @@ -391,6 +395,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: real-image FREAK extraction (×2) — too slow under Miri fn test_freak_descriptor_is_reproducible() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let pyr = build_pyramid(&img); diff --git a/crates/core/src/kpm/freak/visual_database.rs b/crates/core/src/kpm/freak/visual_database.rs index a7a4414..b399811 100644 --- a/crates/core/src/kpm/freak/visual_database.rs +++ b/crates/core/src/kpm/freak/visual_database.rs @@ -887,6 +887,7 @@ mod tests { // ----------------------------------------------------------------- #[test] + #[cfg_attr(miri, ignore)] // #194: full pipeline + BHC index — too slow under Miri fn test_visual_database_add_and_query_same_image() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut db = VisualDatabase::new().expect("new"); @@ -910,6 +911,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pipeline + BHC index — too slow under Miri fn test_visual_database_query_different_image_returns_no_match() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let blank = synthetic_blank(640, 480); @@ -930,6 +932,7 @@ mod tests { // ----------------------------------------------------------------- #[test] + #[cfg_attr(miri, ignore)] // #194: full pipeline + BHC index — too slow under Miri fn test_visual_database_add_same_id_returns_err() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut db = VisualDatabase::new().expect("new"); @@ -939,6 +942,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // #194: full pipeline + BHC index — too slow under Miri fn test_visual_database_erase_removes_keyframe() { let img = load_grayscale("../../benchmarks/data/found.jpg"); let mut db = VisualDatabase::new().expect("new"); diff --git a/crates/core/src/kpm/rust_backend.rs b/crates/core/src/kpm/rust_backend.rs index 2ad919f..42acae6 100644 --- a/crates/core/src/kpm/rust_backend.rs +++ b/crates/core/src/kpm/rust_backend.rs @@ -596,6 +596,7 @@ mod tests { /// Issue #141 required test: add a reference image, query with a /// different image, expect a successful match. #[test] + #[cfg_attr(miri, ignore)] // #194: full FREAK matcher pipeline on real image pair — too slow under Miri fn test_rust_freak_matcher_implements_backend() { let (ref_img, rw, rh) = load_grayscale("../../benchmarks/data/found.jpg"); let (qry_img, qw, qh) = load_grayscale("../../benchmarks/data/img.jpg"); @@ -617,6 +618,7 @@ mod tests { /// Covers the `extract_features` path used by /// `KpmRefDataSet::generate()`. Detects features without storing. #[test] + #[cfg_attr(miri, ignore)] // #194: full FREAK extract on real image — too slow under Miri fn test_rust_freak_matcher_extract_features() { let (img, w, h) = load_grayscale("../../benchmarks/data/found.jpg"); let mut matcher = RustFreakMatcher::new(w as i32, h as i32).unwrap(); @@ -676,6 +678,7 @@ mod tests { /// insertion). Extracts features from `found.jpg`, feeds them back as /// the database, queries with the same image, expects a self-match. #[test] + #[cfg_attr(miri, ignore)] // #194: full FREAK extract + match cycle — too slow under Miri fn test_rust_freak_matcher_add_freak_features() { let (ref_img, rw, rh) = load_grayscale("../../benchmarks/data/found.jpg"); let mut matcher = RustFreakMatcher::new(rw as i32, rh as i32).unwrap(); From e3d31b7a16361e73c2a982cf6b735cf00cbfcea9 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sat, 13 Jun 2026 12:15:18 +0200 Subject: [PATCH 21/40] test(homography): widen cauchy_cost tolerance to 1e-5 for Miri (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #195's Miri run failed on `test_cauchy_cost_known_values`: assertion failed: approx_eq(c2, 26.0_f32.ln(), 1e-6) This is not UB. Miri intentionally makes `f32::ln` non-deterministic by a few ULPs across calls within a single run. The test computes `.ln()` twice — once inside `cauchy_cost_2d`, once on the assert's RHS — and the two values can diverge by more than the 1e-6 native tolerance under Miri. Widen the tolerance to 1e-5, which: * still tests mathematical correctness (f32 has ~7 decimal digits of precision at value 3.26 — well within 1e-5), * keeps the test running under both native cargo test AND Miri (vs. the alternative of `#[cfg_attr(miri, ignore)]` which would shrink Miri's coverage), * documents in-source why the tolerance is wider than the sibling scalar test (which only calls .ln() once and is fine at 1e-6). Audit: this is the only homography.rs assertion that compares .ln()/.sin()/.sqrt() outputs at a tolerance tight enough to trip Miri's transcendental non-determinism. Other Cauchy tests assert exact zero (test_cauchy_cost_zero_residual, the projective tests), which is FP-impl-independent. Verified locally: 42 homography tests pass. Co-Authored-By: Claude Opus 4.7 --- crates/core/src/kpm/freak/homography.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/core/src/kpm/freak/homography.rs b/crates/core/src/kpm/freak/homography.rs index e1e26c0..e55a901 100644 --- a/crates/core/src/kpm/freak/homography.rs +++ b/crates/core/src/kpm/freak/homography.rs @@ -2358,8 +2358,14 @@ mod tests { assert!(approx_eq(c, std::f32::consts::LN_2, 1e-6)); // 2D with x=3, y=4, 1/σ²=1: log(1 + (9+16)) = log(26) ≈ 3.2580965 + // Tolerance is 1e-5 (not 1e-6) because Miri's f32::ln impl is + // non-deterministic by a few ULPs across calls within a single + // run — the function calls .ln() internally and the assert calls + // it again on the RHS, so the two results can diverge slightly. + // 1e-5 is still well within f32 precision (~7 decimal digits at + // this magnitude) and tests mathematical correctness. let c2 = cauchy_cost_2d(3.0, 4.0, 1.0); - assert!(approx_eq(c2, 26.0_f32.ln(), 1e-6)); + assert!(approx_eq(c2, 26.0_f32.ln(), 1e-5)); } #[test] From 960d7ae8494445ef05875be751cae5ab0e30a730 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sat, 13 Jun 2026 13:50:08 +0200 Subject: [PATCH 22/40] ci(miri): enable -Zmiri-ignore-leaks for rayon thread pool (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #195's Miri run failed at process teardown: error: the main thread terminated without waiting for all remaining threads This is rayon's global thread pool: it spawns worker threads up-front and never joins them. When the test binary exits, those threads are still alive, and Miri's default leak check flags it. Add -Zmiri-ignore-leaks. This is the standard configuration for any rayon-using test suite (the rayon docs and Miri docs both recommend it). It does NOT weaken UB detection on the heap-allocation / aliasing / provenance side — only the leak check is disabled. After this, PR #195's Miri job should finally go green end-to-end. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 6 +++++- docs/miri.md | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f07631..9d8f64e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -214,7 +214,11 @@ jobs: env: # -Zmiri-disable-isolation: allow filesystem ops so tests that # use `tempfile` (e.g. ar2::feature_set roundtrip) can run. - MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation + # -Zmiri-ignore-leaks: rayon's global thread pool spawns workers + # that aren't joined at process exit. Miri flags that as a leak + # by default; ignoring it is the recommended setting for rayon- + # using test suites and does NOT weaken UB detection. + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation -Zmiri-ignore-leaks submodule-drift-check: runs-on: ubuntu-latest diff --git a/docs/miri.md b/docs/miri.md index a1c8450..5b61cc8 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -106,6 +106,12 @@ MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test -p webarkitlib-rs # -Zmiri-disable-isolation (already set in CI — lets tests using # tempfile / disk I/O run; e.g. ar2::feature_set roundtrip) MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -p webarkitlib-rs --lib + +# -Zmiri-ignore-leaks (already set in CI — rayon's global thread pool +# spawns workers that aren't joined at exit; Miri flags that as a leak +# by default. Ignoring it is standard for rayon-using suites and does +# NOT weaken UB detection.) +MIRIFLAGS="-Zmiri-ignore-leaks" cargo +nightly miri test -p webarkitlib-rs --lib ``` If a finding is intentional (e.g. a known-safe `transmute` pattern that From 66f836b7d5c1bc959a543f77031e96d96b21e3f0 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sat, 13 Jun 2026 16:34:19 +0200 Subject: [PATCH 23/40] ci(miri): promote to required gate, split workflow, add README badge (#182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final step in the #182 plan. The Miri safety net is now green end-to- end (PR #195 closed out the runtime issues and the one real UB finding in #192/#193), so flip it from advisory to required. Changes: * Split the Miri job from `.github/workflows/ci.yml` into its own workflow file `.github/workflows/miri.yml`. This gives it a dedicated status badge (GitHub workflow badges are workflow-scoped, not job- scoped) and lets the trigger rules live at workflow level instead of inside an `if:` guard. * Drop `continue-on-error: true` — Miri failures now block PR merges, same as every other CI job. * Move the trigger from the job-level `if:` to a workflow-level `on:` block targeting pushes and PRs to `dev`/`main`. * Add a Miri status badge to README.md, next to the existing CI badge. * Update `docs/miri.md`: reference the new workflow path, refresh the "CI gate status" section to describe the required-gate state, and reference #192 as the first real UB the net caught. No code changes — workflow + docs only. `cargo fmt --check` clean. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 60 ++----------------------------- .github/workflows/miri.yml | 73 ++++++++++++++++++++++++++++++++++++++ README.md | 1 + docs/miri.md | 16 ++++----- 4 files changed, 85 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/miri.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d8f64e..bf659fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,63 +162,9 @@ jobs: --test absolute_corner_error \ --features dual-mode -- --nocapture - # Miri UB validation (#182): runs the pure-Rust lib under the MIR - # interpreter to catch use-after-free, OOB reads, invalid `unsafe` - # invariants, uninitialized-memory reads, and reference-aliasing - # violations that escape regular tests. Excludes `ffi-backend` - # (Miri can't execute C++) and `simd-x86-*` (Miri's x86 SIMD support - # is incomplete). Scalar fallbacks ARE validated. - # - # `continue-on-error: true` initially — will be ratcheted to required - # once initial UB findings are resolved via follow-up PRs tracked - # under #182. See docs/miri.md for scope, exclusions, local-run - # instructions, and the nightly-pin bump procedure. - miri: - name: miri (pure-Rust UB validation) - runs-on: ubuntu-latest - # Restrict to PRs against dev/main only — Miri is 5–30x slower than - # cargo test and the pre-merge gate is where it earns its keep. - if: github.event_name == 'pull_request' && (github.base_ref == 'dev' || github.base_ref == 'main') - continue-on-error: true - env: - # Pinned nightly — bump manually if Miri breaks on this date or - # every ~3 months for freshness. See docs/miri.md. - MIRI_NIGHTLY: nightly-2026-06-01 - steps: - - uses: actions/checkout@v5 - - - name: Install pinned nightly + Miri - run: | - rustup toolchain install $MIRI_NIGHTLY --component miri --profile minimal - rustup default $MIRI_NIGHTLY - cargo miri setup - - - name: Rust Cache - uses: Swatinem/rust-cache@v2 - with: - key: miri-${{ env.MIRI_NIGHTLY }} - shared-key: miri - - # NOTES on MIRIFLAGS: - # * -Zmiri-tree-borrows: use the Tree Borrows aliasing model instead - # of the default Stacked Borrows. Stacked Borrows trips inside - # crossbeam-epoch (a transitive dep of rayon, reached from our - # ar2::feature_map par_chunks_mut) on patterns that are sound in - # practice. Tree Borrows accepts them while still catching real - # UB in our own code. - # * -Zmiri-strict-provenance is intentionally NOT enabled — it also - # trips inside crossbeam-epoch (integer-to-pointer casts). Re- - # evaluate once the ecosystem migrates. See docs/miri.md. - - name: Run Miri (pure-Rust, lib targets only) - run: cargo miri test -p webarkitlib-rs --lib - env: - # -Zmiri-disable-isolation: allow filesystem ops so tests that - # use `tempfile` (e.g. ar2::feature_set roundtrip) can run. - # -Zmiri-ignore-leaks: rayon's global thread pool spawns workers - # that aren't joined at process exit. Miri flags that as a leak - # by default; ignoring it is the recommended setting for rayon- - # using test suites and does NOT weaken UB detection. - MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation -Zmiri-ignore-leaks + # Miri UB validation (#182) lives in its own workflow at + # .github/workflows/miri.yml — split out so it can carry a dedicated + # status badge and run on its own cadence. submodule-drift-check: runs-on: ubuntu-latest diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml new file mode 100644 index 0000000..5f02cc3 --- /dev/null +++ b/.github/workflows/miri.yml @@ -0,0 +1,73 @@ +name: Miri + +# Miri UB validation (#182): runs the pure-Rust lib under the MIR +# interpreter to catch use-after-free, OOB reads, invalid `unsafe` +# invariants, uninitialized-memory reads, and reference-aliasing +# violations that escape regular tests. +# +# Excludes `ffi-backend` (Miri can't execute C++) and `simd-x86-*` +# (Miri's x86 SIMD support is incomplete). Scalar fallbacks ARE +# validated. +# +# Heavy algorithmic / pipeline tests are skipped via +# `#[cfg_attr(miri, ignore)]` (#194) — they don't exercise unsafe +# boundaries and would dominate runtime. See docs/miri.md. +# +# Required gate: this workflow MUST pass for PRs targeting dev/main. + +on: + pull_request: + branches: + - dev + - main + push: + branches: + - dev + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + miri: + name: miri (pure-Rust UB validation) + runs-on: ubuntu-latest + env: + # Pinned nightly — bump manually if Miri breaks on this date or + # every ~3 months for freshness. See docs/miri.md. + MIRI_NIGHTLY: nightly-2026-06-01 + steps: + - uses: actions/checkout@v5 + + - name: Install pinned nightly + Miri + run: | + rustup toolchain install $MIRI_NIGHTLY --component miri --profile minimal + rustup default $MIRI_NIGHTLY + cargo miri setup + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: miri-${{ env.MIRI_NIGHTLY }} + shared-key: miri + + # NOTES on MIRIFLAGS: + # * -Zmiri-tree-borrows: use the Tree Borrows aliasing model instead + # of the default Stacked Borrows. Stacked Borrows trips inside + # crossbeam-epoch (a transitive dep of rayon, reached from our + # ar2::feature_map par_chunks_mut) on patterns that are sound in + # practice. Tree Borrows accepts them while still catching real + # UB in our own code. + # * -Zmiri-disable-isolation: allow filesystem ops so tests that + # use `tempfile` (e.g. ar2::feature_set roundtrip) can run. + # * -Zmiri-ignore-leaks: rayon's global thread pool spawns workers + # that aren't joined at process exit. Miri flags that as a leak + # by default; ignoring it is the recommended setting for rayon- + # using test suites and does NOT weaken UB detection. + # * -Zmiri-strict-provenance is intentionally NOT enabled — it also + # trips inside crossbeam-epoch (integer-to-pointer casts). Re- + # evaluate once the ecosystem migrates. See docs/miri.md. + - name: Run Miri (pure-Rust, lib targets only) + run: cargo miri test -p webarkitlib-rs --lib + env: + MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation -Zmiri-ignore-leaks diff --git a/README.md b/README.md index 7684391..aebd804 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![WebARKitLib-rs](./assets/WebARKitLib-Rust-banner.jpg) [![CI](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/ci.yml) +[![Miri](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/miri.yml/badge.svg)](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/miri.yml) [![codecov](https://codecov.io/gh/webarkit/WebARKitLib-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/webarkit/WebARKitLib-rs) [![Crates.io](https://img.shields.io/crates/v/webarkitlib-rs.svg)](https://crates.io/crates/webarkitlib-rs) [![npm](https://img.shields.io/npm/v/@webarkit/webarkitlib-wasm.svg)](https://www.npmjs.com/package/@webarkit/webarkitlib-wasm) diff --git a/docs/miri.md b/docs/miri.md index 5b61cc8..c5367e9 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -121,7 +121,7 @@ understanding the report. ## Bumping the nightly pin -The pin lives in `.github/workflows/ci.yml` as the `MIRI_NIGHTLY` job +The pin lives in `.github/workflows/miri.yml` as the `MIRI_NIGHTLY` job env var. Bump when: - CI hits a known nightly Miri regression (link the upstream issue in @@ -133,7 +133,7 @@ Procedure: 1. Pick a recent known-good nightly from (filter on `miri`). -2. Update `MIRI_NIGHTLY` in `ci.yml` AND the "Running locally" snippet +2. Update `MIRI_NIGHTLY` in `miri.yml` AND the "Running locally" snippet in this file. 3. Open a PR; CI will validate the new pin. @@ -170,10 +170,10 @@ spot-check their own new `unsafe` code locally with ## CI gate status -The Miri job currently runs with `continue-on-error: true` (advisory). -It will be ratcheted to a required gate once initial UB findings -surfaced by the first runs are resolved via follow-up PRs tracked under -[#182](https://github.com/webarkit/WebARKitLib-rs/issues/182). +The Miri job is a **required gate** for PRs targeting `dev` and `main`, +runs in its own workflow at `.github/workflows/miri.yml`, and surfaces +via a dedicated badge in the project README. -Each finding gets its own fix PR — never a mega-PR — mirroring the #180 -cleanup series pattern. +Each future finding gets its own fix PR — never a mega-PR — mirroring +the #180 cleanup series pattern (and #192 from this safety net's first +run, which uncovered a real unaligned-transmute UB). From 97d6bfbfb974eeccb5f302788398600f6ac344ec Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sun, 14 Jun 2026 12:52:02 +0200 Subject: [PATCH 24/40] ci(miri): skip docs-only changes via paths-ignore + same-name shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that `protect-main` / `protect-dev` rulesets mark the Miri job as a required check, docs-only PRs (README typos, docs/ updates, asset swaps) would block at merge because the real Miri workflow's `paths-ignore` makes the check "missing." Branch protection can't tell the difference between "skipped because nothing to validate" and "didn't run for some other reason." GitHub's documented workaround: a companion workflow that declares the same `name:` and the same job `name:`, triggers on exactly the paths the real workflow ignores, and emits a passing check. Branch protection matches by name and is satisfied either way. Changes: * miri.yml: add `paths-ignore` excluding `**/*.md`, `docs/**`, `assets/**`, `LICENSE*`, `.gitignore`, `.gitattributes`. * miri-skip.yml: new file. Same `name: Miri` and job `name: miri (pure-Rust UB validation)`. Triggers on the inverse path list. Single step that just `echo`s a "skipped — docs only" message. * docs/miri.md: document the shim pattern and the "keep both path lists in sync" rule. Effect: README-only PRs no longer spin up the nightly toolchain (saves ~5 minutes of CI per docs PR) while still satisfying the required- check rule. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/miri-skip.yml | 54 +++++++++++++++++++++++++++++++++ .github/workflows/miri.yml | 14 +++++++++ docs/miri.md | 10 ++++++ 3 files changed, 78 insertions(+) create mode 100644 .github/workflows/miri-skip.yml diff --git a/.github/workflows/miri-skip.yml b/.github/workflows/miri-skip.yml new file mode 100644 index 0000000..3544a35 --- /dev/null +++ b/.github/workflows/miri-skip.yml @@ -0,0 +1,54 @@ +name: Miri + +# Skip-shim for the Miri required gate. +# +# The real Miri workflow (.github/workflows/miri.yml) uses `paths-ignore` +# to skip docs-only changes. But the `protect-main` / `protect-dev` +# rulesets require a check named `miri (pure-Rust UB validation)` to +# pass on every PR — including docs-only ones. Without this shim a +# README-only PR would have a "missing" required check and could not +# merge. +# +# This workflow declares the same `name:` and the same job `name:` as +# miri.yml, but triggers ONLY on the paths the real workflow ignores. +# It does no actual work — it just emits a passing check with the +# expected name so branch protection is satisfied. +# +# IMPORTANT: keep the workflow `name:` and job `name:` here in sync +# with miri.yml. GitHub matches required checks on those names +# exactly. + +on: + pull_request: + branches: + - dev + - main + paths: + - '**/*.md' + - 'docs/**' + - 'assets/**' + - 'LICENSE*' + - '.gitignore' + - '.gitattributes' + push: + branches: + - dev + - main + paths: + - '**/*.md' + - 'docs/**' + - 'assets/**' + - 'LICENSE*' + - '.gitignore' + - '.gitattributes' + +jobs: + miri: + name: miri (pure-Rust UB validation) + runs-on: ubuntu-latest + steps: + - name: Skip — docs-only change, no Rust touched + run: | + echo "This change touches only docs / assets / LICENSE." + echo "The real Miri workflow ran zero seconds because there" + echo "is no Rust code change to validate." diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 5f02cc3..8487b48 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -20,10 +20,24 @@ on: branches: - dev - main + paths-ignore: + - '**/*.md' + - 'docs/**' + - 'assets/**' + - 'LICENSE*' + - '.gitignore' + - '.gitattributes' push: branches: - dev - main + paths-ignore: + - '**/*.md' + - 'docs/**' + - 'assets/**' + - 'LICENSE*' + - '.gitignore' + - '.gitattributes' env: CARGO_TERM_COLOR: always diff --git a/docs/miri.md b/docs/miri.md index c5367e9..7aeccee 100644 --- a/docs/miri.md +++ b/docs/miri.md @@ -174,6 +174,16 @@ The Miri job is a **required gate** for PRs targeting `dev` and `main`, runs in its own workflow at `.github/workflows/miri.yml`, and surfaces via a dedicated badge in the project README. +Docs-only changes (`**/*.md`, `docs/**`, `assets/**`, `LICENSE*`, +`.gitignore`, `.gitattributes`) are skipped via `paths-ignore` in the +real Miri workflow. A companion shim workflow at +`.github/workflows/miri-skip.yml` runs on exactly those paths and +emits a passing check with the same name (`miri (pure-Rust UB +validation)`), so branch protection's required-check rule is +satisfied without spinning up the nightly toolchain for a README typo. +If you change the `paths-ignore` list in `miri.yml`, mirror the change +to the `paths` list in `miri-skip.yml`. + Each future finding gets its own fix PR — never a mega-PR — mirroring the #180 cleanup series pattern (and #192 from this safety net's first run, which uncovered a real unaligned-transmute UB). From db70e091fdf4da72e288e780c63d5fb75fa17c67 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 15 Jun 2026 18:33:37 +0200 Subject: [PATCH 25/40] =?UTF-8?q?perf(kpm):=20Gaussian=20scale-space=20pyr?= =?UTF-8?q?amid=20=E2=80=94=20benchmark=20+=20rayon=20parallelization=20(#?= =?UTF-8?q?209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(kpm): criterion benchmark for Gaussian scale-space pyramid (#200) Establish the scalar baseline for the Gaussian pyramid SIMD series (#200 -> #201 -> #202). Unlike the BoxFilterPyramid8u trilogy (#131-133, dead code per #203), GaussianScaleSpacePyramid is the pyramid the KPM/FREAK detector actually uses via DoGScaleInvariantDetector. - Promote the three hot helpers to `pub` with scalar-only dispatchers, following the `pyramid::downsample` precedent from #131: binomial_4th_order_u8_to_f32{,_scalar} binomial_4th_order_f32_to_f32{,_scalar} downsample_bilinear_f32{,_scalar} `build` calls the dispatchers, so #201/#202 only fill in SIMD variants and wire dispatch. Behavior unchanged (dual-mode C++ parity test still green). - Add crates/core/benches/gaussian_pyramid_bench.rs benchmarking each scalar helper plus end-to-end `build` (num_octaves from num_octaves_for(w, h, 8), matching rust_backend) at 640x480, 1280x720 and 1920x1080 with fixed-seed PRNG input. - Wire `[[bench]] name = "gaussian_pyramid_bench"` into Cargo.toml. Baseline (median, this machine): binomial_f32 640x480 710 us | 1280x720 2.04 ms | 1920x1080 5.26 ms binomial_u8 640x480 546 us | 1280x720 1.60 ms | 1920x1080 3.38 ms bilinear 640x480 138 us | 1280x720 381 us | 1920x1080 1.16 ms build (e2e) 640x480 3.11 ms | 1280x720 9.95 ms | 1920x1080 26.0 ms Co-Authored-By: Claude Fable 5 * perf(kpm): parallelize Gaussian pyramid filter passes with rayon (#207) (#208) The binomial filter and bilinear downsample of GaussianScaleSpacePyramid are memory-bandwidth bound, so the SIMD attempt (#201/#206) gave ~1.0× — a single core can't saturate DRAM bandwidth. Multiple cores can, so parallelize over rows with rayon (native; wasm stays single-threaded). - Per-row helpers (binomial_{h,v}_row_{f32,u8}, bilinear_row_f32) are the single source of truth for the arithmetic; serial and rayon paths both call them, so output is bit-for-bit identical regardless of threading (rows are independent). Validated by serial-vs-rayon parity tests + the existing dual-mode C++ parity test (scalar == C++, rayon == scalar). - Added *_rayon variants (par_chunks_mut over rows) for both binomial variants and the bilinear downsample, cfg(not(wasm32)). - Dispatchers pick rayon above PARALLEL_MIN_PIXELS (600k px), else serial; wasm always serial. The threshold keeps <=480p serial (where thread overhead regressed the binomial filter) and parallelizes 720p+. - Bench extended with a `rayon` arm per group. Speedup (scalar -> rayon, same-run medians, this machine): binomial_f32 720p 2.75->1.71 ms (1.61x) | 1080p 4.33->2.84 ms (1.52x) binomial_u8 720p 1.69->1.42 ms (1.19x) | 1080p 3.55->2.36 ms (1.50x) bilinear 720p 0.40->0.17 ms (2.36x) | 1080p 1.01->0.57 ms (1.77x) (<=480p stays serial by design.) Closes #207. Co-authored-by: Claude Fable 5 * test(kpm): skip large rayon-parity tests under Miri; drop redundant h alias The #207 rayon parity tests build pyramids at up to 800×800 — astronomically slow under Miri's interpreter (the run was stuck for 2h). #207 adds no unsafe (rayon is safe), so Miri has nothing to validate there. Annotate the four large-image tests with #[cfg_attr(miri, ignore)], matching the existing #194 idiom; they still run natively. Also replace the redundant `let h = height;` alias in binomial_v_row_{f32,u8} with `height` directly — the elided binding showed as 2 uncovered lines in the tarpaulin/codecov patch report. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- crates/core/Cargo.toml | 4 + crates/core/benches/gaussian_pyramid_bench.rs | 187 ++++++ crates/core/src/kpm/freak/gaussian_pyramid.rs | 583 +++++++++++++----- 3 files changed, 627 insertions(+), 147 deletions(-) create mode 100644 crates/core/benches/gaussian_pyramid_bench.rs diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 980ced0..c8af8cb 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -153,3 +153,7 @@ required-features = ["simd-x86-sse41"] name = "feature_map_bench" harness = false required-features = ["log-helpers"] + +[[bench]] +name = "gaussian_pyramid_bench" +harness = false diff --git a/crates/core/benches/gaussian_pyramid_bench.rs b/crates/core/benches/gaussian_pyramid_bench.rs new file mode 100644 index 0000000..451b7b0 --- /dev/null +++ b/crates/core/benches/gaussian_pyramid_bench.rs @@ -0,0 +1,187 @@ +/* + * gaussian_pyramid_bench.rs + * WebARKitLib-rs + * + * This file is part of WebARKitLib-rs - WebARKit. + * + * WebARKitLib-rs is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WebARKitLib-rs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with WebARKitLib-rs. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2026 WebARKit. + * + * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt + * + */ + +//! Criterion baseline for the **Gaussian scale-space pyramid** (#200) — the +//! pyramid the KPM/FREAK detector actually uses (via +//! `DoGScaleInvariantDetector`). +//! +//! Establishes the scalar baseline so the SIMD work can prove its speedup: +//! the binomial 5-tap filter (#201) and the bilinear downsample (#202). +//! +//! Groups, each at 640×480, 1280×720 and 1920×1080: +//! +//! - `gaussian_binomial_u8`: `binomial_4th_order_u8_to_f32_scalar` (octave 0). +//! - `gaussian_binomial_f32`: `binomial_4th_order_f32_to_f32_scalar` (the +//! dominant per-octave cost — runs 3× per octave). +//! - `gaussian_bilinear`: `downsample_bilinear_f32_scalar` (between octaves). +//! - `gaussian_build`: end-to-end `GaussianScaleSpacePyramid::build` with +//! `num_octaves` from `num_octaves_for(w, h, 8)`, matching the real KPM +//! backend (`rust_backend.rs`). +//! +//! Inputs are filled deterministically with a fixed-seed PRNG so runs are +//! comparable across machines and across PRs in the series. + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use purecv::core::Matrix; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use std::hint::black_box; +use webarkitlib_rs::kpm::freak::gaussian_pyramid::{ + binomial_4th_order_f32_to_f32_scalar, binomial_4th_order_u8_to_f32_scalar, + downsample_bilinear_f32_scalar, +}; +use webarkitlib_rs::kpm::freak::{num_octaves_for, GaussianScaleSpacePyramid}; + +#[cfg(not(target_arch = "wasm32"))] +use webarkitlib_rs::kpm::freak::gaussian_pyramid::{ + binomial_4th_order_f32_to_f32_rayon, binomial_4th_order_u8_to_f32_rayon, + downsample_bilinear_f32_rayon, +}; + +/// Typical AR camera input resolutions as `(width, height)`. +const SIZES: &[(usize, usize)] = &[(640, 480), (1280, 720), (1920, 1080)]; + +/// `min_size` for `num_octaves_for`, matching the real KPM backend. +const MIN_COARSE_SIZE: usize = 8; + +/// Deterministic grayscale `Matrix` (`rows × cols`), fixed-seed PRNG. +fn random_u8(rows: usize, cols: usize) -> Matrix { + let mut rng = StdRng::seed_from_u64(0x6A05_51A2); + let data: Vec = (0..rows * cols).map(|_| rng.random::()).collect(); + Matrix::::from_vec(rows, cols, 1, data) +} + +/// Deterministic `Matrix` with values in `[0, 256)`, fixed-seed PRNG. +fn random_f32(rows: usize, cols: usize) -> Matrix { + let mut rng = StdRng::seed_from_u64(0x6A05_51A3); + let data: Vec = (0..rows * cols) + .map(|_| rng.random::() as f32) + .collect(); + Matrix::::from_vec(rows, cols, 1, data) +} + +fn bench_binomial_u8(c: &mut Criterion) { + let mut group = c.benchmark_group("gaussian_binomial_u8"); + for &(w, h) in SIZES { + let src = random_u8(h, w); + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("scalar", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| binomial_4th_order_u8_to_f32_scalar(black_box(src))), + ); + + #[cfg(not(target_arch = "wasm32"))] + group.bench_with_input( + BenchmarkId::new("rayon", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| binomial_4th_order_u8_to_f32_rayon(black_box(src))), + ); + } + group.finish(); +} + +fn bench_binomial_f32(c: &mut Criterion) { + let mut group = c.benchmark_group("gaussian_binomial_f32"); + for &(w, h) in SIZES { + let src = random_f32(h, w); + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("scalar", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| binomial_4th_order_f32_to_f32_scalar(black_box(src))), + ); + + #[cfg(not(target_arch = "wasm32"))] + group.bench_with_input( + BenchmarkId::new("rayon", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| binomial_4th_order_f32_to_f32_rayon(black_box(src))), + ); + } + group.finish(); +} + +fn bench_bilinear(c: &mut Criterion) { + let mut group = c.benchmark_group("gaussian_bilinear"); + for &(w, h) in SIZES { + let src = random_f32(h, w); + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("scalar", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| downsample_bilinear_f32_scalar(black_box(src))), + ); + + #[cfg(not(target_arch = "wasm32"))] + group.bench_with_input( + BenchmarkId::new("rayon", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| downsample_bilinear_f32_rayon(black_box(src))), + ); + } + group.finish(); +} + +fn bench_build(c: &mut Criterion) { + let mut group = c.benchmark_group("gaussian_build"); + for &(w, h) in SIZES { + let src = random_u8(h, w); + let n_oct = num_octaves_for(w, h, MIN_COARSE_SIZE); + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("scalar", format!("{w}x{h}")), + &src, + |bencher, src| { + bencher.iter(|| { + let mut p = GaussianScaleSpacePyramid::new(n_oct); + p.build(black_box(src)).expect("build should succeed"); + p + }) + }, + ); + } + group.finish(); +} + +criterion_group!( + benches, + bench_binomial_u8, + bench_binomial_f32, + bench_bilinear, + bench_build +); +criterion_main!(benches); diff --git a/crates/core/src/kpm/freak/gaussian_pyramid.rs b/crates/core/src/kpm/freak/gaussian_pyramid.rs index 153a13c..49a60ea 100644 --- a/crates/core/src/kpm/freak/gaussian_pyramid.rs +++ b/crates/core/src/kpm/freak/gaussian_pyramid.rs @@ -269,204 +269,384 @@ pub fn num_octaves_for(width: usize, height: usize, min_size: usize) -> usize { } // ───────────────────────────────────────────────────────────────────────── -// Private filter helpers — byte-for-byte port of C++ `binomial_4th_order` -// and `downsample_bilinear` from `gaussian_scale_space_pyramid.cpp`. +// Filter helpers — byte-for-byte port of C++ `binomial_4th_order` and +// `downsample_bilinear` from `gaussian_scale_space_pyramid.cpp`. +// +// The per-row helpers below are the single source of truth for the +// arithmetic; the serial scalar paths and the rayon paths (#207) both call +// them, so output is bit-for-bit identical regardless of threading +// (validated by the `dual-mode` C++ parity test). rows are independent, so +// parallelizing over them changes nothing numerically. // ───────────────────────────────────────────────────────────────────────── +/// `1 / 256` normalization applied on the vertical pass. Exact in f32. +const INV_256: f32 = 1.0 / 256.0; + +/// Below this pixel count the rayon path's thread-pool overhead outweighs the +/// win, so the dispatchers run serially (CLAUDE.md §3: "don't parallelize +/// small loops"). Benchmarking showed the binomial filter regresses at +/// 640×480 (~0.3 MPx) but scales ~1.5× at 1280×720 (~0.9 MPx); this +/// conservative threshold keeps ≤480p serial and is a machine-dependent +/// heuristic, not a hard tuning point. +const PARALLEL_MIN_PIXELS: usize = 600_000; + +// ── f32 → f32 per-row helpers ──────────────────────────────────────────── + +/// Horizontal pass of one row, f32 → f32. `s` and `t` are the row slices of +/// the source and the temp buffer. +#[inline] +fn binomial_h_row_f32(s: &[f32], t: &mut [f32], width: usize) { + t[0] = 6.0 * s[0] + 4.0 * (s[0] + s[1]) + s[0] + s[2]; + t[1] = 6.0 * s[1] + 4.0 * (s[0] + s[2]) + s[0] + s[3]; + for col in 2..width - 2 { + t[col] = 6.0 * s[col] + 4.0 * (s[col - 1] + s[col + 1]) + s[col - 2] + s[col + 2]; + } + let c = width - 2; + t[c] = 6.0 * s[c] + 4.0 * (s[c - 1] + s[c + 1]) + s[c - 2] + s[c + 1]; + let c = width - 1; + t[c] = 6.0 * s[c] + 4.0 * (s[c - 1] + s[c]) + s[c - 2] + s[c]; +} + +/// Vertical pass of one output `row`, f32 → f32. Reads `tmp`, writes +/// `dst_row` (length `width`). Border rows (0, 1, height-2, height-1) +/// replicate edges; interior rows use the full 5-tap. +#[inline] +fn binomial_v_row_f32(tmp: &[f32], dst_row: &mut [f32], row: usize, width: usize, height: usize) { + if row == 0 { + for (col, d) in dst_row.iter_mut().enumerate() { + let p = tmp[col]; + let pp1 = tmp[width + col]; + let pp2 = tmp[2 * width + col]; + *d = (6.0 * p + 4.0 * (p + pp1) + p + pp2) * INV_256; + } + } else if row == 1 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm = tmp[col]; + let p = tmp[width + col]; + let pp1 = tmp[2 * width + col]; + let pp2 = tmp[3 * width + col]; + *d = (6.0 * p + 4.0 * (pm + pp1) + pm + pp2) * INV_256; + } + } else if row == height - 2 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(height - 4) * width + col]; + let pm1 = tmp[(height - 3) * width + col]; + let p = tmp[(height - 2) * width + col]; + let pp = tmp[(height - 1) * width + col]; + *d = (6.0 * p + 4.0 * (pm1 + pp) + pm2 + pp) * INV_256; + } + } else if row == height - 1 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(height - 3) * width + col]; + let pm1 = tmp[(height - 2) * width + col]; + let p = tmp[(height - 1) * width + col]; + *d = (6.0 * p + 4.0 * (pm1 + p) + pm2 + p) * INV_256; + } + } else { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(row - 2) * width + col]; + let pm1 = tmp[(row - 1) * width + col]; + let p = tmp[row * width + col]; + let pp1 = tmp[(row + 1) * width + col]; + let pp2 = tmp[(row + 2) * width + col]; + *d = (6.0 * p + 4.0 * (pm1 + pp1) + pm2 + pp2) * INV_256; + } + } +} + +// ── u8 → f32 per-row helpers ───────────────────────────────────────────── + +/// Horizontal pass of one row, u8 → u16 (exact integer; max 4080). +#[inline] +fn binomial_h_row_u8(s: &[u8], t: &mut [u16], width: usize) { + t[0] = 6 * s[0] as u16 + 4 * (s[0] as u16 + s[1] as u16) + (s[0] as u16 + s[2] as u16); + t[1] = 6 * s[1] as u16 + 4 * (s[0] as u16 + s[2] as u16) + (s[0] as u16 + s[3] as u16); + for col in 2..width - 2 { + t[col] = 6 * s[col] as u16 + + 4 * (s[col - 1] as u16 + s[col + 1] as u16) + + (s[col - 2] as u16 + s[col + 2] as u16); + } + let c = width - 2; + t[c] = 6 * s[c] as u16 + + 4 * (s[c - 1] as u16 + s[c + 1] as u16) + + (s[c - 2] as u16 + s[c + 1] as u16); + let c = width - 1; + t[c] = 6 * s[c] as u16 + 4 * (s[c - 1] as u16 + s[c] as u16) + (s[c - 2] as u16 + s[c] as u16); +} + +/// Vertical pass of one output `row`, u16 → f32 (sum in `u32`, then +/// `* INV_256`). +#[inline] +fn binomial_v_row_u8(tmp: &[u16], dst_row: &mut [f32], row: usize, width: usize, height: usize) { + if row == 0 { + for (col, d) in dst_row.iter_mut().enumerate() { + let p = tmp[col] as u32; + let pp1 = tmp[width + col] as u32; + let pp2 = tmp[2 * width + col] as u32; + *d = ((6 * p + 4 * (p + pp1) + p + pp2) as f32) * INV_256; + } + } else if row == 1 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm = tmp[col] as u32; + let p = tmp[width + col] as u32; + let pp1 = tmp[2 * width + col] as u32; + let pp2 = tmp[3 * width + col] as u32; + *d = ((6 * p + 4 * (pm + pp1) + pm + pp2) as f32) * INV_256; + } + } else if row == height - 2 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(height - 4) * width + col] as u32; + let pm1 = tmp[(height - 3) * width + col] as u32; + let p = tmp[(height - 2) * width + col] as u32; + let pp = tmp[(height - 1) * width + col] as u32; + *d = ((6 * p + 4 * (pm1 + pp) + pm2 + pp) as f32) * INV_256; + } + } else if row == height - 1 { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(height - 3) * width + col] as u32; + let pm1 = tmp[(height - 2) * width + col] as u32; + let p = tmp[(height - 1) * width + col] as u32; + *d = ((6 * p + 4 * (pm1 + p) + pm2 + p) as f32) * INV_256; + } + } else { + for (col, d) in dst_row.iter_mut().enumerate() { + let pm2 = tmp[(row - 2) * width + col] as u32; + let pm1 = tmp[(row - 1) * width + col] as u32; + let p = tmp[row * width + col] as u32; + let pp1 = tmp[(row + 1) * width + col] as u32; + let pp2 = tmp[(row + 2) * width + col] as u32; + *d = ((6 * p + 4 * (pm1 + pp1) + pm2 + pp2) as f32) * INV_256; + } + } +} + +/// 2x2 bilinear downsample of one output row: `(p00+p01+p10+p11) * 0.25`. +#[inline] +fn bilinear_row_f32(src_data: &[f32], dst_row: &mut [f32], out_row: usize, src_w: usize) { + let r0 = (out_row * 2) * src_w; + let r1 = r0 + src_w; + for (col, d) in dst_row.iter_mut().enumerate() { + let c = col * 2; + let sum = src_data[r0 + c] + src_data[r0 + c + 1] + src_data[r1 + c] + src_data[r1 + c + 1]; + *d = sum * 0.25; + } +} + +// ── u8 → f32 ───────────────────────────────────────────────────────────── + /// 5-tap separable `[1, 4, 6, 4, 1]` binomial filter, u8 source → f32 dest. /// -/// H pass uses `u16` accumulator (max value `16 * 255 = 4080`, fits `u16`). -/// V pass multiplies by `1 / 256` to yield `f32` output. Border replication: -/// edge pixels extend the 2-pixel border on each side. +/// Dispatches to the rayon path above [`PARALLEL_MIN_PIXELS`] (non-wasm), +/// else scalar. Output is identical either way. +/// +/// # Note on visibility +/// +/// This dispatcher, the `*_scalar` / `*_rayon` variants, and +/// [`downsample_bilinear_f32`] are `pub` so the criterion benchmark (a +/// separate crate) can measure them directly. They are not a stability +/// guarantee — prefer [`GaussianScaleSpacePyramid::build`]. +#[must_use] +pub fn binomial_4th_order_u8_to_f32(src: &Matrix) -> Matrix { + #[cfg(not(target_arch = "wasm32"))] + { + if src.rows.saturating_mul(src.cols) >= PARALLEL_MIN_PIXELS { + return binomial_4th_order_u8_to_f32_rayon(src); + } + } + binomial_4th_order_u8_to_f32_scalar(src) +} + +/// Serial u8 → f32 binomial filter. H pass uses `u16` accumulators (max +/// `16 * 255 = 4080`); V pass sums in `u32` then `* 1/256`. Border +/// replication extends edge pixels by 2 on each side. /// /// C equivalent: `vision::binomial_4th_order(float*, unsigned short*, const unsigned char*, ...)`. -fn binomial_4th_order_u8_to_f32(src: &Matrix) -> Matrix { +#[must_use] +pub fn binomial_4th_order_u8_to_f32_scalar(src: &Matrix) -> Matrix { let width = src.cols; let height = src.rows; debug_assert!(width >= 5 && height >= 5); let src_data = src.as_slice(); let mut tmp = vec![0u16; width * height]; - - // Horizontal pass. for row in 0..height { - let row_off = row * width; - let s = &src_data[row_off..row_off + width]; - // col 0: xm2 = xm1 = c = s[0], xp1 = s[1], xp2 = s[2]. - tmp[row_off] = - 6 * s[0] as u16 + 4 * (s[0] as u16 + s[1] as u16) + (s[0] as u16 + s[2] as u16); - // col 1: xm2 = s[0], xm1 = s[0], c = s[1], xp1 = s[2], xp2 = s[3]. - tmp[row_off + 1] = - 6 * s[1] as u16 + 4 * (s[0] as u16 + s[2] as u16) + (s[0] as u16 + s[3] as u16); - // Non-border cols. - for col in 2..width - 2 { - tmp[row_off + col] = 6 * s[col] as u16 - + 4 * (s[col - 1] as u16 + s[col + 1] as u16) - + (s[col - 2] as u16 + s[col + 2] as u16); - } - // col width-2: xp2 clamped to s[width-1]. - let c = width - 2; - tmp[row_off + c] = 6 * s[c] as u16 - + 4 * (s[c - 1] as u16 + s[c + 1] as u16) - + (s[c - 2] as u16 + s[c + 1] as u16); - // col width-1: xp1 = xp2 = s[width-1]. - let c = width - 1; - tmp[row_off + c] = - 6 * s[c] as u16 + 4 * (s[c - 1] as u16 + s[c] as u16) + (s[c - 2] as u16 + s[c] as u16); - } - - // Vertical pass. - let mut dst_data = vec![0f32; width * height]; - const INV_256: f32 = 1.0 / 256.0; - - // Top border: rows 0 and 1. - for col in 0..width { - // row 0: pm2 = pm1 = p = tmp[col]; pp1 = tmp[width+col]; pp2 = tmp[2w+col]. - let p = tmp[col] as u32; - let pp1 = tmp[width + col] as u32; - let pp2 = tmp[2 * width + col] as u32; - dst_data[col] = ((6 * p + 4 * (p + pp1) + p + pp2) as f32) * INV_256; - // row 1: pm2 = pm1 = tmp[col]; p = tmp[w+col]; pp1 = tmp[2w+col]; pp2 = tmp[3w+col]. - let pm = tmp[col] as u32; - let p = tmp[width + col] as u32; - let pp1 = tmp[2 * width + col] as u32; - let pp2 = tmp[3 * width + col] as u32; - dst_data[width + col] = ((6 * p + 4 * (pm + pp1) + pm + pp2) as f32) * INV_256; - } - - // Non-border rows. - for row in 2..height - 2 { - let row_off = row * width; - for col in 0..width { - let pm2 = tmp[(row - 2) * width + col] as u32; - let pm1 = tmp[(row - 1) * width + col] as u32; - let p = tmp[row_off + col] as u32; - let pp1 = tmp[(row + 1) * width + col] as u32; - let pp2 = tmp[(row + 2) * width + col] as u32; - dst_data[row_off + col] = ((6 * p + 4 * (pm1 + pp1) + pm2 + pp2) as f32) * INV_256; - } + let off = row * width; + binomial_h_row_u8( + &src_data[off..off + width], + &mut tmp[off..off + width], + width, + ); } - // Bottom border: rows h-2 and h-1. - let h = height; - for col in 0..width { - // row h-2: pp1 = pp2 = tmp[(h-1)*w + col]. - let pm2 = tmp[(h - 4) * width + col] as u32; - let pm1 = tmp[(h - 3) * width + col] as u32; - let p = tmp[(h - 2) * width + col] as u32; - let pp = tmp[(h - 1) * width + col] as u32; - dst_data[(h - 2) * width + col] = ((6 * p + 4 * (pm1 + pp) + pm2 + pp) as f32) * INV_256; - // row h-1: p = pp1 = pp2 = tmp[(h-1)*w + col]. - let pm2 = tmp[(h - 3) * width + col] as u32; - let pm1 = tmp[(h - 2) * width + col] as u32; - let p = tmp[(h - 1) * width + col] as u32; - dst_data[(h - 1) * width + col] = ((6 * p + 4 * (pm1 + p) + pm2 + p) as f32) * INV_256; + let mut dst_data = vec![0f32; width * height]; + for row in 0..height { + let off = row * width; + binomial_v_row_u8(&tmp, &mut dst_data[off..off + width], row, width, height); } Matrix::::from_vec(height, width, 1, dst_data) } -/// 5-tap separable `[1, 4, 6, 4, 1]` binomial filter, f32 → f32. Used for -/// the second and third levels within an octave and for all levels in -/// non-zero octaves. +/// Rayon u8 → f32 binomial filter — parallel over rows. Bit-for-bit +/// identical to [`binomial_4th_order_u8_to_f32_scalar`] (rows independent). +#[cfg(not(target_arch = "wasm32"))] +#[must_use] +pub fn binomial_4th_order_u8_to_f32_rayon(src: &Matrix) -> Matrix { + use rayon::prelude::*; + + let width = src.cols; + let height = src.rows; + debug_assert!(width >= 5 && height >= 5); + + let src_data = src.as_slice(); + let mut tmp = vec![0u16; width * height]; + tmp.par_chunks_mut(width) + .zip(src_data.par_chunks(width)) + .for_each(|(t_row, s_row)| binomial_h_row_u8(s_row, t_row, width)); + + let mut dst_data = vec![0f32; width * height]; + dst_data + .par_chunks_mut(width) + .enumerate() + .for_each(|(row, dst_row)| binomial_v_row_u8(&tmp, dst_row, row, width, height)); + + Matrix::::from_vec(height, width, 1, dst_data) +} + +// ── f32 → f32 ──────────────────────────────────────────────────────────── + +/// 5-tap separable `[1, 4, 6, 4, 1]` binomial filter, f32 → f32. +/// +/// Dispatches to the rayon path above [`PARALLEL_MIN_PIXELS`] (non-wasm), +/// else scalar. Output is identical either way. +#[must_use] +pub fn binomial_4th_order_f32_to_f32(src: &Matrix) -> Matrix { + #[cfg(not(target_arch = "wasm32"))] + { + if src.rows.saturating_mul(src.cols) >= PARALLEL_MIN_PIXELS { + return binomial_4th_order_f32_to_f32_rayon(src); + } + } + binomial_4th_order_f32_to_f32_scalar(src) +} + +/// Serial f32 → f32 binomial filter. Used for scales 1 and 2 within an +/// octave and for all scales in non-zero octaves. /// /// C equivalent: `vision::binomial_4th_order(float*, float*, const float*, ...)`. -fn binomial_4th_order_f32_to_f32(src: &Matrix) -> Matrix { +#[must_use] +pub fn binomial_4th_order_f32_to_f32_scalar(src: &Matrix) -> Matrix { let width = src.cols; let height = src.rows; debug_assert!(width >= 5 && height >= 5); let src_data = src.as_slice(); let mut tmp = vec![0f32; width * height]; - - // Horizontal pass. Addition order matches C++ left-to-right exactly to - // preserve f32 bit-for-bit parity (no parenthesizing of the (ll + rr) term). for row in 0..height { - let row_off = row * width; - let s = &src_data[row_off..row_off + width]; - tmp[row_off] = 6.0 * s[0] + 4.0 * (s[0] + s[1]) + s[0] + s[2]; - tmp[row_off + 1] = 6.0 * s[1] + 4.0 * (s[0] + s[2]) + s[0] + s[3]; - for col in 2..width - 2 { - tmp[row_off + col] = - 6.0 * s[col] + 4.0 * (s[col - 1] + s[col + 1]) + s[col - 2] + s[col + 2]; - } - let c = width - 2; - tmp[row_off + c] = 6.0 * s[c] + 4.0 * (s[c - 1] + s[c + 1]) + s[c - 2] + s[c + 1]; - let c = width - 1; - tmp[row_off + c] = 6.0 * s[c] + 4.0 * (s[c - 1] + s[c]) + s[c - 2] + s[c]; + let off = row * width; + binomial_h_row_f32( + &src_data[off..off + width], + &mut tmp[off..off + width], + width, + ); } - // Vertical pass. let mut dst_data = vec![0f32; width * height]; - const INV_256: f32 = 1.0 / 256.0; - - for col in 0..width { - // row 0 - let p = tmp[col]; - let pp1 = tmp[width + col]; - let pp2 = tmp[2 * width + col]; - dst_data[col] = (6.0 * p + 4.0 * (p + pp1) + p + pp2) * INV_256; - // row 1 - let pm = tmp[col]; - let p = tmp[width + col]; - let pp1 = tmp[2 * width + col]; - let pp2 = tmp[3 * width + col]; - dst_data[width + col] = (6.0 * p + 4.0 * (pm + pp1) + pm + pp2) * INV_256; - } - - for row in 2..height - 2 { - let row_off = row * width; - for col in 0..width { - let pm2 = tmp[(row - 2) * width + col]; - let pm1 = tmp[(row - 1) * width + col]; - let p = tmp[row_off + col]; - let pp1 = tmp[(row + 1) * width + col]; - let pp2 = tmp[(row + 2) * width + col]; - dst_data[row_off + col] = (6.0 * p + 4.0 * (pm1 + pp1) + pm2 + pp2) * INV_256; - } + for row in 0..height { + let off = row * width; + binomial_v_row_f32(&tmp, &mut dst_data[off..off + width], row, width, height); } - let h = height; - for col in 0..width { - let pm2 = tmp[(h - 4) * width + col]; - let pm1 = tmp[(h - 3) * width + col]; - let p = tmp[(h - 2) * width + col]; - let pp = tmp[(h - 1) * width + col]; - dst_data[(h - 2) * width + col] = (6.0 * p + 4.0 * (pm1 + pp) + pm2 + pp) * INV_256; - let pm2 = tmp[(h - 3) * width + col]; - let pm1 = tmp[(h - 2) * width + col]; - let p = tmp[(h - 1) * width + col]; - dst_data[(h - 1) * width + col] = (6.0 * p + 4.0 * (pm1 + p) + pm2 + p) * INV_256; - } + Matrix::::from_vec(height, width, 1, dst_data) +} + +/// Rayon f32 → f32 binomial filter — parallel over rows. Bit-for-bit +/// identical to [`binomial_4th_order_f32_to_f32_scalar`] (rows independent). +#[cfg(not(target_arch = "wasm32"))] +#[must_use] +pub fn binomial_4th_order_f32_to_f32_rayon(src: &Matrix) -> Matrix { + use rayon::prelude::*; + + let width = src.cols; + let height = src.rows; + debug_assert!(width >= 5 && height >= 5); + + let src_data = src.as_slice(); + let mut tmp = vec![0f32; width * height]; + tmp.par_chunks_mut(width) + .zip(src_data.par_chunks(width)) + .for_each(|(t_row, s_row)| binomial_h_row_f32(s_row, t_row, width)); + + let mut dst_data = vec![0f32; width * height]; + dst_data + .par_chunks_mut(width) + .enumerate() + .for_each(|(row, dst_row)| binomial_v_row_f32(&tmp, dst_row, row, width, height)); Matrix::::from_vec(height, width, 1, dst_data) } +// ── bilinear downsample ────────────────────────────────────────────────── + /// 2x2 bilinear downsample. Output dims: `(src.rows >> 1, src.cols >> 1)`. -/// Per output pixel: `(p00 + p01 + p10 + p11) * 0.25`. No `ceil` adjustment -/// (unlike the M8-1 box-filter pyramid). +/// +/// Dispatches to the rayon path above [`PARALLEL_MIN_PIXELS`] (non-wasm), +/// else scalar. +#[must_use] +pub fn downsample_bilinear_f32(src: &Matrix) -> Matrix { + #[cfg(not(target_arch = "wasm32"))] + { + if src.rows.saturating_mul(src.cols) >= PARALLEL_MIN_PIXELS { + return downsample_bilinear_f32_rayon(src); + } + } + downsample_bilinear_f32_scalar(src) +} + +/// Serial 2x2 bilinear downsample. Per output pixel: +/// `(p00 + p01 + p10 + p11) * 0.25`. No `ceil` adjustment (unlike the M8-1 +/// box-filter pyramid). /// /// C equivalent: `vision::downsample_bilinear`. -fn downsample_bilinear_f32(src: &Matrix) -> Matrix { +#[must_use] +pub fn downsample_bilinear_f32_scalar(src: &Matrix) -> Matrix { let dst_w = src.cols >> 1; let dst_h = src.rows >> 1; let src_data = src.as_slice(); let src_w = src.cols; - let mut dst_data = Vec::::with_capacity(dst_w * dst_h); + let mut dst_data = vec![0f32; dst_w * dst_h]; for row in 0..dst_h { - let r0 = (row * 2) * src_w; - let r1 = r0 + src_w; - for col in 0..dst_w { - let c = col * 2; - let sum = - src_data[r0 + c] + src_data[r0 + c + 1] + src_data[r1 + c] + src_data[r1 + c + 1]; - dst_data.push(sum * 0.25); - } + bilinear_row_f32( + src_data, + &mut dst_data[row * dst_w..(row + 1) * dst_w], + row, + src_w, + ); } Matrix::::from_vec(dst_h, dst_w, 1, dst_data) } +/// Rayon 2x2 bilinear downsample — parallel over output rows. Bit-for-bit +/// identical to [`downsample_bilinear_f32_scalar`]. +#[cfg(not(target_arch = "wasm32"))] +#[must_use] +pub fn downsample_bilinear_f32_rayon(src: &Matrix) -> Matrix { + use rayon::prelude::*; + + let dst_w = src.cols >> 1; + let dst_h = src.rows >> 1; + let src_data = src.as_slice(); + let src_w = src.cols; + + let mut dst_data = vec![0f32; dst_w * dst_h]; + dst_data + .par_chunks_mut(dst_w) + .enumerate() + .for_each(|(out_row, dst_row)| bilinear_row_f32(src_data, dst_row, out_row, src_w)); + Matrix::::from_vec(dst_h, dst_w, 1, dst_data) +} + #[cfg(test)] mod tests { use super::*; @@ -476,6 +656,115 @@ mod tests { Matrix::::from_vec(rows, cols, 1, data) } + // ── rayon parity (#207) ────────────────────────────────────────────── + + /// Deterministic random u8 / f32 images, seeded so failures reproduce. + #[cfg(not(target_arch = "wasm32"))] + fn rng_u8(rows: usize, cols: usize, seed: u64) -> Matrix { + use rand::{Rng, SeedableRng}; + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let data: Vec = (0..rows * cols).map(|_| rng.random::()).collect(); + Matrix::::from_vec(rows, cols, 1, data) + } + + #[cfg(not(target_arch = "wasm32"))] + fn rng_f32(rows: usize, cols: usize, seed: u64) -> Matrix { + use rand::{Rng, SeedableRng}; + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let data: Vec = (0..rows * cols) + .map(|_| rng.random::() as f32) + .collect(); + Matrix::::from_vec(rows, cols, 1, data) + } + + /// Sizes spanning below, at, and above `PARALLEL_MIN_PIXELS`, with odd + /// dimensions and small images. All `>= 5x5`. + #[cfg(not(target_arch = "wasm32"))] + const RAYON_PARITY_SIZES: &[(usize, usize)] = &[ + (5, 5), + (33, 31), + (64, 64), + (256, 256), + (300, 301), + (480, 640), + (513, 511), + ]; + + #[cfg(not(target_arch = "wasm32"))] + fn assert_f32_bits_eq(a: &Matrix, b: &Matrix, what: &str) { + assert_eq!(a.as_slice().len(), b.as_slice().len(), "{what}: size"); + for (i, (&x, &y)) in a.as_slice().iter().zip(b.as_slice().iter()).enumerate() { + assert_eq!(x.to_bits(), y.to_bits(), "{what}: bit mismatch at {i}"); + } + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(miri, ignore)] // #207: rayon parity over large images — too slow under Miri + #[test] + fn test_binomial_f32_rayon_matches_scalar() { + for (i, &(rows, cols)) in RAYON_PARITY_SIZES.iter().enumerate() { + let src = rng_f32(rows, cols, 0x00F3_2A00 + i as u64); + let scalar = binomial_4th_order_f32_to_f32_scalar(&src); + let rayon = binomial_4th_order_f32_to_f32_rayon(&src); + assert_f32_bits_eq( + &scalar, + &rayon, + &format!("binomial_f32 rayon {rows}x{cols}"), + ); + } + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(miri, ignore)] // #207: rayon parity over large images — too slow under Miri + #[test] + fn test_binomial_u8_rayon_matches_scalar() { + for (i, &(rows, cols)) in RAYON_PARITY_SIZES.iter().enumerate() { + let src = rng_u8(rows, cols, 0x00C8_2A00 + i as u64); + let scalar = binomial_4th_order_u8_to_f32_scalar(&src); + let rayon = binomial_4th_order_u8_to_f32_rayon(&src); + assert_f32_bits_eq(&scalar, &rayon, &format!("binomial_u8 rayon {rows}x{cols}")); + } + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(miri, ignore)] // #207: rayon parity over large images — too slow under Miri + #[test] + fn test_bilinear_rayon_matches_scalar() { + for (i, &(rows, cols)) in RAYON_PARITY_SIZES.iter().enumerate() { + let src = rng_f32(rows, cols, 0x00B1_2A00 + i as u64); + let scalar = downsample_bilinear_f32_scalar(&src); + let rayon = downsample_bilinear_f32_rayon(&src); + assert_f32_bits_eq(&scalar, &rayon, &format!("bilinear rayon {rows}x{cols}")); + } + } + + /// The dispatchers (which pick rayon above the threshold) must agree with + /// the scalar path, including for a large image that triggers rayon. + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(miri, ignore)] // #207: rayon parity over large images — too slow under Miri + #[test] + fn test_dispatchers_match_scalar() { + // 800×800 = 640k px > PARALLEL_MIN_PIXELS, so the dispatchers take the + // rayon path here. + let f = rng_f32(800, 800, 0x0D15_2A00); + assert_f32_bits_eq( + &binomial_4th_order_f32_to_f32_scalar(&f), + &binomial_4th_order_f32_to_f32(&f), + "binomial_f32 dispatch", + ); + assert_f32_bits_eq( + &downsample_bilinear_f32_scalar(&f), + &downsample_bilinear_f32(&f), + "bilinear dispatch", + ); + let u = rng_u8(800, 800, 0x0D15_2A01); + assert_f32_bits_eq( + &binomial_4th_order_u8_to_f32_scalar(&u), + &binomial_4th_order_u8_to_f32(&u), + "binomial_u8 dispatch", + ); + } + // ── Configuration sanity ───────────────────────────────────────────── #[test] From ec28cc910f86d920bcaee8d299f8e9e8ce8fb0ae Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 16 Jun 2026 00:06:13 +0200 Subject: [PATCH 26/40] =?UTF-8?q?perf(kpm):=20box-filter=20Pyramid=20downs?= =?UTF-8?q?ample=20=E2=80=94=20criterion=20benchmark=20+=20SIMD=20(#211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(kpm): add criterion benchmark for Pyramid downsample (#131) Establish the scalar baseline for the pyramid downsample trilogy so the SIMD path (#132) and any chunked-layout work (#133) can prove speedups against stable numbers. - Promote the private `downsample` to `pub fn downsample_scalar` and add a `pub fn downsample` dispatcher (scalar-only for now; #132 wires in the SIMD paths). Both are `pub` so the criterion bench (a separate crate) can measure them directly; `Pyramid::build` is the stable API. - Add `crates/core/benches/pyramid_bench.rs` benchmarking `downsample_scalar` and `Pyramid::build` (num_levels=4) at 640x480, 1280x720 and 1920x1080 with a fixed-seed PRNG input. - Wire `[[bench]] name = "pyramid_bench"` into Cargo.toml. Baseline (median, this machine): downsample 640x480 ~116 us | 1280x720 ~341 us | 1920x1080 ~884 us pyramid_build 640x480 ~159 us | 1280x720 ~597 us | 1920x1080 ~1.87 ms Co-Authored-By: Claude Fable 5 * perf(kpm): SIMD path for Pyramid downsample (AVX2/SSE4.1/wasm32) (#132) Add runtime-dispatched SIMD implementations of the pyramid downsample, each byte-identical to the scalar baseline established in #131. - `downsample_avx2`: 32 output cols/iter via 256-bit lanes (`maddubs` horizontal pairwise add + `packus` + `permute4x64` to undo the per-lane interleave). - `downsample_sse41`: 16 output cols/iter via 128-bit lanes. - `downsample_wasm`: 16 output cols/iter via `simd128` (`extadd_pairwise_u8x16` + `narrow`). - `downsample` dispatches AVX2 -> SSE4.1 -> scalar on x86_64 (runtime `is_x86_feature_detected!`) and simd128 on wasm32; scalar remains the fallback. Each `unsafe` site carries a `// SAFETY:` comment. - Shared `downsample_dims` + `downsample_row_tail_scalar` helpers define the rounding in one place; the SIMD main loops handle full blocks and defer the per-row remainder to the scalar tail. - Block counts are sized so loads/stores stay in bounds for any dimension (incl. odd / sub-block widths). Correctness: property tests compare each SIMD path against scalar over 16 sizes (odd dims, tiny images, block boundaries) plus a dispatcher-parity test. `cargo clippy --all-targets --all-features -- -D warnings` clean. Speedup (downsample, median ns -> us, this machine): 640x480 scalar 85.8 | sse41 7.4 (11.6x) | avx2 5.8 (14.7x) 1280x720 scalar 251 | sse41 25.0 (10.0x) | avx2 18.6 (13.5x) 1920x1080 scalar 773 | sse41 96.1 (8.0x) | avx2 75.9 (10.2x) Co-Authored-By: Claude Fable 5 * test(kpm): exhaustive width-sweep parity for SIMD downsample (#132) Add a 2..=160 column sweep (x4 row counts) comparing both the AVX2 and SSE4.1 paths against scalar, covering every remainder case of the 16- and 32-wide main loops. Strengthens confidence beyond the hand-picked sizes that each SIMD path is byte-identical to scalar for all dimensions. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- crates/core/Cargo.toml | 4 + crates/core/benches/pyramid_bench.rs | 131 ++++++++ crates/core/src/kpm/freak/pyramid.rs | 453 ++++++++++++++++++++++++++- 3 files changed, 578 insertions(+), 10 deletions(-) create mode 100644 crates/core/benches/pyramid_bench.rs diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c8af8cb..24d9dfb 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -154,6 +154,10 @@ name = "feature_map_bench" harness = false required-features = ["log-helpers"] +[[bench]] +name = "pyramid_bench" +harness = false + [[bench]] name = "gaussian_pyramid_bench" harness = false diff --git a/crates/core/benches/pyramid_bench.rs b/crates/core/benches/pyramid_bench.rs new file mode 100644 index 0000000..3b479a9 --- /dev/null +++ b/crates/core/benches/pyramid_bench.rs @@ -0,0 +1,131 @@ +/* + * pyramid_bench.rs + * WebARKitLib-rs + * + * This file is part of WebARKitLib-rs - WebARKit. + * + * WebARKitLib-rs is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WebARKitLib-rs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with WebARKitLib-rs. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2026 WebARKit. + * + * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt + * + */ + +//! Criterion baseline for `kpm::freak::pyramid` downsampling (#131). +//! +//! Establishes the scalar baseline so the SIMD path (#132) and any +//! chunked-layout work (#133) can prove their speedup against stable +//! numbers. Two groups are measured at typical AR input resolutions +//! (640×480, 1280×720, 1920×1080): +//! +//! - `downsample`: one 2×2 box-filter decimation step (the hot inner loop). +//! - `pyramid_build`: end-to-end `Pyramid::build` with `num_levels = 4`. +//! +//! Inputs are filled deterministically with a fixed-seed PRNG so runs are +//! comparable across machines and across PRs in the trilogy. + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use purecv::core::Matrix; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use std::hint::black_box; +use webarkitlib_rs::kpm::freak::pyramid::{downsample_scalar, Pyramid}; + +#[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] +use webarkitlib_rs::kpm::freak::pyramid::downsample_sse41; + +#[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] +use webarkitlib_rs::kpm::freak::pyramid::downsample_avx2; + +/// Typical AR camera input resolutions as `(width, height)`. +const SIZES: &[(usize, usize)] = &[(640, 480), (1280, 720), (1920, 1080)]; + +/// Build a deterministic grayscale `Matrix` of `rows × cols` using a +/// fixed-seed PRNG, so every benchmark run sees identical pixel data. +fn random_gray(rows: usize, cols: usize) -> Matrix { + let mut rng = StdRng::seed_from_u64(0xA12B_C3D4); + let data: Vec = (0..rows * cols).map(|_| rng.random::()).collect(); + Matrix::::from_vec(rows, cols, 1, data) +} + +fn bench_downsample(c: &mut Criterion) { + let mut group = c.benchmark_group("downsample"); + for &(w, h) in SIZES { + let src = random_gray(h, w); + // One output pixel per 2×2 source block; throughput is measured in + // source pixels touched. + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("scalar", format!("{w}x{h}")), + &src, + |bencher, src| bencher.iter(|| downsample_scalar(black_box(src))), + ); + + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] + if is_x86_feature_detected!("sse4.1") { + group.bench_with_input( + BenchmarkId::new("sse41", format!("{w}x{h}")), + &src, + // SAFETY: sse4.1 confirmed available by the runtime check above. + |bencher, src| bencher.iter(|| unsafe { downsample_sse41(black_box(src)) }), + ); + } + + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] + if is_x86_feature_detected!("avx2") { + group.bench_with_input( + BenchmarkId::new("avx2", format!("{w}x{h}")), + &src, + // SAFETY: avx2 confirmed available by the runtime check above. + |bencher, src| bencher.iter(|| unsafe { downsample_avx2(black_box(src)) }), + ); + } + } + group.finish(); +} + +fn bench_pyramid_build(c: &mut Criterion) { + let mut group = c.benchmark_group("pyramid_build"); + for &(w, h) in SIZES { + let src = random_gray(h, w); + group.throughput(Throughput::Elements((w * h) as u64)); + group.bench_with_input( + BenchmarkId::new("levels4", format!("{w}x{h}")), + &src, + |bencher, src| { + bencher.iter(|| { + let mut p = Pyramid::new(4, 2.0); + p.build(black_box(src)).expect("build should succeed"); + p + }) + }, + ); + } + group.finish(); +} + +criterion_group!(benches, bench_downsample, bench_pyramid_build); +criterion_main!(benches); diff --git a/crates/core/src/kpm/freak/pyramid.rs b/crates/core/src/kpm/freak/pyramid.rs index cf27206..1167c10 100644 --- a/crates/core/src/kpm/freak/pyramid.rs +++ b/crates/core/src/kpm/freak/pyramid.rs @@ -187,6 +187,79 @@ impl Pyramid { /// 2x2 box-filter downsample, byte-identical to C++ `BoxFilterDecimate`. /// +/// Dispatches to the fastest available implementation at runtime +/// (AVX2 → SSE4.1 on x86_64, simd128 on wasm32), falling back to +/// [`downsample_scalar`]. Every path produces bit-for-bit identical +/// output to the scalar baseline — see the property tests below. +/// +/// Output dimensions: `new_h = ceil((src.rows - 1) / 2)`, +/// `new_w = ceil((src.cols - 1) / 2)`. +/// +/// # Note on visibility +/// +/// `downsample` / `downsample_scalar` (and the SIMD variants) are `pub` +/// so the criterion benchmark (a separate crate) can measure them +/// directly. They are not part of a stability guarantee — prefer +/// [`Pyramid::build`]. +#[must_use] +pub fn downsample(src: &Matrix) -> Matrix { + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] + { + if is_x86_feature_detected!("avx2") { + // SAFETY: the `avx2` target feature is confirmed present at + // runtime, satisfying the precondition of `downsample_avx2`. + return unsafe { downsample_avx2(src) }; + } + } + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] + { + if is_x86_feature_detected!("sse4.1") { + // SAFETY: the `sse4.1` target feature is confirmed present at + // runtime, satisfying the precondition of `downsample_sse41`. + return unsafe { downsample_sse41(src) }; + } + } + #[cfg(all( + target_arch = "wasm32", + feature = "simd-wasm32", + target_feature = "simd128" + ))] + { + // SAFETY: `simd128` is guaranteed by the `target_feature` cfg gate, + // so the intrinsics in `downsample_wasm` are always valid here. + return unsafe { downsample_wasm(src) }; + } + + #[allow(unreachable_code)] + downsample_scalar(src) +} + +/// Output dimensions of a single downsample step: `(new_h, new_w)`. +#[inline] +fn downsample_dims(src: &Matrix) -> (usize, usize) { + let new_h = (src.rows.saturating_sub(1) as f32 / 2.0).ceil() as usize; + let new_w = (src.cols.saturating_sub(1) as f32 / 2.0).ceil() as usize; + (new_h, new_w) +} + +/// Scalar fill of one output row for output columns `start..dst_row.len()`. +/// +/// Shared by the scalar implementation and the SIMD remainder tails so the +/// rounding (`(a + b + c + d + 2) >> 2`) is defined in exactly one place. +#[inline] +fn downsample_row_tail_scalar(row0: &[u8], row1: &[u8], dst_row: &mut [u8], start: usize) { + for (out_col, dst) in dst_row.iter_mut().enumerate().skip(start) { + let c = out_col * 2; + // u16 promotion prevents u8 wrap; max sum = 4*255 + 2 = 1022. + let sum: u16 = + row0[c] as u16 + row0[c + 1] as u16 + row1[c] as u16 + row1[c + 1] as u16 + 2; + *dst = (sum >> 2) as u8; + } +} + +/// Scalar 2x2 box-filter downsample, byte-identical to C++ +/// `BoxFilterDecimate`. +/// /// Output dimensions: `new_h = ceil((src.rows - 1) / 2)`, /// `new_w = ceil((src.cols - 1) / 2)`. /// @@ -194,28 +267,243 @@ impl Pyramid { /// to avoid overflow), add 2 for rounding, then shift right by 2. This /// is the combined effect of `HorizontalBoxFilterDecimate` plus /// `VerticalBoxFilter` from `pyramid-inline.h`. -fn downsample(src: &Matrix) -> Matrix { - let new_h = (src.rows.saturating_sub(1) as f32 / 2.0).ceil() as usize; - let new_w = (src.cols.saturating_sub(1) as f32 / 2.0).ceil() as usize; +#[must_use] +pub fn downsample_scalar(src: &Matrix) -> Matrix { + let (new_h, new_w) = downsample_dims(src); + + let src_data = src.as_slice(); + let src_cols = src.cols; + + let mut dst_data = vec![0u8; new_h * new_w]; + + for out_row in 0..new_h { + let r0 = (out_row * 2) * src_cols; + let r1 = r0 + src_cols; + let row0 = &src_data[r0..r0 + src_cols]; + let row1 = &src_data[r1..r1 + src_cols]; + let dst_row = &mut dst_data[out_row * new_w..(out_row + 1) * new_w]; + downsample_row_tail_scalar(row0, row1, dst_row, 0); + } + + Matrix::::from_vec(new_h, new_w, 1, dst_data) +} + +/// AVX2 2x2 box-filter downsample. Processes 32 output columns per +/// iteration (64 source columns) using 256-bit lanes; the remaining +/// columns of each row fall back to [`downsample_row_tail_scalar`]. +/// +/// Output is byte-identical to [`downsample_scalar`]. +/// +/// # Safety +/// +/// The caller must ensure the `avx2` target feature is available at +/// runtime (the [`downsample`] dispatcher guarantees this via +/// `is_x86_feature_detected!`). +#[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] +#[target_feature(enable = "avx2")] +#[must_use] +pub unsafe fn downsample_avx2(src: &Matrix) -> Matrix { + use std::arch::x86_64::*; + + let (new_h, new_w) = downsample_dims(src); + let src_data = src.as_slice(); + let src_cols = src.cols; + let mut dst_data = vec![0u8; new_h * new_w]; + + // Number of leading output columns we can serve with full 32-wide + // blocks. A block at output col `o` reads source cols `[2o, 2o + 64)` + // and writes `[o, o + 32)`; both stay in bounds while + // `2 * (o + 32) <= src_cols` (which also implies `o + 32 <= new_w`). + let simd_cols = if src_cols >= 64 { + ((src_cols - 64) / 2 / 32) * 32 + 32 + } else { + 0 + }; + + let ones = _mm256_set1_epi8(1); + let two = _mm256_set1_epi16(2); + + for out_row in 0..new_h { + let r0 = (out_row * 2) * src_cols; + let r1 = r0 + src_cols; + let row0 = &src_data[r0..r0 + src_cols]; + let row1 = &src_data[r1..r1 + src_cols]; + let dst_row = &mut dst_data[out_row * new_w..(out_row + 1) * new_w]; + + let mut o = 0; + while o < simd_cols { + let c = o * 2; + // SAFETY: `c + 64 <= src_cols` by construction of `simd_cols`, + // and `o + 32 <= new_w`, so all loads/stores are in bounds. + let a0 = _mm256_loadu_si256(row0.as_ptr().add(c) as *const __m256i); + let a1 = _mm256_loadu_si256(row0.as_ptr().add(c + 32) as *const __m256i); + let b0 = _mm256_loadu_si256(row1.as_ptr().add(c) as *const __m256i); + let b1 = _mm256_loadu_si256(row1.as_ptr().add(c + 32) as *const __m256i); + + // Horizontal pairwise sums of adjacent bytes -> u16 lanes. + let h0lo = _mm256_maddubs_epi16(a0, ones); + let h0hi = _mm256_maddubs_epi16(a1, ones); + let h1lo = _mm256_maddubs_epi16(b0, ones); + let h1hi = _mm256_maddubs_epi16(b1, ones); + + // Vertical: (h0 + h1 + 2) >> 2. + let slo = _mm256_srli_epi16(_mm256_add_epi16(_mm256_add_epi16(h0lo, h1lo), two), 2); + let shi = _mm256_srli_epi16(_mm256_add_epi16(_mm256_add_epi16(h0hi, h1hi), two), 2); + + // packus works per 128-bit lane, interleaving the halves; + // permute the 64-bit chunks back into sequential order. + let packed = _mm256_packus_epi16(slo, shi); + let out = _mm256_permute4x64_epi64(packed, 0xD8); + _mm256_storeu_si256(dst_row.as_mut_ptr().add(o) as *mut __m256i, out); + + o += 32; + } + + downsample_row_tail_scalar(row0, row1, dst_row, simd_cols); + } + + Matrix::::from_vec(new_h, new_w, 1, dst_data) +} +/// SSE4.1 2x2 box-filter downsample. Processes 16 output columns per +/// iteration (32 source columns) using 128-bit lanes; the remaining +/// columns of each row fall back to [`downsample_row_tail_scalar`]. +/// +/// Output is byte-identical to [`downsample_scalar`]. +/// +/// # Safety +/// +/// The caller must ensure the `sse4.1` target feature is available at +/// runtime (the [`downsample`] dispatcher guarantees this via +/// `is_x86_feature_detected!`). +#[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] +#[target_feature(enable = "sse4.1")] +#[must_use] +pub unsafe fn downsample_sse41(src: &Matrix) -> Matrix { + use std::arch::x86_64::*; + + let (new_h, new_w) = downsample_dims(src); let src_data = src.as_slice(); let src_cols = src.cols; + let mut dst_data = vec![0u8; new_h * new_w]; - let mut dst_data = Vec::::with_capacity(new_h * new_w); + // A block at output col `o` reads source cols `[2o, 2o + 32)` and + // writes `[o, o + 16)`; safe while `2 * (o + 16) <= src_cols`. + let simd_cols = if src_cols >= 32 { + ((src_cols - 32) / 2 / 16) * 16 + 16 + } else { + 0 + }; + + let ones = _mm_set1_epi8(1); + let two = _mm_set1_epi16(2); for out_row in 0..new_h { let r0 = (out_row * 2) * src_cols; let r1 = r0 + src_cols; let row0 = &src_data[r0..r0 + src_cols]; let row1 = &src_data[r1..r1 + src_cols]; + let dst_row = &mut dst_data[out_row * new_w..(out_row + 1) * new_w]; + + let mut o = 0; + while o < simd_cols { + let c = o * 2; + // SAFETY: `c + 32 <= src_cols` by construction of `simd_cols`, + // and `o + 16 <= new_w`, so all loads/stores are in bounds. + let a0 = _mm_loadu_si128(row0.as_ptr().add(c) as *const __m128i); + let a1 = _mm_loadu_si128(row0.as_ptr().add(c + 16) as *const __m128i); + let b0 = _mm_loadu_si128(row1.as_ptr().add(c) as *const __m128i); + let b1 = _mm_loadu_si128(row1.as_ptr().add(c + 16) as *const __m128i); + + let h0lo = _mm_maddubs_epi16(a0, ones); + let h0hi = _mm_maddubs_epi16(a1, ones); + let h1lo = _mm_maddubs_epi16(b0, ones); + let h1hi = _mm_maddubs_epi16(b1, ones); + + let slo = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(h0lo, h1lo), two), 2); + let shi = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(h0hi, h1hi), two), 2); + + let out = _mm_packus_epi16(slo, shi); + _mm_storeu_si128(dst_row.as_mut_ptr().add(o) as *mut __m128i, out); + + o += 16; + } + + downsample_row_tail_scalar(row0, row1, dst_row, simd_cols); + } - for out_col in 0..new_w { - let c = out_col * 2; - // u16 promotion prevents u8 wrap; max sum = 4*255 + 2 = 1022. - let sum: u16 = - row0[c] as u16 + row0[c + 1] as u16 + row1[c] as u16 + row1[c + 1] as u16 + 2; - dst_data.push((sum >> 2) as u8); + Matrix::::from_vec(new_h, new_w, 1, dst_data) +} + +/// wasm32 SIMD (`simd128`) 2x2 box-filter downsample. Processes 16 output +/// columns per iteration (32 source columns); the remaining columns of +/// each row fall back to [`downsample_row_tail_scalar`]. +/// +/// Output is byte-identical to [`downsample_scalar`]. +/// +/// # Safety +/// +/// Requires the `simd128` target feature, which is guaranteed by the +/// `#[cfg(target_feature = "simd128")]` gate on the [`downsample`] +/// dispatcher call site. +#[cfg(all( + target_arch = "wasm32", + feature = "simd-wasm32", + target_feature = "simd128" +))] +#[target_feature(enable = "simd128")] +#[must_use] +pub unsafe fn downsample_wasm(src: &Matrix) -> Matrix { + use std::arch::wasm32::*; + + let (new_h, new_w) = downsample_dims(src); + let src_data = src.as_slice(); + let src_cols = src.cols; + let mut dst_data = vec![0u8; new_h * new_w]; + + // A block at output col `o` reads source cols `[2o, 2o + 32)` and + // writes `[o, o + 16)`; safe while `2 * (o + 16) <= src_cols`. + let simd_cols = if src_cols >= 32 { + ((src_cols - 32) / 2 / 16) * 16 + 16 + } else { + 0 + }; + + let two = i16x8_splat(2); + + for out_row in 0..new_h { + let r0 = (out_row * 2) * src_cols; + let r1 = r0 + src_cols; + let row0 = &src_data[r0..r0 + src_cols]; + let row1 = &src_data[r1..r1 + src_cols]; + let dst_row = &mut dst_data[out_row * new_w..(out_row + 1) * new_w]; + + let mut o = 0; + while o < simd_cols { + let c = o * 2; + // SAFETY: `c + 32 <= src_cols` by construction of `simd_cols`, + // and `o + 16 <= new_w`, so all loads/stores are in bounds. + let a0 = v128_load(row0.as_ptr().add(c) as *const v128); + let a1 = v128_load(row0.as_ptr().add(c + 16) as *const v128); + let b0 = v128_load(row1.as_ptr().add(c) as *const v128); + let b1 = v128_load(row1.as_ptr().add(c + 16) as *const v128); + + // Horizontal pairwise sums of adjacent u8 lanes -> u16 lanes. + let h0lo = u16x8_extadd_pairwise_u8x16(a0); + let h0hi = u16x8_extadd_pairwise_u8x16(a1); + let h1lo = u16x8_extadd_pairwise_u8x16(b0); + let h1hi = u16x8_extadd_pairwise_u8x16(b1); + + let slo = u16x8_shr(i16x8_add(i16x8_add(h0lo, h1lo), two), 2); + let shi = u16x8_shr(i16x8_add(i16x8_add(h0hi, h1hi), two), 2); + + let out = u8x16_narrow_i16x8(slo, shi); + v128_store(dst_row.as_mut_ptr().add(o) as *mut v128, out); + + o += 16; } + + downsample_row_tail_scalar(row0, row1, dst_row, simd_cols); } Matrix::::from_vec(new_h, new_w, 1, dst_data) @@ -344,4 +632,149 @@ mod tests { Err(PyramidError::LevelTooSmall { .. }) )); } + + // ── SIMD parity (#132) ─────────────────────────────────────────────── + + /// Deterministic pseudo-random image, seeded so failures reproduce. + #[cfg(any( + all(target_arch = "x86_64", feature = "simd-x86-sse41"), + all(target_arch = "x86_64", feature = "simd-x86-avx2"), + ))] + fn random_img(rows: usize, cols: usize, seed: u64) -> Matrix { + use rand::{Rng, SeedableRng}; + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let data: Vec = (0..rows * cols).map(|_| rng.random::()).collect(); + Matrix::::from_vec(rows, cols, 1, data) + } + + /// Sizes chosen to exercise the SIMD main loop, the scalar remainder + /// tail, odd dimensions, sub-block widths, and block boundaries. + #[cfg(any( + all(target_arch = "x86_64", feature = "simd-x86-sse41"), + all(target_arch = "x86_64", feature = "simd-x86-avx2"), + ))] + const PARITY_SIZES: &[(usize, usize)] = &[ + (2, 2), + (3, 3), + (4, 4), + (5, 7), + (15, 15), + (16, 16), + (17, 17), + (31, 33), + (32, 32), + (33, 31), + (63, 65), + (64, 64), + (65, 63), + (100, 100), + (129, 127), + (200, 320), + ]; + + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] + #[test] + fn test_downsample_sse41_matches_scalar() { + if !is_x86_feature_detected!("sse4.1") { + eprintln!("sse4.1 not available at runtime; skipping parity test"); + return; + } + for (i, &(rows, cols)) in PARITY_SIZES.iter().enumerate() { + let img = random_img(rows, cols, 0x5E_5E_00 + i as u64); + let scalar = downsample_scalar(&img); + // SAFETY: guarded by the runtime `sse4.1` check above. + let simd = unsafe { downsample_sse41(&img) }; + assert_eq!( + scalar.as_slice(), + simd.as_slice(), + "sse41 mismatch at {rows}x{cols}" + ); + } + } + + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] + #[test] + fn test_downsample_avx2_matches_scalar() { + if !is_x86_feature_detected!("avx2") { + eprintln!("avx2 not available at runtime; skipping parity test"); + return; + } + for (i, &(rows, cols)) in PARITY_SIZES.iter().enumerate() { + let img = random_img(rows, cols, 0xA2_A2_00 + i as u64); + let scalar = downsample_scalar(&img); + // SAFETY: guarded by the runtime `avx2` check above. + let simd = unsafe { downsample_avx2(&img) }; + assert_eq!( + scalar.as_slice(), + simd.as_slice(), + "avx2 mismatch at {rows}x{cols}" + ); + } + } + + /// The public dispatcher must agree with the scalar baseline regardless + /// of which path it selects at runtime. + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] + #[test] + fn test_downsample_dispatch_matches_scalar() { + for (i, &(rows, cols)) in PARITY_SIZES.iter().enumerate() { + let img = random_img(rows, cols, 0xD1_5A_00 + i as u64); + assert_eq!( + downsample_scalar(&img).as_slice(), + downsample(&img).as_slice(), + "dispatch mismatch at {rows}x{cols}" + ); + } + } + + /// Exhaustive width sweep: every column count from 2..=160 (covering + /// sub-block widths, single/multi full blocks, and every possible + /// remainder for both the 16-wide SSE/wasm and 32-wide AVX2 main + /// loops) crossed with a few row counts. This is the strong guarantee + /// that the SIMD/scalar split point is correct for *all* dimensions, + /// not just the hand-picked sizes above. + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-avx2"))] + #[test] + fn test_downsample_avx2_matches_scalar_exhaustive() { + if !is_x86_feature_detected!("avx2") { + eprintln!("avx2 not available at runtime; skipping exhaustive parity test"); + return; + } + for rows in [2usize, 3, 4, 5] { + for cols in 2usize..=160 { + let img = random_img(rows, cols, 0xE0_0000 + (rows * 1000 + cols) as u64); + let scalar = downsample_scalar(&img); + // SAFETY: guarded by the runtime `avx2` check above. + let simd = unsafe { downsample_avx2(&img) }; + assert_eq!( + scalar.as_slice(), + simd.as_slice(), + "avx2 exhaustive mismatch at {rows}x{cols}" + ); + } + } + } + + /// Same exhaustive width sweep for the SSE4.1 path. + #[cfg(all(target_arch = "x86_64", feature = "simd-x86-sse41"))] + #[test] + fn test_downsample_sse41_matches_scalar_exhaustive() { + if !is_x86_feature_detected!("sse4.1") { + eprintln!("sse4.1 not available at runtime; skipping exhaustive parity test"); + return; + } + for rows in [2usize, 3, 4, 5] { + for cols in 2usize..=160 { + let img = random_img(rows, cols, 0x55_0000 + (rows * 1000 + cols) as u64); + let scalar = downsample_scalar(&img); + // SAFETY: guarded by the runtime `sse4.1` check above. + let simd = unsafe { downsample_sse41(&img) }; + assert_eq!( + scalar.as_slice(), + simd.as_slice(), + "sse41 exhaustive mismatch at {rows}x{cols}" + ); + } + } + } } From 64d117003f78cabee888dee96b7ca7b2b1c22372 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 13:28:37 +0200 Subject: [PATCH 27/40] doc(kpm): mark box-filter Pyramid as kept-but-unused reference (#203) Decision on #203: keep BoxFilterPyramid8u / Pyramid as a tested reference rather than remove or wire it in. Add a module note making clear it is not wired into any pipeline (the live KPM/FREAK detector uses GaussianScaleSpacePyramid) so future readers aren't misled. Closes #203. Co-Authored-By: Claude Fable 5 --- crates/core/src/kpm/freak/pyramid.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/core/src/kpm/freak/pyramid.rs b/crates/core/src/kpm/freak/pyramid.rs index 1167c10..51551a1 100644 --- a/crates/core/src/kpm/freak/pyramid.rs +++ b/crates/core/src/kpm/freak/pyramid.rs @@ -36,6 +36,12 @@ //! Image pyramid built by repeated box-filter downsampling. //! +//! **Not currently wired into any pipeline** (kept as reference — see #203). +//! This is a faithful, SIMD-accelerated port of the C++ `BoxFilterPyramid8u`, +//! which is itself unused in the original. The live KPM/FREAK detector builds +//! its scale space with [`super::gaussian_pyramid::GaussianScaleSpacePyramid`] +//! instead. Retained as a tested reference implementation. +//! //! Ported from `WebARKitLib/lib/SRC/KPM/FreakMatcher/detectors/pyramid.{h,cpp}` //! (`BoxFilterPyramid8u` / `BoxFilterDecimate`). //! From 45c79cae7f553a3756ad679b3a0269a187c95ca2 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 13:42:22 +0200 Subject: [PATCH 28/40] ci(benchmarks): harden against flaky external C-library downloads (#204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The benchmarks job built a C baseline by downloading external sources (jpeg-9f from ijg.org, WebARKitLib) at CI time; a TLS/network flake there (and a missing `svn`) failed the whole gate even though the Rust code was fine (#204). - Install `subversion` (the bootstrap warned `svn not found`). - Cache the downloaded C sources keyed on libraries.json hash. - Run the C baseline as a NON-BLOCKING step (`continue-on-error`) with a 3x retry on the flaky bootstrap download — it only yields an optional comparison log. - Keep `cargo bench` (marker_bench) as the required gate signal. Now a transient external-download failure degrades gracefully instead of failing the benchmarks gate. Closes #204. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf659fb..ba6fc8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,24 +232,38 @@ jobs: working-directory: crates/core run: cargo run --example simple --features log-helpers -- ../../benchmarks/data/camera_para.dat ../../benchmarks/data/patt.hiro ../../benchmarks/data/img.jpg - - name: Run benchmarks + - name: Install C benchmark deps (svn for bootstrap) + run: sudo apt-get update && sudo apt-get install -y subversion + + - name: Cache C benchmark downloaded sources + uses: actions/cache@v4 + with: + path: benchmarks/c_benchmark/archives + key: c-bench-archives-${{ runner.os }}-${{ hashFiles('benchmarks/c_benchmark/libraries.json') }} + + # The C baseline pulls external sources (jpeg-9f from ijg.org, WebARKitLib) + # whose download occasionally fails with TLS/network errors (#204). It only + # produces an optional comparison log, so keep it NON-BLOCKING: a flake must + # not fail the benchmarks gate. The required signal is the Rust bench below. + - name: Build & run C baseline benchmark (non-blocking) + continue-on-error: true run: | - # Download and Build C benchmark + set -e cd benchmarks/c_benchmark - python ../bootstrap.py --bootstrap-file libraries.json - + for attempt in 1 2 3; do + python ../bootstrap.py --bootstrap-file libraries.json && break + echo "bootstrap attempt $attempt failed; retrying in 15s..." >&2 + sleep 15 + done mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make cd .. - - # Run C benchmark for baseline mkdir -p ../../target/criterion ./build/c_benchmark ../data/camera_para.dat ../data/patt.hiro ../data/hiro.raw 429 317 > ../../target/criterion/c_benchmark.log - - # Run Rust benchmarks - cd ../.. - cargo bench -p webarkitlib-rs --bench marker_bench + + - name: Run Rust benchmarks + run: cargo bench -p webarkitlib-rs --bench marker_bench - name: Upload benchmark reports uses: actions/upload-artifact@v5 with: From 94e7b2d80ecdcde97366bb41baf489db08bf16b1 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 18:13:22 +0200 Subject: [PATCH 29/40] build(wasm): make cc/bindgen optional, fix wasm dead-code (#161 goal 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build hygiene for the post-M9-3 pure-Rust wasm target: - Make `cc` and `bindgen` optional build-dependencies, enabled only by the `ffi-backend` feature (`dep:cc`, `dep:bindgen`); gate their build.rs usage with `#[cfg(feature = "ffi-backend")]`. The default / wasm32 build now pulls no C/C++ toolchain crates (verified via `cargo tree --target wasm32-unknown-unknown`). The FFI backend still builds. - Gate `PARALLEL_MIN_PIXELS` with `#[cfg(not(target_arch = "wasm32"))]` — it is only used by the rayon dispatchers (already wasm-gated from #207), so it was dead code on wasm and warned there. Verified: `cargo build --target wasm32-unknown-unknown -p webarkitlib-wasm` clean (no warnings, no bindgen/cc); `cargo build -p webarkitlib-rs --features ffi-backend` still compiles the C++; `cargo clippy --all-targets --all-features -D warnings` clean. Refs #161. Co-Authored-By: Claude Fable 5 --- crates/core/Cargo.toml | 8 +++++--- crates/core/build.rs | 10 +++++++++- crates/core/src/kpm/freak/gaussian_pyramid.rs | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 24d9dfb..307ec53 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -55,8 +55,10 @@ env_logger = { version = "0.11", optional = true } console_log = { version = "1", optional = true } [build-dependencies] -cc = "1" -bindgen = "0.72.1" +# Only needed for the C++ FFI backend; gated behind `ffi-backend` so the +# default (pure-Rust, incl. wasm32) build pulls no C/C++ toolchain crates. +cc = { version = "1", optional = true } +bindgen = { version = "0.72.1", optional = true } [features] # Default features intentionally empty (M9-3, #142): a plain `cargo build` @@ -70,7 +72,7 @@ simd-pattern = [] simd-wasm32 = [] simd-x86-sse41 = [] simd-x86-avx2 = [] -ffi-backend = [] +ffi-backend = ["dep:cc", "dep:bindgen"] # dual-mode validates pure-Rust implementations against the C++ baseline; # requires ffi-backend so the C++ comparison functions are compiled in. dual-mode = ["ffi-backend"] diff --git a/crates/core/build.rs b/crates/core/build.rs index 9e8e37f..e26322d 100644 --- a/crates/core/build.rs +++ b/crates/core/build.rs @@ -34,16 +34,23 @@ * */ +#[cfg(feature = "ffi-backend")] use std::env; +#[cfg(feature = "ffi-backend")] use std::path::PathBuf; fn main() { - if cfg!(feature = "ffi-backend") { + // `cc` / `bindgen` are optional build-dependencies enabled only by the + // `ffi-backend` feature, so the default (pure-Rust, incl. wasm) build pulls + // no C/C++ toolchain crates. The helpers below are gated to match. + #[cfg(feature = "ffi-backend")] + { build_freak_matcher(); generate_bindings(); } } +#[cfg(feature = "ffi-backend")] fn build_freak_matcher() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -160,6 +167,7 @@ fn build_freak_matcher() { println!("cargo:rerun-if-changed=src/kpm/kpm_c_api.h"); } +#[cfg(feature = "ffi-backend")] fn generate_bindings() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let header = manifest_dir.join("src").join("kpm").join("kpm_c_api.h"); diff --git a/crates/core/src/kpm/freak/gaussian_pyramid.rs b/crates/core/src/kpm/freak/gaussian_pyramid.rs index 49a60ea..cb2d1e9 100644 --- a/crates/core/src/kpm/freak/gaussian_pyramid.rs +++ b/crates/core/src/kpm/freak/gaussian_pyramid.rs @@ -288,6 +288,10 @@ const INV_256: f32 = 1.0 / 256.0; /// 640×480 (~0.3 MPx) but scales ~1.5× at 1280×720 (~0.9 MPx); this /// conservative threshold keeps ≤480p serial and is a machine-dependent /// heuristic, not a hard tuning point. +/// +/// Only referenced by the rayon dispatchers, which are gated off on wasm32 +/// (single-threaded), so the const is too. +#[cfg(not(target_arch = "wasm32"))] const PARALLEL_MIN_PIXELS: usize = 600_000; // ── f32 → f32 per-row helpers ──────────────────────────────────────────── From 5da470c2665a86f1a653c3546c875c56a4307642 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 18:24:52 +0200 Subject: [PATCH 30/40] feat(wasm): wire console logger + clean SIMD wasm build; add .fset3 asset (#161 goals 2,3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Logger (goal 2): enable `log-helpers` by default on the wasm crate and call `ar_log_init_wasm()` from `init_wasm()` so `arlog_*!` output reaches the browser console via `console_log`. - SIMD wiring (goal 3): validated the `+simd128 --features simd` wasm build (the build-dual.sh "SIMD" variant). Silenced two pre-existing unreachable-fallback warnings surfaced by that build in the `rgba_to_gray` and `dot_product` dispatchers (`#[allow(unreachable_code)]`, matching the pyramid dispatchers) — wasm `+simd128` build is now warning-free. - Add `crates/wasm/www/assets/pinball.fset3` (KPM reference data; required by detection — was missing from the demo assets). Refs #161. Co-Authored-By: Claude Fable 5 --- crates/core/src/ar/image_proc.rs | 3 +++ crates/core/src/ar/pattern.rs | 3 +++ crates/wasm/Cargo.toml | 5 ++++- crates/wasm/src/lib.rs | 3 +++ crates/wasm/www/assets/pinball.fset3 | Bin 0 -> 640324 bytes 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 crates/wasm/www/assets/pinball.fset3 diff --git a/crates/core/src/ar/image_proc.rs b/crates/core/src/ar/image_proc.rs index 55a4251..801f752 100644 --- a/crates/core/src/ar/image_proc.rs +++ b/crates/core/src/ar/image_proc.rs @@ -397,6 +397,9 @@ pub fn rgba_to_gray(rgba: &[u8]) -> Vec { } } + // On wasm32+simd128 the block above always returns, making this fallback + // unreachable; it is the active path on every other target. + #[allow(unreachable_code)] rgba_to_gray_scalar(rgba) } diff --git a/crates/core/src/ar/pattern.rs b/crates/core/src/ar/pattern.rs index d502942..59beae5 100644 --- a/crates/core/src/ar/pattern.rs +++ b/crates/core/src/ar/pattern.rs @@ -1062,6 +1062,9 @@ fn dot_product(a: &[i16], b: &[i16]) -> i64 { } } + // On wasm32+simd128 the block above always returns, making this fallback + // unreachable; it is the active path on every other target. + #[allow(unreachable_code)] dot_product_scalar(a, b) } diff --git a/crates/wasm/Cargo.toml b/crates/wasm/Cargo.toml index d2fe98b..1317504 100644 --- a/crates/wasm/Cargo.toml +++ b/crates/wasm/Cargo.toml @@ -24,7 +24,10 @@ serde-wasm-bindgen = "0.6" webarkitlib-rs = { path = "../core" } [features] -default = [] +default = ["log-helpers"] +# Routes `arlog_*!` output to the browser console via `console_log` +# (`ar_log_init_wasm()`), installed by `init_wasm()`. +log-helpers = ["webarkitlib-rs/log-helpers"] simd = ["webarkitlib-rs/simd"] [dev-dependencies] diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index f36a03b..0ddf972 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -85,6 +85,9 @@ pub fn init_panic_hook() { #[wasm_bindgen] pub fn init_wasm() { console_error_panic_hook::set_once(); + // Route arlog_*! output to the browser console (DevTools) via console_log. + #[cfg(all(feature = "log-helpers", target_arch = "wasm32"))] + webarkitlib_rs::arlog::ar_log_init_wasm(); print_version(); } diff --git a/crates/wasm/www/assets/pinball.fset3 b/crates/wasm/www/assets/pinball.fset3 new file mode 100644 index 0000000000000000000000000000000000000000..0291b4ad59788cad0dcbd7f2501499a83d379ff7 GIT binary patch literal 640324 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

ctvM%GmpxXKHOkNks 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

VtbrB%H(hD%bLhM8Haf?5BZY7T|E1Cqc1oY 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ℜ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

Y%f{G`Y|C1GFqmeUW_L!lo8^rM&OuSA~T@rGN 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�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

%L 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%0ANwT`S|w_jkVx>tgByS&mg$s7I(fL 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 z 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žyMP{)t+qB)R|n=2pYQyNfBBFwdu-_8gLiinQB^Zqfs{ZV@=HCG6P;h(}*vVK-?P5&vUh--cZ*1~FHbwH#Ova{95 z7o=mH!Nj>L1wcUz$R*|{hMWT?bJKncEi+K)f^=OzIjB`6Q^h(FIZ2HYJ)XMQT3z3R zhgtmO9^ntaekx#`W0=Btx%Eb=X4VuLu~v`NRP|SDI02o^)=bll6#CKj99%K*hkI{j@)HT2Xc_Te%Rib`Jr&}nO-TZ zqZ@{#Z~vO|QN;74DE?N;_1RBp1dK7hIv6yHYEQYn;Ay>KI55Ch2eTWcL9k!ASq<^7 zvl~1uD1ex=A1vvLorHE09|3z-7KFeM=6C=5WaW4?E429H$EDu z&tNDhHW+o-n?Qz1fPuwlAqNGUH%{8CPgaM=g}4D2230&l|Q0LkBAMdX sn1L4<;#T4HWnAo*5%{|T7n`d)xDxQ1t;i`{&A1qzm*V<6F7{pi3ng&EcmMzZ literal 0 HcmV?d00001 From c088fa97df863fe7ac25988b58ca67b621000c1f Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 19:18:39 +0200 Subject: [PATCH 31/40] feat(wasm): KPM detection binding + browser NFT demo (#161 goal 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wasm NFT scaffolding only did AR2 tracking from a hardcoded pose — there was no KPM detection binding. Add it so the browser demo runs the real pure-Rust KPM detection (the simple_nft.rs steps 3a + 4). - core: add `KpmRefDataSet::load_from_bytes` (refactor `load` to share a reader-based parser) so `.fset3` can be loaded from fetched bytes in the browser (no filesystem). Native parity test vs `load(path)`. - wasm: add `WasmKpmHandle` — `new(param, w, h)` builds a `RustFreakMatcher` + `KpmHandle`; `load_ref_data(.fset3 bytes)`; `detect(rgba)` runs `kpm_matching` and returns `{ pose:[12], page, error }` (or null). - demo: `www/simple_nft_example.html` now fetches `pinball.fset3`, creates a `WasmKpmHandle`, and the "Detect (KPM)" button runs real detection, prints the 3x4 pose, and feeds it into AR2 tracking (replacing the hardcoded pose). - test: headless `wasm-bindgen-test` (run via `wasm-pack test`) covering construction + `.fset3` load + pre-load detect guard. Verified in-browser (manual): KPM detection on pinball-demo.jpg yields page 0, error 5.0879, and a 3x4 pose matching the native simple_nft pipeline (~5.09 error) — the pure-Rust KPM->pose pipeline runs end-to-end in the browser. Also: `cargo build --target wasm32-unknown-unknown -p webarkitlib-wasm --tests` clean; `cargo clippy --all-targets --all-features -D warnings` clean; native kpm tests green. Closes #161. Co-Authored-By: Claude Fable 5 --- crates/core/src/kpm/ref_data_set.rs | 38 ++++++- crates/wasm/src/lib.rs | 125 ++++++++++++++++++++++++ crates/wasm/tests/kpm_detection.rs | 79 +++++++++++++++ crates/wasm/www/simple_nft_example.html | 71 +++++++++----- 4 files changed, 286 insertions(+), 27 deletions(-) create mode 100644 crates/wasm/tests/kpm_detection.rs diff --git a/crates/core/src/kpm/ref_data_set.rs b/crates/core/src/kpm/ref_data_set.rs index 6a035d7..aeb7bf4 100644 --- a/crates/core/src/kpm/ref_data_set.rs +++ b/crates/core/src/kpm/ref_data_set.rs @@ -45,7 +45,7 @@ //! [`merge`](KpmRefDataSet::merge), and //! [`change_page_no`](KpmRefDataSet::change_page_no). -use std::io::{Read, Write}; +use std::io::Write; use std::path::Path; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -405,8 +405,18 @@ impl KpmRefDataSet { /// Load a data set from a binary file written by [`save`](Self::save). pub fn load(path: &Path) -> std::io::Result { let file = std::fs::File::open(path)?; - let mut r = std::io::BufReader::new(file); + Self::load_from_reader(std::io::BufReader::new(file)) + } + + /// Load a data set from in-memory bytes (e.g. a `.fset3` fetched in the + /// browser, where there is no filesystem). Same format as [`load`](Self::load). + pub fn load_from_bytes(bytes: &[u8]) -> std::io::Result { + Self::load_from_reader(std::io::Cursor::new(bytes)) + } + /// Parse a data set from any reader. Shared by [`load`](Self::load) and + /// [`load_from_bytes`](Self::load_from_bytes). + fn load_from_reader(mut r: R) -> std::io::Result { let num = r.read_i32::()?; if num <= 0 { return Err(std::io::Error::new( @@ -728,4 +738,28 @@ mod tests { // The test data lives in crates/kpm/examples/data/ and CARGO_MANIFEST_DIR // now points to crates/core/. The test is covered by // crates/kpm/tests/regression.rs::test_fset3_load_structure. + + /// `load_from_bytes` (the wasm-friendly loader added for #161) must parse a + /// real `.fset3` identically to the path-based `load`. + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(miri, ignore)] // reads + parses a 625 KB .fset3 — too slow under Miri + #[test] + fn test_load_from_bytes_matches_load() { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("Data") + .join("pinball.fset3"); + let from_path = KpmRefDataSet::load(&path).expect("load(path) failed"); + let bytes = std::fs::read(&path).expect("read failed"); + let from_bytes = KpmRefDataSet::load_from_bytes(&bytes).expect("load_from_bytes failed"); + + assert_eq!(from_path.num, from_bytes.num); + assert_eq!(from_path.page_num, from_bytes.page_num); + assert_eq!(from_path.ref_point.len(), from_bytes.ref_point.len()); + // Spot-check the first ref point round-trips identically. + assert_eq!( + from_path.ref_point[0].feature_vec.v, + from_bytes.ref_point[0].feature_vec.v + ); + } } diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index 0ddf972..8ffbdcf 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -62,6 +62,12 @@ use webarkitlib_rs::types::{ }; use webarkitlib_rs::version; +// KPM detection (the `simple_nft.rs` steps 3a + 4 surface). +use std::sync::Arc; +use webarkitlib_rs::kpm::ref_data_set::KPM_CHANGE_PAGE_NO_ALL_PAGES; +use webarkitlib_rs::kpm::types::KpmRefDataSet; +use webarkitlib_rs::kpm::{KpmHandle, RustFreakMatcher}; + /// Returns the current version string of the library. #[wasm_bindgen] pub fn get_version() -> String { @@ -643,3 +649,122 @@ impl Drop for WasmNFTHandle { } } } + +/// Result of [`WasmKpmHandle::detect`]: the detected 3×4 pose (row-major, 12 +/// floats), the matched page number, and the matching error. +#[derive(serde::Serialize)] +struct KpmDetectResult { + pose: [f32; 12], + page: i32, + error: f32, +} + +/// KPM (Keypoint Matching) detection handle — the WASM equivalent of +/// `simple_nft.rs` steps 3a + 4. Loads NFT reference data (`.fset3`) and +/// detects the marker's initial 3×4 pose in a query frame using the pure-Rust +/// [`RustFreakMatcher`]. +/// +/// Pair with [`WasmNFTHandle`] for AR2 tracking: feed `detect()`'s pose into +/// [`WasmNFTHandle::set_initial_pose`]. +#[wasm_bindgen] +pub struct WasmKpmHandle { + handle: KpmHandle, + width: i32, + height: i32, + loaded: bool, +} + +#[wasm_bindgen] +impl WasmKpmHandle { + /// Create a KPM detection handle. + /// + /// * `param_bytes` — `camera_para.dat` contents. + /// * `width` / `height` — query-frame size in pixels. + #[wasm_bindgen(constructor)] + pub fn new(param_bytes: &[u8], width: i32, height: i32) -> Result { + let cursor = Cursor::new(param_bytes); + let mut param = ARParam::load(cursor) + .map_err(|e| JsValue::from_str(&format!("Failed to load camera param: {}", e)))?; + + // Scale camera params to the frame size (arParamChangeSize equivalent). + let sx = width as f64 / param.xsize as f64; + let sy = height as f64 / param.ysize as f64; + for col in 0..4 { + param.mat[0][col] *= sx; + param.mat[1][col] *= sy; + } + param.xsize = width; + param.ysize = height; + + let param_lt = Arc::new(ARParamLT::new_basic(param)); + let backend = RustFreakMatcher::new(width, height) + .map_err(|e| JsValue::from_str(&format!("Failed to create FREAK matcher: {:?}", e)))?; + let handle = KpmHandle::new(width, height, Some(param_lt), Box::new(backend)); + + Ok(WasmKpmHandle { + handle, + width, + height, + loaded: false, + }) + } + + /// Load NFT reference data from `.fset3` bytes (KPM reference keypoints). + /// All pages are remapped to page 0 (single-marker setup). + pub fn load_ref_data(&mut self, fset3_bytes: &[u8]) -> Result<(), JsValue> { + let mut ref_data = KpmRefDataSet::load_from_bytes(fset3_bytes) + .map_err(|e| JsValue::from_str(&format!("Failed to load .fset3: {}", e)))?; + ref_data.change_page_no(KPM_CHANGE_PAGE_NO_ALL_PAGES, 0); + self.handle + .set_ref_data_set(ref_data) + .map_err(|e| JsValue::from_str(&format!("Failed to set ref data: {:?}", e)))?; + self.loaded = true; + Ok(()) + } + + /// Run KPM detection on an RGBA frame (`width * height * 4` bytes, e.g. a + /// canvas `ImageData.data` buffer). + /// + /// Returns `{ pose: number[12], page, error }` on a match, or `null` if no + /// marker was found. + pub fn detect(&mut self, rgba_bytes: &[u8]) -> Result { + if !self.loaded { + return Err(JsValue::from_str( + "reference data not loaded — call load_ref_data first", + )); + } + let expected = (self.width * self.height * 4) as usize; + if rgba_bytes.len() != expected { + return Err(JsValue::from_str(&format!( + "rgba length {} != expected {} ({}x{}x4)", + rgba_bytes.len(), + expected, + self.width, + self.height + ))); + } + + let luma = rgba_to_gray(rgba_bytes); + self.handle + .kpm_matching(&luma) + .map_err(|e| JsValue::from_str(&format!("kpm_matching failed: {:?}", e)))?; + + match self.handle.get_pose() { + Some((cam_pose, page, error)) => { + let mut pose = [0f32; 12]; + for (r, row) in cam_pose.iter().enumerate() { + pose[r * 4..r * 4 + 4].copy_from_slice(row); + } + let result = KpmDetectResult { pose, page, error }; + serde_wasm_bindgen::to_value(&result) + .map_err(|e| JsValue::from_str(&format!("serialize failed: {}", e))) + } + None => Ok(JsValue::NULL), + } + } + + /// Whether reference data has been loaded. + pub fn is_loaded(&self) -> bool { + self.loaded + } +} diff --git a/crates/wasm/tests/kpm_detection.rs b/crates/wasm/tests/kpm_detection.rs new file mode 100644 index 0000000..106d7af --- /dev/null +++ b/crates/wasm/tests/kpm_detection.rs @@ -0,0 +1,79 @@ +/* + * kpm_detection.rs + * WebARKitLib-rs + * + * This file is part of WebARKitLib-rs - WebARKit. + * + * WebARKitLib-rs is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * WebARKitLib-rs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with WebARKitLib-rs. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2026 WebARKit. + * + * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt + * + */ + +//! Headless wasm-bindgen tests for the KPM detection binding (#161). +//! +//! Run with: `wasm-pack test --node` (or `--headless --chrome`) from +//! `crates/wasm`. These exercise `WasmKpmHandle` in a real wasm runtime — +//! construction (camera-param parse + FREAK matcher init) and reference-data +//! loading from in-memory `.fset3` bytes (`KpmRefDataSet::load_from_bytes`). +//! +//! Full `detect()` is covered end-to-end by the native `simple_nft` pipeline +//! (same pure-Rust code) and the browser demo (`www/simple_nft_example.html`); +//! it needs a decoded RGBA frame, out of scope for this lightweight runtime +//! check. + +#![cfg(target_arch = "wasm32")] + +use wasm_bindgen_test::*; +use webarkitlib_wasm::WasmKpmHandle; + +/// Demo assets, baked into the test binary. +const CAMERA_PARA: &[u8] = include_bytes!("../www/assets/camera_para.dat"); +const PINBALL_FSET3: &[u8] = include_bytes!("../www/assets/pinball.fset3"); + +#[wasm_bindgen_test] +fn kpm_handle_constructs_and_loads_ref_data() { + let mut handle = + WasmKpmHandle::new(CAMERA_PARA, 640, 480).expect("WasmKpmHandle::new should succeed"); + assert!(!handle.is_loaded(), "should start unloaded"); + + handle + .load_ref_data(PINBALL_FSET3) + .expect("load_ref_data should parse the .fset3 bytes"); + assert!(handle.is_loaded(), "should be loaded after load_ref_data"); +} + +#[wasm_bindgen_test] +fn detect_before_load_errors() { + let mut handle = WasmKpmHandle::new(CAMERA_PARA, 4, 4).expect("new"); + // 4x4x4 RGBA; detection must refuse before reference data is loaded. + let rgba = [0u8; 4 * 4 * 4]; + assert!( + handle.detect(&rgba).is_err(), + "detect should error before load_ref_data" + ); +} diff --git a/crates/wasm/www/simple_nft_example.html b/crates/wasm/www/simple_nft_example.html index f4ebe8f..55dda8d 100644 --- a/crates/wasm/www/simple_nft_example.html +++ b/crates/wasm/www/simple_nft_example.html @@ -153,7 +153,7 @@

WebARKitLib.rs – NFT Tracking Example

- +
@@ -180,17 +180,10 @@

WebARKitLib.rs – NFT Tracking Example

const resultsEl = document.getElementById('results'); let nftHandle = null; + let kpmHandle = null; let testImage = new Image(); let wasmModule = null; - // A known-good initial pose for the pinball marker - // (from the KPM detection step in the native example). - const TEST_POSE = [ - 0.9866, 0.1643, 0.0027, -181.9168, - 0.1531, -0.9197, -0.3612, 64.6862, - -0.0563, 0.3568, -0.9325, 590.5905 - ]; - async function run() { try { statusEl.innerText = 'Status: Loading WASM module...'; @@ -236,40 +229,46 @@

WebARKitLib.rs – NFT Tracking Example

const paramRes = await fetch('assets/camera_para.dat'); const paramData = new Uint8Array(await paramRes.arrayBuffer()); - // Fetch NFT marker data. - const [isetRes, fsetRes] = await Promise.all([ + // Fetch NFT marker data: .iset/.fset for AR2 tracking, + // .fset3 for KPM detection. + const [isetRes, fsetRes, fset3Res] = await Promise.all([ fetch('assets/pinball.iset'), fetch('assets/pinball.fset'), + fetch('assets/pinball.fset3'), ]); const isetData = new Uint8Array(await isetRes.arrayBuffer()); const fsetData = new Uint8Array(await fsetRes.arrayBuffer()); + const fset3Data = new Uint8Array(await fset3Res.arrayBuffer()); console.log(`[NFT] Loaded: camera_para.dat (${paramData.length} bytes)`); console.log(`[NFT] Loaded: pinball.iset (${isetData.length} bytes)`); console.log(`[NFT] Loaded: pinball.fset (${fsetData.length} bytes)`); + console.log(`[NFT] Loaded: pinball.fset3 (${fset3Data.length} bytes)`); - // Create NFT handle. + // Create handles. const width = canvas.width; const height = canvas.height; nftHandle = new wasmModule.WasmNFTHandle(paramData, width, height); + kpmHandle = new wasmModule.WasmKpmHandle(paramData, width, height); - // Load marker data. + // Load marker data: surface set (tracking) + KPM ref data (detection). nftHandle.load_nft_marker(isetData, fsetData); + kpmHandle.load_ref_data(fset3Data); const mw = nftHandle.get_marker_width(); const mh = nftHandle.get_marker_height(); const dpi = nftHandle.get_marker_dpi(); resultsEl.innerText = - `NFT Handle initialized:\n` + + `NFT + KPM handles initialized:\n` + ` Frame size: ${width}x${height}\n` + ` Marker: ${mw}x${mh} @ ${dpi} DPI\n` + ` Loaded: ✓\n\n` + - `Click "Set Test Pose" to provide an initial pose,\n` + - `then "Track Frame" to run AR2 tracking.`; + `Click "Detect (KPM)" to detect the marker pose,\n` + + `then "Track Frame" to run AR2 tracking refinement.`; - statusEl.innerText = 'Status: NFT marker loaded. Set a pose to begin tracking.'; + statusEl.innerText = 'Status: NFT marker loaded. Run KPM detection to begin.'; statusEl.className = 'status success'; btnInit.disabled = true; btnSetPose.disabled = false; @@ -283,18 +282,40 @@

WebARKitLib.rs – NFT Tracking Example

}; btnSetPose.onclick = () => { - if (!nftHandle) return; + if (!nftHandle || !kpmHandle) return; try { - nftHandle.set_initial_pose(new Float32Array(TEST_POSE)); + // Real KPM detection on the current frame (#161 goal 4): + // canvas RGBA -> WasmKpmHandle.detect() -> 3x4 pose. + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const rgba = new Uint8Array(imageData.data.buffer); - resultsEl.innerText = - `Initial pose set (from KPM detection):\n\n` + - formatMatrix(TEST_POSE) + - `\nContinuity: ${nftHandle.get_cont_num()} frame(s)\n\n` + - `Click "Track Frame" to run AR2 tracking refinement.`; + const t0 = performance.now(); + const det = kpmHandle.detect(rgba); + const elapsed = performance.now() - t0; + + if (!det) { + resultsEl.innerText = + `KPM detection: no marker found (${elapsed.toFixed(1)} ms).`; + statusEl.innerText = 'Status: KPM found no match.'; + statusEl.className = 'status error'; + return; + } - statusEl.innerText = 'Status: Initial pose set. Ready to track.'; + console.log('[KPM] detected pose', det.pose, 'page', det.page, 'error', det.error); + + // Feed the detected pose into the AR2 tracker. + nftHandle.set_initial_pose(new Float32Array(det.pose)); + + resultsEl.innerText = + `KPM detection (${elapsed.toFixed(1)} ms):\n` + + ` Page: ${det.page}\n` + + ` Error: ${det.error.toFixed(4)}\n\n` + + `Detected 3×4 pose:\n` + + formatMatrix(det.pose) + + `\nInitial pose set. Click "Track Frame" for AR2 refinement.`; + + statusEl.innerText = 'Status: KPM detected pose. Ready to track.'; statusEl.className = 'status success'; btnTrack.disabled = false; From d511d000a25b7534c68d824515aaadcbc342e702 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 22 Jun 2026 22:57:17 +0200 Subject: [PATCH 32/40] doc(wasm): note .fset3 / KPM detection in the NFT demo description (#161) Update the demo's intro text to describe the full KPM-detection -> AR2-tracking pipeline and that `.fset3` (KPM reference keypoints) is loaded for detection, matching the actual "Detect (KPM)" behavior. Co-Authored-By: Claude Fable 5 --- crates/wasm/www/simple_nft_example.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/wasm/www/simple_nft_example.html b/crates/wasm/www/simple_nft_example.html index 55dda8d..dee49fa 100644 --- a/crates/wasm/www/simple_nft_example.html +++ b/crates/wasm/www/simple_nft_example.html @@ -133,14 +133,16 @@

WebARKitLib.rs – NFT Tracking Example

- This demo demonstrates AR2 Natural Feature Tracking running - entirely in Rust/WASM. The AR2 tracking engine uses template matching on - image pyramids to refine a camera pose. + This demo demonstrates the full NFT pipeline — KPM detection then + AR2 tracking — running entirely in Rust/WASM. KPM keypoint matching + finds the marker and its initial pose; the AR2 engine then refines that pose + via template matching on image pyramids.

- The pipeline loads .iset (image pyramid) and .fset - (feature points) marker data, accepts an initial 3×4 camera pose, and runs - per-frame tracking to produce a refined pose. + The pipeline loads .fset3 (KPM reference keypoints, used for + detection), plus .iset (image pyramid) and .fset + (feature points) for tracking. "Detect (KPM)" produces the initial 3×4 pose; + "Track Frame" then runs per-frame AR2 refinement.

Note: Full NFT detection (KPM) requires a pure-Rust FREAK From 03fd9d7c3821fcce1ea0ecaa68b68ac883a277ec Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 23 Jun 2026 21:35:30 +0200 Subject: [PATCH 33/40] feat(kpm): facade-parity convenience accessors on VisualDatabase + FeatureStore slices (#148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror `VisualDatabaseFacade`'s Group-A accessors so the Rust port is a drop-in for the C++ facade surface, and callers don't have to loop `0..num_features()` element-by-element. Pure additive getters — no behavior change. - `FeatureStore`: add slice accessors `points() -> &[FeaturePoint]` and `descriptors() -> &[u8]` (counterparts to per-element `point(i)`/`descriptor(i)`). - `VisualDatabase`: add `get_feature_points(id)`, `get_descriptors(id)`, `get_query_feature_points()`, `get_query_descriptors()`, `get_width(id)`, `get_height(id)` (Option-returning, 1-liners over the slice accessors). - Tests for both (deterministic, no image pipeline). Group B (3D feature points — RustFreakMatcher/NFT domain) and Group C (computeFreakFeaturesAndDescriptors — no caller) are out of scope. Refs #148. Co-Authored-By: Claude Fable 5 --- crates/core/src/kpm/freak/matcher.rs | 37 ++++++++ crates/core/src/kpm/freak/visual_database.rs | 91 ++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/crates/core/src/kpm/freak/matcher.rs b/crates/core/src/kpm/freak/matcher.rs index 80820b7..acb739c 100644 --- a/crates/core/src/kpm/freak/matcher.rs +++ b/crates/core/src/kpm/freak/matcher.rs @@ -133,6 +133,22 @@ impl FeatureStore { pub fn bytes_per_feature(&self) -> usize { self.bytes_per_feature } + + /// Returns all feature points as a slice. + /// + /// Slice counterpart to [`point`](Self::point) — avoids forcing callers to + /// loop `0..num_features()` element-by-element (facade parity, #148). + pub fn points(&self) -> &[FeaturePoint] { + &self.points + } + + /// Returns the flat descriptor buffer (`num_features() * bytes_per_feature()` + /// bytes, descriptor `i` at `[i * bpf .. (i+1) * bpf]`). + /// + /// Slice counterpart to [`descriptor`](Self::descriptor) (#148). + pub fn descriptors(&self) -> &[u8] { + &self.descriptors + } } // ============================================================================ @@ -583,6 +599,27 @@ mod tests { assert!(!s.point(2).maxima); } + #[test] + fn test_feature_store_slice_accessors() { + let mut s = FeatureStore::new(96).unwrap(); + let d0 = make_descriptor(0); + let d1 = make_descriptor(1); + s.add(fp(0.0, 0.0, true), &d0).unwrap(); + s.add(fp(1.0, 1.0, false), &d1).unwrap(); + + // points() slice agrees with per-element point(i). + let pts = s.points(); + assert_eq!(pts.len(), 2); + assert_eq!(pts[0].x, s.point(0).x); + assert!(!pts[1].maxima); + + // descriptors() flat buffer agrees with per-element descriptor(i). + let descs = s.descriptors(); + assert_eq!(descs.len(), 2 * 96); + assert_eq!(&descs[0..96], s.descriptor(0)); + assert_eq!(&descs[96..192], s.descriptor(1)); + } + #[test] fn test_feature_store_add_wrong_size() { let mut s = FeatureStore::new(96).unwrap(); diff --git a/crates/core/src/kpm/freak/visual_database.rs b/crates/core/src/kpm/freak/visual_database.rs index b399811..dbc68c3 100644 --- a/crates/core/src/kpm/freak/visual_database.rs +++ b/crates/core/src/kpm/freak/visual_database.rs @@ -667,6 +667,54 @@ impl VisualDatabase { pub fn keyframe(&self, id: usize) -> Option<&Keyframe> { self.keyframes.get(&id) } + + // ── Facade-parity convenience accessors (#148, Group A) ────────────── + // Thin getters mirroring `VisualDatabaseFacade`, so callers don't have to + // reach through `keyframe(id).store` and loop element-by-element. + + /// Feature points of the reference keyframe stored under `id`. + /// + /// C equivalent: `getFeaturePoints(id)`. + pub fn get_feature_points(&self, id: usize) -> Option<&[FeaturePoint]> { + self.keyframes.get(&id).map(|kf| kf.store.points()) + } + + /// Flat descriptor buffer of the reference keyframe stored under `id`. + /// + /// C equivalent: `getDescriptors(id)`. + pub fn get_descriptors(&self, id: usize) -> Option<&[u8]> { + self.keyframes.get(&id).map(|kf| kf.store.descriptors()) + } + + /// Feature points of the most recent query frame (see [`query`](Self::query)). + /// + /// C equivalent: `getQueryFeaturePoints()`. + pub fn get_query_feature_points(&self) -> Option<&[FeaturePoint]> { + self.query_keyframe.as_ref().map(|kf| kf.store.points()) + } + + /// Flat descriptor buffer of the most recent query frame. + /// + /// C equivalent: `getQueryDescriptors()`. + pub fn get_query_descriptors(&self) -> Option<&[u8]> { + self.query_keyframe + .as_ref() + .map(|kf| kf.store.descriptors()) + } + + /// Width of the reference image stored under `id`. + /// + /// C equivalent: `getWidth(id)`. + pub fn get_width(&self, id: usize) -> Option { + self.keyframes.get(&id).map(|kf| kf.width) + } + + /// Height of the reference image stored under `id`. + /// + /// C equivalent: `getHeight(id)`. + pub fn get_height(&self, id: usize) -> Option { + self.keyframes.get(&id).map(|kf| kf.height) + } } impl Default for VisualDatabase { @@ -1019,6 +1067,49 @@ mod tests { assert!(db.keyframe(0).unwrap().index().is_some()); } + #[test] + fn test_facade_parity_accessors() { + // Build a tiny keyframe directly (no image pipeline) so the getters are + // exercised deterministically and fast (#148, Group A). + let mut kf = Keyframe::new(640, 480).unwrap(); + let p0 = FeaturePoint { + x: 1.0, + y: 2.0, + angle: 0.0, + scale: 1.0, + maxima: true, + }; + let p1 = FeaturePoint { + x: 3.0, + y: 4.0, + angle: 0.0, + scale: 1.0, + maxima: false, + }; + kf.store.add(p0, &[7u8; 96]).unwrap(); + kf.store.add(p1, &[9u8; 96]).unwrap(); + + let mut db = VisualDatabase::new().expect("new"); + db.add_keyframe(kf, 0).expect("add_keyframe"); + + // Reference accessors for the stored id. + assert_eq!(db.get_width(0), Some(640)); + assert_eq!(db.get_height(0), Some(480)); + assert_eq!(db.get_feature_points(0).unwrap().len(), 2); + assert_eq!(db.get_feature_points(0).unwrap()[0].x, 1.0); + let descs = db.get_descriptors(0).unwrap(); + assert_eq!(descs.len(), 2 * 96); + assert_eq!(&descs[0..96], &[7u8; 96]); + + // Missing id → None. + assert!(db.get_feature_points(99).is_none()); + assert!(db.get_width(99).is_none()); + + // No query run yet → query accessors are None. + assert!(db.get_query_feature_points().is_none()); + assert!(db.get_query_descriptors().is_none()); + } + // ----------------------------------------------------------------- // Dual-mode parity test (M9-1 gate per #140, closed by M9 #146) // ----------------------------------------------------------------- From 5b6fbee7dd0ced48ba24aa2233239ab99ee53e6b Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 11:57:10 +0200 Subject: [PATCH 34/40] refactor(kpm): factor VisualDatabase::query matching loop + add query_from_keyframe (#147) (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recover part of the C++ query onion. Step 1 (no public API change): extract the per-keyframe matching loop into `match_against_database` and the per-query reset into `reset_query_state`. Step 2: expose `query_from_keyframe(Keyframe)` — runs only the matching loop against the database (skips pyramid build + feature extraction), for deterministic testing and callers that already hold a `Keyframe`. Mirrors C++ `query(const keyframe_t*)`. `query(&Matrix)` is now a thin wrapper (build keyframe → match → stash), behaviorally identical (existing query tests unchanged + green). Skipped per YAGNI: `query_from_pyramid` — KPM uses GaussianScaleSpacePyramid while AR2 uses BoxFilterPyramid8u, so there is no possible cross-pipeline caller (documented in #147). Test: query_from_keyframe self-matches an identical keyframe. Closes #147. Co-authored-by: Claude Fable 5 --- crates/core/src/kpm/freak/visual_database.rs | 82 +++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/crates/core/src/kpm/freak/visual_database.rs b/crates/core/src/kpm/freak/visual_database.rs index dbc68c3..adb8aff 100644 --- a/crates/core/src/kpm/freak/visual_database.rs +++ b/crates/core/src/kpm/freak/visual_database.rs @@ -354,10 +354,7 @@ impl VisualDatabase { /// /// C equivalent: `query(const Image&)` (`visual_database-inline.h:155`). pub fn query(&mut self, image: &Matrix) -> Result { - // Reset per-query state (C++ `visual_database-inline.h:194-195`). - self.inliers.clear(); - self.matched_db_id = -1; - self.matched_geometry = [0.0; 9]; + self.reset_query_state(); // Build the pyramid and the query keyframe. self.ensure_pyramid(image)?; @@ -368,12 +365,45 @@ impl VisualDatabase { query_kf.store.num_features() ); - // Iterate the database; the keyframe with the most inliers wins. - // Collect ids upfront because we need a `&mut self` for the matcher - // build inside the loop body. + let matched = self.match_against_database(&query_kf)?; + self.query_keyframe = Some(query_kf); + Ok(matched) + } + + /// Query with a pre-extracted [`Keyframe`] instead of a raw image. + /// + /// Skips pyramid construction and feature extraction, running only the + /// matching loop against the database. Useful for deterministic testing + /// (feed a hand-built keyframe) and for callers that already hold a + /// `Keyframe`. The supplied keyframe becomes the stashed `query_keyframe`. + /// + /// C equivalent: `query(const keyframe_t*)` (`visual_database.h:193`). + pub fn query_from_keyframe(&mut self, query_kf: Keyframe) -> Result { + self.reset_query_state(); + let matched = self.match_against_database(&query_kf)?; + self.query_keyframe = Some(query_kf); + Ok(matched) + } + + /// Reset per-query result state (C++ `visual_database-inline.h:194-195`). + fn reset_query_state(&mut self) { + self.inliers.clear(); + self.matched_db_id = -1; + self.matched_geometry = [0.0; 9]; + } + + /// Run the matching loop against every stored keyframe; the keyframe with + /// the most inliers (above `min_num_inliers`) wins. Assumes per-query state + /// was already reset; does not touch `query_keyframe`. + /// + /// Shared inner loop of [`query`](Self::query) and + /// [`query_from_keyframe`](Self::query_from_keyframe) + /// (C++ `visual_database-inline.h:200-241`). + fn match_against_database(&mut self, query_kf: &Keyframe) -> Result { + // Collect ids upfront because we need `&mut self` for try_match_one. let ids: Vec = self.keyframes.keys().copied().collect(); for id in ids { - if let Some((inliers, h)) = self.try_match_one(&query_kf, id)? { + if let Some((inliers, h)) = self.try_match_one(query_kf, id)? { if inliers.len() >= self.min_num_inliers && inliers.len() > self.inliers.len() { self.matched_geometry = h; self.inliers = inliers; @@ -381,8 +411,6 @@ impl VisualDatabase { } } } - - self.query_keyframe = Some(query_kf); Ok(self.matched_db_id >= 0) } @@ -1067,6 +1095,40 @@ mod tests { assert!(db.keyframe(0).unwrap().index().is_some()); } + #[test] + #[cfg_attr(miri, ignore)] // real-image pyramid + DoG + BHC pipeline — too slow under Miri + fn test_query_from_keyframe_self_matches() { + // #147: query_from_keyframe runs the matching loop on a pre-built + // keyframe. An identical keyframe must self-match the database entry. + let img = load_grayscale("../../benchmarks/data/found.jpg"); + let build_kf = |img: &Matrix| -> Keyframe { + let mut pyr = crate::kpm::freak::gaussian_pyramid::GaussianScaleSpacePyramid::new(3); + pyr.build(img).unwrap(); + let det = + crate::kpm::freak::detector::DoGScaleInvariantDetector::new(3.0, 4.0, 500, true); + let mut kf = Keyframe::new(img.cols as i32, img.rows as i32).unwrap(); + find_features(&mut kf, &pyr, &det).expect("find_features"); + kf + }; + + let mut db = VisualDatabase::new().expect("new"); + db.add_keyframe(build_kf(&img), 0).expect("add_keyframe"); + + let matched = db + .query_from_keyframe(build_kf(&img)) + .expect("query_from_keyframe"); + assert!(matched, "an identical keyframe must self-match"); + assert_eq!(db.matched_db_id(), 0); + assert!( + db.inliers().len() >= 8, + "self-match should be a strong match" + ); + assert!( + db.query_keyframe().is_some(), + "query_from_keyframe must stash the query keyframe" + ); + } + #[test] fn test_facade_parity_accessors() { // Build a tiny keyframe directly (no image pipeline) so the getters are From 2fcefa23cff35b05b6fb455cb4ec3f93cfced802 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 13:32:33 +0200 Subject: [PATCH 35/40] =?UTF-8?q?test(kpm):=20raise=20M9=20coverage=20?= =?UTF-8?q?=E2=80=94=20exclude=20examples=20+=20edge=20tests=20(#177)=20(#?= =?UTF-8?q?218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(kpm): factor VisualDatabase::query matching loop + add query_from_keyframe (#147) Recover part of the C++ query onion. Step 1 (no public API change): extract the per-keyframe matching loop into `match_against_database` and the per-query reset into `reset_query_state`. Step 2: expose `query_from_keyframe(Keyframe)` — runs only the matching loop against the database (skips pyramid build + feature extraction), for deterministic testing and callers that already hold a `Keyframe`. Mirrors C++ `query(const keyframe_t*)`. `query(&Matrix)` is now a thin wrapper (build keyframe → match → stash), behaviorally identical (existing query tests unchanged + green). Skipped per YAGNI: `query_from_pyramid` — KPM uses GaussianScaleSpacePyramid while AR2 uses BoxFilterPyramid8u, so there is no possible cross-pipeline caller (documented in #147). Test: query_from_keyframe self-matches an identical keyframe. Closes #147. Co-Authored-By: Claude Fable 5 * test(kpm): raise M9 coverage — exclude examples + edge tests (#177) - coverage: exclude `examples/**` from tarpaulin (the 30-line simple_nft_dual.rs diagnostic at 0% was the single biggest gap; examples are demos, not test code — the issue's preferred fix). - clustering: add a degenerate-input test (BHC build over all-identical descriptors → zero-distance k-medoids split + query). - visual_database: add a no-match test via query_from_keyframe (#147) — maximally-distant descriptors short-circuit try_match_one. Codecov on this PR confirms the resulting M9 module coverage; any residual per-file gaps (rust_backend DualFreakMatcher divergence, hough 1-liner) are quick follow-ups. Refs #177. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .github/workflows/coverage.yml | 1 + crates/core/src/kpm/freak/clustering.rs | 16 ++++++++++ crates/core/src/kpm/freak/visual_database.rs | 32 ++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f4436a7..488aaef 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,6 +33,7 @@ jobs: --output-dir coverage \ --timeout 300 \ --exclude-files "**/tests/*" \ + --exclude-files "**/examples/*" \ --ignore-panics - name: Upload coverage reports to Codecov diff --git a/crates/core/src/kpm/freak/clustering.rs b/crates/core/src/kpm/freak/clustering.rs index 50d4bf4..8945e44 100644 --- a/crates/core/src/kpm/freak/clustering.rs +++ b/crates/core/src/kpm/freak/clustering.rs @@ -876,6 +876,22 @@ mod tests { assert!(bhc.query(&feat).is_err()); } + #[test] + fn test_bhc_build_all_identical() { + // Degenerate input: many identical descriptors. k-medoids distances are + // all zero, so the split is degenerate — exercise that path (#177). + let feat = [42u8; 96]; + let owned: Vec<[u8; 96]> = (0..20).map(|_| feat).collect(); + let features: Vec<&[u8; 96]> = owned.iter().collect(); + let mut bhc = BinaryHierarchicalClustering::new().unwrap(); + bhc.build(&features).expect("build over identical features"); + let res = bhc.query(&feat).expect("query"); + assert!( + !res.is_empty(), + "query of an identical feature must return the leaf members" + ); + } + #[test] fn test_bhc_roundtrip() { let mut bhc = BinaryHierarchicalClustering::new().unwrap(); diff --git a/crates/core/src/kpm/freak/visual_database.rs b/crates/core/src/kpm/freak/visual_database.rs index adb8aff..19463a4 100644 --- a/crates/core/src/kpm/freak/visual_database.rs +++ b/crates/core/src/kpm/freak/visual_database.rs @@ -1129,6 +1129,38 @@ mod tests { ); } + #[test] + fn test_query_from_keyframe_no_match() { + // Deterministic, no image: a db keyframe with all-0x00 descriptors vs a + // query keyframe with all-0xFF descriptors — maximally distant, so the + // ratio test rejects every candidate and try_match_one short-circuits + // (no-match path, #177). + let mk = |desc: u8, n: usize| -> Keyframe { + let mut kf = Keyframe::new(640, 480).unwrap(); + for i in 0..n { + let p = FeaturePoint { + x: i as f32, + y: i as f32, + angle: 0.0, + scale: 1.0, + maxima: true, + }; + kf.store.add(p, &[desc; 96]).unwrap(); + } + kf + }; + + let mut db = VisualDatabase::new().expect("new"); + db.add_keyframe(mk(0x00, 12), 0).expect("add_keyframe"); + + let matched = db + .query_from_keyframe(mk(0xFF, 12)) + .expect("query_from_keyframe"); + assert!(!matched, "maximally-distant descriptors must not match"); + assert_eq!(db.matched_db_id(), -1); + assert!(db.inliers().is_empty()); + } + #[test] fn test_facade_parity_accessors() { // Build a tiny keyframe directly (no image pipeline) so the getters are From f756e158a1003f692a52fbe0250cd8c2f3fa58b2 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 24 Jun 2026 18:38:51 +0200 Subject: [PATCH 36/40] =?UTF-8?q?feat(example):=20nft=5Fmarker=5Fgen=20.fs?= =?UTF-8?q?et3=20via=20RustFreakMatcher=20=E2=80=94=20drop=20ffi-backend?= =?UTF-8?q?=20(#179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `nft_marker_gen` now produces a COMPLETE NFT marker (.iset + .fset + .fset3) in a single pure-Rust invocation, with no C++ toolchain. This removes the last user-facing dependence on `ffi-backend`. - Swap the `.fset3` step from `CppFreakMatcher` to `RustFreakMatcher` (`KpmRefDataSet::generate` is backend-agnostic — `&mut dyn FreakMatcherBackend`). Drop the `#[cfg(feature = "ffi-backend")]` gates on the `.fset3` block and the `KPM_SURF_FEATURE_DENSITY` const. - Remove the now-pointless `--yes` flag and the "no .fset3" warning/prompt. - Update the example header docs and README to the single-invocation workflow. Parity (per the agreed policy — accept tolerance if downstream pose holds): the pure-Rust path generated a complete marker for pinball-893x1117-120dpi.jpg (.fset3 667 KB vs the C++ 625 KB — comparable, not byte-identical). Downstream round-trip: feeding the Rust-generated .fset3 into simple_nft detection yields a KPM match (page 0, error 6.05 vs the C++ baseline 5.09) — the marker works. Rust↔C++ FREAK extraction parity is additionally CI-gated by `cross_stack_parity` (pose) and `dual-mode`. Closes #179. Co-Authored-By: Claude Fable 5 --- README.md | 37 +++++--------- crates/core/examples/nft_marker_gen.rs | 67 ++++++-------------------- 2 files changed, 27 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index aebd804..4a7a5db 100644 --- a/README.md +++ b/README.md @@ -123,42 +123,33 @@ This example loads a camera parameter file, a marker (pattern or barcode), and a Generate NFT (Natural Feature Tracking) marker files compatible with ARnft and NFT-Marker-Creator-App: ```bash -# Pure-Rust default (no C++ toolchain required) — produces .iset + .fset. -# Pass --yes to skip the interactive "no .fset3" confirmation prompt -# (recommended for scripted / CI use): +# Pure Rust, no C++ toolchain — produces a COMPLETE marker (.iset + .fset + .fset3): cargo run --release --example nft_marker_gen -- \ - --input path/to/image.jpg \ - --output path/to/output_name \ - --dpi 220 \ - --yes - -# Full output including .fset3 (FREAK descriptors) — opt-in C++ backend: -# .fset3 generation still uses CppFreakMatcher today; the runtime KPM -# tracker reads .fset3 files in pure Rust, only the marker-creation step -# needs the C++ FREAK extractor. Wiring nft_marker_gen to RustFreakMatcher -# for .fset3 is a follow-up task. -cargo run --release --features ffi-backend --example nft_marker_gen -- \ --input path/to/image.jpg \ --output path/to/output_name \ --dpi 220 # With SIMD acceleration + Rayon parallelism (recommended for x86_64): -cargo run --release --features "ffi-backend,simd-x86-sse41,simd-x86-avx2" \ +cargo run --release --features "simd-x86-sse41,simd-x86-avx2" \ --example nft_marker_gen -- \ --input path/to/image.jpg \ --output path/to/output_name \ --dpi 220 ``` +> Since #179, `.fset3` (FREAK descriptors) is generated by the pure-Rust +> `RustFreakMatcher` — a single invocation produces a complete marker with no +> C++ toolchain. + > **Important:** Always use the `--release` flag. Debug builds are 5–10× slower due to missing compiler optimizations. -Output files (depends on whether `ffi-backend` is enabled): +Output files (all produced on the pure-Rust default): -| File | Default (pure Rust) | With `ffi-backend` | Description | -|---|:---:|:---:|---| -| `.iset` | ✅ | ✅ | JPEG-compressed image pyramid (~300 KB, matches C++ `ar2WriteImageSet()` format) | -| `.fset` | ✅ | ✅ | Feature map with one entry per pyramid level | -| `.fset3` | ❌ (skipped, with warning) | ✅ | FREAK descriptors for KPM-based recognition | +| File | Description | +|---|---| +| `.iset` | JPEG-compressed image pyramid (~300 KB, matches C++ `ar2WriteImageSet()` format) | +| `.fset` | Feature map with one entry per pyramid level | +| `.fset3` | FREAK descriptors for KPM-based recognition (pure-Rust `RustFreakMatcher`) | **Options:** @@ -168,14 +159,12 @@ Output files (depends on whether `ffi-backend` is enabled): | `-o` | `--output` | Output base path (without extension) | required | | `-d` | `--dpi` | Source image resolution in DPI | required | | `-l` | `--level` | Tracking extraction level (0–4) | `2` | -| `-n` | `--search-feature-num` | Max features per pyramid level | `250` | -| `-y` | `--yes` | Skip the "no .fset3" confirmation prompt (for scripted / CI use; only relevant when built without `ffi-backend`) | (interactive) | **Performance feature flags:** | Feature flag | What it enables | |---|---| -| `ffi-backend` | C++ FREAK backend (opt-in since M9-3 #142). Enables `.fset3` generation in `nft_marker_gen` (the `.iset`/`.fset` outputs work on the pure-Rust default) and powers development cross-validation (`dual-mode`, `cross_stack_parity`). Runtime NFT tracking with existing `.fset3` files works on the pure-Rust default. | +| `ffi-backend` | C++ FREAK backend (opt-in since M9-3 #142). Used only for development cross-validation (`dual-mode`, `cross_stack_parity`); all of `nft_marker_gen` (`.iset` + `.fset` + `.fset3`) and runtime NFT tracking run on the pure-Rust default. | | `simd-x86-sse41` | SSE4.1 SIMD acceleration for feature map correlation (`get_similarity`) | | `simd-x86-avx2` | AVX2+FMA SIMD acceleration for feature map correlation (faster than SSE4.1) | | `simd` | Umbrella flag — enables all SIMD optimizations (SSE4.1, AVX2, WASM SIMD, image, pattern) | diff --git a/crates/core/examples/nft_marker_gen.rs b/crates/core/examples/nft_marker_gen.rs index f0c43aa..2d581de 100644 --- a/crates/core/examples/nft_marker_gen.rs +++ b/crates/core/examples/nft_marker_gen.rs @@ -37,17 +37,14 @@ //! NFT marker creation example. //! //! A pure-Rust replacement for the C/Emscripten `markerCreator` tool. -//! Generates `.iset`, `.fset`, and (with `--features ffi-backend`) `.fset3` -//! from a JPEG input image. +//! Generates a complete NFT marker (`.iset` + `.fset` + `.fset3`) from a JPEG +//! input image with no C++ toolchain (#179 — `.fset3` now uses the pure-Rust +//! `RustFreakMatcher`). //! //! ## Usage //! //! ```bash -//! # Pure-Rust output (.iset + .fset only): -//! cargo run --example nft_marker_gen -- --input marker.jpg --output marker --dpi 72 -//! -//! # Full output including .fset3 (requires C++ FREAK backend): -//! cargo run --example nft_marker_gen --features ffi-backend -- \ +//! cargo run --release --example nft_marker_gen -- \ //! --input marker.jpg --output marker --dpi 72 //! ``` //! @@ -55,25 +52,20 @@ //! //! | File | Contents | //! |---|---| -//! | `.iset` | Multi-resolution image pyramid (always generated) | -//! | `.fset` | AR2 feature points per scale (always generated) | -//! | `.fset3` | KPM/FREAK keypoints (`ffi-backend` only) | +//! | `.iset` | Multi-resolution image pyramid | +//! | `.fset` | AR2 feature points per scale | +//! | `.fset3` | KPM/FREAK keypoints (pure-Rust `RustFreakMatcher`) | use chrono::Local; use clap::Parser; use std::path::Path; use std::time::Instant; use webarkitlib_rs::ar2::{ar2_gen_feature_map, ar2_gen_image_set}; -#[cfg(not(feature = "ffi-backend"))] -use webarkitlib_rs::arlog_rel; -#[cfg(feature = "ffi-backend")] -use webarkitlib_rs::arlog_w; -use webarkitlib_rs::{arlog_e, arlog_i}; +use webarkitlib_rs::{arlog_e, arlog_i, arlog_w}; /// KPM feature density for `.fset3` generation (default extraction level 1). /// /// Matches `KPM_SURF_FEATURE_DENSITY_L1` in `markerCreator.cpp`. -#[cfg(feature = "ffi-backend")] const KPM_SURF_FEATURE_DENSITY: i32 = 100; // --------------------------------------------------------------------------- @@ -110,10 +102,6 @@ struct Cli { /// Matches the `-level=n` flag in the C `markerCreator` tool. #[arg(long, default_value_t = 2)] level: u8, - - /// Skip the "no .fset3" confirmation prompt (for CI / scripted use). - #[arg(short, long)] - yes: bool, } // --------------------------------------------------------------------------- @@ -137,31 +125,8 @@ fn main() { let start = Instant::now(); let start_time = Local::now(); - // ----------------------------------------------------------------------- - // Warn the user if .fset3 won't be generated (no ffi-backend). - // ----------------------------------------------------------------------- - #[cfg(not(feature = "ffi-backend"))] - { - use std::io::Write as _; - if !cli.yes { - arlog_rel!(""); - arlog_rel!( - "Warning: built without `ffi-backend` feature — .fset3 will NOT be generated." - ); - arlog_rel!("An NFT marker without .fset3 cannot be used for initialization/detection."); - eprint!("Continue anyway? [y/N] "); - let _ = std::io::stdout().flush(); - let mut answer = String::new(); - std::io::stdin() - .read_line(&mut answer) - .expect("failed to read input"); - if !answer.trim().eq_ignore_ascii_case("y") { - arlog_rel!("Aborted."); - std::process::exit(1); - } - arlog_rel!(""); - } - } + // .fset3 is now generated by the pure-Rust RustFreakMatcher (#179), so the + // old "no .fset3 without ffi-backend" warning + confirmation prompt are gone. // ----------------------------------------------------------------------- // Header @@ -289,12 +254,11 @@ fn main() { ); // ----------------------------------------------------------------------- - // Step 3: generate KPM/FREAK keypoints → .fset3 (ffi-backend only) + // Step 3: generate KPM/FREAK keypoints → .fset3 (pure-Rust, #179) // ----------------------------------------------------------------------- - #[cfg(feature = "ffi-backend")] { use webarkitlib_rs::kpm::types::KpmRefDataSet; - use webarkitlib_rs::kpm::{CppFreakMatcher, KpmCompMode, KpmError, KpmProcMode}; + use webarkitlib_rs::kpm::{KpmCompMode, KpmError, KpmProcMode, RustFreakMatcher}; arlog_i!("Generating FeatureSet3..."); let step_start = Instant::now(); @@ -307,8 +271,8 @@ fn main() { let max_feat = KPM_SURF_FEATURE_DENSITY * scale.xsize * scale.ysize / (480 * 360); // Fresh matcher for each scale (scales may have different dimensions). - let mut matcher = CppFreakMatcher::new(scale.xsize, scale.ysize).unwrap_or_else(|e| { - arlog_e!("Error: CppFreakMatcher::new failed: {:?}", e); + let mut matcher = RustFreakMatcher::new(scale.xsize, scale.ysize).unwrap_or_else(|e| { + arlog_e!("Error: RustFreakMatcher::new failed: {:?}", e); std::process::exit(1); }); @@ -387,9 +351,6 @@ fn main() { } } - #[cfg(not(feature = "ffi-backend"))] - arlog_i!("[fset3] skipped (build with --features ffi-backend to generate)"); - // ----------------------------------------------------------------------- // Done // ----------------------------------------------------------------------- From 63bda44122b44adb3ca7eb2e325b634a309dc609 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 13:56:57 +0200 Subject: [PATCH 37/40] fix(ci): disable cargo-bin cache on kpm-build to stop stale rustup-init shim (#134) Swatinem/rust-cache@v2 intermittently restored a stale `cargo` shim into ~/.cargo/bin on the macOS kpm-build runner. The cached shim resolved to `rustup-init`, so `cargo build` was parsed as `rustup-init build` and the job failed non-deterministically. Linux and Windows were unaffected. Set `cache-bin: false` on this job's cache step. kpm-build installs no cargo-managed binaries, so disabling bin caching removes the flake at no cost to cache effectiveness. Refs #134 Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba6fc8b..0acd30f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,8 +93,15 @@ jobs: if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y libclang-dev + # cache-bin: false avoids caching ~/.cargo/bin. On macOS this job + # intermittently restored a stale `cargo` shim resolving to + # `rustup-init`, so `cargo build` was parsed as `rustup-init build` + # and failed (#134). This job installs no cargo-managed binaries, so + # disabling bin caching costs nothing while removing the flake. - name: Rust Cache uses: Swatinem/rust-cache@v2 + with: + cache-bin: false # Strict clippy gate (#180): --all-targets covers examples/tests/benches; # --all-features exercises ffi-backend, dual-mode, simd-* paths the From d7d48b185d6f306b69c094edd690d608a3b91e3d Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 15:21:52 +0200 Subject: [PATCH 38/40] test(ci): verify ffi-backend tests + example link on macOS (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #117 fixed build.rs to link libc++ (not libstdc++) on Apple targets, but its dual-mode CI step was scoped to --lib, so the integration tests and the generate_patt example were never re-built on macOS after the fix. This left open whether the libc++ fix was sufficient or whether a separate macOS linker issue remained. Add a macOS-gated step to the kpm-build matrix that builds the ffi-backend integration tests (--no-run) and the generate_patt example, confirming they link against libc++. Use --no-run because executing the pose-sensitive assertions cross-platform is out of scope — that float-drift question is tracked in #118, which keeps test_full_pipeline_pose gated to Linux. Refs #119 Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0acd30f..3fea445 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,28 @@ jobs: - name: Build kpm backend (FFI backend) run: cargo build -p webarkitlib-rs --features ffi-backend + # macOS ffi-backend link verification (#119). PR #117 fixed build.rs to + # link libc++ (not libstdc++) on Apple targets, but the dual-mode CI step + # was scoped to --lib, so the integration tests and the generate_patt + # example were never re-built on macOS after the fix. Build them here to + # confirm they link against libc++. + # + # --no-run (not a full run): this verifies the linker fix only. Actually + # executing the pose-sensitive integration tests cross-platform is out of + # scope — that float-drift question is tracked separately in #118, which + # keeps test_full_pipeline_pose gated to Linux. + - name: Verify ffi-backend tests + example link (macOS) + if: runner.os == 'macOS' + run: | + cargo test -p webarkitlib-rs \ + --test kpm_regression \ + --test nft_pipeline \ + --test ar2_pinball_io \ + --test cross_stack_parity \ + --features ffi-backend --no-run + cargo build -p webarkitlib-rs \ + --example generate_patt --features log-helpers,ffi-backend + # Dual-mode tests run pure-Rust ports against the C++ baseline via the # FFI backend (webarkit_cpp_* shims) and assert byte-level parity. # Catches regressions that would otherwise only surface in local runs. From 02651b310a8efcffc8d2f2a207d39101b7dd5379 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 19:15:31 +0200 Subject: [PATCH 39/40] doc: update README + ARCHITECTURE for the v0.8.0 milestone - Refresh the status note: KPM/NFT is now a complete pure-Rust pipeline (native + WASM), no longer "early stage". - Bump install snippets 0.7 -> 0.8. - Add a v0.8.0 "Pure-Rust completeness, validation & polish" roadmap entry (WASM/NFT #161, pure-Rust .fset3 #179, VisualDatabase #147/#148, Gaussian pyramid SIMD/rayon #200-#207, coverage/CI hardening) and prune the now-completed short-term goals. - ARCHITECTURE: add simd-x86-avx2 to the feature table, document the freak::visual_database orchestrator and freak::gaussian_pyramid modules, and the wasm crate's NFT bindings + browser demo. Refs #161 #179 #147 #148 #177 Co-Authored-By: Claude Opus 4.8 --- ARCHITECTURE.md | 10 ++++++++++ README.md | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7a7f4e2..37b40ac 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -36,12 +36,14 @@ The unified core library containing all AR functionality: - `kpm::ref_data_set` — `.fset3` reference data I/O and compression modes. - `kpm::types` — KPM data structures and constants. - `kpm::freak` — FREAK descriptor math, homography, and matching utilities (pure Rust port of `WebARKitLib/lib/SRC/KPM/FreakMatcher`): + - `freak::visual_database` — top-level per-frame query orchestrator (M9-1): builds the query keyframe, then runs the two-pass pipeline (feature match → Hough voting → homography → inlier filter, then a homography-guided re-match) across stored reference keyframes. Exposes `query` / `query_from_keyframe` and facade-parity accessors on the database + `FeatureStore` slices (#147, #148). - `freak::math` — linear algebra (matrix operations, linear solvers) and Padé matrix exponential. - `freak::homography` — homography estimation and refinement pipeline. - `freak::hough` — Hough similarity voting (4D bin discretization over translation × angle × scale) for filtering matches by transformation consistency. - `freak::clustering` — K-Medoids partitioning + Binary Hierarchical Clustering (BHC) vocabulary tree for fast approximate-NN search on 96-byte FREAK descriptors. Hamming distance via 24×32-bit bit-magic. Includes a byte-identical port of C++ `vision::FastRandom` / `vision::ArrayShuffle` so the BHC tree topology matches the C++ baseline given the same seed. - `freak::matcher` — `FeatureStore` (points + flat descriptor buffer) and `FeatureMatcher` with three match variants: brute force, BHC-indexed (fast path), and homography-guided (spatial filter via 3×3 inverse + `tr` radius). All three apply the C++ ratio test (default 0.7) and filter by `FeaturePoint::maxima`. - `freak::image_pyramid` — image pyramid construction via `BoxFilterPyramid8u` (box filtering for 8-bit grayscale) and `BinomialPyramid32f` (32-bit floating-point binomial pyramid with scale-space interpolation). + - `freak::gaussian_pyramid` — Gaussian scale-space pyramid driving the DoG detector. Hot binomial-filter passes have NO-FMA SSE4.1 / AVX2 / wasm SIMD paths (runtime-detected, bit-exact against the scalar fallback for dual-mode parity) and rayon-parallelized rows (v0.8.0, #200–#207). - `freak::feature_extraction` — keypoint detection via Difference-of-Gaussians (DoG), dominant orientation assignment via circular gradient voting, and FREAK descriptor computation with native Rust implementation of the FREAK binary pattern and bit-pair comparison. - **Types** (`types`): core data structures (`ARHandle`, `ARParam`, etc.). @@ -50,6 +52,13 @@ The unified core library containing all AR functionality: WASM wrapper and JavaScript/TypeScript glue code for browser targets. Depends only on `webarkitlib-rs` (the core crate). +Since **v0.8.0 (#161)** it exposes the pure-Rust NFT pipeline to the +browser: a `WasmKpmHandle` binding for KPM detection (load reference data +from bytes, detect), `console_log` wiring via `ar_log_init_wasm()`, a dual +**standard + SIMD** build pipeline (`npm run build:wasm`), and an +end-to-end browser NFT demo under `crates/wasm/www`. The `cc`/`bindgen` +build dependencies are optional so the wasm target builds pure-Rust. + ## Feature Flags | Feature | Description | @@ -58,6 +67,7 @@ Depends only on `webarkitlib-rs` (the core crate). | `simd` | Umbrella: enables all SIMD sub-features | | `simd-wasm32` | WASM SIMD128 intrinsics | | `simd-x86-sse41` | x86 SSE4.1 intrinsics | +| `simd-x86-avx2` | x86 AVX2 intrinsics (runtime-detected; NO-FMA for bit-exact scalar parity) | | `log-helpers` | Enable logging infrastructure (installs `env_logger` for desktop/tests, `console_log` for WASM) | | `ffi-backend` | **Opt-in** — compile the C++ FreakMatcher library and generate FFI bindings. Used for cross-validation and the regression-test suite; not required for production tracking. | | `dual-mode` | Enables FFI-based parity tests that validate pure-Rust ports against the live C++ baseline (M6 math/solvers/homography, M7 BHC/matcher, PRNG). Transitively enables `ffi-backend`. Run in CI on Linux/macOS/Windows. | diff --git a/README.md b/README.md index 4a7a5db..2e08be4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This project aims to provide a pure-Rust implementation of the core ARToolKit algorithms, targeting both **native** systems and **WebAssembly (WASM)** for high-performance augmented reality in the browser. > [!NOTE] -> This project is currently a **Work in Progress**. Core marker detection and pose estimation are functional. KPM/NFT (Natural Feature Tracking) is in an early stage with partial Rust porting -- a fully idiomatic Rust KPM pipeline with a working example is the primary goal for v1.0.0. +> Core marker detection and pose estimation are functional, and as of **v0.8.0** the **KPM/NFT (Natural Feature Tracking) pipeline is fully ported to pure Rust** — end-to-end keypoint matching, marker generation (`.iset`/`.fset`/`.fset3`), and runtime tracking all run with **no C++ toolchain**, natively and in the browser via WebAssembly. The project remains a **Work in Progress** toward v1.0.0 (broader API docs, multi-marker support, and a dedicated KPM benchmark are the remaining focus areas). ## 🌟 Key Features @@ -40,7 +40,7 @@ Add `webarkitlib-rs` to your `Cargo.toml`: ```toml [dependencies] -webarkitlib-rs = "0.7" +webarkitlib-rs = "0.8" ``` ### Pure Rust tracking (no C++ compiler required) @@ -66,7 +66,7 @@ need to do anything special: ```toml [dependencies] -webarkitlib-rs = "0.7" # default backend is pure Rust +webarkitlib-rs = "0.8" # default backend is pure Rust ``` ```bash @@ -83,7 +83,7 @@ NFT tracking**: ```toml [dependencies] -webarkitlib-rs = { version = "0.7", features = ["ffi-backend"] } +webarkitlib-rs = { version = "0.8", features = ["ffi-backend"] } ``` > When installing from crates.io, no extra setup is required — the C++ @@ -248,7 +248,7 @@ Enable the `log-helpers` feature and call the bundled initializer once in your b ```toml [dependencies] -webarkitlib-rs = { version = "0.7", features = ["log-helpers"] } +webarkitlib-rs = { version = "0.8", features = ["log-helpers"] } ``` ```rust @@ -429,12 +429,16 @@ The workspace contains two crates: - Cross-platform / cross-stack matcher determinism — Rust `HashMap` → `BTreeMap` and upstream C++ `unordered_map` → `std::map` ([WebARKitLib#39](https://github.com/webarkit/WebARKitLib/pull/39)). - Hand-annotated absolute corner-error regression gate (browser-based annotation tool + 5 fixtures + Linux CI gate). Finding: pure-Rust backend is more accurate than C++ on `pinball-demo` (5.27 px vs 18.79 px max corner error). - Cross-stack parity gate against `@webarkit/jsartoolkit-nft@1.10.0` ([jsartoolkitNFT#584](https://github.com/webarkit/jsartoolkitNFT/pull/584)) — guarantees native Rust pose matches what production WASM consumers see. +- **v0.8.0 — Pure-Rust completeness, validation & polish**: Closed out the remaining gaps on top of M9: + - **Full WASM/NFT support** ([#161](https://github.com/webarkit/WebARKitLib-rs/issues/161)): `WasmKpmHandle` KPM-detection bindings, `console_log` wiring, clean dual (standard + SIMD) wasm builds, and an end-to-end **browser NFT demo** of the pure-Rust pipeline. + - **Pure-Rust `.fset3` marker generation** ([#179](https://github.com/webarkit/WebARKitLib-rs/issues/179)): `nft_marker_gen` now produces `.iset` + `.fset` + `.fset3` entirely via `RustFreakMatcher` — the `ffi-backend` is no longer needed for marker creation. + - **`VisualDatabase` ergonomics** ([#147](https://github.com/webarkit/WebARKitLib-rs/issues/147), [#148](https://github.com/webarkit/WebARKitLib-rs/issues/148)): factored the per-frame query loop, added `query_from_keyframe`, and facade-parity accessors on `VisualDatabase` + `FeatureStore` slices. + - **Gaussian scale-space pyramid performance** ([#200](https://github.com/webarkit/WebARKitLib-rs/issues/200)/[#201](https://github.com/webarkit/WebARKitLib-rs/issues/201)/[#207](https://github.com/webarkit/WebARKitLib-rs/issues/207)): criterion benchmark, NO-FMA SSE4.1/AVX2/wasm SIMD binomial filter with bit-exact scalar parity, and rayon-parallelized filter passes. + - **Validation & CI hardening**: raised M9 patch coverage ≥90% ([#177](https://github.com/webarkit/WebARKitLib-rs/issues/177)), Miri UB gate ([#182](https://github.com/webarkit/WebARKitLib-rs/issues/182)), strict `--all-targets --all-features` clippy gate ([#180](https://github.com/webarkit/WebARKitLib-rs/issues/180)), benchmark download hardening ([#204](https://github.com/webarkit/WebARKitLib-rs/issues/204)), and macOS/Windows build + cache fixes ([#134](https://github.com/webarkit/WebARKitLib-rs/issues/134), [#119](https://github.com/webarkit/WebARKitLib-rs/issues/119)). ### 🎯 Short-term Goals (toward v1.0.0) - **KPM-specific benchmark** ([deferred from #142](https://github.com/webarkit/WebARKitLib-rs/issues/142)): Add a dedicated `kpm_bench.rs` Criterion bench so we can verify the pure-Rust backend stays within 20% of C++ on `pinball-demo`. The existing `marker_bench` only measures barcode marker detection. -- **WASM browser examples for KPM/NFT** ([#161](https://github.com/webarkit/WebARKitLib-rs/issues/161)): End-to-end runnable browser demo of the pure-Rust NFT pipeline. -- **Raise M9 patch coverage** ([#177](https://github.com/webarkit/WebARKitLib-rs/issues/177)): Lift M9 modules from 84.76% → ≥90% patch coverage with no file under 85%. -- **Dependency upgrades**: Criterion 0.5 → 0.8 ([#174](https://github.com/webarkit/WebARKitLib-rs/issues/174)). +- **Live-camera WASM demo + pose parity** ([#215](https://github.com/webarkit/WebARKitLib-rs/issues/215)): Extend the browser NFT demo with a live camera feed and jsartoolkitNFT pose parity. - **Enhanced Documentation**: Expand API reference with complete module-level docs, integration walkthroughs for JS/TS, and detailed usage examples. - **WASM Memory Management**: Improve resource cleanup when switching engines or markers in long-running browser sessions. From 28c75f64c116f9f7e78f98ab998be0a55ad4c1f1 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 26 Jun 2026 20:19:32 +0200 Subject: [PATCH 40/40] chore(release): v0.8.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump the workspace and npm package versions 0.7.0 -> 0.8.0, regenerate the wasm pkg (standard + SIMD), and prepend the 0.8.0 changelog section (git-cliff) for the "Pure-Rust completeness, validation & polish" milestone. Release prep per MAINTAINERS.md §2. The dev->main merge and the v0.8.0 tag (which triggers the crates.io + npm publish) are done separately. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 72 +++++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- crates/wasm/pkg/README.md | 58 +++++++++++++---------------- crates/wasm/pkg/package.json | 2 +- package.json | 2 +- 5 files changed, 99 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7756c7e..5f07f4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,76 @@ -# Changelog - webarkit/webarkitlib-rs +# Changelog - webarkit/webarkitlib-rs All notable changes to this project will be documented in this file. -## [0.7.0] - 2026-06-05 +## [0.8.0] - 2026-06-26 + +## Milestone — Pure-Rust completeness, validation & polish + +### ⚙️ Miscellaneous Tasks + +- *(ar2)* Allow too_many_arguments on SIMD get_similarity variants (#180) +- Clear strict clippy lints in examples/tests/benches (#180) +- *(codecov)* Exclude examples/benches from coverage targets (#180) +- *(coverage)* Fix tarpaulin tests/ glob and mirror in codecov (#180) +- Tighten clippy gate to --all-targets --all-features (#180) +- Relocate strict clippy gate to kpm-build (ubuntu) job (#180) +- *(miri)* Add Miri UB validation for pure-Rust code paths (#182) +- *(miri)* Drop -Zmiri-strict-provenance (trips on third-party deps) (#182) +- *(miri)* Switch to Tree Borrows to avoid crossbeam-epoch false positive (#182) +- *(miri)* Enable -Zmiri-disable-isolation for tempfile-using tests (#182) +- *(miri)* Enable -Zmiri-ignore-leaks for rayon thread pool (#194) +- *(miri)* Promote to required gate, split workflow, add README badge (#182) +- *(miri)* Skip docs-only changes via paths-ignore + same-name shim +- *(benchmarks)* Harden against flaky external C-library downloads (#204) + +### ⚡ Performance + +- *(ci)* Scope Miri job to unsafe boundaries via cfg_attr(miri, ignore) (#194) +- *(ci)* Annotate remaining real-image tests (#194 follow-up) +- *(kpm)* Gaussian scale-space pyramid — benchmark + rayon parallelization (#209) +- *(kpm)* Box-filter Pyramid downsample — criterion benchmark + SIMD (#211) + +### 🐛 Bug Fixes + +- *(core)* Clean up trivial clippy lints under --all-targets --all-features (#180) +- *(kpm)* Tighten dual-mode FFI extern cfg to match caller cfg (#180) +- *(kpm)* Replace unaligned transmute in hamming_distance_96 (#192) +- *(ci)* Disable cargo-bin cache on kpm-build to stop stale rustup-init shim (#134) + +### 💼 Other + +- *(wasm)* Make cc/bindgen optional, fix wasm dead-code (#161 goal 1) + +### 📚 Documentation + +- *(benches)* Refresh BENCHMARKS.md for criterion 0.8 +- *(agents)* Codify #180 clippy conventions and un-ignore .agents/ +- *(kpm)* Mark box-filter Pyramid as kept-but-unused reference (#203) +- *(wasm)* Note .fset3 / KPM detection in the NFT demo description (#161) +- Update README + ARCHITECTURE for the v0.8.0 milestone + +### 🕸️ WebAssembly & Emscripten + +- *(wasm)* Wire console logger + clean SIMD wasm build; add .fset3 asset (#161 goals 2,3) +- *(wasm)* KPM detection binding + browser NFT demo (#161 goal 4) + +### 🚀 Features + +- *(kpm)* Facade-parity convenience accessors on VisualDatabase + FeatureStore slices (#148) +- *(example)* Nft_marker_gen .fset3 via RustFreakMatcher — drop ffi-backend (#179) + +### 🚜 Refactor + +- *(ar)* Convert Default::default() + field reassign to struct-init (#180) +- *(kpm)* Factor VisualDatabase::query matching loop + add query_from_keyframe (#147) (#217) + +### 🧪 Testing + +- *(core)* Add unit tests for relocated math/pattern helpers (#180) +- *(homography)* Widen cauchy_cost tolerance to 1e-5 for Miri (#194) +- *(kpm)* Raise M9 coverage — exclude examples + edge tests (#177) (#218) +- *(ci)* Verify ffi-backend tests + example link on macOS (#119) +## [0.7.0] - 2026-06-05 ## Milestone 9 — VisualDatabase & pure Rust backend diff --git a/Cargo.toml b/Cargo.toml index c00e669..3f4caa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ exclude = [ resolver = "2" [workspace.package] -version = "0.7.0" +version = "0.8.0" authors = ["kalwalt "] edition = "2021" description = "A high-performance, memory-safe Rust port of WebARKitLib (ARToolKit) for native and WASM." diff --git a/crates/wasm/pkg/README.md b/crates/wasm/pkg/README.md index 7684391..2e08be4 100644 --- a/crates/wasm/pkg/README.md +++ b/crates/wasm/pkg/README.md @@ -3,6 +3,7 @@ ![WebARKitLib-rs](./assets/WebARKitLib-Rust-banner.jpg) [![CI](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/ci.yml) +[![Miri](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/miri.yml/badge.svg)](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/miri.yml) [![codecov](https://codecov.io/gh/webarkit/WebARKitLib-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/webarkit/WebARKitLib-rs) [![Crates.io](https://img.shields.io/crates/v/webarkitlib-rs.svg)](https://crates.io/crates/webarkitlib-rs) [![npm](https://img.shields.io/npm/v/@webarkit/webarkitlib-wasm.svg)](https://www.npmjs.com/package/@webarkit/webarkitlib-wasm) @@ -13,7 +14,7 @@ This project aims to provide a pure-Rust implementation of the core ARToolKit algorithms, targeting both **native** systems and **WebAssembly (WASM)** for high-performance augmented reality in the browser. > [!NOTE] -> This project is currently a **Work in Progress**. Core marker detection and pose estimation are functional. KPM/NFT (Natural Feature Tracking) is in an early stage with partial Rust porting -- a fully idiomatic Rust KPM pipeline with a working example is the primary goal for v1.0.0. +> Core marker detection and pose estimation are functional, and as of **v0.8.0** the **KPM/NFT (Natural Feature Tracking) pipeline is fully ported to pure Rust** — end-to-end keypoint matching, marker generation (`.iset`/`.fset`/`.fset3`), and runtime tracking all run with **no C++ toolchain**, natively and in the browser via WebAssembly. The project remains a **Work in Progress** toward v1.0.0 (broader API docs, multi-marker support, and a dedicated KPM benchmark are the remaining focus areas). ## 🌟 Key Features @@ -39,7 +40,7 @@ Add `webarkitlib-rs` to your `Cargo.toml`: ```toml [dependencies] -webarkitlib-rs = "0.7" +webarkitlib-rs = "0.8" ``` ### Pure Rust tracking (no C++ compiler required) @@ -65,7 +66,7 @@ need to do anything special: ```toml [dependencies] -webarkitlib-rs = "0.7" # default backend is pure Rust +webarkitlib-rs = "0.8" # default backend is pure Rust ``` ```bash @@ -82,7 +83,7 @@ NFT tracking**: ```toml [dependencies] -webarkitlib-rs = { version = "0.7", features = ["ffi-backend"] } +webarkitlib-rs = { version = "0.8", features = ["ffi-backend"] } ``` > When installing from crates.io, no extra setup is required — the C++ @@ -122,42 +123,33 @@ This example loads a camera parameter file, a marker (pattern or barcode), and a Generate NFT (Natural Feature Tracking) marker files compatible with ARnft and NFT-Marker-Creator-App: ```bash -# Pure-Rust default (no C++ toolchain required) — produces .iset + .fset. -# Pass --yes to skip the interactive "no .fset3" confirmation prompt -# (recommended for scripted / CI use): +# Pure Rust, no C++ toolchain — produces a COMPLETE marker (.iset + .fset + .fset3): cargo run --release --example nft_marker_gen -- \ - --input path/to/image.jpg \ - --output path/to/output_name \ - --dpi 220 \ - --yes - -# Full output including .fset3 (FREAK descriptors) — opt-in C++ backend: -# .fset3 generation still uses CppFreakMatcher today; the runtime KPM -# tracker reads .fset3 files in pure Rust, only the marker-creation step -# needs the C++ FREAK extractor. Wiring nft_marker_gen to RustFreakMatcher -# for .fset3 is a follow-up task. -cargo run --release --features ffi-backend --example nft_marker_gen -- \ --input path/to/image.jpg \ --output path/to/output_name \ --dpi 220 # With SIMD acceleration + Rayon parallelism (recommended for x86_64): -cargo run --release --features "ffi-backend,simd-x86-sse41,simd-x86-avx2" \ +cargo run --release --features "simd-x86-sse41,simd-x86-avx2" \ --example nft_marker_gen -- \ --input path/to/image.jpg \ --output path/to/output_name \ --dpi 220 ``` +> Since #179, `.fset3` (FREAK descriptors) is generated by the pure-Rust +> `RustFreakMatcher` — a single invocation produces a complete marker with no +> C++ toolchain. + > **Important:** Always use the `--release` flag. Debug builds are 5–10× slower due to missing compiler optimizations. -Output files (depends on whether `ffi-backend` is enabled): +Output files (all produced on the pure-Rust default): -| File | Default (pure Rust) | With `ffi-backend` | Description | -|---|:---:|:---:|---| -| `.iset` | ✅ | ✅ | JPEG-compressed image pyramid (~300 KB, matches C++ `ar2WriteImageSet()` format) | -| `.fset` | ✅ | ✅ | Feature map with one entry per pyramid level | -| `.fset3` | ❌ (skipped, with warning) | ✅ | FREAK descriptors for KPM-based recognition | +| File | Description | +|---|---| +| `.iset` | JPEG-compressed image pyramid (~300 KB, matches C++ `ar2WriteImageSet()` format) | +| `.fset` | Feature map with one entry per pyramid level | +| `.fset3` | FREAK descriptors for KPM-based recognition (pure-Rust `RustFreakMatcher`) | **Options:** @@ -167,14 +159,12 @@ Output files (depends on whether `ffi-backend` is enabled): | `-o` | `--output` | Output base path (without extension) | required | | `-d` | `--dpi` | Source image resolution in DPI | required | | `-l` | `--level` | Tracking extraction level (0–4) | `2` | -| `-n` | `--search-feature-num` | Max features per pyramid level | `250` | -| `-y` | `--yes` | Skip the "no .fset3" confirmation prompt (for scripted / CI use; only relevant when built without `ffi-backend`) | (interactive) | **Performance feature flags:** | Feature flag | What it enables | |---|---| -| `ffi-backend` | C++ FREAK backend (opt-in since M9-3 #142). Enables `.fset3` generation in `nft_marker_gen` (the `.iset`/`.fset` outputs work on the pure-Rust default) and powers development cross-validation (`dual-mode`, `cross_stack_parity`). Runtime NFT tracking with existing `.fset3` files works on the pure-Rust default. | +| `ffi-backend` | C++ FREAK backend (opt-in since M9-3 #142). Used only for development cross-validation (`dual-mode`, `cross_stack_parity`); all of `nft_marker_gen` (`.iset` + `.fset` + `.fset3`) and runtime NFT tracking run on the pure-Rust default. | | `simd-x86-sse41` | SSE4.1 SIMD acceleration for feature map correlation (`get_similarity`) | | `simd-x86-avx2` | AVX2+FMA SIMD acceleration for feature map correlation (faster than SSE4.1) | | `simd` | Umbrella flag — enables all SIMD optimizations (SSE4.1, AVX2, WASM SIMD, image, pattern) | @@ -258,7 +248,7 @@ Enable the `log-helpers` feature and call the bundled initializer once in your b ```toml [dependencies] -webarkitlib-rs = { version = "0.7", features = ["log-helpers"] } +webarkitlib-rs = { version = "0.8", features = ["log-helpers"] } ``` ```rust @@ -439,12 +429,16 @@ The workspace contains two crates: - Cross-platform / cross-stack matcher determinism — Rust `HashMap` → `BTreeMap` and upstream C++ `unordered_map` → `std::map` ([WebARKitLib#39](https://github.com/webarkit/WebARKitLib/pull/39)). - Hand-annotated absolute corner-error regression gate (browser-based annotation tool + 5 fixtures + Linux CI gate). Finding: pure-Rust backend is more accurate than C++ on `pinball-demo` (5.27 px vs 18.79 px max corner error). - Cross-stack parity gate against `@webarkit/jsartoolkit-nft@1.10.0` ([jsartoolkitNFT#584](https://github.com/webarkit/jsartoolkitNFT/pull/584)) — guarantees native Rust pose matches what production WASM consumers see. +- **v0.8.0 — Pure-Rust completeness, validation & polish**: Closed out the remaining gaps on top of M9: + - **Full WASM/NFT support** ([#161](https://github.com/webarkit/WebARKitLib-rs/issues/161)): `WasmKpmHandle` KPM-detection bindings, `console_log` wiring, clean dual (standard + SIMD) wasm builds, and an end-to-end **browser NFT demo** of the pure-Rust pipeline. + - **Pure-Rust `.fset3` marker generation** ([#179](https://github.com/webarkit/WebARKitLib-rs/issues/179)): `nft_marker_gen` now produces `.iset` + `.fset` + `.fset3` entirely via `RustFreakMatcher` — the `ffi-backend` is no longer needed for marker creation. + - **`VisualDatabase` ergonomics** ([#147](https://github.com/webarkit/WebARKitLib-rs/issues/147), [#148](https://github.com/webarkit/WebARKitLib-rs/issues/148)): factored the per-frame query loop, added `query_from_keyframe`, and facade-parity accessors on `VisualDatabase` + `FeatureStore` slices. + - **Gaussian scale-space pyramid performance** ([#200](https://github.com/webarkit/WebARKitLib-rs/issues/200)/[#201](https://github.com/webarkit/WebARKitLib-rs/issues/201)/[#207](https://github.com/webarkit/WebARKitLib-rs/issues/207)): criterion benchmark, NO-FMA SSE4.1/AVX2/wasm SIMD binomial filter with bit-exact scalar parity, and rayon-parallelized filter passes. + - **Validation & CI hardening**: raised M9 patch coverage ≥90% ([#177](https://github.com/webarkit/WebARKitLib-rs/issues/177)), Miri UB gate ([#182](https://github.com/webarkit/WebARKitLib-rs/issues/182)), strict `--all-targets --all-features` clippy gate ([#180](https://github.com/webarkit/WebARKitLib-rs/issues/180)), benchmark download hardening ([#204](https://github.com/webarkit/WebARKitLib-rs/issues/204)), and macOS/Windows build + cache fixes ([#134](https://github.com/webarkit/WebARKitLib-rs/issues/134), [#119](https://github.com/webarkit/WebARKitLib-rs/issues/119)). ### 🎯 Short-term Goals (toward v1.0.0) - **KPM-specific benchmark** ([deferred from #142](https://github.com/webarkit/WebARKitLib-rs/issues/142)): Add a dedicated `kpm_bench.rs` Criterion bench so we can verify the pure-Rust backend stays within 20% of C++ on `pinball-demo`. The existing `marker_bench` only measures barcode marker detection. -- **WASM browser examples for KPM/NFT** ([#161](https://github.com/webarkit/WebARKitLib-rs/issues/161)): End-to-end runnable browser demo of the pure-Rust NFT pipeline. -- **Raise M9 patch coverage** ([#177](https://github.com/webarkit/WebARKitLib-rs/issues/177)): Lift M9 modules from 84.76% → ≥90% patch coverage with no file under 85%. -- **Dependency upgrades**: Criterion 0.5 → 0.8 ([#174](https://github.com/webarkit/WebARKitLib-rs/issues/174)). +- **Live-camera WASM demo + pose parity** ([#215](https://github.com/webarkit/WebARKitLib-rs/issues/215)): Extend the browser NFT demo with a live camera feed and jsartoolkitNFT pose parity. - **Enhanced Documentation**: Expand API reference with complete module-level docs, integration walkthroughs for JS/TS, and detailed usage examples. - **WASM Memory Management**: Improve resource cleanup when switching engines or markers in long-running browser sessions. diff --git a/crates/wasm/pkg/package.json b/crates/wasm/pkg/package.json index e3d4ed7..3bef609 100644 --- a/crates/wasm/pkg/package.json +++ b/crates/wasm/pkg/package.json @@ -5,7 +5,7 @@ "kalwalt \u003cgithub@kalwaltart.it\u003e" ], "description": "A high-performance, memory-safe Rust port of WebARKitLib (ARToolKit) for native and WASM.", - "version": "0.7.0", + "version": "0.8.0", "license": "LGPL-3.0-or-later", "repository": { "type": "git", diff --git a/package.json b/package.json index abf75af..f520df7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webarkitlib-rs", - "version": "0.7.0", + "version": "0.8.0", "description": "Port of WebARKitLib (ARToolKit) to Rust and WASM", "private": true, "scripts": {