| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::ffi::CStr; |
| use std::fs::File; |
| use std::io; |
| use std::os::unix::io::AsRawFd; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::rc::Rc; |
| |
| use thiserror::Error; |
| |
| use crate::bindings; |
| use crate::config::Config; |
| use crate::context::Context; |
| use crate::surface::Surface; |
| use crate::va_check; |
| use crate::SurfaceMemoryDescriptor; |
| use crate::UsageHint; |
| use crate::VaError; |
| |
| /// Iterates over existing DRM devices. |
| /// |
| /// DRM devices can be passed to [`Display::open_drm_display`] in order to create a `Display` on |
| /// that device. |
| pub struct DrmDeviceIterator { |
| cur_idx: usize, |
| } |
| |
| const DRM_NODE_DEFAULT_PREFIX: &str = "/dev/dri/renderD"; |
| const DRM_NUM_NODES: usize = 64; |
| const DRM_RENDER_NODE_START: usize = 128; |
| |
| impl Default for DrmDeviceIterator { |
| fn default() -> Self { |
| Self { |
| cur_idx: DRM_RENDER_NODE_START, |
| } |
| } |
| } |
| |
| impl Iterator for DrmDeviceIterator { |
| type Item = PathBuf; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| match self.cur_idx { |
| idx if idx >= DRM_RENDER_NODE_START + DRM_NUM_NODES => None, |
| idx => { |
| let path = PathBuf::from(format!("{}{}", DRM_NODE_DEFAULT_PREFIX, idx)); |
| if !path.exists() { |
| None |
| } else { |
| self.cur_idx += 1; |
| Some(path) |
| } |
| } |
| } |
| } |
| } |
| |
| /// A VADisplay opened over DRM. |
| /// |
| /// A Display is the starting point to using libva. This struct is essentially a safe wrapper over |
| /// `VADisplay`, from which [`Surface`]s and [`Context`]s can be allocated in order to perform |
| /// actual work using [`Display::create_surfaces`] and [`Display::create_context`], respectively. |
| /// |
| /// Although libva offers several ways to create a display, this struct currently only supports |
| /// opening through DRM. It may be extended to support other display types (X11, Wayland) in the |
| /// future. |
| pub struct Display { |
| /// Handle to interact with the underlying `VADisplay`. |
| handle: bindings::VADisplay, |
| /// DRM file that must be kept open while the display is in use. |
| #[allow(dead_code)] |
| drm_file: File, |
| } |
| |
| /// Error type for `Display::open_drm_display`. |
| #[derive(Debug, Error)] |
| pub enum OpenDrmDisplayError { |
| #[error("cannot open DRM device: {0}")] |
| DeviceOpen(io::Error), |
| #[error("vaGetDisplayDRM returned NULL")] |
| VaGetDisplayDrm, |
| #[error("call to vaInitialize failed: {0}")] |
| VaInitialize(VaError), |
| } |
| |
| impl Display { |
| /// Opens and initializes a specific DRM `Display`. |
| /// |
| /// `path` is the path to a DRM device that supports VAAPI, e.g. `/dev/dri/renderD128`. |
| pub fn open_drm_display<P: AsRef<Path>>(path: P) -> Result<Rc<Self>, OpenDrmDisplayError> { |
| let file = std::fs::File::options() |
| .read(true) |
| .write(true) |
| .open(path.as_ref()) |
| .map_err(OpenDrmDisplayError::DeviceOpen)?; |
| |
| // Safe because fd represents a valid file descriptor and the pointer is checked for |
| // NULL afterwards. |
| let display = unsafe { bindings::vaGetDisplayDRM(file.as_raw_fd()) }; |
| if display.is_null() { |
| return Err(OpenDrmDisplayError::VaGetDisplayDrm); |
| } |
| |
| let mut major = 0i32; |
| let mut minor = 0i32; |
| // Safe because we ensure that the display is valid (i.e not NULL) before calling |
| // vaInitialize. The File will close the DRM fd on drop. |
| va_check(unsafe { bindings::vaInitialize(display, &mut major, &mut minor) }) |
| .map(|()| { |
| Rc::new(Self { |
| handle: display, |
| drm_file: file, |
| }) |
| }) |
| .map_err(OpenDrmDisplayError::VaInitialize) |
| } |
| |
| /// Opens the first device that succeeds and returns its `Display`. |
| /// |
| /// If an error occurs on a given device, it is ignored and the next one is tried until one |
| /// succeeds or we reach the end of the iterator. |
| pub fn open() -> Option<Rc<Self>> { |
| let devices = DrmDeviceIterator::default(); |
| |
| // Try all the DRM devices until one succeeds. |
| for device in devices { |
| if let Ok(display) = Self::open_drm_display(device) { |
| return Some(display); |
| } |
| } |
| |
| None |
| } |
| |
| /// Returns the handle of this display. |
| pub(crate) fn handle(&self) -> bindings::VADisplay { |
| self.handle |
| } |
| |
| /// Queries supported profiles by this display by wrapping `vaQueryConfigProfiles`. |
| pub fn query_config_profiles(&self) -> Result<Vec<bindings::VAProfile::Type>, VaError> { |
| // Safe because `self` represents a valid VADisplay. |
| let mut max_num_profiles = unsafe { bindings::vaMaxNumProfiles(self.handle) }; |
| let mut profiles = Vec::with_capacity(max_num_profiles as usize); |
| |
| // Safe because `self` represents a valid `VADisplay` and the vector has `max_num_profiles` |
| // as capacity. |
| va_check(unsafe { |
| bindings::vaQueryConfigProfiles( |
| self.handle, |
| profiles.as_mut_ptr(), |
| &mut max_num_profiles, |
| ) |
| })?; |
| |
| // Safe because `profiles` is allocated with a `max_num_profiles` capacity and |
| // `vaQueryConfigProfiles` wrote the actual number of profiles to `max_num_entrypoints`. |
| unsafe { |
| profiles.set_len(max_num_profiles as usize); |
| }; |
| |
| Ok(profiles) |
| } |
| |
| /// Returns a string describing some aspects of the VA implemenation on the specific hardware |
| /// accelerator used by this display. Wrapper over `vaQueryVendorString`. |
| /// |
| /// The format of the returned string is vendor specific and at the discretion of the |
| /// implementer. e.g. for the Intel GMA500 implementation, an example would be: `Intel GMA500 - |
| /// 2.0.0.32L.0005`. |
| pub fn query_vendor_string(&self) -> std::result::Result<String, &'static str> { |
| // Safe because `self` represents a valid VADisplay. |
| let vendor_string = unsafe { bindings::vaQueryVendorString(self.handle) }; |
| |
| if vendor_string.is_null() { |
| return Err("vaQueryVendorString() returned NULL"); |
| } |
| |
| // Safe because we check the whether the vendor_String pointer is NULL |
| Ok(unsafe { CStr::from_ptr(vendor_string) } |
| .to_string_lossy() |
| .to_string()) |
| } |
| |
| /// Query supported entrypoints for a given profile by wrapping `vaQueryConfigEntrypoints`. |
| pub fn query_config_entrypoints( |
| &self, |
| profile: bindings::VAProfile::Type, |
| ) -> Result<Vec<bindings::VAEntrypoint::Type>, VaError> { |
| // Safe because `self` represents a valid VADisplay. |
| let mut max_num_entrypoints = unsafe { bindings::vaMaxNumEntrypoints(self.handle) }; |
| let mut entrypoints = Vec::with_capacity(max_num_entrypoints as usize); |
| |
| // Safe because `self` represents a valid VADisplay and the vector has `max_num_entrypoints` |
| // as capacity. |
| va_check(unsafe { |
| bindings::vaQueryConfigEntrypoints( |
| self.handle, |
| profile, |
| entrypoints.as_mut_ptr(), |
| &mut max_num_entrypoints, |
| ) |
| })?; |
| |
| // Safe because `entrypoints` is allocated with a `max_num_entrypoints` capacity, and |
| // `vaQueryConfigEntrypoints` wrote the actual number of entrypoints to |
| // `max_num_entrypoints` |
| unsafe { |
| entrypoints.set_len(max_num_entrypoints as usize); |
| } |
| |
| Ok(entrypoints) |
| } |
| |
| /// Writes attributes for a given `profile`/`entrypoint` pair into `attributes`. Wrapper over |
| /// `vaGetConfigAttributes`. |
| /// |
| /// Entries of `attributes` must have their `type_` member initialized to the desired attribute |
| /// to retrieve. |
| pub fn get_config_attributes( |
| &self, |
| profile: bindings::VAProfile::Type, |
| entrypoint: bindings::VAEntrypoint::Type, |
| attributes: &mut [bindings::VAConfigAttrib], |
| ) -> Result<(), VaError> { |
| // Safe because `self` represents a valid VADisplay. The slice length is passed to the C |
| // function, so it is impossible to write past the end of the slice's storage by mistake. |
| va_check(unsafe { |
| bindings::vaGetConfigAttributes( |
| self.handle, |
| profile, |
| entrypoint, |
| attributes.as_mut_ptr(), |
| attributes.len() as i32, |
| ) |
| }) |
| } |
| |
| /// Creates `Surface`s by wrapping around a `vaCreateSurfaces` call. |
| /// |
| /// The number of surfaces created will be equal to the length of `descriptors`. |
| /// |
| /// # Arguments |
| /// |
| /// * `rt_format` - The desired surface format. See `VA_RT_FORMAT_*` |
| /// * `va_fourcc` - The desired pixel format (optional). See `VA_FOURCC_*` |
| /// * `width` - Width for the create surfaces |
| /// * `height` - Height for the created surfaces |
| /// * `usage_hint` - Optional hint of intended usage to optimize allocation (e.g. tiling) |
| /// * `num_surfaces` - Number of surfaces to create |
| /// * `descriptors` - Memory descriptors used as surface memory backing. |
| /// |
| /// # Return value |
| /// |
| /// Returns as many surfaces as the length of `descriptors`. |
| /// |
| /// Note that the `descriptors`'s ownership is irrevocably given to the surfaces, and that in |
| /// case of error the `descriptors` will be destroyed. Make sure to duplicate the descriptors |
| /// if you need something outside of libva to access them. |
| pub fn 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> { |
| Surface::new( |
| Rc::clone(self), |
| rt_format, |
| va_fourcc, |
| width, |
| height, |
| usage_hint, |
| descriptors, |
| ) |
| } |
| |
| /// Creates a `Context` by wrapping around a `vaCreateContext` call. |
| /// |
| /// # Arguments |
| /// |
| /// * `config` - The configuration for the context |
| /// * `coded_width` - The coded picture width |
| /// * `coded_height` - The coded picture height |
| /// * `surfaces` - Optional hint for the amount of surfaces tied to the context |
| /// * `progressive` - Whether only progressive frame pictures are present in the sequence |
| pub fn 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> { |
| Context::new( |
| Rc::clone(self), |
| config, |
| coded_width, |
| coded_height, |
| surfaces, |
| progressive, |
| ) |
| } |
| |
| /// Creates a `Config` by wrapping around the `vaCreateConfig` call. |
| /// |
| /// `attrs` describe the attributes to set for this config. A list of the supported attributes |
| /// for a given profile/entrypoint pair can be retrieved using |
| /// [`Display::get_config_attributes`]. Other attributes will take their default values, and |
| /// `attrs` can be empty in order to obtain a default configuration. |
| pub fn create_config( |
| self: &Rc<Self>, |
| attrs: Vec<bindings::VAConfigAttrib>, |
| profile: bindings::VAProfile::Type, |
| entrypoint: bindings::VAEntrypoint::Type, |
| ) -> Result<Config, VaError> { |
| Config::new(Rc::clone(self), attrs, profile, entrypoint) |
| } |
| |
| /// Returns available image formats for this display by wrapping around `vaQueryImageFormats`. |
| pub fn query_image_formats(&self) -> Result<Vec<bindings::VAImageFormat>, VaError> { |
| // Safe because `self` represents a valid VADisplay. |
| let mut num_image_formats = unsafe { bindings::vaMaxNumImageFormats(self.handle) }; |
| let mut image_formats = Vec::with_capacity(num_image_formats as usize); |
| |
| // Safe because `self` represents a valid VADisplay. The `image_formats` vector is properly |
| // initialized and a valid size is passed to the C function, so it is impossible to write |
| // past the end of their storage by mistake. |
| va_check(unsafe { |
| bindings::vaQueryImageFormats( |
| self.handle, |
| image_formats.as_mut_ptr(), |
| &mut num_image_formats, |
| ) |
| })?; |
| |
| // Safe because the C function will have written exactly `num_image_format` entries, which |
| // is known to be within the vector's capacity. |
| unsafe { |
| image_formats.set_len(num_image_formats as usize); |
| } |
| |
| Ok(image_formats) |
| } |
| } |
| |
| impl Drop for Display { |
| fn drop(&mut self) { |
| // Safe because `self` represents a valid VADisplay. |
| unsafe { |
| bindings::vaTerminate(self.handle); |
| // The File will close the DRM fd on drop. |
| } |
| } |
| } |