feat(encode/amf): accept AMF runtime >=1.4.34 (graceful degrade) + log loaded amfrt64.dll identity
ci / rust (push) Failing after 47s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 56s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / deploy-docs (push) Successful in 11s
ci / bench (push) Successful in 5m8s
android / android (push) Successful in 14m4s
arch / build-publish (push) Successful in 12m44s
deb / build-publish (push) Successful in 13m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m19s
windows-host / package (push) Successful in 9m36s
apple / swift (push) Successful in 4m40s
apple / screenshots (push) Successful in 19m47s
ci / rust (push) Failing after 47s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 56s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / deploy-docs (push) Successful in 11s
ci / bench (push) Successful in 5m8s
android / android (push) Successful in 14m4s
arch / build-publish (push) Successful in 12m44s
deb / build-publish (push) Successful in 13m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m19s
windows-host / package (push) Successful in 9m36s
apple / swift (push) Successful in 4m40s
apple / screenshots (push) Successful in 19m47s
The Windows AMF encoder hard-rejected any runtime <1.4.36 — a Jan-2025 (Adrenalin 25.1.1) driver floor. Every AMD host on an older driver failed the session with "update the AMD driver" after 8 retries, notably Boot Camp Macs whose bundled amfrt64.dll lags far behind. Split the single pin: - AMF_MIN_VERSION (1.4.34): the ABI floor accepted at load. Every vtable slot the FFI mirrors is a base-interface slot stable since well before 1.4.34; the 1.4.35/1.4.36-only features are string-keyed encoder properties already applied via set_prop(required=false), which log-and-continue — so an older driver degrades those features individually instead of failing. - AMF_HEADER_VERSION (1.4.36): the header the mirror targets, now passed to AMFInit capped at min(header, runtime) so claiming a version newer than the runtime can't make AMFInit reject an otherwise-usable older driver. No functionality removed: a >=1.4.36 runtime behaves exactly as before. Also logs, once per process, the AMF runtime version AND the loaded amfrt64.dll's full path + file-version resource (via GetModuleFileNameW + VerQueryValueW). This surfaces the Boot Camp failure mode where the display driver reads 25.x but the System32 amfrt64.dll is a stale build reporting an old AMF version; the too-old decline now names the DLL path/build and points at reboot + DDU reinstall. Not compile-verified: amf.rs is Windows-only and this Linux box can't cross-build it (a dependency's C build fails for the msvc target). Needs cargo check/clippy on the Windows build box / CI. rustfmt-clean; the windows-crate FFI signatures were verified against the on-disk 0.62.2 bindings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,11 @@
|
|||||||
//! Drives the AMF runtime through its **C vtable ABI**: the GPUOpen public headers define
|
//! Drives the AMF runtime through its **C vtable ABI**: the GPUOpen public headers define
|
||||||
//! C-compatible vtable structs for every interface, and FFmpeg's `amfenc.c` (plain C) drives AMF
|
//! C-compatible vtable structs for every interface, and FFmpeg's `amfenc.c` (plain C) drives AMF
|
||||||
//! exclusively through them, so that ABI — not the C++ classes — is the stable, supported
|
//! exclusively through them, so that ABI — not the C++ classes — is the stable, supported
|
||||||
//! surface. The FFI below mirrors ONLY the interfaces/slots we call, pinned to header version
|
//! surface. The FFI below mirrors ONLY the interfaces/slots we call, written against header
|
||||||
//! **v1.4.36** (`AMF_FULL_VERSION` 1.4.36.0, gated at load via `AMFQueryVersion`). The runtime is
|
//! version **v1.4.36** (`AMF_FULL_VERSION` 1.4.36.0). At load the runtime is accepted down to a
|
||||||
|
//! stable-ABI floor of **v1.4.34** (the `AMFQueryVersion` gate); the 1.4.35/1.4.36-only encoder
|
||||||
|
//! features are string-keyed properties that degrade individually on older drivers, not vtable
|
||||||
|
//! changes (see [`sys::AMF_MIN_VERSION`]). The runtime is
|
||||||
//! loaded at runtime from the driver-installed `amfrt64.dll` — exactly as `nvenc.rs` loads
|
//! loaded at runtime from the driver-installed `amfrt64.dll` — exactly as `nvenc.rs` loads
|
||||||
//! `nvEncodeAPI64.dll` — so this compiles unconditionally on Windows (**no build feature, no new
|
//! `nvEncodeAPI64.dll` — so this compiles unconditionally on Windows (**no build feature, no new
|
||||||
//! dependency**). Since Phase 3 (design §7) this is the sole AMD dispatch: a box without a
|
//! dependency**). Since Phase 3 (design §7) this is the sole AMD dispatch: a box without a
|
||||||
@@ -50,6 +53,7 @@ use std::collections::VecDeque;
|
|||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use windows::core::{w, Interface, PCWSTR};
|
use windows::core::{w, Interface, PCWSTR};
|
||||||
|
use windows::Win32::Foundation::HMODULE;
|
||||||
use windows::Win32::Graphics::Direct3D11::{
|
use windows::Win32::Graphics::Direct3D11::{
|
||||||
ID3D11Device, ID3D11DeviceContext, ID3D11Resource, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET,
|
ID3D11Device, ID3D11DeviceContext, ID3D11Resource, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET,
|
||||||
D3D11_BIND_SHADER_RESOURCE, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
|
D3D11_BIND_SHADER_RESOURCE, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
|
||||||
@@ -57,9 +61,15 @@ use windows::Win32::Graphics::Direct3D11::{
|
|||||||
use windows::Win32::Graphics::Dxgi::Common::{
|
use windows::Win32::Graphics::Dxgi::Common::{
|
||||||
DXGI_FORMAT_NV12, DXGI_FORMAT_P010, DXGI_SAMPLE_DESC,
|
DXGI_FORMAT_NV12, DXGI_FORMAT_P010, DXGI_SAMPLE_DESC,
|
||||||
};
|
};
|
||||||
|
use windows::Win32::Storage::FileSystem::{
|
||||||
|
GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW, VS_FIXEDFILEINFO,
|
||||||
|
};
|
||||||
|
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
// Mirrored AMF C ABI (pinned to GPUOpen header release v1.4.36 — amf/public/include).
|
// Mirrored AMF C ABI (written against GPUOpen header release v1.4.36 — amf/public/include; every
|
||||||
|
// slot below is a base-interface slot whose layout is stable since <= v1.4.34, the loader's
|
||||||
|
// accepted ABI floor, so the mirror is valid on every runtime the loader admits).
|
||||||
//
|
//
|
||||||
// Layout rules this mirror relies on: every AMF interface is a struct whose sole member is a
|
// Layout rules this mirror relies on: every AMF interface is a struct whose sole member is a
|
||||||
// pointer to a C vtable; derived interfaces PREPEND their base's slots in order (AMFInterface →
|
// pointer to a C vtable; derived interfaces PREPEND their base's slots in order (AMFInterface →
|
||||||
@@ -118,11 +128,24 @@ mod sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pinned header version this FFI mirrors: `AMF_FULL_VERSION` for 1.4.36.0
|
/// The AMF header release this FFI mirror was written against: `AMF_FULL_VERSION` for 1.4.36.0
|
||||||
/// (core/Version.h `AMF_MAKE_FULL_VERSION`). The loader requires the runtime to report at
|
/// (core/Version.h `AMF_MAKE_FULL_VERSION`). This is the version claimed to `AMFInit` — but
|
||||||
/// least this via `AMFQueryVersion`, guaranteeing every vtable slot mirrored below exists at
|
/// capped at the runtime's own reported version (see `load_factory`), so an older-but-accepted
|
||||||
/// the mirrored offset.
|
/// runtime is asked only for the ABI it actually provides.
|
||||||
pub const AMF_PINNED_VERSION: u64 = (1u64 << 48) | (4u64 << 32) | (36u64 << 16);
|
pub const AMF_HEADER_VERSION: u64 = (1u64 << 48) | (4u64 << 32) | (36u64 << 16);
|
||||||
|
|
||||||
|
/// The oldest AMF runtime the loader accepts (`AMF_FULL_VERSION` 1.4.34.0 — AMD Adrenalin
|
||||||
|
/// 24.6.1). This is an **ABI floor, not a feature floor**: every vtable slot mirrored in this
|
||||||
|
/// module belongs to a base interface (`AMFFactory`/`AMFContext`/`AMFComponent`/`AMFData`/
|
||||||
|
/// `AMFBuffer`) whose layout has been stable — append-only, no mid-vtable insertions — since
|
||||||
|
/// well before 1.4.34, so a 1.4.34 runtime is guaranteed to expose every mirrored slot at its
|
||||||
|
/// mirrored offset. Everything 1.4.35/1.4.36 added that this path can touch (new HQ presets,
|
||||||
|
/// AV1 B-frame / picture management) is a *string-keyed encoder property*, applied through
|
||||||
|
/// [`set_prop`] with `required=false` — a runtime that lacks it rejects the property (logged)
|
||||||
|
/// and the feature degrades, rather than shifting any vtable offset. Below this floor the
|
||||||
|
/// mirror is not guaranteed to match, so the loader declines cleanly (an old-driver decline,
|
||||||
|
/// never UB).
|
||||||
|
pub const AMF_MIN_VERSION: u64 = (1u64 << 48) | (4u64 << 32) | (34u64 << 16);
|
||||||
|
|
||||||
/// `AMF_SURFACE_FORMAT` (core/Surface.h).
|
/// `AMF_SURFACE_FORMAT` (core/Surface.h).
|
||||||
pub const AMF_SURFACE_NV12: i32 = 1;
|
pub const AMF_SURFACE_NV12: i32 = 1;
|
||||||
@@ -474,6 +497,76 @@ fn amf_ok(r: sys::AmfResult, what: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format an `AMF_FULL_VERSION` u64 as `major.minor.patch` (the build field is dropped — every
|
||||||
|
/// version comparison and log line in this module ignores it).
|
||||||
|
fn amf_version_str(v: u64) -> String {
|
||||||
|
format!(
|
||||||
|
"{}.{}.{}",
|
||||||
|
(v >> 48) & 0xffff,
|
||||||
|
(v >> 32) & 0xffff,
|
||||||
|
(v >> 16) & 0xffff
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Best-effort on-disk identity of the loaded `amfrt64.dll`: `(full path, file-version resource)`.
|
||||||
|
/// The file-version is the driver build baked into the DLL (e.g. `31.0.24033.1003`), which — unlike
|
||||||
|
/// the AMF runtime version — is directly comparable to the display-driver version. This pins down
|
||||||
|
/// the Boot Camp failure mode: the display driver can report 25.x while the `amfrt64.dll` actually
|
||||||
|
/// loaded (System32, via the SYSTEM32-only search) is a stale build whose AMF + file versions lag
|
||||||
|
/// it. Diagnostics only — any failure yields `None` and never affects the load.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `module` must be a live module handle owned by the caller (here, the never-unloaded
|
||||||
|
/// `amfrt64.dll` from `LoadLibraryExW`).
|
||||||
|
unsafe fn loaded_dll_identity(module: HMODULE) -> (Option<String>, Option<String>) {
|
||||||
|
let mut buf = [0u16; 512];
|
||||||
|
let n = GetModuleFileNameW(Some(module), &mut buf) as usize;
|
||||||
|
// n == 0 → failed; n >= len → path truncated (no guaranteed NUL) — bail either way. Otherwise
|
||||||
|
// `GetModuleFileNameW` NUL-terminates at `buf[n]`, so `buf` is a valid PCWSTR for the query.
|
||||||
|
if n == 0 || n >= buf.len() {
|
||||||
|
return (None, None);
|
||||||
|
}
|
||||||
|
let path = String::from_utf16_lossy(&buf[..n]);
|
||||||
|
(Some(path), dll_file_version(PCWSTR(buf.as_ptr())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a DLL's `\`-root `VS_FIXEDFILEINFO` file version as `a.b.c.d`. `None` if the file has no
|
||||||
|
/// version resource or any step fails (diagnostics only).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `path` must be a valid NUL-terminated wide string pointing at a readable file path.
|
||||||
|
unsafe fn dll_file_version(path: PCWSTR) -> Option<String> {
|
||||||
|
let size = GetFileVersionInfoSizeW(path, None);
|
||||||
|
if size == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut block = vec![0u8; size as usize];
|
||||||
|
GetFileVersionInfoW(path, None, size, block.as_mut_ptr() as *mut c_void).ok()?;
|
||||||
|
let mut value: *mut c_void = ptr::null_mut();
|
||||||
|
let mut len: u32 = 0;
|
||||||
|
let ok = VerQueryValueW(
|
||||||
|
block.as_ptr() as *const c_void,
|
||||||
|
w!("\\"),
|
||||||
|
&mut value,
|
||||||
|
&mut len,
|
||||||
|
);
|
||||||
|
if !ok.as_bool() || value.is_null() || (len as usize) < std::mem::size_of::<VS_FIXEDFILEINFO>()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: on success `VerQueryValueW` points `value` at a `VS_FIXEDFILEINFO` living inside
|
||||||
|
// `block` and valid for `len` bytes (checked >= its size); `block` outlives this read.
|
||||||
|
let ffi = &*(value as *const VS_FIXEDFILEINFO);
|
||||||
|
let (ms, ls) = (ffi.dwFileVersionMS, ffi.dwFileVersionLS);
|
||||||
|
Some(format!(
|
||||||
|
"{}.{}.{}.{}",
|
||||||
|
ms >> 16,
|
||||||
|
ms & 0xffff,
|
||||||
|
ls >> 16,
|
||||||
|
ls & 0xffff
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
// Runtime loader (the analogue of nvenc.rs `load_api`): resolve amfrt64.dll's two exports once
|
// Runtime loader (the analogue of nvenc.rs `load_api`): resolve amfrt64.dll's two exports once
|
||||||
// per process, gate on the pinned header version, and keep the factory singleton forever.
|
// per process, gate on the pinned header version, and keep the factory singleton forever.
|
||||||
@@ -493,9 +586,9 @@ unsafe impl Send for AmfLib {}
|
|||||||
unsafe impl Sync for AmfLib {}
|
unsafe impl Sync for AmfLib {}
|
||||||
|
|
||||||
/// Resolve the AMF runtime once per process. `Err` = AMF genuinely unavailable here (no AMD
|
/// Resolve the AMF runtime once per process. `Err` = AMF genuinely unavailable here (no AMD
|
||||||
/// driver / `amfrt64.dll`, or a runtime older than the pinned v1.4.36 headers) — callers fail
|
/// driver / `amfrt64.dll`, or a runtime older than the minimum-supported v1.4.34 — see
|
||||||
/// their open cleanly with an "update the AMD driver" message (the session then fails; since
|
/// [`sys::AMF_MIN_VERSION`]) — callers fail their open cleanly with an "update the AMD driver"
|
||||||
/// Phase 3 there is no libavcodec AMF fallback).
|
/// message (the session then fails; since Phase 3 there is no libavcodec AMF fallback).
|
||||||
fn try_factory() -> std::result::Result<&'static AmfLib, &'static str> {
|
fn try_factory() -> std::result::Result<&'static AmfLib, &'static str> {
|
||||||
static LIB: std::sync::OnceLock<std::result::Result<AmfLib, String>> =
|
static LIB: std::sync::OnceLock<std::result::Result<AmfLib, String>> =
|
||||||
std::sync::OnceLock::new();
|
std::sync::OnceLock::new();
|
||||||
@@ -520,10 +613,11 @@ fn load_factory() -> std::result::Result<AmfLib, String> {
|
|||||||
// System32-only search path keeps a planted DLL out of the SYSTEM-service process (same
|
// System32-only search path keeps a planted DLL out of the SYSTEM-service process (same
|
||||||
// hardening as the NVENC loader). The two transmutes cast the resolved exports to their
|
// hardening as the NVENC loader). The two transmutes cast the resolved exports to their
|
||||||
// documented prototypes (core/Factory.h `AMFQueryVersion_Fn`/`AMFInit_Fn`).
|
// documented prototypes (core/Factory.h `AMFQueryVersion_Fn`/`AMFInit_Fn`).
|
||||||
// `AMFQueryVersion` writes one u64 through a live pointer; `AMFInit` is passed the pinned
|
// `AMFQueryVersion` writes one u64 through a live pointer; `AMFInit` is passed the header
|
||||||
// header version and fills `factory` with the process-global singleton only on AMF_OK
|
// version capped at the runtime's own (never newer than what the runtime provides) and fills
|
||||||
// (null-checked after). The module is never freed, so the factory and both entry points stay
|
// `factory` with the process-global singleton only on AMF_OK (null-checked after). The module
|
||||||
// valid for the process lifetime.
|
// is never freed, so the factory and both entry points stay valid for the process lifetime.
|
||||||
|
// `loaded_dll_identity` only reads that module's own path + version resource (diagnostics).
|
||||||
unsafe {
|
unsafe {
|
||||||
let module = LoadLibraryExW(w!("amfrt64.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32)
|
let module = LoadLibraryExW(w!("amfrt64.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -540,33 +634,75 @@ fn load_factory() -> std::result::Result<AmfLib, String> {
|
|||||||
if r != sys::AMF_OK {
|
if r != sys::AMF_OK {
|
||||||
return Err(format!("AMFQueryVersion failed: {} ({r})", result_name(r)));
|
return Err(format!("AMFQueryVersion failed: {} ({r})", result_name(r)));
|
||||||
}
|
}
|
||||||
// The vtable layouts mirrored above are the pinned header's; an older runtime may lack
|
// On-disk identity of the DLL we actually loaded (System32's amfrt64.dll, via the
|
||||||
// trailing slots (or predate an insertion), so require at least the pinned version — an
|
// SYSTEM32-only search above) — the Boot Camp diagnostic: the display driver can read 25.x
|
||||||
// old driver is a clean decline (clear session error), not UB.
|
// while THIS file is a stale build whose AMF + file versions lag it, so an "update the
|
||||||
if version < sys::AMF_PINNED_VERSION {
|
// driver" decline is confusing (they did — this DLL just didn't follow).
|
||||||
|
let (dll_path, dll_file_ver) = loaded_dll_identity(module);
|
||||||
|
let dll_desc = format!(
|
||||||
|
"{}{}",
|
||||||
|
dll_path.as_deref().unwrap_or("amfrt64.dll"),
|
||||||
|
dll_file_ver
|
||||||
|
.as_deref()
|
||||||
|
.map(|v| format!(" (file version {v})"))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
// Accept any runtime at or above the ABI floor (AMF_MIN_VERSION): every vtable slot this
|
||||||
|
// module mirrors predates it, so its layout is guaranteed; 1.4.35/1.4.36-only encoder
|
||||||
|
// features are string-keyed properties that degrade via `set_prop(required=false)`, not
|
||||||
|
// vtable changes. Below the floor the mirror is not guaranteed — decline cleanly (a clear
|
||||||
|
// old-driver session error, never UB).
|
||||||
|
if version < sys::AMF_MIN_VERSION {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"AMF runtime {}.{}.{} is older than the host's pinned headers 1.4.36 — update \
|
"AMF runtime {amf} (loaded from {dll_desc}) is older than the minimum supported \
|
||||||
the AMD driver",
|
1.4.34 — update the AMD driver (Adrenalin 24.6.1+; 25.1.1+ for the \
|
||||||
(version >> 48) & 0xffff,
|
fully-validated feature set). If the display driver already reports a newer \
|
||||||
(version >> 32) & 0xffff,
|
version, this amfrt64.dll did not update — reboot, then DDU + reinstall so \
|
||||||
(version >> 16) & 0xffff,
|
System32's copy is refreshed.",
|
||||||
|
amf = amf_version_str(version),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// Claim no more than the runtime provides: passing a version NEWER than the runtime can
|
||||||
|
// make AMFInit reject an otherwise-usable older driver, and this path only ever calls ABI
|
||||||
|
// present at/below the runtime's version. On a >=1.4.36 runtime this is a no-op (== header).
|
||||||
|
let init_version = sys::AMF_HEADER_VERSION.min(version);
|
||||||
let mut factory: *mut sys::AmfFactory = ptr::null_mut();
|
let mut factory: *mut sys::AmfFactory = ptr::null_mut();
|
||||||
let r = init(sys::AMF_PINNED_VERSION, &mut factory);
|
let r = init(init_version, &mut factory);
|
||||||
if r != sys::AMF_OK {
|
if r != sys::AMF_OK {
|
||||||
return Err(format!("AMFInit failed: {} ({r})", result_name(r)));
|
return Err(format!("AMFInit failed: {} ({r})", result_name(r)));
|
||||||
}
|
}
|
||||||
if factory.is_null() {
|
if factory.is_null() {
|
||||||
return Err("AMFInit returned a null factory".into());
|
return Err("AMFInit returned a null factory".into());
|
||||||
}
|
}
|
||||||
|
// Visible once per process (this runs inside `try_factory`'s OnceLock init). Both the AMF
|
||||||
|
// runtime version AND the loaded DLL's path + file version are logged, so a field report
|
||||||
|
// shows "display driver says 25.x but amfrt64.dll is an old build" at a glance.
|
||||||
|
if version >= sys::AMF_HEADER_VERSION {
|
||||||
|
tracing::info!(
|
||||||
|
amf_version = %amf_version_str(version),
|
||||||
|
dll = %dll_desc,
|
||||||
|
"AMF runtime loaded (meets the validated 1.4.36 baseline)"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
amf_version = %amf_version_str(version),
|
||||||
|
dll = %dll_desc,
|
||||||
|
"AMF runtime is older than the validated 1.4.36 baseline — accepted (the core \
|
||||||
|
encode ABI is stable), but advanced features (LTR / intra-refresh recovery, AV1 \
|
||||||
|
coded-size alignment, in-band HDR metadata) validated on 1.4.36 may be \
|
||||||
|
unavailable on this driver and will degrade individually (see the per-property \
|
||||||
|
logs below). Update to AMD Adrenalin 25.1.1+ for the fully-validated path."
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(AmfLib { factory, version })
|
Ok(AmfLib { factory, version })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
// Per-codec property tables (names verified against the pinned v1.4.36 headers —
|
// Per-codec property tables (names verified against the v1.4.36 headers —
|
||||||
// components/VideoEncoderVCE.h, VideoEncoderHEVC.h and VideoEncoderAV1.h; the enum VALUES differ
|
// components/VideoEncoderVCE.h, VideoEncoderHEVC.h and VideoEncoderAV1.h; a name a pre-1.4.36
|
||||||
|
// runtime doesn't recognise is applied through `set_prop(required=false)`, which logs and
|
||||||
|
// continues, so an older driver degrades that one feature rather than failing. The enum VALUES differ
|
||||||
// between the codecs, e.g. CBR is 1 on AVC but 3 on HEVC/AV1, SPEED is 1 vs 10 vs 100, and AV1
|
// between the codecs, e.g. CBR is 1 on AVC but 3 on HEVC/AV1, SPEED is 1 vs 10 vs 100, and AV1
|
||||||
// swaps the ULTRA_LOW_LATENCY/LOW_LATENCY usage values relative to AVC/HEVC).
|
// swaps the ULTRA_LOW_LATENCY/LOW_LATENCY usage values relative to AVC/HEVC).
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
@@ -1118,15 +1254,12 @@ impl AmfEncoder {
|
|||||||
bit_depth: u8,
|
bit_depth: u8,
|
||||||
chroma: ChromaFormat,
|
chroma: ChromaFormat,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
// The once-per-process runtime version (and the older-than-baseline warning) is logged in
|
||||||
|
// `load_factory`; this per-session line ties an individual encoder open to that version.
|
||||||
let lib = try_factory().map_err(|e| anyhow!("native AMF unavailable: {e}"))?;
|
let lib = try_factory().map_err(|e| anyhow!("native AMF unavailable: {e}"))?;
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
version = %format!(
|
version = %amf_version_str(lib.version),
|
||||||
"{}.{}.{}",
|
"opening AMF encoder"
|
||||||
(lib.version >> 48) & 0xffff,
|
|
||||||
(lib.version >> 32) & 0xffff,
|
|
||||||
(lib.version >> 16) & 0xffff
|
|
||||||
),
|
|
||||||
"AMF runtime loaded"
|
|
||||||
);
|
);
|
||||||
let props = codec_props(codec);
|
let props = codec_props(codec);
|
||||||
// AV1 is RDNA3+ — probe at open (never assume), so a pre-RDNA3 box fails HERE with a clear
|
// AV1 is RDNA3+ — probe at open (never assume), so a pre-RDNA3 box fails HERE with a clear
|
||||||
@@ -3062,7 +3195,7 @@ mod tests {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
assert!(lib.version >= sys::AMF_PINNED_VERSION);
|
assert!(lib.version >= sys::AMF_MIN_VERSION);
|
||||||
// SAFETY: same contracts as `ensure_inner`, minus the external device: `CreateContext`
|
// SAFETY: same contracts as `ensure_inner`, minus the external device: `CreateContext`
|
||||||
// fills `ctx` only on AMF_OK; `InitDX11(null)` asks AMF to create its own D3D11 device
|
// fills `ctx` only on AMF_OK; `InitDX11(null)` asks AMF to create its own D3D11 device
|
||||||
// (may fail on exotic boxes — treated as a skip); `CreateComponent` likewise. Guards
|
// (may fail on exotic boxes — treated as a skip); `CreateComponent` likewise. Guards
|
||||||
|
|||||||
Reference in New Issue
Block a user