| // 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. |
| |
| //! Crate for displaying simple surfaces and GPU buffers over wayland. |
| |
| extern crate data_model; |
| extern crate sys_util; |
| |
| mod dwl; |
| |
| use std::cell::Cell; |
| use std::collections::HashMap; |
| use std::ffi::CStr; |
| use std::os::unix::io::{AsRawFd, RawFd}; |
| use std::ptr::null_mut; |
| |
| use data_model::{VolatileSlice, VolatileMemory}; |
| use sys_util::{Error as SysError, SharedMemory, MemoryMapping, round_up_to_page_size}; |
| |
| use dwl::*; |
| |
| const BUFFER_COUNT: usize = 2; |
| const BYTES_PER_PIXEL: u32 = 4; |
| |
| /// An error generated by `GpuDisplay`. |
| #[derive(Debug)] |
| pub enum GpuDisplayError { |
| /// An internal allocation failed. |
| Allocate, |
| /// Connecting to the compositor failed. |
| Connect, |
| /// Creating shared memory failed. |
| CreateShm(SysError), |
| /// Setting the size of shared memory failed. |
| SetSize(SysError), |
| /// Failed to create a surface on the compositor. |
| CreateSurface, |
| /// Failed to import a buffer to the compositor. |
| FailedImport, |
| /// The surface ID is invalid. |
| InvalidSurfaceId, |
| } |
| |
| struct DwlContext(*mut dwl_context); |
| impl Drop for DwlContext { |
| fn drop(&mut self) { |
| if !self.0.is_null() { |
| // Safe given that we checked the pointer for non-null and it should always be of the |
| // correct type. |
| unsafe { |
| dwl_context_destroy(&mut self.0); |
| } |
| } |
| } |
| } |
| |
| struct DwlDmabuf(*mut dwl_dmabuf); |
| impl Drop for DwlDmabuf { |
| fn drop(&mut self) { |
| if !self.0.is_null() { |
| // Safe given that we checked the pointer for non-null and it should always be of the |
| // correct type. |
| unsafe { |
| dwl_dmabuf_destroy(&mut self.0); |
| } |
| } |
| } |
| } |
| |
| struct DwlSurface(*mut dwl_surface); |
| impl Drop for DwlSurface { |
| fn drop(&mut self) { |
| if !self.0.is_null() { |
| // Safe given that we checked the pointer for non-null and it should always be of the |
| // correct type. |
| unsafe { |
| dwl_surface_destroy(&mut self.0); |
| } |
| } |
| } |
| } |
| |
| struct GpuDisplaySurface { |
| surface: DwlSurface, |
| buffer_size: usize, |
| buffer_index: Cell<usize>, |
| buffer_mem: MemoryMapping, |
| } |
| |
| impl GpuDisplaySurface { |
| fn surface(&self) -> *mut dwl_surface { |
| self.surface.0 |
| } |
| } |
| |
| /// A connection to the compositor and associated collection of state. |
| /// |
| /// The user of `GpuDisplay` can use `AsRawFd` to poll on the compositor connection's file |
| /// descriptor. When the connection is readable, `dispatch_events` can be called to process it. |
| pub struct GpuDisplay { |
| ctx: DwlContext, |
| dmabufs: HashMap<u32, DwlDmabuf>, |
| dmabuf_next_id: u32, |
| surfaces: HashMap<u32, GpuDisplaySurface>, |
| surface_next_id: u32, |
| } |
| |
| impl GpuDisplay { |
| /// Opens a fresh connection to the compositor. |
| pub fn new() -> Result<GpuDisplay, GpuDisplayError> { |
| // The dwl_context_new call should always be safe to call, and we check its result. |
| let ctx = DwlContext(unsafe { dwl_context_new() }); |
| if ctx.0.is_null() { |
| return Err(GpuDisplayError::Allocate); |
| } |
| // The dwl_context_setup call is always safe to call given that the supplied context is |
| // valid. and we check its result. |
| let setup_success = unsafe { dwl_context_setup(ctx.0) }; |
| if !setup_success { |
| return Err(GpuDisplayError::Connect); |
| } |
| |
| Ok(GpuDisplay { |
| ctx, |
| dmabufs: Default::default(), |
| dmabuf_next_id: 0, |
| surfaces: Default::default(), |
| surface_next_id: 0, |
| }) |
| } |
| |
| fn ctx(&self) -> *mut dwl_context { |
| self.ctx.0 |
| } |
| |
| fn get_surface(&self, surface_id: u32) -> Option<&GpuDisplaySurface> { |
| self.surfaces.get(&surface_id) |
| } |
| |
| /// Imports a dmabuf to the compositor for use as a surface buffer and returns a handle to it. |
| pub fn import_dmabuf(&mut self, |
| fd: RawFd, |
| offset: u32, |
| stride: u32, |
| modifiers: u64, |
| width: u32, |
| height: u32, |
| fourcc: u32) |
| -> Result<u32, GpuDisplayError> { |
| // Safe given that the context pointer is valid. Any other invalid parameters would be |
| // rejected by dwl_context_dmabuf_new safely. We check that the resulting dmabuf is valid |
| // before filing it away. |
| let dmabuf = DwlDmabuf(unsafe { |
| dwl_context_dmabuf_new(self.ctx(), |
| fd, |
| offset, |
| stride, |
| modifiers, |
| width, |
| height, |
| fourcc) |
| }); |
| if dmabuf.0.is_null() { |
| return Err(GpuDisplayError::FailedImport); |
| } |
| |
| let next_id = self.dmabuf_next_id; |
| self.dmabufs.insert(next_id, dmabuf); |
| self.dmabuf_next_id += 1; |
| Ok(next_id) |
| } |
| |
| pub fn import_in_use(&mut self, import_id: u32) -> bool { |
| match self.dmabufs.get(&import_id) { |
| // Safe because only a valid dmabuf is used. |
| Some(dmabuf) => unsafe { dwl_dmabuf_in_use(dmabuf.0) }, |
| None => { |
| debug_assert!(false, "invalid import_id {}", import_id); |
| false |
| } |
| } |
| } |
| |
| /// Releases a previously imported dmabuf identified by the given handle. |
| pub fn release_import(&mut self, import_id: u32) { |
| self.dmabufs.remove(&import_id); |
| } |
| |
| /// Dispatches internal events that were received from the compositor since the last call to |
| /// `dispatch_events`. |
| pub fn dispatch_events(&mut self) { |
| // Safe given that the context pointer is valid. |
| unsafe { |
| dwl_context_dispatch(self.ctx()); |
| } |
| } |
| |
| /// Creates a surface on the the compositor as either a top level window, or child of another |
| /// surface, returning a handle to the new surface. |
| pub fn create_surface(&mut self, |
| parent_surface_id: Option<u32>, |
| width: u32, |
| height: u32) |
| -> Result<u32, GpuDisplayError> { |
| let parent_ptr = match parent_surface_id { |
| Some(id) => { |
| match self.get_surface(id).map(|p| p.surface()) { |
| Some(ptr) => ptr, |
| None => return Err(GpuDisplayError::InvalidSurfaceId), |
| } |
| } |
| None => null_mut(), |
| }; |
| let row_size = width * BYTES_PER_PIXEL; |
| let fb_size = row_size * height; |
| let buffer_size = round_up_to_page_size(fb_size as usize * BUFFER_COUNT); |
| let mut buffer_shm = |
| SharedMemory::new(Some(CStr::from_bytes_with_nul(b"GpuDisplaySurface\0").unwrap())) |
| .map_err(GpuDisplayError::CreateShm)?; |
| buffer_shm |
| .set_size(buffer_size as u64) |
| .map_err(GpuDisplayError::SetSize)?; |
| let buffer_mem = MemoryMapping::from_fd(&buffer_shm, buffer_size).unwrap(); |
| |
| // Safe because only a valid context, parent pointer (if not None), and buffer FD are used. |
| // The returned surface is checked for validity before being filed away. |
| let surface = DwlSurface(unsafe { |
| dwl_context_surface_new(self.ctx(), |
| parent_ptr, |
| buffer_shm.as_raw_fd(), |
| buffer_size, |
| fb_size as usize, |
| width, |
| height, |
| row_size) |
| }); |
| |
| if surface.0.is_null() { |
| return Err(GpuDisplayError::CreateSurface); |
| } |
| |
| let next_id = self.surface_next_id; |
| self.surfaces |
| .insert(next_id, |
| GpuDisplaySurface { |
| surface, |
| buffer_size: fb_size as usize, |
| buffer_index: Cell::new(0), |
| buffer_mem, |
| }); |
| |
| self.surface_next_id += 1; |
| Ok(next_id) |
| } |
| |
| /// Releases a previously created surface identified by the given handle. |
| pub fn release_surface(&mut self, surface_id: u32) { |
| self.surfaces.remove(&surface_id); |
| } |
| |
| /// Gets a reference to an unused framebuffer for the identified surface. |
| pub fn framebuffer_memory(&self, surface_id: u32) -> Option<VolatileSlice> { |
| let surface = self.get_surface(surface_id)?; |
| let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; |
| surface |
| .buffer_mem |
| .get_slice((buffer_index * surface.buffer_size) as u64, |
| surface.buffer_size as u64) |
| .ok() |
| } |
| |
| /// Commits any pending state for the identified surface. |
| pub fn commit(&self, surface_id: u32) { |
| match self.get_surface(surface_id) { |
| Some(surface) => { |
| // Safe because only a valid surface is used. |
| unsafe { |
| dwl_surface_commit(surface.surface()); |
| } |
| } |
| None => debug_assert!(false, "invalid surface_id {}", surface_id), |
| } |
| } |
| |
| /// Returns true if the next buffer in the buffer queue for the given surface is currently in |
| /// use. |
| /// |
| /// If the next buffer is in use, the memory returned from `framebuffer_memory` should not be |
| /// written to. |
| pub fn next_buffer_in_use(&self, surface_id: u32) -> bool { |
| match self.get_surface(surface_id) { |
| Some(surface) => { |
| let next_buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; |
| // Safe because only a valid surface and buffer index is used. |
| unsafe { dwl_surface_buffer_in_use(surface.surface(), next_buffer_index) } |
| } |
| None => { |
| debug_assert!(false, "invalid surface_id {}", surface_id); |
| false |
| } |
| } |
| } |
| |
| /// Changes the visible contents of the identified surface to the contents of the framebuffer |
| /// last returned by `framebuffer_memory` for this surface. |
| pub fn flip(&self, surface_id: u32) { |
| match self.get_surface(surface_id) { |
| Some(surface) => { |
| surface |
| .buffer_index |
| .set((surface.buffer_index.get() + 1) % BUFFER_COUNT); |
| // Safe because only a valid surface and buffer index is used. |
| unsafe { |
| dwl_surface_flip(surface.surface(), surface.buffer_index.get()); |
| } |
| } |
| None => debug_assert!(false, "invalid surface_id {}", surface_id), |
| } |
| } |
| |
| /// Changes the visible contents of the identified surface to that of the identified imported |
| /// buffer. |
| pub fn flip_to(&self, surface_id: u32, import_id: u32) { |
| match self.get_surface(surface_id) { |
| Some(surface) => { |
| match self.dmabufs.get(&import_id) { |
| // Safe because only a valid surface and dmabuf is used. |
| Some(dmabuf) => unsafe { dwl_surface_flip_to(surface.surface(), dmabuf.0) }, |
| None => debug_assert!(false, "invalid import_id {}", import_id), |
| } |
| } |
| None => debug_assert!(false, "invalid surface_id {}", surface_id), |
| } |
| } |
| |
| /// Returns true if the identified top level surface has been told to close by the compositor, |
| /// and by extension the user. |
| pub fn close_requested(&self, surface_id: u32) -> bool { |
| match self.get_surface(surface_id) { |
| Some(surface) => |
| // Safe because only a valid surface is used. |
| unsafe { |
| dwl_surface_close_requested(surface.surface()) |
| }, |
| None => false |
| } |
| } |
| |
| /// Sets the position of the identified subsurface relative to its parent. |
| /// |
| /// The change in position will not be visible until `commit` is called for the parent surface. |
| pub fn set_position(&self, surface_id: u32, x: u32, y: u32) { |
| match self.get_surface(surface_id) { |
| Some(surface) => { |
| // Safe because only a valid surface is used. |
| unsafe { |
| dwl_surface_set_position(surface.surface(), x, y); |
| } |
| } |
| None => debug_assert!(false, "invalid surface_id {}", surface_id), |
| } |
| } |
| } |
| |
| impl Drop for GpuDisplay { |
| fn drop(&mut self) { |
| // Safe given that the context pointer is valid. |
| unsafe { dwl_context_destroy(&mut self.ctx.0) } |
| } |
| } |
| |
| impl AsRawFd for GpuDisplay { |
| fn as_raw_fd(&self) -> RawFd { |
| // Safe given that the context pointer is valid. |
| unsafe { dwl_context_fd(self.ctx.0) } |
| } |
| } |