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