diff options
Diffstat (limited to 'gpu_display/src/gpu_display_x.rs')
-rw-r--r-- | gpu_display/src/gpu_display_x.rs | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs new file mode 100644 index 0000000..62ee505 --- /dev/null +++ b/gpu_display/src/gpu_display_x.rs @@ -0,0 +1,647 @@ +// Copyright 2019 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. + +#[path = "generated/xlib.rs"] +#[allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] +mod xlib; + +use std::collections::BTreeMap; +use std::ffi::{c_void, CStr, CString}; +use std::mem::{transmute_copy, zeroed}; +use std::num::NonZeroU32; +use std::os::raw::c_ulong; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::ptr::{null, null_mut, NonNull}; +use std::rc::Rc; + +use libc::{shmat, shmctl, shmdt, shmget, IPC_CREAT, IPC_PRIVATE, IPC_RMID}; + +use crate::{DisplayT, GpuDisplayError, GpuDisplayFramebuffer}; + +use data_model::VolatileSlice; + +const BUFFER_COUNT: usize = 2; + +type SurfaceId = NonZeroU32; + +/// A wrapper for XFree that takes any type. +unsafe fn x_free<T>(t: *mut T) { + xlib::XFree(t as *mut c_void); +} + +#[derive(Clone)] +struct XDisplay(Rc<NonNull<xlib::Display>>); +impl Drop for XDisplay { + fn drop(&mut self) { + if Rc::strong_count(&self.0) == 1 { + unsafe { + xlib::XCloseDisplay(self.as_ptr()); + } + } + } +} + +impl XDisplay { + fn as_ptr(&self) -> *mut xlib::Display { + self.0.as_ptr() + } + + /// Returns true of the XShm extension is supported on this display. + fn supports_shm(&self) -> bool { + unsafe { xlib::XShmQueryExtension(self.as_ptr()) != 0 } + } + + /// Gets the default screen of this display. + fn default_screen(&self) -> Option<XScreen> { + Some(XScreen(NonNull::new(unsafe { + xlib::XDefaultScreenOfDisplay(self.as_ptr()) + })?)) + } + + /// Returns true if there are events that are on the queue. + fn pending_events(&self) -> bool { + unsafe { xlib::XPending(self.as_ptr()) != 0 } + } + + /// Sends any pending commands to the X server. + fn flush(&self) { + unsafe { + xlib::XFlush(self.as_ptr()); + } + } + + /// Blocks until the next event from the display is received and returns that event. + /// + /// Always flush before using this if any X commands where issued. + fn next_event(&self) -> XEvent { + unsafe { + let mut ev = zeroed(); + xlib::XNextEvent(self.as_ptr(), &mut ev); + ev.into() + } + } +} + +impl AsRawFd for XDisplay { + fn as_raw_fd(&self) -> RawFd { + unsafe { xlib::XConnectionNumber(self.as_ptr()) } + } +} + +struct XEvent(xlib::XEvent); +impl From<xlib::XEvent> for XEvent { + fn from(ev: xlib::XEvent) -> XEvent { + XEvent(ev) + } +} + +impl XEvent { + fn any(&self) -> xlib::XAnyEvent { + // All events have the same xany field. + unsafe { self.0.xany } + } + + fn type_(&self) -> u32 { + // All events have the same type_ field. + unsafe { self.0.type_ as u32 } + } + + fn window(&self) -> xlib::Window { + self.any().window + } + + // Some of the event types are dynamic so they need to be passed in. + fn as_enum(&self, shm_complete_type: u32) -> XEventEnum { + match self.type_() { + xlib::Expose => XEventEnum::Expose, + xlib::ClientMessage => { + XEventEnum::ClientMessage(unsafe { self.0.xclient.data.l[0] as u64 }) + } + t if t == shm_complete_type => { + // Because XShmCompletionEvent is not part of the XEvent union, simulate a union + // with transmute_copy. If the shm_complete_type turns out to be bogus, some of the + // data would be incorrect, but the common event fields would still be valid. + let ev_completion: xlib::XShmCompletionEvent = unsafe { transmute_copy(&self.0) }; + XEventEnum::ShmCompletionEvent(ev_completion.shmseg) + } + _ => XEventEnum::Unhandled, + } + } +} + +enum XEventEnum { + Expose, + ClientMessage(u64), + ShmCompletionEvent(xlib::ShmSeg), + // We don't care about most kinds of events, + Unhandled, +} + +struct XScreen(NonNull<xlib::Screen>); + +impl XScreen { + fn as_ptr(&self) -> *mut xlib::Screen { + self.0.as_ptr() + } + + /// Gets the screen number of this screen. + fn get_number(&self) -> i32 { + unsafe { xlib::XScreenNumberOfScreen(self.as_ptr()) } + } +} + +struct Buffer { + display: XDisplay, + image: *mut xlib::XImage, + /// The documentation says XShmSegmentInfo must last at least as long as the XImage, which + /// probably precludes moving it as well. + segment_info: Box<xlib::XShmSegmentInfo>, + size: usize, + in_use: bool, +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + xlib::XShmDetach(self.display.as_ptr(), self.segment_info.as_mut()); + xlib::XDestroyImage(self.image); + shmdt(self.segment_info.shmaddr as *const _); + shmctl(self.segment_info.shmid, IPC_RMID, null_mut()); + } + } +} + +impl Buffer { + fn as_volatile_slice(&self) -> VolatileSlice { + unsafe { VolatileSlice::new(self.segment_info.shmaddr as *mut _, self.size as u64) } + } + + fn stride(&self) -> usize { + unsafe { (*self.image).bytes_per_line as usize } + } + + fn bytes_per_pixel(&self) -> usize { + let bytes_per_pixel = unsafe { (*self.image).bits_per_pixel / 8 }; + bytes_per_pixel as usize + } +} + +// Surfaces here are equivalent to XWindows. +struct Surface { + display: XDisplay, + visual: *mut xlib::Visual, + depth: u32, + window: xlib::Window, + gc: xlib::GC, + width: u32, + height: u32, + + // Fields for handling the buffer swap chain. + buffers: [Option<Buffer>; BUFFER_COUNT], + buffer_next: usize, + buffer_completion_type: u32, + + // Fields for handling window close requests + delete_window_atom: c_ulong, + close_requested: bool, +} + +impl Surface { + fn create( + display: XDisplay, + screen: &XScreen, + visual: *mut xlib::Visual, + width: u32, + height: u32, + ) -> Result<Surface, GpuDisplayError> { + unsafe { + let depth = xlib::XDefaultDepthOfScreen(screen.as_ptr()) as u32; + + let black_pixel = xlib::XBlackPixelOfScreen(screen.as_ptr()); + + let window = xlib::XCreateSimpleWindow( + display.as_ptr(), + xlib::XRootWindowOfScreen(screen.as_ptr()), + 0, + 0, + width, + height, + 1, + black_pixel, + black_pixel, + ); + + let gc = xlib::XCreateGC(display.as_ptr(), window, 0, null_mut()); + + // Because the event is from an extension, its type must be calculated dynamically. + let buffer_completion_type = + xlib::XShmGetEventBase(display.as_ptr()) as u32 + xlib::ShmCompletion; + + // Mark this window as responding to close requests. + let mut delete_window_atom = xlib::XInternAtom( + display.as_ptr(), + CStr::from_bytes_with_nul(b"WM_DELETE_WINDOW\0") + .unwrap() + .as_ptr(), + 0, + ); + xlib::XSetWMProtocols(display.as_ptr(), window, &mut delete_window_atom, 1); + + let size_hints = xlib::XAllocSizeHints(); + (*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64; + (*size_hints).max_width = width as i32; + (*size_hints).min_width = width as i32; + (*size_hints).max_height = height as i32; + (*size_hints).min_height = height as i32; + xlib::XSetWMNormalHints(display.as_ptr(), window, size_hints); + x_free(size_hints); + + // We will use redraw the buffer when we are exposed. + xlib::XSelectInput(display.as_ptr(), window, xlib::ExposureMask as i64); + + xlib::XClearWindow(display.as_ptr(), window); + xlib::XMapRaised(display.as_ptr(), window); + + // Flush everything so that the window is visible immediately. + display.flush(); + + Ok(Surface { + display, + visual, + depth, + window, + gc, + width, + height, + buffers: Default::default(), + buffer_next: 0, + buffer_completion_type, + delete_window_atom, + close_requested: false, + }) + } + } + + /// Returns index of the current (on-screen) buffer, or 0 if there are no buffers. + fn current_buffer(&self) -> usize { + match self.buffer_next.checked_sub(1) { + Some(i) => i, + None => self.buffers.len() - 1, + } + } + + fn handle_event(&mut self, ev: XEvent) { + match ev.as_enum(self.buffer_completion_type) { + XEventEnum::Expose => self.draw_buffer(self.current_buffer()), + XEventEnum::ClientMessage(xclient_data) => { + if xclient_data == self.delete_window_atom { + self.close_requested = true; + } + } + XEventEnum::ShmCompletionEvent(shmseg) => { + // Find the buffer associated with this event and mark it as not in use. + for buffer_opt in self.buffers.iter_mut() { + if let Some(buffer) = buffer_opt { + if buffer.segment_info.shmseg == shmseg { + buffer.in_use = false; + } + } + } + } + XEventEnum::Unhandled => {} + } + } + + /// Draws the indicated buffer onto the screen. + fn draw_buffer(&mut self, buffer_index: usize) { + let buffer = match self.buffers.get_mut(buffer_index) { + Some(Some(b)) => b, + _ => { + // If there is no buffer, that means the framebuffer was never set and we should + // simply blank the window with arbitrary contents. + unsafe { + xlib::XClearWindow(self.display.as_ptr(), self.window); + } + return; + } + }; + // Mark the buffer as in use. When the XShmCompletionEvent occurs, this will get marked + // false. + buffer.in_use = true; + unsafe { + xlib::XShmPutImage( + self.display.as_ptr(), + self.window, + self.gc, + buffer.image, + 0, // src x + 0, // src y + 0, // dst x + 0, // dst y + self.width, + self.height, + true as i32, /* send XShmCompletionEvent event */ + ); + self.display.flush(); + } + } + + /// Gets the buffer at buffer_index, allocating it if necessary. + fn lazily_allocate_buffer(&mut self, buffer_index: usize) -> Option<&Buffer> { + if buffer_index >= self.buffers.len() { + return None; + } + + if self.buffers[buffer_index].is_some() { + return self.buffers[buffer_index].as_ref(); + } + // The buffer_index is valid and the buffer was never created, so we create it now. + unsafe { + // The docs for XShmCreateImage imply that XShmSegmentInfo must be allocated to live at + // least as long as the XImage, which probably means it can't move either. Use a Box in + // order to fulfill those requirements. + let mut segment_info: Box<xlib::XShmSegmentInfo> = Box::new(zeroed()); + let image = xlib::XShmCreateImage( + self.display.as_ptr(), + self.visual, + self.depth, + xlib::ZPixmap as i32, + null_mut(), + segment_info.as_mut(), + self.width, + self.height, + ); + if image.is_null() { + return None; + } + let size = (*image) + .bytes_per_line + .checked_mul((*image).height) + .unwrap(); + segment_info.shmid = shmget(IPC_PRIVATE, size as usize, IPC_CREAT | 0o777); + if segment_info.shmid == -1 { + xlib::XDestroyImage(image); + return None; + } + segment_info.shmaddr = shmat(segment_info.shmid, null_mut(), 0) as *mut _; + if segment_info.shmaddr == (-1isize) as *mut _ { + xlib::XDestroyImage(image); + shmctl(segment_info.shmid, IPC_RMID, null_mut()); + return None; + } + (*image).data = segment_info.shmaddr; + segment_info.readOnly = true as i32; + xlib::XShmAttach(self.display.as_ptr(), segment_info.as_mut()); + self.buffers[buffer_index] = Some(Buffer { + display: self.display.clone(), + image, + segment_info, + size: size as usize, + in_use: false, + }); + self.buffers[buffer_index].as_ref() + } + } + + /// Gets the next framebuffer, allocating if necessary. + fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> { + // Framebuffers are lazily allocated. If the next buffer is not in self.buffers, add it + // using push_new_buffer and then get its memory. + let framebuffer = self.lazily_allocate_buffer(self.buffer_next)?; + let bytes_per_pixel = framebuffer.bytes_per_pixel() as u32; + Some(GpuDisplayFramebuffer::new( + framebuffer.as_volatile_slice(), + framebuffer.stride() as u32, + bytes_per_pixel, + )) + } + + /// True if the next buffer is in use because of an XShmPutImage call. + fn next_buffer_in_use(&self) -> bool { + // Buffers that have not yet been made are not in use, hence unwrap_or(false). + self.buffers + .get(self.buffer_next) + .and_then(|b| Some(b.as_ref()?.in_use)) + .unwrap_or(false) + } + + /// Puts the next buffer onto the screen and sets the next buffer in the swap chain. + fn flip(&mut self) { + let current_buffer_index = self.buffer_next; + self.buffer_next = (self.buffer_next + 1) % self.buffers.len(); + self.draw_buffer(current_buffer_index); + } +} + +impl Drop for Surface { + fn drop(&mut self) { + // Safe given it should always be of the correct type. + unsafe { + xlib::XFreeGC(self.display.as_ptr(), self.gc); + xlib::XDestroyWindow(self.display.as_ptr(), self.window); + } + } +} + +pub struct DisplayX { + display: XDisplay, + screen: XScreen, + visual: *mut xlib::Visual, + next_surface_id: SurfaceId, + surfaces: BTreeMap<SurfaceId, Surface>, +} + +impl DisplayX { + pub fn open_display(display: Option<&str>) -> Result<DisplayX, GpuDisplayError> { + let display_cstr = match display.map(|s| CString::new(s)) { + Some(Ok(s)) => Some(s), + Some(Err(_)) => return Err(GpuDisplayError::InvalidPath), + None => None, + }; + + unsafe { + // Open the display + let display = match NonNull::new(xlib::XOpenDisplay( + display_cstr + .as_ref() + .map(|s| CStr::as_ptr(s)) + .unwrap_or(null()), + )) { + Some(display_ptr) => XDisplay(Rc::new(display_ptr)), + None => return Err(GpuDisplayError::Connect), + }; + + // Check for required extension. + if !display.supports_shm() { + return Err(GpuDisplayError::RequiredFeature("xshm extension")); + } + + let screen = display + .default_screen() + .ok_or(GpuDisplayError::Connect) + .unwrap(); + let screen_number = screen.get_number(); + + // Check for and save required visual (24-bit BGR for the default screen). + let mut visual_info_template = xlib::XVisualInfo { + visual: null_mut(), + visualid: 0, + screen: screen_number, + depth: 24, + class: 0, + red_mask: 0x00ff0000, + green_mask: 0x0000ff00, + blue_mask: 0x000000ff, + colormap_size: 0, + bits_per_rgb: 0, + }; + let visual_info = xlib::XGetVisualInfo( + display.as_ptr(), + (xlib::VisualScreenMask + | xlib::VisualDepthMask + | xlib::VisualRedMaskMask + | xlib::VisualGreenMaskMask + | xlib::VisualBlueMaskMask) as i64, + &mut visual_info_template, + &mut 0, + ); + if visual_info.is_null() { + return Err(GpuDisplayError::RequiredFeature("no matching visual")); + } + let visual = (*visual_info).visual; + x_free(visual_info); + + Ok(DisplayX { + display, + screen, + visual, + next_surface_id: SurfaceId::new(1).unwrap(), + surfaces: Default::default(), + }) + } + } + + fn surface_ref(&self, surface_id: u32) -> Option<&Surface> { + SurfaceId::new(surface_id).and_then(move |id| self.surfaces.get(&id)) + } + + fn surface_mut(&mut self, surface_id: u32) -> Option<&mut Surface> { + SurfaceId::new(surface_id).and_then(move |id| self.surfaces.get_mut(&id)) + } + + fn handle_event(&mut self, ev: XEvent) { + let window = ev.window(); + for surface in self.surfaces.values_mut() { + if surface.window != window { + continue; + } + surface.handle_event(ev); + return; + } + } +} + +impl DisplayT for DisplayX { + fn dispatch_events(&mut self) { + loop { + self.display.flush(); + if !self.display.pending_events() { + break; + } + let ev = self.display.next_event(); + self.handle_event(ev); + } + } + + fn create_surface( + &mut self, + parent_surface_id: Option<u32>, + width: u32, + height: u32, + ) -> Result<u32, GpuDisplayError> { + if parent_surface_id.is_some() { + return Err(GpuDisplayError::Unsupported); + } + + let new_surface = Surface::create( + self.display.clone(), + &self.screen, + self.visual, + width, + height, + )?; + let new_surface_id = self.next_surface_id; + self.surfaces.insert(new_surface_id, new_surface); + self.next_surface_id = SurfaceId::new(self.next_surface_id.get() + 1).unwrap(); + + Ok(new_surface_id.get()) + } + + fn release_surface(&mut self, surface_id: u32) { + SurfaceId::new(surface_id).and_then(|id| self.surfaces.remove(&id)); + } + + fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> { + self.surface_mut(surface_id).and_then(|s| s.framebuffer()) + } + + fn next_buffer_in_use(&self, surface_id: u32) -> bool { + self.surface_ref(surface_id) + .map(|s| s.next_buffer_in_use()) + .unwrap_or(false) + } + + fn flip(&mut self, surface_id: u32) { + if let Some(surface) = self.surface_mut(surface_id) { + surface.flip() + } + } + + fn close_requested(&self, surface_id: u32) -> bool { + self.surface_ref(surface_id) + .map(|s| s.close_requested) + .unwrap_or(true) + } + + #[allow(unused_variables)] + fn import_dmabuf( + &mut self, + fd: RawFd, + offset: u32, + stride: u32, + modifiers: u64, + width: u32, + height: u32, + fourcc: u32, + ) -> Result<u32, GpuDisplayError> { + return Err(GpuDisplayError::Unsupported); + } + #[allow(unused_variables)] + fn release_import(&mut self, import_id: u32) { + // unsupported + } + #[allow(unused_variables)] + fn commit(&mut self, surface_id: u32) { + // unsupported + } + #[allow(unused_variables)] + fn flip_to(&mut self, surface_id: u32, import_id: u32) { + // unsupported + } + #[allow(unused_variables)] + fn set_position(&mut self, surface_id: u32, x: u32, y: u32) { + // unsupported + } +} + +impl AsRawFd for DisplayX { + fn as_raw_fd(&self) -> RawFd { + self.display.as_raw_fd() + } +} |