• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::os::unix::io::AsRawFd;
8 use std::path::Path;
9 use std::path::PathBuf;
10 use std::rc::Rc;
11 
12 use anyhow::anyhow;
13 use anyhow::Context as AnyhowContext;
14 use anyhow::Result;
15 
16 use crate::bindings;
17 use crate::config::Config;
18 use crate::context::Context;
19 use crate::status::Status;
20 use crate::surface::Surface;
21 use crate::UsageHint;
22 
23 /// Iterates over existing DRM devices.
24 ///
25 /// DRM devices can be passed to [`Display::open_drm_display`] in order to create a `Display` on
26 /// that device.
27 pub struct DrmDeviceIterator {
28     cur_idx: usize,
29 }
30 
31 const DRM_NODE_DEFAULT_PREFIX: &str = "/dev/dri/renderD";
32 const DRM_NUM_NODES: usize = 64;
33 const DRM_RENDER_NODE_START: usize = 128;
34 
35 impl DrmDeviceIterator {
36     /// Create a new iterator over DRM render nodes on the system.
new() -> Self37     pub fn new() -> 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 impl Display {
81     /// Opens and initializes a specific DRM `Display`.
82     ///
83     /// `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>>84     pub fn open_drm_display<P: AsRef<Path>>(path: P) -> Result<Rc<Self>> {
85         let file = std::fs::File::options()
86             .read(true)
87             .write(true)
88             .open(path.as_ref())
89             .context(format!("failed to open {}", path.as_ref().display()))?;
90 
91         // Safe because fd represents a valid file descriptor and the pointer is checked for
92         // NULL afterwards.
93         let display = unsafe { bindings::vaGetDisplayDRM(file.as_raw_fd()) };
94         if display.is_null() {
95             // The File will close the DRM fd on drop.
96             return Err(anyhow!(
97                 "failed to obtain VA display from DRM device {}",
98                 path.as_ref().display()
99             ));
100         }
101 
102         let mut major = 0i32;
103         let mut minor = 0i32;
104         // Safe because we ensure that the display is valid (i.e not NULL) before calling
105         // vaInitialize. The File will close the DRM fd on drop.
106         Status(unsafe { bindings::vaInitialize(display, &mut major, &mut minor) }).check()?;
107 
108         Ok(Rc::new(Self {
109             handle: display,
110             drm_file: file,
111         }))
112     }
113 
114     /// Opens the first device that succeeds and returns its `Display`.
115     ///
116     /// If an error occurs on a given device, it is ignored and the next one is tried until one
117     /// succeeds or we reach the end of the iterator.
open() -> Option<Rc<Self>>118     pub fn open() -> Option<Rc<Self>> {
119         let devices = DrmDeviceIterator::new();
120 
121         // Try all the DRM devices until one succeeds.
122         for device in devices {
123             if let Ok(display) = Self::open_drm_display(device) {
124                 return Some(display);
125             }
126         }
127 
128         None
129     }
130 
131     /// Returns the handle of this display.
handle(&self) -> bindings::VADisplay132     pub(crate) fn handle(&self) -> bindings::VADisplay {
133         self.handle
134     }
135 
136     /// Queries supported profiles by this display.
query_config_profiles(&self) -> Result<Vec<bindings::VAProfile::Type>>137     pub fn query_config_profiles(&self) -> Result<Vec<bindings::VAProfile::Type>> {
138         // Safe because `self` represents a valid VADisplay.
139         let mut max_num_profiles = unsafe { bindings::vaMaxNumProfiles(self.handle) };
140         let mut profiles = Vec::with_capacity(max_num_profiles as usize);
141 
142         // Safe because `self` represents a valid `VADisplay` and the vector has `max_num_profiles`
143         // as capacity.
144         Status(unsafe {
145             bindings::vaQueryConfigProfiles(
146                 self.handle,
147                 profiles.as_mut_ptr(),
148                 &mut max_num_profiles,
149             )
150         })
151         .check()?;
152 
153         // Safe because `profiles` is allocated with a `max_num_profiles` capacity and
154         // `vaQueryConfigProfiles` wrote the actual number of profiles to `max_num_entrypoints`.
155         unsafe {
156             profiles.set_len(max_num_profiles as usize);
157         };
158 
159         Ok(profiles)
160     }
161 
162     /// Returns a string describing some aspects of the VA implemenation on the specific hardware
163     /// accelerator used by this display.
164     ///
165     /// The format of the returned string is vendor specific and at the discretion of the
166     /// implementer. e.g. for the Intel GMA500 implementation, an example would be: `Intel GMA500 -
167     /// 2.0.0.32L.0005`.
query_vendor_string(&self) -> std::result::Result<String, &'static str>168     pub fn query_vendor_string(&self) -> std::result::Result<String, &'static str> {
169         // Safe because `self` represents a valid VADisplay.
170         let vendor_string = unsafe { bindings::vaQueryVendorString(self.handle) };
171 
172         if vendor_string.is_null() {
173             return Err("vaQueryVendorString() returned NULL");
174         }
175 
176         // Safe because we check the whether the vendor_String pointer is NULL
177         Ok(unsafe { CStr::from_ptr(vendor_string) }
178             .to_string_lossy()
179             .to_string())
180     }
181 
182     /// Query supported entrypoints for a given profile.
query_config_entrypoints( &self, profile: bindings::VAProfile::Type, ) -> Result<Vec<bindings::VAEntrypoint::Type>>183     pub fn query_config_entrypoints(
184         &self,
185         profile: bindings::VAProfile::Type,
186     ) -> Result<Vec<bindings::VAEntrypoint::Type>> {
187         // Safe because `self` represents a valid VADisplay.
188         let mut max_num_entrypoints = unsafe { bindings::vaMaxNumEntrypoints(self.handle) };
189         let mut entrypoints = Vec::with_capacity(max_num_entrypoints as usize);
190 
191         // Safe because `self` represents a valid VADisplay and the vector has `max_num_entrypoints`
192         // as capacity.
193         Status(unsafe {
194             bindings::vaQueryConfigEntrypoints(
195                 self.handle,
196                 profile,
197                 entrypoints.as_mut_ptr(),
198                 &mut max_num_entrypoints,
199             )
200         })
201         .check()?;
202 
203         // Safe because `entrypoints` is allocated with a `max_num_entrypoints` capacity, and
204         // `vaQueryConfigEntrypoints` wrote the actual number of entrypoints to
205         // `max_num_entrypoints`
206         unsafe {
207             entrypoints.set_len(max_num_entrypoints as usize);
208         }
209 
210         Ok(entrypoints)
211     }
212 
213     /// Writes attributes for a given `profile`/`entrypoint` pair into `attributes`.
214     ///
215     /// Entries of `attributes` must have their `type_` member initialized to the desired attribute
216     /// to retrieve.
get_config_attributes( &self, profile: bindings::VAProfile::Type, entrypoint: bindings::VAEntrypoint::Type, attributes: &mut [bindings::VAConfigAttrib], ) -> Result<()>217     pub fn get_config_attributes(
218         &self,
219         profile: bindings::VAProfile::Type,
220         entrypoint: bindings::VAEntrypoint::Type,
221         attributes: &mut [bindings::VAConfigAttrib],
222     ) -> Result<()> {
223         // Safe because `self` represents a valid VADisplay. The slice length is passed to the C
224         // function, so it is impossible to write past the end of the slice's storage by mistake.
225         Status(unsafe {
226             bindings::vaGetConfigAttributes(
227                 self.handle,
228                 profile,
229                 entrypoint,
230                 attributes.as_mut_ptr(),
231                 attributes.len() as i32,
232             )
233         })
234         .check()
235     }
236 
237     /// Creates `Surface`s by wrapping around a `vaCreateSurfaces` call.
238     ///
239     /// # Arguments
240     ///
241     /// * `rt_format` - The desired surface format. See `VA_RT_FORMAT_*`
242     /// * `va_fourcc` - The desired pixel format (optional). See `VA_FOURCC_*`
243     /// * `width` - Width for the create surfaces
244     /// * `height` - Height for the created surfaces
245     /// * `usage_hint` - Optional hint of intended usage to optimize allocation (e.g. tiling)
246     /// * `num_surfaces` - Number of surfaces to create
create_surfaces( self: &Rc<Self>, rt_format: u32, va_fourcc: Option<u32>, width: u32, height: u32, usage_hint: Option<UsageHint>, num_surfaces: u32, ) -> Result<Vec<Surface>>247     pub fn create_surfaces(
248         self: &Rc<Self>,
249         rt_format: u32,
250         va_fourcc: Option<u32>,
251         width: u32,
252         height: u32,
253         usage_hint: Option<UsageHint>,
254         num_surfaces: u32,
255     ) -> Result<Vec<Surface>> {
256         Surface::new(
257             Rc::clone(self),
258             rt_format,
259             va_fourcc,
260             width,
261             height,
262             usage_hint,
263             num_surfaces,
264         )
265     }
266 
267     /// Creates a `Context` by wrapping around a `vaCreateContext` call.
268     ///
269     /// # Arguments
270     ///
271     /// * `config` - The configuration for the context
272     /// * `coded_width` - The coded picture width
273     /// * `coded_height` - The coded picture height
274     /// * `surfaces` - Optional hint for the amount of surfaces tied to the context
275     /// * `progressive` - Whether only progressive frame pictures are present in the sequence
create_context( self: &Rc<Self>, config: &Config, coded_width: i32, coded_height: i32, surfaces: Option<&Vec<Surface>>, progressive: bool, ) -> Result<Rc<Context>>276     pub fn create_context(
277         self: &Rc<Self>,
278         config: &Config,
279         coded_width: i32,
280         coded_height: i32,
281         surfaces: Option<&Vec<Surface>>,
282         progressive: bool,
283     ) -> Result<Rc<Context>> {
284         Context::new(
285             Rc::clone(self),
286             config,
287             coded_width,
288             coded_height,
289             surfaces,
290             progressive,
291         )
292     }
293 
294     /// Creates a `Config` by wrapping around the `vaCreateConfig` call.
295     ///
296     /// `attrs` describe the attributes to set for this config. A list of the supported attributes
297     /// for a given profile/entrypoint pair can be retrieved using
298     /// [`Display::get_config_attributes`]. Other attributes will take their default values, and
299     /// `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>300     pub fn create_config(
301         self: &Rc<Self>,
302         attrs: Vec<bindings::VAConfigAttrib>,
303         profile: bindings::VAProfile::Type,
304         entrypoint: bindings::VAEntrypoint::Type,
305     ) -> Result<Config> {
306         Config::new(Rc::clone(self), attrs, profile, entrypoint)
307     }
308 
309     /// Returns available image formats for this display by wrapping around `vaQueryImageFormats`.
query_image_formats(self: &Rc<Self>) -> Result<Vec<bindings::VAImageFormat>>310     pub fn query_image_formats(self: &Rc<Self>) -> Result<Vec<bindings::VAImageFormat>> {
311         // Safe because `self` represents a valid VADisplay.
312         let mut num_image_formats = unsafe { bindings::vaMaxNumImageFormats(self.handle) };
313         let mut image_formats = Vec::with_capacity(num_image_formats as usize);
314 
315         // Safe because `self` represents a valid VADisplay. The `image_formats` vector is properly
316         // initialized and a valid size is passed to the C function, so it is impossible to write
317         // past the end of their storage by mistake.
318         Status(unsafe {
319             bindings::vaQueryImageFormats(
320                 self.handle,
321                 image_formats.as_mut_ptr(),
322                 &mut num_image_formats,
323             )
324         })
325         .check()?;
326 
327         // Safe because the C function will have written exactly `num_image_format` entries, which
328         // is known to be within the vector's capacity.
329         unsafe {
330             image_formats.set_len(num_image_formats as usize);
331         }
332 
333         Ok(image_formats)
334     }
335 }
336 
337 impl Drop for Display {
drop(&mut self)338     fn drop(&mut self) {
339         // Safe because `self` represents a valid VADisplay.
340         unsafe {
341             bindings::vaTerminate(self.handle);
342             // The File will close the DRM fd on drop.
343         }
344     }
345 }
346