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