1use std::cell::RefCell;
33use std::collections::HashMap;
34use std::path::Path;
35use std::rc::Rc;
36use std::sync::Arc;
37
38use crate::dmabuf::{rt_format, DmaBuf};
39use crate::{
40 bindings, BufferType, Color, Config as VaConfig, Context, Display, Picture, ProcColorProperties,
41 ProcPipelineParameterBuffer, Surface, SurfaceMemoryDescriptor, UsageHint, VAConfigAttrib, VAConfigAttribType,
42 VAEntrypoint, VAProfile,
43};
44
45pub fn probe(display: &Display) -> anyhow::Result<()> {
51 let entrypoints = display
52 .query_config_entrypoints(VAProfile::VAProfileNone)
53 .map_err(|e| anyhow::anyhow!("query video processing entrypoints: {e:?}"))?;
54 match entrypoints.contains(&VAEntrypoint::VAEntrypointVideoProc) {
55 true => Ok(()),
56 false => anyhow::bail!("the driver has no video post-processing entrypoint"),
57 }
58}
59
60pub struct Processor {
67 display: Arc<Display>,
68 contexts: RefCell<HashMap<(u32, u32), Rc<Context>>>,
70 config: VaConfig,
71}
72
73impl Processor {
74 pub fn new<P: AsRef<Path>>(device: P) -> anyhow::Result<Self> {
80 let device = device.as_ref();
81 let display =
82 Display::open_drm_display(device).map_err(|e| anyhow::anyhow!("open DRM display {device:?}: {e}"))?;
83 Self::with_display(display)
84 }
85
86 pub fn open() -> anyhow::Result<Self> {
92 let (_, display) =
93 crate::display::open_first(probe).map_err(|e| e.context("find a render node that post-processes video"))?;
94 Self::with_display(display)
95 }
96
97 pub fn with_display(display: Arc<Display>) -> anyhow::Result<Self> {
107 let mut attrs = [VAConfigAttrib {
111 type_: VAConfigAttribType::VAConfigAttribRTFormat,
112 value: 0,
113 }];
114 display
115 .get_config_attributes(
116 VAProfile::VAProfileNone,
117 VAEntrypoint::VAEntrypointVideoProc,
118 &mut attrs,
119 )
120 .map_err(|e| anyhow::anyhow!("query VPP config attributes: {e:?}"))?;
121
122 let config = display
123 .create_config(
124 attrs.to_vec(),
125 VAProfile::VAProfileNone,
126 VAEntrypoint::VAEntrypointVideoProc,
127 )
128 .map_err(|e| anyhow::anyhow!("create VPP config: {e:?}"))?;
129
130 Ok(Self {
131 display,
132 contexts: RefCell::new(HashMap::new()),
133 config,
134 })
135 }
136
137 pub fn display(&self) -> &Arc<Display> {
139 &self.display
140 }
141
142 pub fn import(&self, buffer: DmaBuf) -> anyhow::Result<Surface<DmaBuf>> {
151 buffer.import(&self.display, Some(UsageHint::USAGE_HINT_VPP_READ))
152 }
153
154 pub fn blit<D, E>(
168 &self,
169 input: &Surface<D>,
170 output: &Surface<E>,
171 input_color: Option<Color>,
172 output_color: Option<Color>,
173 ) -> anyhow::Result<()>
174 where
175 D: SurfaceMemoryDescriptor,
176 E: SurfaceMemoryDescriptor,
177 {
178 if !Arc::ptr_eq(input.display(), &self.display) || !Arc::ptr_eq(output.display(), &self.display) {
181 anyhow::bail!("a VPP blit between surfaces of another display");
182 }
183 let size = output.size();
184 let context = self.context(size)?;
185 let result = run_blit(&context, input, output, input_color, output_color);
186 if result.is_err() {
187 self.contexts.borrow_mut().remove(&size);
190 }
191 result
192 }
193
194 pub fn process<D: SurfaceMemoryDescriptor>(
205 &self,
206 input: &Surface<D>,
207 fourcc: u32,
208 (width, height): (u32, u32),
209 input_color: Option<Color>,
210 output_color: Option<Color>,
211 ) -> anyhow::Result<Surface<()>> {
212 let rt_format = rt_format(fourcc).ok_or_else(|| anyhow::anyhow!("no RT format for fourcc {fourcc:#x}"))?;
213 let output = self
214 .display
215 .create_surfaces(
216 rt_format,
217 Some(fourcc),
218 width,
219 height,
220 Some(UsageHint::USAGE_HINT_VPP_WRITE | UsageHint::USAGE_HINT_EXPORT),
221 vec![()],
222 )
223 .map_err(|e| anyhow::anyhow!("allocate a {width}x{height} VPP output surface: {e:?}"))?
224 .pop()
225 .ok_or_else(|| anyhow::anyhow!("vaCreateSurfaces returned no surface"))?;
226
227 self.blit(input, &output, input_color, output_color)?;
228 Ok(output)
229 }
230
231 pub fn retile(&self, buffer: DmaBuf) -> anyhow::Result<Surface<()>> {
240 let fourcc = buffer
241 .fourcc()
242 .ok_or_else(|| anyhow::anyhow!("no VA-API format for DRM format {:#010x}", buffer.drm_format))?;
243 let input = self.import(buffer)?;
244 self.process(&input, fourcc, input.size(), None, None)
245 }
246
247 fn context(&self, (width, height): (u32, u32)) -> anyhow::Result<Rc<Context>> {
249 let mut contexts = self.contexts.borrow_mut();
250 if let Some(context) = contexts.get(&(width, height)) {
251 return Ok(Rc::clone(context));
252 }
253 let context = self
256 .display
257 .create_context::<()>(&self.config, width, height, None, true)
258 .map_err(|e| anyhow::anyhow!("create a {width}x{height} VPP context: {e:?}"))?;
259 contexts.insert((width, height), Rc::clone(&context));
260 Ok(context)
261 }
262}
263
264fn run_blit<D, E>(
266 context: &Rc<Context>,
267 input: &Surface<D>,
268 output: &Surface<E>,
269 input_color: Option<Color>,
270 output_color: Option<Color>,
271) -> anyhow::Result<()>
272where
273 D: SurfaceMemoryDescriptor,
274 E: SurfaceMemoryDescriptor,
275{
276 let parameters = pipeline(input.id(), input_color, output_color);
277 let buffer = context
278 .create_buffer(BufferType::ProcPipelineParameter(parameters))
279 .map_err(|e| anyhow::anyhow!("create VPP pipeline buffer: {e:?}"))?;
280
281 let mut picture = Picture::new(0, Rc::clone(context), output);
282 picture.add_buffer(buffer);
283 picture
284 .begin()
285 .map_err(|e| anyhow::anyhow!("vaBeginPicture: {e:?}"))?
286 .render()
287 .map_err(|e| anyhow::anyhow!("vaRenderPicture: {e:?}"))?
288 .end()
289 .map_err(|e| anyhow::anyhow!("vaEndPicture: {e:?}"))?
290 .sync()
291 .map_err(|(e, _)| anyhow::anyhow!("vaSyncSurface: {e:?}"))?;
292 Ok(())
293}
294
295fn pipeline(
301 surface: bindings::VASurfaceID,
302 input_color: Option<Color>,
303 output_color: Option<Color>,
304) -> ProcPipelineParameterBuffer {
305 let side = |color: Option<Color>| match color {
306 Some(color) => (
307 color.va_standard(),
308 ProcColorProperties::new(0, color.va_range(), 0, 0, 0),
309 ),
310 None => (0, ProcColorProperties::default()),
311 };
312 let (input_standard, input_properties) = side(input_color);
313 let (output_standard, output_properties) = side(output_color);
314 ProcPipelineParameterBuffer::new(
315 surface,
316 None,
317 input_standard,
318 None,
319 0,
320 output_standard,
321 0,
322 0,
323 None,
324 None,
325 None,
326 0,
327 None,
328 0,
329 None,
330 0,
331 0,
332 input_properties,
333 output_properties,
334 0,
335 None,
336 )
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::{VA_FOURCC_NV12, VA_RT_FORMAT_YUV420};
343
344 #[test]
353 fn retile_round_trip() {
354 let Some(display) = Display::open() else {
355 eprintln!("skipping: no VA-API display");
356 return;
357 };
358 let Ok(processor) = Processor::with_display(display) else {
359 eprintln!("skipping: device has no video post-processing entrypoint");
360 return;
361 };
362
363 let mut surfaces = processor
366 .display()
367 .create_surfaces(
368 VA_RT_FORMAT_YUV420,
369 Some(VA_FOURCC_NV12),
370 640,
371 480,
372 Some(UsageHint::USAGE_HINT_DECODER | UsageHint::USAGE_HINT_EXPORT),
373 vec![()],
374 )
375 .expect("allocate a decode surface");
376 let decoded = surfaces.pop().expect("one surface");
377 let exported = decoded.export_prime().expect("export the decode surface");
378 let source_modifier = exported.objects[0].drm_format_modifier;
379 let (fourcc, size, planes) = (
380 exported.fourcc,
381 (exported.width, exported.height),
382 exported.layers[0].num_planes,
383 );
384 let buffer = DmaBuf::from_prime(exported).expect("a composed export is one object");
385
386 let retiled = processor.retile(buffer).expect("re-tile the decode surface");
387 let output = retiled.export_prime().expect("export the re-tiled surface");
388 let target_modifier = output.objects[0].drm_format_modifier;
389
390 eprintln!("modifier {source_modifier:#x} -> {target_modifier:#x}");
391 assert_eq!(output.fourcc, fourcc, "the blit preserves the pixel format");
392 assert_eq!((output.width, output.height), size);
393 assert_eq!(output.objects.len(), 1, "a composed export is one object");
394 assert_eq!(
395 output.layers[0].num_planes, planes,
396 "the blit preserves the plane count"
397 );
398 }
399 #[test]
402 fn a_scaled_surface_reads_back_at_its_new_size() {
403 let Some(display) = Display::open() else {
404 eprintln!("skipping: no VA-API display");
405 return;
406 };
407 let Ok(processor) = Processor::with_display(display) else {
408 eprintln!("skipping: device has no video post-processing entrypoint");
409 return;
410 };
411
412 let (width, height) = (640u32, 480u32);
414 let source = processor
415 .display()
416 .create_surfaces(
417 VA_RT_FORMAT_YUV420,
418 Some(VA_FOURCC_NV12),
419 width,
420 height,
421 Some(UsageHint::USAGE_HINT_EXPORT),
422 vec![()],
423 )
424 .expect("allocate a surface")
425 .pop()
426 .expect("one surface");
427 let format = processor
428 .display()
429 .query_image_formats()
430 .expect("query image formats")
431 .into_iter()
432 .find(|f| f.fourcc == VA_FOURCC_NV12)
433 .expect("an NV12 image format");
434 {
435 let mut image =
436 crate::Image::create_from(&source, format, (width, height), (width, height)).expect("create an image");
437 let va_image = *image.image();
438 let data = image.as_mut();
439 for y in 0..height as usize {
440 let row = va_image.offsets[0] as usize + y * va_image.pitches[0] as usize;
441 for x in 0..width as usize {
442 data[row + x] = if x < width as usize / 2 { 40 } else { 200 };
443 }
444 }
445 for y in 0..height as usize / 2 {
446 let row = va_image.offsets[1] as usize + y * va_image.pitches[1] as usize;
447 data[row..row + width as usize].fill(128);
448 }
449 }
450
451 let scaled = processor
452 .process(&source, VA_FOURCC_NV12, (width / 2, height / 2), None, None)
453 .expect("scale the surface");
454 let exported = crate::decode::ExportedFrame::from_surface(scaled, 7).expect("export the scaled surface");
455 assert_eq!((exported.width, exported.height), (width / 2, height / 2));
456 assert_eq!(exported.timestamp, 7);
457 let frame = exported.download().expect("read the scaled surface back");
458 let (w, h) = ((width / 2) as usize, (height / 2) as usize);
459 assert_eq!(frame.data.len(), w * h * 3 / 2);
460 let (left, right) = (frame.data[h / 2 * w + w / 8], frame.data[h / 2 * w + w * 7 / 8]);
461 assert!(
462 left.abs_diff(40) < 4 && right.abs_diff(200) < 4,
463 "read back {left} and {right}"
464 );
465 }
466}