X-Git-Url: https://git.nihav.org/?p=nihav-player.git;a=blobdiff_plain;f=nihed-cros-libva%2Fsrc%2Fdisplay.rs;fp=nihed-cros-libva%2Fsrc%2Fdisplay.rs;h=462bc3e8db1dcb154b14bf8026fa06ea579f4842;hp=0000000000000000000000000000000000000000;hb=683627242f69bed0b818d976d4b03d651e529697;hpb=549511e9deafd9ec08b3c80489e7e85c5f5f0b3b diff --git a/nihed-cros-libva/src/display.rs b/nihed-cros-libva/src/display.rs new file mode 100644 index 0000000..462bc3e --- /dev/null +++ b/nihed-cros-libva/src/display.rs @@ -0,0 +1,344 @@ +// 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::os::unix::io::AsRawFd; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; + +use anyhow::anyhow; +use anyhow::Context as AnyhowContext; +use anyhow::Result; + +use crate::bindings; +use crate::config::Config; +use crate::context::Context; +use crate::status::Status; +use crate::surface::Surface; +use crate::UsageHint; + +/// 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, +} + +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> { + let file = std::fs::File::options() + .read(true) + .write(true) + .open(path.as_ref()) + .context(format!("failed to open {}", path.as_ref().display()))?; + + // 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() { + // The File will close the DRM fd on drop. + return Err(anyhow!( + "failed to obtain VA display from DRM device {}", + path.as_ref().display() + )); + } + + 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. + Status(unsafe { bindings::vaInitialize(display, &mut major, &mut minor) }).check()?; + + Ok(Rc::new(Self { + handle: display, + drm_file: file, + })) + } + + /// 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. + pub fn query_config_profiles(&self) -> Result> { + // 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. + Status(unsafe { + bindings::vaQueryConfigProfiles( + self.handle, + profiles.as_mut_ptr(), + &mut max_num_profiles, + ) + }) + .check()?; + + // 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. + /// + /// 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. + pub fn query_config_entrypoints( + &self, + profile: bindings::VAProfile::Type, + ) -> Result> { + // 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. + Status(unsafe { + bindings::vaQueryConfigEntrypoints( + self.handle, + profile, + entrypoints.as_mut_ptr(), + &mut max_num_entrypoints, + ) + }) + .check()?; + + // 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`. + /// + /// 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<()> { + // 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. + Status(unsafe { + bindings::vaGetConfigAttributes( + self.handle, + profile, + entrypoint, + attributes.as_mut_ptr(), + attributes.len() as i32, + ) + }) + .check() + } + + /// Creates `Surface`s by wrapping around a `vaCreateSurfaces` call. + /// + /// # 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 + pub fn create_surfaces( + self: &Rc, + rt_format: u32, + va_fourcc: Option, + width: u32, + height: u32, + usage_hint: Option, + num_surfaces: u32, + ) -> Result> { + Surface::new( + Rc::clone(self), + rt_format, + va_fourcc, + width, + height, + usage_hint, + num_surfaces, + ) + } + + /// 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: i32, + coded_height: i32, + surfaces: Option<&Vec>, + progressive: bool, + ) -> Result> { + 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> { + // 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. + Status(unsafe { + bindings::vaQueryImageFormats( + self.handle, + image_formats.as_mut_ptr(), + &mut num_image_formats, + ) + }) + .check()?; + + // 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. + } + } +}