1use std::ffi::{CStr, c_int, c_uint, c_void};
2use std::fs::{File, OpenOptions};
3use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
4use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
5use std::sync::{Arc, Mutex, OnceLock, PoisonError};
6
7use libloading::Library;
8
9use super::ffi::{self, VAConfigAttrib, VAImage, VAImageFormat, VASurfaceID};
10use super::host::HostBuffer;
11use super::{Accel, LAG, SLACK};
12use crate::gpu::biplanar::{self, Biplanar, Geometry};
13use crate::media::{Frame, Rgba};
14
15struct Api {
16 _va: Library,
17 _drm: Library,
18 initialize: ffi::vaInitialize,
19 terminate: ffi::vaTerminate,
20 error_str: ffi::vaErrorStr,
21 vendor_string: ffi::vaQueryVendorString,
22 set_info_callback: ffi::vaSetInfoCallback,
23 set_error_callback: ffi::vaSetErrorCallback,
24 get_config_attributes: ffi::vaGetConfigAttributes,
25 create_config: ffi::vaCreateConfig,
26 destroy_config: ffi::vaDestroyConfig,
27 create_surfaces: ffi::vaCreateSurfaces,
28 destroy_surfaces: ffi::vaDestroySurfaces,
29 create_context: ffi::vaCreateContext,
30 destroy_context: ffi::vaDestroyContext,
31 create_buffer: ffi::vaCreateBuffer,
32 destroy_buffer: ffi::vaDestroyBuffer,
33 begin_picture: ffi::vaBeginPicture,
34 render_picture: ffi::vaRenderPicture,
35 end_picture: ffi::vaEndPicture,
36 sync_surface: ffi::vaSyncSurface,
37 create_image: ffi::vaCreateImage,
38 destroy_image: ffi::vaDestroyImage,
39 get_image: ffi::vaGetImage,
40 derive_image: ffi::vaDeriveImage,
41 export_surface: ffi::vaExportSurfaceHandle,
42 put_image: ffi::vaPutImage,
43 map_buffer: ffi::vaMapBuffer,
44 unmap_buffer: ffi::vaUnmapBuffer,
45 get_display_drm: ffi::vaGetDisplayDRM,
46}
47
48static API: OnceLock<Result<Api, String>> = OnceLock::new();
49
50fn api() -> Result<&'static Api, String> {
51 API.get_or_init(load).as_ref().map_err(Clone::clone)
52}
53
54fn load() -> Result<Api, String> {
55 let va = unsafe { Library::new("libva.so.2") }.map_err(|e| format!("libva.so.2: {e}"))?;
56 let drm =
57 unsafe { Library::new("libva-drm.so.2") }.map_err(|e| format!("libva-drm.so.2: {e}"))?;
58 macro_rules! sym {
59 ($lib:expr, $name:ident) => {{
60 let s = unsafe { $lib.get::<ffi::$name>(concat!(stringify!($name), "\0").as_bytes()) };
61 *s.map_err(|e| format!("{}: {e}", stringify!($name)))?
62 }};
63 }
64 Ok(Api {
65 initialize: sym!(va, vaInitialize),
66 terminate: sym!(va, vaTerminate),
67 error_str: sym!(va, vaErrorStr),
68 vendor_string: sym!(va, vaQueryVendorString),
69 set_info_callback: sym!(va, vaSetInfoCallback),
70 set_error_callback: sym!(va, vaSetErrorCallback),
71 get_config_attributes: sym!(va, vaGetConfigAttributes),
72 create_config: sym!(va, vaCreateConfig),
73 destroy_config: sym!(va, vaDestroyConfig),
74 create_surfaces: sym!(va, vaCreateSurfaces),
75 destroy_surfaces: sym!(va, vaDestroySurfaces),
76 create_context: sym!(va, vaCreateContext),
77 destroy_context: sym!(va, vaDestroyContext),
78 create_buffer: sym!(va, vaCreateBuffer),
79 destroy_buffer: sym!(va, vaDestroyBuffer),
80 begin_picture: sym!(va, vaBeginPicture),
81 render_picture: sym!(va, vaRenderPicture),
82 end_picture: sym!(va, vaEndPicture),
83 sync_surface: sym!(va, vaSyncSurface),
84 create_image: sym!(va, vaCreateImage),
85 destroy_image: sym!(va, vaDestroyImage),
86 get_image: sym!(va, vaGetImage),
87 derive_image: sym!(va, vaDeriveImage),
88 export_surface: sym!(va, vaExportSurfaceHandle),
89 put_image: sym!(va, vaPutImage),
90 map_buffer: sym!(va, vaMapBuffer),
91 unmap_buffer: sym!(va, vaUnmapBuffer),
92 get_display_drm: sym!(drm, vaGetDisplayDRM),
93 _va: va,
94 _drm: drm,
95 })
96}
97
98pub struct Display {
99 api: &'static Api,
100 dpy: ffi::VADisplay,
101 vendor: String,
102 _device: File,
103}
104
105unsafe impl Send for Display {}
106unsafe impl Sync for Display {}
107
108static SHARED: Mutex<Option<Arc<Display>>> = Mutex::new(None);
109
110fn shared() -> Result<Arc<Display>, String> {
111 let mut shared = SHARED.lock().unwrap_or_else(PoisonError::into_inner);
112 if let Some(d) = shared.as_ref() {
113 return Ok(d.clone());
114 }
115 let d = Arc::new(Display::open()?);
116 *shared = Some(d.clone());
117 Ok(d)
118}
119
120fn replace_shared(old: &Arc<Display>, new: Arc<Display>) {
121 let mut shared = SHARED.lock().unwrap_or_else(PoisonError::into_inner);
122 if shared.as_ref().is_none_or(|d| Arc::ptr_eq(d, old)) {
123 *shared = Some(new);
124 }
125}
126
127fn with_display<T>(make: impl Fn(Arc<Display>) -> Result<T, String>) -> Result<T, String> {
128 let display = shared()?;
129 match make(display.clone()) {
130 Ok(s) => Ok(s),
131 Err(first) => {
132 let fresh =
133 Arc::new(Display::open().map_err(|e| format!("{first}; a new display: {e}"))?);
134 let s = make(fresh.clone()).map_err(|_| first)?;
135 replace_shared(&display, fresh);
136 Ok(s)
137 }
138 }
139}
140
141impl Display {
142 pub fn open() -> Result<Self, String> {
143 let api = api()?;
144 let mut why = "no DRM render node (/dev/dri/renderD*)".to_string();
145 for n in 128..144 {
146 let path = format!("/dev/dri/renderD{n}");
147 let Ok(file) = OpenOptions::new().read(true).write(true).open(&path) else {
148 continue;
149 };
150 match Self::init(api, file) {
151 Ok(d) => return Ok(d),
152 Err(e) => why = format!("{path}: {e}"),
153 }
154 }
155 Err(why)
156 }
157
158 fn init(api: &'static Api, device: File) -> Result<Self, String> {
159 let dpy = unsafe { (api.get_display_drm)(device.as_raw_fd()) };
160 if dpy.is_null() {
161 return Err("vaGetDisplayDRM failed".into());
162 }
163 let mut d = Self {
164 api,
165 dpy,
166 vendor: String::new(),
167 _device: device,
168 };
169 unsafe {
170 (api.set_info_callback)(dpy, None, std::ptr::null_mut());
171 (api.set_error_callback)(dpy, None, std::ptr::null_mut());
172 }
173 let (mut major, mut minor): (c_int, c_int) = (0, 0);
174 let st = unsafe { (api.initialize)(dpy, &mut major, &mut minor) };
175 d.check(st, "vaInitialize")?;
176 let vendor = unsafe { (api.vendor_string)(dpy) };
177 if !vendor.is_null() {
178 d.vendor = unsafe { CStr::from_ptr(vendor) }
179 .to_string_lossy()
180 .into_owned();
181 }
182 Ok(d)
183 }
184
185 pub fn vendor(&self) -> &str {
186 &self.vendor
187 }
188
189 fn check(&self, st: ffi::VAStatus, what: &str) -> Result<(), String> {
190 if st == ffi::VA_STATUS_SUCCESS {
191 return Ok(());
192 }
193 let text = unsafe { (self.api.error_str)(st) };
194 let text = if text.is_null() {
195 String::new()
196 } else {
197 unsafe { CStr::from_ptr(text) }
198 .to_string_lossy()
199 .into_owned()
200 };
201 Err(format!("{what}: {text} ({st:#x})"))
202 }
203
204 pub fn attributes(
205 &self,
206 profile: ffi::VAProfile,
207 entrypoint: ffi::VAEntrypoint,
208 types: &[ffi::VAConfigAttribType],
209 ) -> Result<Vec<u32>, String> {
210 let mut attrs: Vec<VAConfigAttrib> = types
211 .iter()
212 .map(|&type_| VAConfigAttrib { type_, value: 0 })
213 .collect();
214 let n = c_int::try_from(attrs.len()).map_err(|_| "too many attributes")?;
215 let st = unsafe {
216 (self.api.get_config_attributes)(self.dpy, profile, entrypoint, attrs.as_mut_ptr(), n)
217 };
218 self.check(st, "vaGetConfigAttributes")?;
219 Ok(attrs.into_iter().map(|a| a.value).collect())
220 }
221
222 fn create_config(
223 &self,
224 profile: ffi::VAProfile,
225 entrypoint: ffi::VAEntrypoint,
226 attrs: &mut [VAConfigAttrib],
227 ) -> Result<ffi::VAConfigID, String> {
228 let n = c_int::try_from(attrs.len()).map_err(|_| "too many attributes")?;
229 let mut id = ffi::VA_INVALID_ID;
230 let st = unsafe {
231 (self.api.create_config)(
232 self.dpy,
233 profile,
234 entrypoint,
235 attrs.as_mut_ptr(),
236 n,
237 &mut id,
238 )
239 };
240 self.check(st, "vaCreateConfig")?;
241 Ok(id)
242 }
243
244 fn create_surfaces(
245 &self,
246 rt_format: c_uint,
247 size: (u32, u32),
248 count: usize,
249 ) -> Result<Vec<VASurfaceID>, String> {
250 let count_c = c_uint::try_from(count).map_err(|_| "too many surfaces")?;
251 let mut surfaces = vec![ffi::VA_INVALID_SURFACE; count];
252 let st = unsafe {
253 (self.api.create_surfaces)(
254 self.dpy,
255 rt_format,
256 size.0,
257 size.1,
258 surfaces.as_mut_ptr(),
259 count_c,
260 std::ptr::null_mut(),
261 0,
262 )
263 };
264 self.check(st, "vaCreateSurfaces")?;
265 Ok(surfaces)
266 }
267
268 fn import(
269 &self,
270 rt_format: c_uint,
271 size: (u32, u32),
272 descriptor: &mut ffi::VADRMPRIMESurfaceDescriptor,
273 ) -> Result<VASurfaceID, String> {
274 let attribute = |type_, value| ffi::VASurfaceAttrib {
275 type_,
276 flags: ffi::VA_SURFACE_ATTRIB_SETTABLE,
277 value,
278 };
279 let mut attributes = [
280 attribute(
281 ffi::VASurfaceAttribMemoryType,
282 ffi::VAGenericValue {
283 type_: ffi::VAGenericValueTypeInteger,
284 value: ffi::VAGenericValueUnion {
285 i: ffi::VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 as c_int,
286 },
287 },
288 ),
289 attribute(
290 ffi::VASurfaceAttribExternalBufferDescriptor,
291 ffi::VAGenericValue {
292 type_: ffi::VAGenericValueTypePointer,
293 value: ffi::VAGenericValueUnion {
294 p: (descriptor as *mut ffi::VADRMPRIMESurfaceDescriptor).cast(),
295 },
296 },
297 ),
298 ];
299 let mut surface = ffi::VA_INVALID_SURFACE;
300 let st = unsafe {
301 (self.api.create_surfaces)(
302 self.dpy,
303 rt_format,
304 size.0,
305 size.1,
306 &mut surface,
307 1,
308 attributes.as_mut_ptr().cast(),
309 attributes.len() as c_uint,
310 )
311 };
312 self.check(st, "vaCreateSurfaces (import)")?;
313 Ok(surface)
314 }
315
316 fn create_rgba(&self, size: (u32, u32)) -> Result<VASurfaceID, String> {
317 let mut attributes = [ffi::VASurfaceAttrib {
318 type_: ffi::VASurfaceAttribPixelFormat,
319 flags: ffi::VA_SURFACE_ATTRIB_SETTABLE,
320 value: ffi::VAGenericValue {
321 type_: ffi::VAGenericValueTypeInteger,
322 value: ffi::VAGenericValueUnion {
323 i: ffi::VA_FOURCC_RGBA as c_int,
324 },
325 },
326 }];
327 let mut surface = ffi::VA_INVALID_SURFACE;
328 let st = unsafe {
329 (self.api.create_surfaces)(
330 self.dpy,
331 ffi::VA_RT_FORMAT_RGB32,
332 size.0,
333 size.1,
334 &mut surface,
335 1,
336 attributes.as_mut_ptr().cast(),
337 attributes.len() as c_uint,
338 )
339 };
340 self.check(st, "vaCreateSurfaces (RGBA)")?;
341 Ok(surface)
342 }
343
344 fn create_rgba_image(&self, size: (u32, u32)) -> Result<VAImage, String> {
345 let mut format = VAImageFormat {
346 fourcc: ffi::VA_FOURCC_RGBA,
347 byte_order: 1,
348 bits_per_pixel: 32,
349 depth: 32,
350 red_mask: 0x0000_00ff,
351 green_mask: 0x0000_ff00,
352 blue_mask: 0x00ff_0000,
353 alpha_mask: 0xff00_0000,
354 ..Default::default()
355 };
356 let mut image = VAImage {
357 image_id: ffi::VA_INVALID_ID,
358 buf: ffi::VA_INVALID_ID,
359 ..Default::default()
360 };
361 let (w, h) = (
362 c_int::try_from(size.0).map_err(|_| "picture too wide")?,
363 c_int::try_from(size.1).map_err(|_| "picture too tall")?,
364 );
365 let st = unsafe { (self.api.create_image)(self.dpy, &mut format, w, h, &mut image) };
366 self.check(st, "vaCreateImage (RGBA)")?;
367 Ok(image)
368 }
369
370 fn create_context(
371 &self,
372 config: ffi::VAConfigID,
373 size: (u32, u32),
374 targets: &mut [VASurfaceID],
375 ) -> Result<ffi::VAContextID, String> {
376 let (w, h) = (
377 c_int::try_from(size.0).map_err(|_| "picture too wide")?,
378 c_int::try_from(size.1).map_err(|_| "picture too tall")?,
379 );
380 let n = c_int::try_from(targets.len()).map_err(|_| "too many surfaces")?;
381 let mut context = ffi::VA_INVALID_ID;
382 let st = unsafe {
383 (self.api.create_context)(
384 self.dpy,
385 config,
386 w,
387 h,
388 ffi::VA_PROGRESSIVE,
389 targets.as_mut_ptr(),
390 n,
391 &mut context,
392 )
393 };
394 self.check(st, "vaCreateContext")?;
395 Ok(context)
396 }
397
398 fn buffer(
399 &self,
400 context: ffi::VAContextID,
401 ty: ffi::VABufferType,
402 data: *const c_void,
403 len: usize,
404 ) -> Result<ffi::VABufferID, String> {
405 let size = c_uint::try_from(len).map_err(|_| "buffer too large")?;
406 let mut id = ffi::VA_INVALID_ID;
407 let st = unsafe {
408 (self.api.create_buffer)(self.dpy, context, ty, size, 1, data.cast_mut(), &mut id)
409 };
410 self.check(st, "vaCreateBuffer")?;
411 Ok(id)
412 }
413
414 fn value_buffer<T>(
415 &self,
416 context: ffi::VAContextID,
417 ty: ffi::VABufferType,
418 value: &T,
419 ) -> Result<ffi::VABufferID, String> {
420 self.buffer(
421 context,
422 ty,
423 (value as *const T).cast(),
424 std::mem::size_of::<T>(),
425 )
426 }
427
428 fn destroy_buffers(&self, buffers: &[ffi::VABufferID]) {
429 for &id in buffers {
430 unsafe { (self.api.destroy_buffer)(self.dpy, id) };
431 }
432 }
433
434 fn render(
435 &self,
436 context: ffi::VAContextID,
437 buffers: &mut [ffi::VABufferID],
438 ) -> Result<(), String> {
439 let n = c_int::try_from(buffers.len()).map_err(|_| "too many buffers")?;
440 let st = unsafe { (self.api.render_picture)(self.dpy, context, buffers.as_mut_ptr(), n) };
441 self.check(st, "vaRenderPicture")
442 }
443
444 fn picture(
445 &self,
446 context: ffi::VAContextID,
447 surface: VASurfaceID,
448 groups: &mut [&mut [ffi::VABufferID]],
449 ) -> Result<(), String> {
450 let st = unsafe { (self.api.begin_picture)(self.dpy, context, surface) };
451 self.check(st, "vaBeginPicture")?;
452 let mut rendered = Ok(());
453 for group in groups.iter_mut() {
454 if group.is_empty() {
455 continue;
456 }
457 rendered = self.render(context, group);
458 if rendered.is_err() {
459 break;
460 }
461 }
462 let st = unsafe { (self.api.end_picture)(self.dpy, context) };
463 rendered?;
464 self.check(st, "vaEndPicture")
465 }
466
467 fn sync(&self, surface: VASurfaceID) -> Result<(), String> {
468 let st = unsafe { (self.api.sync_surface)(self.dpy, surface) };
469 self.check(st, "vaSyncSurface")
470 }
471
472 fn create_image(&self, bits: u32, size: (u32, u32)) -> Result<VAImage, String> {
473 let (_, fourcc, bits_per_pixel) = formats(bits)?;
474 let mut format = VAImageFormat {
475 fourcc,
476 byte_order: 1,
477 bits_per_pixel,
478 ..Default::default()
479 };
480 let mut image = VAImage {
481 image_id: ffi::VA_INVALID_ID,
482 buf: ffi::VA_INVALID_ID,
483 ..Default::default()
484 };
485 let (w, h) = (
486 c_int::try_from(size.0).map_err(|_| "picture too wide")?,
487 c_int::try_from(size.1).map_err(|_| "picture too tall")?,
488 );
489 let st = unsafe { (self.api.create_image)(self.dpy, &mut format, w, h, &mut image) };
490 self.check(st, "vaCreateImage")?;
491 Ok(image)
492 }
493
494 fn with_mapped<T>(
495 &self,
496 buf: ffi::VABufferID,
497 f: impl FnOnce(*mut c_void) -> Result<T, String>,
498 ) -> Result<T, String> {
499 let mut ptr: *mut c_void = std::ptr::null_mut();
500 let st = unsafe { (self.api.map_buffer)(self.dpy, buf, &mut ptr) };
501 self.check(st, "vaMapBuffer")?;
502 let result = if ptr.is_null() {
503 Err("vaMapBuffer returned no data".to_string())
504 } else {
505 f(ptr)
506 };
507 let st = unsafe { (self.api.unmap_buffer)(self.dpy, buf) };
508 let value = result?;
509 self.check(st, "vaUnmapBuffer")?;
510 Ok(value)
511 }
512
513 fn with_image<T>(
514 &self,
515 image: &VAImage,
516 f: impl FnOnce(&mut [u8]) -> Result<T, String>,
517 ) -> Result<T, String> {
518 self.with_mapped(image.buf, |ptr| {
519 let data = unsafe {
520 std::slice::from_raw_parts_mut(ptr.cast::<u8>(), image.data_size as usize)
521 };
522 f(data)
523 })
524 }
525
526 fn derive(&self, surface: VASurfaceID) -> Result<VAImage, String> {
527 self.sync(surface)?;
528 let mut image = VAImage {
529 image_id: ffi::VA_INVALID_ID,
530 buf: ffi::VA_INVALID_ID,
531 ..Default::default()
532 };
533 let st = unsafe { (self.api.derive_image)(self.dpy, surface, &mut image) };
534 self.check(st, "vaDeriveImage")?;
535 Ok(image)
536 }
537
538 fn export(
539 &self,
540 surface: VASurfaceID,
541 separate: bool,
542 ) -> Result<ffi::VADRMPRIMESurfaceDescriptor, String> {
543 self.sync(surface)?;
544 let mut d = ffi::VADRMPRIMESurfaceDescriptor::default();
545 let layout = if separate {
546 ffi::VA_EXPORT_SURFACE_SEPARATE_LAYERS
547 } else {
548 ffi::VA_EXPORT_SURFACE_COMPOSED_LAYERS
549 };
550 let st = unsafe {
551 (self.api.export_surface)(
552 self.dpy,
553 surface,
554 ffi::VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
555 ffi::VA_EXPORT_SURFACE_READ_ONLY | layout,
556 (&mut d as *mut ffi::VADRMPRIMESurfaceDescriptor).cast(),
557 )
558 };
559 self.check(st, "vaExportSurfaceHandle")?;
560 Ok(d)
561 }
562
563 fn destroy_image(&self, image: &VAImage) {
564 unsafe { (self.api.destroy_image)(self.dpy, image.image_id) };
565 }
566
567 fn get_image(
568 &self,
569 surface: VASurfaceID,
570 image: &VAImage,
571 size: (u32, u32),
572 ) -> Result<(), String> {
573 self.sync(surface)?;
574 let st = unsafe {
575 (self.api.get_image)(self.dpy, surface, 0, 0, size.0, size.1, image.image_id)
576 };
577 self.check(st, "vaGetImage")
578 }
579
580 fn put_image(
581 &self,
582 surface: VASurfaceID,
583 image: &VAImage,
584 size: (u32, u32),
585 ) -> Result<(), String> {
586 let (w, h) = size;
587 let st = unsafe {
588 (self.api.put_image)(self.dpy, surface, image.image_id, 0, 0, w, h, 0, 0, w, h)
589 };
590 self.check(st, "vaPutImage")
591 }
592
593 fn release(
594 &self,
595 image: Option<VAImage>,
596 context: ffi::VAContextID,
597 surfaces: &mut [VASurfaceID],
598 config: ffi::VAConfigID,
599 ) {
600 unsafe {
601 if let Some(image) = image {
602 (self.api.destroy_image)(self.dpy, image.image_id);
603 }
604 if context != ffi::VA_INVALID_ID {
605 (self.api.destroy_context)(self.dpy, context);
606 }
607 if !surfaces.is_empty() {
608 let n = c_int::try_from(surfaces.len()).unwrap_or(0);
609 (self.api.destroy_surfaces)(self.dpy, surfaces.as_mut_ptr(), n);
610 }
611 if config != ffi::VA_INVALID_ID {
612 (self.api.destroy_config)(self.dpy, config);
613 }
614 }
615 }
616}
617
618impl Drop for Display {
619 fn drop(&mut self) {
620 unsafe { (self.api.terminate)(self.dpy) };
621 }
622}
623
624pub fn formats(bits: u32) -> Result<(c_uint, u32, u32), String> {
625 match bits {
626 8 => Ok((ffi::VA_RT_FORMAT_YUV420, ffi::VA_FOURCC_NV12, 12)),
627 10 => Ok((ffi::VA_RT_FORMAT_YUV420_10, ffi::VA_FOURCC_P010, 24)),
628 _ => Err(format!("{bits}-bit pictures are not taken through VA-API")),
629 }
630}
631
632fn biplanar_view<'a>(image: &VAImage, data: &'a [u8]) -> Result<Biplanar<'a>, String> {
633 if !matches!(
634 image.format.fourcc,
635 ffi::VA_FOURCC_NV12 | ffi::VA_FOURCC_P010
636 ) || image.num_planes < 2
637 {
638 return Err(format!(
639 "the image is not NV12 / P010 (fourcc {:#x}, {} planes)",
640 image.format.fourcc, image.num_planes
641 ));
642 }
643 let [p0, p1, _] = image.pitches;
644 if p0 != p1 {
645 return Err(format!("NV12 planes with different pitches ({p0}, {p1})"));
646 }
647 let luma = data
648 .get(image.offsets[0] as usize..)
649 .ok_or("NV12 luma offset out of range")?;
650 let chroma = data
651 .get(image.offsets[1] as usize..)
652 .ok_or("NV12 chroma offset out of range")?;
653 Ok(Biplanar {
654 luma,
655 chroma,
656 stride: p0 as usize,
657 rows: usize::from(image.height),
658 })
659}
660
661pub struct Pool {
662 display: Arc<Display>,
663 id: u64,
664 ids: Vec<VASurfaceID>,
665 held: Vec<AtomicBool>,
666 size: (u32, u32),
667 bound: Mutex<(ffi::VAContextID, ffi::VAConfigID)>,
668}
669
670impl Pool {
671 fn new(
672 display: Arc<Display>,
673 rt_format: c_uint,
674 size: (u32, u32),
675 count: usize,
676 ) -> Result<Arc<Self>, String> {
677 static POOLS: AtomicU64 = AtomicU64::new(1);
678 let ids = display.create_surfaces(rt_format, size, count)?;
679 Ok(Arc::new(Pool {
680 held: ids.iter().map(|_| AtomicBool::new(false)).collect(),
681 display,
682 id: POOLS.fetch_add(1, Ordering::Relaxed),
683 ids,
684 size,
685 bound: Mutex::new((ffi::VA_INVALID_ID, ffi::VA_INVALID_ID)),
686 }))
687 }
688
689 fn bind(&self, context: ffi::VAContextID, config: ffi::VAConfigID) {
690 *self.bound.lock().unwrap_or_else(PoisonError::into_inner) = (context, config);
691 }
692
693 fn take(self: &Arc<Self>, geometry: Geometry) -> Option<GpuSurface> {
694 let index = self.held.iter().position(|h| {
695 h.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
696 .is_ok()
697 })?;
698 Some(GpuSurface {
699 pool: self.clone(),
700 index,
701 geometry,
702 pts: 0,
703 })
704 }
705}
706
707impl Drop for Pool {
708 fn drop(&mut self) {
709 let (context, config) = *self.bound.get_mut().unwrap_or_else(PoisonError::into_inner);
710 self.display.release(None, context, &mut self.ids, config);
711 }
712}
713
714pub struct GpuSurface {
715 pool: Arc<Pool>,
716 index: usize,
717 geometry: Geometry,
718 pub pts: i64,
719}
720
721impl Drop for GpuSurface {
722 fn drop(&mut self) {
723 if let Some(h) = self.pool.held.get(self.index) {
724 h.store(false, Ordering::Release);
725 }
726 }
727}
728
729impl GpuSurface {
730 pub fn width(&self) -> u32 {
731 self.geometry.crop.2
732 }
733
734 pub fn height(&self) -> u32 {
735 self.geometry.crop.3
736 }
737
738 fn id(&self) -> Result<VASurfaceID, String> {
739 self.pool
740 .ids
741 .get(self.index)
742 .copied()
743 .ok_or_else(|| "surface out of range".to_string())
744 }
745
746 pub fn key(&self) -> (u64, u32) {
747 (self.pool.id, self.index as u32)
748 }
749
750 pub fn sync(&self) -> Result<(), String> {
751 self.pool.display.sync(self.id()?)
752 }
753
754 pub fn dmabuf(&self, separate: bool) -> Result<DmaBuf, String> {
755 let d = self.pool.display.export(self.id()?, separate)?;
756 let count = (d.num_objects as usize).min(4);
757 let mut objects = Vec::with_capacity(count);
758 for o in &d.objects[..count] {
759 if o.fd < 0 {
760 return Err("vaExportSurfaceHandle gave no file descriptor".into());
761 }
762 objects.push(DmaObject {
763 fd: unsafe { OwnedFd::from_raw_fd(o.fd) },
764 size: o.size,
765 modifier: o.drm_format_modifier,
766 });
767 }
768 let layers = d.layers[..(d.num_layers as usize).min(4)]
769 .iter()
770 .map(|l| DmaLayer {
771 drm_format: l.drm_format,
772 planes: (0..(l.num_planes as usize).min(4))
773 .map(|i| DmaPlane {
774 object: l.object_index[i] as usize,
775 offset: l.offset[i],
776 pitch: l.pitch[i],
777 })
778 .collect(),
779 })
780 .collect();
781 Ok(DmaBuf {
782 fourcc: d.fourcc,
783 width: d.width,
784 height: d.height,
785 crop: self.geometry.crop,
786 objects,
787 layers,
788 })
789 }
790
791 pub fn download(&self) -> Result<Frame, String> {
792 let surface = self.id()?;
793 let d = &self.pool.display;
794 let geometry = self.geometry;
795 if let Ok(image) = d.derive(surface) {
796 let frame = d.with_image(&image, |data| {
797 biplanar::to_frame(&biplanar_view(&image, data)?, &geometry)
798 });
799 d.destroy_image(&image);
800 if let Ok(f) = frame {
801 return Ok(Frame { pts: self.pts, ..f });
802 }
803 }
804 let image = d.create_image(geometry.bits, self.pool.size)?;
805 let frame = d.get_image(surface, &image, self.pool.size).and_then(|()| {
806 d.with_image(&image, |data| {
807 biplanar::to_frame(&biplanar_view(&image, data)?, &geometry)
808 })
809 });
810 d.destroy_image(&image);
811 frame.map(|f| Frame { pts: self.pts, ..f })
812 }
813}
814
815pub struct DmaObject {
816 pub fd: OwnedFd,
817 pub size: u32,
818 pub modifier: u64,
819}
820
821pub struct DmaPlane {
822 pub object: usize,
823 pub offset: u32,
824 pub pitch: u32,
825}
826
827pub struct DmaLayer {
828 pub drm_format: u32,
829 pub planes: Vec<DmaPlane>,
830}
831
832pub struct DmaBuf {
833 pub fourcc: u32,
834 pub width: u32,
835 pub height: u32,
836 pub crop: (u32, u32, u32, u32),
837 pub objects: Vec<DmaObject>,
838 pub layers: Vec<DmaLayer>,
839}
840
841pub enum Picture {
842 Cpu(Frame),
843 Gpu(GpuSurface),
844}
845
846impl Picture {
847 pub fn at(self, pts: i64) -> Self {
848 match self {
849 Picture::Cpu(f) => Picture::Cpu(Frame { pts, ..f }),
850 Picture::Gpu(mut g) => {
851 g.pts = pts;
852 Picture::Gpu(g)
853 }
854 }
855 }
856}
857
858pub struct Session {
859 display: Arc<Display>,
860 config: ffi::VAConfigID,
861 context: ffi::VAContextID,
862 pool: Arc<Pool>,
863 image: Option<VAImage>,
864 size: (u32, u32),
865 geometry: Geometry,
866 derive: bool,
867 staging: Option<Staging>,
868 staged: bool,
869 pub gpu: bool,
870}
871
872struct Staged {
873 surface: VASurfaceID,
874 buffer: HostBuffer,
875 holds: Option<usize>,
876 order: u64,
877}
878
879struct Staging {
880 vpp: Vpp,
881 slots: Vec<Staged>,
882 pitch: usize,
883 chroma: usize,
884 size: (u32, u32),
885 bits: u32,
886 order: u64,
887 most: usize,
888}
889
890impl Staging {
891 fn new(
892 display: Arc<Display>,
893 geometry: Geometry,
894 surface: (u32, u32),
895 (count, most): (usize, usize),
896 ) -> Result<Self, String> {
897 let (w, h) = (
898 geometry.crop.2.next_multiple_of(2),
899 geometry.crop.3.next_multiple_of(2),
900 );
901 if geometry.crop.0.saturating_add(w) > surface.0
902 || geometry.crop.1.saturating_add(h) > surface.1
903 {
904 return Err("the picture has no even-sized area to copy".into());
905 }
906 let bps = if geometry.bits > 8 { 2 } else { 1 };
907 let pitch = (w as usize * bps).next_multiple_of(256);
908 let chroma = pitch * h as usize;
909 let vpp = Vpp::new(display.clone())?;
910 let mut staging = Self {
911 vpp,
912 slots: Vec::with_capacity(most),
913 pitch,
914 chroma,
915 size: (w, h),
916 bits: geometry.bits,
917 order: 0,
918 most,
919 };
920 for _ in 0..count.min(most) {
921 let slot = staging.slot()?;
922 staging.slots.push(slot);
923 }
924 Ok(staging)
925 }
926
927 fn slot(&self) -> Result<Staged, String> {
928 let ten = self.bits > 8;
929 let (rt_format, fourcc, _) = formats(self.bits)?;
930 let (w, h) = self.size;
931 let buffer = HostBuffer::new(self.chroma + self.pitch * (h as usize / 2))?;
932 let pitch = u32::try_from(self.pitch).map_err(|_| "picture too wide")?;
933 let mut layer = ffi::VADRMPRIMELayer {
934 drm_format: if ten {
935 ffi::DRM_FORMAT_P010
936 } else {
937 ffi::DRM_FORMAT_NV12
938 },
939 num_planes: 2,
940 ..Default::default()
941 };
942 layer.offset[1] = u32::try_from(self.chroma).map_err(|_| "picture too large")?;
943 layer.pitch = [pitch, pitch, 0, 0];
944 let mut descriptor = ffi::VADRMPRIMESurfaceDescriptor {
945 fourcc,
946 width: w,
947 height: h,
948 num_objects: 1,
949 num_layers: 1,
950 ..Default::default()
951 };
952 descriptor.objects[0] = ffi::VADRMPRIMEObject {
953 fd: buffer.fd(),
954 size: u32::try_from(buffer.len()).map_err(|_| "picture too large")?,
955 drm_format_modifier: ffi::DRM_FORMAT_MOD_LINEAR,
956 };
957 descriptor.layers[0] = layer;
958 let surface = self
959 .vpp
960 .display
961 .import(rt_format, (w, h), &mut descriptor)?;
962 Ok(Staged {
963 surface,
964 buffer,
965 holds: None,
966 order: 0,
967 })
968 }
969
970 fn copy(
971 &mut self,
972 src: VASurfaceID,
973 index: usize,
974 crop: (u32, u32, u32, u32),
975 ) -> Result<(), String> {
976 for slot in &mut self.slots {
977 if slot.holds == Some(index) {
978 slot.holds = None;
979 }
980 }
981 if self.slots.iter().all(|s| s.holds.is_some())
982 && self.slots.len() < self.most
983 && let Ok(slot) = self.slot()
984 {
985 self.slots.push(slot);
986 }
987 let Some(slot) = self
988 .slots
989 .iter_mut()
990 .min_by_key(|s| (s.holds.is_some(), s.order))
991 else {
992 return Err("no staging surfaces".into());
993 };
994 slot.holds = None;
995 let region = (crop.0, crop.1, self.size.0, self.size.1);
996 self.vpp.copy(src, region, slot.surface, self.size)?;
997 self.order += 1;
998 slot.holds = Some(index);
999 slot.order = self.order;
1000 Ok(())
1001 }
1002
1003 fn read(&mut self, index: usize, crop: (u32, u32, u32, u32)) -> Option<Result<Frame, String>> {
1004 let slot = self.slots.iter_mut().find(|s| s.holds == Some(index))?;
1005 slot.holds = None;
1006 let display = &self.vpp.display;
1007 if let Err(e) = display.sync(slot.surface) {
1008 return Some(Err(e));
1009 }
1010 let data = slot.buffer.data();
1011 let (Some(luma), Some(chroma)) = (data.get(..self.chroma), data.get(self.chroma..)) else {
1012 return Some(Err("staging buffer too small".into()));
1013 };
1014 let view = Biplanar {
1015 luma,
1016 chroma,
1017 stride: self.pitch,
1018 rows: self.size.1 as usize,
1019 };
1020 let geometry = Geometry {
1021 crop: (0, 0, crop.2, crop.3),
1022 bits: self.bits,
1023 };
1024 Some(biplanar::to_frame(&view, &geometry))
1025 }
1026}
1027
1028impl Drop for Staging {
1029 fn drop(&mut self) {
1030 let mut ids: Vec<VASurfaceID> = self.slots.iter().map(|s| s.surface).collect();
1031 self.vpp
1032 .display
1033 .release(None, ffi::VA_INVALID_ID, &mut ids, ffi::VA_INVALID_ID);
1034 }
1035}
1036
1037impl Session {
1038 pub fn new(
1039 profiles: &[ffi::VAProfile],
1040 size: (u32, u32),
1041 count: usize,
1042 geometry: Geometry,
1043 ) -> Result<Self, String> {
1044 with_display(|d| Self::on(d, profiles, size, count, geometry))
1045 }
1046
1047 fn on(
1048 display: Arc<Display>,
1049 profiles: &[ffi::VAProfile],
1050 size: (u32, u32),
1051 count: usize,
1052 geometry: Geometry,
1053 ) -> Result<Self, String> {
1054 let (rt_format, _, _) = formats(geometry.bits)?;
1055 let mut config = None;
1056 let mut why = "no VA-API profile for the stream".to_string();
1057 for &profile in profiles {
1058 let value = match display.attributes(
1059 profile,
1060 ffi::VAEntrypointVLD,
1061 &[ffi::VAConfigAttribRTFormat],
1062 ) {
1063 Ok(v) => v.first().copied().unwrap_or(0),
1064 Err(e) => {
1065 why = e;
1066 continue;
1067 }
1068 };
1069 if value == ffi::VA_ATTRIB_NOT_SUPPORTED || value & rt_format == 0 {
1070 why = format!(
1071 "profile {profile} does not decode to {}-bit 4:2:0",
1072 geometry.bits
1073 );
1074 continue;
1075 }
1076 let mut attr = [VAConfigAttrib {
1077 type_: ffi::VAConfigAttribRTFormat,
1078 value: rt_format,
1079 }];
1080 match display.create_config(profile, ffi::VAEntrypointVLD, &mut attr) {
1081 Ok(id) => {
1082 config = Some(id);
1083 break;
1084 }
1085 Err(e) => why = e,
1086 }
1087 }
1088 let config = config.ok_or(why)?;
1089 let size = (size.0.next_multiple_of(2), size.1.next_multiple_of(2));
1090 let pool = match Pool::new(display.clone(), rt_format, size, count) {
1091 Ok(p) => p,
1092 Err(e) => {
1093 display.release(None, ffi::VA_INVALID_ID, &mut [], config);
1094 return Err(e);
1095 }
1096 };
1097 pool.bind(ffi::VA_INVALID_ID, config);
1098 let mut s = Self {
1099 display,
1100 config,
1101 context: ffi::VA_INVALID_ID,
1102 pool,
1103 image: None,
1104 size,
1105 geometry,
1106 derive: std::env::var_os("PITO_VA_NO_DERIVE").is_none(),
1107 staging: None,
1108 staged: std::env::var_os("PITO_VA_NO_STAGING").is_none(),
1109 gpu: false,
1110 };
1111 let mut targets = s.pool.ids.clone();
1112 s.context = s.display.create_context(s.config, size, &mut targets)?;
1113 s.pool.bind(s.context, s.config);
1114 Ok(s)
1115 }
1116
1117 pub fn vendor(&self) -> &str {
1118 self.display.vendor()
1119 }
1120
1121 pub fn direct(&mut self) {
1122 self.staged = false;
1123 self.staging = None;
1124 }
1125
1126 fn image(&mut self) -> Result<VAImage, String> {
1127 if let Some(image) = self.image {
1128 return Ok(image);
1129 }
1130 let image = self.display.create_image(self.geometry.bits, self.size)?;
1131 self.image = Some(image);
1132 Ok(image)
1133 }
1134
1135 fn surface(&self, index: usize) -> Result<VASurfaceID, String> {
1136 self.pool
1137 .ids
1138 .get(index)
1139 .copied()
1140 .ok_or_else(|| "surface out of range".to_string())
1141 }
1142
1143 fn get_image(&mut self, index: usize) -> Result<VAImage, String> {
1144 let surface = self.surface(index)?;
1145 let image = self.image()?;
1146 self.display.get_image(surface, &image, self.size)?;
1147 Ok(image)
1148 }
1149
1150 pub(super) fn decode_picture<P, Q, S>(
1151 &mut self,
1152 target: usize,
1153 pic: &P,
1154 iq: Option<&Q>,
1155 slices: &[(S, Vec<u8>)],
1156 ) -> Result<(), String> {
1157 let surface = self.surface(target)?;
1158 let mut buffers = Vec::with_capacity(2 + 2 * slices.len());
1159 let decoded = self.decode_into(surface, &mut buffers, pic, iq, slices);
1160 self.display.destroy_buffers(&buffers);
1161 decoded
1162 }
1163
1164 fn decode_into<P, Q, S>(
1165 &self,
1166 surface: VASurfaceID,
1167 buffers: &mut Vec<ffi::VABufferID>,
1168 pic: &P,
1169 iq: Option<&Q>,
1170 slices: &[(S, Vec<u8>)],
1171 ) -> Result<(), String> {
1172 let d = &self.display;
1173 let ctx = self.context;
1174 buffers.push(d.value_buffer(ctx, ffi::VAPictureParameterBufferType, pic)?);
1175 if let Some(iq) = iq {
1176 buffers.push(d.value_buffer(ctx, ffi::VAIQMatrixBufferType, iq)?);
1177 }
1178 let head = buffers.len();
1179 for (params, data) in slices {
1180 buffers.push(d.value_buffer(ctx, ffi::VASliceParameterBufferType, params)?);
1181 buffers.push(d.buffer(
1182 ctx,
1183 ffi::VASliceDataBufferType,
1184 data.as_ptr().cast(),
1185 data.len(),
1186 )?);
1187 }
1188 let (picture, slice_buffers) = buffers.split_at_mut(head);
1189 let mut groups: Vec<&mut [ffi::VABufferID]> = vec![picture];
1190 groups.extend(slice_buffers.chunks_mut(2));
1191 d.picture(ctx, surface, &mut groups)
1192 }
1193
1194 fn read_cpu(&mut self, index: usize) -> Result<Frame, String> {
1195 if let Some(staging) = &mut self.staging
1196 && let Some(frame) = staging.read(index, self.geometry.crop)
1197 {
1198 match frame {
1199 Ok(frame) => return Ok(frame),
1200 Err(_) => {
1201 self.staging = None;
1202 self.staged = false;
1203 }
1204 }
1205 }
1206 if self.derive {
1207 let surface = self.surface(index)?;
1208 match self.display.derive(surface) {
1209 Ok(image) => {
1210 let geometry = self.geometry;
1211 let frame = self.display.with_image(&image, |data| {
1212 biplanar::to_frame(&biplanar_view(&image, data)?, &geometry)
1213 });
1214 self.display.destroy_image(&image);
1215 if frame.is_ok() {
1216 return frame;
1217 }
1218 self.derive = false;
1219 }
1220 Err(_) => self.derive = false,
1221 }
1222 }
1223 let image = self.get_image(index)?;
1224 let geometry = self.geometry;
1225 self.display.with_image(&image, |data| {
1226 biplanar::to_frame(&biplanar_view(&image, data)?, &geometry)
1227 })
1228 }
1229}
1230
1231impl Accel for Session {
1232 fn surfaces(&self) -> &[VASurfaceID] {
1233 &self.pool.ids
1234 }
1235
1236 fn held(&self, index: usize) -> bool {
1237 self.pool
1238 .held
1239 .get(index)
1240 .is_some_and(|h| h.load(Ordering::Acquire))
1241 }
1242
1243 fn decode_h264(
1244 &mut self,
1245 target: usize,
1246 pic: &ffi::VAPictureParameterBufferH264,
1247 iq: &ffi::VAIQMatrixBufferH264,
1248 slices: &[(ffi::VASliceParameterBufferH264, Vec<u8>)],
1249 ) -> Result<(), String> {
1250 self.decode_picture(target, pic, Some(iq), slices)
1251 }
1252
1253 fn decode_hevc(
1254 &mut self,
1255 target: usize,
1256 pic: &ffi::VAPictureParameterBufferHEVC,
1257 iq: Option<&ffi::VAIQMatrixBufferHEVC>,
1258 slices: &[(ffi::VASliceParameterBufferHEVC, Vec<u8>)],
1259 ) -> Result<(), String> {
1260 self.decode_picture(target, pic, iq, slices)
1261 }
1262
1263 fn copies(&self) -> bool {
1264 !self.gpu && self.staged
1265 }
1266
1267 fn prefetch(&mut self, index: usize) {
1268 if self.gpu || !self.staged {
1269 return;
1270 }
1271 let Ok(surface) = self.surface(index) else {
1272 return;
1273 };
1274 if self.staging.is_none() {
1275 match Staging::new(
1276 self.display.clone(),
1277 self.geometry,
1278 self.size,
1279 (LAG + 2, LAG + SLACK + 2),
1280 ) {
1281 Ok(s) => self.staging = Some(s),
1282 Err(_) => {
1283 self.staged = false;
1284 return;
1285 }
1286 }
1287 }
1288 if let Some(staging) = &mut self.staging
1289 && staging.copy(surface, index, self.geometry.crop).is_err()
1290 {
1291 self.staging = None;
1292 self.staged = false;
1293 }
1294 }
1295
1296 fn read(&mut self, index: usize) -> Result<Picture, String> {
1297 if !self.gpu {
1298 return self.read_cpu(index).map(Picture::Cpu);
1299 }
1300 let flag = self.pool.held.get(index).ok_or("surface out of range")?;
1301 flag.store(true, Ordering::Release);
1302 Ok(Picture::Gpu(GpuSurface {
1303 pool: self.pool.clone(),
1304 index,
1305 geometry: self.geometry,
1306 pts: 0,
1307 }))
1308 }
1309
1310 fn fill(&mut self, index: usize, from: Option<usize>) -> Result<(), String> {
1311 let surface = self.surface(index)?;
1312 let image = match from {
1313 Some(from) => self.get_image(from)?,
1314 None => {
1315 let image = self.image()?;
1316 let ten = self.geometry.bits > 8;
1317 self.display.with_image(&image, |data| {
1318 if ten {
1319 for w in data.as_chunks_mut::<2>().0 {
1320 w.copy_from_slice(&0x8000u16.to_le_bytes());
1321 }
1322 } else {
1323 data.fill(128);
1324 }
1325 Ok(())
1326 })?;
1327 image
1328 }
1329 };
1330 self.display.put_image(surface, &image, self.size)
1331 }
1332}
1333
1334impl Drop for Session {
1335 fn drop(&mut self) {
1336 self.display.release(
1337 self.image.take(),
1338 ffi::VA_INVALID_ID,
1339 &mut [],
1340 ffi::VA_INVALID_ID,
1341 );
1342 }
1343}
1344
1345pub struct Vpp {
1346 display: Arc<Display>,
1347 config: ffi::VAConfigID,
1348 context: ffi::VAContextID,
1349 owned: bool,
1350}
1351
1352impl Vpp {
1353 fn new(display: Arc<Display>) -> Result<Self, String> {
1354 let config =
1355 display.create_config(ffi::VAProfileNone, ffi::VAEntrypointVideoProc, &mut [])?;
1356 let mut vpp = Self {
1357 display,
1358 config,
1359 context: ffi::VA_INVALID_ID,
1360 owned: true,
1361 };
1362 vpp.context = vpp.display.create_context(vpp.config, (0, 0), &mut [])?;
1363 Ok(vpp)
1364 }
1365
1366 fn blit(&self, src: &GpuSurface, dst: VASurfaceID, size: (u32, u32)) -> Result<(), String> {
1367 self.copy(src.id()?, src.geometry.crop, dst, size)
1368 }
1369
1370 fn copy(
1371 &self,
1372 src: VASurfaceID,
1373 crop: (u32, u32, u32, u32),
1374 dst: VASurfaceID,
1375 size: (u32, u32),
1376 ) -> Result<(), String> {
1377 self.compose(src, crop, dst, size, None)
1378 }
1379
1380 fn compose(
1381 &self,
1382 src: VASurfaceID,
1383 crop: (u32, u32, u32, u32),
1384 dst: VASurfaceID,
1385 size: (u32, u32),
1386 over: Option<Over>,
1387 ) -> Result<(), String> {
1388 let from = rectangle(crop)?;
1389 let to = rectangle((0, 0, size.0, size.1))?;
1390 let video = pipeline(
1391 src,
1392 &from,
1393 &to,
1394 ffi::VAProcColorStandardBT709,
1395 std::ptr::null(),
1396 );
1397 let mut buffers = vec![self.display.value_buffer(
1398 self.context,
1399 ffi::VAProcPipelineParameterBufferType,
1400 &video,
1401 )?];
1402 let blend = ffi::VABlendState {
1403 flags: ffi::VA_BLEND_PREMULTIPLIED_ALPHA,
1404 global_alpha: 1.0,
1405 ..Default::default()
1406 };
1407 if let Some(o) = over {
1408 let painted = rectangle((0, 0, o.rect.2, o.rect.3))?;
1409 let placed = rectangle(o.rect)?;
1410 let mut layer = pipeline(
1411 o.surface,
1412 &painted,
1413 &placed,
1414 o.standard,
1415 (&blend as *const ffi::VABlendState).cast(),
1416 );
1417 layer.output_color_properties.chroma_sample_location =
1418 ffi::VA_CHROMA_SITING_VERTICAL_CENTER | ffi::VA_CHROMA_SITING_HORIZONTAL_CENTER;
1419 match self.display.value_buffer(
1420 self.context,
1421 ffi::VAProcPipelineParameterBufferType,
1422 &layer,
1423 ) {
1424 Ok(id) => buffers.push(id),
1425 Err(e) => {
1426 self.display.destroy_buffers(&buffers);
1427 return Err(e);
1428 }
1429 }
1430 }
1431 let mut groups: Vec<&mut [ffi::VABufferID]> = vec![&mut buffers[..]];
1432 let done = self.display.picture(self.context, dst, &mut groups);
1433 self.display.destroy_buffers(&buffers);
1434 done
1435 }
1436}
1437
1438fn rectangle((x, y, w, h): (u32, u32, u32, u32)) -> Result<ffi::VARectangle, String> {
1439 let narrow = |v: u32| u16::try_from(v).map_err(|_| "picture too large".to_string());
1440 let wide = |v: u32| i16::try_from(v).map_err(|_| "picture too large".to_string());
1441 Ok(ffi::VARectangle {
1442 x: wide(x)?,
1443 y: wide(y)?,
1444 width: narrow(w)?,
1445 height: narrow(h)?,
1446 })
1447}
1448
1449fn pipeline(
1450 surface: VASurfaceID,
1451 from: &ffi::VARectangle,
1452 to: &ffi::VARectangle,
1453 standard: c_int,
1454 blend_state: *const c_void,
1455) -> ffi::VAProcPipelineParameterBuffer {
1456 ffi::VAProcPipelineParameterBuffer {
1457 surface,
1458 surface_region: from,
1459 surface_color_standard: standard,
1460 output_region: to,
1461 output_background_color: 0xff00_0000,
1462 output_color_standard: standard,
1463 pipeline_flags: 0,
1464 filter_flags: 0,
1465 filters: std::ptr::null_mut(),
1466 num_filters: 0,
1467 forward_references: std::ptr::null_mut(),
1468 num_forward_references: 0,
1469 backward_references: std::ptr::null_mut(),
1470 num_backward_references: 0,
1471 rotation_state: 0,
1472 blend_state,
1473 mirror_state: 0,
1474 additional_outputs: std::ptr::null_mut(),
1475 num_additional_outputs: 0,
1476 input_surface_flag: 0,
1477 output_surface_flag: 0,
1478 input_color_properties: ffi::VAProcColorProperties::default(),
1479 output_color_properties: ffi::VAProcColorProperties::default(),
1480 processing_mode: 0,
1481 output_hdr_metadata: std::ptr::null_mut(),
1482 va_reserved: [0; ffi::PROC_RESERVED],
1483 }
1484}
1485
1486#[derive(Clone, Copy)]
1487struct Over {
1488 surface: VASurfaceID,
1489 rect: (u32, u32, u32, u32),
1490 standard: c_int,
1491}
1492
1493const PAINTED: usize = 3;
1494
1495struct Painted {
1496 surface: VASurfaceID,
1497 image: VAImage,
1498 size: (u32, u32),
1499}
1500
1501struct Shown {
1502 layer: Arc<Rgba>,
1503 size: (u32, u32),
1504 over: Option<Over>,
1505}
1506
1507struct Painter {
1508 display: Arc<Display>,
1509 ring: Vec<Painted>,
1510 next: usize,
1511 layer: Option<Shown>,
1512}
1513
1514impl Painter {
1515 fn new(display: Arc<Display>) -> Self {
1516 Self {
1517 display,
1518 ring: Vec::new(),
1519 next: 0,
1520 layer: None,
1521 }
1522 }
1523
1524 fn paint(&mut self, layer: &Arc<Rgba>, size: (u32, u32)) -> Result<Option<Over>, String> {
1525 if let Some(shown) = &self.layer
1526 && Arc::ptr_eq(&shown.layer, layer)
1527 && shown.size == size
1528 {
1529 return Ok(shown.over);
1530 }
1531 self.layer = None;
1532 if (layer.width, layer.height) != size {
1533 return Err(format!(
1534 "the overlay is {}x{}, the picture {}x{}",
1535 layer.width, layer.height, size.0, size.1
1536 ));
1537 }
1538 let over = match painted_box(layer) {
1539 Some(rect) => Some(self.upload(layer, rect, size)?),
1540 None => None,
1541 };
1542 self.layer = Some(Shown {
1543 layer: layer.clone(),
1544 size,
1545 over,
1546 });
1547 Ok(over)
1548 }
1549
1550 fn upload(
1551 &mut self,
1552 layer: &Rgba,
1553 (x, y, w, h): (u32, u32, u32, u32),
1554 size: (u32, u32),
1555 ) -> Result<Over, String> {
1556 let want = (w.next_multiple_of(64), h.next_multiple_of(64));
1557 let i = self.next % PAINTED;
1558 self.next = self.next.wrapping_add(1);
1559 let fits = self
1560 .ring
1561 .get(i)
1562 .is_some_and(|p| p.size.0 >= want.0 && p.size.1 >= want.1);
1563 if !fits {
1564 let surface = self.display.create_rgba(want)?;
1565 let image = match self.display.create_rgba_image(want) {
1566 Ok(image) => image,
1567 Err(e) => {
1568 self.display.release(
1569 None,
1570 ffi::VA_INVALID_ID,
1571 &mut [surface],
1572 ffi::VA_INVALID_ID,
1573 );
1574 return Err(e);
1575 }
1576 };
1577 let fresh = Painted {
1578 surface,
1579 image,
1580 size: want,
1581 };
1582 if i < self.ring.len() {
1583 let mut old = std::mem::replace(&mut self.ring[i], fresh);
1584 self.display.release(
1585 Some(old.image),
1586 ffi::VA_INVALID_ID,
1587 std::slice::from_mut(&mut old.surface),
1588 ffi::VA_INVALID_ID,
1589 );
1590 } else {
1591 self.ring.push(fresh);
1592 }
1593 }
1594 let slot = self.ring.get(i).ok_or("no overlay surface")?;
1595 let image = slot.image;
1596 let (pitch, offset) = (image.pitches[0] as usize, image.offsets[0] as usize);
1597 let width = layer.width as usize;
1598 self.display.with_image(&image, |data| {
1599 data.fill(0);
1600 for r in 0..h as usize {
1601 let from = ((y as usize + r) * width + x as usize) * 4;
1602 let src = layer
1603 .data
1604 .get(from..from + w as usize * 4)
1605 .ok_or("the overlay is shorter than its size")?;
1606 let at = offset + r * pitch;
1607 let dst = data
1608 .get_mut(at..at + w as usize * 4)
1609 .ok_or("the overlay image is too small")?;
1610 for (d, s) in dst.chunks_exact_mut(4).zip(src.chunks_exact(4)) {
1611 let a = u32::from(s[3]);
1612 for k in 0..3 {
1613 d[k] = ((u32::from(s[k]) * a + 127) / 255) as u8;
1614 }
1615 d[3] = s[3];
1616 }
1617 }
1618 Ok(())
1619 })?;
1620 self.display.put_image(slot.surface, &image, slot.size)?;
1621 Ok(Over {
1622 surface: slot.surface,
1623 rect: (x, y, w, h),
1624 standard: if size.1 >= 720 {
1625 ffi::VAProcColorStandardBT709
1626 } else {
1627 ffi::VAProcColorStandardBT601
1628 },
1629 })
1630 }
1631}
1632
1633impl Drop for Painter {
1634 fn drop(&mut self) {
1635 for p in &mut self.ring {
1636 self.display.release(
1637 Some(p.image),
1638 ffi::VA_INVALID_ID,
1639 std::slice::from_mut(&mut p.surface),
1640 ffi::VA_INVALID_ID,
1641 );
1642 }
1643 }
1644}
1645
1646fn painted_box(layer: &Rgba) -> Option<(u32, u32, u32, u32)> {
1647 let (w, h) = (layer.width as usize, layer.height as usize);
1648 let rows = layer.data.get(..w * h * 4)?.chunks_exact(w * 4);
1649 let mut span: Option<(usize, usize, usize, usize)> = None;
1650 for (y, row) in rows.enumerate() {
1651 let alpha = || row.iter().skip(3).step_by(4);
1652 let Some(first) = alpha().position(|&a| a != 0) else {
1653 continue;
1654 };
1655 let last = w - 1 - alpha().rev().position(|&a| a != 0).unwrap_or(0);
1656 span = Some(match span {
1657 Some((x0, y0, x1, _)) => (x0.min(first), y0, x1.max(last), y),
1658 None => (first, y, last, y),
1659 });
1660 }
1661 let (x0, y0, x1, y1) = span?;
1662 let (x0, y0) = (x0 & !1, y0 & !1);
1663 let (x1, y1) = (((x1 + 2) & !1).min(w), ((y1 + 2) & !1).min(h));
1664 Some((x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32))
1665}
1666
1667pub struct Scaler {
1668 vpp: Vpp,
1669 pool: Arc<Pool>,
1670 size: (u32, u32),
1671 bits: u32,
1672}
1673
1674impl Scaler {
1675 pub fn new(size: (u32, u32), bits: u32, count: usize) -> Result<Self, String> {
1676 let display = shared()?;
1677 let (rt_format, _, _) = formats(bits)?;
1678 let mut vpp = Vpp::new(display.clone())?;
1679 let pool = Pool::new(display, rt_format, size, count.max(1))?;
1680 pool.bind(vpp.context, vpp.config);
1681 vpp.owned = false;
1682 Ok(Self {
1683 vpp,
1684 pool,
1685 size,
1686 bits,
1687 })
1688 }
1689
1690 pub fn scale(&self, src: &GpuSurface) -> Result<GpuSurface, String> {
1691 if !Arc::ptr_eq(&src.pool.display, &self.vpp.display) {
1692 return Err("the GPU frame is on another VA display".into());
1693 }
1694 let geometry = Geometry {
1695 crop: (0, 0, self.size.0, self.size.1),
1696 bits: self.bits,
1697 };
1698 let mut out = self
1699 .pool
1700 .take(geometry)
1701 .ok_or_else(|| format!("all {} scaled frames are still held", self.pool.ids.len()))?;
1702 self.vpp.blit(src, out.id()?, self.size)?;
1703 out.pts = src.pts;
1704 Ok(out)
1705 }
1706}
1707
1708impl Drop for Vpp {
1709 fn drop(&mut self) {
1710 if !self.owned {
1711 return;
1712 }
1713 self.display
1714 .release(None, self.context, &mut [], self.config);
1715 }
1716}
1717
1718pub fn probe() -> Result<Arc<Display>, String> {
1719 static PROBE: OnceLock<Result<(), String>> = OnceLock::new();
1720 PROBE.get_or_init(|| shared().map(|_| ())).clone()?;
1721 shared()
1722}
1723
1724fn write_frame(
1725 image: &VAImage,
1726 data: &mut [u8],
1727 frame: &Frame,
1728 size: (u32, u32),
1729) -> Result<(), String> {
1730 if !matches!(
1731 image.format.fourcc,
1732 ffi::VA_FOURCC_NV12 | ffi::VA_FOURCC_P010
1733 ) || image.num_planes < 2
1734 {
1735 return Err(format!(
1736 "the input image is not NV12 / P010 (fourcc {:#x})",
1737 image.format.fourcc
1738 ));
1739 }
1740 let [p0, p1, _] = image.pitches;
1741 let (o0, o1) = (image.offsets[0] as usize, image.offsets[1] as usize);
1742 biplanar::from_frame(frame, data, (o0, p0 as usize), (o1, p1 as usize), size)
1743}
1744
1745pub struct Slot {
1746 pub input: VASurfaceID,
1747 pub coded: ffi::VABufferID,
1748}
1749
1750pub struct EncodeSession {
1751 display: Arc<Display>,
1752 config: ffi::VAConfigID,
1753 context: ffi::VAContextID,
1754 surfaces: Vec<VASurfaceID>,
1755 coded: Vec<ffi::VABufferID>,
1756 image: Option<VAImage>,
1757 size: (u32, u32),
1758 bits: u32,
1759 inputs: usize,
1760 derive: bool,
1761 vpp: Option<Vpp>,
1762 painter: Option<Painter>,
1763}
1764
1765pub struct EncodeSetup {
1766 pub profile: ffi::VAProfile,
1767 pub rate_control: u32,
1768 pub size: (u32, u32),
1769 pub bits: u32,
1770 pub inputs: usize,
1771 pub references: usize,
1772 pub coded_size: usize,
1773}
1774
1775impl EncodeSession {
1776 pub fn new(setup: &EncodeSetup) -> Result<Self, String> {
1777 with_display(|d| Self::on(d, setup))
1778 }
1779
1780 fn on(display: Arc<Display>, setup: &EncodeSetup) -> Result<Self, String> {
1781 let (rt_format, _, _) = formats(setup.bits)?;
1782 let mut why = String::new();
1783 let mut config = None;
1784 for entrypoint in [ffi::VAEntrypointEncSlice, ffi::VAEntrypointEncSliceLP] {
1785 let mut attrs = [
1786 VAConfigAttrib {
1787 type_: ffi::VAConfigAttribRTFormat,
1788 value: rt_format,
1789 },
1790 VAConfigAttrib {
1791 type_: ffi::VAConfigAttribRateControl,
1792 value: setup.rate_control,
1793 },
1794 VAConfigAttrib {
1795 type_: ffi::VAConfigAttribEncPackedHeaders,
1796 value: ffi::VA_ENC_PACKED_HEADER_SEQUENCE
1797 | ffi::VA_ENC_PACKED_HEADER_PICTURE
1798 | ffi::VA_ENC_PACKED_HEADER_SLICE,
1799 },
1800 ];
1801 match display.create_config(setup.profile, entrypoint, &mut attrs) {
1802 Ok(id) => {
1803 config = Some(id);
1804 break;
1805 }
1806 Err(e) => why = e,
1807 }
1808 }
1809 let config = config.ok_or(why)?;
1810 let mut s = Self {
1811 display,
1812 config,
1813 context: ffi::VA_INVALID_ID,
1814 surfaces: Vec::new(),
1815 coded: Vec::new(),
1816 image: None,
1817 size: setup.size,
1818 bits: setup.bits,
1819 inputs: setup.inputs,
1820 derive: std::env::var_os("PITO_VA_NO_DERIVE").is_none(),
1821 vpp: None,
1822 painter: None,
1823 };
1824 s.surfaces =
1825 s.display
1826 .create_surfaces(rt_format, setup.size, setup.inputs + setup.references)?;
1827 s.context = s
1828 .display
1829 .create_context(s.config, setup.size, &mut s.surfaces)?;
1830 for _ in 0..setup.inputs {
1831 let id = s.display.buffer(
1832 s.context,
1833 ffi::VAEncCodedBufferType,
1834 std::ptr::null(),
1835 setup.coded_size,
1836 )?;
1837 s.coded.push(id);
1838 }
1839 Ok(s)
1840 }
1841
1842 pub fn vendor(&self) -> &str {
1843 self.display.vendor()
1844 }
1845
1846 pub fn slot(&self, i: usize) -> Option<Slot> {
1847 Some(Slot {
1848 input: *self.surfaces.get(i)?,
1849 coded: *self.coded.get(i)?,
1850 })
1851 }
1852
1853 pub fn reference(&self, i: usize) -> Option<VASurfaceID> {
1854 self.surfaces.get(self.inputs + i).copied()
1855 }
1856
1857 pub fn blit(
1858 &mut self,
1859 surface: VASurfaceID,
1860 src: &GpuSurface,
1861 size: (u32, u32),
1862 ) -> Result<(), String> {
1863 if !Arc::ptr_eq(&src.pool.display, &self.display) {
1864 return Err("the GPU frame is on another VA display".into());
1865 }
1866 if self.vpp.is_none() {
1867 self.vpp = Some(Vpp::new(self.display.clone())?);
1868 }
1869 match &self.vpp {
1870 Some(vpp) => vpp.blit(src, surface, size),
1871 None => Err("no VA-API video processor".into()),
1872 }
1873 }
1874
1875 pub fn composes(&self) -> bool {
1876 self.display.vendor().starts_with("Mesa")
1877 }
1878
1879 pub fn blit_over(
1880 &mut self,
1881 surface: VASurfaceID,
1882 src: &GpuSurface,
1883 size: (u32, u32),
1884 layer: &Arc<Rgba>,
1885 ) -> Result<(), String> {
1886 if !self.composes() {
1887 return Err(format!(
1888 "drawing an overlay on the GPU is tested only on Mesa's VA-API, not {}",
1889 self.display.vendor()
1890 ));
1891 }
1892 if !Arc::ptr_eq(&src.pool.display, &self.display) {
1893 return Err("the GPU frame is on another VA display".into());
1894 }
1895 if self.vpp.is_none() {
1896 self.vpp = Some(Vpp::new(self.display.clone())?);
1897 }
1898 let painter = self
1899 .painter
1900 .get_or_insert_with(|| Painter::new(self.display.clone()));
1901 let over = painter.paint(layer, size)?;
1902 match &self.vpp {
1903 Some(vpp) => vpp.compose(src.id()?, src.geometry.crop, surface, size, over),
1904 None => Err("no VA-API video processor".into()),
1905 }
1906 }
1907
1908 pub fn upload(&mut self, surface: VASurfaceID, frame: &Frame) -> Result<(), String> {
1909 let size = self.size;
1910 if self.derive {
1911 match self.display.derive(surface) {
1912 Ok(image) => {
1913 let written = self
1914 .display
1915 .with_image(&image, |data| write_frame(&image, data, frame, size));
1916 self.display.destroy_image(&image);
1917 if written.is_ok() {
1918 return Ok(());
1919 }
1920 self.derive = false;
1921 }
1922 Err(_) => self.derive = false,
1923 }
1924 }
1925 let image = match self.image {
1926 Some(i) => i,
1927 None => {
1928 let i = self.display.create_image(self.bits, self.size)?;
1929 self.image = Some(i);
1930 i
1931 }
1932 };
1933 self.display
1934 .with_image(&image, |data| write_frame(&image, data, frame, size))?;
1935 self.display.put_image(surface, &image, self.size)
1936 }
1937
1938 pub fn encode(
1939 &self,
1940 input: VASurfaceID,
1941 params: &[(ffi::VABufferType, Vec<u8>)],
1942 ) -> Result<(), String> {
1943 let mut buffers = Vec::with_capacity(params.len());
1944 let mut made = Ok(());
1945 for (ty, bytes) in params {
1946 match self
1947 .display
1948 .buffer(self.context, *ty, bytes.as_ptr().cast(), bytes.len())
1949 {
1950 Ok(id) => buffers.push(id),
1951 Err(e) => {
1952 made = Err(e);
1953 break;
1954 }
1955 }
1956 }
1957 let result = made.and_then(|()| {
1958 let mut groups: Vec<&mut [ffi::VABufferID]> = vec![&mut buffers[..]];
1959 self.display.picture(self.context, input, &mut groups)
1960 });
1961 self.display.destroy_buffers(&buffers);
1962 result
1963 }
1964
1965 pub fn collect(&self, input: VASurfaceID, coded: ffi::VABufferID) -> Result<Vec<u8>, String> {
1966 self.display.sync(input)?;
1967 self.display.with_mapped(coded, |ptr| {
1968 let mut out = Vec::new();
1969 let mut seg = ptr.cast::<ffi::VACodedBufferSegment>();
1970 let mut guard = 0;
1971 while !seg.is_null() && guard < 64 {
1972 let s = unsafe { *seg };
1973 if !s.buf.is_null() && s.size > 0 {
1974 let bytes =
1975 unsafe { std::slice::from_raw_parts(s.buf.cast::<u8>(), s.size as usize) };
1976 out.extend_from_slice(bytes);
1977 }
1978 seg = s.next.cast();
1979 guard += 1;
1980 }
1981 Ok(out)
1982 })
1983 }
1984}
1985
1986impl Drop for EncodeSession {
1987 fn drop(&mut self) {
1988 self.painter = None;
1989 self.vpp = None;
1990 self.display.destroy_buffers(&self.coded);
1991 self.display.release(
1992 self.image.take(),
1993 self.context,
1994 &mut self.surfaces,
1995 self.config,
1996 );
1997 }
1998}
1999
2000#[cfg(test)]
2001mod tests {
2002 use super::painted_box;
2003 use crate::media::Rgba;
2004
2005 #[test]
2006 fn the_painted_box_covers_every_visible_pixel_on_even_bounds() {
2007 let mut layer = Rgba::new(16, 10);
2008 assert_eq!(painted_box(&layer), None);
2009 for (x, y) in [(3, 3), (10, 6)] {
2010 layer.data[(y * 16 + x) * 4 + 3] = 1;
2011 }
2012 assert_eq!(painted_box(&layer), Some((2, 2, 10, 6)));
2013 layer.data[(9 * 16 + 15) * 4 + 3] = 255;
2014 assert_eq!(painted_box(&layer), Some((2, 2, 14, 8)));
2015 }
2016}