It must be the first in its process to ask `hevc_available()`, but #2860 moved it into the shared
`it` binary next to `hardware_encode`, which asks too. Under `cargo test` (xtask's fallback without
nextest) the two raced and it failed with "nothing has asked yet". Same layout as its Windows twin
`nvenc_hevc_warm.rs`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ItemKind::Media` holds a `Box<MediaClip>` now. The test is macOS-only, so Linux CI never compiled
it and clippy on macOS failed on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export > Hardware encoding on macOS was CPU-bound by its own colour conversion: the
encode thread converted every RGBA frame to NV12 (rgba_to_yuv420_8) while the rayon
pool rendered the next batch, so the serial conversion took 20-27 ms a 1080p frame
and the media engine waited (perf.stats: convertMs 29.6 s of a 33.7 s export). The
VideoToolbox session now takes the export's RGBA pictures as 32BGRA buffers tagged
BT.709 and converts them itself, as NVENC has since #486; the encode thread copies
8 MB a frame (0.3-0.4 ms). A VideoToolbox that refuses RGB input on the first picture
gets the planar session and the CPU conversion back, so nothing depends on it.
Apple M2 Max, A/B interleaved, median of 3: hardware-encode export of a 60 s 1080p
timeline 35.8 s -> 29.4 s (now render-bound, waitMs 18-25 s), with GPU rendering
22.2 s -> 18.7 s at 277 % -> 219 % CPU; the software-encoder export is byte-identical.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
what_nvenc_cannot_do_is_an_error_that_says_why had a cognitive complexity
of 34, over the workspace limit of 30, so
`cargo clippy -p filmcraft-platform --all-targets -- -D warnings` failed on
Windows. The file is Windows-only, so Ubuntu CI does not lint it.
The test keeps the declined-configuration cases. The HDR cases, the
interlaced and no-encoder cases, the export size limits and the factory
cases move to four tests of their own, with the same assertions.
ItemKind::Media now holds a Box<MediaClip>, but the NVENC HEVC export tests
still passed a bare MediaClip, so filmcraft-platform's Windows-only test
targets failed to compile with E0308. Wrap the MediaClip in Box::new in the
three project() helpers.
* Tests: one integration-test binary per crate
Each crate with several tests/*.rs files keeps them as modules of tests/it/,
so cargo links one test executable per crate instead of one per file. Files
that change process-wide state or time a workload stay binaries of their own.
* Profiles: line tables for workspace crates, no debug info for dependencies
* Windows ARM64: nightly and on release pushes instead of every push to main
* xtask ci: clippy and tests in the dev profile
The release profile's thin LTO and single codegen unit made linking the test
binaries take most of the gate. The codec and audio crates stay at opt-level 3
in the dev profile, so their tests run at release speed.
* xtask ci: nextest, doctests, no examples, one wasm check
Tests run with cargo nextest when it is installed (cargo test otherwise) and
build with --lib --bins --tests, so examples aren't linked; doctests run
separately. The wasm check keeps going past the first failing crate. VP8
fixture generation locks across processes, since nextest runs each test in
its own.
* VP9 synthetic fixtures: write aside and rename
A test in another process could read a fixture ffmpeg was still writing.
* Fuzz tests: a third of each sweep by default, all of it with FILMCRAFT_FUZZ_ITERS
Every seed, mutation kind and thread count still runs. Header bytes each get
one overwrite value and two bit flips in rotation, so every value and bit
index still occurs across the region. FILMCRAFT_FUZZ_ITERS=N runs the whole
sweep plus N random rounds per seed, as before.
* Platform fallback tests: one failure point per hybrid path
The failure-anywhere test injected a failure at four points around every
restart point; the hybrid's paths differ only in the first GOP, around the
second and last restart points, at the end and never. The replay test needs
the first two GOPs.
* Export tests: shorter ranges with the same checks
The bitrate test measures 0.5–1.5 s, where each preset reaches its own
target (on 0–3 s the simple demo left two presets at the same rate). The
every-preset test exports three frames per preset.
* Audio tests: effects in parallel, a table-driven FFT for the Opus quality metric
The extreme-settings test keeps both settings and the 40 s tail for every
effect, one thread each. The Opus quality metric's FFT builds its twiddles
once and allocates nothing per transform (same algorithm and summation order).
* CI: dev-profile gate with nextest
xtask ci no longer builds with --release, so the release-profile overrides
go; nextest is installed for the test step.
* CI: apt packages through the cached install-apt-packages action
* Review fixes: fuzz tests with a panic hook in their own binaries, loud fixture lock
The fuzz tests of av1, h264, hevc, mpeg2v, vp8 and vp9 count panics through
a process-wide hook, so a panic in another test of the same process would
fail them; they are binaries of their own again. The VP8 fixture lock fails
the test instead of skipping it silently, and the full-sweep doc comments say
how many truncation points the full run samples.
* Fuzz tests: rotate the default subset per run (FILMCRAFT_FUZZ_SEED replays one)
* platform tests: decode-switch tests in their own binaries, one NVENC lock
The SDR (Main) HEVC encoder's sample entry carries an nclx BT.709 colr since
678fe732 tagged every SDR export. Assert what "nothing HDR" means now (a
BT.709 limited-range colr, no mdcv / clli) and fix the outdated comment.
Fixes#2375
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ffmpeg keeps a trailing zero byte after the hvcC PPS that the in-band PPS lacks, so the byte-for-byte comparison read it as a parameter-set change and sent the stream to the software decoder from its first sample. A NAL unit never ends with 0x00 (H.264 7.4.1, H.265 7.4.2): those bytes are trailing_zero_8bits, so both sides are compared without them. The platform fallback test used a trailing zero byte as its changed SPS; it now changes nal_ref_idc.
With NVENC registered on Linux, `filmcraft-cli --test cli` aborts in 2 of 45 runs on an RTX 3090
(`malloc(): mismatching next->prev_size`, `malloc(): unaligned fastbin chunk detected`) right
after an export finishes; 0 of 30 runs fail with this reverted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The NVENC HEVC encoder (Main 8-bit SDR and Main 10 HDR PQ / HLG) was gated to
Windows. It now also runs on 64-bit Linux, on the CUDA device the H.264 encoder
already uses: the H.265 format and HDR probes are registered there and the
HEVC tests run on both systems.
NVENC registration on Linux now happens after the VA-API probe. Started beside
it, the background HEVC probe's first cuInit could deadlock with
libva-nvidia-driver, which calls cuInit from its library constructor under the
loader lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in crates/engine/src/export_tools.rs: both sides added an
import (BitrateMode for CRF, GpuRendering from main); both kept.
main's new Chinese catalog requires every tl! string: added zh-CN for
"Quality (CRF)" and "CRF (constant quality)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session kept the parser's user data in a Box and later wrote through that Box,
which invalidates the raw pointer the callbacks use (Stacked/Tree Borrows).
Rust code now reaches the state only through the same into_raw pointer, never
across the parse call, and Drop frees it after the parser is destroyed.
The setting test accepts either Linux backend's decoder when NVDEC registered,
and the README notes perf.stats names NVDEC for streams VA-API decodes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds BitrateMode::Crf with ExportSettings::crf (0-51, default 23) and
maps it to the built-in encoder's RateControl::Crf, which export never
used. The Export panel offers "CRF (constant quality)" with a Quality
(CRF) slider in place of the bitrate fields; file.exportMedia takes
`crf` and `bitrateMode: "crf"`. H.265 (hardware only) rejects it, and
VideoToolbox / NVENC decline it so the built-in encoder runs. Settings
saved before this read as CRF 23.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The i18n CRLF normalisation already landed with #488, so main's version is kept.
abi_tests now asserts the HEVC VUI field offsets (inside the VUI and within
NV_ENC_CONFIG_HEVC) to rule out a binding mismatch behind the reversed timing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/setting.rs: keep the check against the backend register() reported
(main's fix expects VA-API on Linux, which NVDEC now sits in front of).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parser's picture parameters were passed to cuvidDecodePicture unread.
Their shared head is now checked first: a null data pointer, zero length, no
slices, or a slice offset past the end of the data is an error, so the
session fails over to the software decoder and the driver never sees the
picture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a second Linux decoder backend, crates/platform/src/nvdec, that talks to
NVIDIA's driver directly (libnvcuvid + the CUDA driver, both loaded at run
time). It is registered in front of VA-API: what NVDEC declines still reaches
VA-API, then the software decoders. H.264 8-bit and HEVC 8/10-bit, 4:2:0
progressive.
NVIDIA's parser takes Annex B access units, so there is no host-side DPB.
Pictures are read back as NV12 / P016 through the existing biplanar path and
are bit-exact with the software decoders. The CUDA context is the one
nvenc::device already retains; it gains push/pop and copy-to-host. A seek
recreates the parser and keeps the decoder.
Also fixes tests/setting.rs, which expected the Media Foundation backend name
on Linux, and updates the Hardware decoding tooltip and its Spanish row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
winit on Windows sends no cursor events while files are dragged in, so the
pointer was stale and the drop was never placed on the timeline. The desktop
app now passes a host hook that reads the cursor from the OS (GetCursorPos),
which the UI maps into window points.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8-bit NVENC sessions (H.265 Main, hardware H.264) now take the export's RGBA
frames as ABGR input buffers and NVENC converts them to BT.709 limited 4:2:0
on the GPU, with the same codes as rgba_to_yuv420_8. The CPU conversion ran
on the encode thread, where it competed with the render of the next frames
for the thread pool (about 13 ms per 1080p frame instead of 1.5 ms) and held
NVENC back. A driver that refuses RGB input falls back to the CPU conversion
into NV12. HEVC Main 10 is unchanged.
60 s of a 1080p30 project as H.265 in the app: 18.7 s -> 15.0 s (96 -> 120
fps); headless 30.3 s -> 16.8 s.
Co-Authored-By: Claude <noreply@anthropic.com>
Every VA-API session opened its own display and terminated it when dropped. With NVIDIA's driver
that is a CUDA context each: 0.2-0.3 s to open, 60-70 ms to terminate and about 100 MB of video
memory, on the thread creating or dropping the decoder (a frame worker at every clip change, the
UI thread when media is removed: a ~0.6 s freeze deleting five played clips, and dropped frames at
clip changes).
Sessions now take the process's shared display (the startup probe's, kept for the life of the
process like the libraries) and make only their own configuration, context and surfaces. Display
is Sync on the strength of libva's Multithreading Guide (va.h): its functions and drivers are
thread-safe, and every session uses only its own VA objects. A session that cannot be made on the
shared display is tried once on a new display, which is shared from then on if it works (a GPU
reset).
On NVIDIA (RTX 3060): deleting five played clips 650-690 ms -> 132-198 ms, dropped frames in 20 s
of playback 2-3 -> 0, peak video memory 1281 -> 1010 MiB. Intel and software unchanged.
Test: tests/vaapi_displays.rs (decoders one after another and six at once on six threads open no
display; pictures bit-exact). The VA-API hardware suite passes on NVIDIA and Intel, and the
concurrency tests 20 times each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a hardware decoder failed, the replay of the run through the software decoder stopped at the
first sample the software decoder rejected (`?` in the replay loop): the pictures it had already
output were dropped, the rest of the run was never fed, the error was returned for the failed
sample even when the software decoder accepted it, and the replay log was not restored. That
happens when the GPU tolerated a damaged sample our decoder rejects and then fails later in the
same run; one picture went missing on a damaged HEVC file through VA-API (Intel).
The replay now behaves like software decoding from the restart point: a rejected sample is skipped
(logged when the hardware had already decoded it); if the failed sample itself is rejected its
error is this call's, and the pictures replayed before it are kept in `carry` for the next output.
Regression test (every platform, no GPU): a stand-in hardware decoder that decodes one damaged
sample the software decoder rejects and fails at that sample or later; every software picture
comes out once and in order, errors are reported where the software decoder reports them, and the
pictures from the next restart point are the software decoder's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delegate HEVC VUI timing to NVENC's configured frame rate instead of the
explicit timing pair reversed by the affected driver. Preserve existing
stream quality/timing assertions and cover integer and fractional rates.
Normalize line endings in the translation test's source scanner so it
excludes test modules in Windows checkouts as well as LF checkouts.
Assisted-by: Codex
main gained NVENC H.265 Main / Main 10 (#328, #360), export stage timings
(#370) and VA-API decoding (#358). Resolution: main's NVENC session, FFI and
export factory, with this PR's changes ported onto them:
- ffi: the portable Guid (now also for the HEVC GUIDs), NV_ENC_DEVICE_TYPE_CUDA;
- session: device.rs (Direct3D 11 / retained CUDA primary context), the
Linux libnvidia-encode.so.1 loader, Device::KIND;
- export: factory_with and its tests, the even-size check, the saturating
max bitrate; H.265 through NVENC stays Windows-only (the factory declines
it on Linux, where nothing probed or tested it);
- Cargo.toml: main's Linux libloading dependency covers both users;
- docs: README / ROADMAP / architecture now say NVENC H.264 runs on Windows
and Linux; ADR and platform README keep both addenda / test rows;
- abi_tests: note why the MSVC-generated expectations hold on LP64 Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NVENC export: keep main's Main 10 / Main dispatch and time both colour
conversions (rgbf_to_yuv420_10, rgba_to_yuv420_8) as Stage::Convert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
encode / encode_10 compute the plane sizes they check with saturating_mul;
fill_p010, rgbf_to_yuv420_10 and the HDR export test use as_chunks instead of
chunks_exact with a constant size (clippy::chunks_exact_to_as_chunks).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
filmcraft_export::Stage, stage_times(), note_stage() and timed(): the wall
time of setup, loudness, render, encode (and its RGB->YUV convert part),
audio, mux and finish, summed per process like export.hardware. Converters
in the H.264 / ProRes / NVENC / VideoToolbox encoders are timed as Convert.
Two clock reads per stage per batch; the exported file does not change
(determinism tests). Test: an export grows every stage counter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>