Skip to main content

pixelflux/encoders/vaapi/
mod.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! VA-API hardware sessions on a DRM render node for every video codec, driven through libva
8//! directly.
9//!
10//! The session owns what a hardware encoder needs owned: the surfaces, the parameter buffers,
11//! and, for the codecs that take them, the packed headers, so it also owns each picture's
12//! reference lists. A frame a client lost is left out of the predictions the way NVENC and
13//! libx264 allow (`encoders/reference.rs`): H.264 and HEVC keep the decoded picture buffer
14//! the level admits and name the newest surviving frame in the slice header, VP9 and AV1
15//! address their eight buffer slots, and VP8 steers its three buffers. A driver that writes its
16//! own slice headers predicts from the previous frame alone, so such a session declares a
17//! one-frame buffer.
18//!
19//! Pixels reach the codec on a VA surface -- a Wayland dmabuf imported in place, or a packed
20//! host frame uploaded -- and the video processor converts to the surface format on the GPU,
21//! so no colorspace conversion happens on the CPU. Chroma follows `video_fullcolor` where the
22//! codec carries 4:4:4 (HEVC, and VP9 as profile 1), and the sample depth `video_bit_depth`
23//! where the driver encodes the codec's 10-bit profile (HEVC Main 10 and Main 4:4:4 10, VP9
24//! profiles 2 and 3, AV1), a session staying at 8 bits where it does not. A 4:4:4 session
25//! tries the surface formats
26//! the driver allocates and its video processor renders, read through a `VAProfileNone`
27//! configuration, until one survives the surface pool, the convert, and the codec open. Every
28//! session converts with the BT.709 matrix the sRGB source's own primaries and transfer belong
29//! to, at limited range, and declares it; VP8 is held to BT.601, the only matrix its bitstream
30//! can name. libva itself is loaded at run time, so the host's copy serves the driver it was
31//! built for, and a test can put its own functions behind the same table.
32//!
33//! The constant quantizer runs uncapped where a caller asks for a capped one
34//! (`RateSettings::capped`): the rate-control mode is fixed with the session's configuration
35//! (`VAConfigAttribRateControl`), so a constant-quantizer session takes no peak, nor goes over
36//! to a constant rate while one binds, as NVENC's does, short of a new configuration and the key
37//! frame it opens with.
38
39mod av1;
40mod h264;
41mod h265;
42#[cfg(test)]
43pub(crate) mod mock;
44#[cfg(test)]
45mod session_tests;
46mod vp8;
47mod vp9;
48
49use std::collections::HashMap;
50use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
51use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
52use std::ptr;
53use std::sync::{Arc, OnceLock};
54
55use smithay::backend::allocator::{Buffer, dmabuf::Dmabuf};
56use va_sys::*;
57
58use super::codec::{
59    Codec, Hardware, VIDEO_HEADER_LEN, av1_is_key, frame_type_from_key, h264_frame_type,
60    h265_frame_type, push_video_header, vp8_is_key, vp9_is_key,
61};
62use super::dmabuf_identity::DmaBufIdentity;
63use super::frame_rate::FrameRate;
64use super::reference::{
65    ANCHORS, Invalidation, REFERENCE_FRAMES, Reference, ReferenceReport, ReferenceSlots,
66    ReferenceWindow, SlotPlan,
67};
68use super::session::{RateSettings, check_host_frame};
69use super::sps::{NoReorder, h264_frame_num_range};
70use crate::CaptureSettings;
71
72/// The slices an H.264 or HEVC picture is cut into, a decoder threading a frame across them.
73/// AMD's VCE is the exception for H.264: it codes a picture cut into four slices at less than
74/// half its one-slice rate (Radeon Pro VII, 2160p: 44.6 against 20.4 ms a frame, 1080p: 8.7
75/// against 5.7), where its HEVC and every other engine measured here cost nothing for them.
76/// The low-power H.264 of Skylake and Broxton is the other (`whole_picture_vdenc`), one slice
77/// where a session takes it, which it does only where the driver offers no full entry point.
78const SLICES: u32 = 4;
79/// The bytes a coded buffer holds: the uncompressed picture and some, an upper bound on any
80/// frame.
81/// The frame budget, in bits a pixel, under which radeonsi's rate control cannot refine a still
82/// screen it says no quantizer of, so the cleanup ends on a key frame (`refines`). 1080p text,
83/// pixelflux alone, the cleanup through the rate control at 0.004 / 0.008 / 0.016 / 0.03 / 0.12
84/// bits a pixel a frame: H.264 18.0 (60 fps 0.5 Mbit/s) / 30.0-38.2 / 40.1-52.6 / 43.4 / 56.4
85/// dB, HEVC 29.1 / - / 46.2 / 33.1 / -, each the full 10 s at the session's rate (2.1 MB at 60
86/// fps 2 Mbit/s); a key frame at `HELD_KEY_BITS_PER_PIXEL` came to 51.5 dB (H.264) and 47.1
87/// (HEVC) on 0.4-0.55 MB.
88const REFINE_BITS_PER_PIXEL: f64 = 0.04;
89
90/// The bits a pixel a held key frame of a constant rate is given at least (`held_key_bits`):
91/// VCE spends a frame's budget down the picture and codes the rows it runs out on at its
92/// coarsest, so a key frame short of what the picture needs at the paint-over quality comes
93/// out sharp at the top and smeared below: 1080p text in 0.8 bits a pixel came to 271 kB at
94/// 25.5 dB (H.264, 52 dB in the top rows, 18 in the bottom ones) and 307 kB at 39.0 (HEVC), in
95/// 1.6 to 463 kB at 51.5 and 380 kB at 47.1. The driver's best quality level for the key frame
96/// changed nothing.
97const HELD_KEY_BITS_PER_PIXEL: f64 = 1.6;
98
99/// Whether a constant rate of `bps` at `fps` gives a `width` x `height` frame the budget
100/// radeonsi's rate control refines a still screen in (`REFINE_BITS_PER_PIXEL`).
101fn refines(bps: u32, fps: f64, width: u32, height: u32) -> bool {
102    let pixels = f64::from(width) * f64::from(height);
103    f64::from(bps) / fps.max(1.0) >= REFINE_BITS_PER_PIXEL * pixels.max(1.0)
104}
105
106/// The bits a key frame held at a constant rate of `bps` is given for a `width` x `height`
107/// picture: `HELD_KEY_BUDGET_S` of the rate, and at least `HELD_KEY_BITS_PER_PIXEL`, the
108/// maintainer's rule that a key frame is capped only where the cap leaves the picture good.
109fn held_key_bits(bps: u32, width: u32, height: u32) -> f64 {
110    let pixels = f64::from(width) * f64::from(height);
111    (f64::from(bps) * crate::encoders::HELD_KEY_BUDGET_S).max(HELD_KEY_BITS_PER_PIXEL * pixels)
112}
113
114fn coded_buffer_size(width: u32, height: u32) -> u32 {
115    3 * width * height + (1 << 16)
116}
117
118/// The libva of this process, loaded once, or why it could not be.
119fn libva() -> Result<VaApi, String> {
120    static LIBVA: OnceLock<Result<Libva, String>> = OnceLock::new();
121    LIBVA
122        .get_or_init(|| unsafe { Libva::load() })
123        .as_ref()
124        .map(|lib| lib.api)
125        .map_err(Clone::clone)
126}
127
128/// How frames reach a session: a Wayland DRM-PRIME dmabuf, or packed host pixels in B,G,R,A
129/// (`rgba: false`) or R,G,B,A (`rgba: true`) byte order.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub enum Input {
132    Dmabuf,
133    Host { rgba: bool },
134}
135
136/// The 8-bit 4:4:4 surface formats a session tries, in order. The order only breaks a tie
137/// on a driver whose video processor renders both: every frame reaches either through the
138/// convert, so neither is nearer the host.
139const FULLCOLOR_FOURCCS: [u32; 2] = [VA_FOURCC_444P, VA_FOURCC_XYUV];
140
141/// The surface formats a session of this chroma and sample depth tries, in order.
142fn surface_fourccs(fullcolor: bool, bit_depth: u32) -> &'static [u32] {
143    match (fullcolor, bit_depth) {
144        (false, 10) => &[VA_FOURCC_P010],
145        (true, 10) => &[VA_FOURCC_Y410],
146        (true, _) => &FULLCOLOR_FOURCCS,
147        (false, _) => &[VA_FOURCC_NV12],
148    }
149}
150
151/// The render target format of a chroma and sample depth.
152fn rt_format(fullcolor: bool, bit_depth: u32) -> u32 {
153    match (fullcolor, bit_depth) {
154        (false, 10) => VA_RT_FORMAT_YUV420_10,
155        (true, 10) => VA_RT_FORMAT_YUV444_10,
156        (true, _) => VA_RT_FORMAT_YUV444,
157        (false, _) => VA_RT_FORMAT_YUV420,
158    }
159}
160
161/// The name of a surface format, for the session log.
162pub(crate) fn fourcc_name(fourcc: u32) -> String {
163    match fourcc {
164        VA_FOURCC_NV12 => "nv12".into(),
165        VA_FOURCC_P010 => "p010".into(),
166        VA_FOURCC_Y410 => "y410".into(),
167        VA_FOURCC_444P => "yuv444p".into(),
168        VA_FOURCC_XYUV => "vuyx".into(),
169        VA_FOURCC_BGRA => "bgra".into(),
170        VA_FOURCC_RGBA => "rgba".into(),
171        other => String::from_utf8_lossy(&other.to_le_bytes())
172            .trim()
173            .to_string(),
174    }
175}
176
177unsafe extern "C" fn log_error(_user: *mut c_void, message: *const c_char) {
178    if !message.is_null() {
179        eprintln!(
180            "[vaapi] {}",
181            unsafe { CStr::from_ptr(message) }
182                .to_string_lossy()
183                .trim_end()
184        );
185    }
186}
187
188unsafe extern "C" fn log_info(_user: *mut c_void, message: *const c_char) {
189    if !message.is_null() {
190        crate::log::debug!(
191            "[vaapi] {}",
192            unsafe { CStr::from_ptr(message) }
193                .to_string_lossy()
194                .trim_end()
195        );
196    }
197}
198
199/// What bounds the frames of a constant-rate session, by what the driver's rate control does
200/// with each bound.
201///
202/// A buffer of `vbv_bits` (a frame and a half) with every frame capped at it is the tightest,
203/// and the default. Intel's iHD stops coding under it: measured on Intel Arc at 1080p and
204/// 60 fps, its H.264 and HEVC rate controls skip every block and pad the frame with zeros up
205/// to the target once the buffer or the cap binds, and its AV1 one spends a third of the
206/// target, so a scroll sat at 19 to 20 dB at 2, 8, and 30 Mbit/s alike and a still screen was
207/// never refined. So on that driver H.264 and HEVC run with no bound of their own, where a
208/// still screen of text at 2 Mbit/s reaches 46 to 51 dB, and AV1, which takes no cap, in a
209/// buffer of `IHD_AV1_BUFFER_S`, which halved its largest frame. The driver's low-delay frame
210/// tolerance holds an H.264 frame to three budgets, but leaves a still screen of dense text
211/// at 27 dB, every block skipped; a cap of six budgets cost HEVC 3 dB. Its VP9 refines in
212/// the default buffer and keeps it.
213#[derive(Clone, Copy, Debug, PartialEq)]
214enum ConstantRate {
215    /// The session's buffer, and the frame cap where the driver takes one.
216    Buffer,
217    /// No buffer and no cap.
218    Unbounded,
219    /// A buffer of this many seconds of the target and no cap.
220    Seconds(f64),
221}
222
223/// Seconds of the target iHD's AV1 rate control is given as a buffer (`ConstantRate`).
224const IHD_AV1_BUFFER_S: f64 = 0.1;
225
226/// The rows a reconstruction is compared with its source on (`VaapiEncoder::last_psnr`): one
227/// in this many.
228const PSNR_ROW_STEP: usize = 8;
229
230/// The PSNR under which a reconstruction is no picture of its source (`picture_psnr`): the
231/// coarsest quantizer leaves a screen of text near 19 dB.
232const UNREADABLE_DB: f32 = 12.0;
233
234/// Drop the zero bytes a driver's constant rate pads an H.264 or HEVC frame with (iHD fills a
235/// frame up to the target after the last slice): trailing zero bytes are no part of a NAL
236/// unit, whose last byte carries the stop bit.
237fn strip_zero_padding(out: &mut Vec<u8>, from: usize) {
238    let end = out[from..]
239        .iter()
240        .rposition(|&b| b != 0)
241        .map_or(from, |i| from + i + 1);
242    out.truncate(end);
243}
244
245/// A VA display opened on a render node, terminated with the device.
246pub(crate) struct Device {
247    api: VaApi,
248    display: VADisplay,
249    _fd: OwnedFd,
250    vendor: String,
251    /// Whether the device encodes H.264 on AMD's VCE (`amd_vce`).
252    vce: bool,
253    /// Whether the device's low-power H.264 encoder codes a picture as one slice only
254    /// (`whole_picture_vdenc`).
255    whole_picture_vdenc: bool,
256}
257
258/// Whether the render node `fd` is an Intel part whose low-power H.264 encoder walks a picture
259/// once, top to bottom, wherever its slices are cut: Skylake and Broxton, told by the PCI
260/// device the kernel names, since their drivers list the entry point, and iHD a slice
261/// structure, as they do for the later parts that code the cut. A picture of four slices came
262/// out corrupt below the first there (HD 530), and whole from the full entry point.
263fn whole_picture_vdenc(fd: c_int) -> bool {
264    let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
265    if unsafe { libc::fstat(fd, &mut stat) } != 0 {
266        return false;
267    }
268    let id = |name: &str| {
269        let path = format!(
270            "/sys/dev/char/{}:{}/device/{name}",
271            libc::major(stat.st_rdev),
272            libc::minor(stat.st_rdev)
273        );
274        let text = std::fs::read_to_string(path).ok()?;
275        u32::from_str_radix(text.trim().trim_start_matches("0x"), 16).ok()
276    };
277    id("vendor") == Some(0x8086) && id("device").is_some_and(skylake_or_broxton)
278}
279
280/// Whether an Intel PCI device id is a Skylake or a Broxton part's.
281fn skylake_or_broxton(device: u32) -> bool {
282    matches!(
283        device,
284        0x1900..=0x19ff | 0x0a84 | 0x1a84 | 0x1a85 | 0x5a84 | 0x5a85
285    )
286}
287
288/// Whether the render node `fd` is an amdgpu device whose video encoder is VCE rather than VCN.
289/// The kernel reports the video blocks a device carries (`AMDGPU_INFO_HW_IP_COUNT`, the query
290/// Mesa reads to pick its encoder): a VCE-generation part answers for VCE and refuses the
291/// query for VCN's encoder. Any other driver is not asked.
292fn amd_vce(fd: c_int) -> bool {
293    #[repr(C)]
294    struct Version {
295        major: c_int,
296        minor: c_int,
297        patch: c_int,
298        name_len: usize,
299        name: *mut c_char,
300        date_len: usize,
301        date: *mut c_char,
302        desc_len: usize,
303        desc: *mut c_char,
304    }
305    #[repr(C)]
306    struct Info {
307        return_pointer: u64,
308        return_size: u32,
309        query: u32,
310        ip_type: u32,
311        ip_instance: u32,
312        reserved: [u32; 2],
313    }
314    const DRM_IOCTL_VERSION: u64 = 0xc040_6400;
315    const DRM_IOCTL_AMDGPU_INFO: u64 = 0x4020_6445;
316    const AMDGPU_INFO_HW_IP_COUNT: u32 = 0x03;
317    const AMDGPU_HW_IP_VCE: u32 = 4;
318    const AMDGPU_HW_IP_VCN_ENC: u32 = 7;
319    let mut name = [0u8; 16];
320    let mut version = Version {
321        major: 0,
322        minor: 0,
323        patch: 0,
324        name_len: name.len(),
325        name: name.as_mut_ptr() as *mut c_char,
326        date_len: 0,
327        date: ptr::null_mut(),
328        desc_len: 0,
329        desc: ptr::null_mut(),
330    };
331    if unsafe { libc::ioctl(fd, DRM_IOCTL_VERSION as _, &mut version as *mut Version) } != 0
332        || name[..version.name_len.min(name.len())] != *b"amdgpu"
333    {
334        return false;
335    }
336    let engines = |ip_type: u32| {
337        let mut count: u32 = 0;
338        let mut info = Info {
339            return_pointer: &mut count as *mut u32 as u64,
340            return_size: 4,
341            query: AMDGPU_INFO_HW_IP_COUNT,
342            ip_type,
343            ip_instance: 0,
344            reserved: [0; 2],
345        };
346        if unsafe { libc::ioctl(fd, DRM_IOCTL_AMDGPU_INFO as _, &mut info as *mut Info) } != 0 {
347            0
348        } else {
349            count
350        }
351    };
352    engines(AMDGPU_HW_IP_VCE) > 0 && engines(AMDGPU_HW_IP_VCN_ENC) == 0
353}
354
355/// The display is used from the one thread that holds the session.
356unsafe impl Send for Device {}
357unsafe impl Sync for Device {}
358
359impl Drop for Device {
360    fn drop(&mut self) {
361        unsafe { (self.api.vaTerminate)(self.display) };
362    }
363}
364
365impl Device {
366    /// Open the render node behind `encode_node_index` (`renderD128` when none is named) and
367    /// initialize the driver on it.
368    pub(crate) fn open(api: &VaApi, encode_node_index: i32) -> Result<Self, String> {
369        let render_node = format!("/dev/dri/renderD{}", 128 + encode_node_index.max(0));
370        // NVIDIA's VA driver only decodes, and its constructor calls cuInit under the loader's
371        // lock, while a cuInit in another thread loads libraries under CUDA's: each would wait
372        // for the other.
373        if crate::get_gpu_driver(encode_node_index.max(0)).contains("nvidia") {
374            return Err(format!(
375                "{render_node} is NVIDIA's, whose VA driver encodes nothing"
376            ));
377        }
378        let path = CString::new(render_node.clone()).unwrap();
379        let raw = unsafe { libc::open(path.as_ptr(), libc::O_RDWR | libc::O_CLOEXEC) };
380        if raw < 0 {
381            return Err(format!(
382                "{render_node}: {}",
383                std::io::Error::last_os_error()
384            ));
385        }
386        Self::on(api, unsafe { OwnedFd::from_raw_fd(raw) }, &render_node)
387    }
388
389    /// Initialize the driver on the open render node `fd`, with its messages routed to the
390    /// log; `render_node` names it in errors.
391    pub(crate) fn on(api: &VaApi, fd: OwnedFd, render_node: &str) -> Result<Self, String> {
392        let display = unsafe { (api.vaGetDisplayDRM)(fd.as_raw_fd()) };
393        if display.is_null() {
394            return Err(format!("{render_node} carries no VA display"));
395        }
396        unsafe {
397            (api.vaSetErrorCallback)(display, Some(log_error), ptr::null_mut());
398            (api.vaSetInfoCallback)(display, Some(log_info), ptr::null_mut());
399        }
400        let (mut major, mut minor) = (0 as c_int, 0 as c_int);
401        let status = unsafe { (api.vaInitialize)(display, &mut major, &mut minor) };
402        if status != VA_STATUS_SUCCESS as VAStatus {
403            return Err(format!(
404                "{render_node}: no VA driver initialized ({})",
405                error_text(api, status)
406            ));
407        }
408        let vendor = unsafe {
409            let text = (api.vaQueryVendorString)(display);
410            if text.is_null() {
411                String::new()
412            } else {
413                CStr::from_ptr(text).to_string_lossy().into_owned()
414            }
415        };
416        let vce = amd_vce(fd.as_raw_fd());
417        let whole_picture_vdenc = whole_picture_vdenc(fd.as_raw_fd());
418        Ok(Self {
419            api: *api,
420            display,
421            _fd: fd,
422            vendor,
423            vce,
424            whole_picture_vdenc,
425        })
426    }
427
428    /// Whether the driver's video processor, converting to 4:2:0, reads a linear surface at a
429    /// pitch rounded up to 64 bytes rather than the one its import declares, as Intel's does:
430    /// such a surface converts sheared unless its pitch is already a multiple of 64.
431    fn rounds_linear_pitch(&self) -> bool {
432        self.vendor.contains("Intel")
433    }
434
435    /// The quantizer curve the driver's sessions read: Intel's has its own where its encoders
436    /// were measured apart from AMD's.
437    fn hardware(&self) -> Hardware {
438        if self.vendor.contains("Intel") {
439            Hardware::VaapiIntel
440        } else {
441            Hardware::Vaapi
442        }
443    }
444
445    /// Whether an H.264 picture of `entrypoint` is left as one slice (`SLICES`): on AMD's VCE,
446    /// and on a low-power encoder that codes no other cut.
447    fn one_slice_h264(&self, entrypoint: VAEntrypoint) -> bool {
448        self.vce || (self.whole_picture_vdenc && entrypoint == VAEntrypointEncSliceLP)
449    }
450
451    /// Whether a session keeps anchors (`ReferenceWindow::with_anchors`): the driver codes a
452    /// picture from the reference it is named, as radeonsi's VCE (H.264) and UVD (H.265) were
453    /// measured to (`vaapi_predicts_past_a_lost_frame`, `vaapi_predicts_from_an_anchor`); others
454    /// are not yet. H.264 keeps one, long-term; H.265 keeps two, listed as short-term pictures in
455    /// every reference picture set, as NVENC's are.
456    fn keeps_anchors(&self) -> bool {
457        self.vendor.contains("radeonsi")
458    }
459
460    /// Whether the driver's rate control stops coding in a buffer of a frame or two, as Intel's
461    /// iHD does (`ConstantRate`).
462    fn starves_in_a_small_buffer(&self) -> bool {
463        self.vendor.contains("iHD")
464    }
465
466    /// The highest H.264 `level_idc` the driver's sequence parameters may name
467    /// (`Negotiated::h264_driver_level`). iHD's level table ends at 5.2: above it its rate
468    /// control is handed a maximum frame size of 0 (`GetProfileLevelMaxFrameSize`), and every
469    /// constant-rate picture after the key frame comes out undecodable past its first rows a
470    /// slice (29 of 30 at 2160p and 75 fps on an Arc A750,
471    /// `vaapi_h264_constant_rate_decodes_above_level_5_2`), where a constant quantizer, which
472    /// runs no rate control, decodes clean. Told 5.2, its frame cap stays above any frame.
473    fn h264_driver_level(&self) -> u32 {
474        if self.vendor.contains("iHD") {
475            52
476        } else {
477            u32::MAX
478        }
479    }
480
481    fn check(&self, status: VAStatus, what: &str) -> Result<(), String> {
482        if status == VA_STATUS_SUCCESS as VAStatus {
483            Ok(())
484        } else {
485            Err(format!("{what}: {}", error_text(&self.api, status)))
486        }
487    }
488
489    /// The profiles the driver lists.
490    fn profiles(&self) -> Result<Vec<VAProfile>, String> {
491        let mut profiles = vec![
492            0 as VAProfile;
493            unsafe { (self.api.vaMaxNumProfiles)(self.display) }.max(0) as usize
494        ];
495        let mut listed: c_int = 0;
496        self.check(
497            unsafe {
498                (self.api.vaQueryConfigProfiles)(self.display, profiles.as_mut_ptr(), &mut listed)
499            },
500            "vaQueryConfigProfiles",
501        )?;
502        profiles.truncate(listed.max(0) as usize);
503        Ok(profiles)
504    }
505
506    /// The encode entry points of `profile`, the low-power one first, since recent Intel
507    /// generations expose it as the only one for HEVC, VP9, and AV1 and it is the shorter path
508    /// where both exist; a session the low-power one cannot serve, such as a constant-rate one
509    /// where it runs constant quantizer only, takes the full one. Empty where the profile
510    /// encodes on neither. H.264 on a part whose low-power encoder codes one slice a picture
511    /// takes the full one first instead (`on_device`).
512    fn encode_entrypoints(&self, profile: VAProfile) -> Vec<VAEntrypoint> {
513        let mut entrypoints = vec![
514            0 as VAEntrypoint;
515            unsafe { (self.api.vaMaxNumEntrypoints)(self.display) }.max(0)
516                as usize
517        ];
518        let mut listed: c_int = 0;
519        let status = unsafe {
520            (self.api.vaQueryConfigEntrypoints)(
521                self.display,
522                profile,
523                entrypoints.as_mut_ptr(),
524                &mut listed,
525            )
526        };
527        if status != VA_STATUS_SUCCESS as VAStatus {
528            return Vec::new();
529        }
530        let listed = &entrypoints[..listed.max(0) as usize];
531        [VAEntrypointEncSliceLP, VAEntrypointEncSlice]
532            .into_iter()
533            .filter(|e| listed.contains(e))
534            .collect()
535    }
536
537    /// One configuration attribute of a profile and entry point, None where the driver does
538    /// not report it.
539    fn attribute(
540        &self,
541        profile: VAProfile,
542        entrypoint: VAEntrypoint,
543        kind: VAConfigAttribType,
544    ) -> Option<u32> {
545        let mut attrib = VAConfigAttrib {
546            type_: kind,
547            value: 0,
548        };
549        let status = unsafe {
550            (self.api.vaGetConfigAttributes)(self.display, profile, entrypoint, &mut attrib, 1)
551        };
552        (status == VA_STATUS_SUCCESS as VAStatus && attrib.value != VA_ATTRIB_NOT_SUPPORTED)
553            .then_some(attrib.value)
554    }
555
556    /// The surface attributes a configuration reports, asked for the way libva wants: once
557    /// for the count, once for the values. Empty where the driver reports none.
558    fn surface_attributes(&self, config: VAConfigID) -> Vec<VASurfaceAttrib> {
559        let mut count: c_uint = 0;
560        let mut attribs: Vec<VASurfaceAttrib> = Vec::new();
561        for pass in 0..2 {
562            let list = if pass == 0 {
563                ptr::null_mut()
564            } else {
565                attribs.as_mut_ptr()
566            };
567            if unsafe {
568                (self.api.vaQuerySurfaceAttributes)(self.display, config, list, &mut count)
569            } != VA_STATUS_SUCCESS as VAStatus
570            {
571                return Vec::new();
572            }
573            attribs.resize(count as usize, unsafe { std::mem::zeroed() });
574        }
575        attribs
576    }
577
578    /// The surface formats a configuration renders into, empty where it constrains none.
579    fn surface_fourccs(&self, config: VAConfigID) -> Vec<u32> {
580        self.surface_attributes(config)
581            .iter()
582            .filter(|a| a.type_ == VASurfaceAttribPixelFormat)
583            .map(|a| unsafe { a.value.value.i } as u32)
584            .collect()
585    }
586
587    /// The surface alignment a configuration asks for, as `(width, height)`, one where it
588    /// asks none.
589    fn surface_alignment(&self, config: VAConfigID) -> (u32, u32) {
590        self.surface_attributes(config)
591            .iter()
592            .find(|a| a.type_ == VASurfaceAttribAlignmentSize)
593            .map(|a| {
594                let v = unsafe { a.value.value.i } as u32;
595                (1 << (v & 0xf), 1 << ((v & 0xf0) >> 4))
596            })
597            .unwrap_or((1, 1))
598    }
599
600    /// Surfaces of `fourcc` at `width` x `height`, `count` of them, on the driver's own memory.
601    fn create_surfaces(
602        &self,
603        rt_format: u32,
604        fourcc: u32,
605        width: u32,
606        height: u32,
607        count: usize,
608    ) -> Result<Vec<VASurfaceID>, String> {
609        let mut attrib: VASurfaceAttrib = unsafe { std::mem::zeroed() };
610        attrib.type_ = VASurfaceAttribPixelFormat;
611        attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
612        attrib.value.type_ = VAGenericValueTypeInteger;
613        attrib.value.value.i = fourcc as i32;
614        let mut surfaces = vec![VA_INVALID_SURFACE; count];
615        self.check(
616            unsafe {
617                (self.api.vaCreateSurfaces)(
618                    self.display,
619                    rt_format,
620                    width,
621                    height,
622                    surfaces.as_mut_ptr(),
623                    count as c_uint,
624                    &mut attrib,
625                    1,
626                )
627            },
628            &format!(
629                "this VA-API driver allocates no {} surfaces",
630                fourcc_name(fourcc)
631            ),
632        )?;
633        Ok(surfaces)
634    }
635
636    fn destroy_surfaces(&self, surfaces: &mut [VASurfaceID]) {
637        if !surfaces.is_empty() {
638            unsafe {
639                (self.api.vaDestroySurfaces)(
640                    self.display,
641                    surfaces.as_mut_ptr(),
642                    surfaces.len() as c_int,
643                )
644            };
645        }
646    }
647
648    fn create_buffer(
649        &self,
650        context: VAContextID,
651        kind: VABufferType,
652        data: &[u8],
653    ) -> Result<VABufferID, String> {
654        self.create_buffer_raw(
655            context,
656            kind,
657            data.len() as c_uint,
658            data.as_ptr() as *mut c_void,
659        )
660    }
661
662    /// A buffer the driver fills, created without initial data: iHD refuses a coded buffer
663    /// handed any.
664    fn create_output_buffer(
665        &self,
666        context: VAContextID,
667        kind: VABufferType,
668        size: u32,
669    ) -> Result<VABufferID, String> {
670        self.create_buffer_raw(context, kind, size as c_uint, ptr::null_mut())
671    }
672
673    fn create_buffer_raw(
674        &self,
675        context: VAContextID,
676        kind: VABufferType,
677        size: c_uint,
678        data: *mut c_void,
679    ) -> Result<VABufferID, String> {
680        let mut id = VA_INVALID_ID;
681        self.check(
682            unsafe {
683                (self.api.vaCreateBuffer)(self.display, context, kind, size, 1, data, &mut id)
684            },
685            &format!("vaCreateBuffer(type {kind})"),
686        )?;
687        Ok(id)
688    }
689}
690
691fn error_text(api: &VaApi, status: VAStatus) -> String {
692    let text = unsafe { (api.vaErrorStr)(status) };
693    if text.is_null() {
694        format!("VA status {status}")
695    } else {
696        unsafe { CStr::from_ptr(text) }
697            .to_string_lossy()
698            .into_owned()
699    }
700}
701
702/// The video codecs the VA-API driver of the render node behind `encode_node_index` encodes:
703/// those one of whose profiles the driver lists with an encode entry point, which is what
704/// `vainfo` reports and what a session checks first. The device is opened the way a session
705/// opens it and released. An error names the step that failed: no such node, no VA driver on
706/// it, or a libva the probe cannot reach.
707pub(crate) fn probe_codecs(encode_node_index: i32) -> Result<Vec<Served>, String> {
708    probe_codecs_on(&Device::open(&libva()?, encode_node_index)?)
709}
710
711/// A codec a device encodes, with the formats it encodes the codec in.
712pub(crate) type Served = (Codec, super::Formats);
713
714/// `probe_codecs` on an open device: each codec a profile of its 8-bit 4:2:0 ladder encodes,
715/// and whether the profile each other format's session opens under encodes and renders that
716/// format's surfaces too.
717pub(crate) fn probe_codecs_on(device: &Device) -> Result<Vec<Served>, String> {
718    let profiles = device.profiles()?;
719    let entrypoints = |profile: VAProfile| {
720        if profiles.contains(&profile) {
721            device.encode_entrypoints(profile)
722        } else {
723            Vec::new()
724        }
725    };
726    Ok(Codec::VIDEO
727        .into_iter()
728        .filter(|&codec| {
729            profile_ladder(codec, false, 8)
730                .into_iter()
731                .any(|p| !entrypoints(p).is_empty())
732        })
733        .map(|codec| {
734            let carries = |fullcolor: bool, bit_depth: u32| {
735                profile_ladder(codec, fullcolor, bit_depth)
736                    .into_iter()
737                    .any(|p| {
738                        entrypoints(p).into_iter().any(|e| {
739                            device
740                                .attribute(p, e, VAConfigAttribRTFormat)
741                                .is_none_or(|f| f & rt_format(fullcolor, bit_depth) != 0)
742                        })
743                    })
744            };
745            (
746                codec,
747                super::Formats {
748                    fullcolor: carries(true, 8),
749                    ten_bit: [carries(false, 10), carries(true, 10)],
750                },
751            )
752        })
753        .collect())
754}
755
756/// The VA profiles a session opens under, in order of preference: for 8-bit 4:2:0, the ones
757/// such a session comes up as; for 4:4:4 or 10 bits, the profile that carries the format, or
758/// nothing where the codec has none the session serves. AV1's main profile carries both of
759/// its depths, told apart by the surface format.
760fn profile_ladder(codec: Codec, fullcolor: bool, bit_depth: u32) -> Vec<VAProfile> {
761    match (codec, fullcolor, bit_depth) {
762        (Codec::H264, false, 8) => vec![
763            VAProfileH264High,
764            VAProfileH264Main,
765            VAProfileH264ConstrainedBaseline,
766        ],
767        (Codec::H265, false, 8) => vec![VAProfileHEVCMain],
768        (Codec::H265, false, 10) => vec![VAProfileHEVCMain10],
769        (Codec::H265, true, 8) => vec![VAProfileHEVCMain444],
770        (Codec::H265, true, 10) => vec![VAProfileHEVCMain444_10],
771        (Codec::Vp8, false, 8) => vec![VAProfileVP8Version0_3],
772        (Codec::Vp9, false, 8) => vec![VAProfileVP9Profile0],
773        (Codec::Vp9, true, 8) => vec![VAProfileVP9Profile1],
774        (Codec::Vp9, false, 10) => vec![VAProfileVP9Profile2],
775        (Codec::Vp9, true, 10) => vec![VAProfileVP9Profile3],
776        (Codec::Av1, false, 8 | 10) => vec![VAProfileAV1Profile0],
777        _ => Vec::new(),
778    }
779}
780
781/// The parameter buffers of one picture, in the order they are rendered.
782pub(super) struct Buffers {
783    entries: Vec<(VABufferType, Vec<u8>)>,
784}
785
786impl Buffers {
787    fn new() -> Self {
788        Self {
789            entries: Vec::new(),
790        }
791    }
792
793    /// One parameter structure as a buffer of its type.
794    pub(super) fn push<T: Copy>(&mut self, kind: VABufferType, value: &T) {
795        let bytes = unsafe {
796            std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
797        };
798        self.entries.push((kind, bytes.to_vec()));
799    }
800
801    /// One miscellaneous parameter, behind the header naming its type.
802    pub(super) fn push_misc<T: Copy>(&mut self, kind: VAEncMiscParameterType, value: &T) {
803        let mut bytes = kind.to_ne_bytes().to_vec();
804        bytes.extend_from_slice(unsafe {
805            std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
806        });
807        self.entries.push((VAEncMiscParameterBufferType, bytes));
808    }
809
810    /// One packed header: its parameter buffer, then its bytes.
811    pub(super) fn push_packed(&mut self, kind: u32, bytes: &[u8], bit_length: u32) {
812        let param = VAEncPackedHeaderParameterBuffer {
813            type_: kind,
814            bit_length,
815            has_emulation_bytes: 1,
816            va_reserved: [0; 4],
817        };
818        self.push(VAEncPackedHeaderParameterBufferType, &param);
819        self.entries
820            .push((VAEncPackedHeaderDataBufferType, bytes.to_vec()));
821    }
822
823    #[cfg(test)]
824    pub(crate) fn entries(&self) -> &[(VABufferType, Vec<u8>)] {
825        &self.entries
826    }
827}
828
829/// What the session negotiated at open, which the codec arms read.
830#[derive(Clone, Copy)]
831pub(super) struct Negotiated {
832    pub profile: VAProfile,
833    pub entrypoint: VAEntrypoint,
834    /// The packed headers the driver takes, `VA_ENC_PACKED_HEADER_*` bits.
835    pub packed: u32,
836    /// The rate-control mode, `VA_RC_CQP` or `VA_RC_CBR`.
837    pub rc_mode: u32,
838    pub width: u32,
839    pub height: u32,
840    pub fps: FrameRate,
841    pub bits_per_second: u32,
842    /// The reference frames the decoded picture buffer holds.
843    pub dpb: u32,
844    /// The level the decoded picture buffer was sized for, the lowest an H.264 or HEVC
845    /// sequence declares.
846    pub dpb_level: u32,
847    pub fullcolor: bool,
848    pub bit_depth: u32,
849    /// The quantizer bounds of a constant-rate session in the codec's own domain, 0 for none.
850    pub min_qp: u32,
851    pub max_qp: u32,
852    /// The finest quantizer the entry point codes a constant-quantizer picture at
853    /// (`h265::INTEL_LOW_POWER_MIN_QP` on Intel's low-power HEVC encoder, 1 elsewhere).
854    pub qp_floor: u32,
855    /// Whether the driver's rate control codes its quantizer in every coding unit as a delta
856    /// from the slice's (Intel's full HEVC encoder), which the slice's quantizer then has to keep
857    /// within the syntax's range (`h265::Arm::configure`).
858    pub unit_qp_deltas: bool,
859    /// The highest H.264 level the driver is told (`Device::h264_driver_level`); the
860    /// packed SPS still declares the level the stream needs.
861    pub h264_driver_level: u32,
862    /// The regions of interest a frame may name with a quantizer delta
863    /// (`VAConfigAttribEncROI`), 0 where the driver takes none, or none under its rate control
864    /// at a constant rate.
865    pub roi_regions: u32,
866}
867
868/// One frame as the codec arms see it: what it is, where its reconstruction and coded output
869/// go, and what it may predict from.
870pub(super) struct Frame<'a> {
871    pub key: bool,
872    /// The session's count of encoded frames.
873    pub pts: u64,
874    /// The count at the last key frame, where a codec's own counters restart.
875    pub key_pts: u64,
876    pub recon: VASurfaceID,
877    pub coded: VABufferID,
878    /// The frame predicted from, by timestamp and reconstruction surface.
879    pub reference: Option<(u64, VASurfaceID)>,
880    /// Every frame the decoder holds, oldest first, with the surface each was reconstructed
881    /// into and whether a client lost it.
882    pub held: &'a [(u64, VASurfaceID, bool)],
883    /// Whether the session keeps long-term anchors, its key frame the first.
884    pub anchored: bool,
885    /// The long-term index the frame is marked under, an anchor (`ReferenceWindow::plan_anchor`).
886    pub anchor: Option<u8>,
887    /// The anchors among `held`, by timestamp, with the long-term index each is marked under.
888    pub long_term: &'a [(u64, u8)],
889    /// The quantizer, in the codec's domain, of a constant-quantizer session.
890    pub qp: u32,
891    /// VP8's buffer plan.
892    pub slots: SlotPlan,
893    /// The surface each VP8 buffer holds, LAST, GOLDEN, ALTREF.
894    pub slot_surfaces: [VASurfaceID; 3],
895}
896
897/// The codec arm of a session.
898enum Arm {
899    H264(Box<h264::Arm>),
900    H265(h265::Arm),
901    Vp8(vp8::Arm),
902    Vp9(vp9::Arm),
903    Av1(av1::Arm),
904}
905
906impl Arm {
907    /// The surface alignment the codec's blocks want.
908    fn alignment(&self) -> (u32, u32) {
909        match self {
910            Arm::H264(_) | Arm::Vp8(_) => (16, 16),
911            Arm::H265(a) => a.alignment(),
912            Arm::Vp9(_) => (64, 64),
913            Arm::Av1(a) => a.alignment(),
914        }
915    }
916
917    /// The packed headers the codec writes itself.
918    fn wanted_packed(&self) -> u32 {
919        match self {
920            Arm::H264(_) | Arm::H265(_) => {
921                VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_SLICE
922            }
923            Arm::Av1(_) => VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE,
924            Arm::Vp8(_) | Arm::Vp9(_) => 0,
925        }
926    }
927
928    /// Whether the session can name what a frame predicts from and leave a lost one out: the
929    /// codecs whose headers the session writes need the driver to take them, since a driver
930    /// writing its own slice headers names the references it chooses.
931    fn tracks_references(&self, packed: u32) -> bool {
932        match self {
933            Arm::H264(_) | Arm::H265(_) => packed & VA_ENC_PACKED_HEADER_SLICE != 0,
934            Arm::Av1(_) => packed & VA_ENC_PACKED_HEADER_PICTURE != 0,
935            Arm::Vp8(_) | Arm::Vp9(_) => true,
936        }
937    }
938
939    fn sequence(&mut self, negotiated: &Negotiated, out: &mut Buffers) {
940        match self {
941            Arm::H264(a) => a.sequence(negotiated, out),
942            Arm::H265(a) => a.sequence(negotiated, out),
943            Arm::Vp8(a) => a.sequence(negotiated, out),
944            Arm::Vp9(a) => a.sequence(negotiated, out),
945            Arm::Av1(a) => a.sequence(negotiated, out),
946        }
947    }
948
949    /// The level the codec's sequence names for the configured geometry and rate, zero for a
950    /// codec that names none.
951    fn level(&self) -> u32 {
952        match self {
953            Arm::H264(a) => a.level(),
954            Arm::H265(a) => a.level(),
955            Arm::Av1(a) => a.level(),
956            Arm::Vp8(_) | Arm::Vp9(_) => 0,
957        }
958    }
959
960    fn picture(
961        &mut self,
962        negotiated: &Negotiated,
963        frame: &Frame,
964        out: &mut Buffers,
965    ) -> Result<(), String> {
966        match self {
967            Arm::H264(a) => a.picture(negotiated, frame, out),
968            Arm::H265(a) => a.picture(negotiated, frame, out),
969            Arm::Vp8(a) => a.picture(negotiated, frame, out),
970            Arm::Vp9(a) => a.picture(negotiated, frame, out),
971            Arm::Av1(a) => a.picture(negotiated, frame, out),
972        }
973    }
974}
975
976/// The references a session keeps: a decoded picture buffer, or VP8's three buffers.
977enum References {
978    Window(ReferenceWindow),
979    Slots(ReferenceSlots),
980}
981
982/// The host frame's way onto a surface: through the surface's own image where the driver
983/// derives one, else through an image the driver copies in.
984struct HostUpload {
985    surface: VASurfaceID,
986    fourcc: u32,
987    derive: bool,
988    image: Option<VAImage>,
989}
990
991/// The dmabuf imports a zero-copy session keeps, past the few buffers any capture cycles.
992const DMABUF_IMPORTS: usize = 8;
993
994/// Quality index steps finer than the paint-over one a refined block is coded at
995/// (`VaapiEncoder::refines`): on Intel's iHD (N97) 1080p H.264 text beside a video went from
996/// 41.3 dB to 44.1, 45.4, and 47.1 at a constant quantizer at 0, 3, and 6 steps finer, where the
997/// driver's cleanup of the still screen reaches 46.2; at a constant rate, held to `REFINE_REACH`,
998/// from 31.0 to 33.3 at 8 Mbit/s and from 19.0 to 21.1 at 2, the video unchanged.
999const REFINE_FINER: u32 = 3;
1000
1001/// The most quantizer steps off the frame's own a refined region is coded at
1002/// (`VaapiEncoder::refines`): on Intel's iHD (N97, H.264 on the low-power entry point, 3 Mbit/s
1003/// CBR at 1080p60) a region 10 finer came out at a quantizer 7 under the rest with the rate held,
1004/// 25 finer at 20 under it at 54 Mbit/s, the rate control lost; 5 finer, a delta the driver
1005/// codes natively, moved nothing.
1006const REFINE_REACH: u32 = 10;
1007
1008/// The zones Intel's low-power H.264 encoder codes regions of interest in natively.
1009const INTEL_VDENC_NATIVE_ROI: u32 = 3;
1010
1011/// The regions of interest a frame may name (`Negotiated::roi_regions`), from the driver's
1012/// `VAConfigAttribEncROI`: none where it reports none, or at a constant rate whose rate control
1013/// takes no quantizer deltas (bit 9). On Intel's low-power entry point at a constant quantizer
1014/// no more than `INTEL_VDENC_NATIVE_ROI`: iHD's VDEnc from Gen9 to Gen12 copies the delta of
1015/// every region it is handed into a stack array of three when they share at most three deltas,
1016/// which one refinement always does, so a fourth region writes past it and a frame naming a
1017/// dozen aborted the process on `__stack_chk_fail` inside `vaEndPicture` (N97, a window dragged
1018/// at 144 fps). Its rate control hands the regions to the firmware instead.
1019fn roi_regions(
1020    attribute: Option<u32>,
1021    hardware: Hardware,
1022    entrypoint: VAEntrypoint,
1023    cbr: bool,
1024) -> u32 {
1025    let Some(v) = attribute else {
1026        return 0;
1027    };
1028    let qp_deltas_under_rate_control = v & (1 << 9) != 0;
1029    if cbr && !qp_deltas_under_rate_control {
1030        return 0;
1031    }
1032    let regions = v & 0xff;
1033    if !cbr && hardware == Hardware::VaapiIntel && entrypoint == VAEntrypointEncSliceLP {
1034        regions.min(INTEL_VDENC_NATIVE_ROI)
1035    } else {
1036        regions
1037    }
1038}
1039
1040/// One VA-API encoder session, hardware, for one capture.
1041pub struct VaapiEncoder {
1042    codec: Codec,
1043    input: Input,
1044    device: Arc<Device>,
1045    config: VAConfigID,
1046    context: VAContextID,
1047    vpp_config: VAConfigID,
1048    vpp_context: VAContextID,
1049    /// The color standard the video processor is told to convert from and to.
1050    vpp_standards: (VAProcColorStandardType, VAProcColorStandardType),
1051    negotiated: Negotiated,
1052    fourcc: u32,
1053    rt_format: u32,
1054    surface_width: u32,
1055    surface_height: u32,
1056    low_power: bool,
1057    /// The reconstruction surfaces, one more than the frames the decoder holds.
1058    recon: Vec<VASurfaceID>,
1059    /// The converted picture the encoder reads.
1060    converted: Vec<VASurfaceID>,
1061    host: Option<HostUpload>,
1062    /// The surface over each dmabuf a zero-copy session encoded, by fd, oldest first, at most
1063    /// `DMABUF_IMPORTS` (`dmabuf_surface`).
1064    imports: Vec<(i32, DmaBufIdentity, VASurfaceID)>,
1065    coded: VABufferID,
1066    arm: Arm,
1067    references: Option<References>,
1068    /// The frames encoded and the count at the last key frame, which a session that tracks no
1069    /// references keeps itself; a tracked one keeps both in its references.
1070    frame_count: u64,
1071    key_count: u64,
1072    /// The surface each held frame was reconstructed into, by timestamp.
1073    surfaces_of: HashMap<u64, VASurfaceID>,
1074    last_reference: Reference,
1075    rate: RateSettings,
1076    qp: u32,
1077    /// The quantizer the next frame is held at (`hold_quantizer`).
1078    held: Option<u32>,
1079    /// The blocks the next frame of the rate control's refines (`refine_blocks`), and the
1080    /// regions of interest of the frame being issued (`roi_parameter`).
1081    refinement: Option<crate::ledger::Refinement>,
1082    roi: Vec<VAEncROI>,
1083    /// Whether the driver caps each coded frame at the size a constant-rate session names.
1084    frame_cap: bool,
1085    /// The bytes of the last coded frame, padding aside.
1086    last_bytes: Option<usize>,
1087    /// The rate the next frame is coded at, a cleanup frame's (`cap`), where it differs from the session's.
1088    cleanup_bps: Option<u32>,
1089    /// The rate the driver was last told, by a sequence or a cleanup frame.
1090    sent_bps: u32,
1091    /// The quality index of the average quantizer the driver reported for it.
1092    last_quality: Option<u32>,
1093    /// Whether the next frame is measured, the last measurement (`last_psnr`), and whether
1094    /// the driver's surfaces have been readable so far (`measures`).
1095    measure: bool,
1096    last_psnr: Option<f32>,
1097    measures: bool,
1098    /// The quality level asked of the driver: the highest it takes, its fastest, where libva's
1099    /// level 1 is the best quality and the slowest; None where it reports none.
1100    quality_level: Option<u32>,
1101    /// Whether the next frame opens a sequence: a key frame carrying the sequence parameters
1102    /// and the rate control.
1103    sequence_start: bool,
1104    /// The level the last sequence named (`Arm::level`), which a constant-rate target taken
1105    /// with a picture has to stay within.
1106    declared_level: u32,
1107    omit_headers: bool,
1108    fresh: bool,
1109    /// The reorder bound the H.264 stream is held to. A driver may write its own sequence
1110    /// parameter set in place of the session's (radeonsi's VCE did before Mesa 25.0, declaring no
1111    /// restriction or a depth of three), and the session submits every picture in the order it
1112    /// is shown, so the bound is written whatever the driver's picture order count.
1113    reorder: NoReorder,
1114}
1115
1116unsafe impl Send for VaapiEncoder {}
1117
1118impl Drop for VaapiEncoder {
1119    fn drop(&mut self) {
1120        let api = self.device.api;
1121        let display = self.device.display;
1122        unsafe {
1123            if self.coded != VA_INVALID_ID {
1124                (api.vaDestroyBuffer)(display, self.coded);
1125            }
1126            if let Some(host) = &self.host
1127                && let Some(image) = host.image
1128            {
1129                (api.vaDestroyImage)(display, image.image_id);
1130            }
1131            if self.vpp_context != VA_INVALID_ID {
1132                (api.vaDestroyContext)(display, self.vpp_context);
1133            }
1134            if self.context != VA_INVALID_ID {
1135                (api.vaDestroyContext)(display, self.context);
1136            }
1137            let mut surfaces = self.recon.clone();
1138            surfaces.extend(&self.converted);
1139            surfaces.extend(self.host.iter().map(|h| h.surface));
1140            surfaces.extend(self.imports.iter().map(|i| i.2));
1141            self.device.destroy_surfaces(&mut surfaces);
1142            if self.vpp_config != VA_INVALID_ID {
1143                (api.vaDestroyConfig)(display, self.vpp_config);
1144            }
1145            if self.config != VA_INVALID_ID {
1146                (api.vaDestroyConfig)(display, self.config);
1147            }
1148        }
1149    }
1150}
1151
1152impl VaapiEncoder {
1153    /// Stand up a session for `codec` on the render node the settings select, fed through
1154    /// `input`, with the libva of this process.
1155    pub fn new(settings: &CaptureSettings, codec: Codec, input: Input) -> Result<Self, String> {
1156        if !codec.is_video() {
1157            return Err("JPEG has no VA-API encoder".into());
1158        }
1159        Self::on_device(
1160            Arc::new(Device::open(&libva()?, settings.encode_node_index)?),
1161            settings,
1162            codec,
1163            input,
1164        )
1165    }
1166
1167    /// `new` on an open device.
1168    pub(crate) fn on_device(
1169        device: Arc<Device>,
1170        settings: &CaptureSettings,
1171        codec: Codec,
1172        input: Input,
1173    ) -> Result<Self, String> {
1174        let fullcolor = settings.video_fullcolor && codec.fullcolor();
1175        let depths: &[u32] = if settings.video_bit_depth >= 10 && codec.high_bit_depth() {
1176            &[10, 8]
1177        } else {
1178            &[8]
1179        };
1180        let listed = device.profiles()?;
1181        let mut last = None;
1182        for &bit_depth in depths {
1183            let served = profile_ladder(codec, fullcolor, bit_depth)
1184                .into_iter()
1185                .filter(|p| listed.contains(p))
1186                .map(|p| (p, device.encode_entrypoints(p)))
1187                .find(|(_, entrypoints)| !entrypoints.is_empty());
1188            let Some((profile, mut entrypoints)) = served else {
1189                last.get_or_insert_with(|| {
1190                    format!(
1191                        "this VA-API driver encodes no {} {} at {bit_depth} bits",
1192                        codec.display(),
1193                        super::chroma_name(fullcolor)
1194                    )
1195                });
1196                continue;
1197            };
1198            if codec == Codec::H264 && device.whole_picture_vdenc {
1199                entrypoints.sort_by_key(|&e| e == VAEntrypointEncSliceLP);
1200                crate::log::debug!(
1201                    "[vaapi] This device's low-power H.264 encoder codes one slice a picture: {}",
1202                    if entrypoints[0] == VAEntrypointEncSliceLP {
1203                        "the driver offers no full entry point, so the session codes one slice."
1204                    } else {
1205                        "H.264 tries the full entry point first."
1206                    }
1207                );
1208            }
1209            for entrypoint in entrypoints {
1210                for &fourcc in surface_fourccs(fullcolor, bit_depth) {
1211                    match Self::open(
1212                        &device, settings, codec, input, profile, entrypoint, fourcc, fullcolor,
1213                        bit_depth,
1214                    ) {
1215                        Ok(session) => return Ok(session),
1216                        Err(e) => last = Some(e),
1217                    }
1218                }
1219            }
1220        }
1221        Err(last.unwrap_or_else(|| "no VA-API surface format to try".into()))
1222    }
1223
1224    /// One bring-up on `fourcc` surfaces: the encode configuration and its attributes, the
1225    /// surfaces, the encode and processing contexts, and the codec arm.
1226    #[allow(clippy::too_many_arguments)]
1227    fn open(
1228        device: &Arc<Device>,
1229        settings: &CaptureSettings,
1230        codec: Codec,
1231        input: Input,
1232        profile: VAProfile,
1233        entrypoint: VAEntrypoint,
1234        fourcc: u32,
1235        fullcolor: bool,
1236        bit_depth: u32,
1237    ) -> Result<Self, String> {
1238        let api = device.api;
1239        let rate = RateSettings::new(settings).uncapped();
1240        let rc_mode = if rate.cbr { VA_RC_CBR } else { VA_RC_CQP };
1241        let rt_format = rt_format(fullcolor, bit_depth);
1242        let width = settings.width.max(1) as u32;
1243        let height = settings.height.max(1) as u32;
1244        let fps = rate.fps;
1245        let bits_per_second = if rate.cbr {
1246            rate.bps().min(u64::from(u32::MAX)) as u32
1247        } else {
1248            0
1249        };
1250
1251        let mut attribs = Vec::new();
1252        if let Some(formats) = device.attribute(profile, entrypoint, VAConfigAttribRTFormat) {
1253            if formats & rt_format == 0 {
1254                return Err(format!(
1255                    "the {} entry point renders no {} {bit_depth}-bit surfaces",
1256                    fourcc_name(fourcc),
1257                    super::chroma_name(fullcolor)
1258                ));
1259            }
1260            attribs.push(VAConfigAttrib {
1261                type_: VAConfigAttribRTFormat,
1262                value: rt_format,
1263            });
1264        }
1265        match device.attribute(profile, entrypoint, VAConfigAttribRateControl) {
1266            Some(modes) if modes & rc_mode != 0 => attribs.push(VAConfigAttrib {
1267                type_: VAConfigAttribRateControl,
1268                value: rc_mode,
1269            }),
1270            Some(_) => {
1271                return Err(format!(
1272                    "this VA-API driver has no {} rate control for {}",
1273                    if rate.cbr {
1274                        "constant-rate"
1275                    } else {
1276                        "constant-quantizer"
1277                    },
1278                    codec.display()
1279                ));
1280            }
1281            None if rate.cbr => {
1282                return Err("this VA-API driver reports no rate control modes".into());
1283            }
1284            None => {}
1285        }
1286        let ref_l0 = device
1287            .attribute(profile, entrypoint, VAConfigAttribEncMaxRefFrames)
1288            .map_or(0, |v| v & 0xffff);
1289        if ref_l0 < 1 {
1290            return Err("this VA-API driver takes no reference frames".into());
1291        }
1292        let level_bitrate = u64::from(bits_per_second);
1293        let dpb_level = match codec {
1294            Codec::H264 => super::codec::h264_level(width, height, fps.ceil(), level_bitrate),
1295            Codec::H265 => super::codec::h265_level(width, height, fps.ceil(), level_bitrate, true),
1296            _ => 0,
1297        };
1298        let dpb = match codec {
1299            Codec::H264 => super::codec::h264_dpb_frames(dpb_level, width, height)
1300                .min(super::reference_frames(settings)),
1301            Codec::H265 => super::codec::h265_dpb_frames(dpb_level, width, height)
1302                .min(super::reference_frames(settings)),
1303            _ => REFERENCE_FRAMES,
1304        };
1305        let unit_qp_deltas = codec == Codec::H265
1306            && device.hardware() == Hardware::VaapiIntel
1307            && entrypoint == VAEntrypointEncSlice;
1308        let mut arm = match codec {
1309            Codec::H264 => Arm::H264(Box::new(h264::Arm::new(profile))),
1310            Codec::H265 => Arm::H265(h265::Arm::new(
1311                profile,
1312                device.attribute(profile, entrypoint, VAConfigAttribEncHEVCFeatures),
1313                device.attribute(profile, entrypoint, VAConfigAttribEncHEVCBlockSizes),
1314                device.attribute(profile, entrypoint, VAConfigAttribPredictionDirection),
1315                unit_qp_deltas,
1316            )),
1317            Codec::Vp8 => Arm::Vp8(vp8::Arm::new()),
1318            Codec::Vp9 => Arm::Vp9(vp9::Arm::new()),
1319            Codec::Av1 => Arm::Av1(av1::Arm::new(
1320                device.attribute(profile, entrypoint, VAConfigAttribEncAV1),
1321                device.attribute(profile, entrypoint, VAConfigAttribEncAV1Ext1),
1322                device
1323                    .attribute(profile, entrypoint, VAConfigAttribEncAV1Ext2)
1324                    .ok_or("this VA-API driver reports no AV1 encoder attributes")?,
1325            )),
1326            Codec::Jpeg => unreachable!(),
1327        };
1328        let packed = device
1329            .attribute(profile, entrypoint, VAConfigAttribEncPackedHeaders)
1330            .map_or(0, |v| v & arm.wanted_packed());
1331        if packed != 0 {
1332            attribs.push(VAConfigAttrib {
1333                type_: VAConfigAttribEncPackedHeaders,
1334                value: packed,
1335            });
1336        }
1337        let dpb = if arm.tracks_references(packed) {
1338            dpb
1339        } else {
1340            1
1341        };
1342        let quality_range = device.attribute(profile, entrypoint, VAConfigAttribEncQualityRange);
1343        let slice_caps = (
1344            device.attribute(profile, entrypoint, VAConfigAttribEncMaxSlices),
1345            device.attribute(profile, entrypoint, VAConfigAttribEncSliceStructure),
1346        );
1347
1348        let mut config = VA_INVALID_ID;
1349        device.check(
1350            unsafe {
1351                (api.vaCreateConfig)(
1352                    device.display,
1353                    profile,
1354                    entrypoint,
1355                    attribs.as_mut_ptr(),
1356                    attribs.len() as c_int,
1357                    &mut config,
1358                )
1359            },
1360            &format!(
1361                "no VA-API encode configuration for {} on {} surfaces",
1362                codec.display(),
1363                fourcc_name(fourcc)
1364            ),
1365        )?;
1366        let mut me = Self {
1367            codec,
1368            input,
1369            device: Arc::clone(device),
1370            config,
1371            context: VA_INVALID_ID,
1372            vpp_config: VA_INVALID_ID,
1373            vpp_context: VA_INVALID_ID,
1374            vpp_standards: (VAProcColorStandardNone, VAProcColorStandardNone),
1375            negotiated: Negotiated {
1376                profile,
1377                entrypoint,
1378                packed,
1379                rc_mode,
1380                width,
1381                height,
1382                fps,
1383                bits_per_second,
1384                dpb,
1385                dpb_level,
1386                fullcolor,
1387                bit_depth,
1388                min_qp: codec.hardware_quantizer_bound(device.hardware(), rate.min_qp),
1389                max_qp: codec.hardware_quantizer_bound(device.hardware(), rate.max_qp),
1390                qp_floor: if codec == Codec::H265
1391                    && device.hardware() == Hardware::VaapiIntel
1392                    && entrypoint == VAEntrypointEncSliceLP
1393                {
1394                    h265::INTEL_LOW_POWER_MIN_QP
1395                } else {
1396                    1
1397                },
1398                unit_qp_deltas,
1399                h264_driver_level: device.h264_driver_level(),
1400                roi_regions: roi_regions(
1401                    device.attribute(profile, entrypoint, VAConfigAttribEncROI),
1402                    device.hardware(),
1403                    entrypoint,
1404                    rate.cbr,
1405                ),
1406            },
1407            fourcc,
1408            rt_format,
1409            surface_width: 0,
1410            surface_height: 0,
1411            low_power: entrypoint == VAEntrypointEncSliceLP,
1412            recon: Vec::new(),
1413            converted: Vec::new(),
1414            host: None,
1415            coded: VA_INVALID_ID,
1416            arm: Arm::Vp8(vp8::Arm::new()),
1417            references: None,
1418            frame_count: 0,
1419            key_count: 0,
1420            surfaces_of: HashMap::new(),
1421            imports: Vec::new(),
1422            last_reference: Reference::Untracked,
1423            rate,
1424            qp: codec.hardware_quantizer(device.hardware(), settings.video_crf),
1425            held: None,
1426            refinement: None,
1427            roi: Vec::new(),
1428            frame_cap: false,
1429            last_bytes: None,
1430            cleanup_bps: None,
1431            sent_bps: 0,
1432            last_quality: None,
1433            measure: false,
1434            last_psnr: None,
1435            measures: true,
1436            quality_level: None,
1437            sequence_start: true,
1438            declared_level: 0,
1439            omit_headers: settings.omit_stripe_headers,
1440            fresh: true,
1441            reorder: NoReorder::new("The VA-API driver", true),
1442        };
1443        let (align_w, align_h) = {
1444            let (aw, ah) = arm.alignment();
1445            let (dw, dh) = device.surface_alignment(config);
1446            (aw.max(dw), ah.max(dh))
1447        };
1448        me.surface_width = width.div_ceil(align_w) * align_w;
1449        me.surface_height = height.div_ceil(align_h) * align_h;
1450        let rendered = device.surface_fourccs(config);
1451        if !rendered.is_empty() && !rendered.contains(&fourcc) {
1452            return Err(format!(
1453                "the {} encoder takes no {} surfaces",
1454                codec.display(),
1455                fourcc_name(fourcc)
1456            ));
1457        }
1458        let wanted_slices = if matches!(arm, Arm::H264(_)) && device.one_slice_h264(entrypoint) {
1459            1
1460        } else {
1461            SLICES
1462        };
1463        let slices = match (&arm, slice_caps) {
1464            (Arm::H264(_) | Arm::H265(_), (Some(max), Some(structure))) => Some(slice_layout(
1465                structure,
1466                max,
1467                me.surface_height.div_ceil(arm_block(&arm)),
1468                wanted_slices,
1469            )?),
1470            (Arm::H264(_) | Arm::H265(_), _) => {
1471                Some((1, me.surface_height.div_ceil(arm_block(&arm))))
1472            }
1473            _ => None,
1474        };
1475        match &mut arm {
1476            Arm::H264(a) => a.configure(
1477                &me.negotiated,
1478                me.surface_width,
1479                me.surface_height,
1480                slices.unwrap(),
1481            ),
1482            Arm::H265(a) => a.configure(
1483                &me.negotiated,
1484                me.surface_width,
1485                me.surface_height,
1486                slices.unwrap(),
1487            ),
1488            Arm::Vp8(a) => a.configure(&me.negotiated),
1489            Arm::Vp9(a) => a.configure(&me.negotiated),
1490            Arm::Av1(a) => a.configure(&me.negotiated, me.surface_width, me.surface_height)?,
1491        }
1492        me.arm = arm;
1493        me.references = me.arm.tracks_references(packed).then(|| match me.arm {
1494            Arm::Vp8(_) => References::Slots(ReferenceSlots::new()),
1495            Arm::H264(_) if me.device.keeps_anchors() && dpb >= 3 => {
1496                let mut w = ReferenceWindow::with_anchors(dpb, 1);
1497                if settings.acknowledge_references {
1498                    w.set_acknowledged();
1499                }
1500                References::Window(w)
1501            }
1502            Arm::H265(_) if me.device.keeps_anchors() && dpb as usize >= ANCHORS + 2 => {
1503                let mut w = ReferenceWindow::with_anchors(dpb, ANCHORS);
1504                if settings.acknowledge_references {
1505                    w.set_acknowledged();
1506                }
1507                References::Window(w)
1508            }
1509            _ => References::Window(ReferenceWindow::new(dpb)),
1510        });
1511        if let Some(References::Window(w)) = &mut me.references
1512            && let Arm::H264(a) = &me.arm
1513        {
1514            w.set_frame_num_range(a.frame_num_range());
1515        }
1516        me.quality_level = quality_range;
1517        me.frame_cap = device
1518            .attribute(profile, entrypoint, VAConfigAttribMaxFrameSize)
1519            .is_some_and(|v| v & 1 != 0);
1520
1521        let recon_count = match me.arm {
1522            Arm::Vp8(_) => 4,
1523            _ => dpb as usize + 1,
1524        };
1525        me.recon = device.create_surfaces(
1526            rt_format,
1527            fourcc,
1528            me.surface_width,
1529            me.surface_height,
1530            recon_count,
1531        )?;
1532        me.converted =
1533            device.create_surfaces(rt_format, fourcc, me.surface_width, me.surface_height, 1)?;
1534        let mut render_targets = me.recon.clone();
1535        render_targets.extend(&me.converted);
1536        device.check(
1537            unsafe {
1538                (api.vaCreateContext)(
1539                    device.display,
1540                    config,
1541                    me.surface_width as c_int,
1542                    me.surface_height as c_int,
1543                    VA_PROGRESSIVE as c_int,
1544                    render_targets.as_mut_ptr(),
1545                    render_targets.len() as c_int,
1546                    &mut me.context,
1547                )
1548            },
1549            "no VA-API encode context",
1550        )?;
1551        me.coded = device.create_output_buffer(
1552            me.context,
1553            VAEncCodedBufferType,
1554            coded_buffer_size(me.surface_width, me.surface_height),
1555        )?;
1556        me.open_vpp(device, input)?;
1557        Ok(me)
1558    }
1559}
1560
1561/// The block a codec's slices are counted in: a macroblock, or the coding tree unit.
1562fn arm_block(arm: &Arm) -> u32 {
1563    match arm {
1564        Arm::H265(a) => a.ctu_size(),
1565        _ => 16,
1566    }
1567}
1568
1569/// How many slices a picture of `rows` block rows is cut into and how many rows each
1570/// takes, for `wanted` slices under the driver's slice structure and at most `max_slices`:
1571/// arbitrary rows as asked, equal rows with a shorter last slice where the driver wants them
1572/// equal (iHD's H.264), a power of two of rows where that is all it takes, one row each
1573/// where it takes only that. A slice a row is 68 slices at 1080p, none predicting from the
1574/// row above and each restarting the entropy coder: on Intel Arc a scroll of text at one
1575/// quantizer cost 12.4 kB a frame so cut and 8.6 kB in four slices, at the same PSNR.
1576fn slice_layout(
1577    structure: u32,
1578    max_slices: u32,
1579    rows: u32,
1580    wanted: u32,
1581) -> Result<(u32, u32), String> {
1582    let max_slices = max_slices.max(1);
1583    let wanted = wanted.min(rows).min(max_slices).max(1);
1584    let (count, size) = if structure
1585        & (VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS | VA_ENC_SLICE_STRUCTURE_ARBITRARY_MACROBLOCKS)
1586        != 0
1587    {
1588        (wanted, rows / wanted)
1589    } else if structure & VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS != 0 {
1590        let mut k = 1;
1591        while (wanted > 1 && 2 * k * (wanted - 1) + 1 < rows) || rows.div_ceil(k) > max_slices {
1592            k *= 2;
1593        }
1594        (rows.div_ceil(k), k)
1595    } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS != 0 {
1596        let size = rows.div_ceil(wanted);
1597        (rows.div_ceil(size), size)
1598    } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS != 0 {
1599        (rows, 1)
1600    } else {
1601        return Err(format!(
1602            "this VA-API driver supports no usable slice structure ({structure:#x})"
1603        ));
1604    };
1605    if count > max_slices {
1606        return Err(format!(
1607            "this VA-API driver encodes at most {max_slices} slices, not {count}"
1608        ));
1609    }
1610    Ok((count, size))
1611}
1612
1613impl VaapiEncoder {
1614    /// The video processor: a configuration and a context on the converted surface, and the
1615    /// color standards it is told to convert between, chosen the way libavfilter's `scale_vaapi`
1616    /// chooses them: explicit color properties where the driver takes them, else the standard
1617    /// nearest to the matrix and range asked for.
1618    fn open_vpp(&mut self, device: &Device, input: Input) -> Result<(), String> {
1619        let api = device.api;
1620        device.check(
1621            unsafe {
1622                (api.vaCreateConfig)(
1623                    device.display,
1624                    VAProfileNone,
1625                    VAEntrypointVideoProc,
1626                    ptr::null_mut(),
1627                    0,
1628                    &mut self.vpp_config,
1629                )
1630            },
1631            "no VA-API video processing configuration",
1632        )?;
1633        let rendered = device.surface_fourccs(self.vpp_config);
1634        if !rendered.is_empty() && !rendered.contains(&self.fourcc) {
1635            return Err(format!(
1636                "this VA-API driver's video processor renders no {} surfaces",
1637                fourcc_name(self.fourcc)
1638            ));
1639        }
1640        device.check(
1641            unsafe {
1642                (api.vaCreateContext)(
1643                    device.display,
1644                    self.vpp_config,
1645                    self.surface_width as c_int,
1646                    self.surface_height as c_int,
1647                    VA_PROGRESSIVE as c_int,
1648                    self.converted.as_mut_ptr(),
1649                    self.converted.len() as c_int,
1650                    &mut self.vpp_context,
1651                )
1652            },
1653            "no VA-API video processing context",
1654        )?;
1655        let mut caps: VAProcPipelineCaps = unsafe { std::mem::zeroed() };
1656        device.check(
1657            unsafe {
1658                (api.vaQueryVideoProcPipelineCaps)(
1659                    device.display,
1660                    self.vpp_context,
1661                    ptr::null_mut(),
1662                    0,
1663                    &mut caps,
1664                )
1665            },
1666            "vaQueryVideoProcPipelineCaps",
1667        )?;
1668        let standards =
1669            |list: *mut VAProcColorStandardType, count: u32| -> Vec<VAProcColorStandardType> {
1670                if list.is_null() {
1671                    Vec::new()
1672                } else {
1673                    unsafe { std::slice::from_raw_parts(list, count as usize) }.to_vec()
1674                }
1675            };
1676        let input_standards = standards(caps.input_color_standards, caps.num_input_color_standards);
1677        let output_standards =
1678            standards(caps.output_color_standards, caps.num_output_color_standards);
1679        self.vpp_standards = (
1680            color_standard(&input_standards, ColorDescription::SRGB_SOURCE),
1681            color_standard(&output_standards, self.declared_color()),
1682        );
1683        if let Input::Host { rgba } = input {
1684            let fourcc = if rgba { VA_FOURCC_RGBA } else { VA_FOURCC_BGRA };
1685            let surface = device.create_surfaces(
1686                VA_RT_FORMAT_RGB32,
1687                fourcc,
1688                self.negotiated.width,
1689                self.negotiated.height,
1690                1,
1691            )?[0];
1692            let mut derived: VAImage = unsafe { std::mem::zeroed() };
1693            let derive = unsafe { (api.vaDeriveImage)(device.display, surface, &mut derived) }
1694                == VA_STATUS_SUCCESS as VAStatus
1695                && {
1696                    let same = derived.format.fourcc == fourcc;
1697                    unsafe { (api.vaDestroyImage)(device.display, derived.image_id) };
1698                    same
1699                };
1700            let host = self.host.insert(HostUpload {
1701                surface,
1702                fourcc,
1703                derive,
1704                image: None,
1705            });
1706            if !derive {
1707                let mut format = image_format(device, fourcc).ok_or_else(|| {
1708                    format!(
1709                        "this VA-API driver has no {} image format",
1710                        fourcc_name(fourcc)
1711                    )
1712                })?;
1713                let mut image: VAImage = unsafe { std::mem::zeroed() };
1714                device.check(
1715                    unsafe {
1716                        (api.vaCreateImage)(
1717                            device.display,
1718                            &mut format,
1719                            self.negotiated.width as c_int,
1720                            self.negotiated.height as c_int,
1721                            &mut image,
1722                        )
1723                    },
1724                    "vaCreateImage",
1725                )?;
1726                host.image = Some(image);
1727            }
1728        }
1729        Ok(())
1730    }
1731
1732    /// The color the session declares: BT.709, whose primaries and transfer the sRGB desktop
1733    /// source already carries, at limited range; VP8 is held to BT.601, the only matrix its
1734    /// keyframe header's one color-space bit can name.
1735    fn declared_color(&self) -> ColorDescription {
1736        ColorDescription {
1737            primaries: 1,
1738            transfer: 1,
1739            matrix: if self.codec == Codec::Vp8 { 6 } else { 1 },
1740            full_range: false,
1741            rgb: false,
1742        }
1743    }
1744
1745    pub fn codec(&self) -> Codec {
1746        self.codec
1747    }
1748
1749    /// Whether this session negotiated 4:4:4 chroma.
1750    pub fn is_fullcolor(&self) -> bool {
1751        self.negotiated.fullcolor
1752    }
1753
1754    /// The bits per sample the session opened at.
1755    pub fn bit_depth(&self) -> u32 {
1756        self.negotiated.bit_depth
1757    }
1758
1759    /// The name of the surface format frames reach the codec in, for the session log.
1760    pub fn surface_format_name(&self) -> String {
1761        fourcc_name(self.fourcc)
1762    }
1763
1764    /// A hardware session converts at limited range.
1765    pub fn is_full_range(&self) -> bool {
1766        false
1767    }
1768
1769    /// The driver behind the session, as libva names it.
1770    pub fn vendor(&self) -> &str {
1771        &self.device.vendor
1772    }
1773
1774    /// Whether the codec runs on the low-power entry point.
1775    pub fn low_power(&self) -> bool {
1776        self.low_power
1777    }
1778
1779    /// The frame the last encoded frame predicted from.
1780    pub fn last_reference(&self) -> Reference {
1781        self.last_reference
1782    }
1783
1784    /// Apply what the consumers say of a frame: a loss as `invalidate_reference`, a frame every
1785    /// one of them holds or was sent (`ReferenceWindow::acknowledge`, `ReferenceSlots::acknowledge`).
1786    pub fn take_report(&mut self, report: ReferenceReport) -> bool {
1787        let (frame_id, held) = match report {
1788            ReferenceReport::Lost(frame_id) | ReferenceReport::Withheld(frame_id) => {
1789                return self.invalidate_reference(frame_id);
1790            }
1791            ReferenceReport::Held(frame_id) => (frame_id, true),
1792            ReferenceReport::Sent(frame_id) => (frame_id, false),
1793        };
1794        match &mut self.references {
1795            Some(References::Window(w)) => {
1796                w.acknowledge(frame_id, held);
1797            }
1798            Some(References::Slots(s)) => {
1799                s.acknowledge(frame_id, held);
1800            }
1801            None => {}
1802        }
1803        true
1804    }
1805
1806    /// Leave frame `frame_id` and every frame after it out of the predictions. False where the
1807    /// session tracks none, and the caller codes a key frame instead.
1808    pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
1809        match &mut self.references {
1810            Some(References::Window(w)) => {
1811                w.invalidate(frame_id);
1812                true
1813            }
1814            Some(References::Slots(s)) => {
1815                s.invalidate(frame_id);
1816                true
1817            }
1818            None => false,
1819        }
1820    }
1821
1822    /// Take a rate or frame-rate change. A constant-rate target that moved alone goes out with
1823    /// the next picture's rate control, as a still screen's cleanup rate does (`encode`), so
1824    /// congestion control steering the target costs no key frame. Any other change, or a target
1825    /// the declared level does not admit, opens a sequence with the new rate control, as a key
1826    /// frame: the decoder learns a level only from a sequence header.
1827    pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> Result<(), String> {
1828        let Some(rate) = self.rate.changed(settings) else {
1829            return Ok(());
1830        };
1831        let target_only = rate.cbr
1832            && RateSettings {
1833                bitrate_kbps: rate.bitrate_kbps,
1834                ..self.rate
1835            } == rate;
1836        self.rate = rate;
1837        self.negotiated.fps = rate.fps;
1838        self.negotiated.bits_per_second = if rate.cbr {
1839            rate.bps().min(u64::from(u32::MAX)) as u32
1840        } else {
1841            0
1842        };
1843        self.negotiated.min_qp = self
1844            .codec
1845            .hardware_quantizer_bound(self.device.hardware(), rate.min_qp);
1846        self.negotiated.max_qp = self
1847            .codec
1848            .hardware_quantizer_bound(self.device.hardware(), rate.max_qp);
1849        match &mut self.arm {
1850            Arm::H264(a) => a.configure(
1851                &self.negotiated,
1852                self.surface_width,
1853                self.surface_height,
1854                a.slices(),
1855            ),
1856            Arm::H265(a) => a.configure(
1857                &self.negotiated,
1858                self.surface_width,
1859                self.surface_height,
1860                a.slices(),
1861            ),
1862            Arm::Vp8(a) => a.configure(&self.negotiated),
1863            Arm::Vp9(a) => a.configure(&self.negotiated),
1864            Arm::Av1(a) => {
1865                a.configure(&self.negotiated, self.surface_width, self.surface_height)?
1866            }
1867        }
1868        if !target_only || self.arm.level() > self.declared_level {
1869            self.sequence_start = true;
1870        }
1871        Ok(())
1872    }
1873
1874    /// Whether the session runs a constant rate.
1875    pub fn is_cbr(&self) -> bool {
1876        self.rate.cbr
1877    }
1878
1879    /// The bytes of the last frame coded, the driver's padding aside: what tells a
1880    /// constant-rate cleanup the rate control has nothing left to refine.
1881    pub fn last_size(&self) -> Option<usize> {
1882        self.last_bytes
1883    }
1884
1885    /// The quality index the rate control last coded a frame at: for H.264 and HEVC where the
1886    /// driver reports a picture's average quantizer with its coded buffer (iHD does), for AV1
1887    /// and VP9 the `base_q_idx` it wrote into the frame header. A picture that is mostly skipped
1888    /// blocks reports near the quantizer its slices open at, so the figure is trusted for
1889    /// having reached a quality, never for the lack of it.
1890    pub fn last_quality(&self) -> Option<u32> {
1891        self.last_quality
1892    }
1893
1894    /// Whether `measure` yields a measurement: until a surface turns out unreadable, as the
1895    /// reconstruction of iHD's packed 4:4:4 does, kept at another geometry than its source.
1896    pub fn measures(&self) -> bool {
1897        self.measures
1898    }
1899
1900    /// Measure the next frame's reconstruction against its source (`last_psnr`).
1901    pub fn measure(&mut self) {
1902        self.measure = true;
1903    }
1904
1905    /// The luma PSNR, in dB, of the last frame measured: its reconstruction against the
1906    /// converted picture it was coded from, both read back on every `PSNR_ROW_STEP`th row.
1907    /// What tells a constant-rate cleanup whether the driver's rate control is still refining
1908    /// a still screen, whatever the driver says of its quantizer; None where a surface cannot
1909    /// be read as the picture's rows.
1910    pub fn last_psnr(&self) -> Option<f32> {
1911        self.last_psnr
1912    }
1913
1914    /// The sampled luma rows of `surface`, as 8-bit samples (the high eight bits of a 10-bit
1915    /// one), with the format and the rows the driver keeps it in.
1916    fn luma_rows(&self, surface: VASurfaceID) -> Option<(u32, u16, Vec<u8>)> {
1917        let api = self.device.api;
1918        let display = self.device.display;
1919        let mut image: VAImage = unsafe { std::mem::zeroed() };
1920        if unsafe { (api.vaDeriveImage)(display, surface, &mut image) }
1921            != VA_STATUS_SUCCESS as VAStatus
1922        {
1923            return None;
1924        }
1925        let fourcc = image.format.fourcc;
1926        let (bytes, luma) = match fourcc {
1927            VA_FOURCC_NV12 | VA_FOURCC_444P => (1, 0),
1928            VA_FOURCC_P010 => (2, 1),
1929            VA_FOURCC_XYUV | VA_FOURCC_Y410 => (4, 2),
1930            _ => (0, 0),
1931        };
1932        let mut address: *mut c_void = ptr::null_mut();
1933        let mut rows = None;
1934        if bytes > 0
1935            && u32::from(image.height) >= self.negotiated.height
1936            && unsafe { (api.vaMapBuffer)(display, image.buf, &mut address) }
1937                == VA_STATUS_SUCCESS as VAStatus
1938        {
1939            let width = (self.negotiated.width as usize).min(image.width as usize);
1940            let height = self.negotiated.height as usize;
1941            let (offset, pitch) = (image.offsets[0] as usize, image.pitches[0] as usize);
1942            let mut out = Vec::with_capacity(width * height.div_ceil(PSNR_ROW_STEP));
1943            for y in (0..height).step_by(PSNR_ROW_STEP) {
1944                let row = unsafe {
1945                    std::slice::from_raw_parts(
1946                        (address as *const u8).add(offset + y * pitch),
1947                        width * bytes,
1948                    )
1949                };
1950                if fourcc == VA_FOURCC_Y410 {
1951                    out.extend(
1952                        row.as_chunks::<4>()
1953                            .0
1954                            .iter()
1955                            .map(|p| ((u32::from_le_bytes(*p) >> 12) & 0xff) as u8),
1956                    );
1957                } else {
1958                    out.extend(row.iter().skip(luma).step_by(bytes));
1959                }
1960            }
1961            unsafe { (api.vaUnmapBuffer)(display, image.buf) };
1962            rows = Some((fourcc, image.height, out));
1963        }
1964        unsafe { (api.vaDestroyImage)(display, image.image_id) };
1965        rows
1966    }
1967
1968    /// The luma PSNR of `recon` against the converted picture, over the sampled rows; None
1969    /// where the driver keeps the reconstruction in another layout than the source, which
1970    /// would read as noise: iHD's of 10-bit HEVC and VP9 reports `P016` for `p010`, its packed
1971    /// 4:4:4 one fewer rows, and either measured under `UNREADABLE_DB`.
1972    fn picture_psnr(&self, recon: VASurfaceID) -> Option<f32> {
1973        let (format, rows, source) = self.luma_rows(self.converted[0])?;
1974        let (coded_format, coded_rows, coded) = self.luma_rows(recon)?;
1975        if (format, rows) != (coded_format, coded_rows) || source.is_empty() {
1976            return None;
1977        }
1978        let sse: u64 = source
1979            .iter()
1980            .zip(&coded)
1981            .map(|(&a, &b)| {
1982                let d = i64::from(a) - i64::from(b);
1983                (d * d) as u64
1984            })
1985            .sum();
1986        let mse = (sse as f64 / source.len() as f64).max(1e-3);
1987        Some((10.0 * (255.0 * 255.0 / mse).log10()) as f32).filter(|&db| db >= UNREADABLE_DB)
1988    }
1989
1990    /// How this driver's rate control is bounded (`ConstantRate`).
1991    fn constant_rate(&self) -> ConstantRate {
1992        if !self.device.starves_in_a_small_buffer() {
1993            return ConstantRate::Buffer;
1994        }
1995        match self.arm {
1996            Arm::H264(_) | Arm::H265(_) => ConstantRate::Unbounded,
1997            Arm::Av1(_) => ConstantRate::Seconds(IHD_AV1_BUFFER_S),
1998            Arm::Vp8(_) | Arm::Vp9(_) => ConstantRate::Buffer,
1999        }
2000    }
2001
2002    /// Whether a constant-rate cleanup ends on a key frame held at the paint-over quantizer
2003    /// (`FrameEncoder::ends_cleanup_on_key`): where the driver says no quantizer of its H.264 or
2004    /// HEVC frames (radeonsi) and the session's frame budget is under
2005    /// `REFINE_BITS_PER_PIXEL` (`refines`).
2006    pub fn ends_cleanup_on_key(&self) -> bool {
2007        self.rate.cbr
2008            && matches!(self.arm, Arm::H264(_) | Arm::H265(_))
2009            && self.last_bytes.is_some()
2010            && self.last_quality.is_none()
2011            && !refines(
2012                self.negotiated.bits_per_second,
2013                self.negotiated.fps.fps(),
2014                self.negotiated.width,
2015                self.negotiated.height,
2016            )
2017    }
2018
2019    /// Whether a still screen's cleanup frame is coded at the rate its cap runs at (`cap`): at
2020    /// a constant rate, where the driver takes a new target with a picture.
2021    pub fn follows_cap(&self) -> bool {
2022        self.rate.cbr
2023    }
2024
2025    /// Code the next frame, a still screen's cleanup, at the rate that spends `bytes` in
2026    /// `pipeline::CLEANUP_FRAME_S`, and its buffer as long as the session's
2027    /// (`rate_control_at`); the frame after goes back to the session's rate. The session's own
2028    /// rate holds a 1080p page at 60 fps 0.5 Mbit/s to frames of 1 kB, which refine nothing
2029    /// (18 dB), where a cleanup at a 2 Mbit/s path's rate reaches 52 dB.
2030    pub fn cap(&mut self, bytes: usize) {
2031        if self.rate.cbr {
2032            let bps = (bytes as f64 * 8.0 / crate::pipeline::CLEANUP_FRAME_S)
2033                .min(f64::from(u32::MAX)) as u32;
2034            self.cleanup_bps = Some(bps.max(self.negotiated.bits_per_second));
2035        }
2036    }
2037
2038    /// Encode the next frame at the quantizer the quality index `crf` selects, and leave the
2039    /// session's own quantizer for the frame after: the cleanup of a still screen, at a constant
2040    /// quantizer. A constant-rate session codes the frame under its rate control: radeonsi drops
2041    /// per-frame quantizer bounds (a key frame held by them came out starved in the session's
2042    /// small buffer, 26 dB), and a key frame given a budget of its own through a restarted rate
2043    /// control came out coarser than the picture the driver's rate control refines a still
2044    /// screen to by itself (51 against 61 dB at 8 Mbit/s), so `FrameEncoder::holds_quantizer`
2045    /// says no there. A key frame held there, the cleanup of a session whose rate control
2046    /// cannot refine (`ends_cleanup_on_key`), is coded with the budget `held_key_bits` gives it,
2047    /// sent with the picture.
2048    /// How the session refines blocks of a frame of its rate control's (`refine_blocks`): an
2049    /// H.264 session whose driver takes regions of interest with a quantizer delta, one region a
2050    /// run of blocks along a row (`Negotiated::roi_regions`), each at most `REFINE_REACH` steps
2051    /// off the frame's quality. HEVC takes none: on Intel's iHD (N97) refined regions took 1080p
2052    /// text beside a video from 41.4 dB to 25.4 at 8 Mbit/s and the video from 34.7 to 30.3, and
2053    /// from 47.6 to 44.5 at a constant quantizer, where the driver's HEVC path carries the
2054    /// regions as a delta map it lays out wrong.
2055    pub fn refines(&self) -> Option<crate::ledger::Refiner> {
2056        (self.codec == Codec::H264 && self.negotiated.roi_regions > 0).then_some(
2057            crate::ledger::Refiner {
2058                finer: REFINE_FINER,
2059                reach: REFINE_REACH,
2060                runs: self.negotiated.roi_regions as usize,
2061            },
2062        )
2063    }
2064
2065    /// Code the blocks `refinement` names in the next frame of the rate control's at its target
2066    /// quality, as regions of interest (`roi_parameter`).
2067    pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
2068        self.refinement = self.refines().map(|_| refinement.clone());
2069    }
2070
2071    /// The regions of interest of `refinement` into `out`: a rectangle a run of its blocks
2072    /// along a row, each at the quantizer delta from the rate control's last to the target,
2073    /// within `REFINE_REACH`. The driver reads the rectangles through the parameter's pointer
2074    /// when the picture is issued, so they live in the session until then.
2075    fn roi_parameter(&mut self, refinement: &crate::ledger::Refinement, out: &mut Buffers) {
2076        use crate::ledger::BLOCK;
2077        let hw = |index: i32| self.codec.hardware_quantizer(self.device.hardware(), index) as i32;
2078        let delta =
2079            (hw(refinement.target) - hw(refinement.from)).clamp(-(REFINE_REACH as i32), 0) as i8;
2080        let plan = &refinement.plan;
2081        let (width, height) = (self.negotiated.width as i32, self.negotiated.height as i32);
2082        self.roi.clear();
2083        for row in 0..plan.rows {
2084            let mut col = 0;
2085            while col < plan.cols {
2086                if !plan.refines(col, row) {
2087                    col += 1;
2088                    continue;
2089                }
2090                let start = col;
2091                while col < plan.cols && plan.refines(col, row) {
2092                    col += 1;
2093                }
2094                let (x, y) = (start as i32 * BLOCK, row as i32 * BLOCK);
2095                self.roi.push(VAEncROI {
2096                    roi_rectangle: VARectangle {
2097                        x: x as i16,
2098                        y: y as i16,
2099                        width: ((col as i32 * BLOCK).min(width) - x) as u16,
2100                        height: (BLOCK.min(height - y)) as u16,
2101                    },
2102                    roi_value: delta,
2103                });
2104            }
2105        }
2106        self.roi.truncate(self.negotiated.roi_regions as usize);
2107        let mut parameter: VAEncMiscParameterBufferROI = unsafe { std::mem::zeroed() };
2108        parameter.num_roi = self.roi.len() as u32;
2109        parameter.max_delta_qp = 51;
2110        parameter.min_delta_qp = -51;
2111        parameter.roi = self.roi.as_mut_ptr();
2112        unsafe { parameter.roi_flags.bits.set_roi_value_is_qp_delta(1) };
2113        out.push_misc(VAEncMiscParameterTypeROI, &parameter);
2114    }
2115
2116    pub fn hold_quantizer(&mut self, crf: u32) {
2117        self.held = Some(
2118            self.codec
2119                .hardware_quantizer(self.device.hardware(), crf as i32),
2120        );
2121    }
2122
2123    /// The rate control of a sequence: the target, buffer, and frame rate a constant-rate
2124    /// session holds, with no filler data up to the target and each frame capped at the buffer
2125    /// where the driver takes a cap and codes under one (`ConstantRate`), and the frame rate
2126    /// and quality level of any.
2127    fn rate_control_at(&self, out: &mut Buffers, bps: u32) {
2128        if self.rate.cbr {
2129            // The session's buffer spans the same time at a cleanup frame's rate.
2130            let vbv = (u64::from(self.rate.vbv()) * u64::from(bps)
2131                / u64::from(self.negotiated.bits_per_second.max(1)))
2132            .min(u64::from(u32::MAX)) as u32;
2133            let mut rc: VAEncMiscParameterRateControl = unsafe { std::mem::zeroed() };
2134            rc.bits_per_second = bps;
2135            rc.target_percentage = 100;
2136            rc.window_size = (u64::from(vbv) * 1000 / u64::from(bps.max(1))) as u32;
2137            rc.initial_qp = 0;
2138            rc.min_qp = if matches!(self.arm, Arm::H264(_)) {
2139                self.negotiated.min_qp.max(h264::MIN_QP)
2140            } else {
2141                self.negotiated.min_qp
2142            };
2143            // Intel's full encoder takes 0 for no bound, under which a coding unit can name a
2144            // quantizer past 51; its low-power one reads 0 as 51 itself.
2145            rc.max_qp = if self.negotiated.unit_qp_deltas && self.negotiated.max_qp == 0 {
2146                51
2147            } else {
2148                self.negotiated.max_qp
2149            };
2150            rc.basic_unit_size = 0;
2151            rc.ICQ_quality_factor = 1;
2152            rc.quality_factor = 0;
2153            let bound = self.constant_rate();
2154            unsafe {
2155                rc.rc_flags.bits.set_mb_rate_control(2);
2156                rc.rc_flags.bits.set_disable_bit_stuffing(1);
2157            }
2158            out.push_misc(VAEncMiscParameterTypeRateControl, &rc);
2159            let buffer = match bound {
2160                ConstantRate::Buffer => Some(vbv),
2161                ConstantRate::Seconds(s) => Some((f64::from(bps) * s) as u32),
2162                ConstantRate::Unbounded => None,
2163            };
2164            if let Some(bits) = buffer {
2165                let hrd = VAEncMiscParameterHRD {
2166                    initial_buffer_fullness: bits,
2167                    buffer_size: bits,
2168                    va_reserved: [0; 4],
2169                };
2170                out.push_misc(VAEncMiscParameterTypeHRD, &hrd);
2171            }
2172            if self.frame_cap && bound == ConstantRate::Buffer {
2173                let cap = VAEncMiscParameterBufferMaxFrameSize {
2174                    type_: VAEncMiscParameterTypeMaxFrameSize,
2175                    max_frame_size: vbv,
2176                    va_reserved: [0; 4],
2177                };
2178                out.push_misc(VAEncMiscParameterTypeMaxFrameSize, &cap);
2179            }
2180        }
2181        let fps = self.negotiated.fps.within(0xffff);
2182        let mut frame_rate: VAEncMiscParameterFrameRate = unsafe { std::mem::zeroed() };
2183        frame_rate.framerate = (fps.den << 16) | fps.num;
2184        out.push_misc(VAEncMiscParameterTypeFrameRate, &frame_rate);
2185        if let Some(level) = self.quality_level {
2186            let quality = VAEncMiscParameterBufferQualityLevel {
2187                quality_level: level,
2188                va_reserved: [0; 4],
2189            };
2190            out.push_misc(VAEncMiscParameterTypeQualityLevel, &quality);
2191        }
2192    }
2193
2194    /// Encode one packed host frame (`stride` bytes per row, in the byte order the session
2195    /// was built for) at the quality index `crf`: uploaded straight from the caller's rows
2196    /// onto the host surface and converted on the GPU.
2197    pub fn encode_host(
2198        &mut self,
2199        pixels: &[u8],
2200        stride: usize,
2201        rgba: bool,
2202        frame_number: u64,
2203        crf: u32,
2204        force_idr: bool,
2205    ) -> Result<Vec<u8>, String> {
2206        let Input::Host { rgba: built } = self.input else {
2207            return Err("this session takes dmabufs".into());
2208        };
2209        if built != rgba {
2210            return Err("this session was built for the other byte order".into());
2211        }
2212        let (width, height) = (
2213            self.negotiated.width as usize,
2214            self.negotiated.height as usize,
2215        );
2216        check_host_frame(pixels, stride, width, height)?;
2217        let host = self.host.as_ref().ok_or("no host upload surface")?;
2218        let api = self.device.api;
2219        let display = self.device.display;
2220        unsafe {
2221            let mut image: VAImage = std::mem::zeroed();
2222            if host.derive {
2223                self.device.check(
2224                    (api.vaDeriveImage)(display, host.surface, &mut image),
2225                    "vaDeriveImage",
2226                )?;
2227            } else {
2228                image = host.image.unwrap();
2229            }
2230            let mut address: *mut c_void = ptr::null_mut();
2231            let mapped = self.device.check(
2232                (api.vaMapBuffer)(display, image.buf, &mut address),
2233                "vaMapBuffer",
2234            );
2235            if let Err(e) = mapped {
2236                if host.derive {
2237                    (api.vaDestroyImage)(display, image.image_id);
2238                }
2239                return Err(e);
2240            }
2241            let pitch = image.pitches[0] as usize;
2242            let dst = (address as *mut u8).add(image.offsets[0] as usize);
2243            for row in 0..height {
2244                ptr::copy_nonoverlapping(
2245                    pixels.as_ptr().add(row * stride),
2246                    dst.add(row * pitch),
2247                    width * 4,
2248                );
2249            }
2250            (api.vaUnmapBuffer)(display, image.buf);
2251            if host.derive {
2252                (api.vaDestroyImage)(display, image.image_id);
2253            } else {
2254                self.device.check(
2255                    (api.vaPutImage)(
2256                        display,
2257                        host.surface,
2258                        image.image_id,
2259                        0,
2260                        0,
2261                        width as c_uint,
2262                        height as c_uint,
2263                        0,
2264                        0,
2265                        width as c_uint,
2266                        height as c_uint,
2267                    ),
2268                    "vaPutImage",
2269                )?;
2270            }
2271        }
2272        let surface = host.surface;
2273        self.encode_surface(surface, frame_number, crf, force_idr)
2274    }
2275
2276    /// Encode one Wayland DRM-PRIME dmabuf: imported as a VA surface in place for the frame and
2277    /// converted on the GPU.
2278    pub fn encode_dmabuf(
2279        &mut self,
2280        dmabuf: &Dmabuf,
2281        frame_number: u64,
2282        crf: u32,
2283        force_idr: bool,
2284    ) -> Result<Vec<u8>, String> {
2285        if self.input != Input::Dmabuf {
2286            return Err("this session takes host frames".into());
2287        }
2288        let surface = self.dmabuf_surface(dmabuf)?;
2289        let result = self.encode_surface(surface, frame_number, crf, force_idr);
2290        if result.is_err() {
2291            self.forget_import(surface);
2292        }
2293        result
2294    }
2295
2296    /// The VA surface over `dmabuf`, imported once and kept while its fd names the same buffer
2297    /// (`DmaBufIdentity`). A capture cycles a few buffers, and a surface made anew each frame
2298    /// costs the driver a fresh bind of the buffer into the GPU's address space on every
2299    /// submission: on a discrete Arc at 1080p and 240 fps, a quarter of the capture thread's CPU
2300    /// and 0.3 ms of each encode. The surface holds its own reference to the buffer, so a dropped
2301    /// capture buffer is released once its entry is evicted or the session ends.
2302    fn dmabuf_surface(&mut self, dmabuf: &Dmabuf) -> Result<VASurfaceID, String> {
2303        let fd = dmabuf
2304            .handles()
2305            .next()
2306            .map(|h| h.as_raw_fd())
2307            .ok_or("the dmabuf carries no object")?;
2308        let identity = DmaBufIdentity::probe(
2309            fd,
2310            u64::from(dmabuf.format().modifier),
2311            dmabuf.width(),
2312            dmabuf.height(),
2313        );
2314        if let Some(at) = self.imports.iter().position(|i| i.0 == fd) {
2315            if self.imports[at].1 == identity {
2316                return Ok(self.imports[at].2);
2317            }
2318            let (_, _, stale) = self.imports.remove(at);
2319            self.device.destroy_surfaces(&mut [stale]);
2320        }
2321        let surface = self.import_dmabuf(dmabuf)?;
2322        if self.imports.len() == DMABUF_IMPORTS {
2323            let (_, _, oldest) = self.imports.remove(0);
2324            self.device.destroy_surfaces(&mut [oldest]);
2325        }
2326        self.imports.push((fd, identity, surface));
2327        Ok(surface)
2328    }
2329
2330    /// Release the import behind `surface`, which a failed encode leaves in doubt.
2331    fn forget_import(&mut self, surface: VASurfaceID) {
2332        if let Some(at) = self.imports.iter().position(|i| i.2 == surface) {
2333            self.imports.remove(at);
2334            self.device.destroy_surfaces(&mut [surface]);
2335        }
2336    }
2337
2338    /// A VA surface over a dmabuf: through the PRIME 2 descriptor, which carries the format
2339    /// modifier, else the older external-buffer one.
2340    fn import_dmabuf(&mut self, dmabuf: &Dmabuf) -> Result<VASurfaceID, String> {
2341        let api = self.device.api;
2342        let display = self.device.display;
2343        let (width, height) = (self.negotiated.width, self.negotiated.height);
2344        // One fd a plane; the planes of one buffer (a compressed layout's metadata beside its
2345        // pixels, as radeonsi's DCC modifiers make) come as fds of the same object, which the
2346        // descriptor names once.
2347        let mut objects: Vec<(i32, (u64, u64), u32)> = Vec::new();
2348        let mut plane_object: Vec<u32> = Vec::new();
2349        for fd in dmabuf.handles().map(|h| h.as_raw_fd()) {
2350            let id = object_identity(fd).ok_or("a dmabuf plane's object cannot be read")?;
2351            let at = match objects.iter().position(|o| o.1 == id) {
2352                Some(at) => at,
2353                None => {
2354                    let size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) };
2355                    if size <= 0 {
2356                        return Err("the dmabuf object reports no size".into());
2357                    }
2358                    objects.push((fd, id, size as u32));
2359                    objects.len() - 1
2360                }
2361            };
2362            plane_object.push(at as u32);
2363        }
2364        if plane_object.is_empty() || plane_object.len() > 4 {
2365            return Err(format!(
2366                "VA-API maps a dmabuf of one to four planes, not {}",
2367                plane_object.len()
2368            ));
2369        }
2370        let drm_format = dmabuf.format().code as u32;
2371        let fourcc = match drm_format {
2372            0x34325241 => VA_FOURCC_BGRA,
2373            0x34325258 => VA_FOURCC_BGRX,
2374            0x34324241 => VA_FOURCC_RGBA,
2375            0x34324258 => VA_FOURCC_RGBX,
2376            other => return Err(format!("DRM format {other:#x} is not one VA-API maps")),
2377        };
2378        let planes: Vec<(u32, u32)> = dmabuf.strides().zip(dmabuf.offsets()).collect();
2379        let modifier = u64::from(dmabuf.format().modifier);
2380        if modifier == 0
2381            && !self.negotiated.fullcolor
2382            && self.device.rounds_linear_pitch()
2383            && !planes[0].0.is_multiple_of(64)
2384        {
2385            return Err(format!(
2386                "this VA-API driver reads a linear surface at a 64-byte pitch, not the {} of this dmabuf",
2387                planes[0].0
2388            ));
2389        }
2390        let mut surface = VA_INVALID_SURFACE;
2391        let mut attribs: [VASurfaceAttrib; 2] = unsafe { std::mem::zeroed() };
2392        attribs[0].type_ = VASurfaceAttribMemoryType;
2393        attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
2394        attribs[0].value.type_ = VAGenericValueTypeInteger;
2395        attribs[1].type_ = VASurfaceAttribExternalBufferDescriptor;
2396        attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
2397        attribs[1].value.type_ = VAGenericValueTypePointer;
2398        if modifier != 0x00ff_ffff_ffff_ffff {
2399            let mut desc: VADRMPRIMESurfaceDescriptor = unsafe { std::mem::zeroed() };
2400            desc.fourcc = fourcc;
2401            desc.width = width;
2402            desc.height = height;
2403            desc.num_objects = objects.len() as u32;
2404            for (i, &(fd, _, size)) in objects.iter().enumerate() {
2405                desc.objects[i].fd = fd;
2406                desc.objects[i].size = size;
2407                desc.objects[i].drm_format_modifier = modifier;
2408            }
2409            desc.num_layers = 1;
2410            desc.layers[0].drm_format = drm_format;
2411            desc.layers[0].num_planes = planes.len() as u32;
2412            for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2413                desc.layers[0].object_index[i] = plane_object[i];
2414                desc.layers[0].offset[i] = offset;
2415                desc.layers[0].pitch[i] = pitch;
2416            }
2417            attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 as i32;
2418            attribs[1].value.value.p = &mut desc as *mut VADRMPRIMESurfaceDescriptor as *mut c_void;
2419            let status = unsafe {
2420                (api.vaCreateSurfaces)(
2421                    display,
2422                    VA_RT_FORMAT_RGB32,
2423                    width,
2424                    height,
2425                    &mut surface,
2426                    1,
2427                    attribs.as_mut_ptr(),
2428                    2,
2429                )
2430            };
2431            if status == VA_STATUS_SUCCESS as VAStatus {
2432                return Ok(surface);
2433            }
2434        }
2435        // The older descriptor carries one object.
2436        if objects.len() != 1 {
2437            return Err("this VA-API driver maps no dmabuf of several objects".into());
2438        }
2439        let (fd, _, size) = objects[0];
2440        let mut handle = fd as usize;
2441        let mut desc: VASurfaceAttribExternalBuffers = unsafe { std::mem::zeroed() };
2442        desc.pixel_format = fourcc;
2443        desc.width = width;
2444        desc.height = height;
2445        desc.data_size = size;
2446        desc.buffers = &mut handle;
2447        desc.num_buffers = 1;
2448        desc.num_planes = planes.len() as u32;
2449        for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2450            desc.pitches[i] = pitch;
2451            desc.offsets[i] = offset;
2452        }
2453        attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME as i32;
2454        attribs[1].value.value.p = &mut desc as *mut VASurfaceAttribExternalBuffers as *mut c_void;
2455        self.device.check(
2456            unsafe {
2457                (api.vaCreateSurfaces)(
2458                    display,
2459                    VA_RT_FORMAT_RGB32,
2460                    width,
2461                    height,
2462                    &mut surface,
2463                    1,
2464                    attribs.as_mut_ptr(),
2465                    2,
2466                )
2467            },
2468            "no VA surface over the dmabuf",
2469        )?;
2470        Ok(surface)
2471    }
2472
2473    /// Convert `source` onto the encoder's surface: BT.709 primaries and transfer in, full
2474    /// range, the declared matrix out at limited range, chroma sited at the center of each
2475    /// block, which is where the software convert puts it. The picture lands unscaled at the
2476    /// origin of the aligned surface, whose margin past it the stream crops.
2477    fn convert(&mut self, source: VASurfaceID) -> Result<(), String> {
2478        let api = self.device.api;
2479        let display = self.device.display;
2480        let region = VARectangle {
2481            x: 0,
2482            y: 0,
2483            width: self.negotiated.width as u16,
2484            height: self.negotiated.height as u16,
2485        };
2486        let declared = self.declared_color();
2487        let mut params: VAProcPipelineParameterBuffer = unsafe { std::mem::zeroed() };
2488        params.surface = source;
2489        params.surface_region = &region;
2490        params.output_region = &region;
2491        params.output_background_color = 0xff00_0000;
2492        params.pipeline_flags = 0;
2493        params.filter_flags = VA_FRAME_PICTURE;
2494        params.rotation_state = VA_ROTATION_NONE;
2495        params.mirror_state = VA_MIRROR_NONE;
2496        params.surface_color_standard = self.vpp_standards.0;
2497        params.output_color_standard = self.vpp_standards.1;
2498        params.input_color_properties = ColorDescription::SRGB_SOURCE.properties();
2499        params.output_color_properties = declared.properties();
2500        let buffer = self.device.create_buffer(
2501            self.vpp_context,
2502            VAProcPipelineParameterBufferType,
2503            unsafe {
2504                std::slice::from_raw_parts(
2505                    &params as *const VAProcPipelineParameterBuffer as *const u8,
2506                    std::mem::size_of::<VAProcPipelineParameterBuffer>(),
2507                )
2508            },
2509        )?;
2510        let mut buffers = [buffer];
2511        let result = (|| {
2512            self.device.check(
2513                unsafe { (api.vaBeginPicture)(display, self.vpp_context, self.converted[0]) },
2514                "vaBeginPicture (video processing)",
2515            )?;
2516            let rendered = self.device.check(
2517                unsafe {
2518                    (api.vaRenderPicture)(display, self.vpp_context, buffers.as_mut_ptr(), 1)
2519                },
2520                "vaRenderPicture (video processing)",
2521            );
2522            let ended = self.device.check(
2523                unsafe { (api.vaEndPicture)(display, self.vpp_context) },
2524                "vaEndPicture (video processing)",
2525            );
2526            rendered.and(ended)
2527        })();
2528        unsafe { (api.vaDestroyBuffer)(display, buffer) };
2529        result
2530    }
2531
2532    /// Encode the picture on `source`: convert it, then issue the frame with its parameter
2533    /// buffers and packed headers, wait for it, and frame the coded bytes for the wire.
2534    fn encode_surface(
2535        &mut self,
2536        source: VASurfaceID,
2537        frame_number: u64,
2538        crf: u32,
2539        force_idr: bool,
2540    ) -> Result<Vec<u8>, String> {
2541        if !self.rate.cbr {
2542            self.qp = self
2543                .codec
2544                .hardware_quantizer(self.device.hardware(), crf as i32);
2545        }
2546        let held_qp = self.held.take();
2547        self.convert(source)?;
2548        let frame_id = frame_number as u16;
2549        let has_reference = match &self.references {
2550            Some(References::Window(w)) => w.has_reference(),
2551            Some(References::Slots(s)) => s.has_reference(),
2552            None => !self.fresh,
2553        };
2554        let mut key = force_idr || self.sequence_start || !has_reference;
2555        let (mut anchor, mut shared) = (None, false);
2556        if let Some(References::Window(w)) = &mut self.references
2557            && w.anchored()
2558        {
2559            if !key && w.plan_anchor(false).is_some() && w.settle() == Some(Invalidation::KeyFrame)
2560            {
2561                key = true;
2562            }
2563            if !key && w.forget_stale() == Some(Invalidation::KeyFrame) {
2564                key = true;
2565            }
2566            anchor = w.plan_anchor(key);
2567            shared = w.acknowledging() && w.predicts_from_shared();
2568        }
2569        let pts = match &self.references {
2570            Some(References::Window(w)) => w.next_pts(),
2571            Some(References::Slots(s)) => s.next_pts(),
2572            None => self.frame_count,
2573        };
2574        let held: Vec<(u64, VASurfaceID, bool)> = match &self.references {
2575            Some(References::Window(w)) => w
2576                .held()
2577                .filter_map(|(_, p, lost)| self.surfaces_of.get(&p).map(|&s| (p, s, lost)))
2578                .collect(),
2579            _ => Vec::new(),
2580        };
2581        let (anchored, long_term): (bool, Vec<(u64, u8)>) = match &self.references {
2582            Some(References::Window(w)) if w.anchored() => (
2583                true,
2584                held.iter()
2585                    .filter_map(|h| w.anchor_slot(h.0).map(|slot| (h.0, slot)))
2586                    .collect(),
2587            ),
2588            _ => (false, Vec::new()),
2589        };
2590        let (reference, key_pts, slots, slot_surfaces) = match &self.references {
2591            Some(References::Window(w)) => (
2592                if key {
2593                    None
2594                } else {
2595                    w.newest_valid()
2596                        .and_then(|(_, p)| self.surfaces_of.get(&p).map(|&s| (p, s)))
2597                },
2598                if key { pts } else { w.key_pts() },
2599                SlotPlan::KEY,
2600                [VA_INVALID_SURFACE; 3],
2601            ),
2602            Some(References::Slots(s)) => {
2603                let plan = s.plan(key);
2604                let surfaces = [1u8, 2, 4].map(|slot| {
2605                    s.slot(slot)
2606                        .and_then(|(_, p, _)| self.surfaces_of.get(&p).copied())
2607                        .unwrap_or(VA_INVALID_SURFACE)
2608                });
2609                let reference = (plan.predict_from != 0)
2610                    .then(|| s.slot(plan.predict_from))
2611                    .flatten()
2612                    .and_then(|(_, p, _)| self.surfaces_of.get(&p).map(|&surf| (p, surf)));
2613                (reference, 0, plan, surfaces)
2614            }
2615            None => (
2616                if key {
2617                    None
2618                } else {
2619                    self.surfaces_of.get(&(pts - 1)).map(|&s| (pts - 1, s))
2620                },
2621                if key { pts } else { self.key_count },
2622                SlotPlan::KEY,
2623                [VA_INVALID_SURFACE; 3],
2624            ),
2625        };
2626        let recon = match &self.references {
2627            Some(References::Slots(_)) => *self
2628                .recon
2629                .iter()
2630                .find(|s| !slot_surfaces.contains(s))
2631                .ok_or("no VP8 reconstruction surface is free")?,
2632            Some(References::Window(_)) => *self
2633                .recon
2634                .iter()
2635                .find(|s| !held.iter().any(|h| h.1 == **s))
2636                .ok_or("no reconstruction surface is free")?,
2637            None => self.recon[(pts % self.recon.len() as u64) as usize],
2638        };
2639        let qp = if self.rate.cbr {
2640            self.qp
2641        } else {
2642            held_qp.unwrap_or(self.qp)
2643        };
2644        let frame = Frame {
2645            key,
2646            pts,
2647            key_pts,
2648            recon,
2649            coded: self.coded,
2650            reference,
2651            held: &held,
2652            anchored,
2653            anchor,
2654            long_term: &long_term,
2655            qp,
2656            slots,
2657            slot_surfaces,
2658        };
2659        let mut out = Buffers::new();
2660        let mut bps = self
2661            .cleanup_bps
2662            .take()
2663            .unwrap_or(self.negotiated.bits_per_second);
2664        if self.rate.cbr && key && held_qp.is_some() {
2665            // A held key frame's budget, as one frame's of a rate sent with it.
2666            let fps = self.negotiated.fps.fps().max(1.0);
2667            let bits = held_key_bits(
2668                self.negotiated.bits_per_second,
2669                self.negotiated.width,
2670                self.negotiated.height,
2671            );
2672            bps = (bits * fps).min(f64::from(u32::MAX)) as u32;
2673        }
2674        if key {
2675            self.arm.sequence(&self.negotiated, &mut out);
2676            self.declared_level = self.arm.level();
2677            self.rate_control_at(&mut out, bps);
2678            self.sent_bps = bps;
2679        } else if self.rate.cbr && bps != self.sent_bps {
2680            // radeonsi's VCE takes a new target with a picture, no new sequence.
2681            self.rate_control_at(&mut out, bps);
2682            self.sent_bps = bps;
2683        }
2684        self.arm.picture(&self.negotiated, &frame, &mut out)?;
2685        let refinement = self.refinement.take().filter(|_| !key && held_qp.is_none());
2686        if let Some(refinement) = &refinement {
2687            self.roi_parameter(refinement, &mut out);
2688        }
2689        let header_len = if self.omit_headers {
2690            0
2691        } else {
2692            VIDEO_HEADER_LEN
2693        };
2694        let mut output = vec![0; header_len];
2695        self.issue(&out, &mut output)?;
2696        if std::mem::take(&mut self.measure) {
2697            self.last_psnr = self.picture_psnr(recon);
2698            self.measures = self.last_psnr.is_some();
2699        }
2700        if key
2701            && self.codec == Codec::H264
2702            && let Some(bounded) = self.reorder.apply(&output[header_len..])
2703        {
2704            output.truncate(header_len);
2705            output.extend_from_slice(&bounded);
2706        }
2707        self.fresh = false;
2708        self.sequence_start = false;
2709        self.surfaces_of.insert(pts, recon);
2710        let coded = &output[header_len..];
2711        let frame_type = match self.codec {
2712            Codec::H264 => h264_frame_type(coded),
2713            Codec::H265 => h265_frame_type(coded),
2714            Codec::Vp8 => frame_type_from_key(vp8_is_key(coded)),
2715            Codec::Vp9 => frame_type_from_key(vp9_is_key(coded)),
2716            _ => frame_type_from_key(av1_is_key(coded)),
2717        };
2718        let is_key = frame_type != super::codec::FRAME_DELTA;
2719        if is_key
2720            && self.codec == Codec::H264
2721            && let Some(References::Window(w)) = &mut self.references
2722            && let Some(range) = h264_frame_num_range(coded)
2723        {
2724            w.set_frame_num_range(range);
2725        }
2726        self.last_reference = match &mut self.references {
2727            Some(References::Window(w)) => w.record_marked(frame_id, is_key, anchor),
2728            Some(References::Slots(s)) => {
2729                let plan = if is_key { s.plan(true) } else { slots };
2730                s.record(frame_id, plan)
2731            }
2732            None => {
2733                self.frame_count = pts + 1;
2734                if is_key {
2735                    self.key_count = pts;
2736                }
2737                Reference::Untracked
2738            }
2739        };
2740        match &self.references {
2741            Some(References::Slots(s)) => {
2742                let held: Vec<u64> = [1u8, 2, 4]
2743                    .iter()
2744                    .filter_map(|&slot| s.slot(slot).map(|(_, p, _)| p))
2745                    .collect();
2746                self.surfaces_of.retain(|p, _| held.contains(p));
2747            }
2748            Some(References::Window(w)) => {
2749                let held: Vec<u64> = w.held().map(|(_, p, _)| p).collect();
2750                self.surfaces_of.retain(|p, _| held.contains(p));
2751            }
2752            None => self
2753                .surfaces_of
2754                .retain(|&p, _| p + self.recon.len() as u64 > pts),
2755        }
2756        if !self.omit_headers {
2757            let mut header = Vec::with_capacity(VIDEO_HEADER_LEN);
2758            let anchor_flag = if anchor.is_some() && !is_key && shared {
2759                super::codec::FRAME_ANCHOR
2760            } else {
2761                0
2762            };
2763            push_video_header(
2764                &mut header,
2765                self.codec,
2766                frame_type | anchor_flag,
2767                frame_id,
2768                0,
2769                self.negotiated.width as u16,
2770                self.negotiated.height as u16,
2771                self.last_reference,
2772            );
2773            output[..VIDEO_HEADER_LEN].copy_from_slice(&header);
2774        }
2775        Ok(output)
2776    }
2777
2778    /// Render `buffers` as one picture on the encode context, wait for it, and append the
2779    /// coded bytes to `out`.
2780    fn issue(&mut self, buffers: &Buffers, out: &mut Vec<u8>) -> Result<(), String> {
2781        let api = self.device.api;
2782        let display = self.device.display;
2783        let mut ids = Vec::with_capacity(buffers.entries.len());
2784        let mut created = Ok(());
2785        for (kind, bytes) in &buffers.entries {
2786            match self.device.create_buffer(self.context, *kind, bytes) {
2787                Ok(id) => ids.push(id),
2788                Err(e) => {
2789                    created = Err(e);
2790                    break;
2791                }
2792            }
2793        }
2794        let result = created.and_then(|()| {
2795            self.device.check(
2796                unsafe { (api.vaBeginPicture)(display, self.context, self.converted[0]) },
2797                "vaBeginPicture",
2798            )?;
2799            let rendered = self.device.check(
2800                unsafe {
2801                    (api.vaRenderPicture)(
2802                        display,
2803                        self.context,
2804                        ids.as_mut_ptr(),
2805                        ids.len() as c_int,
2806                    )
2807                },
2808                "vaRenderPicture",
2809            );
2810            let ended = self.device.check(
2811                unsafe { (api.vaEndPicture)(display, self.context) },
2812                "vaEndPicture",
2813            );
2814            rendered.and(ended)
2815        });
2816        for id in ids {
2817            unsafe { (api.vaDestroyBuffer)(display, id) };
2818        }
2819        result?;
2820        let synced = match api.vaSyncBuffer {
2821            Some(sync) => {
2822                let status = unsafe { sync(display, self.coded, u64::MAX) };
2823                if status == VA_STATUS_ERROR_UNIMPLEMENTED as VAStatus {
2824                    None
2825                } else {
2826                    Some(self.device.check(status, "vaSyncBuffer"))
2827                }
2828            }
2829            None => None,
2830        };
2831        match synced {
2832            Some(result) => result?,
2833            None => self.device.check(
2834                unsafe { (api.vaSyncSurface)(display, self.converted[0]) },
2835                "vaSyncSurface",
2836            )?,
2837        }
2838        let mut list: *mut c_void = ptr::null_mut();
2839        self.device.check(
2840            unsafe { (api.vaMapBuffer)(display, self.coded, &mut list) },
2841            "vaMapBuffer (coded)",
2842        )?;
2843        let segments = || {
2844            std::iter::successors(
2845                unsafe { (list as *const VACodedBufferSegment).as_ref() },
2846                |s| unsafe { (s.next as *const VACodedBufferSegment).as_ref() },
2847            )
2848            .filter(|s| !s.buf.is_null())
2849        };
2850        let from = out.len();
2851        let average_qp = segments()
2852            .next()
2853            .map_or(0, |s| s.status & VA_CODED_BUF_STATUS_PICTURE_AVE_QP_MASK);
2854        out.reserve(segments().map(|s| s.size as usize).sum());
2855        for s in segments() {
2856            out.extend_from_slice(unsafe {
2857                std::slice::from_raw_parts(s.buf as *const u8, s.size as usize)
2858            });
2859        }
2860        unsafe { (api.vaUnmapBuffer)(display, self.coded) };
2861        if self.rate.cbr && matches!(self.arm, Arm::H264(_) | Arm::H265(_)) {
2862            strip_zero_padding(out, from);
2863        }
2864        self.last_bytes = Some(out.len() - from);
2865        let quantizer = match &self.arm {
2866            Arm::H264(_) | Arm::H265(_) => Some(average_qp).filter(|&q| q > 0),
2867            Arm::Av1(a) => a.coded_qindex(&out[from..]),
2868            Arm::Vp9(_) => super::codec::vp9_base_q_idx(&out[from..]),
2869            Arm::Vp8(_) => None,
2870        };
2871        self.last_quality = quantizer
2872            .filter(|_| self.rate.cbr)
2873            .map(|q| self.codec.hardware_quality_index(self.device.hardware(), q));
2874        Ok(())
2875    }
2876}
2877
2878/// A color description as the video processor is told it: the ISO/IEC 23091-2 code points
2879/// and the range, and whether the picture is RGB, whose matrix is fixed.
2880#[derive(Clone, Copy)]
2881struct ColorDescription {
2882    primaries: u8,
2883    transfer: u8,
2884    matrix: u8,
2885    full_range: bool,
2886    rgb: bool,
2887}
2888
2889impl ColorDescription {
2890    /// The sRGB desktop picture every session starts from: BT.709 primaries and transfer,
2891    /// full range.
2892    const SRGB_SOURCE: Self = Self {
2893        primaries: 1,
2894        transfer: 1,
2895        matrix: 0,
2896        full_range: true,
2897        rgb: true,
2898    };
2899
2900    fn properties(self) -> VAProcColorProperties {
2901        VAProcColorProperties {
2902            chroma_sample_location: (VA_CHROMA_SITING_VERTICAL_CENTER
2903                | VA_CHROMA_SITING_HORIZONTAL_CENTER) as u8,
2904            color_range: if self.full_range {
2905                VA_SOURCE_RANGE_FULL
2906            } else {
2907                VA_SOURCE_RANGE_REDUCED
2908            } as u8,
2909            colour_primaries: self.primaries,
2910            transfer_characteristics: self.transfer,
2911            matrix_coefficients: self.matrix,
2912            reserved: [0; 3],
2913        }
2914    }
2915}
2916
2917/// The color standard a video processor is told for `wanted`: the explicit one where the
2918/// driver takes it, since the properties then say everything; else the standard whose
2919/// matrix, transfer, and primaries come closest, weighted four, two, and one, or none when
2920/// nothing matches at all.
2921fn color_standard(
2922    offered: &[VAProcColorStandardType],
2923    wanted: ColorDescription,
2924) -> VAProcColorStandardType {
2925    if offered.contains(&VAProcColorStandardExplicit) {
2926        return VAProcColorStandardExplicit;
2927    }
2928    const TABLE: [(VAProcColorStandardType, u8, u8, u8); 12] = [
2929        (VAProcColorStandardBT601, 5, 6, 5),
2930        (VAProcColorStandardBT601, 6, 6, 6),
2931        (VAProcColorStandardBT709, 1, 1, 1),
2932        (VAProcColorStandardBT470M, 4, 4, 4),
2933        (VAProcColorStandardBT470BG, 5, 5, 5),
2934        (VAProcColorStandardSMPTE170M, 6, 6, 6),
2935        (VAProcColorStandardSMPTE240M, 7, 7, 7),
2936        (VAProcColorStandardGenericFilm, 8, 1, 1),
2937        (VAProcColorStandardSRGB, 1, 13, 0),
2938        (VAProcColorStandardXVYCC601, 1, 11, 5),
2939        (VAProcColorStandardXVYCC709, 1, 11, 1),
2940        (VAProcColorStandardBT2020, 9, 14, 9),
2941    ];
2942    let matrix_counts = !wanted.rgb;
2943    let worst = 4 * u32::from(matrix_counts) + 2 + 1;
2944    let mut best = (worst, VAProcColorStandardNone);
2945    for &standard in offered {
2946        for &(_, primaries, transfer, matrix) in TABLE.iter().filter(|t| t.0 == standard) {
2947            let score = 4 * u32::from(matrix_counts && wanted.matrix != matrix)
2948                + 2 * u32::from(wanted.transfer != transfer)
2949                + u32::from(wanted.primaries != primaries);
2950            if score < best.0 {
2951                best = (score, standard);
2952            }
2953        }
2954    }
2955    best.1
2956}
2957
2958/// The driver's image format of `fourcc`, with the masks it wants an image created with.
2959fn image_format(device: &Device, fourcc: u32) -> Option<VAImageFormat> {
2960    let api = device.api;
2961    let mut formats: Vec<VAImageFormat> = vec![
2962        unsafe { std::mem::zeroed() };
2963        unsafe { (api.vaMaxNumImageFormats)(device.display) }.max(0)
2964            as usize
2965    ];
2966    let mut count: c_int = 0;
2967    if unsafe { (api.vaQueryImageFormats)(device.display, formats.as_mut_ptr(), &mut count) }
2968        != VA_STATUS_SUCCESS as VAStatus
2969    {
2970        return None;
2971    }
2972    formats[..count.max(0) as usize]
2973        .iter()
2974        .find(|f| f.fourcc == fourcc)
2975        .copied()
2976}
2977
2978/// The dma-buf object `fd` names, as its device and inode: two fds of one buffer give the same.
2979fn object_identity(fd: i32) -> Option<(u64, u64)> {
2980    let mut st: libc::stat = unsafe { std::mem::zeroed() };
2981    (unsafe { libc::fstat(fd, &mut st) } == 0).then_some((st.st_dev, st.st_ino))
2982}
2983
2984#[cfg(test)]
2985mod tests {
2986    use super::*;
2987
2988    /// A 1080p frame is refined from a twenty-fifth of a bit a pixel on: 60 fps at 5 Mbit/s,
2989    /// 8 fps at 0.7, not 60 fps at 2 Mbit/s or 8 fps at 0.5.
2990    #[test]
2991    fn radeonsi_refines_from_a_twenty_fifth_of_a_bit_a_pixel() {
2992        assert!(refines(5_000_000, 60.0, 1920, 1080));
2993        assert!(refines(700_000, 8.0, 1920, 1080));
2994        assert!(!refines(2_000_000, 60.0, 1920, 1080));
2995        assert!(!refines(500_000, 8.0, 1920, 1080));
2996    }
2997
2998    /// Intel's low-power H.264 encoder is named no more regions than its three native zones at a
2999    /// constant quantizer, and every region it reports under its rate control.
3000    #[test]
3001    fn intel_low_power_constant_quantizer_names_at_most_three_regions() {
3002        let lp = VAEntrypointEncSliceLP;
3003        let full = VAEntrypointEncSlice;
3004        let intel = Hardware::VaapiIntel;
3005        assert_eq!(roi_regions(Some(0x210), intel, lp, false), 3);
3006        assert_eq!(roi_regions(Some(0x210), intel, lp, true), 16);
3007        assert_eq!(roi_regions(Some(0x010), intel, lp, true), 0);
3008        assert_eq!(roi_regions(Some(0x210), intel, full, false), 16);
3009        assert_eq!(roi_regions(Some(0x002), intel, lp, false), 2);
3010        assert_eq!(roi_regions(None, intel, lp, false), 0);
3011    }
3012
3013    /// A held key frame gets a second of the rate, and never less than 1.6 bits a pixel.
3014    #[test]
3015    fn a_held_key_frame_gets_a_second_of_the_rate_and_enough_for_the_picture() {
3016        assert_eq!(held_key_bits(500_000, 1920, 1080), 1.6 * 1920.0 * 1080.0);
3017        assert_eq!(held_key_bits(8_000_000, 1920, 1080), 8_000_000.0);
3018    }
3019
3020    /// The planar surface is tried first wherever a device both allocates and renders it, the
3021    /// packed one after it, and the profile ladder names the 4:4:4 profiles only where a
3022    /// codec carries them.
3023    #[test]
3024    fn surface_and_profile_ladders() {
3025        assert_eq!(FULLCOLOR_FOURCCS, [VA_FOURCC_444P, VA_FOURCC_XYUV]);
3026        assert_eq!(fourcc_name(VA_FOURCC_XYUV), "vuyx");
3027        assert_eq!(fourcc_name(VA_FOURCC_444P), "yuv444p");
3028        assert!(
3029            profile_ladder(Codec::H264, true, 8).is_empty(),
3030            "no 4:4:4 H.264 profile is served"
3031        );
3032        assert_eq!(profile_ladder(Codec::H265, true, 8), [VAProfileHEVCMain444]);
3033        assert_eq!(profile_ladder(Codec::Vp9, true, 8), [VAProfileVP9Profile1]);
3034        assert!(profile_ladder(Codec::Av1, true, 8).is_empty());
3035        assert_eq!(
3036            profile_ladder(Codec::H265, false, 10),
3037            [VAProfileHEVCMain10]
3038        );
3039        assert_eq!(
3040            profile_ladder(Codec::H265, true, 10),
3041            [VAProfileHEVCMain444_10]
3042        );
3043        assert_eq!(
3044            profile_ladder(Codec::Vp9, false, 10),
3045            [VAProfileVP9Profile2]
3046        );
3047        assert_eq!(profile_ladder(Codec::Vp9, true, 10), [VAProfileVP9Profile3]);
3048        assert_eq!(
3049            profile_ladder(Codec::Av1, false, 10),
3050            [VAProfileAV1Profile0]
3051        );
3052        assert!(profile_ladder(Codec::H264, false, 10).is_empty());
3053        assert!(profile_ladder(Codec::Vp8, false, 10).is_empty());
3054        for codec in Codec::VIDEO {
3055            assert!(!profile_ladder(codec, false, 8).is_empty(), "{codec:?}");
3056        }
3057    }
3058
3059    /// The color standard follows libavfilter's choice: explicit wherever the driver takes
3060    /// it, else the closest standard by matrix, transfer, and primaries, with an RGB source
3061    /// scored on transfer and primaries alone and a tie going to the one the driver lists
3062    /// first.
3063    #[test]
3064    fn the_color_standard_is_the_nearest_the_driver_offers() {
3065        let explicit = [VAProcColorStandardBT601, VAProcColorStandardExplicit];
3066        assert_eq!(
3067            color_standard(&explicit, ColorDescription::SRGB_SOURCE),
3068            VAProcColorStandardExplicit
3069        );
3070        let classic = [
3071            VAProcColorStandardBT601,
3072            VAProcColorStandardBT709,
3073            VAProcColorStandardSMPTE170M,
3074            VAProcColorStandardSRGB,
3075        ];
3076        assert_eq!(
3077            color_standard(&classic, ColorDescription::SRGB_SOURCE),
3078            VAProcColorStandardBT709
3079        );
3080        let bt709 = ColorDescription {
3081            primaries: 1,
3082            transfer: 1,
3083            matrix: 1,
3084            full_range: false,
3085            rgb: false,
3086        };
3087        assert_eq!(color_standard(&classic, bt709), VAProcColorStandardBT709);
3088        let bt601 = ColorDescription { matrix: 6, ..bt709 };
3089        assert_eq!(
3090            color_standard(&classic, bt601),
3091            VAProcColorStandardBT601,
3092            "the driver's first of two equal matches"
3093        );
3094        assert_eq!(
3095            color_standard(&classic[2..], bt601),
3096            VAProcColorStandardSMPTE170M
3097        );
3098        assert_eq!(
3099            color_standard(&[VAProcColorStandardBT2020], bt709),
3100            VAProcColorStandardNone,
3101            "a total mismatch names no standard"
3102        );
3103    }
3104
3105    /// Slices follow the driver's structure: as many rows as asked where rows are free, equal
3106    /// rows where the driver wants them equal, a power of two of rows where that is all it
3107    /// takes, and one row each where only that is.
3108    #[test]
3109    fn slice_layout_follows_the_driver() {
3110        assert_eq!(
3111            slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 68, 4),
3112            Ok((4, 17))
3113        );
3114        assert_eq!(
3115            slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 32, 68, 4),
3116            Ok((5, 16))
3117        );
3118        assert_eq!(
3119            slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 128, 68, 4),
3120            Ok((68, 1))
3121        );
3122        assert!(
3123            slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 32, 68, 4).is_err(),
3124            "more slices than the driver takes"
3125        );
3126        assert_eq!(
3127            slice_layout(
3128                VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS | VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS,
3129                256,
3130                68,
3131                4
3132            ),
3133            Ok((4, 17)),
3134            "equal rows of more than one where the driver offers both"
3135        );
3136        assert_eq!(
3137            slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 68, 4),
3138            Ok((4, 17))
3139        );
3140        assert_eq!(
3141            slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 67, 4),
3142            Ok((4, 17)),
3143            "the last slice is the shorter one"
3144        );
3145        assert!(slice_layout(0, 32, 68, 4).is_err());
3146        assert_eq!(
3147            slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 2, 4),
3148            Ok((2, 1)),
3149            "no more slices than rows"
3150        );
3151        assert_eq!(
3152            slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 2, 68, 4),
3153            Ok((2, 34)),
3154            "no more slices than the driver takes"
3155        );
3156        assert_eq!(
3157            slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 1, 68, 4),
3158            Ok((1, 68))
3159        );
3160        assert_eq!(
3161            slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 4, 68, 4),
3162            Ok((3, 32))
3163        );
3164        assert_eq!(
3165            slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 1, 68, 4),
3166            Ok((1, 128))
3167        );
3168    }
3169
3170    /// Construction either stands a session up or says why it could not; a half-built
3171    /// encoder must never reach a caller, and a session never quietly changes chroma. Runs
3172    /// everywhere: a host without a VA-API device exercises the error path.
3173    #[test]
3174    fn construction_answers_or_refuses() {
3175        let mut settings = CaptureSettings {
3176            width: 128,
3177            height: 128,
3178            codec: Codec::H264,
3179            video_fullcolor: true,
3180            ..Default::default()
3181        };
3182        for codec in Codec::VIDEO {
3183            settings.codec = codec;
3184            match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
3185                Ok(enc) => assert_eq!(
3186                    enc.is_fullcolor(),
3187                    matches!(codec, Codec::H265 | Codec::Vp9),
3188                    "{codec:?}"
3189                ),
3190                Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
3191            }
3192        }
3193        settings.video_fullcolor = false;
3194        match VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false }) {
3195            Ok(enc) => assert!(!enc.is_fullcolor(), "4:2:0 session reports 4:4:4"),
3196            Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
3197        }
3198        assert!(VaapiEncoder::new(&settings, Codec::Jpeg, Input::Host { rgba: false }).is_err());
3199    }
3200
3201    /// On a VA-API device (`cargo test vaapi_ -- --ignored --nocapture`): an AMD VCE device is
3202    /// told from a VCN one by the kernel, and its H.264 key frame is one slice, as a Skylake's
3203    /// or a Broxton's is on the low-power entry point.
3204    #[test]
3205    #[ignore]
3206    fn vaapi_vce_h264_is_one_slice() {
3207        let (w, h) = (1920usize, 1080usize);
3208        let settings = CaptureSettings {
3209            width: w as i32,
3210            height: h as i32,
3211            codec: Codec::H264,
3212            omit_stripe_headers: true,
3213            ..Default::default()
3214        };
3215        let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3216            .expect("a VA-API H.264 session");
3217        let frame: Vec<u8> = (0..w * h)
3218            .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
3219            .collect();
3220        let key = enc
3221            .encode_host(&frame, w * 4, false, 0, 25, true)
3222            .expect("encode");
3223        let slices = super::super::codec::annexb_nals(&key)
3224            .filter(|n| matches!(n[0] & 0x1f, 1 | 5))
3225            .count();
3226        println!(
3227            "{}: VCE {}, {slices} slice(s) in the key frame",
3228            enc.vendor(),
3229            enc.device.vce
3230        );
3231        let wanted = if enc.device.one_slice_h264(enc.negotiated.entrypoint) {
3232            1
3233        } else {
3234            SLICES as usize
3235        };
3236        assert_eq!(slices, wanted);
3237    }
3238
3239    /// A 1280x720 BGRA picture that moves: a bar crossing 12 pixels a frame, a checkerboard band
3240    /// 4, and fresh noise below row 480.
3241    fn moving_picture(t: usize) -> Vec<u8> {
3242        let (w, h) = (1280usize, 720usize);
3243        let noise = |n: usize| {
3244            let mut x = (n as u64 ^ 0x9e37_79b9_7f4a_7c15).wrapping_mul(0xbf58_476d_1ce4_e5b9);
3245            x ^= x >> 31;
3246            x as u8
3247        };
3248        let bar = (t * 12) % (w - 160);
3249        let mut f = vec![0u8; w * h * 4];
3250        for (i, px) in f.as_chunks_mut::<4>().0.iter_mut().enumerate() {
3251            let (x, y) = (i % w, i / w);
3252            let bgr: [u8; 3] = if y >= 480 {
3253                let n = (t % 4) * w * 240 + (y - 480) * w + x;
3254                [noise(3 * n), noise(3 * n + 1), noise(3 * n + 2)]
3255            } else if (96..160).contains(&y) {
3256                [if ((x + t * 4) / 16 + (y - 96) / 16).is_multiple_of(2) {
3257                    255
3258                } else {
3259                    0
3260                }; 3]
3261            } else if y >= 160 && (bar..bar + 160).contains(&x) {
3262                [0x28, 0x3c, 0xdc]
3263            } else {
3264                [0x78, 0x28, 0x1e]
3265            };
3266            px[..3].copy_from_slice(&bgr);
3267            px[3] = 0xff;
3268        }
3269        f
3270    }
3271
3272    /// Encode `moving_picture` for 120 frames, reporting a loss `depth` frames back every
3273    /// `every` frames from frame `from`, and decode it twice, once without the frames lost:
3274    /// the frames predicted past a loss, those from an anchor among them, the key frames after
3275    /// the first, how far the second decoder's pictures come from the first's at most, the
3276    /// worst average distance of a picture from its source, and whether the device keeps
3277    /// anchors (`keeps_anchors`).
3278    fn predict_past_losses(
3279        codec: Codec,
3280        from: usize,
3281        every: usize,
3282        depth: usize,
3283    ) -> Option<(usize, usize, usize, f64, f64, bool)> {
3284        use crate::webcam::decode::{Decoder as _, VideoDecoder};
3285        let (w, h) = (1280usize, 720usize);
3286        let settings = CaptureSettings {
3287            width: w as i32,
3288            height: h as i32,
3289            codec,
3290            target_fps: 60.0,
3291            video_cbr_mode: true,
3292            video_bitrate_kbps: 8000,
3293            omit_stripe_headers: true,
3294            ..Default::default()
3295        };
3296        let mut enc = match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
3297            Ok(enc) => enc,
3298            Err(e) => {
3299                println!("{codec:?}: {e}");
3300                return None;
3301            }
3302        };
3303        println!("{codec:?} on {}", enc.vendor());
3304        let (mut whole, mut lossy) = (
3305            VideoDecoder::new(codec).expect("decoder"),
3306            VideoDecoder::new(codec).expect("decoder"),
3307        );
3308        let reported = |r: usize| r >= from && (r - from).is_multiple_of(every);
3309        let (mut past, mut anchored, mut keys, mut apart, mut worst) = (0, 0, 0, 0f64, 0f64);
3310        for t in 0..120usize {
3311            if reported(t) {
3312                assert!(enc.invalidate_reference((t - depth) as u16), "{codec:?}");
3313            }
3314            let from_anchor = matches!(&enc.references,
3315                Some(References::Window(win)) if win.predicting_anchor().is_some());
3316            let src = moving_picture(t);
3317            let out = enc
3318                .encode_host(&src, w * 4, false, t as u64, 25, t == 0)
3319                .expect("encode");
3320            match enc.last_reference() {
3321                Reference::None if t > 0 => keys += 1,
3322                Reference::Frame(r) if r as usize + 1 != t => {
3323                    past += 1;
3324                    anchored += usize::from(from_anchor);
3325                }
3326                _ => {}
3327            }
3328            assert!(whole.decode(&out).expect("decode"), "{codec:?}: frame {t}");
3329            if (t + 1..=t + depth).any(reported) {
3330                continue;
3331            }
3332            assert!(
3333                lossy.decode(&out).expect("decode"),
3334                "{codec:?}: lossy frame {t}"
3335            );
3336            let (a, b) = (whole.frame().unwrap(), lossy.frame().unwrap());
3337            let mut sum = 0f64;
3338            for y in (0..480).step_by(2) {
3339                for x in (0..w).step_by(2) {
3340                    let i = (y * w + x) * 4;
3341                    let (bb, g, r) = (
3342                        f64::from(src[i]),
3343                        f64::from(src[i + 1]),
3344                        f64::from(src[i + 2]),
3345                    );
3346                    let luma = 16.0 + (0.2126 * r + 0.7152 * g + 0.0722 * bb) * 219.0 / 255.0;
3347                    let (pa, pb) = (
3348                        f64::from(a.y[y * a.y_stride + x]),
3349                        f64::from(b.y[y * b.y_stride + x]),
3350                    );
3351                    sum += (pb - luma).abs();
3352                    apart = apart.max((pa - pb).abs());
3353                }
3354            }
3355            worst = worst.max(sum / (240.0 * (w / 2) as f64));
3356        }
3357        println!(
3358            "{codec:?}: {past} frames predicted past a loss ({anchored} from an anchor), {keys} key frames; the lossy decoder at most {apart:.0} levels from the whole one, {worst:.1} on average from the source at worst"
3359        );
3360        Some((
3361            past,
3362            anchored,
3363            keys,
3364            apart,
3365            worst,
3366            enc.device.keeps_anchors(),
3367        ))
3368    }
3369
3370    /// On a VA-API device (`cargo test vaapi_ -- --ignored --nocapture`): a loss reported every
3371    /// thirteen frames, four frames deep, is predicted past from the frame before it: a decoder
3372    /// that never saw the lost frames shows every later picture as one that saw them all does.
3373    #[test]
3374    #[ignore]
3375    fn vaapi_predicts_past_a_lost_frame() {
3376        for codec in [Codec::H264, Codec::H265] {
3377            if let Some((past, _, keys, apart, worst, _)) = predict_past_losses(codec, 13, 13, 4) {
3378                assert!(past >= 5, "{codec:?}: {past} predicted past a loss");
3379                assert_eq!(keys, 0, "{codec:?}");
3380                assert!(
3381                    apart < 2.0,
3382                    "{codec:?}: the decoder without the lost frames is {apart} off"
3383                );
3384                assert!(worst < 20.0, "{codec:?}: {worst} off the source");
3385            }
3386        }
3387    }
3388
3389    /// On a VA-API device that keeps anchors (`keeps_anchors`): a loss older than the recent
3390    /// frames, twelve frames deep after the anchors at 48 and 96 (H.264; H.265 marks one every
3391    /// twelve), is predicted past from an anchor, and an H.264 one four frames deep covering the
3392    /// anchor at 48 from the recent frame before it, the lost anchor listed long-term while held;
3393    /// either way the decoder that never saw the lost frames shows every later picture as one
3394    /// that saw them all does. (Deeper, a gap FFmpeg fills would push that frame out of a
3395    /// browser's decoder, and the window codes a key frame: `forget_stale`.) On one that keeps
3396    /// none (iHD) the loss older than the recent frames is answered with a key frame, and the
3397    /// one four frames deep is predicted past from a recent frame, with no key frame.
3398    #[test]
3399    #[ignore]
3400    fn vaapi_predicts_from_an_anchor() {
3401        for (codec, from, every, depth, want) in [
3402            (Codec::H264, 70, 40, 12, 2),
3403            (Codec::H264, 52, 1000, 4, 0),
3404            (Codec::H265, 70, 40, 12, 2),
3405        ] {
3406            if let Some((past, anchored, keys, apart, worst, anchors)) =
3407                predict_past_losses(codec, from, every, depth)
3408            {
3409                if anchors {
3410                    assert!(
3411                        past >= 1 && anchored == want.min(past),
3412                        "{codec:?}: {anchored} of {past} from an anchor"
3413                    );
3414                    assert_eq!(keys, 0);
3415                } else if want > 0 {
3416                    assert!(
3417                        anchored == 0 && keys >= 1,
3418                        "{codec:?}: a loss past the recent frames took {keys} key frames"
3419                    );
3420                } else {
3421                    assert!(
3422                        past >= 1 && keys == 0,
3423                        "{codec:?}: {past} predicted past the loss, {keys} key frames"
3424                    );
3425                }
3426                assert!(
3427                    apart < 2.0,
3428                    "the decoder without the lost frames is {apart} off"
3429                );
3430                assert!(worst < 20.0, "{worst} off the source");
3431            }
3432        }
3433    }
3434
3435    /// A node the kernel answers no DRM query on is not VCE.
3436    #[test]
3437    fn a_node_that_is_not_amdgpu_is_not_vce() {
3438        let node = std::fs::File::open("/dev/null").unwrap();
3439        assert!(!amd_vce(node.as_raw_fd()));
3440    }
3441
3442    /// On a VA-API device (`cargo test vaapi_ -- --ignored --nocapture`): whichever sequence
3443    /// parameter set the driver writes, the key frame bounds reordering at zero and decodes.
3444    #[test]
3445    #[ignore]
3446    fn vaapi_h264_bounds_reordering_at_zero() {
3447        use crate::encoders::sps::fixtures::assert_no_reorder;
3448        use crate::webcam::decode::{Decoder as _, VideoDecoder};
3449        let (w, h) = (1280usize, 720usize);
3450        let settings = CaptureSettings {
3451            width: w as i32,
3452            height: h as i32,
3453            codec: Codec::H264,
3454            video_cbr_mode: true,
3455            video_bitrate_kbps: 4000,
3456            omit_stripe_headers: true,
3457            ..Default::default()
3458        };
3459        let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3460            .expect("a VA-API H.264 session");
3461        let frame: Vec<u8> = (0..w * h)
3462            .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
3463            .collect();
3464        let key = enc
3465            .encode_host(&frame, w * 4, false, 0, 25, true)
3466            .expect("encode");
3467        assert_no_reorder(&key, enc.vendor());
3468        let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3469        assert!(
3470            dec.decode(&key).expect("the bounded key frame decodes"),
3471            "no picture"
3472        );
3473    }
3474
3475    /// On a VA-API device (`cargo test vaapi_ -- --ignored --nocapture`): a constant-rate H.264
3476    /// session at 2160p decodes close to its source at 60 fps, after a change to 75 fps, and
3477    /// back, its SPS declaring the level each rate needs (5.2, 6.0, 5.2) whatever level the
3478    /// driver is told (`h264_driver_level`). OpenH264, the decoder here, refuses an SPS above
3479    /// 5.2, so the copy it decodes declares 5.2; the pictures are the encoder's.
3480    #[test]
3481    #[ignore]
3482    fn vaapi_h264_constant_rate_decodes_above_level_5_2() {
3483        use crate::webcam::decode::{Decoder as _, VideoDecoder};
3484        let (w, h, tile_w, tile_h) = (3840usize, 2160usize, 1280usize, 720usize);
3485        let mut settings = CaptureSettings {
3486            width: w as i32,
3487            height: h as i32,
3488            codec: Codec::H264,
3489            target_fps: 60.0,
3490            video_cbr_mode: true,
3491            video_bitrate_kbps: 39000,
3492            omit_stripe_headers: true,
3493            ..Default::default()
3494        };
3495        let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3496            .expect("a VA-API H.264 session");
3497        let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3498        let mut phases = Vec::new();
3499        for (phase, fps, level) in [(0usize, 60.0, 52u8), (1, 75.0, 60), (2, 60.0, 52)] {
3500            if phase > 0 {
3501                settings.target_fps = fps;
3502                enc.reconfigure_rate(&settings).expect("reconfigure");
3503            }
3504            let (mut declared, mut failed, mut worst) = (None, 0, 0f64);
3505            for t in phase * 30..phase * 30 + 30 {
3506                let tile = moving_picture(t);
3507                let src: Vec<u8> = (0..h)
3508                    .flat_map(|y| {
3509                        let row = &tile[(y % tile_h) * tile_w * 4..][..tile_w * 4];
3510                        [row, row, row].concat()
3511                    })
3512                    .collect();
3513                let out = enc
3514                    .encode_host(&src, w * 4, false, t as u64, 25, t == 0)
3515                    .expect("encode");
3516                let sps = super::super::codec::annexb_nals(&out)
3517                    .find(|n| n[0] & 0x1f == 7 && n.len() > 3)
3518                    .map(|n| n.as_ptr() as usize - out.as_ptr() as usize + 3);
3519                let mut fed = out.clone();
3520                if let Some(at) = sps {
3521                    declared = Some(out[at]);
3522                    fed[at] = fed[at].min(52);
3523                }
3524                if !dec.decode(&fed).unwrap_or(false) {
3525                    failed += 1;
3526                    continue;
3527                }
3528                let p = dec.frame().unwrap();
3529                let mut sum = 0f64;
3530                for y in (0..h).step_by(4) {
3531                    for x in (0..w).step_by(4) {
3532                        let i = (y * w + x) * 4;
3533                        let (b, g, r) = (
3534                            f64::from(src[i]),
3535                            f64::from(src[i + 1]),
3536                            f64::from(src[i + 2]),
3537                        );
3538                        let luma = 16.0 + (0.2126 * r + 0.7152 * g + 0.0722 * b) * 219.0 / 255.0;
3539                        sum += (f64::from(p.y[y * p.y_stride + x]) - luma).abs();
3540                    }
3541                }
3542                worst = worst.max(sum / ((h / 4) * (w / 4)) as f64);
3543            }
3544            println!(
3545                "{} {fps} fps: level {declared:?}, {failed} frames undecodable, {worst:.1} on average from the source at worst",
3546                enc.vendor()
3547            );
3548            phases.push((fps, level, declared, failed, worst));
3549        }
3550        for (fps, level, declared, failed, worst) in phases {
3551            assert_eq!(declared, Some(level), "{fps} fps");
3552            assert_eq!(failed, 0, "{fps} fps");
3553            assert!(worst < 20.0, "{fps} fps: {worst} off the source");
3554        }
3555    }
3556
3557    /// On a VA-API device (`cargo test vaapi_ -- --ignored --nocapture`, its render node in
3558    /// `PIXELFLUX_TEST_RENDER_NODE`): a zero-copy session keeps its import of each recurring
3559    /// dmabuf (`dmabuf_surface`), so a buffer repainted between frames, with another buffer's
3560    /// frames in between, decodes to what was painted last, never to the picture it held when
3561    /// it was first imported.
3562    #[test]
3563    #[ignore]
3564    fn vaapi_dmabuf_import_follows_the_buffer() {
3565        use crate::encoders::nvenc::gpu_tests::{
3566            assert_painted, block_rect, decoded_means, gpu_render, paint_dmabuf, painted_dmabuf,
3567        };
3568        use crate::webcam::decode::VideoDecoder;
3569        let (w, h) = (1280u32, 720u32);
3570        let Some((gbm, mut renderer)) = gpu_render() else {
3571            return;
3572        };
3573        let settings = CaptureSettings {
3574            width: w as i32,
3575            height: h as i32,
3576            codec: Codec::H264,
3577            target_fps: 60.0,
3578            video_crf: 25,
3579            ..Default::default()
3580        };
3581        let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Dmabuf)
3582            .expect("a zero-copy VA-API H.264 session");
3583        let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3584        let (_a_bo, mut a) = painted_dmabuf(&gbm, &mut renderer, w, h, 1);
3585        let (_b_bo, mut b) = painted_dmabuf(&gbm, &mut renderer, w, h, 2);
3586        for frame in 0..8u32 {
3587            let seed = frame + 1;
3588            let (name, buffer) = if frame % 2 == 0 {
3589                ("A", &mut a)
3590            } else {
3591                ("B", &mut b)
3592            };
3593            if frame >= 2 {
3594                paint_dmabuf(&mut renderer, buffer, w, h, seed);
3595            }
3596            let out = enc
3597                .encode_dmabuf(buffer, u64::from(frame), 25, frame == 0)
3598                .expect("encode");
3599            let (block, bg) = decoded_means(&mut dec, &out, block_rect(w, h, seed));
3600            println!(
3601                "{} frame {frame} (buffer {name}): block {block:.1?}, background {bg:.1?}",
3602                enc.vendor()
3603            );
3604            assert_painted(&format!("frame {frame}, buffer {name}"), block, bg, 6.0);
3605        }
3606        assert_eq!(enc.imports.len(), 2, "one import a buffer");
3607    }
3608}