// 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 { 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>(path: P) -> Result, 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> { 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, 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 { // 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, 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( self: &Rc, rt_format: u32, va_fourcc: Option, width: u32, height: u32, usage_hint: Option, descriptors: Vec, ) -> Result>, 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( self: &Rc, config: &Config, coded_width: u32, coded_height: u32, surfaces: Option<&Vec>>, progressive: bool, ) -> Result, 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, attrs: Vec, profile: bindings::VAProfile::Type, entrypoint: bindings::VAEntrypoint::Type, ) -> Result { 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, 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. } } }