Linux: H.265 export through NVENC

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>
This commit is contained in:
SilentYeti 2026-10-09 23:48:21 -07:00
parent c4bb63bb6d
commit c635d9af9e
16 changed files with 56 additions and 51 deletions

View File

@ -185,7 +185,7 @@ No FFmpeg inside. The video codecs, AAC, Opus and the containers are our own Rus
| **Matroska / WebM** | ✓ | | Lacing, Cues, header stripping, HDR colour metadata |
| **Stills** | ✓ | ✓ | Import PNG, JPEG, GIF, WebP, TIFF and BMP; export PNG sequences and animated GIF |
Also imported: MPEG-2 / MPEG-1 video, AC-3, MP2, MXF (OP1a / OP-Atom), MPEG transport and program streams (AVCHD, broadcast, DVD), Ogg and image sequences. Not yet: AV1 export, camera RAW, E-AC-3. Hardware decoding works on macOS (VideoToolbox), Windows (Media Foundation) and, for H.264 and HEVC, Linux (VA-API); H.264 export can use NVIDIA's encoder on Windows and Linux (opt-in), and H.265 (HEVC Main, 8-bit) export works through it (NVENC on Windows, VideoToolbox on macOS) ([#30](https://github.com/storytold/filmcraft/issues/30)).
Also imported: MPEG-2 / MPEG-1 video, AC-3, MP2, MXF (OP1a / OP-Atom), MPEG transport and program streams (AVCHD, broadcast, DVD), Ogg and image sequences. Not yet: AV1 export, camera RAW, E-AC-3. Hardware decoding works on macOS (VideoToolbox), Windows (Media Foundation) and, for H.264 and HEVC, Linux (VA-API); H.264 export can use NVIDIA's encoder on Windows and Linux (opt-in), and H.265 (HEVC Main, 8-bit) export works through it (NVENC on Windows and Linux, VideoToolbox on macOS) ([#30](https://github.com/storytold/filmcraft/issues/30)).
<br>
@ -289,10 +289,10 @@ FilmCraft is in **alpha**. We track two numbers ([docs/target-app-parity.md](doc
The biggest gaps today:
- **Speed on big footage.** Hardware decoding works on macOS and Windows, and for H.264 and HEVC on Linux (VA-API, or NVDEC with NVIDIA's driver; VP9 and AV1 decode in software there). Blend modes and the most common effects run on the GPU, but Lumetri, keys and export rendering still run on the CPU; H.264 encoding can use the hardware encoder on macOS and on Windows and Linux with an NVIDIA GPU (opt-in, Export ▸ Hardware encoding), and H.265 export is hardware-only on both (HDR sequences export Main 10 PQ / HLG with NVENC; VideoToolbox writes 8-bit SDR) ([#30](https://github.com/storytold/filmcraft/issues/30)).
- **Speed on big footage.** Hardware decoding works on macOS and Windows, and for H.264 and HEVC on Linux (VA-API, or NVDEC with NVIDIA's driver; VP9 and AV1 decode in software there). Blend modes and the most common effects run on the GPU, but Lumetri, keys and export rendering still run on the CPU; H.264 encoding can use the hardware encoder on macOS and on Windows and Linux with an NVIDIA GPU (opt-in, Export ▸ Hardware encoding), and H.265 export is hardware-only on all three (HDR sequences export Main 10 PQ / HLG with NVENC; VideoToolbox writes 8-bit SDR) ([#30](https://github.com/storytold/filmcraft/issues/30)).
- **Premiere projects.** `.prproj` files can't be opened yet; bring sequences over as FCP7 XML, AAF or OTIO.
- **No plugins.** No VST3 / Audio Units or OpenFX hosting.
- **Delivery codecs.** H.264 is our only software delivery-codec export; H.265 exports only through a hardware encoder (NVIDIA on Windows: Main 8-bit SDR and Main 10 HDR PQ / HLG; VideoToolbox on macOS: 8-bit Main, SDR); no AV1 export yet.
- **Delivery codecs.** H.264 is our only software delivery-codec export; H.265 exports only through a hardware encoder (NVIDIA on Windows and Linux: Main 8-bit SDR and Main 10 HDR PQ / HLG; VideoToolbox on macOS: 8-bit Main, SDR); no AV1 export yet.
- **Real-world media and platforms.** Our decoders are bit-exact on conformance streams, but camera and phone files in the wild are less tested. Windows and Linux get far less testing than macOS.
- **AI features.** Few so far; speech to text is optional and off by default.

View File

@ -62,7 +62,7 @@ impl H264Profile {
}
/// Export ▸ Hardware encoding: whether H.264 may be encoded by the system's hardware video encoder
/// (VideoToolbox on macOS, NVENC on NVIDIA GPUs on Windows) instead of FilmCraft's own encoder. Off
/// (VideoToolbox on macOS, NVENC on NVIDIA GPUs on Windows and Linux) instead of FilmCraft's own encoder. Off
/// by default: hardware encoders make different streams, and exports are otherwise byte-identical
/// from run to run and machine to machine.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -61,7 +61,7 @@ let availability = filmcraft_platform::register(); // Available("VideoToolbox")
- After a `flush` (which drains the MFT) the MFT only restarts at an IDR picture; the GOP cache
always seeks after a flush, and a caller that continues from the middle of a GOP gets an error
that `HybridDecoder` answers by replaying the run in software.
- **Windows and 64-bit Linux: NVIDIA NVENC H.264 encoding, and H.265 (HEVC) on Windows** (`nvenc/`), 8-bit SDR 4:2:0 for Export. The driver's
- **Windows and 64-bit Linux: NVIDIA NVENC H.264 and H.265 (HEVC) encoding** (`nvenc/`), 8-bit SDR 4:2:0 for Export. The driver's
`nvEncodeAPI64.dll` (Windows) or `libnvidia-encode.so.1` (Linux), API 12.1, is loaded at run time,
so machines without NVIDIA still start. Windows uses a Direct3D 11 device; Linux retains a primary
CUDA context through the runtime-loaded `libcuda.so.1`, released after the encoder is destroyed.
@ -76,8 +76,8 @@ let availability = filmcraft_platform::register(); // Available("VideoToolbox")
It declines two-pass VBR, HDR, MXF, interlaced output, sizes outside NVENC's limits and systems
without an NVIDIA GPU or driver, and the software encoder runs instead. A failure during an export
ends it with an error, since a hardware stream cannot be finished in software. The same session,
ring and input path encode H.265, see [Hardware H.265 (HEVC) encoding (Windows,
NVENC)](#hardware-h265-hevc-encoding-windows-nvenc).
ring and input path encode H.265, see [Hardware H.265 (HEVC) encoding
(NVENC)](#hardware-h265-hevc-encoding-nvenc).
- **Linux: NVDEC, H.264 (`avcC`) and HEVC (`hvcC`)** on NVIDIA's proprietary driver (`nvdec/`): H.264
8-bit and HEVC 8- / 10-bit, 4:2:0 progressive. `libnvcuvid.so.1` and `libcuda.so.1` are loaded at
run time; without them (or without a device) nothing is registered. NVIDIA's own parser takes the
@ -188,7 +188,7 @@ The sample entry's `hvcC` record is the one VideoToolbox wrote for the stream (r
format description's sample description extension atoms, with the VPS / SPS / PPS), so profile,
level and flags are the encoder's own. If it is missing the export stops with that reason.
## Hardware H.265 (HEVC) encoding (Windows, NVENC)
## Hardware H.265 (HEVC) encoding (NVENC, Windows and Linux)
One codec enum (`nvenc::Codec`, chosen by `Profile::HevcMain` the way `VtProfile::HevcMain` does it
for VideoToolbox) parameterises the NVENC session: the codec and profile GUIDs, the capability
@ -295,13 +295,13 @@ level 4 High tier, which many hardware decoders refuse; it is now level 4.1 Main
| `tests/hardware_encode.rs` (macOS) | what the hardware path takes and declines; round trip through our software decoder (every picture, in order, luma PSNR above 30 dB, keyframes no further apart than asked, no composition offsets); an export through `filmcraft_export` that decodes in our decoder and in ffmpeg / ffprobe (profile, size, frame count, BT.709); the built-in encoder still exporting everything hardware declines; exact output size at sizes that are not multiples of 16; hostile configurations (zero, huge, odd sizes, frame rates, bitrates, keyframe intervals, wrong planes) give errors and never panic; encoders dropped at any point do not crash or hang. The same for **HEVC**: Main profile, 8-bit 4:2:0, `hvc1` entry with VPS / SPS / PPS and 4-byte lengths, MP4 and QuickTime, AAC audio, two-pass refused, the format list agreeing with the probe, ffprobe reading `codec_name=hevc`, `profile=Main`, `codec_tag_string=hvc1`, `pix_fmt=yuv420p`, BT.709 |
| `tests/nvenc.rs` (Windows / Linux, NVIDIA) | H.264 from NVENC (1280×720, 6 Mbps, 72 frames) decodes with our decoder at worst 46.9 dB luma PSNR; IDR at 0, 24 and 48; dts / pts right |
| `tests/nvenc_export.rs` (Windows / Linux; fallback also tested without NVIDIA) | Export with hardware encoding against the software encoder through the export pipeline: the two decoded files at worst 54.8 dB luma PSNR; ffmpeg decodes the file without errors; declined cases go to the software encoder; the counters |
| `tests/nvenc_hevc.rs` (Windows, NVIDIA with HEVC) | HEVC from NVENC (1280×720, 6 Mbps, 72 frames, keyframe every 24) decodes with our HEVC decoder at worst 48.9 dB luma / 51.0 dB chroma PSNR; IDR at 0, 24, 48; dts strictly increasing, pts a permutation, `sps_max_num_reorder_pics` within the dts shift; no parameter sets in the samples; the `hvcC`'s profile / tier / level bytes are the SPS's own; VUI BT.709 limited and timing `(1, 24)`; hostile configurations, sizes, planes and encoders dropped mid-stream give errors, never panics; keyframes every 1–2 pictures without B-frames |
| `tests/nvenc_hevc_export.rs` (Windows, NVIDIA with HEVC) | H.265 export through the real pipeline, MP4 and QuickTime, toggle Auto and Off: counters (72 frames, 1 session, 0 declined), our decoder against the software H.264 export at worst 54.8 dB luma PSNR, ffprobe `codec_name=hevc`, `profile=Main`, `codec_tag_string=hvc1`, `pix_fmt=yuv420p`, BT.709 limited, 72 frames, 24/1, keyframes at 0 and 48, start 0, `ffmpeg -xerror` clean; with AAC audio; 1920×1080 and 642×362 cropped back from the coded size; every decline (HDR, analysis pass, two-pass, interlaced, non-square pixels, MXF, sizes over 65535, odd sizes, sizes outside the GPU's limits) an error naming NVENC, counted once; H.264 with hardware encoding Off never touches NVENC |
| `tests/nvenc_rgba_input.rs` (Windows, NVIDIA) | RGBA input, HEVC Main and H.264 High, 1280×720: solid red, green, blue, white, black and grey decode (our decoder) to the exact BT.709 limited codes of `rgba_to_yuv420_8`, within 2; a moving picture at worst 47.7 dB luma / 50.7 dB chroma PSNR against that conversion; Main 10 has no RGBA input; short pictures and planar / RGBA pictures for the other kind of encoder are errors |
| `tests/nvenc_hevc_probe.rs` (Windows) | the HEVC probe in a fresh process: its cost, the cached answer, `available(Hevc)` following it after `register()` (twice) |
| `tests/nvenc_hevc_warm.rs` (Windows) | `register()` answers the HEVC and the Main 10 questions on a thread of its own, before anyone asks; the answers are kept and `hdr_available(Hevc)` follows the Main 10 probe |
| `tests/nvenc_hevc_main10.rs` (Windows, NVIDIA with 10-bit HEVC) | HEVC Main 10 in process, PQ and HLG: a float picture (grey ramp to peak white, a moving box, saturated BT.2020 bars) is converted with `rgbf_to_yuv420_10`, encoded and decoded with our HEVC decoder (`Yuv16`, 10 bits, codes below 1024): worst 71.1 dB luma / 70.6 dB chroma PSNR on the 10-bit scale, 803 distinct luma levels on a ramp row, peak code 940 survives; IDR at 0 and 24; the VUI is BT.2020 / 16 or 18 / BT.2020 NCL / limited; the IDR samples carry two prefix SEI NAL units whose messages are byte-for-byte the expected 137 / 144 payloads (HLG: none; other pictures: none); Main and Main 10 refuse each other's pictures; hostile sizes, bitrates, signals, huge or empty SEI payloads, encoders dropped with SEI in flight |
| `tests/nvenc_hevc_hdr_export.rs` (Windows, NVIDIA with 10-bit HEVC) | HDR sequences through the real export job (PQ MP4, HLG MP4, PQ QuickTime): 24 frames, 1 session, 0 declined; ffprobe `hevc` / `Main 10` / `hvc1` / `yuv420p10le`, `bt2020` / `smpte2084` or `arib-std-b67` / `bt2020nc` / `tv`; PQ first-frame side data Mastering display (max 10000000/10000, min 1/10000) and Content light level, HLG none; `ffmpeg -xerror` clean; ffmpeg's yuv420p10le picture equals our decoder's, code for code; our importer reads PQ / HLG, BT.2020, 10-bit, and the `colr` / `mdcv` / `clli` boxes; luma PSNR against a ProRes HDR export of the same sequence 62.6 dB (PQ) / 61.8 dB (HLG); `settings.sdr` and SDR sequences stay Main 8-bit BT.709 with no HDR boxes; the factory rejects mismatched or wrong-length pictures and clamps NaN / infinity |
| `tests/nvenc_hevc.rs` (Windows / Linux, NVIDIA with HEVC) | HEVC from NVENC (1280×720, 6 Mbps, 72 frames, keyframe every 24) decodes with our HEVC decoder at worst 48.9 dB luma / 51.0 dB chroma PSNR; IDR at 0, 24, 48; dts strictly increasing, pts a permutation, `sps_max_num_reorder_pics` within the dts shift; no parameter sets in the samples; the `hvcC`'s profile / tier / level bytes are the SPS's own; VUI BT.709 limited and timing `(1, 24)`; hostile configurations, sizes, planes and encoders dropped mid-stream give errors, never panics; keyframes every 1–2 pictures without B-frames |
| `tests/nvenc_hevc_export.rs` (Windows / Linux, NVIDIA with HEVC) | H.265 export through the real pipeline, MP4 and QuickTime, toggle Auto and Off: counters (72 frames, 1 session, 0 declined), our decoder against the software H.264 export at worst 54.8 dB luma PSNR, ffprobe `codec_name=hevc`, `profile=Main`, `codec_tag_string=hvc1`, `pix_fmt=yuv420p`, BT.709 limited, 72 frames, 24/1, keyframes at 0 and 48, start 0, `ffmpeg -xerror` clean; with AAC audio; 1920×1080 and 642×362 cropped back from the coded size; every decline (HDR, analysis pass, two-pass, interlaced, non-square pixels, MXF, sizes over 65535, odd sizes, sizes outside the GPU's limits) an error naming NVENC, counted once; H.264 with hardware encoding Off never touches NVENC |
| `tests/nvenc_rgba_input.rs` (Windows / Linux, NVIDIA) | RGBA input, HEVC Main and H.264 High, 1280×720: solid red, green, blue, white, black and grey decode (our decoder) to the exact BT.709 limited codes of `rgba_to_yuv420_8`, within 2; a moving picture at worst 47.7 dB luma / 50.7 dB chroma PSNR against that conversion; Main 10 has no RGBA input; short pictures and planar / RGBA pictures for the other kind of encoder are errors |
| `tests/nvenc_hevc_probe.rs` (Windows / Linux) | the HEVC probe in a fresh process: its cost, the cached answer, `available(Hevc)` following it after `register()` (twice) |
| `tests/nvenc_hevc_warm.rs` (Windows / Linux) | `register()` answers the HEVC and the Main 10 questions on a thread of its own, before anyone asks; the answers are kept and `hdr_available(Hevc)` follows the Main 10 probe |
| `tests/nvenc_hevc_main10.rs` (Windows / Linux, NVIDIA with 10-bit HEVC) | HEVC Main 10 in process, PQ and HLG: a float picture (grey ramp to peak white, a moving box, saturated BT.2020 bars) is converted with `rgbf_to_yuv420_10`, encoded and decoded with our HEVC decoder (`Yuv16`, 10 bits, codes below 1024): worst 71.1 dB luma / 70.6 dB chroma PSNR on the 10-bit scale, 803 distinct luma levels on a ramp row, peak code 940 survives; IDR at 0 and 24; the VUI is BT.2020 / 16 or 18 / BT.2020 NCL / limited; the IDR samples carry two prefix SEI NAL units whose messages are byte-for-byte the expected 137 / 144 payloads (HLG: none; other pictures: none); Main and Main 10 refuse each other's pictures; hostile sizes, bitrates, signals, huge or empty SEI payloads, encoders dropped with SEI in flight |
| `tests/nvenc_hevc_hdr_export.rs` (Windows / Linux, NVIDIA with 10-bit HEVC) | HDR sequences through the real export job (PQ MP4, HLG MP4, PQ QuickTime): 24 frames, 1 session, 0 declined; ffprobe `hevc` / `Main 10` / `hvc1` / `yuv420p10le`, `bt2020` / `smpte2084` or `arib-std-b67` / `bt2020nc` / `tv`; PQ first-frame side data Mastering display (max 10000000/10000, min 1/10000) and Content light level, HLG none; `ffmpeg -xerror` clean; ffmpeg's yuv420p10le picture equals our decoder's, code for code; our importer reads PQ / HLG, BT.2020, 10-bit, and the `colr` / `mdcv` / `clli` boxes; luma PSNR against a ProRes HDR export of the same sequence 62.6 dB (PQ) / 61.8 dB (HLG); `settings.sdr` and SDR sequences stay Main 8-bit BT.709 with no HDR boxes; the factory rejects mismatched or wrong-length pictures and clamps NaN / infinity |
| `src/nvenc/abi_tests.rs` (Windows / Linux) | FFI structs' sizes, alignments, field offsets, constants, GUIDs and the bit-field masks of the `flags` words (H.264, HEVC, and the HEVC picture parameters with the SEI payload array) against a C compiler's view of NVIDIA's `nvEncodeAPI.h` (12.1) |
| `src/nvenc/{mod,hevc,export}.rs` unit tests (Windows) | HEVC NAL types, stripping and parameter-set splitting (including `IDR_N_LP`, whose header byte reads as a PPS in H.264, and one-byte or truncated NAL units); the `hvcC` built from real VPS / SPS / PPS (Main and Main 10: accepted as what was asked for, refused as the other, other depths refused), every truncation and bit flip of the SPS, wrong profile, size and NAL types; HEVC levels; P010 layout (shift, little-endian, interleaving, pitch in bytes, narrow pitches and short buffers); the 10-bit capability decline; the SEI messages |
| `crates/export/src/hdr_tests.rs` | `rgbf_to_yuv420_10`: exact codes for black, white, grey and the BT.2020 primaries and secondaries, NaN / infinity / out-of-range, odd sizes, wrong lengths and overflowing sizes; the HDR probe registry; which exports are HDR (HEVC SDR without a probe, HDR with one, `settings.sdr` forcing SDR); the SEI of the software H.264 encoder equals `ColorSignal::static_metadata` |
@ -344,7 +344,7 @@ Zero-copy upload of decoded pictures into wgpu textures (`CVPixelBuffer`s on mac
textures on Windows); B-frames in the VideoToolbox encoders and 10-bit / HDR HEVC (Main 10) in
VideoToolbox; VP9 and AV1 through VA-API (Linux), and VA-API encoding; field-coded H.264; 4:4:4 and 4:2:2 encoding with NVENC, HDR
H.264 with NVENC (HDR H.264 stays with the software encoder), HDR10 MaxCLL / MaxFALL measured from
the pictures (they are written as 0, unknown), AV1 with NVENC, H.265 with the other Windows vendors and on Linux, and encoders
the pictures (they are written as 0, unknown), AV1 with NVENC, H.265 with the other Windows and Linux vendors, and encoders
from other vendors on Windows (through Media Foundation); VP9 / AV1 4:4:4 and 12-bit on Windows.
### Linux NVENC sources and limits

View File

@ -8,7 +8,7 @@
//! Linux with neither) registration does nothing and reports [`Availability::Unavailable`]. It also registers a
//! hardware H.264 encoder factory (`filmcraft_export::register_encoder`) that only acts when an
//! export asks for it (`ExportSettings::hardware_encoding` = `Auto`), see [`hardware_encode`]; on
//! macOS and on Windows (NVENC, [`nvenc`]) it also makes the H.265 export format available, and on Windows
//! macOS and with NVENC ([`nvenc`], Windows and Linux) it also makes the H.265 export format available, and with NVENC
//! (Main 10) lets HDR sequences export as HDR H.265.
//!
//! Hardware decoding never makes a file undecodable:
@ -68,8 +68,6 @@ pub enum Availability {
/// harmless). Streams they do not take, and every stream while hardware decoding is Off, keep
/// using FilmCraft's own decoders.
pub fn register() -> Availability {
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
filmcraft_export::register_encoder(nvenc::export::factory);
#[cfg(target_os = "macos")]
{
filmcraft_codecs::register_video_decoder(videotoolbox_factory);
@ -81,23 +79,9 @@ pub fn register() -> Availability {
filmcraft_codecs::hw::set_hw_backend("VideoToolbox");
Availability::Available("VideoToolbox")
}
// Export ▸ H.265: NVENC is FilmCraft's only HEVC encoder on Windows, so the format is available
// when a small HEVC session opens. That takes about half a second, so it is asked on a thread of
// its own now rather than by the first draw of the format list.
#[cfg(target_os = "windows")]
{
filmcraft_export::register_format_probe(filmcraft_export::Format::Hevc, nvenc::hevc_available);
// HDR sequences export as HEVC Main 10 (PQ / HLG) only where NVENC has a 10-bit encoder; elsewhere
// (and on macOS, whose VideoToolbox path is 8-bit) they are tone-mapped to SDR, as before
filmcraft_export::register_hdr_probe(filmcraft_export::Format::Hevc, nvenc::hevc_hdr_available);
nvenc::warm_hevc_probe();
}
#[cfg(target_os = "windows")]
{
static ENCODERS: std::sync::Once = std::sync::Once::new();
// Export ▸ Hardware encoding (NVENC H.264): in front of the software encoder, taking an
// export only when asked for and when NVENC can do it
ENCODERS.call_once(|| filmcraft_export::register_encoder(nvenc::export::factory));
register_nvenc();
filmcraft_codecs::register_video_decoder(media_foundation_factory);
filmcraft_codecs::hw::set_hw_backend("Media Foundation");
Availability::Available("Media Foundation")
@ -130,6 +114,11 @@ pub fn register() -> Availability {
}
Err(why) => log::info!("no NVDEC hardware decoding: {why}"),
}
// After the VA-API probe, never beside it: libva-nvidia-driver calls `cuInit` from its
// library constructor (under the loader lock) while a first `cuInit` on another thread
// loads libraries under CUDA's own lock, and the two wait for each other forever.
#[cfg(target_pointer_width = "64")]
register_nvenc();
available
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
@ -138,6 +127,23 @@ pub fn register() -> Availability {
}
}
/// NVENC: the Export encoder factory, and what makes the H.265 format (and HDR H.265) available.
#[cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
fn register_nvenc() {
// Export ▸ H.265: NVENC is FilmCraft's only HEVC encoder on Windows and Linux, so the format is available
// when a small HEVC session opens. That takes about half a second, so it is asked on a thread of
// its own now rather than by the first draw of the format list.
filmcraft_export::register_format_probe(filmcraft_export::Format::Hevc, nvenc::hevc_available);
// HDR sequences export as HEVC Main 10 (PQ / HLG) only where NVENC has a 10-bit encoder; elsewhere
// (and on macOS, whose VideoToolbox path is 8-bit) they are tone-mapped to SDR, as before
filmcraft_export::register_hdr_probe(filmcraft_export::Format::Hevc, nvenc::hevc_hdr_available);
nvenc::warm_hevc_probe();
static ENCODERS: std::sync::Once = std::sync::Once::new();
// Export ▸ Hardware encoding (NVENC H.264): in front of the software encoder, taking an
// export only when asked for and when NVENC can do it
ENCODERS.call_once(|| filmcraft_export::register_encoder(nvenc::export::factory));
}
/// Whether [`register`] has put a hardware decoder factory in front of our decoders.
pub fn registered() -> bool {
#[cfg(target_os = "macos")]

View File

@ -211,8 +211,7 @@ fn config(format: Format, w: u32, h: u32, rate: FrameRate, s: &ExportSettings) -
/// The Export encoder factory (see the module documentation).
pub fn factory(format: Format, w: u32, h: u32, rate: FrameRate, s: &ExportSettings) -> Option<Result<Box<dyn VideoEncoder>>> {
if format == Format::Hevc {
// H.265 through NVENC is Windows-only for now; on Linux this backend takes H.264 only
return if cfg!(target_os = "windows") { hevc_factory(w, h, rate, s) } else { None };
return hevc_factory(w, h, rate, s);
}
factory_with(format, w, h, rate, s, |cfg| open(cfg, &Signal::default()))
}
@ -261,7 +260,7 @@ fn factory_with(
}
}
/// The H.265 side of [`factory`]: FilmCraft's only HEVC encoder on Windows, so the Hardware encoding toggle
/// The H.265 side of [`factory`]: FilmCraft's only HEVC encoder on Windows and Linux, so the Hardware encoding toggle
/// does not apply and a request NVENC cannot take is an error, not a fall-through.
fn hevc_factory(w: u32, h: u32, rate: FrameRate, s: &ExportSettings) -> Option<Result<Box<dyn VideoEncoder>>> {
let declined = |why: &str| {

View File

@ -1,5 +1,5 @@
//! NVIDIA NVENC hardware encoding: H.264 (Windows and 64-bit Linux) and H.265 (HEVC) Main / Main 10
//! (Windows).
//! NVIDIA NVENC hardware encoding: H.264 and H.265 (HEVC) Main / Main 10 (Windows and
//! 64-bit Linux).
//!
//! The encoder runs on the GPU's NVENC engine through the driver's `nvEncodeAPI64.dll` on Windows
//! (on a Direct3D 11 device) or `libnvidia-encode.so.1` on Linux (on the CUDA driver's primary

View File

@ -2,7 +2,7 @@
//! with our own HEVC decoder to pictures close to the source, keyframes and timestamps are right, the
//! samples carry no parameter sets and the `hvcC` describes the SPS it was built from. Skips
//! without an NVIDIA GPU / driver with HEVC encoding.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use filmcraft_bitstream::unescape_rbsp;
use filmcraft_hevc::params::Sps;

View File

@ -4,7 +4,7 @@
//! counters; and what NVENC declines (an error that says why, counted, never a crash). Its own test
//! binary: the encoder registry and the counters are process-wide. Skips without an NVIDIA GPU with
//! an HEVC encoder.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use std::path::Path;
use std::process::Command;

View File

@ -5,7 +5,7 @@
//! our importer reads back as PQ / HLG, 10-bit; whose pixels follow a ProRes HDR export of the same
//! sequence. SDR stays Main 8-bit BT.709, `settings.sdr` forces it. Its own test binary: the encoder
//! registry and the counters are process-wide. Skips (SKIPPED) only without a 10-bit HEVC encoder.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use std::path::Path;
use std::process::Command;

View File

@ -3,7 +3,7 @@
//! pictures that come out of our own HEVC decoder are 10-bit and close to them (smooth ramps keep far more
//! than 256 levels, bright code values survive), the VUI and the HDR10 SEI messages are right, and
//! hostile inputs are errors. Skips (with SKIPPED) only on a machine without a 10-bit HEVC encoder.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use filmcraft_bitstream::unescape_rbsp;
use filmcraft_export::{ColorSignal, rgbf_to_yuv420_10};

View File

@ -1,7 +1,7 @@
//! The HEVC availability probe of the NVENC backend: it opens one small encoder session, once. Its own
//! test binary so that "first call" means a fresh process (driver load, Direct3D 11 device, NVENC
//! session and encoder initialisation all included).
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use std::time::{Duration, Instant};

View File

@ -1,7 +1,7 @@
//! `register` asks the NVENC HEVC probe on a thread of its own, so the first draw of Export's format
//! list does not open an encoder session on the UI thread. Its own test binary: anything else asking
//! `hevc_available` first would make the wait meaningless.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use std::time::{Duration, Instant};

View File

@ -1,7 +1,7 @@
//! NVENC with packed RGBA input (Windows, NVIDIA GPU): the GPU's RGB → 4:2:0 conversion matches the
//! NVENC with packed RGBA input (Windows and Linux, NVIDIA GPU): the GPU's RGB → 4:2:0 conversion matches the
//! export's own BT.709 limited-range conversion (`filmcraft_export::rgba_to_yuv420_8`), for HEVC Main
//! and H.264. Skips without an NVIDIA GPU / driver.
#![cfg(target_os = "windows")]
#![cfg(any(target_os = "windows", all(target_os = "linux", target_pointer_width = "64")))]
use filmcraft_isobmff::{AvcConfig, SampleEntry};
use filmcraft_platform::nvenc::{Codec, Config, Nvenc, Packet, Profile, Signal, available, hevc_available};

View File

@ -16,7 +16,7 @@ contents). Percent is ready-for-real-work for that codec: depth, profiles, speed
| Codec | Premiere | Our decode | Our encode | Fidelity and tests | % | Est. |
|---|---|---|---|---|---|---|
| H.264 / AVC | decode all common profiles incl. High 10, 4:2:2 (XAVC S-I, AVC-Intra); encode 8/10-bit | own decoder, **8-bit 4:2:0 only** (Baseline / Main / High, CABAC / CAVLC); no High 10 / 4:2:2 / 4:4:4, interlaced field / MBAFF, lossless | own encoder (High / Main / Baseline, B-frames, CBR / VBR / 2-pass); 8-bit 4:2:0 only; hardware VideoToolbox / NVENC | decode bit-exact on 37+ conformance streams, 500–600 fps 1080p; encode ffprobe-checked, byte-identical across machines | 65% | 20–35 h |
| HEVC / H.265 | decode Main, Main 10, 4:2:2 10 (XAVC HS); encode 8/10-bit, HDR | own decoder Main / Main 10 / Main Still Picture, bit-exact on 41 fixtures, ~225 fps 1080p; **no 4:2:2 / RExt** | **hardware only** (VideoToolbox 8-bit; NVENC 8-bit and Main 10 HDR on Windows); no software encoder | NVENC round trip 48.9 dB (8-bit), 71.1 dB (10-bit); ffprobe | 60% | 30–60 h |
| HEVC / H.265 | decode Main, Main 10, 4:2:2 10 (XAVC HS); encode 8/10-bit, HDR | own decoder Main / Main 10 / Main Still Picture, bit-exact on 41 fixtures, ~225 fps 1080p; **no 4:2:2 / RExt** | **hardware only** (VideoToolbox 8-bit; NVENC 8-bit and Main 10 HDR on Windows and Linux); no software encoder | NVENC round trip 48.9 dB (8-bit), 71.1 dB (10-bit); ffprobe | 60% | 30–60 h |
| ProRes 422 family / 4444 / XQ | decode + encode all | own decode + encode, all six profiles, alpha | yes (export UI exposes 422 flavours; 4444 / XQ request #342) | ±1 LSB, 318 fps | 85% | 3–6 h |
| ProRes RAW | decode | none (#345) | — | — | 0% | 30–50 h (spec availability uncertain) |
| DNxHD / DNxHR | decode + encode, MXF / MOV | own decode (all SMPTE ST 2019-1 CIDs, 8/10/12-bit, 4:2:2 / 4:4:4, interlaced, alpha) and encode LB / SQ / HQ / HQX / 444 | yes, MOV + MXF | within IDCT precision of ffmpeg on 22 fixtures | 90% | 2–4 h |

View File

@ -137,8 +137,8 @@ Evidence for the cross-cutting rows:
- **Hardware ~45%.** Hardware decode exists on all three desktop OSs (VideoToolbox; Media
Foundation / D3D11 incl. VP9 and AV1; VA-API and NVDEC for H.264 / HEVC on Linux). Hardware
encode: VideoToolbox H.264 / HEVC 8-bit; NVENC H.264 (Windows, Linux) and HEVC incl. Main 10 HDR
(Windows). Missing against Premiere: Intel Quick Sync and AMD encoders, 10-bit HEVC on macOS,
encode: VideoToolbox H.264 / HEVC 8-bit; NVENC H.264 and HEVC incl. Main 10 HDR (Windows,
Linux). Missing against Premiere: Intel Quick Sync and AMD encoders, 10-bit HEVC on macOS,
ProRes hardware, zero-copy upload, ~48 of Premiere's accelerated effects, external video I/O,
control surfaces. Detail: [hardware-parity.md](hardware-parity.md).
- **Performance ~55%.** 4K H.264 / HEVC play with no dropped frames with hardware decoding (M4

View File

@ -77,7 +77,7 @@ are never linked or shipped ([AGENTS.md](../AGENTS.md) §2).
The NVENC tests (`crates/platform/tests/nvenc.rs` and `nvenc_export.rs` for H.264, `nvenc_rgba_input.rs` for
the GPU's RGB → 4:2:0 conversion, `nvenc_hevc.rs`,
`nvenc_hevc_export.rs`, `nvenc_hevc_probe.rs` and `nvenc_hevc_warm.rs` for H.265, `nvenc_hevc_main10.rs` and
`nvenc_hevc_hdr_export.rs` for Main 10 HDR; Windows only) skip, printing `SKIPPED`, without an NVIDIA GPU
`nvenc_hevc_hdr_export.rs` for Main 10 HDR; Windows and Linux) skip, printing `SKIPPED`, without an NVIDIA GPU
with NVENC (H.265: when the HEVC probe says there is no HEVC encoder; Main 10: when the Main 10 probe says
there is no 10-bit encoder; on a machine that has one, a configuration NVENC refuses fails the test). The FFI layout tests in
`crates/platform/src/nvenc/abi_tests.rs` were generated from a C program built with MSVC