diff options
author | Roman Yepishev <ryepishev@google.com> | 2024-03-28 15:05:33 +0000 |
---|---|---|
committer | Roman Yepishev <ryepishev@google.com> | 2024-03-28 15:05:33 +0000 |
commit | 4d2bf6d6932c4551c6c6e9661ce099dad06ee05d (patch) | |
tree | b0cc0dc0e8c27e95ffea0b288da2b9b82acb4dcf | |
parent | 3873302c6716e710b9ccae31d0de5d8af07b1554 (diff) | |
parent | 367c0f6625381ba9b0ae299bb94d3e6b729e797f (diff) | |
download | gbm-sdk-release.tar.gz |
Merge remote-tracking branch 'origin/upstream'platform-tools-35.0.1sdk-releasebusytown-mac-infra-release
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Android.bp | 30 | ||||
-rw-r--r-- | CHANGELOG.md | 81 | ||||
-rw-r--r-- | Cargo.toml | 76 | ||||
-rw-r--r-- | Cargo.toml.orig | 49 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | METADATA | 20 | ||||
-rw-r--r-- | MODULE_LICENSE_MIT | 0 | ||||
-rw-r--r-- | OWNERS | 7 | ||||
-rw-r--r-- | README.md | 65 | ||||
-rw-r--r-- | cargo_embargo.json | 10 | ||||
-rw-r--r-- | src/buffer_object.rs | 711 | ||||
-rw-r--r-- | src/device.rs | 421 | ||||
-rw-r--r-- | src/lib.rs | 209 | ||||
-rw-r--r-- | src/surface.rs | 125 |
15 files changed, 1831 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..c8aa4db --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "606c4260e0147256fb5c2901bbe837c0dc7d9f2d" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..6b22e07 --- /dev/null +++ b/Android.bp @@ -0,0 +1,30 @@ +// This file is generated by cargo_embargo. +// Do not modify this file as changes will be overridden on upgrade. + +// TODO: Add license. +rust_library { + name: "libgbm_rust", + host_supported: true, + crate_name: "gbm", + cargo_env_compat: true, + cargo_pkg_version: "0.14.2", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "drm", + "drm-support", + ], + rustlibs: [ + "libbitflags", + "libdrm_fourcc", + "libdrm_rust", + "libgbm_sys", + "liblibc", + ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + product_available: true, + vendor_available: true, +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..402ff68 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,81 @@ +## 0.14.2 + +- buffer objects: Add a method to receive the corresponding devices file descriptor + +## 0.14.1 + +- Bugfix: Don't limit modifier lists to `GBM_MAX_PLANES` + +## 0.14.0 + +- Update to drm-rs 0.11 +- Use `BorrowedFd` instead of `RawFd` in API +- Don't require generated bindings for specific OS/architecture to build +- Fix build without default features + +## 0.13.0 + +- Update to drm-rs 0.10 +- Update wayland-server to 0.31 + +## 0.12.0 + +- Update to drm-rs 0.9 + +## 0.11.0 + +- Test for `-1` in fd and fd_for_plane + +## 0.10.0 + +- Update `wayland-rs` to 0.30 +- Use io-safe types over `RawFd` +- Update to drm-rs 0.8 +- YANKED: No errors for fd-methods, use 0.11.0 + +## 0.9.0 + +- Update to drm-rs 0.7 + +## 0.8.0 + +- Update to drm-rs 0.6 + +## 0.7.0 + +- Update to drm-rs 0.5 + +## 0.6.0 + +- Update to drm-rs 0.4 +- Update bindings, add new functionality +- Make Device clonable +- Use drm-fourcc for Formats +- Implement Send where applicable +- Switch to new std-Error trait + +## 0.5.0 + +- Make `Surface::lock_front_buffer` unsafe as it may cause segfaults + +## 0.4.0 + +- API overhaul to use ref-counting internally to: + - Enable out-of-order destruction without causing leaks, crashes or double-frees + - Remove lifetimes, which made this api a pain to work with and almost required hacks like the `rental` crate +- Remove `FromRaw` as it is not possible to create most structs anymore without a reference to the underlying `Device` +- Remove `Device` initializers other then `new_from_fd`. Lifetimes do not exist anymore and it is part of the contract to drop the `Device` before closing the file descriptor. +- Add `Device` initializer `new` that wraps any open drm device. +- Implement the [`drm-rs`](https://github.com/Smithay/drm-rs) `Device` traits for `Device` where applicable. + +## 0.3.0 + +- Upgrade to bitflags 1.0 with associated consts + +## 0.2.0 + +- drm-rs support + +## 0.1.0 + +- Initial release diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..693d325 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,76 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "gbm" +version = "0.14.2" +authors = ["Victoria Brekenfeld <github@drakulix.de>"] +exclude = [ + ".gitignore", + ".travis.yml", + ".rustfmt.toml", + ".github", +] +description = "libgbm bindings for rust" +documentation = "https://docs.rs/gbm" +readme = "README.md" +keywords = [ + "wayland", + "gbm", + "drm", + "bindings", +] +categories = ["external-ffi-bindings"] +license = "MIT" +repository = "https://github.com/Smithay/gbm.rs" + +[dependencies.bitflags] +version = "1.2" + +[dependencies.drm] +version = "0.11.0" +optional = true + +[dependencies.drm-fourcc] +version = "2.2" + +[dependencies.gbm-sys] +version = "0.3.0" + +[dependencies.libc] +version = "0.2" + +[dependencies.wayland-backend] +version = "0.3" +features = ["server_system"] +optional = true + +[dependencies.wayland-server] +version = "0.31" +optional = true + +[dev-dependencies.drm] +version = "0.11.0" + +[features] +default = [ + "import-wayland", + "import-egl", + "drm-support", +] +drm-support = ["drm"] +import-egl = [] +import-wayland = [ + "wayland-server", + "wayland-backend", +] +use_bindgen = ["gbm-sys/use_bindgen"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..ecc5649 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,49 @@ +[package] +name = "gbm" +description = "libgbm bindings for rust" +license = "MIT" +documentation = "https://docs.rs/gbm" +repository = "https://github.com/Smithay/gbm.rs" +version = "0.14.2" +keywords = ["wayland", "gbm", "drm", "bindings"] +categories = ["external-ffi-bindings"] +authors = ["Victoria Brekenfeld <github@drakulix.de>"] +exclude = [".gitignore", ".travis.yml", ".rustfmt.toml", ".github"] +edition = "2021" + +[dependencies] +libc = "0.2" +bitflags = "1.2" +drm-fourcc = "2.2" + +[dependencies.gbm-sys] +version = "0.3.0" +path = "./gbm-sys" + +[dependencies.drm] +version = "0.11.0" +optional = true + +[dependencies.wayland-server] +version = "0.31" +optional = true + +[dependencies.wayland-backend] +version = "0.3" +features = ["server_system"] +optional = true + +[dev-dependencies.drm] +version = "0.11.0" + +[features] +default = ["import-wayland", "import-egl", "drm-support"] +import-wayland = ["wayland-server", "wayland-backend"] +import-egl = [] +drm-support = ["drm"] +use_bindgen = ["gbm-sys/use_bindgen"] + +[workspace] +members = [ + "gbm-sys" +] @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Victor Brekenfeld + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..53181fb --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "gbm" +description: "libgbm bindings for rust" +third_party { + identifier { + type: "crates.io" + value: "gbm" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/gbm/gbm-0.14.2.crate" + primary_source: true + } + version: "0.14.2" + license_type: NOTICE + last_upgrade_date { + year: 2024 + month: 3 + day: 13 + } +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1,7 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS + +dextero@google.com +vill@google.com +nputikhin@google.com +istvannador@google.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..116f775 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +## Safe `libgbm` bindings for [rust](https://www.rust-lang.org) + +The Generic Buffer Manager + +This module provides an abstraction that the caller can use to request a +buffer from the underlying memory management system for the platform. + +This allows the creation of portable code whilst still allowing access to +the underlying memory manager. + +This library is best used in combination with [`drm-rs`](https://github.com/Smithay/drm-rs), +provided through the `drm-support` feature. + +## Usage + +Add to your Cargo.toml + +```toml +gbm = "0.14.2" +``` + +## Example + +```rust +extern crate drm; +extern crate gbm; + +use drm::control::{self, crtc, framebuffer}; +use gbm::{BufferObjectFlags, Device, Format}; + +// ... init your drm device ... +let drm = init_drm_device(); + +// init a GBM device +let gbm = Device::new(drm).unwrap(); + +// create a buffer +let mut bo = gbm + .create_buffer_object::<()>( + 1280, + 720, + Format::Argb8888, + BufferObjectFlags::SCANOUT | BufferObjectFlags::WRITE, + ) + .unwrap(); + +// write something to it (usually use import or egl rendering instead) +let buffer = { + let mut buffer = Vec::new(); + for i in 0..1280 { + for _ in 0..720 { + buffer.push(if i % 2 == 0 { 0 } else { 255 }); + } + } + buffer +}; +bo.write(&buffer).unwrap(); + +// create a framebuffer from our buffer +let fb = gbm.add_framebuffer(&bo, 32, 32).unwrap(); + +// display it (and get a crtc, mode and connector before) +gbm.set_crtc(crtc_handle, Some(fb), (0, 0), &[con], Some(mode)) + .unwrap(); +``` diff --git a/cargo_embargo.json b/cargo_embargo.json new file mode 100644 index 0000000..f275bc5 --- /dev/null +++ b/cargo_embargo.json @@ -0,0 +1,10 @@ +{ + "run_cargo": false, + "module_name_overrides": { + "libdrm": "libdrm_rust", + "libgbm": "libgbm_rust" + }, + "features": [ + "drm-support" + ] +} diff --git a/src/buffer_object.rs b/src/buffer_object.rs new file mode 100644 index 0000000..514ee8e --- /dev/null +++ b/src/buffer_object.rs @@ -0,0 +1,711 @@ +use crate::{AsRaw, Device, DeviceDestroyedError, Format, Modifier, Ptr, WeakPtr}; + +#[cfg(feature = "drm-support")] +use drm::buffer::{Buffer as DrmBuffer, Handle, PlanarBuffer as DrmPlanarBuffer}; +use std::os::unix::io::{AsFd, BorrowedFd, FromRawFd, OwnedFd}; + +use std::error; +use std::fmt; +use std::io::{Error as IoError, Result as IoResult}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::slice; + +/// A GBM buffer object +pub struct BufferObject<T: 'static> { + pub(crate) ffi: Ptr<ffi::gbm_bo>, + pub(crate) _device: WeakPtr<ffi::gbm_device>, + pub(crate) _userdata: PhantomData<T>, +} + +impl<T> fmt::Debug for BufferObject<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("BufferObject") + .field("ptr", &format_args!("{:p}", self.ffi)) + .field("device", &format_args!("{:p}", &self._device)) + .field("width", &self.width().unwrap_or(0)) + .field("height", &self.height().unwrap_or(0)) + .field("offsets", &self.offsets()) + .field("stride", &self.stride().unwrap_or(0)) + .field("format", &self.format().ok()) + .field("modifier", &self.modifier().ok()) + .finish() + } +} + +bitflags! { + /// Flags to indicate the intended use for the buffer - these are passed into + /// [`Device::create_buffer_object()`]. + /// + /// Use [`Device::is_format_supported()`] to check if the combination of format + /// and use flags are supported + pub struct BufferObjectFlags: u32 { + /// Buffer is going to be presented to the screen using an API such as KMS + const SCANOUT = ffi::gbm_bo_flags::GBM_BO_USE_SCANOUT as u32; + /// Buffer is going to be used as cursor + const CURSOR = ffi::gbm_bo_flags::GBM_BO_USE_CURSOR as u32; + /// Buffer is going to be used as cursor (deprecated) + #[deprecated = "Use CURSOR instead"] + const CURSOR_64X64 = ffi::gbm_bo_flags::GBM_BO_USE_CURSOR_64X64 as u32; + /// Buffer is to be used for rendering - for example it is going to be used + /// as the storage for a color buffer + const RENDERING = ffi::gbm_bo_flags::GBM_BO_USE_RENDERING as u32; + /// Buffer can be used for [`BufferObject::write()`]. This is guaranteed to work + /// with [`Self::CURSOR`], but may not work for other combinations. + const WRITE = ffi::gbm_bo_flags::GBM_BO_USE_WRITE as u32; + /// Buffer is linear, i.e. not tiled. + const LINEAR = ffi::gbm_bo_flags::GBM_BO_USE_LINEAR as u32; + /// Buffer is protected + const PROTECTED = ffi::gbm_bo_flags::GBM_BO_USE_PROTECTED as u32; + } +} + +/// Abstraction representing the handle to a buffer allocated by the manager +pub type BufferObjectHandle = ffi::gbm_bo_handle; + +enum BORef<'a, T: 'static> { + Ref(&'a BufferObject<T>), + Mut(&'a mut BufferObject<T>), +} + +/// A mapped buffer object +pub struct MappedBufferObject<'a, T: 'static> { + bo: BORef<'a, T>, + buffer: &'a mut [u8], + data: *mut ::libc::c_void, + stride: u32, + height: u32, + width: u32, + x: u32, + y: u32, +} + +impl<'a, T> fmt::Debug for MappedBufferObject<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MappedBufferObject") + .field( + "mode", + &match self.bo { + BORef::Ref(_) => format_args!("read"), + BORef::Mut(_) => format_args!("write"), + }, + ) + .field( + "buffer", + match &self.bo { + BORef::Ref(bo) => *bo, + BORef::Mut(bo) => *bo, + }, + ) + .finish() + } +} + +impl<'a, T: 'static> MappedBufferObject<'a, T> { + /// Get the stride of the buffer object + /// + /// This is calculated by the backend when it does the allocation of the buffer. + pub fn stride(&self) -> u32 { + self.stride + } + + /// The height of the mapped region for the buffer + pub fn height(&self) -> u32 { + self.height + } + + /// The width of the mapped region for the buffer + pub fn width(&self) -> u32 { + self.width + } + + /// The X (top left origin) starting position of the mapped region for the buffer + pub fn x(&self) -> u32 { + self.x + } + + /// The Y (top left origin) starting position of the mapped region for the buffer + pub fn y(&self) -> u32 { + self.y + } + + /// Access to the underlying image buffer + pub fn buffer(&self) -> &[u8] { + self.buffer + } + + /// Mutable access to the underlying image buffer + pub fn buffer_mut(&mut self) -> &mut [u8] { + self.buffer + } +} + +impl<'a, T: 'static> Deref for MappedBufferObject<'a, T> { + type Target = BufferObject<T>; + fn deref(&self) -> &BufferObject<T> { + match &self.bo { + BORef::Ref(bo) => bo, + BORef::Mut(bo) => bo, + } + } +} + +impl<'a, T: 'static> DerefMut for MappedBufferObject<'a, T> { + fn deref_mut(&mut self) -> &mut BufferObject<T> { + match &mut self.bo { + BORef::Ref(_) => unreachable!(), + BORef::Mut(bo) => bo, + } + } +} + +impl<'a, T: 'static> Drop for MappedBufferObject<'a, T> { + fn drop(&mut self) { + let ffi = match &self.bo { + BORef::Ref(bo) => &bo.ffi, + BORef::Mut(bo) => &bo.ffi, + }; + unsafe { ffi::gbm_bo_unmap(**ffi, self.data) } + } +} + +unsafe extern "C" fn destroy<T: 'static>(_: *mut ffi::gbm_bo, ptr: *mut ::libc::c_void) { + let ptr = ptr as *mut T; + if !ptr.is_null() { + let _ = Box::from_raw(ptr); + } +} + +impl<T: 'static> BufferObject<T> { + /// Get the width of the buffer object + pub fn width(&self) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_width(*self.ffi) }) + } + + /// Get the height of the buffer object + pub fn height(&self) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_height(*self.ffi) }) + } + + /// Get the stride of the buffer object + pub fn stride(&self) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_stride(*self.ffi) }) + } + + /// Get the stride of the buffer object + pub fn stride_for_plane(&self, plane: i32) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_stride_for_plane(*self.ffi, plane) }) + } + + /// Get the format of the buffer object + pub fn format(&self) -> Result<Format, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok( + Format::try_from(unsafe { ffi::gbm_bo_get_format(*self.ffi) }) + .expect("libgbm returned invalid buffer format"), + ) + } + + /// Get the bits per pixel of the buffer object + pub fn bpp(&self) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_bpp(*self.ffi) }) + } + + /// Get the offset for a plane of the buffer object + pub fn offset(&self, plane: i32) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_offset(*self.ffi, plane) }) + } + + /// Get the plane count of the buffer object + pub fn plane_count(&self) -> Result<u32, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_plane_count(*self.ffi) as u32 }) + } + + /// Get the modifier of the buffer object + pub fn modifier(&self) -> Result<Modifier, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(Modifier::from(unsafe { + ffi::gbm_bo_get_modifier(*self.ffi) + })) + } + + /// Get a DMA-BUF file descriptor for the buffer object + /// + /// This function creates a DMA-BUF (also known as PRIME) file descriptor + /// handle for the buffer object. Each call to [`Self::fd()`] returns a new + /// file descriptor and the caller is responsible for closing the file + /// descriptor. + pub fn fd(&self) -> Result<OwnedFd, FdError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + unsafe { + let fd = ffi::gbm_bo_get_fd(*self.ffi); + + if fd == -1 { + return Err(InvalidFdError.into()); + } + + Ok(OwnedFd::from_raw_fd(fd)) + } + } + + /// Get the file descriptor of the gbm device of this buffer object + pub fn device_fd(&self) -> Result<BorrowedFd, DeviceDestroyedError> { + let device_ptr = self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { BorrowedFd::borrow_raw(ffi::gbm_device_get_fd(*device_ptr)) }) + } + + /// Get the handle of the buffer object + /// + /// This is stored in the platform generic union [`BufferObjectHandle`] type. However + /// the format of this handle is platform specific. + pub fn handle(&self) -> Result<BufferObjectHandle, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_handle(*self.ffi) }) + } + + /// Get a DMA-BUF file descriptor for a plane of the buffer object + /// + /// This function creates a DMA-BUF (also known as PRIME) file descriptor + /// handle for a plane of the buffer object. Each call to [`Self::fd_for_plane()`] + /// returns a new file descriptor and the caller is responsible for closing + /// the file descriptor. + pub fn fd_for_plane(&self, plane: i32) -> Result<OwnedFd, FdError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + unsafe { + let fd = ffi::gbm_bo_get_fd_for_plane(*self.ffi, plane); + + if fd == -1 { + return Err(InvalidFdError.into()); + } + + Ok(OwnedFd::from_raw_fd(fd)) + } + } + + /// Get the handle of a plane of the buffer object + /// + /// This is stored in the platform generic union [`BufferObjectHandle`] type. However + /// the format of this handle is platform specific. + pub fn handle_for_plane(&self, plane: i32) -> Result<BufferObjectHandle, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + Ok(unsafe { ffi::gbm_bo_get_handle_for_plane(*self.ffi, plane) }) + } + + /// Map a region of a GBM buffer object for cpu access + /// + /// This function maps a region of a GBM bo for cpu read access. + pub fn map<'a, D, F, S>( + &'a self, + device: &Device<D>, + x: u32, + y: u32, + width: u32, + height: u32, + f: F, + ) -> Result<IoResult<S>, WrongDeviceError> + where + D: AsFd + 'static, + F: FnOnce(&MappedBufferObject<'a, T>) -> S, + { + let device_ref = self._device.upgrade().ok_or(WrongDeviceError)?; + if *device_ref != device.as_raw_mut() { + // not matching + return Err(WrongDeviceError); + } + + unsafe { + let mut data: *mut ::libc::c_void = ptr::null_mut(); + let mut stride = 0; + let ptr = ffi::gbm_bo_map( + *self.ffi, + x, + y, + width, + height, + ffi::gbm_bo_transfer_flags::GBM_BO_TRANSFER_READ as u32, + &mut stride as *mut _, + &mut data as *mut _, + ); + + if ptr.is_null() { + Ok(Err(IoError::last_os_error())) + } else { + Ok(Ok(f(&MappedBufferObject { + bo: BORef::Ref(self), + buffer: slice::from_raw_parts_mut(ptr as *mut _, (height * stride) as usize), + data, + stride, + height, + width, + x, + y, + }))) + } + } + } + + /// Map a region of a GBM buffer object for cpu access + /// + /// This function maps a region of a GBM bo for cpu read/write access. + pub fn map_mut<'a, D, F, S>( + &'a mut self, + device: &Device<D>, + x: u32, + y: u32, + width: u32, + height: u32, + f: F, + ) -> Result<IoResult<S>, WrongDeviceError> + where + D: AsFd + 'static, + F: FnOnce(&mut MappedBufferObject<'a, T>) -> S, + { + let device_ref = self._device.upgrade().ok_or(WrongDeviceError)?; + if *device_ref != device.as_raw_mut() { + // not matching + return Err(WrongDeviceError); + } + + unsafe { + let mut data: *mut ::libc::c_void = ptr::null_mut(); + let mut stride = 0; + let ptr = ffi::gbm_bo_map( + *self.ffi, + x, + y, + width, + height, + ffi::gbm_bo_transfer_flags::GBM_BO_TRANSFER_READ_WRITE as u32, + &mut stride as *mut _, + &mut data as *mut _, + ); + + if ptr.is_null() { + Ok(Err(IoError::last_os_error())) + } else { + Ok(Ok(f(&mut MappedBufferObject { + bo: BORef::Mut(self), + buffer: slice::from_raw_parts_mut(ptr as *mut _, (height * stride) as usize), + data, + stride, + height, + width, + x, + y, + }))) + } + } + } + + /// Write data into the buffer object + /// + /// If the buffer object was created with the [`BufferObjectFlags::WRITE`] flag, + /// this function can be used to write data into the buffer object. The + /// data is copied directly into the object and it's the responsibility + /// of the caller to make sure the data represents valid pixel data, + /// according to the width, height, stride and format of the buffer object. + pub fn write(&mut self, buffer: &[u8]) -> Result<IoResult<()>, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let result = + unsafe { ffi::gbm_bo_write(*self.ffi, buffer.as_ptr() as *const _, buffer.len() as _) }; + if result != 0 { + Ok(Err(IoError::last_os_error())) + } else { + Ok(Ok(())) + } + } + + /// Sets the userdata of the buffer object. + /// + /// If previously userdata was set, it is returned. + pub fn set_userdata(&mut self, userdata: T) -> Result<Option<T>, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let old = self.take_userdata(); + + let boxed = Box::new(userdata); + unsafe { + ffi::gbm_bo_set_user_data( + *self.ffi, + Box::into_raw(boxed) as *mut _, + Some(destroy::<T>), + ); + } + + old + } + + /// Clears the set userdata of the buffer object. + pub fn clear_userdata(&mut self) -> Result<(), DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let _ = self.take_userdata(); + Ok(()) + } + + /// Returns a reference to set userdata, if any. + pub fn userdata(&self) -> Result<Option<&T>, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let raw = unsafe { ffi::gbm_bo_get_user_data(*self.ffi) }; + + if raw.is_null() { + Ok(None) + } else { + unsafe { Ok(Some(&*(raw as *mut T))) } + } + } + + /// Returns a mutable reference to set userdata, if any. + pub fn userdata_mut(&mut self) -> Result<Option<&mut T>, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let raw = unsafe { ffi::gbm_bo_get_user_data(*self.ffi) }; + + if raw.is_null() { + Ok(None) + } else { + unsafe { Ok(Some(&mut *(raw as *mut T))) } + } + } + + /// Takes ownership of previously set userdata, if any. + /// + /// This removes the userdata from the buffer object. + pub fn take_userdata(&mut self) -> Result<Option<T>, DeviceDestroyedError> { + self._device.upgrade().ok_or(DeviceDestroyedError)?; + let raw = unsafe { ffi::gbm_bo_get_user_data(*self.ffi) }; + + if raw.is_null() { + Ok(None) + } else { + unsafe { + let boxed = Box::from_raw(raw as *mut T); + ffi::gbm_bo_set_user_data(*self.ffi, ptr::null_mut(), None); + Ok(Some(*boxed)) + } + } + } + + pub(crate) unsafe fn new( + ffi: *mut ffi::gbm_bo, + device: WeakPtr<ffi::gbm_device>, + ) -> BufferObject<T> { + BufferObject { + ffi: Ptr::<ffi::gbm_bo>::new(ffi, |ptr| ffi::gbm_bo_destroy(ptr)), + _device: device, + _userdata: PhantomData, + } + } + + fn offsets(&self) -> [u32; 4] { + let num = self + .plane_count() + .expect("GbmDevice does not exist anymore"); + [ + BufferObject::<T>::offset(self, 0).unwrap(), + if num > 1 { + BufferObject::<T>::offset(self, 1).unwrap() + } else { + 0 + }, + if num > 2 { + BufferObject::<T>::offset(self, 2).unwrap() + } else { + 0 + }, + if num > 3 { + BufferObject::<T>::offset(self, 3).unwrap() + } else { + 0 + }, + ] + } +} + +impl<T: 'static> AsRaw<ffi::gbm_bo> for BufferObject<T> { + fn as_raw(&self) -> *const ffi::gbm_bo { + *self.ffi + } +} + +#[cfg(feature = "drm-support")] +impl<T: 'static> DrmBuffer for BufferObject<T> { + fn size(&self) -> (u32, u32) { + ( + self.width().expect("GbmDevice does not exist anymore"), + self.height().expect("GbmDevice does not exist anymore"), + ) + } + + fn format(&self) -> Format { + BufferObject::<T>::format(self).expect("GbmDevice does not exist anymore") + } + + fn pitch(&self) -> u32 { + self.stride().expect("GbmDevice does not exist anymore") + } + + fn handle(&self) -> Handle { + use std::num::NonZeroU32; + unsafe { + Handle::from(NonZeroU32::new_unchecked( + self.handle() + .expect("GbmDevice does not exist anymore") + .u32_, + )) + } + } +} + +#[cfg(feature = "drm-support")] +impl<T: 'static> DrmPlanarBuffer for BufferObject<T> { + fn size(&self) -> (u32, u32) { + ( + self.width().expect("GbmDevice does not exist anymore"), + self.height().expect("GbmDevice does not exist anymore"), + ) + } + fn format(&self) -> Format { + BufferObject::<T>::format(self).expect("GbmDevice does not exist anymore") + } + fn modifier(&self) -> Option<Modifier> { + Some(BufferObject::<T>::modifier(self).expect("GbmDevice does not exist anymore")) + } + fn pitches(&self) -> [u32; 4] { + let num = self + .plane_count() + .expect("GbmDevice does not exist anymore"); + [ + BufferObject::<T>::stride_for_plane(self, 0).unwrap(), + if num > 1 { + BufferObject::<T>::stride_for_plane(self, 1).unwrap() + } else { + 0 + }, + if num > 2 { + BufferObject::<T>::stride_for_plane(self, 2).unwrap() + } else { + 0 + }, + if num > 3 { + BufferObject::<T>::stride_for_plane(self, 3).unwrap() + } else { + 0 + }, + ] + } + fn handles(&self) -> [Option<Handle>; 4] { + use std::num::NonZeroU32; + let num = self + .plane_count() + .expect("GbmDevice does not exist anymore"); + [ + Some(unsafe { + Handle::from(NonZeroU32::new_unchecked( + BufferObject::<T>::handle_for_plane(self, 0).unwrap().u32_, + )) + }), + if num > 1 { + Some(unsafe { + Handle::from(NonZeroU32::new_unchecked( + BufferObject::<T>::handle_for_plane(self, 1).unwrap().u32_, + )) + }) + } else { + None + }, + if num > 2 { + Some(unsafe { + Handle::from(NonZeroU32::new_unchecked( + BufferObject::<T>::handle_for_plane(self, 2).unwrap().u32_, + )) + }) + } else { + None + }, + if num > 3 { + Some(unsafe { + Handle::from(NonZeroU32::new_unchecked( + BufferObject::<T>::handle_for_plane(self, 3).unwrap().u32_, + )) + }) + } else { + None + }, + ] + } + fn offsets(&self) -> [u32; 4] { + self.offsets() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Thrown when the GBM device does not belong to the buffer object +pub struct WrongDeviceError; + +impl fmt::Display for WrongDeviceError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "The gbm device specified is not the one this buffer object belongs to" + ) + } +} + +impl error::Error for WrongDeviceError {} + +/// Thrown when the fd is invalid +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidFdError; + +impl fmt::Display for InvalidFdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "The returned fd is invalid") + } +} + +impl error::Error for InvalidFdError {} + +/// Thrown when an error occurs during getting a bo fd +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FdError { + /// The underlying device has been destroyed + DeviceDestroyed(DeviceDestroyedError), + /// The operation returned an invalid fd + InvalidFd(InvalidFdError), +} + +impl From<DeviceDestroyedError> for FdError { + fn from(err: DeviceDestroyedError) -> Self { + FdError::DeviceDestroyed(err) + } +} + +impl From<InvalidFdError> for FdError { + fn from(err: InvalidFdError) -> Self { + FdError::InvalidFd(err) + } +} + +impl fmt::Display for FdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FdError::DeviceDestroyed(err) => err.fmt(f), + FdError::InvalidFd(err) => err.fmt(f), + } + } +} + +impl error::Error for FdError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + FdError::DeviceDestroyed(err) => Some(err), + FdError::InvalidFd(err) => Some(err), + } + } +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..b5aa670 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,421 @@ +use crate::{AsRaw, BufferObject, BufferObjectFlags, Format, Modifier, Ptr, Surface}; + +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; + +use std::error; +use std::ffi::CStr; +use std::fmt; +use std::io::{Error as IoError, Result as IoResult}; +use std::ops::{Deref, DerefMut}; + +#[cfg(feature = "import-wayland")] +use wayland_server::protocol::wl_buffer::WlBuffer; + +#[cfg(feature = "import-egl")] +/// An EGLImage handle +pub type EGLImage = *mut libc::c_void; + +#[cfg(feature = "drm-support")] +use drm::control::Device as DrmControlDevice; +#[cfg(feature = "drm-support")] +use drm::Device as DrmDevice; + +/// An open GBM device +pub struct Device<T: AsFd> { + // Declare `ffi` first so it is dropped before `fd` + ffi: Ptr<ffi::gbm_device>, + fd: T, +} + +impl<T: AsFd> fmt::Debug for Device<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Device") + .field("ptr", &format_args!("{:p}", &self.ffi)) + .finish() + } +} + +impl<T: AsFd + Clone> Clone for Device<T> { + fn clone(&self) -> Device<T> { + Device { + fd: self.fd.clone(), + ffi: self.ffi.clone(), + } + } +} + +impl<T: AsFd> AsFd for Device<T> { + fn as_fd(&self) -> BorrowedFd { + unsafe { BorrowedFd::borrow_raw(ffi::gbm_device_get_fd(*self.ffi)) } + } +} + +impl<T: AsFd> AsRaw<ffi::gbm_device> for Device<T> { + fn as_raw(&self) -> *const ffi::gbm_device { + *self.ffi + } +} + +impl<T: AsFd> Deref for Device<T> { + type Target = T; + fn deref(&self) -> &T { + &self.fd + } +} + +impl<T: AsFd> DerefMut for Device<T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.fd + } +} + +impl<T: AsFd> Device<T> { + /// Open a GBM device from a given open DRM device. + /// + /// The underlying file descriptor passed in is used by the backend to communicate with + /// platform for allocating the memory. For allocations using DRI this would be + /// the file descriptor returned when opening a device such as `/dev/dri/card0`. + pub fn new(fd: T) -> IoResult<Device<T>> { + let ptr = unsafe { ffi::gbm_create_device(fd.as_fd().as_raw_fd()) }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(Device { + fd, + ffi: Ptr::<ffi::gbm_device>::new(ptr, |ptr| unsafe { + ffi::gbm_device_destroy(ptr) + }), + }) + } + } + + /// Get the backend name + pub fn backend_name(&self) -> &str { + unsafe { + CStr::from_ptr(ffi::gbm_device_get_backend_name(*self.ffi)) + .to_str() + .expect("GBM passed invalid utf8 string") + } + } + + /// Test if a format is supported for a given set of usage flags + pub fn is_format_supported(&self, format: Format, usage: BufferObjectFlags) -> bool { + unsafe { ffi::gbm_device_is_format_supported(*self.ffi, format as u32, usage.bits()) != 0 } + } + + /// Allocate a new surface object + pub fn create_surface<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + usage: BufferObjectFlags, + ) -> IoResult<Surface<U>> { + let ptr = unsafe { + ffi::gbm_surface_create(*self.ffi, width, height, format as u32, usage.bits()) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { Surface::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Allocate a new surface object with explicit modifiers + pub fn create_surface_with_modifiers<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + modifiers: impl Iterator<Item = Modifier>, + ) -> IoResult<Surface<U>> { + let mods = modifiers.map(|m| m.into()).collect::<Vec<u64>>(); + let ptr = unsafe { + ffi::gbm_surface_create_with_modifiers( + *self.ffi, + width, + height, + format as u32, + mods.as_ptr(), + mods.len() as u32, + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { Surface::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Allocate a new surface object with explicit modifiers and flags + pub fn create_surface_with_modifiers2<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + modifiers: impl Iterator<Item = Modifier>, + usage: BufferObjectFlags, + ) -> IoResult<Surface<U>> { + let mods = modifiers.map(|m| m.into()).collect::<Vec<u64>>(); + let ptr = unsafe { + ffi::gbm_surface_create_with_modifiers2( + *self.ffi, + width, + height, + format as u32, + mods.as_ptr(), + mods.len() as u32, + usage.bits(), + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { Surface::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Allocate a buffer object for the given dimensions + pub fn create_buffer_object<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + usage: BufferObjectFlags, + ) -> IoResult<BufferObject<U>> { + let ptr = + unsafe { ffi::gbm_bo_create(*self.ffi, width, height, format as u32, usage.bits()) }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Allocate a buffer object for the given dimensions with explicit modifiers + pub fn create_buffer_object_with_modifiers<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + modifiers: impl Iterator<Item = Modifier>, + ) -> IoResult<BufferObject<U>> { + let mods = modifiers.map(|m| m.into()).collect::<Vec<u64>>(); + let ptr = unsafe { + ffi::gbm_bo_create_with_modifiers( + *self.ffi, + width, + height, + format as u32, + mods.as_ptr(), + mods.len() as u32, + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Allocate a buffer object for the given dimensions with explicit modifiers and flags + pub fn create_buffer_object_with_modifiers2<U: 'static>( + &self, + width: u32, + height: u32, + format: Format, + modifiers: impl Iterator<Item = Modifier>, + usage: BufferObjectFlags, + ) -> IoResult<BufferObject<U>> { + let mods = modifiers.map(|m| m.into()).collect::<Vec<u64>>(); + let ptr = unsafe { + ffi::gbm_bo_create_with_modifiers2( + *self.ffi, + width, + height, + format as u32, + mods.as_ptr(), + mods.len() as u32, + usage.bits(), + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Create a GBM buffer object from a wayland buffer + /// + /// This function imports a foreign [`WlBuffer`] object and creates a new GBM + /// buffer object for it. + /// This enables using the foreign object with a display API such as KMS. + /// + /// The GBM bo shares the underlying pixels but its life-time is + /// independent of the foreign object. + #[cfg(feature = "import-wayland")] + pub fn import_buffer_object_from_wayland<U: 'static>( + &self, + buffer: &WlBuffer, + usage: BufferObjectFlags, + ) -> IoResult<BufferObject<U>> { + use wayland_server::Resource; + + let ptr = unsafe { + ffi::gbm_bo_import( + *self.ffi, + ffi::GBM_BO_IMPORT_WL_BUFFER as u32, + buffer.id().as_ptr() as *mut _, + usage.bits(), + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Create a GBM buffer object from an egl buffer + /// + /// This function imports a foreign [`EGLImage`] object and creates a new GBM + /// buffer object for it. + /// This enables using the foreign object with a display API such as KMS. + /// + /// The GBM bo shares the underlying pixels but its life-time is + /// independent of the foreign object. + /// + /// # Safety + /// + /// The given [`EGLImage`] is a raw pointer. Passing null or an invalid [`EGLImage`] will + /// cause undefined behavior. + #[cfg(feature = "import-egl")] + pub unsafe fn import_buffer_object_from_egl<U: 'static>( + &self, + buffer: EGLImage, + usage: BufferObjectFlags, + ) -> IoResult<BufferObject<U>> { + let ptr = ffi::gbm_bo_import( + *self.ffi, + ffi::GBM_BO_IMPORT_EGL_IMAGE as u32, + buffer, + usage.bits(), + ); + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(BufferObject::new(ptr, self.ffi.downgrade())) + } + } + + /// Create a GBM buffer object from a dma buffer + /// + /// This function imports a foreign dma buffer from an open file descriptor + /// and creates a new GBM buffer object for it. + /// This enables using the foreign object with a display API such as KMS. + /// + /// The GBM bo shares the underlying pixels but its life-time is + /// independent of the foreign object. + pub fn import_buffer_object_from_dma_buf<U: 'static>( + &self, + buffer: BorrowedFd<'_>, + width: u32, + height: u32, + stride: u32, + format: Format, + usage: BufferObjectFlags, + ) -> IoResult<BufferObject<U>> { + let mut fd_data = ffi::gbm_import_fd_data { + fd: buffer.as_raw_fd(), + width, + height, + stride, + format: format as u32, + }; + + let ptr = unsafe { + ffi::gbm_bo_import( + *self.ffi, + ffi::GBM_BO_IMPORT_FD as u32, + &mut fd_data as *mut ffi::gbm_import_fd_data as *mut _, + usage.bits(), + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } + + /// Create a GBM buffer object from a dma buffer with explicit modifiers + /// + /// This function imports a foreign dma buffer from an open file descriptor + /// and creates a new GBM buffer object for it. + /// This enables using the foreign object with a display API such as KMS. + /// + /// The GBM bo shares the underlying pixels but its life-time is + /// independent of the foreign object. + #[allow(clippy::too_many_arguments)] + pub fn import_buffer_object_from_dma_buf_with_modifiers<U: 'static>( + &self, + len: u32, + buffers: [Option<BorrowedFd<'_>>; 4], + width: u32, + height: u32, + format: Format, + usage: BufferObjectFlags, + strides: [i32; 4], + offsets: [i32; 4], + modifier: Modifier, + ) -> IoResult<BufferObject<U>> { + let fds = buffers.map(|fd| fd.map_or(-1, |x| x.as_raw_fd())); + let mut fd_data = ffi::gbm_import_fd_modifier_data { + fds, + width, + height, + format: format as u32, + strides, + offsets, + modifier: modifier.into(), + num_fds: len, + }; + + let ptr = unsafe { + ffi::gbm_bo_import( + *self.ffi, + ffi::GBM_BO_IMPORT_FD_MODIFIER as u32, + &mut fd_data as *mut ffi::gbm_import_fd_modifier_data as *mut _, + usage.bits(), + ) + }; + if ptr.is_null() { + Err(IoError::last_os_error()) + } else { + Ok(unsafe { BufferObject::new(ptr, self.ffi.downgrade()) }) + } + } +} + +#[cfg(feature = "drm-support")] +impl<T: DrmDevice + AsFd> DrmDevice for Device<T> {} + +#[cfg(feature = "drm-support")] +impl<T: DrmControlDevice + AsFd> DrmControlDevice for Device<T> {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Thrown when the underlying GBM device was already destroyed +pub struct DeviceDestroyedError; + +impl fmt::Display for DeviceDestroyedError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "The underlying GBM device was already destroyed") + } +} + +impl error::Error for DeviceDestroyedError { + fn cause(&self) -> Option<&dyn error::Error> { + None + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7d0c8cf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,209 @@ +//! # Safe `libgbm` bindings for [rust](https://www.rust-lang.org) +//! +//! The Generic Buffer Manager +//! +//! This module provides an abstraction that the caller can use to request a +//! buffer from the underlying memory management system for the platform. +//! +//! This allows the creation of portable code whilst still allowing access to +//! the underlying memory manager. +//! +//! This library is best used in combination with [`drm-rs`](https://github.com/Smithay/drm-rs), +//! provided through the `drm-support` feature. +//! +//! ## Example +//! +//! ```rust,no_run +//! # extern crate drm; +//! # extern crate gbm; +//! # use drm::control::connector::Info as ConnectorInfo; +//! # use drm::control::Mode; +//! use drm::control::{self, crtc, framebuffer}; +//! use gbm::{BufferObjectFlags, Device, Format}; +//! +//! # use std::fs::{File, OpenOptions}; +//! # use std::os::unix::io::{AsFd, BorrowedFd}; +//! # +//! # use drm::control::Device as ControlDevice; +//! # use drm::Device as BasicDevice; +//! # struct Card(File); +//! # +//! # impl AsFd for Card { +//! # fn as_fd(&self) -> BorrowedFd { +//! # self.0.as_fd() +//! # } +//! # } +//! # +//! # impl BasicDevice for Card {} +//! # impl ControlDevice for Card {} +//! # +//! # fn init_drm_device() -> Card { +//! # let mut options = OpenOptions::new(); +//! # options.read(true); +//! # options.write(true); +//! # let file = options.open("/dev/dri/card0").unwrap(); +//! # Card(file) +//! # } +//! # fn main() { +//! // ... init your drm device ... +//! let drm = init_drm_device(); +//! +//! // init a GBM device +//! let gbm = Device::new(drm).unwrap(); +//! +//! // create a 4x4 buffer +//! let mut bo = gbm +//! .create_buffer_object::<()>( +//! 1280, +//! 720, +//! Format::Argb8888, +//! BufferObjectFlags::SCANOUT | BufferObjectFlags::WRITE, +//! ) +//! .unwrap(); +//! // write something to it (usually use import or egl rendering instead) +//! let buffer = { +//! let mut buffer = Vec::new(); +//! for i in 0..1280 { +//! for _ in 0..720 { +//! buffer.push(if i % 2 == 0 { 0 } else { 255 }); +//! } +//! } +//! buffer +//! }; +//! bo.write(&buffer).unwrap(); +//! +//! // create a framebuffer from our buffer +//! let fb = gbm.add_framebuffer(&bo, 32, 32).unwrap(); +//! +//! # let res_handles = gbm.resource_handles().unwrap(); +//! # let con = *res_handles.connectors().iter().next().unwrap(); +//! # let crtc_handle = *res_handles.crtcs().iter().next().unwrap(); +//! # let connector_info: ConnectorInfo = gbm.get_connector(con, false).unwrap(); +//! # let mode: Mode = connector_info.modes()[0]; +//! # +//! // display it (and get a crtc, mode and connector before) +//! gbm.set_crtc(crtc_handle, Some(fb), (0, 0), &[con], Some(mode)) +//! .unwrap(); +//! # } +//! ``` +#![warn(missing_debug_implementations)] +#![deny(missing_docs)] + +extern crate gbm_sys as ffi; +extern crate libc; + +#[cfg(feature = "import-wayland")] +extern crate wayland_server; + +#[cfg(feature = "drm-support")] +extern crate drm; + +extern crate drm_fourcc; + +#[macro_use] +extern crate bitflags; + +mod buffer_object; +mod device; +mod surface; + +pub use self::buffer_object::*; +pub use self::device::*; +pub use self::surface::*; +pub use drm_fourcc::{DrmFourcc as Format, DrmModifier as Modifier}; + +use std::fmt; +use std::sync::{Arc, Weak}; + +/// Trait for types that allow to obtain the underlying raw libinput pointer. +pub trait AsRaw<T> { + /// Receive a raw pointer representing this type. + fn as_raw(&self) -> *const T; + + #[doc(hidden)] + fn as_raw_mut(&self) -> *mut T { + self.as_raw() as *mut _ + } +} + +struct PtrDrop<T>(*mut T, Option<Box<dyn FnOnce(*mut T) + Send + 'static>>); + +impl<T> Drop for PtrDrop<T> { + fn drop(&mut self) { + (self.1.take().unwrap())(self.0); + } +} + +#[derive(Clone)] +pub(crate) struct Ptr<T>(Arc<PtrDrop<T>>); +// SAFETY: The types used with Ptr in this crate are all Send (namely gbm_device, gbm_surface and gbm_bo). +// The type is private and can thus not be used unsoundly by other crates. +unsafe impl<T> Send for Ptr<T> {} + +impl<T> Ptr<T> { + fn new<F: FnOnce(*mut T) + Send + 'static>(ptr: *mut T, destructor: F) -> Ptr<T> { + Ptr(Arc::new(PtrDrop(ptr, Some(Box::new(destructor))))) + } + + fn downgrade(&self) -> WeakPtr<T> { + WeakPtr(Arc::downgrade(&self.0)) + } +} + +impl<T> std::ops::Deref for Ptr<T> { + type Target = *mut T; + + fn deref(&self) -> &Self::Target { + &(self.0).0 + } +} + +impl<T> fmt::Pointer for Ptr<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Pointer::fmt(&self.0 .0, f) + } +} + +#[derive(Clone)] +pub(crate) struct WeakPtr<T>(Weak<PtrDrop<T>>); + +impl<T> WeakPtr<T> { + fn upgrade(&self) -> Option<Ptr<T>> { + self.0.upgrade().map(Ptr) + } +} + +impl<T> fmt::Pointer for WeakPtr<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self.upgrade() { + Some(x) => fmt::Pointer::fmt(&x, f), + None => fmt::Pointer::fmt(&std::ptr::null::<T>(), f), + } + } +} + +unsafe impl<T> Send for WeakPtr<T> where Ptr<T>: Send {} + +#[cfg(test)] +mod test { + use std::os::unix::io::OwnedFd; + + fn is_send<T: Send>() {} + + #[test] + fn device_is_send() { + is_send::<super::Device<std::fs::File>>(); + is_send::<super::Device<OwnedFd>>(); + } + + #[test] + fn surface_is_send() { + is_send::<super::Surface<std::fs::File>>(); + is_send::<super::Surface<OwnedFd>>(); + } + + #[test] + fn unmapped_bo_is_send() { + is_send::<super::BufferObject<()>>(); + } +} diff --git a/src/surface.rs b/src/surface.rs new file mode 100644 index 0000000..f8040d2 --- /dev/null +++ b/src/surface.rs @@ -0,0 +1,125 @@ +use crate::{AsRaw, BufferObject, DeviceDestroyedError, Ptr, WeakPtr}; +use std::error; +use std::fmt; +use std::marker::PhantomData; + +/// A GBM rendering surface +pub struct Surface<T: 'static> { + ffi: Ptr<ffi::gbm_surface>, + _device: WeakPtr<ffi::gbm_device>, + _bo_userdata: PhantomData<T>, +} + +impl<T: 'static> fmt::Debug for Surface<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Surface") + .field("ptr", &format_args!("{:p}", &self.ffi)) + .field("device", &format_args!("{:p}", &self._device)) + .finish() + } +} + +/// Errors that may happen when locking the front buffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FrontBufferError { + /// No free buffers are currently available + NoFreeBuffers, + /// An unknown error happened + Unknown, + /// Device was already released + Destroyed(DeviceDestroyedError), +} + +impl fmt::Display for FrontBufferError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + FrontBufferError::NoFreeBuffers => write!(f, "No free buffers remaining"), + FrontBufferError::Unknown => write!(f, "Unknown error"), + FrontBufferError::Destroyed(ref err) => write!(f, "Buffer was destroyed: {}", err), + } + } +} + +impl error::Error for FrontBufferError { + fn cause(&self) -> Option<&dyn error::Error> { + match *self { + FrontBufferError::Destroyed(ref err) => Some(err), + _ => None, + } + } +} + +impl<T: 'static> Surface<T> { + /// Return whether or not a surface has free (non-locked) buffers + /// + /// Before starting a new frame, the surface must have a buffer + /// available for rendering. Initially, a GBM surface will have a free + /// buffer, but after one or more buffers + /// [have been locked](Self::lock_front_buffer()), + /// the application must check for a free buffer before rendering. + pub fn has_free_buffers(&self) -> bool { + let device = self._device.upgrade(); + if device.is_some() { + unsafe { ffi::gbm_surface_has_free_buffers(*self.ffi) != 0 } + } else { + false + } + } + + /// Lock the surface's current front buffer + /// + /// Locks rendering to the surface's current front buffer and returns + /// a handle to the underlying [`BufferObject`]. + /// + /// If an error occurs a [`FrontBufferError`] is returned. + /// + /// # Safety + /// This function must be called exactly once after calling + /// `eglSwapBuffers`. Calling it before any `eglSwapBuffers` has happened + /// on the surface or two or more times after `eglSwapBuffers` is an + /// error and may cause undefined behavior. + pub unsafe fn lock_front_buffer(&self) -> Result<BufferObject<T>, FrontBufferError> { + let device = self._device.upgrade(); + if device.is_some() { + if ffi::gbm_surface_has_free_buffers(*self.ffi) != 0 { + let buffer_ptr = ffi::gbm_surface_lock_front_buffer(*self.ffi); + if !buffer_ptr.is_null() { + let surface_ptr = self.ffi.downgrade(); + let buffer = BufferObject { + ffi: Ptr::new(buffer_ptr, move |ptr| { + if let Some(surface) = surface_ptr.upgrade() { + ffi::gbm_surface_release_buffer(*surface, ptr); + } + }), + _device: self._device.clone(), + _userdata: std::marker::PhantomData, + }; + Ok(buffer) + } else { + Err(FrontBufferError::Unknown) + } + } else { + Err(FrontBufferError::NoFreeBuffers) + } + } else { + Err(FrontBufferError::Destroyed(DeviceDestroyedError)) + } + } + + pub(crate) unsafe fn new( + ffi: *mut ffi::gbm_surface, + device: WeakPtr<ffi::gbm_device>, + ) -> Surface<T> { + Surface { + ffi: Ptr::new(ffi, |ptr| ffi::gbm_surface_destroy(ptr)), + _device: device, + _bo_userdata: PhantomData, + } + } +} + +impl<T: 'static> AsRaw<ffi::gbm_surface> for Surface<T> { + fn as_raw(&self) -> *const ffi::gbm_surface { + *self.ffi + } +} |