1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::ffi::CStr;
6 use std::fs::File;
7 use std::io;
8 use std::os::unix::io::AsRawFd;
9 use std::path::Path;
10 use std::path::PathBuf;
11 use std::rc::Rc;
12 
13 use thiserror::Error;
14 
15 use crate::bindings;
16 use crate::config::Config;
17 use crate::context::Context;
18 use crate::surface::Surface;
19 use crate::va_check;
20 use crate::SurfaceMemoryDescriptor;
21 use crate::UsageHint;
22 use crate::VaError;
23 
24 /// Iterates over existing DRM devices.
25 ///
26 /// DRM devices can be passed to [`Display::open_drm_display`] in order to create a `Display` on
27 /// that device.
28 pub struct DrmDeviceIterator {
29     cur_idx: usize,
30 }
31 
32 const DRM_NODE_DEFAULT_PREFIX: &str = "/dev/dri/renderD";
33 const DRM_NUM_NODES: usize = 64;
34 const DRM_RENDER_NODE_START: usize = 128;
35 
36 impl Default for DrmDeviceIterator {
default() -> Self37     fn default() -> Self {
38         Self {
39             cur_idx: DRM_RENDER_NODE_START,
40         }
41     }
42 }
43 
44 impl Iterator for DrmDeviceIterator {
45     type Item = PathBuf;
46 
next(&mut self) -> Option<Self::Item>47     fn next(&mut self) -> Option<Self::Item> {
48         match self.cur_idx {
49             idx if idx >= DRM_RENDER_NODE_START + DRM_NUM_NODES => None,
50             idx => {
51                 let path = PathBuf::from(format!("{}{}", DRM_NODE_DEFAULT_PREFIX, idx));
52                 if !path.exists() {
53                     None
54                 } else {
55                     self.cur_idx += 1;
56                     Some(path)
57                 }
58             }
59         }
60     }
61 }
62 
63 /// A VADisplay opened over DRM.
64 ///
65 /// A Display is the starting point to using libva. This struct is essentially a safe wrapper over
66 /// `VADisplay`, from which [`Surface`]s and [`Context`]s can be allocated in order to perform
67 /// actual work using [`Display::create_surfaces`] and [`Display::create_context`], respectively.
68 ///
69 /// Although libva offers several ways to create a display, this struct currently only supports
70 /// opening through DRM. It may be extended to support other display types (X11, Wayland) in the
71 /// future.
72 pub struct Display {
73     /// Handle to interact with the underlying `VADisplay`.
74     handle: bindings::VADisplay,
75     /// DRM file that must be kept open while the display is in use.
76     #[allow(dead_code)]
77     drm_file: File,
78 }
79 
80 /// Error type for `Display::open_drm_display`.
81 #[derive(Debug, Error)]
82 pub enum OpenDrmDisplayError {
83     #[error("cannot open DRM device: {0}")]
84     DeviceOpen(io::Error),
85     #[error("vaGetDisplayDRM returned NULL")]
86     VaGetDisplayDrm,
87     #[error("call to vaInitialize failed: {0}")]
88     VaInitialize(VaError),
89 }
90 
91 impl Display {
92     /// Opens and initializes a specific DRM `Display`.
93     ///
94     /// `path` is the path to a DRM device that supports VAAPI, e.g. `/dev/dri/renderD128`.
open_drm_display<P: AsRef<Path>>(path: P) -> Result<Rc<Self>, OpenDrmDisplayError>95     pub fn open_drm_display<P: AsRef<Path>>(path: P) -> Result<Rc<Self>, OpenDrmDisplayError> {
96         let file = std::fs::File::options()
97             .read(true)
98             .write(true)
99             .open(path.as_ref())
100             .map_err(OpenDrmDisplayError::DeviceOpen)?;
101 
102         // Safe because fd represents a valid file descriptor and the pointer is checked for
103         // NULL afterwards.
104         let display = unsafe { bindings::vaGetDisplayDRM(file.as_raw_fd()) };
105         if display.is_null() {
106             return Err(OpenDrmDisplayError::VaGetDisplayDrm);
107         }
108 
109         let mut major = 0i32;
110         let mut minor = 0i32;
111         // Safe because we ensure that the display is valid (i.e not NULL) before calling
112         // vaInitialize. The File will close the DRM fd on drop.
113         va_check(unsafe { bindings::vaInitialize(display, &mut major, &mut minor) })
114             .map(|()| {
115                 Rc::new(Self {
116                     handle: display,
117                     drm_file: file,
118                 })
119             })
120             .map_err(OpenDrmDisplayError::VaInitialize)
121     }
122 
123     /// Opens the first device that succeeds and returns its `Display`.
124     ///
125     /// If an error occurs on a given device, it is ignored and the next one is tried until one
126     /// succeeds or we reach the end of the iterator.
open() -> Option<Rc<Self>>127     pub fn open() -> Option<Rc<Self>> {
128         let devices = DrmDeviceIterator::default();
129 
130         // Try all the DRM devices until one succeeds.
131         for device in devices {
132             if let Ok(display) = Self::open_drm_display(device) {
133                 return Some(display);
134             }
135         }
136 
137         None
138     }
139 
140     /// Returns the handle of this display.
handle(&self) -> bindings::VADisplay141     pub(crate) fn handle(&self) -> bindings::VADisplay {
142         self.handle
143     }
144 
145     /// Queries supported profiles by this display by wrapping `vaQueryConfigProfiles`.
query_config_profiles(&self) -> Result<Vec<bindings::VAProfile::Type>, VaError>146     pub fn query_config_profiles(&self) -> Result<Vec<bindings::VAProfile::Type>, VaError> {
147         // Safe because `self` represents a valid VADisplay.
148         let mut max_num_profiles = unsafe { bindings::vaMaxNumProfiles(self.handle) };
149         let mut profiles = Vec::with_capacity(max_num_profiles as usize);
150 
151         // Safe because `self` represents a valid `VADisplay` and the vector has `max_num_profiles`
152         // as capacity.
153         va_check(unsafe {
154             bindings::vaQueryConfigProfiles(
155                 self.handle,
156                 profiles.as_mut_ptr(),
157                 &mut max_num_profiles,
158             )
159         })?;
160 
161         // Safe because `profiles` is allocated with a `max_num_profiles` capacity and
162         // `vaQueryConfigProfiles` wrote the actual number of profiles to `max_num_entrypoints`.
163         unsafe {
164             profiles.set_len(max_num_profiles as usize);
165         };
166 
167         Ok(profiles)
168     }
169 
170     /// Returns a string describing some aspects of the VA implemenation on the specific hardware
171     /// accelerator used by this display. Wrapper over `vaQueryVendorString`.
172     ///
173     /// The format of the returned string is vendor specific and at the discretion of the
174     /// implementer. e.g. for the Intel GMA500 implementation, an example would be: `Intel GMA500 -
175     /// 2.0.0.32L.0005`.
query_vendor_string(&self) -> std::result::Result<String, &'static str>176     pub fn query_vendor_string(&self) -> std::result::Result<String, &'static str> {
177         // Safe because `self` represents a valid VADisplay.
178         let vendor_string = unsafe { bindings::vaQueryVendorString(self.handle) };
179 
180         if vendor_string.is_null() {
181             return Err("vaQueryVendorString() returned NULL");
182         }
183 
184         // Safe because we check the whether the vendor_String pointer is NULL
185         Ok(unsafe { CStr::from_ptr(vendor_string) }
186             .to_string_lossy()
187             .to_string())
188     }
189 
190     /// Query supported entrypoints for a given profile by wrapping `vaQueryConfigEntrypoints`.
query_config_entrypoints( &self, profile: bindings::VAProfile::Type, ) -> Result<Vec<bindings::VAEntrypoint::Type>, VaError>191     pub fn query_config_entrypoints(
192         &self,
193         profile: bindings::VAProfile::Type,
194     ) -> Result<Vec<bindings::VAEntrypoint::Type>, VaError> {
195         // Safe because `self` represents a valid VADisplay.
196         let mut max_num_entrypoints = unsafe { bindings::vaMaxNumEntrypoints(self.handle) };
197         let mut entrypoints = Vec::with_capacity(max_num_entrypoints as usize);
198 
199         // Safe because `self` represents a valid VADisplay and the vector has `max_num_entrypoints`
200         // as capacity.
201         va_check(unsafe {
202             bindings::vaQueryConfigEntrypoints(
203                 self.handle,
204                 profile,
205                 entrypoints.as_mut_ptr(),
206                 &mut max_num_entrypoints,
207             )
208         })?;
209 
210         // Safe because `entrypoints` is allocated with a `max_num_entrypoints` capacity, and
211         // `vaQueryConfigEntrypoints` wrote the actual number of entrypoints to
212         // `max_num_entrypoints`
213         unsafe {
214             entrypoints.set_len(max_num_entrypoints as usize);
215         }
216 
217         Ok(entrypoints)
218     }
219 
220     /// Writes attributes for a given `profile`/`entrypoint` pair into `attributes`. Wrapper over
221     /// `vaGetConfigAttributes`.
222     ///
223     /// Entries of `attributes` must have their `type_` member initialized to the desired attribute
224     /// to retrieve.
get_config_attributes( &self, profile: bindings::VAProfile::Type, entrypoint: bindings::VAEntrypoint::Type, attributes: &mut [bindings::VAConfigAttrib], ) -> Result<(), VaError>225     pub fn get_config_attributes(
226         &self,
227         profile: bindings::VAProfile::Type,
228         entrypoint: bindings::VAEntrypoint::Type,
229         attributes: &mut [bindings::VAConfigAttrib],
230     ) -> Result<(), VaError> {
231         // Safe because `self` represents a valid VADisplay. The slice length is passed to the C
232         // function, so it is impossible to write past the end of the slice's storage by mistake.
233         va_check(unsafe {
234             bindings::vaGetConfigAttributes(
235                 self.handle,
236                 profile,
237                 entrypoint,
238                 attributes.as_mut_ptr(),
239                 attributes.len() as i32,
240             )
241         })
242     }
243 
244     /// Creates `Surface`s by wrapping around a `vaCreateSurfaces` call.
245     ///
246     /// The number of surfaces created will be equal to the length of `descriptors`.
247     ///
248     /// # Arguments
249     ///
250     /// * `rt_format` - The desired surface format. See `VA_RT_FORMAT_*`
251     /// * `va_fourcc` - The desired pixel format (optional). See `VA_FOURCC_*`
252     /// * `width` - Width for the create surfaces
253     /// * `height` - Height for the created surfaces
254     /// * `usage_hint` - Optional hint of intended usage to optimize allocation (e.g. tiling)
255     /// * `num_surfaces` - Number of surfaces to create
256     /// * `descriptors` - Memory descriptors used as surface memory backing.
257     ///
258     /// # Return value
259     ///
260     /// Returns as many surfaces as the length of `descriptors`.
261     ///
262     /// Note that the `descriptors`'s ownership is irrevocably given to the surfaces, and that in
263     /// case of error the `descriptors` will be destroyed. Make sure to duplicate the descriptors
264     /// if you need something outside of libva to access them.
create_surfaces<D: SurfaceMemoryDescriptor>( self: &Rc<Self>, rt_format: u32, va_fourcc: Option<u32>, width: u32, height: u32, usage_hint: Option<UsageHint>, descriptors: Vec<D>, ) -> Result<Vec<Surface<D>>, VaError>265     pub fn create_surfaces<D: SurfaceMemoryDescriptor>(
266         self: &Rc<Self>,
267         rt_format: u32,
268         va_fourcc: Option<u32>,
269         width: u32,
270         height: u32,
271         usage_hint: Option<UsageHint>,
272         descriptors: Vec<D>,
273     ) -> Result<Vec<Surface<D>>, VaError> {
274         Surface::new(
275             Rc::clone(self),
276             rt_format,
277             va_fourcc,
278             width,
279             height,
280             usage_hint,
281             descriptors,
282         )
283     }
284 
285     /// Creates a `Context` by wrapping around a `vaCreateContext` call.
286     ///
287     /// # Arguments
288     ///
289     /// * `config` - The configuration for the context
290     /// * `coded_width` - The coded picture width
291     /// * `coded_height` - The coded picture height
292     /// * `surfaces` - Optional hint for the amount of surfaces tied to the context
293     /// * `progressive` - Whether only progressive frame pictures are present in the sequence
create_context<D: SurfaceMemoryDescriptor>( self: &Rc<Self>, config: &Config, coded_width: u32, coded_height: u32, surfaces: Option<&Vec<Surface<D>>>, progressive: bool, ) -> Result<Rc<Context>, VaError>294     pub fn create_context<D: SurfaceMemoryDescriptor>(
295         self: &Rc<Self>,
296         config: &Config,
297         coded_width: u32,
298         coded_height: u32,
299         surfaces: Option<&Vec<Surface<D>>>,
300         progressive: bool,
301     ) -> Result<Rc<Context>, VaError> {
302         Context::new(
303             Rc::clone(self),
304             config,
305             coded_width,
306             coded_height,
307             surfaces,
308             progressive,
309         )
310     }
311 
312     /// Creates a `Config` by wrapping around the `vaCreateConfig` call.
313     ///
314     /// `attrs` describe the attributes to set for this config. A list of the supported attributes
315     /// for a given profile/entrypoint pair can be retrieved using
316     /// [`Display::get_config_attributes`]. Other attributes will take their default values, and
317     /// `attrs` can be empty in order to obtain a default configuration.
create_config( self: &Rc<Self>, attrs: Vec<bindings::VAConfigAttrib>, profile: bindings::VAProfile::Type, entrypoint: bindings::VAEntrypoint::Type, ) -> Result<Config, VaError>318     pub fn create_config(
319         self: &Rc<Self>,
320         attrs: Vec<bindings::VAConfigAttrib>,
321         profile: bindings::VAProfile::Type,
322         entrypoint: bindings::VAEntrypoint::Type,
323     ) -> Result<Config, VaError> {
324         Config::new(Rc::clone(self), attrs, profile, entrypoint)
325     }
326 
327     /// Returns available image formats for this display by wrapping around `vaQueryImageFormats`.
query_image_formats(&self) -> Result<Vec<bindings::VAImageFormat>, VaError>328     pub fn query_image_formats(&self) -> Result<Vec<bindings::VAImageFormat>, VaError> {
329         // Safe because `self` represents a valid VADisplay.
330         let mut num_image_formats = unsafe { bindings::vaMaxNumImageFormats(self.handle) };
331         let mut image_formats = Vec::with_capacity(num_image_formats as usize);
332 
333         // Safe because `self` represents a valid VADisplay. The `image_formats` vector is properly
334         // initialized and a valid size is passed to the C function, so it is impossible to write
335         // past the end of their storage by mistake.
336         va_check(unsafe {
337             bindings::vaQueryImageFormats(
338                 self.handle,
339                 image_formats.as_mut_ptr(),
340                 &mut num_image_formats,
341             )
342         })?;
343 
344         // Safe because the C function will have written exactly `num_image_format` entries, which
345         // is known to be within the vector's capacity.
346         unsafe {
347             image_formats.set_len(num_image_formats as usize);
348         }
349 
350         Ok(image_formats)
351     }
352 }
353 
354 impl Drop for Display {
drop(&mut self)355     fn drop(&mut self) {
356         // Safe because `self` represents a valid VADisplay.
357         unsafe {
358             bindings::vaTerminate(self.handle);
359             // The File will close the DRM fd on drop.
360         }
361     }
362 }
363