diff options
author | Zach Reizner <zachr@google.com> | 2018-04-25 17:49:56 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-05-08 04:57:58 -0700 |
commit | 6f6854312df5691763f3ee80be84fde16e82f6b8 (patch) | |
tree | 2d853cff0b8f3e5ad442a198af7c8840243b5435 /gpu_buffer/src/lib.rs | |
parent | e0823392f4dde29f3bde7f98d0bc481654843d27 (diff) | |
download | crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.gz crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.bz2 crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.lz crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.xz crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.zst crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.zip |
gpu_buffer: create bindings to minigbm
These bindings are needed to allocate dmabufs that will be used for accelerated rendering and zero-copy virtio-wayland support. TEST=cargo test -p gpu_buffer BUG=chromium:837073 Change-Id: I96d7bcdeaa1eda616a25fdcfedcbb734cd585ae7 Reviewed-on: https://chromium-review.googlesource.com/1029410 Commit-Ready: David Reveman <reveman@chromium.org> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'gpu_buffer/src/lib.rs')
-rw-r--r-- | gpu_buffer/src/lib.rs | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs new file mode 100644 index 0000000..6d0cfa9 --- /dev/null +++ b/gpu_buffer/src/lib.rs @@ -0,0 +1,553 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! A crate for creating [DRM](https://en.wikipedia.org/wiki/Direct_Rendering_Manager) managed +//! buffer objects. Such objects are useful for exporting as DMABUFs/prime FDs, texturing, render +//! targets, memory mapping, and scanout. +//! +//! # Examples +//! +//! ```rust +//! # use std::error::Error; +//! # use std::fs::File; +//! # use std::result::Result; +//! # use gpu_buffer::*; +//! # fn test() -> Result<(), Box<Error>> { +//! let drm_card = File::open("/dev/dri/card0")?; +//! let device = Device::new(drm_card).map_err(|_| "failed to create device")?; +//! let bo = device +//! .create_buffer(1024, +//! 512, +//! Format::new(b'X', b'R', b'2', b'4'), +//! Flags::empty().use_scanout(true)) +//! .map_err(|_| "failed to create buffer")?; +//! assert_eq!(bo.width(), 1024); +//! assert_eq!(bo.height(), 512); +//! assert_eq!(bo.format(), Format::new(b'X', b'R', b'2', b'4')); +//! assert_eq!(bo.num_planes(), 1); +//! # Ok(()) +//! # } +//! ``` + +extern crate data_model; + +mod raw; + +use std::os::raw::c_void; +use std::fmt; +use std::cmp::min; +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::ptr::{copy_nonoverlapping, null_mut}; +use std::rc::Rc; +use std::result::Result; + +use data_model::VolatileSlice; + +use raw::*; + +const MAP_FAILED: *mut c_void = (-1isize as *mut _); + +/// A [fourcc](https://en.wikipedia.org/wiki/FourCC) format identifier. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Format(u32); + +impl Format { + /// Constructs a format identifer using a fourcc byte sequence. + /// + /// # Examples + /// + /// ```rust + /// use gpu_buffer::Format; + /// + /// let format = Format::new(b'X', b'R', b'2', b'4'); + /// println!("format: {:?}", format); + /// ``` + #[inline(always)] + pub fn new(a: u8, b: u8, c: u8, d: u8) -> Format { + Format(a as u32 | (b as u32) << 8 | (c as u32) << 16 | (d as u32) << 24) + } + + /// Returns the fourcc code as a sequence of bytes. + #[inline(always)] + pub fn to_bytes(&self) -> [u8; 4] { + let f = self.0; + [f as u8, (f >> 8) as u8, (f >> 16) as u8, (f >> 24) as u8] + } +} + +impl From<u32> for Format { + fn from(u: u32) -> Format { + Format(u) + } +} + +impl From<Format> for u32 { + fn from(f: Format) -> u32 { + f.0 + } +} + +impl fmt::Debug for Format { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let b = self.to_bytes(); + if b.iter().all(u8::is_ascii_graphic) { + write!(f, + "fourcc({}{}{}{})", + b[0] as char, + b[1] as char, + b[2] as char, + b[3] as char) + } else { + write!(f, + "fourcc(0x{:02x}{:02x}{:02x}{:02x})", + b[0], + b[1], + b[2], + b[3]) + } + } +} + +/// Usage flags for constructing a buffer object. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Flags(u32); + +impl Flags { + /// Returns empty set of flags. + #[inline(always)] + pub fn empty() -> Flags { + Flags(0) + } + + /// Returns the given set of raw `GBM_BO` flags wrapped in a `Flags` struct. + #[inline(always)] + pub fn new(raw: u32) -> Flags { + Flags(raw) + } + + /// Sets the scanout flag's presence + #[inline(always)] + pub fn use_scanout(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_SCANOUT) + } else { + Flags(self.0 & !GBM_BO_USE_SCANOUT) + } + } + + /// Sets the cursor flag's presence + #[inline(always)] + pub fn use_cursor(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_CURSOR) + } else { + Flags(self.0 & !GBM_BO_USE_CURSOR) + } + } + + /// Sets the cursor 64x64 flag's presence + #[inline(always)] + pub fn use_cursor64(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_CURSOR_64X64) + } else { + Flags(self.0 & !GBM_BO_USE_CURSOR_64X64) + } + } + + /// Sets the rendering flag's presence + #[inline(always)] + pub fn use_rendering(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_RENDERING) + } else { + Flags(self.0 & !GBM_BO_USE_RENDERING) + } + } + + /// Sets the linear flag's presence + #[inline(always)] + pub fn use_linear(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_LINEAR) + } else { + Flags(self.0 & !GBM_BO_USE_LINEAR) + } + } + + /// Sets the texturing flag's presence + #[inline(always)] + pub fn use_texturing(self, e: bool) -> Flags { + if e { + Flags(self.0 | GBM_BO_USE_TEXTURING) + } else { + Flags(self.0 & !GBM_BO_USE_TEXTURING) + } + } +} + + +struct DeviceInner { + _fd: File, + gbm: *mut gbm_device, +} + +impl Drop for DeviceInner { + fn drop(self: &mut DeviceInner) { + // Safe because DeviceInner is only constructed with a valid gbm_device. + unsafe { + gbm_device_destroy(self.gbm); + } + } +} + +/// A device capable of allocating `Buffer`. +#[derive(Clone)] +pub struct Device(Rc<DeviceInner>); + +impl Device { + /// Returns a new `Device` using the given `fd` opened from a device in `/dev/dri/`. + pub fn new(fd: File) -> Result<Device, ()> { + // gbm_create_device is safe to call with a valid fd, and we check that a valid one is + // returned. The FD is not of the appropriate kind (i.e. not a DRM device), + // gbm_create_device should reject it. + let gbm = unsafe { gbm_create_device(fd.as_raw_fd()) }; + if gbm.is_null() { + Err(()) + } else { + Ok(Device(Rc::new(DeviceInner { _fd: fd, gbm }))) + } + } + + /// Creates a new buffer with the given metadata. + pub fn create_buffer(&self, + width: u32, + height: u32, + format: Format, + usage: Flags) + -> Result<Buffer, ()> { + // This is safe because only a valid gbm_device is used and the return value is checked. + let bo = unsafe { gbm_bo_create(self.0.gbm, width, height, format.0, usage.0) }; + if bo.is_null() { + Err(()) + } else { + Ok(Buffer(bo, self.clone())) + } + } +} + +/// An allocation from a `Device`. +pub struct Buffer(*mut gbm_bo, Device); + +impl Buffer { + /// The device + pub fn device(&self) -> &Device { + &self.1 + } + + /// Width in pixels. + pub fn width(&self) -> u32 { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_width(self.0) } + } + + /// Height in pixels. + pub fn height(&self) -> u32 { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_height(self.0) } + } + + /// Length in bytes of one row of the buffer. + pub fn stride(&self) -> u32 { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_stride(self.0) } + } + + /// Length in bytes of the stride or tiling. + pub fn stride_or_tiling(&self) -> u32 { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_stride_or_tiling(self.0) } + } + + /// `Format` of the buffer. + pub fn format(&self) -> Format { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { Format(gbm_bo_get_format(self.0)) } + } + + /// Format modifier flags for the buffer. + pub fn format_modifier(&self) -> u64 { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_format_modifier(self.0) } + } + + /// Number of planes present in this buffer. + pub fn num_planes(&self) -> usize { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_num_planes(self.0) } + } + + /// Exports a new dmabuf/prime file descriptor for the given plane. + pub fn export_plane_fd(&self, plane: usize) -> Result<File, i32> { + // This is always safe to call with a valid gbm_bo pointer. + match unsafe { gbm_bo_get_plane_fd(self.0, plane) } { + fd if fd >= 0 => Ok(unsafe { File::from_raw_fd(fd) }), + ret => Err(ret), + } + } + + /// Reads the given subsection of the buffer to `dst`. + pub fn read_to_volatile(&self, + x: u32, + y: u32, + width: u32, + height: u32, + plane: usize, + dst: VolatileSlice) + -> Result<(), ()> { + let mut stride = 0; + let mut map_data = null_mut(); + // Safe because only a valid gbm_bo object is used and the return value is checked. Only + // pointers coerced from stack references are used for returned values, and we trust gbm to + // only write as many bytes as the size of the pointed to values. + let mapping = unsafe { + gbm_bo_map(self.0, + x, + y, + width, + height, + GBM_BO_TRANSFER_READ, + &mut stride, + &mut map_data, + plane) + }; + if mapping == MAP_FAILED { + return Err(()); + } + + let copy_size = (y as u64) * (stride as u64); + + let res = if copy_size <= dst.size() { + // The two buffers can not be overlapping because we just made a new mapping in this + // scope. + unsafe { + copy_nonoverlapping(mapping as *mut u8, dst.as_ptr(), copy_size as usize); + } + Ok(()) + } else { + Err(()) + }; + + // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by + // gbm_bo_map. + unsafe { + gbm_bo_unmap(self.0, map_data); + } + + res + } + + /// Writes to the given subsection of the buffer from `src`. + pub fn write_from_slice(&self, + x: u32, + y: u32, + width: u32, + height: u32, + plane: usize, + src: &[u8]) + -> Result<(), ()> { + let mut stride = 0; + let mut map_data = null_mut(); + // Safe because only a valid gbm_bo object is used and the return value is checked. Only + // pointers coerced from stack references are used for returned values, and we trust gbm to + // only write as many bytes as the size of the pointed to values. + let mapping = unsafe { + gbm_bo_map(self.0, + x, + y, + width, + height, + GBM_BO_TRANSFER_WRITE, + &mut stride, + &mut map_data, + plane) + }; + if mapping == MAP_FAILED { + return Err(()); + } + + let copy_size = (height as u64) * (stride as u64); + let copy_sg_size = min(src.len() as u64, copy_size); + + // The two buffers can not be overlapping because we just made a new mapping in this scope. + unsafe { + copy_nonoverlapping(src.as_ptr(), mapping as *mut u8, copy_sg_size as usize); + } + + // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by + // gbm_bo_map. + unsafe { + gbm_bo_unmap(self.0, map_data); + } + + Ok(()) + } + + /// Writes to the given subsection of the buffer from `sgs`. + pub fn write_from_sg<'a, S: Iterator<Item = VolatileSlice<'a>>>(&self, + x: u32, + y: u32, + width: u32, + height: u32, + plane: usize, + sgs: S) + -> Result<(), ()> { + let mut stride = 0; + let mut map_data = null_mut(); + // Safe because only a valid gbm_bo object is used and the return value is checked. Only + // pointers coerced from stack references are used for returned values, and we trust gbm to + // only write as many bytes as the size of the pointed to values. + let mut mapping = unsafe { + gbm_bo_map(self.0, + x, + y, + width, + height, + GBM_BO_TRANSFER_WRITE, + &mut stride, + &mut map_data, + plane) + }; + if mapping == MAP_FAILED { + return Err(()); + } + + let mut copy_size = (height as u64) * (stride as u64); + + for sg in sgs { + let copy_sg_size = min(sg.size(), copy_size); + // The two buffers can not be overlapping because we just made a new mapping in this + // scope. + unsafe { + copy_nonoverlapping(sg.as_ptr(), mapping as *mut u8, copy_sg_size as usize); + } + + mapping = mapping.wrapping_offset(copy_sg_size as isize); + copy_size -= copy_sg_size; + if copy_size == 0 { + break; + } + } + + // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by + // gbm_bo_map. + unsafe { + gbm_bo_unmap(self.0, map_data); + } + + Ok(()) + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_destroy(self.0) } + } +} + +impl AsRawFd for Buffer { + fn as_raw_fd(&self) -> RawFd { + // This is always safe to call with a valid gbm_bo pointer. + unsafe { gbm_bo_get_fd(self.0) } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::fmt::Write; + use data_model::VolatileMemory; + + #[test] + fn format_debug() { + let f = Format::new(b'X', b'R', b'2', b'4'); + let mut buf = String::new(); + write!(&mut buf, "{:?}", f).unwrap(); + assert_eq!(buf, "fourcc(XR24)"); + + let f = Format::new(0, 1, 2, 16); + let mut buf = String::new(); + write!(&mut buf, "{:?}", f).unwrap(); + assert_eq!(buf, "fourcc(0x00010210)"); + } + + #[test] + #[ignore] // no access to /dev/dri + fn open_device() { + let drm_card = File::open("/dev/dri/card0").expect("failed to open card"); + Device::new(drm_card).expect("failed to create device with card"); + } + + #[test] + #[ignore] // no access to /dev/dri + fn create_buffer() { + let drm_card = File::open("/dev/dri/card0").expect("failed to open card"); + let device = Device::new(drm_card).expect("failed to create device with card"); + let bo = device + .create_buffer(1024, + 512, + Format::new(b'X', b'R', b'2', b'4'), + Flags::empty().use_scanout(true)) + .expect("failed to create buffer"); + + assert_eq!(bo.width(), 1024); + assert_eq!(bo.height(), 512); + assert_eq!(bo.format(), Format::new(b'X', b'R', b'2', b'4')); + assert_eq!(bo.num_planes(), 1); + } + + #[test] + #[ignore] // no access to /dev/dri + fn export_buffer() { + let drm_card = File::open("/dev/dri/card0").expect("failed to open card"); + let device = Device::new(drm_card).expect("failed to create device with card"); + let bo = device + .create_buffer(1024, + 1024, + Format::new(b'X', b'R', b'2', b'4'), + Flags::empty().use_scanout(true)) + .expect("failed to create buffer"); + bo.export_plane_fd(0).expect("failed to export plane"); + } + + + #[test] + #[ignore] // no access to /dev/dri + fn buffer_transfer() { + let drm_card = File::open("/dev/dri/card0").expect("failed to open card"); + let device = Device::new(drm_card).expect("failed to create device with card"); + let bo = device + .create_buffer(1024, + 1024, + Format::new(b'X', b'R', b'2', b'4'), + Flags::empty().use_scanout(true).use_linear(true)) + .expect("failed to create buffer"); + let mut dst: Vec<u8> = Vec::new(); + dst.resize((bo.stride() * bo.height()) as usize, 0x4A); + let dst_len = dst.len() as u64; + bo.write_from_slice(0, 0, 1024, 1024, 0, dst.as_slice()) + .expect("failed to read bo"); + bo.read_to_volatile(0, + 0, + 1024, + 1024, + 0, + dst.as_mut_slice().get_slice(0, dst_len).unwrap()) + .expect("failed to read bo"); + assert!(dst.iter().all(|&x| x == 0x4A)); + } +} |