Skip to main content

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 {}