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