rav1d-safe 0.6.0

Safe SIMD fork of rav1d - Rust AV1 decoder with archmage intrinsics
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# rav1d-safe [![CI](https://img.shields.io/github/actions/workflow/status/imazen/rav1d-safe/ci.yml?style=flat-square)](https://github.com/imazen/rav1d-safe/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/rav1d-safe?style=flat-square)](https://crates.io/crates/rav1d-safe) [![lib.rs](https://img.shields.io/badge/lib.rs-rav1d--safe-orange?style=flat-square)](https://lib.rs/crates/rav1d-safe) [![docs.rs](https://img.shields.io/docsrs/rav1d-safe?style=flat-square)](https://docs.rs/rav1d-safe) [![license](https://img.shields.io/crates/l/rav1d-safe?style=flat-square)](https://github.com/imazen/rav1d-safe#license)

An AV1 decoder with a native **Rust API**: `Decoder`, `Settings`, `Frame`, and
borrowed pixel-plane views are available directly from `rav1d_safe`. Forked from
[rav1d](https://github.com/memorysafety/rav1d), with checked safe Rust SIMD enabled
by default. Rust callers need neither a C FFI wrapper nor the `c-ffi` feature.

Use [zenrav1e](https://github.com/imazen/zenrav1e) to encode raw AV1 and
[zenavif](https://github.com/imazen/zenavif) for complete AVIF files, including
container handling and color conversion. See the
[compiled Rust round-trip examples](docs/RUST_CODEC_WORKFLOW.md).

## Quick Start

Add to your `Cargo.toml`:
```toml
[dependencies]
rav1d-safe = "0.6.0"
```

Decode an AV1 bitstream:
```rust
use rav1d_safe::{Decoder, Planes};

fn decode(obu_data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
    let mut decoder = Decoder::new()?;

    // Feed raw OBU data (not IVF/WebM containers)
    if let Some(frame) = decoder.decode(obu_data)? {
        println!("{}x{} @ {}bpc", frame.width(), frame.height(), frame.bit_depth());

        match frame.planes() {
            Planes::Depth8(planes) => {
                for row in planes.y().rows() {
                    // row is &[u8] — zero-copy, no allocation
                }
            }
            Planes::Depth16(planes) => {
                let px = planes.y().pixel(0, 0); // 10 or 12-bit value
            }
        }
    }

    // Drain any buffered frames
    for frame in decoder.flush()? {
        // ...
    }
    Ok(())
}
```

## API Overview

The reference below describes the 0.6.0 API, including `Strictness` and
cooperative cancellation. The [historical runnable codec examples](docs/RUST_CODEC_WORKFLOW.md)
remain pinned to the published versions used for their recorded validation.

The public API lives in `src/managed.rs` and is re-exported at the crate root, so **every public type is reachable directly from `rav1d_safe`** — no `src::managed::` path needed. One canonical import covering the whole surface:

```rust
use rav1d_safe::{
    Decoder, Settings, Strictness, CpuLevel, Error, Frame, Result,
    Planes,                       // enum; you match Planes::Depth8(_) / Planes::Depth16(_)
    Planes8, Planes16,            // the inner per-bit-depth plane sets the variants wrap
    PlaneView8, PlaneView16,      // zero-copy 2D plane views
    PixelLayout,                  // I400 / I420 / I422 / I444 (chroma subsampling)
    DecodeFrameType, InloopFilters,
    // HDR / color metadata:
    ColorInfo, ColorPrimaries, TransferCharacteristics, MatrixCoefficients,
    ColorRange, ContentLightLevel, MasteringDisplay,
    enabled_features,
};
```

Configure the conformance policy by modifying the default settings:

```rust
use rav1d_safe::{Decoder, Settings, Strictness};

# fn main() -> rav1d_safe::Result<()> {
let mut settings = Settings::default();
settings.strictness = Strictness::Lenient;
let decoder = Decoder::with_settings(settings)?;
# Ok(())
# }
```

The default is `Strictness::Strict`. `Settings` remains non-exhaustive, so
downstream callers use field assignment rather than a struct literal.

**Core types:**

| Type | Purpose |
|------|---------|
| `Decoder` | Decodes AV1 OBU data into frames |
| `Frame` | Decoded frame with metadata (cloneable, Arc-backed) |
| `Planes` | Enum with variants `Planes::Depth8(Planes8)` / `Planes::Depth16(Planes16)`, dispatched by bit depth |
| `Planes8` / `Planes16` | Per-bit-depth plane set: `.y()` → luma view, `.u()` / `.v()` → `Option<view>` (`None` for I400 monochrome) |
| `PlaneView8` / `PlaneView16` | Zero-copy 2D view: `row(y)`, `pixel(x, y)`, `rows()`, `width()`, `height()`, `stride()` |
| `Settings` | Thread count, film grain, frame size limit, inloop filters, CPU level, etc. |
| `CpuLevel` | SIMD dispatch level (`Scalar`, `X86V2`, `X86V3`, `X86V4`, `Neon`, `Native`) |
| `Error` | Enum: `InvalidData`, `OutOfMemory`, `NeedMoreData`, `InvalidSettings(&str)`, `InitFailed`, `Cancelled`, `Other(String)` |

> **Naming note (reconciles the example above):** `Depth8` / `Depth16` are the *variants* of the `Planes` enum — you write `Planes::Depth8(..)` in a `match`. `Planes8` / `Planes16` are the *struct types* those variants wrap, and are also re-exported at the crate root so you can name them in signatures. Both are correct; they refer to different things.

**Metadata types:** `ColorInfo`, `ColorPrimaries`, `TransferCharacteristics`, `MatrixCoefficients`, `ColorRange`, `ContentLightLevel`, `MasteringDisplay`, `PixelLayout`

### Input Format

The decoder expects raw AV1 Open Bitstream Unit (OBU) data. If you have IVF or WebM containers, strip the container framing first and pass the OBU payload. See `tests/ivf_parser.rs` for an IVF parser example. For complete AVIF images, use [zenavif](https://crates.io/crates/zenavif), which uses rav1d-safe’s Rust API and handles the container, alpha, and color conversion. Use [zenavif-parse](https://crates.io/crates/zenavif-parse) directly only when implementing that container layer yourself; AVIF files can contain multiple image items or tiles, not just one OBU payload.

### Output Format

`decode()` / `flush()` yield a `Frame` of **planar YUV** pixels — there is no built-in RGB conversion (do that downstream, e.g. with `zenpixels-convert`, or via `zenavif` for AVIF). Read the planes through the bit-depth-dispatched `Planes` enum:

```rust
let layout = frame.pixel_layout();        // PixelLayout: I400 | I420 | I422 | I444
let bpc    = frame.bit_depth();           // 8, 10, or 12

match frame.planes() {
    Planes::Depth8(p) => {                 // 8-bit content: planes are u8
        let y = p.y();                     // PlaneView8 (luma, always present)
        let u = p.u();                     // Option<PlaneView8> — None for I400 monochrome
        let v = p.v();                     // Option<PlaneView8>
        let (w, h, stride) = (y.width(), y.height(), y.stride());
        for row in y.rows() { /* row: &[u8], one luma scanline, zero-copy */ }
        let _ = (u, v, w, h, stride);
    }
    Planes::Depth16(p) => {                // 10/12-bit content: planes are u16
        let _y = p.y();                    // PlaneView16; pixel(x, y) -> u16
    }
}
```

- **Subsampling** is reported by `frame.pixel_layout()`: `I420` (4:2:0, chroma half-width & half-height), `I422` (4:2:2, half-width), `I444` (4:4:4, full-res chroma), `I400` (monochrome — `u()`/`v()` return `None`).
- **Bit depth** is `frame.bit_depth()` (8 / 10 / 12). `Planes::Depth8` carries `u8` samples; `Planes::Depth16` carries `u16` (the 10/12-bit value is in the low bits).
- Each `PlaneView{8,16}` is a strided 2D view: `row(y)` borrows one scanline, `rows()` iterates them, `pixel(x, y)` reads one sample, and `width()`/`height()`/`stride()` give its geometry. All accessors are zero-copy borrows into the decoder's frame buffer (held alive by the `Frame`).

### Threading

```rust
use rav1d_safe::{Decoder, Settings, CpuLevel};

// Single-threaded (default) — synchronous, deterministic
let decoder = Decoder::new()?;

// Tile threading within a frame; safe checked mode supports this.
let mut settings = Settings::default();
settings.threads = 4;
settings.max_frame_delay = 1;
let decoder = Decoder::with_settings(settings)?;

// Constrained decoding — limit frame size and CPU features
let mut settings = Settings::default();
settings.frame_size_limit = 3840 * 2160; // total pixels, not bytes
settings.cpu_level = CpuLevel::Native;
let decoder = Decoder::with_settings(settings)?;
```

#### `frame_size_limit` — the pre-decode DoS guard (read this before decoding untrusted AV1)

This is the only resource bound applied *before* a frame is decoded, so it is the knob that matters for untrusted input. Verified against `src/managed.rs` and `src/obu.rs`:

- **Unit:** the maximum **`width * height` in pixels** (total luma sample count). Not bytes, not the longest dimension. The check is literally `width * height > frame_size_limit` during OBU header parsing — a frame is rejected before any pixel buffer is allocated or decoded.
- **Default:** **`120_000_000` (120 megapixels)** — chosen to admit ~108 MP phone photos. It is **not** unlimited by default, but 120 MP is large; **set it lower for untrusted servers** (e.g. `7680 * 4320` for an 8K cap, or `3840 * 2160` for 4K).
- **Disable:** set `frame_size_limit: 0` to turn the limit off entirely (no cap).
- **On rejection:** the offending OBU fails parsing and `decode()` returns an `Err` (surfaced as `Error::Other` carrying the internal `ERANGE` range error — *not* `Error::InvalidData`). The decoder logs `Frame size WxH exceeds limit N`.
- **32-bit hosts:** on targets where `usize` is < 64 bits, the effective cap is additionally clamped to `8192 * 8192` regardless of the value you set (a non-zero larger value is silently reduced; `0`/unlimited still means unlimited).

> **Caveat for servers:** `frame_size_limit` bounds the *declared frame dimensions* only. It does **not** bound total decode time, memory of a large-but-legal frame, or the number of OBUs/frames in a stream. There is no per-decode time budget — see *Cancellation* below.

With `threads >= 2` or `threads == 0`, the decoder uses tile threading to parallelize decode within each frame. `decode()` may return `None` for complete frames because processing is asynchronous — call it repeatedly or use `flush()` to drain.

**Tile threading works under `forbid(unsafe_code)` without the `unchecked` feature.** No special feature flags needed:

```toml
rav1d-safe = { version = "0.6.0", features = ["bitdepth_8", "bitdepth_16"] }
```

Still-image scaling depends on the encoded tile layout. A single-tile image
may gain little from additional workers; tiled images can benefit substantially.
Measure the intended workload and set `max_frame_delay = 1` when comparing
single-frame latency. Frame threading (`max_frame_delay > 1`) still requires
the `unchecked` feature. See the [2K/4K/8K still investigation](benchmarks/stills-2026-09-07/README.md)
and the [performance parity goal](docs/PERFORMANCE_PARITY_GOAL.md).

#### `strictness` — what to do with a stream that breaks the spec

AV1 leaves the handling of non-conforming streams to the decoder. dav1d's library default conceals and keeps going (a player would rather show a damaged frame than stop); the AV1 reference decoder `aomdec` rejects the frame. `Settings::strictness` picks between the two:

- `Strictness::Strict` (**default**) rejects what libaom rejects: dav1d's `strict_std_compliance` checks (OBU trailing bits, the padding after each tile's symbol decoder, the OBU forbidden bit, zero `timing_info`, …) plus the spec's requirement that every decoded `segment_id` stays within `LastActiveSegId`. A desynchronised symbol stream is an `Error::InvalidData` instead of garbage pixels. These are compares at points the decoder already evaluates; none is in a per-pixel loop.
- `Strictness::Lenient` is dav1d's behaviour, bit-exact with dav1d even on corrupt input. Use it for differential testing against dav1d, or when a best-effort frame beats an error.

Why `Strict` is the default: this decoder's main job is decoding still images and verifying encoder output. An encoder bug that desynchronised the symbol stream (zenrav1e#35) passed every encode→decode round trip for weeks because the decoder concealed it (#422). The 766-vector dav1d md5 gate decodes identically under both settings, and across the AVIF corpora in `benchmarks/strictness_2026-08-28.meta` the only file `Strict` rejects that `Lenient` accepts is a deliberately corrupted conformance-suite sample. `cargo run --release --example strictness_sweep -- <dir>` reports the two verdicts side by side for your own corpus.

### HDR Metadata

```rust
if let Some(cll) = frame.content_light() {
    println!("MaxCLL: {} nits", cll.max_content_light_level);
}
if let Some(mdcv) = frame.mastering_display() {
    println!("Peak: {} nits", mdcv.max_luminance_nits());
}
let color = frame.color_info();
// color.primaries, color.transfer_characteristics, color.matrix_coefficients
```

### Error Handling

Fallible operations return the crate's `Result<T>` alias, which is `Result<T, whereat::At<Error>>` — the `Error` is wrapped in [`whereat`]'s `At<…>`, recording the source location where the failure surfaced (handy for server logs). Unwrap the inner `Error` with `err.error()` (borrow) or `err.decompose().0` (owned). `Error` variants: `InvalidData`, `OutOfMemory`, `NeedMoreData`, `InitFailed`, `InvalidSettings(&str)`, `Cancelled`, `Other(String)`. (`From<Rav1dError>` maps the internal `EAGAIN → NeedMoreData`, `ENOMEM → OutOfMemory`, `EINVAL → InvalidData`, and everything else — including the `frame_size_limit` `ERANGE` — to `Other`.)

### Cancellation

The 0.6.0 Rust API provides `Decoder::set_stop`, accepting an
`Option<Arc<dyn enough::Stop>>`. Single-threaded decoding checks at superblock-row
boundaries; tile workers also check for cancellation. A triggered token returns
`Error::Cancelled`. `None` disables cancellation checks. This is cooperative
cancellation, not a hard wall-clock deadline; `frame_size_limit` remains useful
as a separate bound on declared dimensions. This API is not in published 0.5.7.

## Safety Model

The default library compiles under crate-wide `forbid(unsafe_code)` and uses
runtime borrow-overlap checks. Unsafe implementations behind dependency APIs
remain part of the trust boundary, including `rav1d-disjoint-mut`, alignment
helpers, archmage, and SIMD memory-access helpers. This is not a proof of the
whole decoder or its dependencies.

Enabling `unchecked`, `c-ffi`, or `asm` removes the crate-wide prohibition.
`c-ffi` implies `unchecked`; `asm` implies `c-ffi`; `partial_asm` also implies
`unchecked`. In unchecked SIMD paths, local exceptions allow raw loads/stores,
and untracked buffer constructors bypass runtime overlap tracking. Some modules
retain their own prohibitions. Normal Rust callers need none of these features.

The default SIMD path uses archmage for feature-token dispatch and
`safe_unaligned_simd` for reference-based loads/stores, with slice-based kernel
interfaces.

Verify at runtime with `rav1d_safe::enabled_features()` — returns a comma-delimited list including the active safety level (e.g. `"bitdepth_8, bitdepth_16, safety:forbid-unsafe"`).

## What's Been Ported

The default build compiles under `forbid(unsafe_code)` in the main crate. SIMD kernels live in `src/safe_simd/`, with additional entropy specialization in `src/msac.rs`.

### Ported: All DSP Kernels (AVX2 + NEON)

Every DSP kernel family has a safe Rust SIMD implementation that compiles under `forbid(unsafe_code)`:

| Module | x86 ASM replaced | ARM ASM replaced | Safe Rust |
|--------|-------------------|-------------------|-----------|
| mc (motion compensation) | 3 files (SSE/AVX2/AVX-512) x2 bitdepths | 4 files (32+64-bit) x2 bitdepths + SVE/dotprod | `mc.rs` + `mc_arm.rs` |
| itx (inverse transforms) | 3 files x2 bitdepths | 2 files x2 bitdepths | `itx.rs` + `itx_arm.rs` |
| ipred (intra prediction) | 3 files x2 bitdepths | 2 files x2 bitdepths | `ipred.rs` + `ipred_arm.rs` |
| cdef (directional enhancement) | 3 files x2 bitdepths | 2+tmpl files x2 bitdepths | `cdef.rs` + `cdef_arm.rs` |
| loopfilter | 3 files x2 bitdepths | 2 files x2 bitdepths | `loopfilter.rs` + `loopfilter_arm.rs` |
| looprestoration (Wiener + SGR) | 3 files x2 bitdepths | 2+common+tmpl files x2 bitdepths | `looprestoration.rs` + `looprestoration_arm.rs` |
| filmgrain | 3+common files x2 bitdepths | 2 files x2 bitdepths | `filmgrain.rs` + `filmgrain_arm.rs` |
| pal (palette) | 1 file | *(none — ARM uses scalar)* | `pal.rs` |
| refmvs (reference MVs) | 1 file | 2 files (32+64-bit) | `refmvs.rs` + `refmvs_arm.rs` |
| msac (entropy decoder) | 1 file (shared) | 1 file (shared) | inline in `msac.rs` |
| cpuid | 1 file (55 lines) | — | replaced by `std::arch` detection in `cpu.rs` |

The entropy decoder combines scalar routines with selected safe SIMD specialization; see the [measured entropy experiments](benchmarks/still-entropy-2026-09-07/README.md).

### Not Ported (With Rationale)

**Scaled MC** (put_8tap_scaled, prep_8tap_scaled, put_bilin_scaled, prep_bilin_scaled) — These functions use per-pixel variable step sizes with per-pixel filter selection, making them fundamentally different from fixed-block MC. The ASM versions are heavily register-scheduled for this pattern. Falls back to scalar Rust. ~2% of profile on inter-frame content.

**SSE-only paths** — 14 files, ~52k lines. The safe SIMD dispatch jumps straight to AVX2 when available. On pre-AVX2 hardware (pre-Haswell, 2013), the decoder falls back to scalar Rust rather than SSE intrinsics. SSE-only x86 hardware is rare enough that maintaining a second intrinsics tier isn't worth the code.

**ARM SVE2, dotprod, i8mm extensions** — `mc_dotprod.S` (1,880 lines) and `mc16_sve.S` (1,649 lines) are optional fast paths for newer ARM cores. The safe SIMD covers baseline NEON; these extension paths fall back to the NEON implementation.

**Remaining AVX-512 paths** — Some AVX-512 paths have been ported (itx, mc, ipred, looprestoration Wiener), but others remain unported. Falls back to AVX2 where not implemented.

**ASM infrastructure files** — `x86inc.asm` (1,983 lines), `asm.S`, `util.S`, `*_tmpl.S`, `*_common.S` are macro libraries and constants that only exist to support the raw assembly. No independent functionality to port.

## Performance

For the current assembly-disabled upstream comparison, see the
[matched 2K/4K/8K benchmark](benchmarks/noasm-2026-09-08/README.md).
Upstream rav1d has no memory-safe feature mode; turning off its assembly selects
its Rust implementation, which still contains unsafe code. Checked rav1d-safe
retains safe SIMD and overlap checks.

### Historical feature-mode measurements

The following older measurements compare rav1d-safe's own feature modes. They
are not the new upstream no-assembly comparison.

Historical benchmark setup: x86_64 (Zen 4, AVX2), single-threaded, Rust 1.93+, fat LTO. Run with `just profile` (500 iterations for IVF, 20 iterations for AVIF).

### Real photographs (AVIF decode, single image)

Single still images at web-typical quality (YUV420, q60). Source: Google-native 8K photo, downscaled with ImageMagick. These numbers reflect real-world AVIF decode performance where SIMD kernels dominate.

| Resolution | ASM | Safe (checked) | Safe (unchecked) | Safe vs ASM |
|------------|-----|----------------|------------------|-------------|
| 4K (3840x2561) | 120.7 ms | 187.8 ms | 179.5 ms | 1.56x |
| 8K (8192x5464) | 714.1 ms | 1103.2 ms | 1066.9 ms | 1.54x |

(Measured at v0.5.6. The 0.5.6 SIMD work — i16-packed `pmaddwd` transform row+col passes
and YMM-widened loopfilter — brought the 4K checked ratio from 2.0× down to 1.56×.)

### Small test vectors (IVF, multi-frame decode)

dav1d-test-data allintra 352x288 (39 frames). Entropy-heavy bitstream where the serial msac decoder dominates, which compresses the ratio compared to pixel-heavy workloads.

| Build | ms/iter | ms/frame | vs ASM |
|-------|---------|----------|--------|
| ASM | 104.3 | 2.67 | 1.0x |
| Safe (checked) | 158.6 | 4.07 | 1.52x |
| Safe (unchecked) | 153.3 | 3.93 | 1.47x |
| Partial ASM | 141.2 | 3.62 | 1.35x |

### Where the gap comes from

The safe build is ~1.56x slower on 4K real images and ~1.52x on entropy-heavy vectors. The gap breaks down:

- **Entropy decoder (msac)**: ~45% of decode time. Serial dependency chain — the core symbol decode loop can't be parallelized; ~95% of `decode_coefs` is irreducible algorithmic cost (only ~3-5% is Rust-specific bounds-check/index overhead). The `partial_asm` feature uses hand-tuned ASM for msac and loopfilter, bringing 4K photo decodes to 1.25x vs full ASM.
- **Calling conventions**: The ASM kernels use custom register allocation across function boundaries. Rust's ABI reloads registers at each call site.
- **Scaled MC**: Falls back to scalar Rust (~2% of inter-frame content). The ASM version uses per-pixel variable-step register scheduling that doesn't map cleanly to safe intrinsics.
- **Bounds checking**: The `unchecked` feature skips DisjointMut borrow tracking. This saves ~5% on photos, confirming the tracking overhead is modest.

### Reproduce locally

```sh
just generate-bench-avif  # create 4K/8K AVIF test images (requires avifdec + avifenc)
just profile              # all four modes side-by-side (ASM, partial ASM, checked, unchecked)
just profile-quick        # same with fewer iterations
```

## Conformance

Tested against the [dav1d-test-data](https://code.videolan.org/videolan/dav1d-test-data) suite. MD5 hashes verified at all CPU dispatch levels (scalar, SSE4.2, AVX2, native).

**784 of 803 test vectors pass** across all levels.

19 vectors are not exercised by the test harness:

| Category | Count | Reason |
|----------|-------|--------|
| sframe | 1 | Requires S-frame support |
| svc (operating points) | 6 | Always decodes at default operating point |
| argon (vq_suite) | 12 | Various decode modes and operating point selection tests |

Run conformance tests with `cargo test --release --test decode_cpu_levels`.

## Building

Use a current stable Rust toolchain. The default checked library is tested on its declared Rust 1.89 minimum; the workflow examples and benchmarks record their exact compiler versions. Install via [rustup.rs](https://rustup.rs).

```sh
# Default safe-SIMD build (recommended)
cargo build --release

# With original hand-written assembly (for benchmarking)
cargo build --features asm --release

# Run tests
cargo test --release
```

### Feature Flags

| Feature | Default | Description |
|---------|---------|-------------|
| `bitdepth_8` | on | 8-bit pixel support |
| `bitdepth_16` | on | 10/12-bit pixel support |
| `unchecked` | off | Skip DisjointMut borrow tracking; enables frame threading and SSE2 msac on x86_64 |
| `partial_asm` | off | ASM for entropy decoding (msac) and loopfilter only; safe SIMD everything else. Implies `unchecked` |
| `c-ffi` | off | C API entry points (`dav1d_*` symbols). Implies `unchecked` |
| `asm` | off | Full hand-written assembly. Implies `c-ffi` |

Safety chain: `default` (`forbid(unsafe_code)`, tile threading) -> `unchecked` (frame threading) -> `c-ffi` -> `asm`. Each level relaxes the safety constraint.

### Cross-Compilation

```sh
# aarch64
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" \
  cargo build --target aarch64-unknown-linux-gnu --release

# Verify aarch64 NEON compiles
cargo check --target aarch64-unknown-linux-gnu
```

Supported targets: `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `i686-unknown-linux-gnu`, `armv7-unknown-linux-gnueabihf`, `riscv64gc-unknown-linux-gnu`.

## Image tech I maintain

| | |
|:--|:--|
| **Codecs** ¹ | [zenjpeg] · [zenpng] · [zenwebp] · [zengif] · [zenavif] · [zenjxl] · [zenjxl-decoder] · [jxl-encoder] · [zenbitmaps] · [heic] · [zentiff] · [zenpdf] · [zensvg] · [zenjp2] · [zenraw] · [ultrahdr] |
| Codec internals | [zenrav1e] · **rav1d-safe** · [zenravif] · [zenavif-parse] · [zenavif-serialize] |
| Compression | [zenflate] · [zenzop] · [zenzstd] |
| Processing | [zenresize] · [zenquant] · [zenblend] · [zenfilters] · [zensally] · [zentone] |
| Pixels & color | [zenpixels] · [zenpixels-convert] · [linear-srgb] · [garb] · [zenyuv] |
| Pipeline & framework | [zenpipe] · [zencodec] · [zencodecs] · [zenlayout] · [zennode] · [zenwasm] · [zentract] |
| Metrics | [zensim] · [fast-ssim2] · [butteraugli] · [zenmetrics] · [resamplescope-rs] |
| Pickers & ML | [zenanalyze] · [zenpredict] · [zenpicker] · [zenanalyze-api] |
| Test corpora | [codec-corpus] · [imazen-26] |
| Products | [Imageflow] image engine ([.NET][imageflow-dotnet] · [Node][imageflow-node] · [Go][imageflow-go]) · [Imageflow Server] · [ImageResizer] (C#) |

<sub>¹ pure-Rust, `#![forbid(unsafe_code)]` codecs, as of 2026</sub>

### General Rust awesomeness

[zenbench] · [archmage] · [magetypes] · [enough] · [whereat] · [cargo-copter] · [zenutils]

[Open source](https://www.imazen.io/open-source) · [@imazen](https://github.com/imazen) · [@lilith](https://github.com/lilith) · [lib.rs/~lilith](https://lib.rs/~lilith)

[zenjpeg]: https://github.com/imazen/zenjpeg
[zenpng]: https://github.com/imazen/zenpng
[zenwebp]: https://github.com/imazen/zenwebp
[zengif]: https://github.com/imazen/zengif
[zenavif]: https://github.com/imazen/zenavif
[zenjxl]: https://github.com/imazen/zenjxl
[zenjxl-decoder]: https://github.com/imazen/zenjxl-decoder
[jxl-encoder]: https://github.com/imazen/jxl-encoder
[zenbitmaps]: https://github.com/imazen/zenbitmaps
[heic]: https://github.com/imazen/heic
[zentiff]: https://github.com/imazen/zenextras
[zenpdf]: https://github.com/imazen/zenextras
[zensvg]: https://github.com/imazen/zenextras
[zenjp2]: https://github.com/imazen/zenextras
[zenraw]: https://github.com/imazen/zenraw
[ultrahdr]: https://github.com/imazen/ultrahdr
[zenrav1e]: https://github.com/imazen/zenrav1e
[zenravif]: https://github.com/imazen/cavif-rs
[zenavif-parse]: https://github.com/imazen/zenavif
[zenavif-serialize]: https://github.com/imazen/zenavif
[zenflate]: https://github.com/imazen/zenflate
[zenzop]: https://github.com/imazen/zenzop
[zenzstd]: https://github.com/imazen/zenzstd
[zenresize]: https://github.com/imazen/zenresize
[zenquant]: https://github.com/imazen/zenquant
[zenblend]: https://github.com/imazen/zenblend
[zenfilters]: https://github.com/imazen/zenpipe
[zensally]: https://github.com/imazen/zensally
[zentone]: https://github.com/imazen/zentone
[zenpixels]: https://github.com/imazen/zenpixels
[zenpixels-convert]: https://github.com/imazen/zenpixels
[linear-srgb]: https://github.com/imazen/linear-srgb
[garb]: https://github.com/imazen/garb
[zenyuv]: https://github.com/imazen/zenjpeg
[zenpipe]: https://github.com/imazen/zenpipe
[zencodec]: https://github.com/imazen/zencodec
[zencodecs]: https://github.com/imazen/zenpipe
[zenlayout]: https://github.com/imazen/zenpipe
[zennode]: https://github.com/imazen/zennode
[zenwasm]: https://github.com/imazen/zenwasm
[zentract]: https://github.com/imazen/zentract
[zensim]: https://github.com/imazen/zensim
[fast-ssim2]: https://github.com/imazen/fast-ssim2
[butteraugli]: https://github.com/imazen/butteraugli
[zenmetrics]: https://github.com/imazen/zenmetrics
[resamplescope-rs]: https://github.com/imazen/resamplescope-rs
[zenanalyze]: https://github.com/imazen/zenanalyze
[zenpredict]: https://github.com/imazen/zenanalyze
[zenpicker]: https://github.com/imazen/zenanalyze
[zenanalyze-api]: https://github.com/imazen/zenanalyze
[codec-corpus]: https://github.com/imazen/codec-corpus
[imazen-26]: https://github.com/imazen/imazen-26
[zenbench]: https://github.com/imazen/zenbench
[archmage]: https://github.com/imazen/archmage
[magetypes]: https://github.com/imazen/archmage
[enough]: https://github.com/imazen/enough
[whereat]: https://github.com/lilith/whereat
[cargo-copter]: https://github.com/imazen/cargo-copter
[zenutils]: https://github.com/imazen/zenutils
[Imageflow]: https://github.com/imazen/imageflow
[Imageflow Server]: https://github.com/imazen/imageflow-dotnet-server
[ImageResizer]: https://github.com/imazen/resizer
[imageflow-dotnet]: https://github.com/imazen/imageflow-dotnet
[imageflow-node]: https://github.com/imazen/imageflow-node
[imageflow-go]: https://github.com/imazen/imageflow-go