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