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