mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
Keep the bounded GPU readback wait (the #1058 fix); leave out the VA-API encode probe
The probe only logged and always returned None, opened a VA display on every H.264 Auto export, and made VA-API decode fail to load where libva lacks the new symbol. A real VA-API encoder can come in its own PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
96959e5490
commit
0a3abdc3d0
@@ -131,14 +131,6 @@ let availability = filmcraft_platform::register(); // Available("VideoToolbox")
|
||||
log is bounded (600 samples / 256 MB); beyond it one error is returned and the next seek restarts
|
||||
in software. Streams our decoders cannot decode (HEVC 4:2:2) have no fallback: the error stands.
|
||||
|
||||
## Hardware H.264 encoding (Linux)
|
||||
|
||||
`register()` puts NVENC (NVIDIA) and a VA-API EncSlice probe (`vaapi::encode`) in front of the
|
||||
built-in encoder. AMD VCN and Intel QSV expose `VAEntrypointEncSlice`; the probe logs that and
|
||||
falls through (a slice encoder is not wired yet) so the software encoder runs instead of hanging
|
||||
on a missing `libnvidia-encode`. Export GPU readback waits at most eight seconds (`filmcraft-gpu`)
|
||||
so a wedged radv device fails the GPU path instead of blocking forever.
|
||||
|
||||
## Hardware H.264 encoding (macOS)
|
||||
|
||||
`videotoolbox_encode.rs` (FFI) wraps a VideoToolbox compression session with a hardware encoder
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
//! 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
|
||||
//! Linux that is NVENC ([`nvenc`]) after a VA-API EncSlice probe ([`vaapi::encode`]); on
|
||||
//! macOS and on Windows (NVENC, [`nvenc`]) it also makes the H.265 export format available, and on Windows
|
||||
//! (Main 10) lets HDR sequences export as HDR H.265.
|
||||
//!
|
||||
@@ -70,11 +69,7 @@ pub enum Availability {
|
||||
/// 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);
|
||||
// Tried first: AMD / Intel EncSlice before NVENC, which otherwise logs a missing NVIDIA library.
|
||||
filmcraft_export::register_encoder(vaapi::encode::factory);
|
||||
}
|
||||
filmcraft_export::register_encoder(nvenc::export::factory);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
filmcraft_codecs::register_video_decoder(videotoolbox_factory);
|
||||
|
||||
@@ -259,8 +259,6 @@ fn constants_match_the_header() {
|
||||
assert_eq!(VAProfileH264High as i64, 7, "VAProfileH264High");
|
||||
assert_eq!(VAProfileH264ConstrainedBaseline as i64, 13, "VAProfileH264ConstrainedBaseline");
|
||||
assert_eq!(VAEntrypointVLD as i64, 1, "VAEntrypointVLD");
|
||||
assert_eq!(VAEntrypointEncSlice as i64, 6, "VAEntrypointEncSlice");
|
||||
assert_eq!(VAEntrypointEncSliceLP as i64, 8, "VAEntrypointEncSliceLP");
|
||||
assert_eq!(VAConfigAttribRTFormat as i64, 0, "VAConfigAttribRTFormat");
|
||||
assert_eq!(VA_RT_FORMAT_YUV420 as i64, 1, "VA_RT_FORMAT_YUV420");
|
||||
assert_eq!(VA_PROGRESSIVE as i64, 1, "VA_PROGRESSIVE");
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//! VA-API as an Export encoder factory.
|
||||
//!
|
||||
//! Linux hardware H.264 encode on AMD and Intel is VA-API `EncSlice` (VCN / QSV), not NVENC.
|
||||
//! [`factory`] is registered in front of NVENC so those GPUs are probed first. A full slice encoder
|
||||
//! is not in this crate yet: when the driver exposes `EncSlice` we still return `None` so NVENC
|
||||
//! (NVIDIA) and then the software encoder run. The probe exists so logs name the real encode path
|
||||
//! instead of only "NVENC: no libnvidia-encode.so.1", and so a later encoder can take the same
|
||||
//! factory without changing `register()`.
|
||||
//!
|
||||
//! The factory never hangs: `vaQueryConfigEntrypoints` is a driver query, not a session.
|
||||
|
||||
use filmcraft_export::{ExportSettings, Format, HardwareEncoding, Result, VideoEncoder};
|
||||
use filmcraft_time::FrameRate;
|
||||
|
||||
/// The H.264 side of the VA-API 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 s.hardware_encoding != HardwareEncoding::Auto || format != Format::H264 {
|
||||
return None;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
match super::va::h264_encode_probe() {
|
||||
Ok(driver) => {
|
||||
log::info!("VA-API H.264 EncSlice present ({driver}); slice encode is not implemented, trying other factories");
|
||||
None
|
||||
}
|
||||
Err(why) => {
|
||||
log::info!("VA-API H.264 encode declined: {why}");
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn off_and_non_h264_never_probe() {
|
||||
let mut s = ExportSettings { hardware_encoding: HardwareEncoding::Off, ..Default::default() };
|
||||
assert!(factory(Format::H264, 640, 360, FrameRate::FPS_24, &s).is_none());
|
||||
s.hardware_encoding = HardwareEncoding::Auto;
|
||||
assert!(factory(Format::ProRes, 640, 360, FrameRate::FPS_24, &s).is_none());
|
||||
assert!(factory(Format::Hevc, 640, 360, FrameRate::FPS_24, &s).is_none());
|
||||
}
|
||||
}
|
||||
@@ -71,10 +71,6 @@ pub const VAProfileH264ConstrainedBaseline: VAProfile = 13;
|
||||
pub const VAProfileHEVCMain: VAProfile = 17;
|
||||
pub const VAProfileHEVCMain10: VAProfile = 18;
|
||||
pub const VAEntrypointVLD: VAEntrypoint = 1;
|
||||
/// H.264 / HEVC slice encode (AMD VCN, Intel QSV, and other Mesa encode paths).
|
||||
pub const VAEntrypointEncSlice: VAEntrypoint = 6;
|
||||
/// Low-power slice encode, when the driver exposes it separately from [`VAEntrypointEncSlice`].
|
||||
pub const VAEntrypointEncSliceLP: VAEntrypoint = 8;
|
||||
|
||||
pub const VAConfigAttribRTFormat: VAConfigAttribType = 0;
|
||||
pub const VA_RT_FORMAT_YUV420: c_uint = 0x0000_0001;
|
||||
@@ -451,8 +447,6 @@ pub type vaErrorStr = unsafe extern "C" fn(status: VAStatus) -> *const c_char;
|
||||
pub type vaQueryVendorString = unsafe extern "C" fn(dpy: VADisplay) -> *const c_char;
|
||||
pub type vaSetInfoCallback = unsafe extern "C" fn(dpy: VADisplay, callback: VAMessageCallback, user_context: *mut c_void) -> VAMessageCallback;
|
||||
pub type vaSetErrorCallback = unsafe extern "C" fn(dpy: VADisplay, callback: VAMessageCallback, user_context: *mut c_void) -> VAMessageCallback;
|
||||
pub type vaQueryConfigEntrypoints =
|
||||
unsafe extern "C" fn(dpy: VADisplay, profile: VAProfile, entrypoint_list: *mut VAEntrypoint, num_entrypoints: *mut c_int) -> VAStatus;
|
||||
pub type vaGetConfigAttributes =
|
||||
unsafe extern "C" fn(dpy: VADisplay, profile: VAProfile, entrypoint: VAEntrypoint, attrib_list: *mut VAConfigAttrib, num_attribs: c_int) -> VAStatus;
|
||||
pub type vaCreateConfig = unsafe extern "C" fn(
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
//!
|
||||
//! The FFI is in [`ffi`] (declarations) and `va` (calls); the rest of this module is safe code.
|
||||
|
||||
pub mod encode;
|
||||
pub mod ffi;
|
||||
pub mod h264;
|
||||
pub mod hevc;
|
||||
|
||||
@@ -30,7 +30,6 @@ struct Api {
|
||||
vendor_string: ffi::vaQueryVendorString,
|
||||
set_info_callback: ffi::vaSetInfoCallback,
|
||||
set_error_callback: ffi::vaSetErrorCallback,
|
||||
query_config_entrypoints: ffi::vaQueryConfigEntrypoints,
|
||||
get_config_attributes: ffi::vaGetConfigAttributes,
|
||||
create_config: ffi::vaCreateConfig,
|
||||
destroy_config: ffi::vaDestroyConfig,
|
||||
@@ -81,7 +80,6 @@ fn load() -> Result<Api, String> {
|
||||
vendor_string: sym!(va, vaQueryVendorString),
|
||||
set_info_callback: sym!(va, vaSetInfoCallback),
|
||||
set_error_callback: sym!(va, vaSetErrorCallback),
|
||||
query_config_entrypoints: sym!(va, vaQueryConfigEntrypoints),
|
||||
get_config_attributes: sym!(va, vaGetConfigAttributes),
|
||||
create_config: sym!(va, vaCreateConfig),
|
||||
destroy_config: sym!(va, vaDestroyConfig),
|
||||
@@ -585,29 +583,3 @@ pub fn probe() -> &'static Result<String, String> {
|
||||
// the display opened to answer is the one sessions share
|
||||
PROBE.get_or_init(|| shared().map(|d| d.vendor().to_string()))
|
||||
}
|
||||
|
||||
/// Whether any H.264 profile on this display exposes slice encode (`EncSlice` or `EncSliceLP`):
|
||||
/// AMD VCN and Intel QSV do; NVIDIA's proprietary stack does not. The encoder factory uses this to
|
||||
/// decline quickly instead of opening NVENC on a machine that has no NVIDIA encode library.
|
||||
pub fn h264_encode_probe() -> Result<String, String> {
|
||||
let display = shared()?;
|
||||
let api = display.api;
|
||||
let dpy = display.dpy;
|
||||
const PROFILES: [ffi::VAProfile; 3] = [ffi::VAProfileH264ConstrainedBaseline, ffi::VAProfileH264Main, ffi::VAProfileH264High];
|
||||
let mut why = "no H.264 EncSlice / EncSliceLP entrypoint".to_string();
|
||||
for profile in PROFILES {
|
||||
let mut list = [0 as ffi::VAEntrypoint; 32];
|
||||
let mut n: c_int = c_int::try_from(list.len()).unwrap_or(32);
|
||||
// SAFETY: `dpy` is an initialised display; `list` has room for `n` entrypoints and `n` is a live output.
|
||||
let st = unsafe { (api.query_config_entrypoints)(dpy, profile, list.as_mut_ptr(), &mut n) };
|
||||
if let Err(e) = display.check(st, "vaQueryConfigEntrypoints") {
|
||||
why = e;
|
||||
continue;
|
||||
}
|
||||
let n = usize::try_from(n.max(0)).unwrap_or(0).min(list.len());
|
||||
if list[..n].iter().any(|&e| e == ffi::VAEntrypointEncSlice || e == ffi::VAEntrypointEncSliceLP) {
|
||||
return Ok(display.vendor().to_string());
|
||||
}
|
||||
}
|
||||
Err(why)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user